aboutsummaryrefslogtreecommitdiff
path: root/core/fxge/skia
diff options
context:
space:
mode:
Diffstat (limited to 'core/fxge/skia')
-rw-r--r--core/fxge/skia/DEPS8
-rw-r--r--core/fxge/skia/cfx_dibbase_skia.cpp288
-rw-r--r--core/fxge/skia/fx_skia_device.cpp397
-rw-r--r--core/fxge/skia/fx_skia_device.h12
-rw-r--r--core/fxge/skia/fx_skia_device_embeddertest.cpp119
5 files changed, 502 insertions, 322 deletions
diff --git a/core/fxge/skia/DEPS b/core/fxge/skia/DEPS
index 17c026539..9927648c3 100644
--- a/core/fxge/skia/DEPS
+++ b/core/fxge/skia/DEPS
@@ -1,5 +1,9 @@
include_rules = [
- '+fpdfsdk',
- '+public',
'+third_party/skia/include',
]
+
+specific_include_rules = {
+ 'fx_skia_device_embeddertest.cpp': [
+ '+fpdfsdk',
+ ]
+}
diff --git a/core/fxge/skia/cfx_dibbase_skia.cpp b/core/fxge/skia/cfx_dibbase_skia.cpp
new file mode 100644
index 000000000..417aa93b6
--- /dev/null
+++ b/core/fxge/skia/cfx_dibbase_skia.cpp
@@ -0,0 +1,288 @@
+// Copyright 2023 The PDFium Authors
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "core/fxge/dib/cfx_dibbase.h"
+
+#include <stddef.h>
+#include <stdint.h>
+
+#include <memory>
+#include <type_traits>
+#include <utility>
+
+#include "core/fxcrt/fx_2d_size.h"
+#include "core/fxcrt/fx_memory.h"
+#include "core/fxcrt/fx_memory_wrappers.h"
+#include "core/fxcrt/fx_safe_types.h"
+#include "core/fxcrt/retain_ptr.h"
+#include "core/fxge/calculate_pitch.h"
+#include "core/fxge/dib/cfx_dibitmap.h"
+#include "core/fxge/dib/fx_dib.h"
+#include "third_party/base/check_op.h"
+#include "third_party/base/containers/span.h"
+#include "third_party/base/notreached.h"
+#include "third_party/skia/include/core/SkAlphaType.h"
+#include "third_party/skia/include/core/SkColor.h"
+#include "third_party/skia/include/core/SkColorPriv.h"
+#include "third_party/skia/include/core/SkColorType.h"
+#include "third_party/skia/include/core/SkImage.h"
+#include "third_party/skia/include/core/SkImageInfo.h"
+#include "third_party/skia/include/core/SkPixmap.h"
+#include "third_party/skia/include/core/SkRefCnt.h"
+
+namespace {
+
+// Releases `CFX_DIBBase` "leaked" by `CreateSkiaImageFromDib()`.
+void ReleaseRetainedHeldBySkImage(const void* /*pixels*/,
+ SkImages::ReleaseContext context) {
+ RetainPtr<const CFX_DIBBase> retained;
+ retained.Unleak(reinterpret_cast<const CFX_DIBBase*>(context));
+}
+
+// Creates an `SkImage` from a `CFX_DIBBase`, sharing the underlying pixels if
+// possible.
+//
+// Note that an `SkImage` must be immutable, so if sharing pixels, they must not
+// be modified during the lifetime of the `SkImage`.
+sk_sp<SkImage> CreateSkiaImageFromDib(const CFX_DIBBase* source,
+ SkColorType color_type,
+ SkAlphaType alpha_type) {
+ // Make sure the DIB is backed by a buffer.
+ RetainPtr<const CFX_DIBBase> retained;
+ if (source->GetBuffer().empty()) {
+ retained = source->Realize();
+ if (!retained) {
+ return nullptr;
+ }
+ DCHECK(!retained->GetBuffer().empty());
+ } else {
+ retained.Reset(source);
+ }
+
+ // Convert unowned pointer to a retained pointer, then "leak" to `SkImage`.
+ source = retained.Leak();
+ SkImageInfo info = SkImageInfo::Make(source->GetWidth(), source->GetHeight(),
+ color_type, alpha_type);
+ return SkImages::RasterFromPixmap(
+ SkPixmap(info, source->GetBuffer().data(), source->GetPitch()),
+ /*rasterReleaseProc=*/ReleaseRetainedHeldBySkImage,
+ /*releaseContext=*/const_cast<CFX_DIBBase*>(source));
+}
+
+// Releases allocated memory "leaked" by `CreateSkiaImageFromTransformedDib()`.
+void ReleaseAllocatedHeldBySkImage(const void* pixels,
+ SkImages::ReleaseContext /*context*/) {
+ FX_Free(const_cast<void*>(pixels));
+}
+
+// Template defining traits of a pixel transform function.
+template <size_t source_bits_per_pixel, typename PixelTransform>
+class PixelTransformTraits;
+
+template <typename PixelTransform>
+class PixelTransformTraits<1, PixelTransform> {
+ public:
+ using Result = std::invoke_result_t<PixelTransform, bool>;
+
+ static Result Invoke(PixelTransform&& pixel_transform,
+ const uint8_t* scanline,
+ size_t column) {
+ uint8_t kMask = 1 << (7 - column % 8);
+ return pixel_transform(!!(scanline[column / 8] & kMask));
+ }
+};
+
+template <typename PixelTransform>
+class PixelTransformTraits<8, PixelTransform> {
+ public:
+ using Result = std::invoke_result_t<PixelTransform, uint8_t>;
+
+ static Result Invoke(PixelTransform&& pixel_transform,
+ const uint8_t* scanline,
+ size_t column) {
+ return pixel_transform(scanline[column]);
+ }
+};
+
+template <typename PixelTransform>
+class PixelTransformTraits<24, PixelTransform> {
+ public:
+ using Result =
+ std::invoke_result_t<PixelTransform, uint8_t, uint8_t, uint8_t>;
+
+ static Result Invoke(PixelTransform&& pixel_transform,
+ const uint8_t* scanline,
+ size_t column) {
+ size_t offset = column * 3;
+ return pixel_transform(scanline[offset + 2], scanline[offset + 1],
+ scanline[offset]);
+ }
+};
+
+void ValidateScanlineSize(pdfium::span<const uint8_t> scanline,
+ size_t min_row_bytes) {
+ DCHECK_GE(scanline.size(), min_row_bytes);
+}
+
+void ValidateBufferSize(pdfium::span<const uint8_t> buffer,
+ const CFX_DIBBase& source) {
+#if DCHECK_IS_ON()
+ if (source.GetHeight() == 0) {
+ return;
+ }
+
+ FX_SAFE_SIZE_T buffer_size = source.GetHeight() - 1;
+ buffer_size *= source.GetPitch();
+ buffer_size += fxge::CalculatePitch8OrDie(source.GetBPP(), /*components=*/1,
+ source.GetWidth());
+
+ DCHECK_GE(buffer.size(), buffer_size.ValueOrDie());
+#endif // DCHECK_IS_ON()
+}
+
+// Creates an `SkImage` from a `CFX_DIBBase`, transforming the source pixels
+// using `pixel_transform`.
+//
+// TODO(crbug.com/pdfium/2048): Consolidate with `CFX_DIBBase::ConvertBuffer()`.
+template <size_t source_bits_per_pixel, typename PixelTransform>
+sk_sp<SkImage> CreateSkiaImageFromTransformedDib(
+ const CFX_DIBBase& source,
+ SkColorType color_type,
+ SkAlphaType alpha_type,
+ PixelTransform&& pixel_transform) {
+ using Traits = PixelTransformTraits<source_bits_per_pixel, PixelTransform>;
+ using Result = typename Traits::Result;
+
+ // Allocate output buffer.
+ const int width = source.GetWidth();
+ const int height = source.GetHeight();
+ SkImageInfo info = SkImageInfo::Make(width, height, color_type, alpha_type);
+ DCHECK_EQ(info.minRowBytes(), width * sizeof(Result));
+
+ size_t output_size = Fx2DSizeOrDie(info.minRowBytes(), height);
+ std::unique_ptr<void, FxFreeDeleter> output(
+ FX_TryAlloc(uint8_t, output_size));
+ if (!output) {
+ return nullptr;
+ }
+
+ // Transform source pixels to output pixels.
+ pdfium::span<const uint8_t> source_buffer = source.GetBuffer();
+ Result* output_cursor = reinterpret_cast<Result*>(output.get());
+ if (source_buffer.empty()) {
+ // No buffer; iterate by individual scanline.
+ const size_t min_row_bytes =
+ fxge::CalculatePitch8OrDie(source.GetBPP(), /*components=*/1, width);
+ DCHECK_LE(min_row_bytes, source.GetPitch());
+
+ int line = 0;
+ for (int row = 0; row < height; ++row) {
+ pdfium::span<const uint8_t> scanline = source.GetScanline(line++);
+ ValidateScanlineSize(scanline, min_row_bytes);
+
+ for (int column = 0; column < width; ++column) {
+ *output_cursor++ =
+ Traits::Invoke(std::forward<PixelTransform>(pixel_transform),
+ scanline.data(), column);
+ }
+ }
+ } else {
+ // Iterate over the entire buffer.
+ ValidateBufferSize(source_buffer, source);
+ const size_t row_bytes = source.GetPitch();
+
+ const uint8_t* next_scanline = source_buffer.data();
+ for (int row = 0; row < height; ++row) {
+ const uint8_t* scanline = next_scanline;
+ next_scanline += row_bytes;
+
+ for (int column = 0; column < width; ++column) {
+ *output_cursor++ = Traits::Invoke(
+ std::forward<PixelTransform>(pixel_transform), scanline, column);
+ }
+ }
+ }
+
+ // "Leak" allocated memory to `SkImage`.
+ return SkImages::RasterFromPixmap(
+ SkPixmap(info, output.release(), info.minRowBytes()),
+ /*rasterReleaseProc=*/ReleaseAllocatedHeldBySkImage,
+ /*releaseContext=*/nullptr);
+}
+
+bool IsRGBColorGrayScale(uint32_t color) {
+ return FXARGB_R(color) == FXARGB_G(color) &&
+ FXARGB_R(color) == FXARGB_B(color);
+}
+
+} // namespace
+
+sk_sp<SkImage> CFX_DIBBase::RealizeSkImage() const {
+ switch (GetBPP()) {
+ case 1: {
+ // By default, the two colors for grayscale are 0xFF and 0x00 unless they
+ // are specified in the palette.
+ uint8_t color0 = 0x00;
+ uint8_t color1 = 0xFF;
+
+ if (GetFormat() == FXDIB_Format::k1bppRgb && HasPalette()) {
+ uint32_t palette_color0 = GetPaletteArgb(0);
+ uint32_t palette_color1 = GetPaletteArgb(1);
+ bool use_gray_colors = IsRGBColorGrayScale(palette_color0) &&
+ IsRGBColorGrayScale(palette_color1);
+ if (!use_gray_colors) {
+ return CreateSkiaImageFromTransformedDib</*source_bits_per_pixel=*/1>(
+ *this, kBGRA_8888_SkColorType, kPremul_SkAlphaType,
+ [palette_color0, palette_color1](bool bit) {
+ return bit ? palette_color1 : palette_color0;
+ });
+ }
+
+ color0 = FXARGB_R(palette_color0);
+ color1 = FXARGB_R(palette_color1);
+ }
+
+ return CreateSkiaImageFromTransformedDib</*source_bits_per_pixel=*/1>(
+ *this, IsMaskFormat() ? kAlpha_8_SkColorType : kGray_8_SkColorType,
+ kPremul_SkAlphaType,
+ [color0, color1](bool bit) { return bit ? color1 : color0; });
+ }
+
+ case 8:
+ // we upscale ctables to 32bit.
+ if (HasPalette()) {
+ return CreateSkiaImageFromTransformedDib</*source_bits_per_pixel=*/8>(
+ *this, kBGRA_8888_SkColorType, kPremul_SkAlphaType,
+ [palette = GetPaletteSpan().first(GetRequiredPaletteSize())](
+ uint8_t index) {
+ if (index >= palette.size()) {
+ index = 0;
+ }
+ return palette[index];
+ });
+ }
+ return CreateSkiaImageFromDib(
+ this, IsMaskFormat() ? kAlpha_8_SkColorType : kGray_8_SkColorType,
+ kPremul_SkAlphaType);
+
+ case 24:
+ return CreateSkiaImageFromTransformedDib</*source_bits_per_pixel=*/24>(
+ *this, kBGRA_8888_SkColorType, kOpaque_SkAlphaType,
+ [](uint8_t red, uint8_t green, uint8_t blue) {
+ return SkPackARGB32NoCheck(0xFF, red, green, blue);
+ });
+
+ case 32:
+ return CreateSkiaImageFromDib(
+ this, kBGRA_8888_SkColorType,
+ IsPremultiplied() ? kPremul_SkAlphaType : kUnpremul_SkAlphaType);
+
+ default:
+ NOTREACHED_NORETURN();
+ }
+}
+
+bool CFX_DIBBase::IsPremultiplied() const {
+ return false;
+}
diff --git a/core/fxge/skia/fx_skia_device.cpp b/core/fxge/skia/fx_skia_device.cpp
index ef3218d7f..6bff316e5 100644
--- a/core/fxge/skia/fx_skia_device.cpp
+++ b/core/fxge/skia/fx_skia_device.cpp
@@ -5,6 +5,7 @@
#include "core/fxge/skia/fx_skia_device.h"
#include <math.h>
+#include <stddef.h>
#include <stdint.h>
#include <algorithm>
@@ -28,9 +29,9 @@
#include "core/fxcrt/data_vector.h"
#include "core/fxcrt/fx_2d_size.h"
#include "core/fxcrt/fx_coordinates.h"
+#include "core/fxcrt/fx_memory.h"
#include "core/fxcrt/fx_system.h"
#include "core/fxcrt/stl_util.h"
-#include "core/fxge/calculate_pitch.h"
#include "core/fxge/cfx_defaultrenderdevice.h"
#include "core/fxge/cfx_fillrenderoptions.h"
#include "core/fxge/cfx_font.h"
@@ -46,11 +47,10 @@
#include "core/fxge/text_char_pos.h"
#include "third_party/base/check.h"
#include "third_party/base/check_op.h"
+#include "third_party/base/containers/span.h"
+#include "third_party/base/memory/ptr_util.h"
#include "third_party/base/notreached.h"
#include "third_party/base/numerics/safe_conversions.h"
-#include "third_party/base/ptr_util.h"
-#include "third_party/base/span.h"
-#include "third_party/skia/include/core/SkBitmap.h"
#include "third_party/skia/include/core/SkBlendMode.h"
#include "third_party/skia/include/core/SkCanvas.h"
#include "third_party/skia/include/core/SkClipOp.h"
@@ -64,14 +64,16 @@
#include "third_party/skia/include/core/SkPath.h"
#include "third_party/skia/include/core/SkPathEffect.h"
#include "third_party/skia/include/core/SkPathUtils.h"
-#include "third_party/skia/include/core/SkPictureRecorder.h"
+#include "third_party/skia/include/core/SkPixmap.h"
#include "third_party/skia/include/core/SkRSXform.h"
#include "third_party/skia/include/core/SkRect.h"
#include "third_party/skia/include/core/SkRefCnt.h"
#include "third_party/skia/include/core/SkSamplingOptions.h"
#include "third_party/skia/include/core/SkShader.h"
#include "third_party/skia/include/core/SkStream.h"
+#include "third_party/skia/include/core/SkSurface.h"
#include "third_party/skia/include/core/SkTextBlob.h"
+#include "third_party/skia/include/core/SkTileMode.h"
#include "third_party/skia/include/core/SkTypeface.h"
#include "third_party/skia/include/effects/SkDashPathEffect.h"
#include "third_party/skia/include/effects/SkGradientShader.h"
@@ -154,66 +156,6 @@ void DebugShowSkiaDrawRect(CFX_SkiaDeviceDriver* driver,
#endif // SHOW_SKIA_PATH
}
-bool IsRGBColorGrayScale(uint32_t color) {
- return FXARGB_R(color) == FXARGB_G(color) &&
- FXARGB_R(color) == FXARGB_B(color);
-}
-
-// Called by Upsample, return a 32 bit-per-pixel buffer filled with 2 colors
-// from a 1 bit-per-pixel source palette.
-DataVector<uint32_t> Fill32BppDestStorageWith1BppSource(
- const RetainPtr<CFX_DIBBase>& source) {
- DCHECK_EQ(1, source->GetBPP());
- int width = source->GetWidth();
- int height = source->GetHeight();
- void* buffer = source->GetBuffer().data();
- DCHECK(buffer);
-
- uint32_t color0 = source->GetPaletteArgb(0);
- uint32_t color1 = source->GetPaletteArgb(1);
- DataVector<uint32_t> dst32_storage(Fx2DSizeOrDie(width, height));
- pdfium::span<SkPMColor> dst32_pixels(dst32_storage);
-
- for (int y = 0; y < height; ++y) {
- const uint8_t* src_row =
- static_cast<const uint8_t*>(buffer) + y * source->GetPitch();
- pdfium::span<uint32_t> dst_row = dst32_pixels.subspan(y * width);
- for (int x = 0; x < width; ++x) {
- bool use_color1 = src_row[x / 8] & (1 << (7 - x % 8));
- dst_row[x] = use_color1 ? color1 : color0;
- }
- }
- return dst32_storage;
-}
-
-// Called by Upsample(), returns a 32 bit-per-pixel buffer filled with colors
-// from `palette`.
-DataVector<uint32_t> Fill32BppDestStorageWithPalette(
- const RetainPtr<CFX_DIBBase>& source,
- pdfium::span<const uint32_t> palette) {
- DCHECK_EQ(8, source->GetBPP());
- int width = source->GetWidth();
- int height = source->GetHeight();
- void* buffer = source->GetBuffer().data();
- DCHECK(buffer);
- DataVector<uint32_t> dst32_storage(Fx2DSizeOrDie(width, height));
- pdfium::span<SkPMColor> dst32_pixels(dst32_storage);
-
- for (int y = 0; y < height; ++y) {
- const uint8_t* src_row =
- static_cast<const uint8_t*>(buffer) + y * source->GetPitch();
- pdfium::span<uint32_t> dst_row = dst32_pixels.subspan(y * width);
- for (int x = 0; x < width; ++x) {
- unsigned index = src_row[x];
- if (index >= palette.size()) {
- index = 0;
- }
- dst_row[x] = palette[index];
- }
- }
- return dst32_storage;
-}
-
static void DebugValidate(const RetainPtr<CFX_DIBitmap>& bitmap,
const RetainPtr<CFX_DIBitmap>& device) {
if (bitmap) {
@@ -331,7 +273,6 @@ SkBlendMode GetSkiaBlendMode(BlendMode blend_type) {
case BlendMode::kLuminosity:
return SkBlendMode::kLuminosity;
case BlendMode::kNormal:
- default:
return SkBlendMode::kSrcOver;
}
}
@@ -668,104 +609,6 @@ void SetBitmapPaintForMerge(bool is_mask,
paint->setBlendMode(GetSkiaBlendMode(blend_type));
}
-bool Upsample(const RetainPtr<CFX_DIBBase>& pSource,
- DataVector<uint32_t>& dst32_storage,
- SkBitmap* skBitmap,
- bool forceAlpha) {
- void* buffer = pSource->GetBuffer().data();
- if (!buffer)
- return false;
- SkColorType colorType = forceAlpha || pSource->IsMaskFormat()
- ? SkColorType::kAlpha_8_SkColorType
- : SkColorType::kGray_8_SkColorType;
- SkAlphaType alphaType = kPremul_SkAlphaType;
- int width = pSource->GetWidth();
- int height = pSource->GetHeight();
- int rowBytes = pSource->GetPitch();
- switch (pSource->GetBPP()) {
- case 1: {
- // By default, the two colors for grayscale are 0xFF and 0x00 unless they
- // are specified in the palette.
- uint8_t color0 = 0x00;
- uint8_t color1 = 0xFF;
-
- if (pSource->GetFormat() == FXDIB_Format::k1bppRgb &&
- pSource->HasPalette()) {
- uint32_t palette_color0 = pSource->GetPaletteArgb(0);
- uint32_t palette_color1 = pSource->GetPaletteArgb(1);
- bool use_gray_colors = IsRGBColorGrayScale(palette_color0) &&
- IsRGBColorGrayScale(palette_color1);
- if (!use_gray_colors) {
- dst32_storage = Fill32BppDestStorageWith1BppSource(pSource);
- rowBytes = width * sizeof(uint32_t);
- colorType = kBGRA_8888_SkColorType;
- break;
- }
-
- color0 = FXARGB_R(palette_color0);
- color1 = FXARGB_R(palette_color1);
- }
-
- const int src_row_bytes = rowBytes; // Save original value.
- rowBytes = fxge::CalculatePitch32OrDie(/*bpp=*/8, width);
- dst32_storage = DataVector<uint32_t>(Fx2DSizeOrDie(rowBytes / 4, height));
- pdfium::span<uint8_t> dst8_pixels =
- pdfium::as_writable_bytes(pdfium::make_span(dst32_storage));
- for (int y = 0; y < height; ++y) {
- const uint8_t* src_row =
- static_cast<const uint8_t*>(buffer) + y * src_row_bytes;
- pdfium::span<uint8_t> dst_row = dst8_pixels.subspan(y * rowBytes);
- for (int x = 0; x < width; ++x)
- dst_row[x] = src_row[x >> 3] & (1 << (~x & 0x07)) ? color1 : color0;
- }
- break;
- }
- case 8:
- // we upscale ctables to 32bit.
- if (pSource->HasPalette()) {
- const size_t src_palette_size = pSource->GetRequiredPaletteSize();
- pdfium::span<const uint32_t> src_palette = pSource->GetPaletteSpan();
- CHECK_LE(src_palette_size, src_palette.size());
- if (src_palette_size < src_palette.size())
- src_palette = src_palette.first(src_palette_size);
-
- dst32_storage = Fill32BppDestStorageWithPalette(pSource, src_palette);
- rowBytes = width * sizeof(uint32_t);
- colorType = kBGRA_8888_SkColorType;
- }
- break;
- case 24: {
- dst32_storage = DataVector<uint32_t>(Fx2DSizeOrDie(width, height));
- pdfium::span<uint32_t> dst32_pixels(dst32_storage);
- for (int y = 0; y < height; ++y) {
- const uint8_t* srcRow =
- static_cast<const uint8_t*>(buffer) + y * rowBytes;
- pdfium::span<uint32_t> dst_row = dst32_pixels.subspan(y * width);
- for (int x = 0; x < width; ++x) {
- dst_row[x] = SkPackARGB32NoCheck(
- 0xFF, srcRow[x * 3 + 2], srcRow[x * 3 + 1], srcRow[x * 3 + 0]);
- }
- }
- rowBytes = width * sizeof(uint32_t);
- colorType = kBGRA_8888_SkColorType;
- alphaType = kOpaque_SkAlphaType;
- break;
- }
- case 32:
- colorType = kBGRA_8888_SkColorType;
- break;
- default:
- NOTREACHED();
- }
- if (!dst32_storage.empty()) {
- buffer = dst32_storage.data();
- }
- SkImageInfo imageInfo =
- SkImageInfo::Make(width, height, colorType, alphaType);
- skBitmap->installPixels(imageInfo, buffer, rowBytes);
- return true;
-}
-
// Makes a bitmap filled with a solid color for debugging with `SkPicture`.
RetainPtr<CFX_DIBitmap> MakeDebugBitmap(int width, int height, uint32_t color) {
auto bitmap = pdfium::MakeRetain<CFX_DIBitmap>();
@@ -833,10 +676,8 @@ CFX_SkiaDeviceDriver::CFX_SkiaDeviceDriver(
bool bGroupKnockout)
: m_pBitmap(std::move(pBitmap)),
m_pBackdropBitmap(pBackdropBitmap),
- m_pRecorder(nullptr),
m_bRgbByteOrder(bRgbByteOrder),
m_bGroupKnockout(bGroupKnockout) {
- SkBitmap skBitmap;
SkColorType color_type;
const int bpp = m_pBitmap->GetBPP();
if (bpp == 8) {
@@ -866,14 +707,13 @@ CFX_SkiaDeviceDriver::CFX_SkiaDeviceDriver(
SkImageInfo imageInfo =
SkImageInfo::Make(m_pBitmap->GetWidth(), m_pBitmap->GetHeight(),
color_type, kPremul_SkAlphaType);
- skBitmap.installPixels(imageInfo, m_pBitmap->GetBuffer().data(),
- m_pBitmap->GetPitch());
- m_pCanvas = new SkCanvas(skBitmap);
+ surface_ = SkSurfaces::WrapPixels(
+ imageInfo, m_pBitmap->GetWritableBuffer().data(), m_pBitmap->GetPitch());
+ m_pCanvas = surface_->getCanvas();
}
-CFX_SkiaDeviceDriver::CFX_SkiaDeviceDriver(SkPictureRecorder* recorder)
- : m_pRecorder(recorder), m_bGroupKnockout(false) {
- m_pCanvas = m_pRecorder->getRecordingCanvas();
+CFX_SkiaDeviceDriver::CFX_SkiaDeviceDriver(SkCanvas* canvas)
+ : m_pCanvas(canvas), m_bGroupKnockout(false) {
int width = m_pCanvas->imageInfo().width();
int height = m_pCanvas->imageInfo().height();
DCHECK_EQ(kUnknown_SkColorType, m_pCanvas->imageInfo().colorType());
@@ -897,10 +737,6 @@ CFX_SkiaDeviceDriver::~CFX_SkiaDeviceDriver() {
height, m_pBitmap, /*src_left=*/0,
/*src_top=*/0);
}
-
- if (!m_pRecorder) {
- delete m_pCanvas;
- }
}
bool CFX_SkiaDeviceDriver::DrawDeviceText(
@@ -917,21 +753,6 @@ bool CFX_SkiaDeviceDriver::DrawDeviceText(
if (pFont->GetFontSpan().empty())
return false;
- // If a glyph's default width is no less than its width defined in the PDF,
- // draw the glyph with path since it can be scaled to avoid overlapping with
- // the adjacent glyphs (if there are any). Otherwise, use the device driver
- // to render the glyph without any adjustments.
- const CFX_SubstFont* subst_font = pFont->GetSubstFont();
- const int subst_font_weight =
- (subst_font && subst_font->IsBuiltInGenericFont()) ? subst_font->m_Weight
- : 0;
- for (const TextCharPos& cp : pCharPos) {
- const int glyph_width = pFont->GetGlyphWidth(
- cp.m_GlyphIndex, cp.m_FontCharWidth, subst_font_weight);
- if (cp.m_FontCharWidth <= glyph_width)
- return false;
- }
-
if (TryDrawText(pCharPos, pFont, mtObject2Device, font_size, color,
options)) {
return true;
@@ -1007,6 +828,8 @@ bool CFX_SkiaDeviceDriver::DrawDeviceText(
// TODO(crbug.com/pdfium/1999): Merge with `DrawDeviceText()` and refactor
// common logic.
+// TODO(crbug.com/pdfium/1774): Sometimes the thickness of the glyphs is not
+// ideal. Improve text rendering results regarding different font weight.
bool CFX_SkiaDeviceDriver::TryDrawText(pdfium::span<const TextCharPos> char_pos,
const CFX_Font* pFont,
const CFX_Matrix& matrix,
@@ -1091,13 +914,26 @@ bool CFX_SkiaDeviceDriver::TryDrawText(pdfium::span<const TextCharPos> char_pos,
glyphs.data(), glyphs.size() * sizeof(uint16_t), m_rsxform.data(), font,
SkTextEncoding::kGlyphID);
m_pCanvas->drawTextBlob(blob, 0, 0, skPaint);
- } else {
- const DataVector<SkPoint>& positions = m_charDetails.GetPositions();
- for (size_t i = 0; i < m_charDetails.Count(); ++i) {
- sk_sp<SkTextBlob> blob = SkTextBlob::MakeFromText(
- &glyphs[i], sizeof(glyphs[i]), font, SkTextEncoding::kGlyphID);
- m_pCanvas->drawTextBlob(blob, positions[i].fX, positions[i].fY, skPaint);
+ return true;
+ }
+ const DataVector<SkPoint>& positions = m_charDetails.GetPositions();
+ const DataVector<uint32_t>& widths = m_charDetails.GetFontCharWidths();
+ for (size_t i = 0; i < m_charDetails.Count(); ++i) {
+ const uint32_t font_glyph_width =
+ pFont ? pFont->GetGlyphWidth(glyphs[i]) : 0;
+ const uint32_t pdf_glyph_width = widths[i];
+ if (pdf_glyph_width > 0 && font_glyph_width > 0) {
+ // Scale the glyph from its default width `pdf_glyph_width` to the
+ // targeted width `pdf_glyph_width`.
+ font.setScaleX(scaleX * SkIntToScalar(pdf_glyph_width) /
+ font_glyph_width);
+ } else {
+ font.setScaleX(scaleX);
}
+ auto blob =
+ SkTextBlob::MakeFromPosText(&glyphs[i], sizeof(uint16_t), &positions[i],
+ font, SkTextEncoding::kGlyphID);
+ m_pCanvas->drawTextBlob(blob, 0, 0, skPaint);
}
return true;
}
@@ -1115,22 +951,17 @@ bool CFX_SkiaDeviceDriver::MultiplyAlpha(float alpha) {
}
bool CFX_SkiaDeviceDriver::MultiplyAlpha(const RetainPtr<CFX_DIBBase>& mask) {
- if (!mask->IsMaskFormat()) {
- NOTREACHED();
- return false;
- }
+ CHECK(mask->IsMaskFormat());
- // Storage vector must outlive `skia_mask`.
- DataVector<uint32_t> dst32_storage;
- SkBitmap skia_mask;
- if (!Upsample(mask, dst32_storage, &skia_mask, /*forceAlpha=*/true)) {
+ sk_sp<SkImage> skia_mask = mask->RealizeSkImage();
+ if (!skia_mask) {
return false;
}
- skia_mask.setImmutable();
+ DCHECK_EQ(skia_mask->colorType(), kAlpha_8_SkColorType);
SkPaint paint;
paint.setBlendMode(SkBlendMode::kDstIn);
- m_pCanvas->drawImageRect(skia_mask.asImage(),
+ m_pCanvas->drawImageRect(skia_mask,
SkRect::Make(m_pCanvas->imageInfo().bounds()),
SkSamplingOptions(), &paint);
return true;
@@ -1156,8 +987,7 @@ int CFX_SkiaDeviceDriver::GetDeviceCaps(int caps_id) const {
FXRC_BLEND_MODE | FXRC_SOFT_CLIP | FXRC_ALPHA_OUTPUT |
FXRC_FILLSTROKE_PATH | FXRC_SHADING;
default:
- NOTREACHED();
- return 0;
+ NOTREACHED_NORETURN();
}
}
@@ -1518,35 +1348,28 @@ bool CFX_SkiaDeviceDriver::GetDIBits(const RetainPtr<CFX_DIBitmap>& pBitmap,
if (!m_pBitmap)
return true;
- uint8_t* srcBuffer = m_pBitmap->GetBuffer().data();
- if (!srcBuffer)
+ const uint8_t* input_buffer = m_pBitmap->GetBuffer().data();
+ if (!input_buffer) {
return true;
+ }
- int srcWidth = m_pBitmap->GetWidth();
- int srcHeight = m_pBitmap->GetHeight();
- size_t srcRowBytes = m_pBitmap->GetPitch();
- SkImageInfo srcImageInfo = SkImageInfo::Make(
- srcWidth, srcHeight, SkColorType::kN32_SkColorType, kPremul_SkAlphaType);
- SkBitmap skSrcBitmap;
- skSrcBitmap.installPixels(srcImageInfo, srcBuffer, srcRowBytes);
- skSrcBitmap.setImmutable();
-
- uint8_t* dstBuffer = pBitmap->GetBuffer().data();
- DCHECK(dstBuffer);
-
- int dstWidth = pBitmap->GetWidth();
- int dstHeight = pBitmap->GetHeight();
- size_t dstRowBytes = pBitmap->GetPitch();
- SkImageInfo dstImageInfo = SkImageInfo::Make(
- dstWidth, dstHeight, Get32BitSkColorType(m_bRgbByteOrder),
- kPremul_SkAlphaType);
- SkBitmap skDstBitmap;
- skDstBitmap.installPixels(dstImageInfo, dstBuffer, dstRowBytes);
-
- SkCanvas canvas(skDstBitmap);
- canvas.drawImageRect(skSrcBitmap.asImage(),
- SkRect::MakeXYWH(left, top, dstWidth, dstHeight),
- SkSamplingOptions(), /*paint=*/nullptr);
+ uint8_t* output_buffer = pBitmap->GetWritableBuffer().data();
+ DCHECK(output_buffer);
+
+ SkImageInfo input_info =
+ SkImageInfo::Make(m_pBitmap->GetWidth(), m_pBitmap->GetHeight(),
+ SkColorType::kN32_SkColorType, kPremul_SkAlphaType);
+ sk_sp<SkImage> input = SkImages::RasterFromPixmap(
+ SkPixmap(input_info, input_buffer, m_pBitmap->GetPitch()),
+ /*rasterReleaseProc=*/nullptr, /*releaseContext=*/nullptr);
+
+ SkImageInfo output_info = SkImageInfo::Make(
+ pBitmap->GetWidth(), pBitmap->GetHeight(),
+ Get32BitSkColorType(m_bRgbByteOrder), kPremul_SkAlphaType);
+ sk_sp<SkSurface> output =
+ SkSurfaces::WrapPixels(output_info, output_buffer, pBitmap->GetPitch());
+
+ output->getCanvas()->drawImage(input, left, top, SkSamplingOptions());
return true;
}
@@ -1620,41 +1443,16 @@ bool CFX_SkiaDeviceDriver::ContinueDIBits(CFX_ImageRenderer* handle,
return false;
}
-void CFX_DIBitmap::PreMultiply() {
- if (GetBPP() != 32)
- return;
-
- void* buffer = GetBuffer().data();
- if (!buffer)
- return;
-
- Format prior_format = m_nFormat;
- ForcePreMultiply();
- if (prior_format == Format::kPreMultiplied)
- return;
-
- int height = GetHeight();
- int width = GetWidth();
- int row_bytes = GetPitch();
- SkImageInfo unpremultiplied_info =
- SkImageInfo::Make(width, height, kN32_SkColorType, kUnpremul_SkAlphaType);
- SkPixmap unpremultiplied(unpremultiplied_info, buffer, row_bytes);
- SkImageInfo premultiplied_info =
- SkImageInfo::Make(width, height, kN32_SkColorType, kPremul_SkAlphaType);
- SkPixmap premultiplied(premultiplied_info, buffer, row_bytes);
- unpremultiplied.readPixels(premultiplied);
-}
-
void CFX_DIBitmap::UnPreMultiply() {
if (GetBPP() != 32)
return;
- void* buffer = GetBuffer().data();
+ void* buffer = GetWritableBuffer().data();
if (!buffer)
return;
Format prior_format = m_nFormat;
- ForceUnPreMultiply();
+ m_nFormat = Format::kUnPreMultiplied;
if (prior_format == Format::kUnPreMultiplied)
return;
@@ -1674,8 +1472,8 @@ void CFX_DIBitmap::ForcePreMultiply() {
m_nFormat = Format::kPreMultiplied;
}
-void CFX_DIBitmap::ForceUnPreMultiply() {
- m_nFormat = Format::kUnPreMultiplied;
+bool CFX_DIBitmap::IsPremultiplied() const {
+ return m_nFormat == Format::kPreMultiplied;
}
bool CFX_SkiaDeviceDriver::DrawBitsWithMask(
@@ -1685,17 +1483,19 @@ bool CFX_SkiaDeviceDriver::DrawBitsWithMask(
const CFX_Matrix& matrix,
BlendMode blend_type) {
DebugValidate(m_pBitmap, m_pBackdropBitmap);
- // Storage vectors must outlive `skBitmap` and `skMask`.
- DataVector<uint32_t> src32_storage;
- DataVector<uint32_t> mask32_storage;
- SkBitmap skBitmap;
- SkBitmap skMask;
- if (!Upsample(pSource, src32_storage, &skBitmap, /*forceAlpha=*/false)) {
+
+ sk_sp<SkImage> skia_source = pSource->RealizeSkImage();
+ if (!skia_source) {
return false;
}
- if (!Upsample(pMask, mask32_storage, &skMask, /*forceAlpha=*/true)) {
+
+ DCHECK(pMask->IsMaskFormat());
+ sk_sp<SkImage> skia_mask = pMask->RealizeSkImage();
+ if (!skia_mask) {
return false;
}
+ DCHECK_EQ(skia_mask->colorType(), kAlpha_8_SkColorType);
+
{
SkAutoCanvasRestore scoped_save_restore(m_pCanvas, /*doSave=*/true);
@@ -1707,17 +1507,17 @@ bool CFX_SkiaDeviceDriver::DrawBitsWithMask(
SkPaint paint;
SetBitmapPaintForMerge(pSource->IsMaskFormat(), !m_FillOptions.aliased_path,
0xFFFFFFFF, bitmap_alpha, blend_type, &paint);
- sk_sp<SkImage> skSrc = SkImages::RasterFromBitmap(skBitmap);
- sk_sp<SkShader> skSrcShader = skSrc->makeShader(
+ sk_sp<SkShader> source_shader = skia_source->makeShader(
SkTileMode::kClamp, SkTileMode::kClamp, SkSamplingOptions());
- sk_sp<SkImage> skMaskImage = SkImages::RasterFromBitmap(skMask);
- sk_sp<SkShader> skMaskShader = skMaskImage->makeShader(
+ sk_sp<SkShader> mask_shader = skia_mask->makeShader(
SkTileMode::kClamp, SkTileMode::kClamp, SkSamplingOptions());
- paint.setShader(
- SkShaders::Blend(SkBlendMode::kSrcIn, skMaskShader, skSrcShader));
- SkRect r = {0, 0, SkIntToScalar(src_width), SkIntToScalar(src_height)};
- m_pCanvas->drawRect(r, paint);
+ paint.setShader(SkShaders::Blend(
+ SkBlendMode::kSrcIn, std::move(mask_shader), std::move(source_shader)));
+ m_pCanvas->drawRect(
+ SkRect::MakeWH(SkIntToScalar(src_width), SkIntToScalar(src_height)),
+ paint);
}
+
DebugValidate(m_pBitmap, m_pBackdropBitmap);
return true;
}
@@ -1755,13 +1555,10 @@ bool CFX_SkiaDeviceDriver::StartDIBitsSkia(
BlendMode blend_type) {
DebugValidate(m_pBitmap, m_pBackdropBitmap);
- // Storage vector must outlive `skBitmap`.
- DataVector<uint32_t> dst32_storage;
- SkBitmap skBitmap;
- if (!Upsample(pSource, dst32_storage, &skBitmap, /*forceAlpha=*/false)) {
+ sk_sp<SkImage> skia_source = pSource->RealizeSkImage();
+ if (!skia_source) {
return false;
}
- skBitmap.setImmutable();
{
SkAutoCanvasRestore scoped_save_restore(m_pCanvas, /*doSave=*/true);
@@ -1793,12 +1590,13 @@ bool CFX_SkiaDeviceDriver::StartDIBitsSkia(
}
m_pCanvas->drawImageRect(
- skBitmap.asImage(),
+ skia_source,
SkRect::MakeLTRB(src_rect.left, src_rect.top, src_rect.right,
src_rect.bottom),
SkRect::MakeWH(src_rect.Width(), src_rect.Height()), sampling_options,
&paint, SkCanvas::kFast_SrcRectConstraint);
}
+
DebugValidate(m_pBitmap, m_pBackdropBitmap);
return true;
}
@@ -1810,15 +1608,6 @@ void CFX_DefaultRenderDevice::Clear(uint32_t color) {
static_cast<CFX_SkiaDeviceDriver*>(GetDeviceDriver())->Clear(color);
}
-std::unique_ptr<SkPictureRecorder> CFX_DefaultRenderDevice::CreateRecorder(
- const SkRect& bounds) {
- auto recorder = std::make_unique<SkPictureRecorder>();
- recorder->beginRecording(bounds);
-
- SetDeviceDriver(std::make_unique<CFX_SkiaDeviceDriver>(recorder.get()));
- return recorder;
-}
-
bool CFX_DefaultRenderDevice::AttachSkiaImpl(
RetainPtr<CFX_DIBitmap> pBitmap,
bool bRgbByteOrder,
@@ -1837,10 +1626,11 @@ bool CFX_DefaultRenderDevice::AttachSkiaImpl(
return true;
}
-bool CFX_DefaultRenderDevice::AttachRecorder(SkPictureRecorder* recorder) {
- if (!recorder)
+bool CFX_DefaultRenderDevice::AttachCanvas(SkCanvas* canvas) {
+ if (!canvas) {
return false;
- SetDeviceDriver(std::make_unique<CFX_SkiaDeviceDriver>(recorder));
+ }
+ SetDeviceDriver(std::make_unique<CFX_SkiaDeviceDriver>(canvas));
return true;
}
@@ -1862,14 +1652,3 @@ bool CFX_DefaultRenderDevice::CreateSkia(
SetDeviceDriver(std::move(driver));
return true;
}
-
-bool CFX_DefaultRenderDevice::SetBitsWithMask(
- const RetainPtr<CFX_DIBBase>& pBitmap,
- const RetainPtr<CFX_DIBBase>& pMask,
- int left,
- int top,
- int bitmap_alpha,
- BlendMode blend_type) {
- return static_cast<CFX_SkiaDeviceDriver*>(GetDeviceDriver())
- ->SetBitsWithMask(pBitmap, pMask, left, top, bitmap_alpha, blend_type);
-}
diff --git a/core/fxge/skia/fx_skia_device.h b/core/fxge/skia/fx_skia_device.h
index beed9a308..3285f8fb0 100644
--- a/core/fxge/skia/fx_skia_device.h
+++ b/core/fxge/skia/fx_skia_device.h
@@ -12,18 +12,20 @@
#include "core/fxcrt/data_vector.h"
#include "core/fxcrt/fx_memory_wrappers.h"
#include "core/fxcrt/retain_ptr.h"
+#include "core/fxcrt/unowned_ptr.h"
#include "core/fxge/cfx_fillrenderoptions.h"
#include "core/fxge/cfx_path.h"
#include "core/fxge/renderdevicedriver_iface.h"
#include "third_party/base/check_op.h"
-#include "third_party/base/span.h"
+#include "third_party/base/containers/span.h"
#include "third_party/skia/include/core/SkPoint.h"
#include "third_party/skia/include/core/SkRSXform.h"
+#include "third_party/skia/include/core/SkRefCnt.h"
class CFX_Font;
class CFX_Matrix;
class SkCanvas;
-class SkPictureRecorder;
+class SkSurface;
class TextCharPos;
struct CFX_TextRenderOptions;
@@ -39,7 +41,7 @@ class CFX_SkiaDeviceDriver final : public RenderDeviceDriverIface {
RetainPtr<CFX_DIBitmap> pBackdropBitmap,
bool bGroupKnockout);
- explicit CFX_SkiaDeviceDriver(SkPictureRecorder* recorder);
+ explicit CFX_SkiaDeviceDriver(SkCanvas* canvas);
~CFX_SkiaDeviceDriver() override;
/** Options */
@@ -217,8 +219,8 @@ class CFX_SkiaDeviceDriver final : public RenderDeviceDriverIface {
// bitmap is 24 bpp and cannot be directly used as the back of a SkCanvas.
RetainPtr<CFX_DIBitmap> m_pOriginalBitmap;
- SkCanvas* m_pCanvas;
- SkPictureRecorder* const m_pRecorder;
+ sk_sp<SkSurface> surface_;
+ UnownedPtr<SkCanvas> m_pCanvas;
CFX_FillRenderOptions m_FillOptions;
bool m_bRgbByteOrder;
bool m_bGroupKnockout;
diff --git a/core/fxge/skia/fx_skia_device_embeddertest.cpp b/core/fxge/skia/fx_skia_device_embeddertest.cpp
index 517d86369..ca38a1ed0 100644
--- a/core/fxge/skia/fx_skia_device_embeddertest.cpp
+++ b/core/fxge/skia/fx_skia_device_embeddertest.cpp
@@ -5,7 +5,12 @@
#include "core/fxge/skia/fx_skia_device.h"
#include <memory>
+#include <set>
+#include <utility>
+#include "core/fpdfapi/page/cpdf_page.h"
+#include "core/fpdfapi/render/cpdf_pagerendercontext.h"
+#include "core/fxcrt/fx_codepage.h"
#include "core/fxge/cfx_defaultrenderdevice.h"
#include "core/fxge/cfx_fillrenderoptions.h"
#include "core/fxge/cfx_font.h"
@@ -13,15 +18,26 @@
#include "core/fxge/cfx_path.h"
#include "core/fxge/cfx_renderdevice.h"
#include "core/fxge/cfx_textrenderoptions.h"
+#include "core/fxge/dib/cfx_dibitmap.h"
#include "core/fxge/text_char_pos.h"
#include "fpdfsdk/cpdfsdk_helpers.h"
+#include "fpdfsdk/cpdfsdk_renderpage.h"
#include "public/cpp/fpdf_scopers.h"
#include "public/fpdfview.h"
+#include "testing/embedder_test.h"
+#include "testing/gmock/include/gmock/gmock.h"
#include "testing/gtest/include/gtest/gtest.h"
-#include "third_party/skia/include/core/SkPictureRecorder.h"
+#include "third_party/skia/include/core/SkCanvas.h"
+#include "third_party/skia/include/core/SkImage.h"
+#include "third_party/skia/include/core/SkSize.h"
+#include "third_party/skia/include/utils/SkNoDrawCanvas.h"
namespace {
+using ::testing::NiceMock;
+using ::testing::SizeIs;
+using ::testing::WithArg;
+
struct State {
enum class Change { kNo, kYes };
enum class Save { kNo, kYes };
@@ -44,11 +60,14 @@ void EmptyTest(CFX_SkiaDeviceDriver* driver, const State&) {
void CommonTest(CFX_SkiaDeviceDriver* driver, const State& state) {
TextCharPos charPos[1];
charPos[0].m_Origin = CFX_PointF(0, 1);
- charPos[0].m_GlyphIndex = 1;
+ charPos[0].m_GlyphIndex = 0;
charPos[0].m_FontCharWidth = 4;
CFX_Font font;
- float fontSize = 1;
+ font.LoadSubst("Courier", /*bTrueType=*/true, /*flags=*/0,
+ /*weight=*/400, /*italic_angle=*/0, FX_CodePage::kShiftJIS,
+ /*bVertical=*/false);
+ float fontSize = 20;
CFX_Path clipPath;
CFX_Path clipPath2;
clipPath.AppendRect(0, 0, 3, 1);
@@ -63,7 +82,10 @@ void CommonTest(CFX_SkiaDeviceDriver* driver, const State& state) {
CFX_Matrix matrix2;
matrix2.Translate(1, 0);
CFX_GraphStateData graphState;
- static constexpr CFX_TextRenderOptions kTextOptions;
+ // Turn off anti-aliasing so that pixels with transitional colors can be
+ // avoided.
+ static constexpr CFX_TextRenderOptions kTextOptions(
+ CFX_TextRenderOptions::kAliasing);
if (state.m_save == State::Save::kYes)
driver->SaveState();
if (state.m_clip != State::Clip::kNo)
@@ -141,6 +163,47 @@ void Harness(void (*Test)(CFX_SkiaDeviceDriver*, const State&),
EXPECT_EQ(state.m_pixel, pixel);
}
+void RenderPageToSkCanvas(FPDF_PAGE page,
+ int start_x,
+ int start_y,
+ int size_x,
+ int size_y,
+ SkCanvas* canvas) {
+ CPDF_Page* cpdf_page = CPDFPageFromFPDFPage(page);
+
+ auto context = std::make_unique<CPDF_PageRenderContext>();
+ CPDF_PageRenderContext* unowned_context = context.get();
+
+ CPDF_Page::RenderContextClearer clearer(cpdf_page);
+ cpdf_page->SetRenderContext(std::move(context));
+
+ auto default_device = std::make_unique<CFX_DefaultRenderDevice>();
+ default_device->AttachCanvas(canvas);
+ unowned_context->m_pDevice = std::move(default_device);
+
+ CPDFSDK_RenderPageWithContext(unowned_context, cpdf_page, start_x, start_y,
+ size_x, size_y, /*rotate=*/0, /*flags=*/0,
+ /*color_scheme=*/nullptr,
+ /*need_to_restore=*/true, /*pause=*/nullptr);
+}
+
+class MockCanvas : public SkNoDrawCanvas {
+ public:
+ MockCanvas(int width, int height) : SkNoDrawCanvas(width, height) {}
+
+ MOCK_METHOD(void,
+ onDrawImageRect2,
+ (const SkImage*,
+ const SkRect&,
+ const SkRect&,
+ const SkSamplingOptions&,
+ const SkPaint*,
+ SrcRectConstraint),
+ (override));
+};
+
+using FxgeSkiaEmbedderTest = EmbedderTest;
+
} // namespace
TEST(fxge, SkiaStateEmpty) {
@@ -165,8 +228,7 @@ TEST(fxge, SkiaStatePath) {
State::Graphic::kPath, 0xFF112233});
}
-// TODO(crbug.com/pdfium/11): Fix this test and enable.
-TEST(fxge, DISABLED_SkiaStateText) {
+TEST(fxge, SkiaStateText) {
if (!CFX_DefaultRenderDevice::SkiaIsDefaultRenderer())
return;
@@ -182,3 +244,48 @@ TEST(fxge, SkiaStateOOSClip) {
return;
Harness(&OutOfSequenceClipTest, {});
}
+
+TEST_F(FxgeSkiaEmbedderTest, RenderBigImageTwice) {
+ static constexpr int kImageWidth = 5100;
+ static constexpr int kImageHeight = 6600;
+
+ // Page size that renders 20 image pixels per output pixel. This value evenly
+ // divides both the image width and half the image height.
+ static constexpr int kPageToImageFactor = 20;
+ static constexpr int kPageWidth = kImageWidth / kPageToImageFactor;
+ static constexpr int kPageHeight = kImageHeight / kPageToImageFactor;
+
+ if (!CFX_DefaultRenderDevice::SkiaIsDefaultRenderer()) {
+ GTEST_SKIP() << "Skia is not the default renderer";
+ }
+
+ ASSERT_TRUE(OpenDocument("bug_2034.pdf"));
+ FPDF_PAGE page = LoadPage(0);
+ ASSERT_TRUE(page);
+
+ std::set<int> image_ids;
+ NiceMock<MockCanvas> canvas(kPageWidth, kPageHeight / 2);
+ EXPECT_CALL(canvas, onDrawImageRect2)
+ .WillRepeatedly(WithArg<0>([&image_ids](const SkImage* image) {
+ ASSERT_TRUE(image);
+ image_ids.insert(image->uniqueID());
+
+ // TODO(crbug.com/pdfium/2026): Image dimensions should be clipped to
+ // 5100x3320. The extra `kPageToImageFactor` accounts for anti-aliasing.
+ EXPECT_EQ(SkISize::Make(kImageWidth, kImageHeight), image->dimensions())
+ << "Actual image dimensions: " << image->width() << "x"
+ << image->height();
+ }));
+
+ // Render top half.
+ RenderPageToSkCanvas(page, /*start_x=*/0, /*start_y=*/0,
+ /*size_x=*/kPageWidth, /*size_y=*/kPageHeight, &canvas);
+
+ // Render bottom half.
+ RenderPageToSkCanvas(page, /*start_x=*/0, /*start_y=*/-kPageHeight / 2,
+ /*size_x=*/kPageWidth, /*size_y=*/kPageHeight, &canvas);
+
+ EXPECT_THAT(image_ids, SizeIs(1));
+
+ UnloadPage(page);
+}