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)