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