include/vcl/BitmapDarkenBlendFilter.hxx         |   29 ++++++
 include/vcl/BitmapMultiplyBlendFilter.hxx       |   29 ++++++
 svgio/inc/svgfeblendnode.hxx                    |    2 
 svgio/qa/cppunit/SvgImportTest.cxx              |   88 ++++++++++++++++++
 svgio/qa/cppunit/data/darkenBlend.svg           |    8 +
 svgio/qa/cppunit/data/multiplyBlend.svg         |    8 +
 svgio/source/svgreader/svgfeblendnode.cxx       |   30 +++++-
 vcl/Library_vcl.mk                              |    2 
 vcl/qa/cppunit/BitmapFilterTest.cxx             |  114 ++++++++++++++++++++++++
 vcl/source/bitmap/BitmapDarkenBlendFilter.cxx   |  109 ++++++++++++++++++++++
 vcl/source/bitmap/BitmapMultiplyBlendFilter.cxx |  109 ++++++++++++++++++++++
 11 files changed, 525 insertions(+), 3 deletions(-)

New commits:
commit a9f1b705275ef9abbcf17366d8f3713235dd4675
Author:     Xisco Fauli <xiscofa...@libreoffice.org>
AuthorDate: Mon Apr 22 13:32:15 2024 +0200
Commit:     Xisco Fauli <xiscofa...@libreoffice.org>
CommitDate: Mon Apr 22 14:55:44 2024 +0200

    tdf#159660: Add support for darken mode in feBlend
    
    Change-Id: I56862163b7bf1177120081c95ab7851a5fc4019b
    Reviewed-on: https://gerrit.libreoffice.org/c/core/+/166428
    Tested-by: Jenkins
    Reviewed-by: Xisco Fauli <xiscofa...@libreoffice.org>

