Add FXDIB_Format::kBgraPremul blending support to CFX_ScanlineCompositor
Add CompositeRgbBitmapLineSrcBgraPremul() and helpers to do blending for
premultiplied pixels. Add corresponding unit tests that show the
results, when un-premultiplied, are mostly the same except for rounding
errors and when the alpha value is 0.
Bug: 359317049
Change-Id: I3bdd8850370a66f0f828916f67ee96d6888cdebc
Reviewed-on: https://pdfium-review.googlesource.com/c/pdfium/+/123611
Reviewed-by: Tom Sepez <tsepez@chromium.org>
Commit-Queue: Lei Zhang <thestig@chromium.org>
Reviewed-by: Tom Sepez <tsepez@google.com>
diff --git a/core/fxge/dib/cfx_scanlinecompositor.cpp b/core/fxge/dib/cfx_scanlinecompositor.cpp
index 579cd6d..6e3b86e 100644
--- a/core/fxge/dib/cfx_scanlinecompositor.cpp
+++ b/core/fxge/dib/cfx_scanlinecompositor.cpp
@@ -141,6 +141,17 @@
output.red = FXDIB_ALPHA_MERGE(output.red, input.red, alpha);
}
+#if defined(PDF_USE_SKIA)
+template <typename T, typename U>
+void AlphaMergeToDestPremul(const T& input, U& output) {
+ const int in_alpha = 255 - input.alpha;
+ const int out_alpha = 255 - output.alpha;
+ output.blue = (output.blue * in_alpha + input.blue * out_alpha) / 255;
+ output.green = (output.green * in_alpha + input.green * out_alpha) / 255;
+ output.red = (output.red * in_alpha + input.red * out_alpha) / 255;
+}
+#endif // defined(PDF_USE_SKIA)
+
template <typename T, typename U>
void AlphaMergeToSource(const T& input, U& output, uint8_t alpha) {
output.blue = FXDIB_ALPHA_MERGE(input.blue, output.blue, alpha);
@@ -527,7 +538,8 @@
}
// Returns 0 when no further work is required by the caller. Otherwise, returns
-// `src_alpha` and the caller needs to use that to do more work.
+// `src_alpha` and the caller needs to use that to call one of the
+// CompositePixelBgra2Bgra*Blend() functions.
template <typename DestPixelStruct>
uint8_t CompositePixelBgra2BgraCommon(const FX_BGRA_STRUCT<uint8_t>& input,
uint8_t clip,
@@ -656,6 +668,117 @@
}
}
+#if defined(PDF_USE_SKIA)
+// Returns false when no further work is required by the caller. Otherwise,
+// returns true and the caller needs to call one of the
+// CompositePixelBgraPremul2BgraPremul*Blend() functions.
+template <typename DestPixelStruct>
+uint8_t CompositePixelBgraPremul2BgraPremulCommon(
+ const FX_BGRA_STRUCT<uint8_t>& input,
+ DestPixelStruct& output) {
+ if (output.alpha != 0) {
+ return true;
+ }
+
+ output.blue = input.blue;
+ output.green = input.green;
+ output.red = input.red;
+ output.alpha = input.alpha;
+ return false;
+}
+
+template <typename DestPixelStruct>
+void CompositePixelBgraPremul2BgraPremulNonSeparableBlend(
+ const FX_BGRA_STRUCT<uint8_t>& input,
+ DestPixelStruct& output,
+ BlendMode blend_type) {
+ if (!CompositePixelBgraPremul2BgraPremulCommon(input, output)) {
+ return;
+ }
+
+ FX_BGRA_STRUCT<uint8_t> input_for_blend;
+ input_for_blend.blue = input.blue * output.alpha / 255;
+ input_for_blend.green = input.green * output.alpha / 255;
+ input_for_blend.red = input.red * output.alpha / 255;
+ DestPixelStruct output_for_blend;
+ output_for_blend.blue = output.blue * input.alpha / 255;
+ output_for_blend.green = output.green * input.alpha / 255;
+ output_for_blend.red = output.red * input.alpha / 255;
+ FX_RGB_STRUCT<int> blended_color =
+ RgbBlend(blend_type, input_for_blend, output_for_blend);
+
+ AlphaMergeToDestPremul(input, output);
+ output.blue += blended_color.blue;
+ output.green += blended_color.green;
+ output.red += blended_color.red;
+ output.alpha = AlphaUnion(output.alpha, input.alpha);
+}
+
+template <typename DestPixelStruct>
+void CompositePixelBgraPremul2BgraPremulBlend(
+ const FX_BGRA_STRUCT<uint8_t>& input,
+ DestPixelStruct& output,
+ BlendMode blend_type) {
+ if (!CompositePixelBgraPremul2BgraPremulCommon(input, output)) {
+ return;
+ }
+
+ FX_BGR_STRUCT<int> blended_color = {
+ .blue = Blend(blend_type, input.blue * output.alpha / 255,
+ output.blue * input.alpha / 255),
+ .green = Blend(blend_type, input.green * output.alpha / 255,
+ output.green * input.alpha / 255),
+ .red = Blend(blend_type, input.red * output.alpha / 255,
+ output.red * input.alpha / 255),
+ };
+
+ AlphaMergeToDestPremul(input, output);
+ output.blue += blended_color.blue;
+ output.green += blended_color.green;
+ output.red += blended_color.red;
+ output.alpha = AlphaUnion(output.alpha, input.alpha);
+}
+
+template <typename DestPixelStruct>
+void CompositePixelBgraPremul2BgraPremulNoBlend(
+ const FX_BGRA_STRUCT<uint8_t>& input,
+ DestPixelStruct& output) {
+ if (!CompositePixelBgraPremul2BgraPremulCommon(input, output)) {
+ return;
+ }
+
+ const int in_alpha = 255 - input.alpha;
+ output.blue = input.blue + output.blue * in_alpha / 255;
+ output.green = input.green + output.green * in_alpha / 255;
+ output.red = input.red + output.red * in_alpha / 255;
+ output.alpha = AlphaUnion(output.alpha, input.alpha);
+}
+
+template <typename DestPixelStruct>
+void CompositeRowBgraPremul2BgraPremul(
+ pdfium::span<const FX_BGRA_STRUCT<uint8_t>> src_span,
+ pdfium::span<DestPixelStruct> dest_span,
+ BlendMode blend_type) {
+ const bool non_separable_blend = IsNonSeparableBlendMode(blend_type);
+ if (non_separable_blend) {
+ for (auto [input, output] : fxcrt::Zip(src_span, dest_span)) {
+ CompositePixelBgraPremul2BgraPremulNonSeparableBlend(input, output,
+ blend_type);
+ }
+ return;
+ }
+ if (blend_type != BlendMode::kNormal) {
+ for (auto [input, output] : fxcrt::Zip(src_span, dest_span)) {
+ CompositePixelBgraPremul2BgraPremulBlend(input, output, blend_type);
+ }
+ return;
+ }
+ for (auto [input, output] : fxcrt::Zip(src_span, dest_span)) {
+ CompositePixelBgraPremul2BgraPremulNoBlend(input, output);
+ }
+}
+#endif // defined(PDF_USE_SKIA)
+
void CompositeRow_Rgb2Rgb_Blend_NoClip(pdfium::span<uint8_t> dest_span,
pdfium::span<const uint8_t> src_span,
int width,
@@ -2280,6 +2403,13 @@
CompositeRgbBitmapLineSrcBgrx(dest_scan, src_scan, width, clip_scan);
return;
}
+#if defined(PDF_USE_SKIA)
+ if (m_SrcFormat == FXDIB_Format::kBgraPremul) {
+ CHECK(clip_scan.empty()); // AGG-only.
+ CompositeRgbBitmapLineSrcBgraPremul(dest_scan, src_scan, width);
+ return;
+ }
+#endif
CompositeRgbBitmapLineSrcBgra(dest_scan, src_scan, width, clip_scan);
}
@@ -2479,6 +2609,55 @@
}
}
+#if defined(PDF_USE_SKIA)
+void CFX_ScanlineCompositor::CompositeRgbBitmapLineSrcBgraPremul(
+ pdfium::span<uint8_t> dest_scan,
+ pdfium::span<const uint8_t> src_scan,
+ int width) const {
+ CHECK_EQ(m_SrcFormat, FXDIB_Format::kBgraPremul);
+
+ auto src_span =
+ fxcrt::reinterpret_span<const FX_BGRA_STRUCT<uint8_t>>(src_scan).first(
+ width);
+
+ switch (m_DestFormat) {
+ case FXDIB_Format::kInvalid:
+ case FXDIB_Format::k1bppRgb:
+ case FXDIB_Format::k1bppMask: {
+ NOTREACHED_NORETURN(); // Disallowed by Init().
+ }
+ case FXDIB_Format::k8bppRgb: {
+ CHECK(!m_bRgbByteOrder); // Disallowed by Init();
+ // TODO(crbug.com/42271020): Consider adding support.
+ NOTREACHED_NORETURN();
+ }
+ case FXDIB_Format::k8bppMask: {
+ CHECK(!m_bRgbByteOrder); // Disallowed by Init();
+ // TODO(crbug.com/42271020): Consider adding support.
+ NOTREACHED_NORETURN();
+ }
+ case FXDIB_Format::kBgr:
+ case FXDIB_Format::kBgrx:
+ case FXDIB_Format::kBgra: {
+ // TODO(crbug.com/42271020): Consider adding support.
+ NOTREACHED_NORETURN();
+ }
+ case FXDIB_Format::kBgraPremul: {
+ if (m_bRgbByteOrder) {
+ auto dest_span =
+ fxcrt::reinterpret_span<FX_RGBA_STRUCT<uint8_t>>(dest_scan);
+ CompositeRowBgraPremul2BgraPremul(src_span, dest_span, m_BlendType);
+ return;
+ }
+ auto dest_span =
+ fxcrt::reinterpret_span<FX_BGRA_STRUCT<uint8_t>>(dest_scan);
+ CompositeRowBgraPremul2BgraPremul(src_span, dest_span, m_BlendType);
+ return;
+ }
+ }
+}
+#endif // defined(PDF_USE_SKIA)
+
void CFX_ScanlineCompositor::CompositePalBitmapLine(
pdfium::span<uint8_t> dest_scan,
pdfium::span<const uint8_t> src_scan,
diff --git a/core/fxge/dib/cfx_scanlinecompositor.h b/core/fxge/dib/cfx_scanlinecompositor.h
index 526d379..b54a7ee 100644
--- a/core/fxge/dib/cfx_scanlinecompositor.h
+++ b/core/fxge/dib/cfx_scanlinecompositor.h
@@ -89,6 +89,11 @@
pdfium::span<const uint8_t> src_scan,
int width,
pdfium::span<const uint8_t> clip_scan) const;
+#if defined(PDF_USE_SKIA)
+ void CompositeRgbBitmapLineSrcBgraPremul(pdfium::span<uint8_t> dest_scan,
+ pdfium::span<const uint8_t> src_scan,
+ int width) const;
+#endif
void CompositePalBitmapLineSrcBpp1(
pdfium::span<uint8_t> dest_scan,
diff --git a/core/fxge/dib/cfx_scanlinecompositor_unittest.cpp b/core/fxge/dib/cfx_scanlinecompositor_unittest.cpp
index ea1b3e0..6181e55 100644
--- a/core/fxge/dib/cfx_scanlinecompositor_unittest.cpp
+++ b/core/fxge/dib/cfx_scanlinecompositor_unittest.cpp
@@ -70,6 +70,37 @@
EXPECT_THAT(dest_scan, ElementsAreArray(expectations));
}
+#if defined(PDF_USE_SKIA)
+void PreMultiplyScanLine(pdfium::span<FX_BGRA_STRUCT<uint8_t>> scanline) {
+ for (auto& pixel : scanline) {
+ pixel = PreMultiplyColor(pixel);
+ }
+}
+
+void UnPreMultiplyScanLine(pdfium::span<FX_BGRA_STRUCT<uint8_t>> scanline) {
+ for (auto& pixel : scanline) {
+ pixel = UnPreMultiplyColor(pixel);
+ }
+}
+
+void RunPreMultiplyTest(
+ CFX_ScanlineCompositor& compositor,
+ pdfium::span<const FX_BGRA_STRUCT<uint8_t>> src_span,
+ pdfium::span<const FX_BGRA_STRUCT<uint8_t>> expectations) {
+ std::array<FX_BGRA_STRUCT<uint8_t>, 8> dest_scan;
+ fxcrt::Copy(kDestScan, dest_scan);
+ PreMultiplyScanLine(dest_scan);
+ std::array<FX_BGRA_STRUCT<uint8_t>, 8> src_scan;
+ fxcrt::Copy(src_span, src_scan);
+ PreMultiplyScanLine(src_scan);
+ compositor.CompositeRgbBitmapLine(pdfium::as_writable_byte_span(dest_scan),
+ pdfium::as_byte_span(src_scan),
+ dest_scan.size(), {});
+ UnPreMultiplyScanLine(dest_scan);
+ EXPECT_THAT(dest_scan, ElementsAreArray(expectations));
+}
+#endif // defined(PDF_USE_SKIA)
+
} // namespace
inline bool operator==(const FX_BGRA_STRUCT<uint8_t>& lhs,
@@ -209,3 +240,137 @@
};
RunTest(compositor, kSrcScan3, kExpectations3);
}
+
+#if defined(PDF_USE_SKIA)
+TEST(ScanlineCompositorTest, CompositeRgbBitmapLineBgraPremulNormal) {
+ CFX_ScanlineCompositor compositor;
+ ASSERT_TRUE(compositor.Init(/*dest_format=*/FXDIB_Format::kBgraPremul,
+ /*src_format=*/FXDIB_Format::kBgraPremul,
+ /*src_palette=*/{},
+ /*mask_color=*/0,
+ /*blend_type=*/BlendMode::kNormal,
+ /*bRgbByteOrder=*/false));
+
+ constexpr FX_BGRA_STRUCT<uint8_t> kExpectations1[] = {
+ {.blue = 0, .green = 0, .red = 0, .alpha = 0},
+ {.blue = 255, .green = 100, .red = 0, .alpha = 255},
+ {.blue = 255, .green = 100, .red = 0, .alpha = 255},
+ {.blue = 255, .green = 100, .red = 0, .alpha = 255},
+ {.blue = 253, .green = 98, .red = 0, .alpha = 161},
+ {.blue = 253, .green = 98, .red = 0, .alpha = 222},
+ {.blue = 253, .green = 98, .red = 0, .alpha = 222},
+ {.blue = 253, .green = 98, .red = 0, .alpha = 244},
+ };
+ RunPreMultiplyTest(compositor, kSrcScan1, kExpectations1);
+ constexpr FX_BGRA_STRUCT<uint8_t> kExpectations2[] = {
+ {.blue = 0, .green = 0, .red = 0, .alpha = 0},
+ {.blue = 100, .green = 0, .red = 255, .alpha = 255},
+ {.blue = 255, .green = 100, .red = 0, .alpha = 255},
+ {.blue = 100, .green = 0, .red = 255, .alpha = 255},
+ {.blue = 156, .green = 36, .red = 158, .alpha = 161},
+ {.blue = 113, .green = 9, .red = 229, .alpha = 222},
+ {.blue = 183, .green = 53, .red = 114, .alpha = 222},
+ {.blue = 126, .green = 16, .red = 209, .alpha = 244},
+ };
+ RunPreMultiplyTest(compositor, kSrcScan2, kExpectations2);
+ constexpr FX_BGRA_STRUCT<uint8_t> kExpectations3[] = {
+ {.blue = 0, .green = 0, .red = 0, .alpha = 0},
+ {.blue = 0, .green = 255, .red = 100, .alpha = 255},
+ {.blue = 255, .green = 100, .red = 0, .alpha = 255},
+ {.blue = 0, .green = 255, .red = 100, .alpha = 255},
+ {.blue = 95, .green = 194, .red = 61, .alpha = 161},
+ {.blue = 24, .green = 238, .red = 89, .alpha = 222},
+ {.blue = 138, .green = 168, .red = 44, .alpha = 222},
+ {.blue = 44, .green = 225, .red = 81, .alpha = 244},
+ };
+ RunPreMultiplyTest(compositor, kSrcScan3, kExpectations3);
+}
+
+TEST(ScanlineCompositorTest, CompositeRgbBitmapLineBgraPremulDarken) {
+ CFX_ScanlineCompositor compositor;
+ ASSERT_TRUE(compositor.Init(/*dest_format=*/FXDIB_Format::kBgraPremul,
+ /*src_format=*/FXDIB_Format::kBgraPremul,
+ /*src_palette=*/{},
+ /*mask_color=*/0,
+ /*blend_type=*/BlendMode::kDarken,
+ /*bRgbByteOrder=*/false));
+
+ constexpr FX_BGRA_STRUCT<uint8_t> kExpectations1[] = {
+ {.blue = 0, .green = 0, .red = 0, .alpha = 0},
+ {.blue = 255, .green = 100, .red = 0, .alpha = 255},
+ {.blue = 255, .green = 100, .red = 0, .alpha = 255},
+ {.blue = 255, .green = 100, .red = 0, .alpha = 255},
+ {.blue = 253, .green = 98, .red = 0, .alpha = 161},
+ {.blue = 253, .green = 97, .red = 0, .alpha = 222},
+ {.blue = 253, .green = 97, .red = 0, .alpha = 222},
+ {.blue = 252, .green = 98, .red = 0, .alpha = 244},
+ };
+ RunPreMultiplyTest(compositor, kSrcScan1, kExpectations1);
+ constexpr FX_BGRA_STRUCT<uint8_t> kExpectations2[] = {
+ {.blue = 0, .green = 0, .red = 0, .alpha = 0},
+ {.blue = 100, .green = 0, .red = 255, .alpha = 255},
+ {.blue = 255, .green = 100, .red = 0, .alpha = 255},
+ {.blue = 100, .green = 0, .red = 0, .alpha = 255},
+ {.blue = 156, .green = 36, .red = 95, .alpha = 161},
+ {.blue = 112, .green = 9, .red = 138, .alpha = 222},
+ {.blue = 182, .green = 53, .red = 24, .alpha = 222},
+ {.blue = 125, .green = 16, .red = 44, .alpha = 244},
+ };
+ RunPreMultiplyTest(compositor, kSrcScan2, kExpectations2);
+ constexpr FX_BGRA_STRUCT<uint8_t> kExpectations3[] = {
+ {.blue = 0, .green = 0, .red = 0, .alpha = 0},
+ {.blue = 0, .green = 255, .red = 100, .alpha = 255},
+ {.blue = 255, .green = 100, .red = 0, .alpha = 255},
+ {.blue = 0, .green = 100, .red = 0, .alpha = 255},
+ {.blue = 95, .green = 156, .red = 36, .alpha = 161},
+ {.blue = 24, .green = 182, .red = 53, .alpha = 222},
+ {.blue = 138, .green = 112, .red = 9, .alpha = 222},
+ {.blue = 44, .green = 125, .red = 16, .alpha = 244},
+ };
+ RunPreMultiplyTest(compositor, kSrcScan3, kExpectations3);
+}
+
+TEST(ScanlineCompositorTest, CompositeRgbBitmapLineBgraPremulHue) {
+ CFX_ScanlineCompositor compositor;
+ ASSERT_TRUE(compositor.Init(/*dest_format=*/FXDIB_Format::kBgraPremul,
+ /*src_format=*/FXDIB_Format::kBgraPremul,
+ /*src_palette=*/{},
+ /*mask_color=*/0,
+ /*blend_type=*/BlendMode::kHue,
+ /*bRgbByteOrder=*/false));
+
+ constexpr FX_BGRA_STRUCT<uint8_t> kExpectations1[] = {
+ {.blue = 0, .green = 0, .red = 0, .alpha = 0},
+ {.blue = 255, .green = 100, .red = 0, .alpha = 255},
+ {.blue = 255, .green = 100, .red = 0, .alpha = 255},
+ {.blue = 255, .green = 100, .red = 0, .alpha = 255},
+ {.blue = 253, .green = 98, .red = 0, .alpha = 161},
+ {.blue = 253, .green = 97, .red = 0, .alpha = 222},
+ {.blue = 253, .green = 97, .red = 0, .alpha = 222},
+ {.blue = 252, .green = 98, .red = 0, .alpha = 244},
+ };
+ RunPreMultiplyTest(compositor, kSrcScan1, kExpectations1);
+ constexpr FX_BGRA_STRUCT<uint8_t> kExpectations2[] = {
+ {.blue = 0, .green = 0, .red = 0, .alpha = 0},
+ {.blue = 100, .green = 0, .red = 255, .alpha = 255},
+ {.blue = 255, .green = 100, .red = 0, .alpha = 255},
+ {.blue = 100, .green = 0, .red = 255, .alpha = 255},
+ {.blue = 156, .green = 36, .red = 156, .alpha = 161},
+ {.blue = 112, .green = 9, .red = 228, .alpha = 222},
+ {.blue = 182, .green = 53, .red = 113, .alpha = 222},
+ {.blue = 125, .green = 16, .red = 207, .alpha = 244},
+ };
+ RunPreMultiplyTest(compositor, kSrcScan2, kExpectations2);
+ constexpr FX_BGRA_STRUCT<uint8_t> kExpectations3[] = {
+ {.blue = 0, .green = 0, .red = 0, .alpha = 0},
+ {.blue = 0, .green = 255, .red = 100, .alpha = 255},
+ {.blue = 255, .green = 100, .red = 0, .alpha = 255},
+ {.blue = 0, .green = 123, .red = 49, .alpha = 255},
+ {.blue = 95, .green = 161, .red = 49, .alpha = 161},
+ {.blue = 24, .green = 189, .red = 71, .alpha = 222},
+ {.blue = 138, .green = 119, .red = 26, .alpha = 222},
+ {.blue = 44, .green = 140, .red = 48, .alpha = 244},
+ };
+ RunPreMultiplyTest(compositor, kSrcScan3, kExpectations3);
+}
+#endif // defined(PDF_USE_SKIA)