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