diff --git a/include/vcl/BitmapDarkenBlendFilter.hxx 
b/include/vcl/BitmapDarkenBlendFilter.hxx
new file mode 100644
index 000000000000..ea017e13e0fa
--- /dev/null
+++ b/include/vcl/BitmapDarkenBlendFilter.hxx
@@ -0,0 +1,29 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ */
+
+#ifndef INCLUDED_VCL_BITMAPDARKENBLENDFILTER_HXX
+#define INCLUDED_VCL_BITMAPDARKENBLENDFILTER_HXX
+
+#include <vcl/bitmapex.hxx>
+
+class VCL_DLLPUBLIC BitmapDarkenBlendFilter
+{
+private:
+    BitmapEx maBitmapEx;
+    BitmapEx maBitmapEx2;
+
+public:
+    BitmapDarkenBlendFilter(BitmapEx const& rBmpEx, BitmapEx const& rBmpEx2);
+
+    virtual ~BitmapDarkenBlendFilter();
+    BitmapEx execute();
+};
+#endif
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/svgio/inc/svgfeblendnode.hxx b/svgio/inc/svgfeblendnode.hxx
index 86c79a4d353a..7e6a6c747ea4 100644
--- a/svgio/inc/svgfeblendnode.hxx
+++ b/svgio/inc/svgfeblendnode.hxx
@@ -26,6 +26,7 @@ namespace svgio::svgreader
 {
 enum class Mode
 {
+    Darken,
     Multiply,
     Normal,
     Screen
diff --git a/svgio/qa/cppunit/SvgImportTest.cxx 
b/svgio/qa/cppunit/SvgImportTest.cxx
index 8a97d5b93302..87aa04887d2f 100644
--- a/svgio/qa/cppunit/SvgImportTest.cxx
+++ b/svgio/qa/cppunit/SvgImportTest.cxx
@@ -1865,6 +1865,50 @@ CPPUNIT_TEST_FIXTURE(Test, testMultiplyBlend)
     CPPUNIT_ASSERT_EQUAL(OUString("008000"), aPixels[125]);
 }
 
+CPPUNIT_TEST_FIXTURE(Test, testDarkenBlend)
+{
+    xmlDocUniquePtr pDocument = 
dumpAndParseSvg(u"/svgio/qa/cppunit/data/darkenBlend.svg");
+
+    assertXPath(pDocument,
+            "/primitive2D/transform/transform/bitmap"_ostr, "height"_ostr, 
"150");
+    assertXPath(pDocument,
+            "/primitive2D/transform/transform/bitmap"_ostr, "width"_ostr, 
"150");
+    assertXPath(pDocument,
+            "/primitive2D/transform/transform/bitmap/data"_ostr, 150);
+
+    assertXPath(pDocument,
+            "/primitive2D/transform/transform/bitmap"_ostr, "xy11"_ostr, 
"150");
+    assertXPath(pDocument,
+            "/primitive2D/transform/transform/bitmap"_ostr, "xy12"_ostr, "0");
+    assertXPath(pDocument,
+            "/primitive2D/transform/transform/bitmap"_ostr, "xy13"_ostr, "20");
+    assertXPath(pDocument,
+            "/primitive2D/transform/transform/bitmap"_ostr, "xy21"_ostr, "0");
+    assertXPath(pDocument,
+            "/primitive2D/transform/transform/bitmap"_ostr, "xy22"_ostr, 
"150");
+    assertXPath(pDocument,
+            "/primitive2D/transform/transform/bitmap"_ostr, "xy23"_ostr, "20");
+    assertXPath(pDocument,
+            "/primitive2D/transform/transform/bitmap"_ostr, "xy31"_ostr, "0");
+    assertXPath(pDocument,
+            "/primitive2D/transform/transform/bitmap"_ostr, "xy32"_ostr, "0");
+    assertXPath(pDocument,
+            "/primitive2D/transform/transform/bitmap"_ostr, "xy33"_ostr, "1");
+
+    // Check the colors in the diagonal
+    OUString sDataRow = getXPath(pDocument, 
"/primitive2D/transform/transform/bitmap/data[25]"_ostr, "row"_ostr);
+    std::vector<OUString> aPixels = comphelper::string::split(sDataRow, ',');
+    CPPUNIT_ASSERT_EQUAL(OUString("ff0000"), aPixels[25]);
+
+    sDataRow = getXPath(pDocument, 
"/primitive2D/transform/transform/bitmap/data[75]"_ostr, "row"_ostr);
+    aPixels = comphelper::string::split(sDataRow, ',');
+    CPPUNIT_ASSERT_EQUAL(OUString("000000"), aPixels[75]);
+
+    sDataRow = getXPath(pDocument, 
"/primitive2D/transform/transform/bitmap/data[125]"_ostr, "row"_ostr);
+    aPixels = comphelper::string::split(sDataRow, ',');
+    CPPUNIT_ASSERT_EQUAL(OUString("008000"), aPixels[125]);
+}
+
 CPPUNIT_TEST_FIXTURE(Test, testTdf149880)
 {
     xmlDocUniquePtr pDocument = 
dumpAndParseSvg(u"/svgio/qa/cppunit/data/tdf149880.svg");
diff --git a/svgio/qa/cppunit/data/darkenBlend.svg 
b/svgio/qa/cppunit/data/darkenBlend.svg
new file mode 100644
index 000000000000..40609ed7090a
--- /dev/null
+++ b/svgio/qa/cppunit/data/darkenBlend.svg
@@ -0,0 +1,8 @@
+<svg width="100" height="140" viewBox="0 0 200 280" 
xmlns="http://www.w3.org/2000/svg";>
+  <filter id="filter" filterUnits="userSpaceOnUse" x="0" y="0" width="100%" 
height="100%">
+    <feFlood x="20" y="20" width="100" height="100" flood-color="red" 
flood-opacity="1" result="img1"></feFlood>
+    <feFlood x="50" y="50" width="100" height="100" flood-color="green" 
flood-opacity="1" result="img2"></feFlood>
+    <feBlend in="img1" in2="img2" mode="darken"></feBlend>
+  </filter>
+  <use style="filter: url(#filter)"></use>
+</svg>
diff --git a/svgio/source/svgreader/svgfeblendnode.cxx 
b/svgio/source/svgreader/svgfeblendnode.cxx
index c5d367d17429..ad545dc8856f 100644
--- a/svgio/source/svgreader/svgfeblendnode.cxx
+++ b/svgio/source/svgreader/svgfeblendnode.cxx
@@ -24,6 +24,7 @@
 #include <vcl/bitmapex.hxx>
 #include <drawinglayer/converters.hxx>
 #include <basegfx/matrix/b2dhommatrixtools.hxx>
+#include <vcl/BitmapDarkenBlendFilter.hxx>
 #include <vcl/BitmapMultiplyBlendFilter.hxx>
 #include <vcl/BitmapScreenBlendFilter.hxx>
 #include <vcl/BitmapTools.hxx>
@@ -79,6 +80,10 @@ void SvgFeBlendNode::parseAttribute(SVGToken aSVGToken, 
const OUString& aContent
                 {
                     maMode = Mode::Multiply;
                 }
+                else if (o3tl::equalsIgnoreAsciiCase(o3tl::trim(aContent), 
u"darken"))
+                {
+                    maMode = Mode::Darken;
+                }
             }
             break;
         }
@@ -172,6 +177,11 @@ void 
SvgFeBlendNode::apply(drawinglayer::primitive2d::Primitive2DContainer& rTar
             BitmapMultiplyBlendFilter aMultiplyBlendFilter(aBmpEx, aBmpEx2);
             aResBmpEx = aMultiplyBlendFilter.execute();
         }
+        else if (maMode == Mode::Darken)
+        {
+            BitmapDarkenBlendFilter aDarkenBlendFilter(aBmpEx, aBmpEx2);
+            aResBmpEx = aDarkenBlendFilter.execute();
+        }
 
         const drawinglayer::primitive2d::Primitive2DReference xRef(
             new drawinglayer::primitive2d::BitmapPrimitive2D(
diff --git a/vcl/Library_vcl.mk b/vcl/Library_vcl.mk
index 0b0a5f565e52..d45fce3296ee 100644
--- a/vcl/Library_vcl.mk
+++ b/vcl/Library_vcl.mk
@@ -345,6 +345,7 @@ $(eval $(call gb_Library_add_exception_objects,vcl,\
     vcl/source/bitmap/BitmapShadowFilter \
     vcl/source/bitmap/BitmapAlphaClampFilter \
     vcl/source/bitmap/BitmapBasicMorphologyFilter \
+    vcl/source/bitmap/BitmapDarkenBlendFilter \
     vcl/source/bitmap/BitmapMaskToAlphaFilter \
     vcl/source/bitmap/BitmapMonochromeFilter \
     vcl/source/bitmap/BitmapMultiplyBlendFilter \
diff --git a/vcl/qa/cppunit/BitmapFilterTest.cxx 
b/vcl/qa/cppunit/BitmapFilterTest.cxx
index fb01bfde0018..dcb7bd587a5e 100644
--- a/vcl/qa/cppunit/BitmapFilterTest.cxx
+++ b/vcl/qa/cppunit/BitmapFilterTest.cxx
@@ -16,6 +16,7 @@
 #include <vcl/graphicfilter.hxx>
 
 #include <vcl/BitmapArithmeticBlendFilter.hxx>
+#include <vcl/BitmapDarkenBlendFilter.hxx>
 #include <vcl/BitmapMultiplyBlendFilter.hxx>
 #include <vcl/BitmapScreenBlendFilter.hxx>
 #include <vcl/BitmapBasicMorphologyFilter.hxx>
@@ -43,6 +44,7 @@ public:
     void testPerformance();
     void testGenerateStripRanges();
     void testMultiplyBlendFilter();
+    void testDarkenBlendFilter();
     void testScreenBlendFilter();
     void testArithmeticBlendFilter();
 
@@ -52,6 +54,7 @@ public:
     CPPUNIT_TEST(testPerformance);
     CPPUNIT_TEST(testGenerateStripRanges);
     CPPUNIT_TEST(testMultiplyBlendFilter);
+    CPPUNIT_TEST(testDarkenBlendFilter);
     CPPUNIT_TEST(testScreenBlendFilter);
     CPPUNIT_TEST(testArithmeticBlendFilter);
     CPPUNIT_TEST_SUITE_END();
@@ -339,6 +342,60 @@ void BitmapFilterTest::testMultiplyBlendFilter()
     }
 }
 
+void BitmapFilterTest::testDarkenBlendFilter()
+{
+    Bitmap aRedBitmap(Size(4, 4), vcl::PixelFormat::N24_BPP);
+    CPPUNIT_ASSERT_EQUAL(vcl::PixelFormat::N24_BPP, 
aRedBitmap.getPixelFormat());
+    {
+        BitmapScopedWriteAccess aWriteAccess(aRedBitmap);
+        aWriteAccess->Erase(COL_LIGHTRED);
+    }
+
+    Bitmap aGreenBitmap(Size(4, 4), vcl::PixelFormat::N24_BPP);
+    CPPUNIT_ASSERT_EQUAL(vcl::PixelFormat::N24_BPP, 
aGreenBitmap.getPixelFormat());
+    {
+        BitmapScopedWriteAccess aWriteAccess(aGreenBitmap);
+        aWriteAccess->Erase(COL_GREEN);
+    }
+
+    Bitmap aTransparentBitmap(Size(4, 4), vcl::PixelFormat::N24_BPP);
+    CPPUNIT_ASSERT_EQUAL(vcl::PixelFormat::N24_BPP, 
aTransparentBitmap.getPixelFormat());
+    {
+        BitmapScopedWriteAccess aWriteAccess(aTransparentBitmap);
+        aWriteAccess->Erase(COL_AUTO);
+    }
+
+    BitmapEx aRedBitmapEx(aRedBitmap);
+    BitmapEx aGreenBitmapEx(aGreenBitmap);
+    BitmapEx aTransparentBitmapEx(aTransparentBitmap);
+
+    // same color
+    {
+        BitmapDarkenBlendFilter* pArithmeticFilter
+            = new BitmapDarkenBlendFilter(aRedBitmapEx, aRedBitmapEx);
+        BitmapEx aResBitmapEx = pArithmeticFilter->execute();
+        CPPUNIT_ASSERT_EQUAL(COL_LIGHTRED, aResBitmapEx.GetPixelColor(2, 2));
+    }
+
+    // different color
+    {
+        BitmapDarkenBlendFilter* pArithmeticFilter
+            = new BitmapDarkenBlendFilter(aRedBitmapEx, aGreenBitmapEx);
+        BitmapEx aResBitmapEx = pArithmeticFilter->execute();
+        CPPUNIT_ASSERT_EQUAL(Color(ColorAlpha, 0xFF, 0x00, 0x00, 0x00),
+                             aResBitmapEx.GetPixelColor(2, 2));
+    }
+
+    // transparent
+    {
+        BitmapDarkenBlendFilter* pArithmeticFilter
+            = new BitmapDarkenBlendFilter(aRedBitmapEx, aTransparentBitmapEx);
+        BitmapEx aResBitmapEx = pArithmeticFilter->execute();
+        CPPUNIT_ASSERT_EQUAL(Color(ColorAlpha, 0xFF, 0xFF, 0x00, 0x00),
+                             aResBitmapEx.GetPixelColor(2, 2));
+    }
+}
+
 void BitmapFilterTest::testScreenBlendFilter()
 {
     Bitmap aRedBitmap(Size(4, 4), vcl::PixelFormat::N24_BPP);
diff --git a/vcl/source/bitmap/BitmapDarkenBlendFilter.cxx 
b/vcl/source/bitmap/BitmapDarkenBlendFilter.cxx
new file mode 100644
index 000000000000..1aa912a43ec4
--- /dev/null
+++ b/vcl/source/bitmap/BitmapDarkenBlendFilter.cxx
@@ -0,0 +1,109 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ */
+
+#include <comphelper/diagnose_ex.hxx>
+#include <vcl/BitmapDarkenBlendFilter.hxx>
+#include <vcl/BitmapWriteAccess.hxx>
+#include <vcl/BitmapTools.hxx>
+
+BitmapDarkenBlendFilter::BitmapDarkenBlendFilter(BitmapEx const& rBitmapEx,
+                                                 BitmapEx const& rBitmapEx2)
+    : maBitmapEx(rBitmapEx)
+    , maBitmapEx2(rBitmapEx2)
+{
+}
+
+BitmapDarkenBlendFilter::~BitmapDarkenBlendFilter() {}
+
+static sal_uInt8 lcl_calculate(const sal_uInt8 aColor, const sal_uInt8 aAlpha,
+                               const sal_uInt8 aColor2, const sal_uInt8 
aAlpha2)
+{
+    const double c1 = aColor / 255.0;
+    const double c2 = aColor2 / 255.0;
+    const double a1 = aAlpha / 255.0;
+    const double a2 = aAlpha2 / 255.0;
+    const double result = std::min((1.0 - a1) * c2 + c1, (1.0 - a2) * c1 + c2);
+    return result * 255.0;
+}
+
+static BitmapColor premultiply(const BitmapColor c)
+{
+    return BitmapColor(ColorAlpha, vcl::bitmap::premultiply(c.GetRed(), 
c.GetAlpha()),
+                       vcl::bitmap::premultiply(c.GetGreen(), c.GetAlpha()),
+                       vcl::bitmap::premultiply(c.GetBlue(), c.GetAlpha()), 
c.GetAlpha());
+}
+
+static BitmapColor unpremultiply(const BitmapColor c)
+{
+    return BitmapColor(ColorAlpha, vcl::bitmap::unpremultiply(c.GetRed(), 
c.GetAlpha()),
+                       vcl::bitmap::unpremultiply(c.GetGreen(), c.GetAlpha()),
+                       vcl::bitmap::unpremultiply(c.GetBlue(), c.GetAlpha()), 
c.GetAlpha());
+}
+
+BitmapEx BitmapDarkenBlendFilter::execute()
+{
+    if (maBitmapEx.IsEmpty() || maBitmapEx2.IsEmpty())
+        return BitmapEx();
+
+    Size aSize = maBitmapEx.GetBitmap().GetSizePixel();
+    Size aSize2 = maBitmapEx2.GetBitmap().GetSizePixel();
+    sal_Int32 nHeight = std::min(aSize.getHeight(), aSize2.getHeight());
+    sal_Int32 nWidth = std::min(aSize.getWidth(), aSize2.getWidth());
+
+    BitmapScopedReadAccess pReadAccess(maBitmapEx.GetBitmap());
+    Bitmap aDstBitmap(Size(nWidth, nHeight), 
maBitmapEx.GetBitmap().getPixelFormat(),
+                      &pReadAccess->GetPalette());
+    Bitmap aDstAlpha(AlphaMask(Size(nWidth, nHeight)).GetBitmap());
+
+    {
+        // just to be on the safe side: let the
+        // ScopedAccessors get destructed before
+        // copy-constructing the resulting bitmap. This will
+        // rule out the possibility that cached accessor data
+        // is not yet written back.
+
+        BitmapScopedWriteAccess pWriteAccess(aDstBitmap);
+        BitmapScopedWriteAccess pAlphaWriteAccess(aDstAlpha);
+
+        if (pWriteAccess.get() != nullptr && pAlphaWriteAccess.get() != 
nullptr)
+        {
+            for (tools::Long y(0); y < nHeight; ++y)
+            {
+                Scanline pScanline = pWriteAccess->GetScanline(y);
+                Scanline pScanAlpha = pAlphaWriteAccess->GetScanline(y);
+                for (tools::Long x(0); x < nWidth; ++x)
+                {
+                    BitmapColor i1 = premultiply(maBitmapEx.GetPixelColor(x, 
y));
+                    BitmapColor i2 = premultiply(maBitmapEx2.GetPixelColor(x, 
y));
+                    sal_uInt8 r(
+                        lcl_calculate(i1.GetRed(), i1.GetAlpha(), i2.GetRed(), 
i2.GetAlpha()));
+                    sal_uInt8 g(
+                        lcl_calculate(i1.GetGreen(), i1.GetAlpha(), 
i2.GetGreen(), i2.GetAlpha()));
+                    sal_uInt8 b(
+                        lcl_calculate(i1.GetBlue(), i1.GetAlpha(), 
i2.GetBlue(), i2.GetAlpha()));
+                    sal_uInt8 a(
+                        lcl_calculate(i1.GetAlpha(), i1.GetAlpha(), 
i2.GetAlpha(), i2.GetAlpha()));
+
+                    pWriteAccess->SetPixelOnData(
+                        pScanline, x, unpremultiply(BitmapColor(ColorAlpha, r, 
g, b, a)));
+                    pAlphaWriteAccess->SetPixelOnData(pScanAlpha, x, 
BitmapColor(a));
+                }
+            }
+        }
+        else
+        {
+            // TODO(E2): Error handling!
+            ENSURE_OR_THROW(false, "BitmapDarkenBlendFilter: could not access 
bitmap");
+        }
+    }
+
+    return BitmapEx(aDstBitmap, AlphaMask(aDstAlpha));
+}
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
commit fdf3027a0eb8270d527b3e64c6157917a6718787
Author:     Xisco Fauli <xiscofa...@libreoffice.org>
AuthorDate: Mon Apr 22 13:06:36 2024 +0200
Commit:     Xisco Fauli <xiscofa...@libreoffice.org>
CommitDate: Mon Apr 22 14:55:37 2024 +0200

    tdf#159660: Add support for multiply mode in feBlend
    
    Change-Id: I03230e122a10dd6ada6af357c674c278b6b99d9e
    Reviewed-on: https://gerrit.libreoffice.org/c/core/+/166427
    Tested-by: Jenkins
    Reviewed-by: Xisco Fauli <xiscofa...@libreoffice.org>

diff --git a/include/vcl/BitmapMultiplyBlendFilter.hxx 
b/include/vcl/BitmapMultiplyBlendFilter.hxx
new file mode 100644
index 000000000000..4c71470eaadd
--- /dev/null
+++ b/include/vcl/BitmapMultiplyBlendFilter.hxx
@@ -0,0 +1,29 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ */
+
+#ifndef INCLUDED_VCL_BITMAPMULTIPLYBLENDFILTER_HXX
+#define INCLUDED_VCL_BITMAPMULTIPLYBLENDFILTER_HXX
+
+#include <vcl/bitmapex.hxx>
+
+class VCL_DLLPUBLIC BitmapMultiplyBlendFilter
+{
+private:
+    BitmapEx maBitmapEx;
+    BitmapEx maBitmapEx2;
+
+public:
+    BitmapMultiplyBlendFilter(BitmapEx const& rBmpEx, BitmapEx const& rBmpEx2);
+
+    virtual ~BitmapMultiplyBlendFilter();
+    BitmapEx execute();
+};
+#endif
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/svgio/inc/svgfeblendnode.hxx b/svgio/inc/svgfeblendnode.hxx
index 5881959ad418..86c79a4d353a 100644
--- a/svgio/inc/svgfeblendnode.hxx
+++ b/svgio/inc/svgfeblendnode.hxx
@@ -26,6 +26,7 @@ namespace svgio::svgreader
 {
 enum class Mode
 {
+    Multiply,
     Normal,
     Screen
 };
diff --git a/svgio/qa/cppunit/SvgImportTest.cxx 
b/svgio/qa/cppunit/SvgImportTest.cxx
index c336458df004..8a97d5b93302 100644
--- a/svgio/qa/cppunit/SvgImportTest.cxx
+++ b/svgio/qa/cppunit/SvgImportTest.cxx
@@ -1821,6 +1821,50 @@ CPPUNIT_TEST_FIXTURE(Test, testScreenBlend)
     CPPUNIT_ASSERT_EQUAL(OUString("008000"), aPixels[125]);
 }
 
+CPPUNIT_TEST_FIXTURE(Test, testMultiplyBlend)
+{
+    xmlDocUniquePtr pDocument = 
dumpAndParseSvg(u"/svgio/qa/cppunit/data/multiplyBlend.svg");
+
+    assertXPath(pDocument,
+            "/primitive2D/transform/transform/bitmap"_ostr, "height"_ostr, 
"150");
+    assertXPath(pDocument,
+            "/primitive2D/transform/transform/bitmap"_ostr, "width"_ostr, 
"150");
+    assertXPath(pDocument,
+            "/primitive2D/transform/transform/bitmap/data"_ostr, 150);
+
+    assertXPath(pDocument,
+            "/primitive2D/transform/transform/bitmap"_ostr, "xy11"_ostr, 
"150");
+    assertXPath(pDocument,
+            "/primitive2D/transform/transform/bitmap"_ostr, "xy12"_ostr, "0");
+    assertXPath(pDocument,
+            "/primitive2D/transform/transform/bitmap"_ostr, "xy13"_ostr, "20");
+    assertXPath(pDocument,
+            "/primitive2D/transform/transform/bitmap"_ostr, "xy21"_ostr, "0");
+    assertXPath(pDocument,
+            "/primitive2D/transform/transform/bitmap"_ostr, "xy22"_ostr, 
"150");
+    assertXPath(pDocument,
+            "/primitive2D/transform/transform/bitmap"_ostr, "xy23"_ostr, "20");
+    assertXPath(pDocument,
+            "/primitive2D/transform/transform/bitmap"_ostr, "xy31"_ostr, "0");
+    assertXPath(pDocument,
+            "/primitive2D/transform/transform/bitmap"_ostr, "xy32"_ostr, "0");
+    assertXPath(pDocument,
+            "/primitive2D/transform/transform/bitmap"_ostr, "xy33"_ostr, "1");
+
+    // Check the colors in the diagonal
+    OUString sDataRow = getXPath(pDocument, 
"/primitive2D/transform/transform/bitmap/data[25]"_ostr, "row"_ostr);
+    std::vector<OUString> aPixels = comphelper::string::split(sDataRow, ',');
+    CPPUNIT_ASSERT_EQUAL(OUString("ff0000"), aPixels[25]);
+
+    sDataRow = getXPath(pDocument, 
"/primitive2D/transform/transform/bitmap/data[75]"_ostr, "row"_ostr);
+    aPixels = comphelper::string::split(sDataRow, ',');
+    CPPUNIT_ASSERT_EQUAL(OUString("000000"), aPixels[75]);
+
+    sDataRow = getXPath(pDocument, 
"/primitive2D/transform/transform/bitmap/data[125]"_ostr, "row"_ostr);
+    aPixels = comphelper::string::split(sDataRow, ',');
+    CPPUNIT_ASSERT_EQUAL(OUString("008000"), aPixels[125]);
+}
+
 CPPUNIT_TEST_FIXTURE(Test, testTdf149880)
 {
     xmlDocUniquePtr pDocument = 
dumpAndParseSvg(u"/svgio/qa/cppunit/data/tdf149880.svg");
diff --git a/svgio/qa/cppunit/data/multiplyBlend.svg 
b/svgio/qa/cppunit/data/multiplyBlend.svg
new file mode 100644
index 000000000000..90d21d59565a
--- /dev/null
+++ b/svgio/qa/cppunit/data/multiplyBlend.svg
@@ -0,0 +1,8 @@
+<svg width="100" height="140" viewBox="0 0 200 280" 
xmlns="http://www.w3.org/2000/svg";>
+  <filter id="filter" filterUnits="userSpaceOnUse" x="0" y="0" width="100%" 
height="100%">
+    <feFlood x="20" y="20" width="100" height="100" flood-color="red" 
flood-opacity="1" result="img1"></feFlood>
+    <feFlood x="50" y="50" width="100" height="100" flood-color="green" 
flood-opacity="1" result="img2"></feFlood>
+    <feBlend in="img1" in2="img2" mode="multiply"></feBlend>
+  </filter>
+  <use style="filter: url(#filter)"></use>
+</svg>
diff --git a/svgio/source/svgreader/svgfeblendnode.cxx 
b/svgio/source/svgreader/svgfeblendnode.cxx
index eace3a53f63e..c5d367d17429 100644
--- a/svgio/source/svgreader/svgfeblendnode.cxx
+++ b/svgio/source/svgreader/svgfeblendnode.cxx
@@ -24,6 +24,7 @@
 #include <vcl/bitmapex.hxx>
 #include <drawinglayer/converters.hxx>
 #include <basegfx/matrix/b2dhommatrixtools.hxx>
+#include <vcl/BitmapMultiplyBlendFilter.hxx>
 #include <vcl/BitmapScreenBlendFilter.hxx>
 #include <vcl/BitmapTools.hxx>
 
@@ -74,6 +75,10 @@ void SvgFeBlendNode::parseAttribute(SVGToken aSVGToken, 
const OUString& aContent
                 {
                     maMode = Mode::Screen;
                 }
+                else if (o3tl::equalsIgnoreAsciiCase(o3tl::trim(aContent), 
u"multiply"))
+                {
+                    maMode = Mode::Multiply;
+                }
             }
             break;
         }
@@ -105,7 +110,7 @@ void 
SvgFeBlendNode::apply(drawinglayer::primitive2d::Primitive2DContainer& rTar
             rTarget.append(*pSource);
         }
     }
-    else if (maMode == Mode::Screen)
+    else
     {
         basegfx::B2DRange aRange, aRange2;
         const drawinglayer::geometry::ViewInformation2D aViewInformation2D;
@@ -156,8 +161,17 @@ void 
SvgFeBlendNode::apply(drawinglayer::primitive2d::Primitive2DContainer& rTar
                 aBaseRange.getWidth() * aBaseRange.getHeight());
         }
 
-        BitmapScreenBlendFilter aScreenBlendFilter(aBmpEx, aBmpEx2);
-        BitmapEx aResBmpEx = aScreenBlendFilter.execute();
+        BitmapEx aResBmpEx;
+        if (maMode == Mode::Screen)
+        {
+            BitmapScreenBlendFilter aScreenBlendFilter(aBmpEx, aBmpEx2);
+            aResBmpEx = aScreenBlendFilter.execute();
+        }
+        else if (maMode == Mode::Multiply)
+        {
+            BitmapMultiplyBlendFilter aMultiplyBlendFilter(aBmpEx, aBmpEx2);
+            aResBmpEx = aMultiplyBlendFilter.execute();
+        }
 
         const drawinglayer::primitive2d::Primitive2DReference xRef(
             new drawinglayer::primitive2d::BitmapPrimitive2D(
diff --git a/vcl/Library_vcl.mk b/vcl/Library_vcl.mk
index 9158030749aa..0b0a5f565e52 100644
--- a/vcl/Library_vcl.mk
+++ b/vcl/Library_vcl.mk
@@ -347,6 +347,7 @@ $(eval $(call gb_Library_add_exception_objects,vcl,\
     vcl/source/bitmap/BitmapBasicMorphologyFilter \
     vcl/source/bitmap/BitmapMaskToAlphaFilter \
     vcl/source/bitmap/BitmapMonochromeFilter \
+    vcl/source/bitmap/BitmapMultiplyBlendFilter \
     vcl/source/bitmap/BitmapScreenBlendFilter \
     vcl/source/bitmap/BitmapSmoothenFilter \
     vcl/source/bitmap/BitmapLightenFilter \
diff --git a/vcl/qa/cppunit/BitmapFilterTest.cxx 
b/vcl/qa/cppunit/BitmapFilterTest.cxx
index 902d5934f07d..fb01bfde0018 100644
--- a/vcl/qa/cppunit/BitmapFilterTest.cxx
+++ b/vcl/qa/cppunit/BitmapFilterTest.cxx
@@ -16,6 +16,7 @@
 #include <vcl/graphicfilter.hxx>
 
 #include <vcl/BitmapArithmeticBlendFilter.hxx>
+#include <vcl/BitmapMultiplyBlendFilter.hxx>
 #include <vcl/BitmapScreenBlendFilter.hxx>
 #include <vcl/BitmapBasicMorphologyFilter.hxx>
 #include <vcl/BitmapFilterStackBlur.hxx>
@@ -41,6 +42,7 @@ public:
     void testBasicMorphology();
     void testPerformance();
     void testGenerateStripRanges();
+    void testMultiplyBlendFilter();
     void testScreenBlendFilter();
     void testArithmeticBlendFilter();
 
@@ -49,6 +51,7 @@ public:
     CPPUNIT_TEST(testBasicMorphology);
     CPPUNIT_TEST(testPerformance);
     CPPUNIT_TEST(testGenerateStripRanges);
+    CPPUNIT_TEST(testMultiplyBlendFilter);
     CPPUNIT_TEST(testScreenBlendFilter);
     CPPUNIT_TEST(testArithmeticBlendFilter);
     CPPUNIT_TEST_SUITE_END();
@@ -282,6 +285,60 @@ void BitmapFilterTest::testGenerateStripRanges()
     }
 }
 
+void BitmapFilterTest::testMultiplyBlendFilter()
+{
+    Bitmap aRedBitmap(Size(4, 4), vcl::PixelFormat::N24_BPP);
+    CPPUNIT_ASSERT_EQUAL(vcl::PixelFormat::N24_BPP, 
aRedBitmap.getPixelFormat());
+    {
+        BitmapScopedWriteAccess aWriteAccess(aRedBitmap);
+        aWriteAccess->Erase(COL_LIGHTRED);
+    }
+
+    Bitmap aGreenBitmap(Size(4, 4), vcl::PixelFormat::N24_BPP);
+    CPPUNIT_ASSERT_EQUAL(vcl::PixelFormat::N24_BPP, 
aGreenBitmap.getPixelFormat());
+    {
+        BitmapScopedWriteAccess aWriteAccess(aGreenBitmap);
+        aWriteAccess->Erase(COL_GREEN);
+    }
+
+    Bitmap aTransparentBitmap(Size(4, 4), vcl::PixelFormat::N24_BPP);
+    CPPUNIT_ASSERT_EQUAL(vcl::PixelFormat::N24_BPP, 
aTransparentBitmap.getPixelFormat());
+    {
+        BitmapScopedWriteAccess aWriteAccess(aTransparentBitmap);
+        aWriteAccess->Erase(COL_AUTO);
+    }
+
+    BitmapEx aRedBitmapEx(aRedBitmap);
+    BitmapEx aGreenBitmapEx(aGreenBitmap);
+    BitmapEx aTransparentBitmapEx(aTransparentBitmap);
+
+    // same color
+    {
+        BitmapMultiplyBlendFilter* pArithmeticFilter
+            = new BitmapMultiplyBlendFilter(aRedBitmapEx, aRedBitmapEx);
+        BitmapEx aResBitmapEx = pArithmeticFilter->execute();
+        CPPUNIT_ASSERT_EQUAL(COL_LIGHTRED, aResBitmapEx.GetPixelColor(2, 2));
+    }
+
+    // different color
+    {
+        BitmapMultiplyBlendFilter* pArithmeticFilter
+            = new BitmapMultiplyBlendFilter(aRedBitmapEx, aGreenBitmapEx);
+        BitmapEx aResBitmapEx = pArithmeticFilter->execute();
+        CPPUNIT_ASSERT_EQUAL(Color(ColorAlpha, 0xFF, 0x00, 0x00, 0x00),
+                             aResBitmapEx.GetPixelColor(2, 2));
+    }
+
+    // transparent
+    {
+        BitmapMultiplyBlendFilter* pArithmeticFilter
+            = new BitmapMultiplyBlendFilter(aRedBitmapEx, 
aTransparentBitmapEx);
+        BitmapEx aResBitmapEx = pArithmeticFilter->execute();
+        CPPUNIT_ASSERT_EQUAL(Color(ColorAlpha, 0xFF, 0xFF, 0x00, 0x00),
+                             aResBitmapEx.GetPixelColor(2, 2));
+    }
+}
+
 void BitmapFilterTest::testScreenBlendFilter()
 {
     Bitmap aRedBitmap(Size(4, 4), vcl::PixelFormat::N24_BPP);
diff --git a/vcl/source/bitmap/BitmapMultiplyBlendFilter.cxx 
b/vcl/source/bitmap/BitmapMultiplyBlendFilter.cxx
new file mode 100644
index 000000000000..e5d3a185ae15
--- /dev/null
+++ b/vcl/source/bitmap/BitmapMultiplyBlendFilter.cxx
@@ -0,0 +1,109 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ */
+
+#include <comphelper/diagnose_ex.hxx>
+#include <vcl/BitmapMultiplyBlendFilter.hxx>
+#include <vcl/BitmapWriteAccess.hxx>
+#include <vcl/BitmapTools.hxx>
+
+BitmapMultiplyBlendFilter::BitmapMultiplyBlendFilter(BitmapEx const& rBitmapEx,
+                                                     BitmapEx const& 
rBitmapEx2)
+    : maBitmapEx(rBitmapEx)
+    , maBitmapEx2(rBitmapEx2)
+{
+}
+
+BitmapMultiplyBlendFilter::~BitmapMultiplyBlendFilter() {}
+
+static sal_uInt8 lcl_calculate(const sal_uInt8 aColor, const sal_uInt8 aAlpha,
+                               const sal_uInt8 aColor2, const sal_uInt8 
aAlpha2)
+{
+    const double c1 = aColor / 255.0;
+    const double c2 = aColor2 / 255.0;
+    const double a1 = aAlpha / 255.0;
+    const double a2 = aAlpha2 / 255.0;
+    const double result = (1.0 - a1) * c2 + (1.0 - a2) * c1 + c1 * c2;
+    return result * 255.0;
+}
+
+static BitmapColor premultiply(const BitmapColor c)
+{
+    return BitmapColor(ColorAlpha, vcl::bitmap::premultiply(c.GetRed(), 
c.GetAlpha()),
+                       vcl::bitmap::premultiply(c.GetGreen(), c.GetAlpha()),
+                       vcl::bitmap::premultiply(c.GetBlue(), c.GetAlpha()), 
c.GetAlpha());
+}
+
+static BitmapColor unpremultiply(const BitmapColor c)
+{
+    return BitmapColor(ColorAlpha, vcl::bitmap::unpremultiply(c.GetRed(), 
c.GetAlpha()),
+                       vcl::bitmap::unpremultiply(c.GetGreen(), c.GetAlpha()),
+                       vcl::bitmap::unpremultiply(c.GetBlue(), c.GetAlpha()), 
c.GetAlpha());
+}
+
+BitmapEx BitmapMultiplyBlendFilter::execute()
+{
+    if (maBitmapEx.IsEmpty() || maBitmapEx2.IsEmpty())
+        return BitmapEx();
+
+    Size aSize = maBitmapEx.GetBitmap().GetSizePixel();
+    Size aSize2 = maBitmapEx2.GetBitmap().GetSizePixel();
+    sal_Int32 nHeight = std::min(aSize.getHeight(), aSize2.getHeight());
+    sal_Int32 nWidth = std::min(aSize.getWidth(), aSize2.getWidth());
+
+    BitmapScopedReadAccess pReadAccess(maBitmapEx.GetBitmap());
+    Bitmap aDstBitmap(Size(nWidth, nHeight), 
maBitmapEx.GetBitmap().getPixelFormat(),
+                      &pReadAccess->GetPalette());
+    Bitmap aDstAlpha(AlphaMask(Size(nWidth, nHeight)).GetBitmap());
+
+    {
+        // just to be on the safe side: let the
+        // ScopedAccessors get destructed before
+        // copy-constructing the resulting bitmap. This will
+        // rule out the possibility that cached accessor data
+        // is not yet written back.
+
+        BitmapScopedWriteAccess pWriteAccess(aDstBitmap);
+        BitmapScopedWriteAccess pAlphaWriteAccess(aDstAlpha);
+
+        if (pWriteAccess.get() != nullptr && pAlphaWriteAccess.get() != 
nullptr)
+        {
+            for (tools::Long y(0); y < nHeight; ++y)
+            {
+                Scanline pScanline = pWriteAccess->GetScanline(y);
+                Scanline pScanAlpha = pAlphaWriteAccess->GetScanline(y);
+                for (tools::Long x(0); x < nWidth; ++x)
+                {
+                    BitmapColor i1 = premultiply(maBitmapEx.GetPixelColor(x, 
y));
+                    BitmapColor i2 = premultiply(maBitmapEx2.GetPixelColor(x, 
y));
+                    sal_uInt8 r(
+                        lcl_calculate(i1.GetRed(), i1.GetAlpha(), i2.GetRed(), 
i2.GetAlpha()));
+                    sal_uInt8 g(
+                        lcl_calculate(i1.GetGreen(), i1.GetAlpha(), 
i2.GetGreen(), i2.GetAlpha()));
+                    sal_uInt8 b(
+                        lcl_calculate(i1.GetBlue(), i1.GetAlpha(), 
i2.GetBlue(), i2.GetAlpha()));
+                    sal_uInt8 a(
+                        lcl_calculate(i1.GetAlpha(), i1.GetAlpha(), 
i2.GetAlpha(), i2.GetAlpha()));
+
+                    pWriteAccess->SetPixelOnData(
+                        pScanline, x, unpremultiply(BitmapColor(ColorAlpha, r, 
g, b, a)));
+                    pAlphaWriteAccess->SetPixelOnData(pScanAlpha, x, 
BitmapColor(a));
+                }
+            }
+        }
+        else
+        {
+            // TODO(E2): Error handling!
+            ENSURE_OR_THROW(false, "BitmapMultiplyBlendFilter: could not 
access bitmap");
+        }
+    }
+
+    return BitmapEx(aDstBitmap, AlphaMask(aDstAlpha));
+}
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */

Reply via email to