• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 // Copyright 2017 The PDFium Authors
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4 
5 // Original code copyright 2014 Foxit Software Inc. http://www.foxitsoftware.com
6 
7 #include "core/fxge/dib/cstretchengine.h"
8 
9 #include <math.h>
10 
11 #include <algorithm>
12 #include <type_traits>
13 #include <utility>
14 
15 #include "core/fxcrt/fx_safe_types.h"
16 #include "core/fxcrt/fx_system.h"
17 #include "core/fxcrt/pauseindicator_iface.h"
18 #include "core/fxge/calculate_pitch.h"
19 #include "core/fxge/dib/cfx_dibbase.h"
20 #include "core/fxge/dib/cfx_dibitmap.h"
21 #include "core/fxge/dib/fx_dib.h"
22 #include "core/fxge/dib/scanlinecomposer_iface.h"
23 #include "third_party/base/check.h"
24 #include "third_party/base/cxx17_backports.h"
25 
26 static_assert(
27     std::is_trivially_destructible<CStretchEngine::PixelWeight>::value,
28     "PixelWeight storage may be re-used without invoking its destructor");
29 
30 // static
UseInterpolateBilinear(const FXDIB_ResampleOptions & options,int dest_width,int dest_height,int src_width,int src_height)31 bool CStretchEngine::UseInterpolateBilinear(
32     const FXDIB_ResampleOptions& options,
33     int dest_width,
34     int dest_height,
35     int src_width,
36     int src_height) {
37   return !options.bInterpolateBilinear && !options.bNoSmoothing &&
38          abs(dest_width) != 0 &&
39          abs(dest_height) / 8 <
40              static_cast<long long>(src_width) * src_height / abs(dest_width);
41 }
42 
43 // static
TotalBytesForWeightCount(size_t weight_count)44 size_t CStretchEngine::PixelWeight::TotalBytesForWeightCount(
45     size_t weight_count) {
46   // Always room for one weight even for empty ranges due to declaration
47   // of m_Weights[1] in the header. Don't shrink below this since
48   // CalculateWeights() relies on this later.
49   const size_t extra_weights = weight_count > 0 ? weight_count - 1 : 0;
50   FX_SAFE_SIZE_T total_bytes = extra_weights;
51   total_bytes *= sizeof(m_Weights[0]);
52   total_bytes += sizeof(PixelWeight);
53   return total_bytes.ValueOrDie();
54 }
55 
56 CStretchEngine::WeightTable::WeightTable() = default;
57 
58 CStretchEngine::WeightTable::~WeightTable() = default;
59 
CalculateWeights(int dest_len,int dest_min,int dest_max,int src_len,int src_min,int src_max,const FXDIB_ResampleOptions & options)60 bool CStretchEngine::WeightTable::CalculateWeights(
61     int dest_len,
62     int dest_min,
63     int dest_max,
64     int src_len,
65     int src_min,
66     int src_max,
67     const FXDIB_ResampleOptions& options) {
68   // 512MB should be large enough for this while preventing OOM.
69   static constexpr size_t kMaxTableBytesAllowed = 512 * 1024 * 1024;
70 
71   // Help the compiler realize that these can't change during a loop iteration:
72   const bool bilinear = options.bInterpolateBilinear;
73 
74   m_DestMin = 0;
75   m_ItemSizeBytes = 0;
76   m_WeightTablesSizeBytes = 0;
77   m_WeightTables.clear();
78   if (dest_len == 0)
79     return true;
80 
81   if (dest_min > dest_max)
82     return false;
83 
84   m_DestMin = dest_min;
85 
86   const double scale = static_cast<double>(src_len) / dest_len;
87   const double base = dest_len < 0 ? src_len : 0;
88   const size_t weight_count = static_cast<size_t>(ceil(fabs(scale))) + 1;
89   m_ItemSizeBytes = PixelWeight::TotalBytesForWeightCount(weight_count);
90 
91   const size_t dest_range = static_cast<size_t>(dest_max - dest_min);
92   const size_t kMaxTableItemsAllowed = kMaxTableBytesAllowed / m_ItemSizeBytes;
93   if (dest_range > kMaxTableItemsAllowed)
94     return false;
95 
96   m_WeightTablesSizeBytes = dest_range * m_ItemSizeBytes;
97   m_WeightTables.resize(m_WeightTablesSizeBytes);
98   if (options.bNoSmoothing || fabs(scale) < 1.0f) {
99     for (int dest_pixel = dest_min; dest_pixel < dest_max; ++dest_pixel) {
100       PixelWeight& pixel_weights = *GetPixelWeight(dest_pixel);
101       double src_pos = dest_pixel * scale + scale / 2 + base;
102       if (bilinear) {
103         int src_start = static_cast<int>(floor(src_pos - 0.5));
104         int src_end = static_cast<int>(floor(src_pos + 0.5));
105         src_start = std::max(src_start, src_min);
106         src_end = std::min(src_end, src_max - 1);
107         pixel_weights.SetStartEnd(src_start, src_end, weight_count);
108         if (pixel_weights.m_SrcStart >= pixel_weights.m_SrcEnd) {
109           // Always room for one weight per size calculation.
110           pixel_weights.m_Weights[0] = kFixedPointOne;
111         } else {
112           pixel_weights.m_Weights[1] =
113               FixedFromDouble(src_pos - pixel_weights.m_SrcStart - 0.5f);
114           pixel_weights.m_Weights[0] =
115               kFixedPointOne - pixel_weights.m_Weights[1];
116         }
117       } else {
118         int pixel_pos = static_cast<int>(floor(src_pos));
119         int src_start = std::max(pixel_pos, src_min);
120         int src_end = std::min(pixel_pos, src_max - 1);
121         pixel_weights.SetStartEnd(src_start, src_end, weight_count);
122         pixel_weights.m_Weights[0] = kFixedPointOne;
123       }
124     }
125     return true;
126   }
127 
128   for (int dest_pixel = dest_min; dest_pixel < dest_max; ++dest_pixel) {
129     PixelWeight& pixel_weights = *GetPixelWeight(dest_pixel);
130     double src_start = dest_pixel * scale + base;
131     double src_end = src_start + scale;
132     int start_i = floor(std::min(src_start, src_end));
133     int end_i = floor(std::max(src_start, src_end));
134     start_i = std::max(start_i, src_min);
135     end_i = std::min(end_i, src_max - 1);
136     if (start_i > end_i) {
137       start_i = std::min(start_i, src_max - 1);
138       pixel_weights.SetStartEnd(start_i, start_i, weight_count);
139       continue;
140     }
141     pixel_weights.SetStartEnd(start_i, end_i, weight_count);
142     uint32_t remaining = kFixedPointOne;
143     double rounding_error = 0.0;
144     for (int j = start_i; j < end_i; ++j) {
145       double dest_start = (j - base) / scale;
146       double dest_end = (j + 1 - base) / scale;
147       if (dest_start > dest_end)
148         std::swap(dest_start, dest_end);
149       double area_start = std::max(dest_start, static_cast<double>(dest_pixel));
150       double area_end = std::min(dest_end, static_cast<double>(dest_pixel + 1));
151       double weight = std::max(0.0, area_end - area_start);
152       uint32_t fixed_weight = FixedFromDouble(weight + rounding_error);
153       pixel_weights.SetWeightForPosition(j, fixed_weight);
154       remaining -= fixed_weight;
155       rounding_error =
156           weight - static_cast<double>(fixed_weight) / kFixedPointOne;
157     }
158     // Note: underflow is defined behaviour for unsigned types and will
159     // result in an out-of-range value.
160     if (remaining && remaining <= kFixedPointOne) {
161       pixel_weights.SetWeightForPosition(end_i, remaining);
162     } else {
163       pixel_weights.RemoveLastWeightAndAdjust(remaining);
164     }
165   }
166   return true;
167 }
168 
GetPixelWeight(int pixel) const169 const CStretchEngine::PixelWeight* CStretchEngine::WeightTable::GetPixelWeight(
170     int pixel) const {
171   DCHECK(pixel >= m_DestMin);
172   return reinterpret_cast<const PixelWeight*>(
173       &m_WeightTables[(pixel - m_DestMin) * m_ItemSizeBytes]);
174 }
175 
GetPixelWeight(int pixel)176 CStretchEngine::PixelWeight* CStretchEngine::WeightTable::GetPixelWeight(
177     int pixel) {
178   return const_cast<PixelWeight*>(std::as_const(*this).GetPixelWeight(pixel));
179 }
180 
CStretchEngine(ScanlineComposerIface * pDestBitmap,FXDIB_Format dest_format,int dest_width,int dest_height,const FX_RECT & clip_rect,const RetainPtr<const CFX_DIBBase> & pSrcBitmap,const FXDIB_ResampleOptions & options)181 CStretchEngine::CStretchEngine(ScanlineComposerIface* pDestBitmap,
182                                FXDIB_Format dest_format,
183                                int dest_width,
184                                int dest_height,
185                                const FX_RECT& clip_rect,
186                                const RetainPtr<const CFX_DIBBase>& pSrcBitmap,
187                                const FXDIB_ResampleOptions& options)
188     : m_DestFormat(dest_format),
189       m_DestBpp(GetBppFromFormat(dest_format)),
190       m_SrcBpp(pSrcBitmap->GetBPP()),
191       m_bHasAlpha(pSrcBitmap->IsAlphaFormat()),
192       m_pSource(pSrcBitmap),
193       m_pSrcPalette(pSrcBitmap->GetPaletteSpan()),
194       m_SrcWidth(pSrcBitmap->GetWidth()),
195       m_SrcHeight(pSrcBitmap->GetHeight()),
196       m_pDestBitmap(pDestBitmap),
197       m_DestWidth(dest_width),
198       m_DestHeight(dest_height),
199       m_DestClip(clip_rect) {
200   if (m_bHasAlpha) {
201     DCHECK_EQ(m_DestFormat, FXDIB_Format::kArgb);
202     DCHECK_EQ(m_DestBpp, GetBppFromFormat(FXDIB_Format::kArgb));
203     DCHECK_EQ(m_pSource->GetFormat(), FXDIB_Format::kArgb);
204     DCHECK_EQ(m_SrcBpp, GetBppFromFormat(FXDIB_Format::kArgb));
205   }
206 
207   absl::optional<uint32_t> maybe_size =
208       fxge::CalculatePitch32(m_DestBpp, clip_rect.Width());
209   if (!maybe_size.has_value())
210     return;
211 
212   m_DestScanline.resize(maybe_size.value());
213   if (dest_format == FXDIB_Format::kRgb32)
214     std::fill(m_DestScanline.begin(), m_DestScanline.end(), 255);
215   m_InterPitch = fxge::CalculatePitch32OrDie(m_DestBpp, m_DestClip.Width());
216   m_ExtraMaskPitch = fxge::CalculatePitch32OrDie(8, m_DestClip.Width());
217   if (options.bNoSmoothing) {
218     m_ResampleOptions.bNoSmoothing = true;
219   } else {
220     if (UseInterpolateBilinear(options, dest_width, dest_height, m_SrcWidth,
221                                m_SrcHeight)) {
222       m_ResampleOptions.bInterpolateBilinear = true;
223     } else {
224       m_ResampleOptions = options;
225     }
226   }
227   double scale_x = static_cast<float>(m_SrcWidth) / m_DestWidth;
228   double scale_y = static_cast<float>(m_SrcHeight) / m_DestHeight;
229   double base_x = m_DestWidth > 0 ? 0.0f : m_DestWidth;
230   double base_y = m_DestHeight > 0 ? 0.0f : m_DestHeight;
231   double src_left = scale_x * (clip_rect.left + base_x);
232   double src_right = scale_x * (clip_rect.right + base_x);
233   double src_top = scale_y * (clip_rect.top + base_y);
234   double src_bottom = scale_y * (clip_rect.bottom + base_y);
235   if (src_left > src_right)
236     std::swap(src_left, src_right);
237   if (src_top > src_bottom)
238     std::swap(src_top, src_bottom);
239   m_SrcClip.left = static_cast<int>(floor(src_left));
240   m_SrcClip.right = static_cast<int>(ceil(src_right));
241   m_SrcClip.top = static_cast<int>(floor(src_top));
242   m_SrcClip.bottom = static_cast<int>(ceil(src_bottom));
243   FX_RECT src_rect(0, 0, m_SrcWidth, m_SrcHeight);
244   m_SrcClip.Intersect(src_rect);
245 
246   switch (m_SrcBpp) {
247     case 1:
248       m_TransMethod = m_DestBpp == 8 ? TransformMethod::k1BppTo8Bpp
249                                      : TransformMethod::k1BppToManyBpp;
250       break;
251     case 8:
252       m_TransMethod = m_DestBpp == 8 ? TransformMethod::k8BppTo8Bpp
253                                      : TransformMethod::k8BppToManyBpp;
254       break;
255     default:
256       m_TransMethod = m_bHasAlpha ? TransformMethod::kManyBpptoManyBppWithAlpha
257                                   : TransformMethod::kManyBpptoManyBpp;
258       break;
259   }
260 }
261 
262 CStretchEngine::~CStretchEngine() = default;
263 
Continue(PauseIndicatorIface * pPause)264 bool CStretchEngine::Continue(PauseIndicatorIface* pPause) {
265   while (m_State == State::kHorizontal) {
266     if (ContinueStretchHorz(pPause))
267       return true;
268 
269     m_State = State::kVertical;
270     StretchVert();
271   }
272   return false;
273 }
274 
StartStretchHorz()275 bool CStretchEngine::StartStretchHorz() {
276   if (m_DestWidth == 0 || m_InterPitch == 0 || m_DestScanline.empty())
277     return false;
278 
279   FX_SAFE_SIZE_T safe_size = m_SrcClip.Height();
280   safe_size *= m_InterPitch;
281   const size_t size = safe_size.ValueOrDefault(0);
282   if (size == 0)
283     return false;
284 
285   m_InterBuf = FixedTryAllocZeroedDataVector<uint8_t>(size);
286   if (m_InterBuf.empty())
287     return false;
288 
289   if (!m_WeightTable.CalculateWeights(
290           m_DestWidth, m_DestClip.left, m_DestClip.right, m_SrcWidth,
291           m_SrcClip.left, m_SrcClip.right, m_ResampleOptions)) {
292     return false;
293   }
294   m_CurRow = m_SrcClip.top;
295   m_State = State::kHorizontal;
296   return true;
297 }
298 
ContinueStretchHorz(PauseIndicatorIface * pPause)299 bool CStretchEngine::ContinueStretchHorz(PauseIndicatorIface* pPause) {
300   if (!m_DestWidth)
301     return false;
302   if (m_pSource->SkipToScanline(m_CurRow, pPause))
303     return true;
304 
305   int Bpp = m_DestBpp / 8;
306   static const int kStrechPauseRows = 10;
307   int rows_to_go = kStrechPauseRows;
308   for (; m_CurRow < m_SrcClip.bottom; ++m_CurRow) {
309     if (rows_to_go == 0) {
310       if (pPause && pPause->NeedToPauseNow())
311         return true;
312 
313       rows_to_go = kStrechPauseRows;
314     }
315 
316     const uint8_t* src_scan = m_pSource->GetScanline(m_CurRow).data();
317     pdfium::span<uint8_t> dest_span = m_InterBuf.writable_span().subspan(
318         (m_CurRow - m_SrcClip.top) * m_InterPitch, m_InterPitch);
319     size_t dest_span_index = 0;
320     // TODO(npm): reduce duplicated code here
321     switch (m_TransMethod) {
322       case TransformMethod::k1BppTo8Bpp:
323       case TransformMethod::k1BppToManyBpp: {
324         for (int col = m_DestClip.left; col < m_DestClip.right; ++col) {
325           PixelWeight* pWeights = m_WeightTable.GetPixelWeight(col);
326           uint32_t dest_a = 0;
327           for (int j = pWeights->m_SrcStart; j <= pWeights->m_SrcEnd; ++j) {
328             uint32_t pixel_weight = pWeights->GetWeightForPosition(j);
329             if (src_scan[j / 8] & (1 << (7 - j % 8)))
330               dest_a += pixel_weight * 255;
331           }
332           dest_span[dest_span_index++] = PixelFromFixed(dest_a);
333         }
334         break;
335       }
336       case TransformMethod::k8BppTo8Bpp: {
337         for (int col = m_DestClip.left; col < m_DestClip.right; ++col) {
338           PixelWeight* pWeights = m_WeightTable.GetPixelWeight(col);
339           uint32_t dest_a = 0;
340           for (int j = pWeights->m_SrcStart; j <= pWeights->m_SrcEnd; ++j) {
341             uint32_t pixel_weight = pWeights->GetWeightForPosition(j);
342             dest_a += pixel_weight * src_scan[j];
343           }
344           dest_span[dest_span_index++] = PixelFromFixed(dest_a);
345         }
346         break;
347       }
348       case TransformMethod::k8BppToManyBpp: {
349         for (int col = m_DestClip.left; col < m_DestClip.right; ++col) {
350           PixelWeight* pWeights = m_WeightTable.GetPixelWeight(col);
351           uint32_t dest_r = 0;
352           uint32_t dest_g = 0;
353           uint32_t dest_b = 0;
354           for (int j = pWeights->m_SrcStart; j <= pWeights->m_SrcEnd; ++j) {
355             uint32_t pixel_weight = pWeights->GetWeightForPosition(j);
356             unsigned long argb = m_pSrcPalette[src_scan[j]];
357             if (m_DestFormat == FXDIB_Format::kRgb) {
358               dest_r += pixel_weight * static_cast<uint8_t>(argb >> 16);
359               dest_g += pixel_weight * static_cast<uint8_t>(argb >> 8);
360               dest_b += pixel_weight * static_cast<uint8_t>(argb);
361             } else {
362               dest_b += pixel_weight * static_cast<uint8_t>(argb >> 24);
363               dest_g += pixel_weight * static_cast<uint8_t>(argb >> 16);
364               dest_r += pixel_weight * static_cast<uint8_t>(argb >> 8);
365             }
366           }
367           dest_span[dest_span_index++] = PixelFromFixed(dest_b);
368           dest_span[dest_span_index++] = PixelFromFixed(dest_g);
369           dest_span[dest_span_index++] = PixelFromFixed(dest_r);
370         }
371         break;
372       }
373       case TransformMethod::kManyBpptoManyBpp: {
374         for (int col = m_DestClip.left; col < m_DestClip.right; ++col) {
375           PixelWeight* pWeights = m_WeightTable.GetPixelWeight(col);
376           uint32_t dest_r = 0;
377           uint32_t dest_g = 0;
378           uint32_t dest_b = 0;
379           for (int j = pWeights->m_SrcStart; j <= pWeights->m_SrcEnd; ++j) {
380             uint32_t pixel_weight = pWeights->GetWeightForPosition(j);
381             const uint8_t* src_pixel = src_scan + j * Bpp;
382             dest_b += pixel_weight * (*src_pixel++);
383             dest_g += pixel_weight * (*src_pixel++);
384             dest_r += pixel_weight * (*src_pixel);
385           }
386           dest_span[dest_span_index++] = PixelFromFixed(dest_b);
387           dest_span[dest_span_index++] = PixelFromFixed(dest_g);
388           dest_span[dest_span_index++] = PixelFromFixed(dest_r);
389           dest_span_index += Bpp - 3;
390         }
391         break;
392       }
393       case TransformMethod::kManyBpptoManyBppWithAlpha: {
394         DCHECK(m_bHasAlpha);
395         for (int col = m_DestClip.left; col < m_DestClip.right; ++col) {
396           PixelWeight* pWeights = m_WeightTable.GetPixelWeight(col);
397           uint32_t dest_a = 0;
398           uint32_t dest_r = 0;
399           uint32_t dest_g = 0;
400           uint32_t dest_b = 0;
401           for (int j = pWeights->m_SrcStart; j <= pWeights->m_SrcEnd; ++j) {
402             const uint8_t* src_pixel = src_scan + j * Bpp;
403             uint32_t pixel_weight =
404                 pWeights->GetWeightForPosition(j) * src_pixel[3] / 255;
405             dest_b += pixel_weight * (*src_pixel++);
406             dest_g += pixel_weight * (*src_pixel++);
407             dest_r += pixel_weight * (*src_pixel);
408             dest_a += pixel_weight;
409           }
410           dest_span[dest_span_index++] = PixelFromFixed(dest_b);
411           dest_span[dest_span_index++] = PixelFromFixed(dest_g);
412           dest_span[dest_span_index++] = PixelFromFixed(dest_r);
413           dest_span[dest_span_index] = PixelFromFixed(255 * dest_a);
414           dest_span_index += Bpp - 3;
415         }
416         break;
417       }
418     }
419     rows_to_go--;
420   }
421   return false;
422 }
423 
StretchVert()424 void CStretchEngine::StretchVert() {
425   if (m_DestHeight == 0)
426     return;
427 
428   WeightTable table;
429   if (!table.CalculateWeights(m_DestHeight, m_DestClip.top, m_DestClip.bottom,
430                               m_SrcHeight, m_SrcClip.top, m_SrcClip.bottom,
431                               m_ResampleOptions)) {
432     return;
433   }
434 
435   const int DestBpp = m_DestBpp / 8;
436   for (int row = m_DestClip.top; row < m_DestClip.bottom; ++row) {
437     unsigned char* dest_scan = m_DestScanline.data();
438     PixelWeight* pWeights = table.GetPixelWeight(row);
439     switch (m_TransMethod) {
440       case TransformMethod::k1BppTo8Bpp:
441       case TransformMethod::k1BppToManyBpp:
442       case TransformMethod::k8BppTo8Bpp: {
443         for (int col = m_DestClip.left; col < m_DestClip.right; ++col) {
444           pdfium::span<const uint8_t> src_span =
445               m_InterBuf.span().subspan((col - m_DestClip.left) * DestBpp);
446           uint32_t dest_a = 0;
447           for (int j = pWeights->m_SrcStart; j <= pWeights->m_SrcEnd; ++j) {
448             uint32_t pixel_weight = pWeights->GetWeightForPosition(j);
449             dest_a +=
450                 pixel_weight * src_span[(j - m_SrcClip.top) * m_InterPitch];
451           }
452           *dest_scan = PixelFromFixed(dest_a);
453           dest_scan += DestBpp;
454         }
455         break;
456       }
457       case TransformMethod::k8BppToManyBpp:
458       case TransformMethod::kManyBpptoManyBpp: {
459         for (int col = m_DestClip.left; col < m_DestClip.right; ++col) {
460           pdfium::span<const uint8_t> src_span =
461               m_InterBuf.span().subspan((col - m_DestClip.left) * DestBpp);
462           uint32_t dest_r = 0;
463           uint32_t dest_g = 0;
464           uint32_t dest_b = 0;
465           for (int j = pWeights->m_SrcStart; j <= pWeights->m_SrcEnd; ++j) {
466             uint32_t pixel_weight = pWeights->GetWeightForPosition(j);
467             pdfium::span<const uint8_t> src_pixel =
468                 src_span.subspan((j - m_SrcClip.top) * m_InterPitch, 3);
469             dest_b += pixel_weight * src_pixel[0];
470             dest_g += pixel_weight * src_pixel[1];
471             dest_r += pixel_weight * src_pixel[2];
472           }
473           dest_scan[0] = PixelFromFixed(dest_b);
474           dest_scan[1] = PixelFromFixed(dest_g);
475           dest_scan[2] = PixelFromFixed(dest_r);
476           dest_scan += DestBpp;
477         }
478         break;
479       }
480       case TransformMethod::kManyBpptoManyBppWithAlpha: {
481         DCHECK(m_bHasAlpha);
482         for (int col = m_DestClip.left; col < m_DestClip.right; ++col) {
483           pdfium::span<const uint8_t> src_span =
484               m_InterBuf.span().subspan((col - m_DestClip.left) * DestBpp);
485           uint32_t dest_a = 0;
486           uint32_t dest_r = 0;
487           uint32_t dest_g = 0;
488           uint32_t dest_b = 0;
489           constexpr size_t kPixelBytes = 4;
490           for (int j = pWeights->m_SrcStart; j <= pWeights->m_SrcEnd; ++j) {
491             uint32_t pixel_weight = pWeights->GetWeightForPosition(j);
492             pdfium::span<const uint8_t> src_pixel = src_span.subspan(
493                 (j - m_SrcClip.top) * m_InterPitch, kPixelBytes);
494             dest_b += pixel_weight * src_pixel[0];
495             dest_g += pixel_weight * src_pixel[1];
496             dest_r += pixel_weight * src_pixel[2];
497             dest_a += pixel_weight * src_pixel[3];
498           }
499           if (dest_a) {
500             int r = static_cast<uint32_t>(dest_r) * 255 / dest_a;
501             int g = static_cast<uint32_t>(dest_g) * 255 / dest_a;
502             int b = static_cast<uint32_t>(dest_b) * 255 / dest_a;
503             dest_scan[0] = pdfium::clamp(b, 0, 255);
504             dest_scan[1] = pdfium::clamp(g, 0, 255);
505             dest_scan[2] = pdfium::clamp(r, 0, 255);
506           }
507           dest_scan[3] = PixelFromFixed(dest_a);
508           dest_scan += DestBpp;
509         }
510         break;
511       }
512     }
513     m_pDestBitmap->ComposeScanline(row - m_DestClip.top, m_DestScanline);
514   }
515 }
516