| // Copyright 2017 The PDFium Authors | |
| // Use of this source code is governed by a BSD-style license that can be | |
| // found in the LICENSE file. | |
| // Original code copyright 2014 Foxit Software Inc. http://www.foxitsoftware.com | |
| #include "core/fxge/dib/cstretchengine.h" | |
| #include <math.h> | |
| #include <algorithm> | |
| #include <type_traits> | |
| #include <utility> | |
| #include "core/fxcrt/fx_safe_types.h" | |
| #include "core/fxcrt/fx_system.h" | |
| #include "core/fxcrt/pauseindicator_iface.h" | |
| #include "core/fxge/calculate_pitch.h" | |
| #include "core/fxge/dib/cfx_dibbase.h" | |
| #include "core/fxge/dib/cfx_dibitmap.h" | |
| #include "core/fxge/dib/fx_dib.h" | |
| #include "core/fxge/dib/scanlinecomposer_iface.h" | |
| #include "third_party/base/check.h" | |
| #include "third_party/base/cxx17_backports.h" | |
| static_assert( | |
| std::is_trivially_destructible<CStretchEngine::PixelWeight>::value, | |
| "PixelWeight storage may be re-used without invoking its destructor"); | |
| // static | |
| bool CStretchEngine::UseInterpolateBilinear( | |
| const FXDIB_ResampleOptions& options, | |
| int dest_width, | |
| int dest_height, | |
| int src_width, | |
| int src_height) { | |
| return !options.bInterpolateBilinear && !options.bNoSmoothing && | |
| abs(dest_width) != 0 && | |
| abs(dest_height) / 8 < | |
| static_cast<long long>(src_width) * src_height / abs(dest_width); | |
| } | |
| // static | |
| size_t CStretchEngine::PixelWeight::TotalBytesForWeightCount( | |
| size_t weight_count) { | |
| // Always room for one weight even for empty ranges due to declaration | |
| // of m_Weights[1] in the header. Don't shrink below this since | |
| // CalculateWeights() relies on this later. | |
| const size_t extra_weights = weight_count > 0 ? weight_count - 1 : 0; | |
| FX_SAFE_SIZE_T total_bytes = extra_weights; | |
| total_bytes *= sizeof(m_Weights[0]); | |
| total_bytes += sizeof(PixelWeight); | |
| return total_bytes.ValueOrDie(); | |
| } | |
| CStretchEngine::WeightTable::WeightTable() = default; | |
| CStretchEngine::WeightTable::~WeightTable() = default; | |
| bool CStretchEngine::WeightTable::CalculateWeights( | |
| int dest_len, | |
| int dest_min, | |
| int dest_max, | |
| int src_len, | |
| int src_min, | |
| int src_max, | |
| const FXDIB_ResampleOptions& options) { | |
| // 512MB should be large enough for this while preventing OOM. | |
| static constexpr size_t kMaxTableBytesAllowed = 512 * 1024 * 1024; | |
| // Help the compiler realize that these can't change during a loop iteration: | |
| const bool bilinear = options.bInterpolateBilinear; | |
| m_DestMin = 0; | |
| m_ItemSizeBytes = 0; | |
| m_WeightTablesSizeBytes = 0; | |
| m_WeightTables.clear(); | |
| if (dest_len == 0) | |
| return true; | |
| if (dest_min > dest_max) | |
| return false; | |
| m_DestMin = dest_min; | |
| const double scale = static_cast<double>(src_len) / dest_len; | |
| const double base = dest_len < 0 ? src_len : 0; | |
| const size_t weight_count = static_cast<size_t>(ceil(fabs(scale))) + 1; | |
| m_ItemSizeBytes = PixelWeight::TotalBytesForWeightCount(weight_count); | |
| const size_t dest_range = static_cast<size_t>(dest_max - dest_min); | |
| const size_t kMaxTableItemsAllowed = kMaxTableBytesAllowed / m_ItemSizeBytes; | |
| if (dest_range > kMaxTableItemsAllowed) | |
| return false; | |
| m_WeightTablesSizeBytes = dest_range * m_ItemSizeBytes; | |
| m_WeightTables.resize(m_WeightTablesSizeBytes); | |
| if (options.bNoSmoothing || fabs(scale) < 1.0f) { | |
| for (int dest_pixel = dest_min; dest_pixel < dest_max; ++dest_pixel) { | |
| PixelWeight& pixel_weights = *GetPixelWeight(dest_pixel); | |
| double src_pos = dest_pixel * scale + scale / 2 + base; | |
| if (bilinear) { | |
| int src_start = static_cast<int>(floor(src_pos - 0.5)); | |
| int src_end = static_cast<int>(floor(src_pos + 0.5)); | |
| src_start = std::max(src_start, src_min); | |
| src_end = std::min(src_end, src_max - 1); | |
| pixel_weights.SetStartEnd(src_start, src_end, weight_count); | |
| if (pixel_weights.m_SrcStart >= pixel_weights.m_SrcEnd) { | |
| // Always room for one weight per size calculation. | |
| pixel_weights.m_Weights[0] = kFixedPointOne; | |
| } else { | |
| pixel_weights.m_Weights[1] = | |
| FixedFromDouble(src_pos - pixel_weights.m_SrcStart - 0.5f); | |
| pixel_weights.m_Weights[0] = | |
| kFixedPointOne - pixel_weights.m_Weights[1]; | |
| } | |
| } else { | |
| int pixel_pos = static_cast<int>(floor(src_pos)); | |
| int src_start = std::max(pixel_pos, src_min); | |
| int src_end = std::min(pixel_pos, src_max - 1); | |
| pixel_weights.SetStartEnd(src_start, src_end, weight_count); | |
| pixel_weights.m_Weights[0] = kFixedPointOne; | |
| } | |
| } | |
| return true; | |
| } | |
| for (int dest_pixel = dest_min; dest_pixel < dest_max; ++dest_pixel) { | |
| PixelWeight& pixel_weights = *GetPixelWeight(dest_pixel); | |
| double src_start = dest_pixel * scale + base; | |
| double src_end = src_start + scale; | |
| int start_i = floor(std::min(src_start, src_end)); | |
| int end_i = floor(std::max(src_start, src_end)); | |
| start_i = std::max(start_i, src_min); | |
| end_i = std::min(end_i, src_max - 1); | |
| if (start_i > end_i) { | |
| start_i = std::min(start_i, src_max - 1); | |
| pixel_weights.SetStartEnd(start_i, start_i, weight_count); | |
| continue; | |
| } | |
| pixel_weights.SetStartEnd(start_i, end_i, weight_count); | |
| uint32_t remaining = kFixedPointOne; | |
| double rounding_error = 0.0; | |
| for (int j = start_i; j < end_i; ++j) { | |
| double dest_start = (j - base) / scale; | |
| double dest_end = (j + 1 - base) / scale; | |
| if (dest_start > dest_end) | |
| std::swap(dest_start, dest_end); | |
| double area_start = std::max(dest_start, static_cast<double>(dest_pixel)); | |
| double area_end = std::min(dest_end, static_cast<double>(dest_pixel + 1)); | |
| double weight = std::max(0.0, area_end - area_start); | |
| uint32_t fixed_weight = FixedFromDouble(weight + rounding_error); | |
| pixel_weights.SetWeightForPosition(j, fixed_weight); | |
| remaining -= fixed_weight; | |
| rounding_error = | |
| weight - static_cast<double>(fixed_weight) / kFixedPointOne; | |
| } | |
| // Note: underflow is defined behaviour for unsigned types and will | |
| // result in an out-of-range value. | |
| if (remaining && remaining <= kFixedPointOne) { | |
| pixel_weights.SetWeightForPosition(end_i, remaining); | |
| } else { | |
| pixel_weights.RemoveLastWeightAndAdjust(remaining); | |
| } | |
| } | |
| return true; | |
| } | |
| const CStretchEngine::PixelWeight* CStretchEngine::WeightTable::GetPixelWeight( | |
| int pixel) const { | |
| DCHECK(pixel >= m_DestMin); | |
| return reinterpret_cast<const PixelWeight*>( | |
| &m_WeightTables[(pixel - m_DestMin) * m_ItemSizeBytes]); | |
| } | |
| CStretchEngine::PixelWeight* CStretchEngine::WeightTable::GetPixelWeight( | |
| int pixel) { | |
| return const_cast<PixelWeight*>(std::as_const(*this).GetPixelWeight(pixel)); | |
| } | |
| CStretchEngine::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) | |
| : m_DestFormat(dest_format), | |
| m_DestBpp(GetBppFromFormat(dest_format)), | |
| m_SrcBpp(pSrcBitmap->GetBPP()), | |
| m_bHasAlpha(pSrcBitmap->IsAlphaFormat()), | |
| m_pSource(pSrcBitmap), | |
| m_pSrcPalette(pSrcBitmap->GetPaletteSpan()), | |
| m_SrcWidth(pSrcBitmap->GetWidth()), | |
| m_SrcHeight(pSrcBitmap->GetHeight()), | |
| m_pDestBitmap(pDestBitmap), | |
| m_DestWidth(dest_width), | |
| m_DestHeight(dest_height), | |
| m_DestClip(clip_rect) { | |
| absl::optional<uint32_t> maybe_size = | |
| fxge::CalculatePitch32(m_DestBpp, clip_rect.Width()); | |
| if (!maybe_size.has_value()) | |
| return; | |
| m_DestScanline.resize(maybe_size.value()); | |
| if (dest_format == FXDIB_Format::kRgb32) | |
| std::fill(m_DestScanline.begin(), m_DestScanline.end(), 255); | |
| m_InterPitch = fxge::CalculatePitch32OrDie(m_DestBpp, m_DestClip.Width()); | |
| m_ExtraMaskPitch = fxge::CalculatePitch32OrDie(8, m_DestClip.Width()); | |
| if (options.bNoSmoothing) { | |
| m_ResampleOptions.bNoSmoothing = true; | |
| } else { | |
| if (UseInterpolateBilinear(options, dest_width, dest_height, m_SrcWidth, | |
| m_SrcHeight)) { | |
| m_ResampleOptions.bInterpolateBilinear = true; | |
| } else { | |
| m_ResampleOptions = options; | |
| } | |
| } | |
| double scale_x = static_cast<float>(m_SrcWidth) / m_DestWidth; | |
| double scale_y = static_cast<float>(m_SrcHeight) / m_DestHeight; | |
| double base_x = m_DestWidth > 0 ? 0.0f : m_DestWidth; | |
| double base_y = m_DestHeight > 0 ? 0.0f : m_DestHeight; | |
| double src_left = scale_x * (clip_rect.left + base_x); | |
| double src_right = scale_x * (clip_rect.right + base_x); | |
| double src_top = scale_y * (clip_rect.top + base_y); | |
| double src_bottom = scale_y * (clip_rect.bottom + base_y); | |
| if (src_left > src_right) | |
| std::swap(src_left, src_right); | |
| if (src_top > src_bottom) | |
| std::swap(src_top, src_bottom); | |
| m_SrcClip.left = static_cast<int>(floor(src_left)); | |
| m_SrcClip.right = static_cast<int>(ceil(src_right)); | |
| m_SrcClip.top = static_cast<int>(floor(src_top)); | |
| m_SrcClip.bottom = static_cast<int>(ceil(src_bottom)); | |
| FX_RECT src_rect(0, 0, m_SrcWidth, m_SrcHeight); | |
| m_SrcClip.Intersect(src_rect); | |
| switch (m_SrcBpp) { | |
| case 1: | |
| m_TransMethod = m_DestBpp == 8 ? TransformMethod::k1BppTo8Bpp | |
| : TransformMethod::k1BppToManyBpp; | |
| break; | |
| case 8: | |
| if (m_DestBpp == 8) { | |
| m_TransMethod = m_bHasAlpha ? TransformMethod::k8BppTo8BppWithAlpha | |
| : TransformMethod::k8BppTo8Bpp; | |
| } else { | |
| m_TransMethod = m_bHasAlpha ? TransformMethod::k8BppToManyBppWithAlpha | |
| : TransformMethod::k8BppToManyBpp; | |
| } | |
| break; | |
| default: | |
| m_TransMethod = m_bHasAlpha ? TransformMethod::kManyBpptoManyBppWithAlpha | |
| : TransformMethod::kManyBpptoManyBpp; | |
| break; | |
| } | |
| } | |
| CStretchEngine::~CStretchEngine() = default; | |
| bool CStretchEngine::Continue(PauseIndicatorIface* pPause) { | |
| while (m_State == State::kHorizontal) { | |
| if (ContinueStretchHorz(pPause)) | |
| return true; | |
| m_State = State::kVertical; | |
| StretchVert(); | |
| } | |
| return false; | |
| } | |
| bool CStretchEngine::StartStretchHorz() { | |
| if (m_DestWidth == 0 || m_InterPitch == 0 || m_DestScanline.empty()) | |
| return false; | |
| FX_SAFE_SIZE_T safe_size = m_SrcClip.Height(); | |
| safe_size *= m_InterPitch; | |
| const size_t size = safe_size.ValueOrDefault(0); | |
| if (size == 0) | |
| return false; | |
| m_InterBuf = FixedTryAllocZeroedDataVector<uint8_t>(size); | |
| if (m_InterBuf.empty()) | |
| return false; | |
| if (m_pSource && m_bHasAlpha && m_pSource->HasAlphaMask()) { | |
| m_ExtraAlphaBuf.resize(m_SrcClip.Height(), m_ExtraMaskPitch); | |
| m_DestMaskScanline.resize(m_ExtraMaskPitch); | |
| } | |
| if (!m_WeightTable.CalculateWeights( | |
| m_DestWidth, m_DestClip.left, m_DestClip.right, m_SrcWidth, | |
| m_SrcClip.left, m_SrcClip.right, m_ResampleOptions)) { | |
| return false; | |
| } | |
| m_CurRow = m_SrcClip.top; | |
| m_State = State::kHorizontal; | |
| return true; | |
| } | |
| bool CStretchEngine::ContinueStretchHorz(PauseIndicatorIface* pPause) { | |
| if (!m_DestWidth) | |
| return false; | |
| if (m_pSource->SkipToScanline(m_CurRow, pPause)) | |
| return true; | |
| int Bpp = m_DestBpp / 8; | |
| static const int kStrechPauseRows = 10; | |
| int rows_to_go = kStrechPauseRows; | |
| for (; m_CurRow < m_SrcClip.bottom; ++m_CurRow) { | |
| if (rows_to_go == 0) { | |
| if (pPause && pPause->NeedToPauseNow()) | |
| return true; | |
| rows_to_go = kStrechPauseRows; | |
| } | |
| const uint8_t* src_scan = m_pSource->GetScanline(m_CurRow).data(); | |
| pdfium::span<uint8_t> dest_span = m_InterBuf.writable_span().subspan( | |
| (m_CurRow - m_SrcClip.top) * m_InterPitch, m_InterPitch); | |
| size_t dest_span_index = 0; | |
| const uint8_t* src_scan_mask = nullptr; | |
| uint8_t* dest_scan_mask = nullptr; | |
| if (!m_ExtraAlphaBuf.empty()) { | |
| src_scan_mask = m_pSource->GetAlphaMaskScanline(m_CurRow).data(); | |
| dest_scan_mask = m_ExtraAlphaBuf.data() + | |
| (m_CurRow - m_SrcClip.top) * m_ExtraMaskPitch; | |
| } | |
| // TODO(npm): reduce duplicated code here | |
| switch (m_TransMethod) { | |
| case TransformMethod::k1BppTo8Bpp: | |
| case TransformMethod::k1BppToManyBpp: { | |
| for (int col = m_DestClip.left; col < m_DestClip.right; ++col) { | |
| PixelWeight* pWeights = m_WeightTable.GetPixelWeight(col); | |
| uint32_t dest_a = 0; | |
| for (int j = pWeights->m_SrcStart; j <= pWeights->m_SrcEnd; ++j) { | |
| uint32_t pixel_weight = pWeights->GetWeightForPosition(j); | |
| if (src_scan[j / 8] & (1 << (7 - j % 8))) | |
| dest_a += pixel_weight * 255; | |
| } | |
| dest_span[dest_span_index++] = PixelFromFixed(dest_a); | |
| } | |
| break; | |
| } | |
| case TransformMethod::k8BppTo8Bpp: { | |
| for (int col = m_DestClip.left; col < m_DestClip.right; ++col) { | |
| PixelWeight* pWeights = m_WeightTable.GetPixelWeight(col); | |
| uint32_t dest_a = 0; | |
| for (int j = pWeights->m_SrcStart; j <= pWeights->m_SrcEnd; ++j) { | |
| uint32_t pixel_weight = pWeights->GetWeightForPosition(j); | |
| dest_a += pixel_weight * src_scan[j]; | |
| } | |
| dest_span[dest_span_index++] = PixelFromFixed(dest_a); | |
| } | |
| break; | |
| } | |
| case TransformMethod::k8BppTo8BppWithAlpha: { | |
| for (int col = m_DestClip.left; col < m_DestClip.right; ++col) { | |
| PixelWeight* pWeights = m_WeightTable.GetPixelWeight(col); | |
| uint32_t dest_a = 0; | |
| uint32_t dest_r = 0; | |
| for (int j = pWeights->m_SrcStart; j <= pWeights->m_SrcEnd; ++j) { | |
| uint32_t pixel_weight = pWeights->GetWeightForPosition(j); | |
| pixel_weight = pixel_weight * src_scan_mask[j] / 255; | |
| dest_r += pixel_weight * src_scan[j]; | |
| dest_a += pixel_weight; | |
| } | |
| dest_span[dest_span_index++] = PixelFromFixed(dest_r); | |
| *dest_scan_mask++ = PixelFromFixed(255 * dest_a); | |
| } | |
| break; | |
| } | |
| case TransformMethod::k8BppToManyBpp: { | |
| for (int col = m_DestClip.left; col < m_DestClip.right; ++col) { | |
| PixelWeight* pWeights = m_WeightTable.GetPixelWeight(col); | |
| uint32_t dest_r = 0; | |
| uint32_t dest_g = 0; | |
| uint32_t dest_b = 0; | |
| for (int j = pWeights->m_SrcStart; j <= pWeights->m_SrcEnd; ++j) { | |
| uint32_t pixel_weight = pWeights->GetWeightForPosition(j); | |
| unsigned long argb = m_pSrcPalette[src_scan[j]]; | |
| if (m_DestFormat == FXDIB_Format::kRgb) { | |
| dest_r += pixel_weight * static_cast<uint8_t>(argb >> 16); | |
| dest_g += pixel_weight * static_cast<uint8_t>(argb >> 8); | |
| dest_b += pixel_weight * static_cast<uint8_t>(argb); | |
| } else { | |
| dest_b += pixel_weight * static_cast<uint8_t>(argb >> 24); | |
| dest_g += pixel_weight * static_cast<uint8_t>(argb >> 16); | |
| dest_r += pixel_weight * static_cast<uint8_t>(argb >> 8); | |
| } | |
| } | |
| dest_span[dest_span_index++] = PixelFromFixed(dest_b); | |
| dest_span[dest_span_index++] = PixelFromFixed(dest_g); | |
| dest_span[dest_span_index++] = PixelFromFixed(dest_r); | |
| } | |
| break; | |
| } | |
| case TransformMethod::k8BppToManyBppWithAlpha: { | |
| for (int col = m_DestClip.left; col < m_DestClip.right; ++col) { | |
| PixelWeight* pWeights = m_WeightTable.GetPixelWeight(col); | |
| uint32_t dest_a = 0; | |
| uint32_t dest_r = 0; | |
| uint32_t dest_g = 0; | |
| uint32_t dest_b = 0; | |
| for (int j = pWeights->m_SrcStart; j <= pWeights->m_SrcEnd; ++j) { | |
| uint32_t pixel_weight = pWeights->GetWeightForPosition(j); | |
| pixel_weight = pixel_weight * src_scan_mask[j] / 255; | |
| unsigned long argb = m_pSrcPalette[src_scan[j]]; | |
| dest_b += pixel_weight * static_cast<uint8_t>(argb >> 24); | |
| dest_g += pixel_weight * static_cast<uint8_t>(argb >> 16); | |
| dest_r += pixel_weight * static_cast<uint8_t>(argb >> 8); | |
| dest_a += pixel_weight; | |
| } | |
| dest_span[dest_span_index++] = PixelFromFixed(dest_b); | |
| dest_span[dest_span_index++] = PixelFromFixed(dest_g); | |
| dest_span[dest_span_index++] = PixelFromFixed(dest_r); | |
| *dest_scan_mask++ = PixelFromFixed(255 * dest_a); | |
| } | |
| break; | |
| } | |
| case TransformMethod::kManyBpptoManyBpp: { | |
| for (int col = m_DestClip.left; col < m_DestClip.right; ++col) { | |
| PixelWeight* pWeights = m_WeightTable.GetPixelWeight(col); | |
| uint32_t dest_r = 0; | |
| uint32_t dest_g = 0; | |
| uint32_t dest_b = 0; | |
| for (int j = pWeights->m_SrcStart; j <= pWeights->m_SrcEnd; ++j) { | |
| uint32_t pixel_weight = pWeights->GetWeightForPosition(j); | |
| const uint8_t* src_pixel = src_scan + j * Bpp; | |
| dest_b += pixel_weight * (*src_pixel++); | |
| dest_g += pixel_weight * (*src_pixel++); | |
| dest_r += pixel_weight * (*src_pixel); | |
| } | |
| dest_span[dest_span_index++] = PixelFromFixed(dest_b); | |
| dest_span[dest_span_index++] = PixelFromFixed(dest_g); | |
| dest_span[dest_span_index++] = PixelFromFixed(dest_r); | |
| dest_span_index += Bpp - 3; | |
| } | |
| break; | |
| } | |
| case TransformMethod::kManyBpptoManyBppWithAlpha: { | |
| for (int col = m_DestClip.left; col < m_DestClip.right; ++col) { | |
| PixelWeight* pWeights = m_WeightTable.GetPixelWeight(col); | |
| uint32_t dest_a = 0; | |
| uint32_t dest_r = 0; | |
| uint32_t dest_g = 0; | |
| uint32_t dest_b = 0; | |
| for (int j = pWeights->m_SrcStart; j <= pWeights->m_SrcEnd; ++j) { | |
| uint32_t pixel_weight = pWeights->GetWeightForPosition(j); | |
| const uint8_t* src_pixel = src_scan + j * Bpp; | |
| if (m_DestFormat == FXDIB_Format::kArgb) { | |
| pixel_weight = pixel_weight * src_pixel[3] / 255; | |
| } else { | |
| pixel_weight = pixel_weight * src_scan_mask[j] / 255; | |
| } | |
| dest_b += pixel_weight * (*src_pixel++); | |
| dest_g += pixel_weight * (*src_pixel++); | |
| dest_r += pixel_weight * (*src_pixel); | |
| dest_a += pixel_weight; | |
| } | |
| dest_span[dest_span_index++] = PixelFromFixed(dest_b); | |
| dest_span[dest_span_index++] = PixelFromFixed(dest_g); | |
| dest_span[dest_span_index++] = PixelFromFixed(dest_r); | |
| if (m_DestFormat == FXDIB_Format::kArgb) | |
| dest_span[dest_span_index] = PixelFromFixed(255 * dest_a); | |
| if (dest_scan_mask) | |
| *dest_scan_mask++ = PixelFromFixed(255 * dest_a); | |
| dest_span_index += Bpp - 3; | |
| } | |
| break; | |
| } | |
| } | |
| rows_to_go--; | |
| } | |
| return false; | |
| } | |
| void CStretchEngine::StretchVert() { | |
| if (m_DestHeight == 0) | |
| return; | |
| WeightTable table; | |
| if (!table.CalculateWeights(m_DestHeight, m_DestClip.top, m_DestClip.bottom, | |
| m_SrcHeight, m_SrcClip.top, m_SrcClip.bottom, | |
| m_ResampleOptions)) { | |
| return; | |
| } | |
| const int DestBpp = m_DestBpp / 8; | |
| for (int row = m_DestClip.top; row < m_DestClip.bottom; ++row) { | |
| unsigned char* dest_scan = m_DestScanline.data(); | |
| unsigned char* dest_scan_mask = m_DestMaskScanline.data(); | |
| PixelWeight* pWeights = table.GetPixelWeight(row); | |
| switch (m_TransMethod) { | |
| case TransformMethod::k1BppTo8Bpp: | |
| case TransformMethod::k1BppToManyBpp: | |
| case TransformMethod::k8BppTo8Bpp: { | |
| for (int col = m_DestClip.left; col < m_DestClip.right; ++col) { | |
| pdfium::span<const uint8_t> src_span = | |
| m_InterBuf.span().subspan((col - m_DestClip.left) * DestBpp); | |
| uint32_t dest_a = 0; | |
| for (int j = pWeights->m_SrcStart; j <= pWeights->m_SrcEnd; ++j) { | |
| uint32_t pixel_weight = pWeights->GetWeightForPosition(j); | |
| dest_a += | |
| pixel_weight * src_span[(j - m_SrcClip.top) * m_InterPitch]; | |
| } | |
| *dest_scan = PixelFromFixed(dest_a); | |
| dest_scan += DestBpp; | |
| } | |
| break; | |
| } | |
| case TransformMethod::k8BppTo8BppWithAlpha: { | |
| for (int col = m_DestClip.left; col < m_DestClip.right; ++col) { | |
| pdfium::span<const uint8_t> src_span = | |
| m_InterBuf.span().subspan((col - m_DestClip.left) * DestBpp); | |
| unsigned char* src_scan_mask = | |
| m_ExtraAlphaBuf.data() + (col - m_DestClip.left); | |
| uint32_t dest_a = 0; | |
| uint32_t dest_k = 0; | |
| for (int j = pWeights->m_SrcStart; j <= pWeights->m_SrcEnd; ++j) { | |
| uint32_t pixel_weight = pWeights->GetWeightForPosition(j); | |
| dest_k += | |
| pixel_weight * src_span[(j - m_SrcClip.top) * m_InterPitch]; | |
| dest_a += pixel_weight * | |
| src_scan_mask[(j - m_SrcClip.top) * m_ExtraMaskPitch]; | |
| } | |
| *dest_scan = PixelFromFixed(dest_k); | |
| dest_scan += DestBpp; | |
| *dest_scan_mask++ = PixelFromFixed(dest_a); | |
| } | |
| break; | |
| } | |
| case TransformMethod::k8BppToManyBpp: | |
| case TransformMethod::kManyBpptoManyBpp: { | |
| for (int col = m_DestClip.left; col < m_DestClip.right; ++col) { | |
| pdfium::span<const uint8_t> src_span = | |
| m_InterBuf.span().subspan((col - m_DestClip.left) * DestBpp); | |
| uint32_t dest_r = 0; | |
| uint32_t dest_g = 0; | |
| uint32_t dest_b = 0; | |
| for (int j = pWeights->m_SrcStart; j <= pWeights->m_SrcEnd; ++j) { | |
| uint32_t pixel_weight = pWeights->GetWeightForPosition(j); | |
| pdfium::span<const uint8_t> src_pixel = | |
| src_span.subspan((j - m_SrcClip.top) * m_InterPitch, 3); | |
| dest_b += pixel_weight * src_pixel[0]; | |
| dest_g += pixel_weight * src_pixel[1]; | |
| dest_r += pixel_weight * src_pixel[2]; | |
| } | |
| dest_scan[0] = PixelFromFixed(dest_b); | |
| dest_scan[1] = PixelFromFixed(dest_g); | |
| dest_scan[2] = PixelFromFixed(dest_r); | |
| dest_scan += DestBpp; | |
| } | |
| break; | |
| } | |
| case TransformMethod::k8BppToManyBppWithAlpha: | |
| case TransformMethod::kManyBpptoManyBppWithAlpha: { | |
| for (int col = m_DestClip.left; col < m_DestClip.right; ++col) { | |
| pdfium::span<const uint8_t> src_span = | |
| m_InterBuf.span().subspan((col - m_DestClip.left) * DestBpp); | |
| unsigned char* src_scan_mask = nullptr; | |
| if (m_DestFormat != FXDIB_Format::kArgb) | |
| src_scan_mask = m_ExtraAlphaBuf.data() + (col - m_DestClip.left); | |
| uint32_t dest_a = 0; | |
| uint32_t dest_r = 0; | |
| uint32_t dest_g = 0; | |
| uint32_t dest_b = 0; | |
| const size_t pixel_bytes = | |
| m_DestFormat == FXDIB_Format::kArgb ? 4 : 3; | |
| for (int j = pWeights->m_SrcStart; j <= pWeights->m_SrcEnd; ++j) { | |
| uint32_t pixel_weight = pWeights->GetWeightForPosition(j); | |
| pdfium::span<const uint8_t> src_pixel = src_span.subspan( | |
| (j - m_SrcClip.top) * m_InterPitch, pixel_bytes); | |
| int mask_v = 255; | |
| if (src_scan_mask) | |
| mask_v = src_scan_mask[(j - m_SrcClip.top) * m_ExtraMaskPitch]; | |
| dest_b += pixel_weight * src_pixel[0]; | |
| dest_g += pixel_weight * src_pixel[1]; | |
| dest_r += pixel_weight * src_pixel[2]; | |
| if (m_DestFormat == FXDIB_Format::kArgb) | |
| dest_a += pixel_weight * src_pixel[3]; | |
| else | |
| dest_a += pixel_weight * mask_v; | |
| } | |
| if (dest_a) { | |
| int r = static_cast<uint32_t>(dest_r) * 255 / dest_a; | |
| int g = static_cast<uint32_t>(dest_g) * 255 / dest_a; | |
| int b = static_cast<uint32_t>(dest_b) * 255 / dest_a; | |
| dest_scan[0] = pdfium::clamp(b, 0, 255); | |
| dest_scan[1] = pdfium::clamp(g, 0, 255); | |
| dest_scan[2] = pdfium::clamp(r, 0, 255); | |
| } | |
| if (m_DestFormat == FXDIB_Format::kArgb) | |
| dest_scan[3] = PixelFromFixed(dest_a); | |
| else | |
| *dest_scan_mask = PixelFromFixed(dest_a); | |
| dest_scan += DestBpp; | |
| if (dest_scan_mask) | |
| dest_scan_mask++; | |
| } | |
| break; | |
| } | |
| } | |
| m_pDestBitmap->ComposeScanline(row - m_DestClip.top, m_DestScanline, | |
| m_DestMaskScanline); | |
| } | |
| } |