| // Copyright 2017 PDFium Authors. All rights reserved. |
| // 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/cfx_imagetransformer.h" |
| |
| #include <cmath> |
| #include <memory> |
| #include <utility> |
| |
| #include "core/fxge/dib/cfx_imagestretcher.h" |
| #include "core/fxge/fx_dib.h" |
| #include "third_party/base/compiler_specific.h" |
| #include "third_party/base/numerics/safe_conversions.h" |
| #include "third_party/base/ptr_util.h" |
| |
| namespace { |
| |
| constexpr int kBase = 256; |
| constexpr float kFix16 = 0.05f; |
| constexpr uint8_t kOpaqueAlpha = 0xff; |
| |
| uint8_t bilinear_interpol(const uint8_t* buf, |
| int row_offset_l, |
| int row_offset_r, |
| int src_col_l, |
| int src_col_r, |
| int res_x, |
| int res_y, |
| int bpp, |
| int c_offset) { |
| int i_resx = 255 - res_x; |
| int col_bpp_l = src_col_l * bpp; |
| int col_bpp_r = src_col_r * bpp; |
| const uint8_t* buf_u = buf + row_offset_l + c_offset; |
| const uint8_t* buf_d = buf + row_offset_r + c_offset; |
| const uint8_t* src_pos0 = buf_u + col_bpp_l; |
| const uint8_t* src_pos1 = buf_u + col_bpp_r; |
| const uint8_t* src_pos2 = buf_d + col_bpp_l; |
| const uint8_t* src_pos3 = buf_d + col_bpp_r; |
| uint8_t r_pos_0 = (*src_pos0 * i_resx + *src_pos1 * res_x) >> 8; |
| uint8_t r_pos_1 = (*src_pos2 * i_resx + *src_pos3 * res_x) >> 8; |
| return (r_pos_0 * (255 - res_y) + r_pos_1 * res_y) >> 8; |
| } |
| |
| uint8_t bicubic_interpol(const uint8_t* buf, |
| uint32_t pitch, |
| const int pos_pixel[], |
| const int u_w[], |
| const int v_w[], |
| int res_x, |
| int res_y, |
| int bpp, |
| int c_offset) { |
| int s_result = 0; |
| for (int i = 0; i < 4; i++) { |
| int a_result = 0; |
| for (int j = 0; j < 4; j++) { |
| uint8_t val = |
| *(buf + pos_pixel[i + 4] * pitch + pos_pixel[j] * bpp + c_offset); |
| a_result += u_w[j] * val; |
| } |
| s_result += a_result * v_w[i]; |
| } |
| s_result >>= 16; |
| return static_cast<uint8_t>(pdfium::clamp(s_result, 0, 255)); |
| } |
| |
| void bicubic_get_pos_weight(int pos_pixel[], |
| int u_w[], |
| int v_w[], |
| int src_col_l, |
| int src_row_l, |
| int res_x, |
| int res_y, |
| int stretch_width, |
| int stretch_height) { |
| pos_pixel[0] = src_col_l - 1; |
| pos_pixel[1] = src_col_l; |
| pos_pixel[2] = src_col_l + 1; |
| pos_pixel[3] = src_col_l + 2; |
| pos_pixel[4] = src_row_l - 1; |
| pos_pixel[5] = src_row_l; |
| pos_pixel[6] = src_row_l + 1; |
| pos_pixel[7] = src_row_l + 2; |
| for (int i = 0; i < 4; i++) { |
| pos_pixel[i] = pdfium::clamp(pos_pixel[i], 0, stretch_width - 1); |
| pos_pixel[i + 4] = pdfium::clamp(pos_pixel[i + 4], 0, stretch_height - 1); |
| } |
| u_w[0] = SDP_Table[256 + res_x]; |
| u_w[1] = SDP_Table[res_x]; |
| u_w[2] = SDP_Table[256 - res_x]; |
| u_w[3] = SDP_Table[512 - res_x]; |
| v_w[0] = SDP_Table[256 + res_y]; |
| v_w[1] = SDP_Table[res_y]; |
| v_w[2] = SDP_Table[256 - res_y]; |
| v_w[3] = SDP_Table[512 - res_y]; |
| } |
| |
| FXDIB_Format GetTransformedFormat(const RetainPtr<CFX_DIBSource>& pDrc) { |
| if (pDrc->IsAlphaMask()) |
| return FXDIB_8bppMask; |
| |
| FXDIB_Format format = pDrc->GetFormat(); |
| if (format >= 1025) |
| return FXDIB_Cmyka; |
| if (format <= 32 || format == FXDIB_Argb) |
| return FXDIB_Argb; |
| return FXDIB_Rgba; |
| } |
| |
| void WriteMonoResult(uint32_t r_bgra_cmyk, FXDIB_Format format, uint8_t* dest) { |
| if (format == FXDIB_Rgba) { |
| dest[0] = static_cast<uint8_t>(r_bgra_cmyk >> 24); |
| dest[1] = static_cast<uint8_t>(r_bgra_cmyk >> 16); |
| dest[2] = static_cast<uint8_t>(r_bgra_cmyk >> 8); |
| } else { |
| *reinterpret_cast<uint32_t*>(dest) = r_bgra_cmyk; |
| } |
| } |
| |
| void WriteColorResult(std::function<uint8_t(int offset)> func, |
| bool bHasAlpha, |
| FXDIB_Format format, |
| uint8_t* dest) { |
| uint8_t blue_c = func(0); |
| uint8_t green_m = func(1); |
| uint8_t red_y = func(2); |
| |
| uint32_t* dest32 = reinterpret_cast<uint32_t*>(dest); |
| if (bHasAlpha) { |
| if (format == FXDIB_Argb) { |
| *dest32 = FXARGB_TODIB(ArgbEncode(func(3), red_y, green_m, blue_c)); |
| } else if (format == FXDIB_Rgba) { |
| dest[0] = blue_c; |
| dest[1] = green_m; |
| dest[2] = red_y; |
| } else { |
| *dest32 = FXCMYK_TODIB(CmykEncode(blue_c, green_m, red_y, func(3))); |
| } |
| return; |
| } |
| |
| if (format == FXDIB_Cmyka) { |
| *dest32 = FXCMYK_TODIB(CmykEncode(blue_c, green_m, red_y, func(3))); |
| } else { |
| *dest32 = FXARGB_TODIB(ArgbEncode(kOpaqueAlpha, red_y, green_m, blue_c)); |
| } |
| } |
| |
| class CPDF_FixedMatrix { |
| public: |
| explicit CPDF_FixedMatrix(const CFX_Matrix& src) |
| : a(FXSYS_round(src.a * kBase)), |
| b(FXSYS_round(src.b * kBase)), |
| c(FXSYS_round(src.c * kBase)), |
| d(FXSYS_round(src.d * kBase)), |
| e(FXSYS_round(src.e * kBase)), |
| f(FXSYS_round(src.f * kBase)) {} |
| |
| void Transform(int x, int y, int* x1, int* y1) const { |
| std::pair<float, float> val = TransformInternal(x, y); |
| *x1 = pdfium::base::saturated_cast<int>(val.first / kBase); |
| *y1 = pdfium::base::saturated_cast<int>(val.second / kBase); |
| } |
| |
| protected: |
| std::pair<float, float> TransformInternal(float x, float y) const { |
| return std::make_pair(a * x + c * y + e + kBase / 2, |
| b * x + d * y + f + kBase / 2); |
| } |
| |
| const int a; |
| const int b; |
| const int c; |
| const int d; |
| const int e; |
| const int f; |
| }; |
| |
| class CFX_BilinearMatrix : public CPDF_FixedMatrix { |
| public: |
| explicit CFX_BilinearMatrix(const CFX_Matrix& src) : CPDF_FixedMatrix(src) {} |
| |
| void Transform(int x, int y, int* x1, int* y1, int* res_x, int* res_y) const { |
| std::pair<float, float> val = TransformInternal(x, y); |
| *x1 = pdfium::base::saturated_cast<int>(val.first / kBase); |
| *y1 = pdfium::base::saturated_cast<int>(val.second / kBase); |
| |
| *res_x = static_cast<int>(val.first) % kBase; |
| *res_y = static_cast<int>(val.second) % kBase; |
| if (*res_x < 0 && *res_x > -kBase) |
| *res_x = kBase + *res_x; |
| if (*res_y < 0 && *res_y > -kBase) |
| *res_y = kBase + *res_y; |
| } |
| }; |
| |
| } // namespace |
| |
| CFX_ImageTransformer::CFX_ImageTransformer(const RetainPtr<CFX_DIBSource>& pSrc, |
| const CFX_Matrix* pMatrix, |
| int flags, |
| const FX_RECT* pClip) |
| : m_pSrc(pSrc), |
| m_pMatrix(pMatrix), |
| m_pClip(pClip), |
| m_Flags(flags), |
| m_Status(0) { |
| FX_RECT result_rect = m_pMatrix->GetUnitRect().GetClosestRect(); |
| FX_RECT result_clip = result_rect; |
| if (m_pClip) |
| result_clip.Intersect(*m_pClip); |
| |
| if (result_clip.IsEmpty()) |
| return; |
| |
| m_result = result_clip; |
| if (fabs(m_pMatrix->a) < fabs(m_pMatrix->b) / 20 && |
| fabs(m_pMatrix->d) < fabs(m_pMatrix->c) / 20 && |
| fabs(m_pMatrix->a) < 0.5f && fabs(m_pMatrix->d) < 0.5f) { |
| int dest_width = result_rect.Width(); |
| int dest_height = result_rect.Height(); |
| result_clip.Offset(-result_rect.left, -result_rect.top); |
| result_clip = FXDIB_SwapClipBox(result_clip, dest_width, dest_height, |
| m_pMatrix->c > 0, m_pMatrix->b < 0); |
| m_Stretcher = pdfium::MakeUnique<CFX_ImageStretcher>( |
| &m_Storer, m_pSrc, dest_height, dest_width, result_clip, m_Flags); |
| m_Stretcher->Start(); |
| m_Status = 1; |
| return; |
| } |
| if (fabs(m_pMatrix->b) < kFix16 && fabs(m_pMatrix->c) < kFix16) { |
| int dest_width = static_cast<int>(m_pMatrix->a > 0 ? ceil(m_pMatrix->a) |
| : floor(m_pMatrix->a)); |
| int dest_height = static_cast<int>(m_pMatrix->d > 0 ? -ceil(m_pMatrix->d) |
| : -floor(m_pMatrix->d)); |
| result_clip.Offset(-result_rect.left, -result_rect.top); |
| m_Stretcher = pdfium::MakeUnique<CFX_ImageStretcher>( |
| &m_Storer, m_pSrc, dest_width, dest_height, result_clip, m_Flags); |
| m_Stretcher->Start(); |
| m_Status = 2; |
| return; |
| } |
| int stretch_width = |
| static_cast<int>(ceil(FXSYS_sqrt2(m_pMatrix->a, m_pMatrix->b))); |
| int stretch_height = |
| static_cast<int>(ceil(FXSYS_sqrt2(m_pMatrix->c, m_pMatrix->d))); |
| CFX_Matrix stretch2dest(1.0f, 0.0f, 0.0f, -1.0f, 0.0f, stretch_height); |
| stretch2dest.Concat( |
| CFX_Matrix(m_pMatrix->a / stretch_width, m_pMatrix->b / stretch_width, |
| m_pMatrix->c / stretch_height, m_pMatrix->d / stretch_height, |
| m_pMatrix->e, m_pMatrix->f)); |
| m_dest2stretch = stretch2dest.GetInverse(); |
| |
| m_StretchClip = |
| m_dest2stretch.TransformRect(CFX_FloatRect(result_clip)).GetOuterRect(); |
| m_StretchClip.Intersect(0, 0, stretch_width, stretch_height); |
| m_Stretcher = pdfium::MakeUnique<CFX_ImageStretcher>( |
| &m_Storer, m_pSrc, stretch_width, stretch_height, m_StretchClip, m_Flags); |
| m_Stretcher->Start(); |
| m_Status = 3; |
| } |
| |
| CFX_ImageTransformer::~CFX_ImageTransformer() {} |
| |
| bool CFX_ImageTransformer::Continue(PauseIndicatorIface* pPause) { |
| if (m_Status == 1) { |
| if (m_Stretcher->Continue(pPause)) |
| return true; |
| |
| if (m_Storer.GetBitmap()) { |
| m_Storer.Replace( |
| m_Storer.GetBitmap()->SwapXY(m_pMatrix->c > 0, m_pMatrix->b < 0)); |
| } |
| return false; |
| } |
| |
| if (m_Status == 2) |
| return m_Stretcher->Continue(pPause); |
| if (m_Status != 3) |
| return false; |
| if (m_Stretcher->Continue(pPause)) |
| return true; |
| |
| if (!m_Storer.GetBitmap()) |
| return false; |
| |
| auto pTransformed = pdfium::MakeRetain<CFX_DIBitmap>(); |
| FXDIB_Format format = GetTransformedFormat(m_Stretcher->source()); |
| if (!pTransformed->Create(m_result.Width(), m_result.Height(), format)) |
| return false; |
| |
| const auto& pSrcMask = m_Storer.GetBitmap()->m_pAlphaMask; |
| const uint8_t* pSrcMaskBuf = pSrcMask ? pSrcMask->GetBuffer() : nullptr; |
| |
| pTransformed->Clear(0); |
| auto& pDestMask = pTransformed->m_pAlphaMask; |
| if (pDestMask) |
| pDestMask->Clear(0); |
| |
| CFX_Matrix result2stretch(1.0f, 0.0f, 0.0f, 1.0f, m_result.left, |
| m_result.top); |
| result2stretch.Concat(m_dest2stretch); |
| result2stretch.Translate(-m_StretchClip.left, -m_StretchClip.top); |
| if (!pSrcMaskBuf && pDestMask) { |
| pDestMask->Clear(0xff000000); |
| } else if (pDestMask) { |
| CalcData cdata = { |
| pDestMask.Get(), result2stretch, pSrcMaskBuf, |
| m_Storer.GetBitmap()->m_pAlphaMask->GetPitch(), |
| }; |
| CalcMask(cdata); |
| } |
| |
| CalcData cdata = {pTransformed.Get(), result2stretch, |
| m_Storer.GetBitmap()->GetBuffer(), |
| m_Storer.GetBitmap()->GetPitch()}; |
| if (m_Storer.GetBitmap()->IsAlphaMask()) { |
| CalcAlpha(cdata); |
| } else { |
| int Bpp = m_Storer.GetBitmap()->GetBPP() / 8; |
| if (Bpp == 1) |
| CalcMono(cdata, format); |
| else |
| CalcColor(cdata, format, Bpp); |
| } |
| m_Storer.Replace(std::move(pTransformed)); |
| return false; |
| } |
| |
| RetainPtr<CFX_DIBitmap> CFX_ImageTransformer::DetachBitmap() { |
| return m_Storer.Detach(); |
| } |
| |
| void CFX_ImageTransformer::CalcMask(const CalcData& cdata) { |
| if (IsBilinear()) { |
| auto func = [&cdata](const BilinearData& data, uint8_t* dest) { |
| *dest = bilinear_interpol(cdata.buf, data.row_offset_l, data.row_offset_r, |
| data.src_col_l, data.src_col_r, data.res_x, |
| data.res_y, 1, 0); |
| }; |
| DoBilinearLoop(cdata, 1, func); |
| } else if (IsBiCubic()) { |
| auto func = [&cdata](const BicubicData& data, uint8_t* dest) { |
| *dest = bicubic_interpol(cdata.buf, cdata.pitch, data.pos_pixel, data.u_w, |
| data.v_w, data.res_x, data.res_y, 1, 0); |
| }; |
| DoBicubicLoop(cdata, 1, func); |
| } else { |
| auto func = [&cdata](const DownSampleData& data, uint8_t* dest) { |
| *dest = cdata.buf[data.src_row * cdata.pitch + data.src_col]; |
| }; |
| DoDownSampleLoop(cdata, 1, func); |
| } |
| } |
| |
| void CFX_ImageTransformer::CalcAlpha(const CalcData& cdata) { |
| if (IsBilinear()) { |
| auto func = [&cdata](const BilinearData& data, uint8_t* dest) { |
| *dest = bilinear_interpol(cdata.buf, data.row_offset_l, data.row_offset_r, |
| data.src_col_l, data.src_col_r, data.res_x, |
| data.res_y, 1, 0); |
| }; |
| DoBilinearLoop(cdata, 1, func); |
| } else if (IsBiCubic()) { |
| auto func = [&cdata](const BicubicData& data, uint8_t* dest) { |
| *dest = bicubic_interpol(cdata.buf, cdata.pitch, data.pos_pixel, data.u_w, |
| data.v_w, data.res_x, data.res_y, 1, 0); |
| }; |
| DoBicubicLoop(cdata, 1, func); |
| } else { |
| auto func = [&cdata](const DownSampleData& data, uint8_t* dest) { |
| const uint8_t* src_pixel = |
| cdata.buf + cdata.pitch * data.src_row + data.src_col; |
| *dest = *src_pixel; |
| }; |
| DoDownSampleLoop(cdata, 1, func); |
| } |
| } |
| |
| void CFX_ImageTransformer::CalcMono(const CalcData& cdata, |
| FXDIB_Format format) { |
| uint32_t argb[256]; |
| FX_ARGB* pPal = m_Storer.GetBitmap()->GetPalette(); |
| if (pPal) { |
| for (size_t i = 0; i < FX_ArraySize(argb); i++) |
| argb[i] = pPal[i]; |
| } else if (m_Storer.GetBitmap()->IsCmykImage()) { |
| for (size_t i = 0; i < FX_ArraySize(argb); i++) |
| argb[i] = 255 - i; |
| } else { |
| for (size_t i = 0; i < FX_ArraySize(argb); i++) |
| argb[i] = 0xff000000 | (i * 0x010101); |
| } |
| int destBpp = cdata.bitmap->GetBPP() / 8; |
| if (IsBilinear()) { |
| auto func = [&cdata, format, &argb](const BilinearData& data, |
| uint8_t* dest) { |
| uint8_t idx = bilinear_interpol( |
| cdata.buf, data.row_offset_l, data.row_offset_r, data.src_col_l, |
| data.src_col_r, data.res_x, data.res_y, 1, 0); |
| uint32_t r_bgra_cmyk = argb[idx]; |
| WriteMonoResult(r_bgra_cmyk, format, dest); |
| }; |
| DoBilinearLoop(cdata, destBpp, func); |
| } else if (IsBiCubic()) { |
| auto func = [&cdata, format, &argb](const BicubicData& data, |
| uint8_t* dest) { |
| uint32_t r_bgra_cmyk = argb[bicubic_interpol( |
| cdata.buf, cdata.pitch, data.pos_pixel, data.u_w, data.v_w, |
| data.res_x, data.res_y, 1, 0)]; |
| WriteMonoResult(r_bgra_cmyk, format, dest); |
| }; |
| DoBicubicLoop(cdata, destBpp, func); |
| } else { |
| auto func = [&cdata, format, &argb](const DownSampleData& data, |
| uint8_t* dest) { |
| uint32_t r_bgra_cmyk = |
| argb[cdata.buf[data.src_row * cdata.pitch + data.src_col]]; |
| WriteMonoResult(r_bgra_cmyk, format, dest); |
| }; |
| DoDownSampleLoop(cdata, destBpp, func); |
| } |
| } |
| |
| void CFX_ImageTransformer::CalcColor(const CalcData& cdata, |
| FXDIB_Format format, |
| int Bpp) { |
| bool bHasAlpha = m_Storer.GetBitmap()->HasAlpha(); |
| int destBpp = cdata.bitmap->GetBPP() / 8; |
| if (IsBilinear()) { |
| auto func = [&cdata, format, Bpp, bHasAlpha](const BilinearData& data, |
| uint8_t* dest) { |
| auto bilinear_interpol_func = [&cdata, &data, Bpp](int offset) { |
| return bilinear_interpol( |
| cdata.buf, data.row_offset_l, data.row_offset_r, data.src_col_l, |
| data.src_col_r, data.res_x, data.res_y, Bpp, offset); |
| }; |
| WriteColorResult(bilinear_interpol_func, bHasAlpha, format, dest); |
| }; |
| DoBilinearLoop(cdata, destBpp, func); |
| } else if (IsBiCubic()) { |
| auto func = [&cdata, format, Bpp, bHasAlpha](const BicubicData& data, |
| uint8_t* dest) { |
| auto bicubic_interpol_func = [&cdata, &data, Bpp](int offset) { |
| return bicubic_interpol(cdata.buf, cdata.pitch, data.pos_pixel, |
| data.u_w, data.v_w, data.res_x, data.res_y, Bpp, |
| offset); |
| }; |
| WriteColorResult(bicubic_interpol_func, bHasAlpha, format, dest); |
| }; |
| DoBicubicLoop(cdata, destBpp, func); |
| } else { |
| auto func = [&cdata, format, bHasAlpha, Bpp](const DownSampleData& data, |
| uint8_t* dest) { |
| const uint8_t* src_pos = |
| cdata.buf + data.src_row * cdata.pitch + data.src_col * Bpp; |
| auto sample_func = [src_pos](int offset) { return src_pos[offset]; }; |
| WriteColorResult(sample_func, bHasAlpha, format, dest); |
| }; |
| DoDownSampleLoop(cdata, destBpp, func); |
| } |
| } |
| |
| void CFX_ImageTransformer::AdjustCoords(int* col, int* row) const { |
| int& src_col = *col; |
| int& src_row = *row; |
| if (src_col == stretch_width()) |
| src_col--; |
| if (src_row == stretch_height()) |
| src_row--; |
| } |
| |
| void CFX_ImageTransformer::DoBilinearLoop( |
| const CalcData& cdata, |
| int increment, |
| std::function<void(const BilinearData&, uint8_t*)> func) { |
| CFX_BilinearMatrix matrix_fix(cdata.matrix); |
| for (int row = 0; row < m_result.Height(); row++) { |
| uint8_t* dest = const_cast<uint8_t*>(cdata.bitmap->GetScanline(row)); |
| for (int col = 0; col < m_result.Width(); col++) { |
| BilinearData d; |
| d.res_x = 0; |
| d.res_y = 0; |
| d.src_col_l = 0; |
| d.src_row_l = 0; |
| matrix_fix.Transform(col, row, &d.src_col_l, &d.src_row_l, &d.res_x, |
| &d.res_y); |
| if (LIKELY(InStretchBounds(d.src_col_l, d.src_row_l))) { |
| AdjustCoords(&d.src_col_l, &d.src_row_l); |
| d.src_col_r = d.src_col_l + 1; |
| d.src_row_r = d.src_row_l + 1; |
| AdjustCoords(&d.src_col_r, &d.src_row_r); |
| d.row_offset_l = d.src_row_l * cdata.pitch; |
| d.row_offset_r = d.src_row_r * cdata.pitch; |
| func(d, dest); |
| } |
| dest += increment; |
| } |
| } |
| } |
| |
| void CFX_ImageTransformer::DoBicubicLoop( |
| const CalcData& cdata, |
| int increment, |
| std::function<void(const BicubicData&, uint8_t*)> func) { |
| CFX_BilinearMatrix matrix_fix(cdata.matrix); |
| for (int row = 0; row < m_result.Height(); row++) { |
| uint8_t* dest = const_cast<uint8_t*>(cdata.bitmap->GetScanline(row)); |
| for (int col = 0; col < m_result.Width(); col++) { |
| BicubicData d; |
| d.res_x = 0; |
| d.res_y = 0; |
| d.src_col_l = 0; |
| d.src_row_l = 0; |
| matrix_fix.Transform(col, row, &d.src_col_l, &d.src_row_l, &d.res_x, |
| &d.res_y); |
| if (LIKELY(InStretchBounds(d.src_col_l, d.src_row_l))) { |
| AdjustCoords(&d.src_col_l, &d.src_row_l); |
| bicubic_get_pos_weight(d.pos_pixel, d.u_w, d.v_w, d.src_col_l, |
| d.src_row_l, d.res_x, d.res_y, stretch_width(), |
| stretch_height()); |
| func(d, dest); |
| } |
| dest += increment; |
| } |
| } |
| } |
| |
| void CFX_ImageTransformer::DoDownSampleLoop( |
| const CalcData& cdata, |
| int increment, |
| std::function<void(const DownSampleData&, uint8_t*)> func) { |
| CPDF_FixedMatrix matrix_fix(cdata.matrix); |
| for (int row = 0; row < m_result.Height(); row++) { |
| uint8_t* dest = const_cast<uint8_t*>(cdata.bitmap->GetScanline(row)); |
| for (int col = 0; col < m_result.Width(); col++) { |
| DownSampleData d; |
| d.src_col = 0; |
| d.src_row = 0; |
| matrix_fix.Transform(col, row, &d.src_col, &d.src_row); |
| if (LIKELY(InStretchBounds(d.src_col, d.src_row))) { |
| AdjustCoords(&d.src_col, &d.src_row); |
| func(d, dest); |
| } |
| dest += increment; |
| } |
| } |
| } |