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