include/vcl/BitmapArithmeticBlendFilter.hxx       |   31 +++
 svgio/inc/svgfecompositenode.hxx                  |    6 
 svgio/inc/svgtoken.hxx                            |    4 
 svgio/source/svgreader/svgfecompositenode.cxx     |  214 +++++++++++++++++-----
 svgio/source/svgreader/svgtoken.cxx               |    4 
 vcl/Library_vcl.mk                                |    1 
 vcl/qa/cppunit/BitmapFilterTest.cxx               |  122 ++++++++++++
 vcl/source/bitmap/BitmapArithmeticBlendFilter.cxx |  105 ++++++++++
 8 files changed, 439 insertions(+), 48 deletions(-)

New commits:
commit cb4698626f17e005c820a7138c63a03c21120ecd
Author:     Xisco Fauli <xiscofa...@libreoffice.org>
AuthorDate: Fri Apr 5 10:08:36 2024 +0200
Commit:     Xisco Fauli <xiscofa...@libreoffice.org>
CommitDate: Tue Apr 16 09:36:37 2024 +0200

    tdf#48062: Add support for arithmetic in feComposite
    
    Took https://github.com/w3c/csswg-drafts/issues/3831
    as a reference
    
    Change-Id: I42039c481ec114c3faeae51526a5f29b86960146
    Reviewed-on: https://gerrit.libreoffice.org/c/core/+/165828
    Tested-by: Jenkins
    Reviewed-by: Xisco Fauli <xiscofa...@libreoffice.org>

diff --git a/include/vcl/BitmapArithmeticBlendFilter.hxx 
b/include/vcl/BitmapArithmeticBlendFilter.hxx
new file mode 100644
index 000000000000..a2de3ae28c19
--- /dev/null
+++ b/include/vcl/BitmapArithmeticBlendFilter.hxx
@@ -0,0 +1,31 @@
+/* -*- 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_BITMAPARITHMETICBLENDFILTER_HXX
+#define INCLUDED_VCL_BITMAPARITHMETICBLENDFILTER_HXX
+
+#include <vcl/bitmapex.hxx>
+
+class VCL_DLLPUBLIC BitmapArithmeticBlendFilter
+{
+private:
+    BitmapEx maBitmapEx;
+    BitmapEx maBitmapEx2;
+
+public:
+    BitmapArithmeticBlendFilter(BitmapEx const& rBmpEx, BitmapEx const& 
rBmpEx2);
+    virtual ~BitmapArithmeticBlendFilter();
+
+    BitmapEx execute(double aK1, double aK2, double aK3, double aK4);
+};
+
+#endif
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/svgio/inc/svgfecompositenode.hxx b/svgio/inc/svgfecompositenode.hxx
index fcbc24ecc8ff..8a34851c08d0 100644
--- a/svgio/inc/svgfecompositenode.hxx
+++ b/svgio/inc/svgfecompositenode.hxx
@@ -31,6 +31,7 @@ enum class Operator
     Out,
     Xor,
     Atop,
+    Arithmetic
 };
 
 class SvgFeCompositeNode : public SvgFilterNode
@@ -41,6 +42,11 @@ private:
     OUString maResult;
     Operator maOperator;
 
+    SvgNumber maK1;
+    SvgNumber maK2;
+    SvgNumber maK3;
+    SvgNumber maK4;
+
 public:
     SvgFeCompositeNode(SvgDocument& rDocument, SvgNode* pParent);
     virtual ~SvgFeCompositeNode() override;
diff --git a/svgio/inc/svgtoken.hxx b/svgio/inc/svgtoken.hxx
index 9e6945859cd4..401a4667154e 100644
--- a/svgio/inc/svgtoken.hxx
+++ b/svgio/inc/svgtoken.hxx
@@ -122,6 +122,10 @@ namespace svgio::svgreader
             Title,
             Desc,
             Overflow,
+            K1,
+            K2,
+            K3,
+            K4,
 
             // AspectRatio and params
             PreserveAspectRatio,
diff --git a/svgio/source/svgreader/svgfecompositenode.cxx 
b/svgio/source/svgreader/svgfecompositenode.cxx
index 88ba5c62df68..28f161d7b6ce 100644
--- a/svgio/source/svgreader/svgfecompositenode.cxx
+++ b/svgio/source/svgreader/svgfecompositenode.cxx
@@ -23,6 +23,14 @@
 #include <basegfx/polygon/b2dpolypolygon.hxx>
 #include <drawinglayer/primitive2d/maskprimitive2d.hxx>
 #include <drawinglayer/processor2d/contourextractor2d.hxx>
+#include <drawinglayer/primitive2d/transformprimitive2d.hxx>
+#include <drawinglayer/primitive2d/bitmapprimitive2d.hxx>
+#include <vcl/bitmapex.hxx>
+#include <vcl/BitmapArithmeticBlendFilter.hxx>
+#include <drawinglayer/converters.hxx>
+#include <basegfx/matrix/b2dhommatrixtools.hxx>
+#include <vcl/BitmapWriteAccess.hxx>
+#include <vcl/BitmapTools.hxx>
 
 namespace svgio::svgreader
 {
@@ -83,6 +91,50 @@ void SvgFeCompositeNode::parseAttribute(SVGToken aSVGToken, 
const OUString& aCon
                 {
                     maOperator = Operator::Atop;
                 }
+                else if (o3tl::equalsIgnoreAsciiCase(o3tl::trim(aContent), 
u"arithmetic"))
+                {
+                    maOperator = Operator::Arithmetic;
+                }
+            }
+            break;
+        }
+        case SVGToken::K1:
+        {
+            SvgNumber aNum;
+
+            if (readSingleNumber(aContent, aNum))
+            {
+                maK1 = aNum;
+            }
+            break;
+        }
+        case SVGToken::K2:
+        {
+            SvgNumber aNum;
+
+            if (readSingleNumber(aContent, aNum))
+            {
+                maK2 = aNum;
+            }
+            break;
+        }
+        case SVGToken::K3:
+        {
+            SvgNumber aNum;
+
+            if (readSingleNumber(aContent, aNum))
+            {
+                maK3 = aNum;
+            }
+            break;
+        }
+        case SVGToken::K4:
+        {
+            SvgNumber aNum;
+
+            if (readSingleNumber(aContent, aNum))
+            {
+                maK4 = aNum;
             }
             break;
         }
@@ -96,61 +148,127 @@ void SvgFeCompositeNode::parseAttribute(SVGToken 
aSVGToken, const OUString& aCon
 void 
SvgFeCompositeNode::apply(drawinglayer::primitive2d::Primitive2DContainer& 
rTarget,
                                const SvgFilterNode* pParent) const
 {
-    basegfx::B2DPolyPolygon aPolyPolygon, aPolyPolygon2;
-
-    // Process maIn2 first
-    if (const drawinglayer::primitive2d::Primitive2DContainer* pSource2
-        = pParent->findGraphicSource(maIn2))
+    if (maOperator != Operator::Arithmetic)
     {
-        rTarget.append(*pSource2);
-        drawinglayer::processor2d::ContourExtractor2D aExtractor(
-            drawinglayer::geometry::ViewInformation2D(), true);
-        aExtractor.process(*pSource2);
-        const basegfx::B2DPolyPolygonVector& 
rResult(aExtractor.getExtractedContour());
-        aPolyPolygon2 = basegfx::utils::mergeToSinglePolyPolygon(rResult);
-    }
+        basegfx::B2DPolyPolygon aPolyPolygon, aPolyPolygon2;
 
-    if (const drawinglayer::primitive2d::Primitive2DContainer* pSource
-        = pParent->findGraphicSource(maIn))
-    {
-        rTarget.append(*pSource);
-        drawinglayer::processor2d::ContourExtractor2D aExtractor(
-            drawinglayer::geometry::ViewInformation2D(), true);
-        aExtractor.process(*pSource);
-        const basegfx::B2DPolyPolygonVector& 
rResult(aExtractor.getExtractedContour());
-        aPolyPolygon = basegfx::utils::mergeToSinglePolyPolygon(rResult);
-    }
+        // Process maIn2 first
+        if (const drawinglayer::primitive2d::Primitive2DContainer* pSource2
+            = pParent->findGraphicSource(maIn2))
+        {
+            rTarget.append(*pSource2);
+            drawinglayer::processor2d::ContourExtractor2D aExtractor(
+                drawinglayer::geometry::ViewInformation2D(), true);
+            aExtractor.process(*pSource2);
+            const basegfx::B2DPolyPolygonVector& 
rResult(aExtractor.getExtractedContour());
+            aPolyPolygon2 = basegfx::utils::mergeToSinglePolyPolygon(rResult);
+        }
 
-    basegfx::B2DPolyPolygon aResult;
-    if (maOperator == Operator::Over)
-    {
-        aResult = basegfx::utils::solvePolygonOperationOr(aPolyPolygon, 
aPolyPolygon2);
-    }
-    else if (maOperator == Operator::Out)
-    {
-        aResult = basegfx::utils::solvePolygonOperationDiff(aPolyPolygon, 
aPolyPolygon2);
-    }
-    else if (maOperator == Operator::In)
-    {
-        aResult = basegfx::utils::solvePolygonOperationAnd(aPolyPolygon, 
aPolyPolygon2);
-    }
-    else if (maOperator == Operator::Xor)
-    {
-        aResult = basegfx::utils::solvePolygonOperationXor(aPolyPolygon, 
aPolyPolygon2);
+        if (const drawinglayer::primitive2d::Primitive2DContainer* pSource
+            = pParent->findGraphicSource(maIn))
+        {
+            rTarget.append(*pSource);
+            drawinglayer::processor2d::ContourExtractor2D aExtractor(
+                drawinglayer::geometry::ViewInformation2D(), true);
+            aExtractor.process(*pSource);
+            const basegfx::B2DPolyPolygonVector& 
rResult(aExtractor.getExtractedContour());
+            aPolyPolygon = basegfx::utils::mergeToSinglePolyPolygon(rResult);
+        }
+
+        basegfx::B2DPolyPolygon aResult;
+        if (maOperator == Operator::Over)
+        {
+            aResult = basegfx::utils::solvePolygonOperationOr(aPolyPolygon, 
aPolyPolygon2);
+        }
+        else if (maOperator == Operator::Out)
+        {
+            aResult = basegfx::utils::solvePolygonOperationDiff(aPolyPolygon, 
aPolyPolygon2);
+        }
+        else if (maOperator == Operator::In)
+        {
+            aResult = basegfx::utils::solvePolygonOperationAnd(aPolyPolygon, 
aPolyPolygon2);
+        }
+        else if (maOperator == Operator::Xor)
+        {
+            aResult = basegfx::utils::solvePolygonOperationXor(aPolyPolygon, 
aPolyPolygon2);
+        }
+        else if (maOperator == Operator::Atop)
+        {
+            // Atop is the union of In and Out.
+            // The parts of in2 graphic that do not overlap with the in 
graphic stay untouched.
+            aResult = basegfx::utils::solvePolygonOperationDiff(aPolyPolygon2, 
aPolyPolygon);
+            
aResult.append(basegfx::utils::solvePolygonOperationAnd(aPolyPolygon, 
aPolyPolygon2));
+        }
+
+        rTarget = drawinglayer::primitive2d::Primitive2DContainer{
+            new drawinglayer::primitive2d::MaskPrimitive2D(std::move(aResult), 
std::move(rTarget))
+        };
+
+        pParent->addGraphicSourceToMapper(maResult, rTarget);
     }
-    else if (maOperator == Operator::Atop)
+    else
     {
-        // Atop is the union of In and Out.
-        // The parts of in2 graphic that do not overlap with the in graphic 
stay untouched.
-        aResult = basegfx::utils::solvePolygonOperationDiff(aPolyPolygon2, 
aPolyPolygon);
-        aResult.append(basegfx::utils::solvePolygonOperationAnd(aPolyPolygon, 
aPolyPolygon2));
-    }
+        basegfx::B2DRange aRange, aRange2;
+        BitmapEx aBmpEx, aBmpEx2;
+
+        if (const drawinglayer::primitive2d::Primitive2DContainer* pSource
+            = pParent->findGraphicSource(maIn))
+        {
+            const drawinglayer::geometry::ViewInformation2D aViewInformation2D;
+            aRange = pSource->getB2DRange(aViewInformation2D);
+            basegfx::B2DHomMatrix aEmbedding(
+                basegfx::utils::createTranslateB2DHomMatrix(-aRange.getMinX(), 
-aRange.getMinY()));
+
+            aEmbedding.scale(aRange.getWidth(), aRange.getHeight());
 
-    rTarget = drawinglayer::primitive2d::Primitive2DContainer{
-        new drawinglayer::primitive2d::MaskPrimitive2D(std::move(aResult), 
std::move(rTarget))
-    };
+            const drawinglayer::primitive2d::Primitive2DReference xEmbedRef(
+                new drawinglayer::primitive2d::TransformPrimitive2D(
+                    aEmbedding, 
drawinglayer::primitive2d::Primitive2DContainer(*pSource)));
+            drawinglayer::primitive2d::Primitive2DContainer xEmbedSeq{ 
xEmbedRef };
+
+            aBmpEx = drawinglayer::convertToBitmapEx(std::move(xEmbedSeq), 
aViewInformation2D,
+                                                     aRange.getWidth(), 
aRange.getHeight(), 500000);
+        }
+
+        if (const drawinglayer::primitive2d::Primitive2DContainer* pSource2
+            = pParent->findGraphicSource(maIn2))
+        {
+            const drawinglayer::geometry::ViewInformation2D aViewInformation2D;
+            aRange2 = pSource2->getB2DRange(aViewInformation2D);
+            basegfx::B2DHomMatrix 
aEmbedding(basegfx::utils::createTranslateB2DHomMatrix(
+                -aRange2.getMinX(), -aRange2.getMinY()));
 
-    pParent->addGraphicSourceToMapper(maResult, rTarget);
+            aEmbedding.scale(aRange2.getWidth(), aRange2.getHeight());
+
+            const drawinglayer::primitive2d::Primitive2DReference xEmbedRef(
+                new drawinglayer::primitive2d::TransformPrimitive2D(
+                    aEmbedding, 
drawinglayer::primitive2d::Primitive2DContainer(*pSource2)));
+            drawinglayer::primitive2d::Primitive2DContainer xEmbedSeq{ 
xEmbedRef };
+
+            aBmpEx2
+                = drawinglayer::convertToBitmapEx(std::move(xEmbedSeq), 
aViewInformation2D,
+                                                  aRange2.getWidth(), 
aRange2.getHeight(), 500000);
+        }
+
+        basegfx::B2DRectangle aBaseRect(std::min(aRange.getMinX(), 
aRange2.getMinX()),
+                                        std::min(aRange.getMinY(), 
aRange2.getMinY()),
+                                        std::max(aRange.getMaxX(), 
aRange2.getMaxX()),
+                                        std::max(aRange.getMaxY(), 
aRange2.getMaxY()));
+
+        aBmpEx = vcl::bitmap::DrawBitmapInRect(aBmpEx, aRange, aBaseRect);
+        aBmpEx2 = vcl::bitmap::DrawBitmapInRect(aBmpEx2, aRange2, aBaseRect);
+
+        BitmapArithmeticBlendFilter* pArithmeticFilter
+            = new BitmapArithmeticBlendFilter(aBmpEx, aBmpEx2);
+        BitmapEx aResBmpEx = pArithmeticFilter->execute(maK1.getNumber(), 
maK2.getNumber(),
+                                                        maK3.getNumber(), 
maK4.getNumber());
+
+        const drawinglayer::primitive2d::Primitive2DReference xRef(
+            new drawinglayer::primitive2d::BitmapPrimitive2D(
+                aResBmpEx, basegfx::utils::createScaleTranslateB2DHomMatrix(
+                               aBaseRect.getRange(), aBaseRect.getMinimum())));
+        rTarget = drawinglayer::primitive2d::Primitive2DContainer{ xRef };
+    }
 }
 
 } // end of namespace svgio::svgreader
diff --git a/svgio/source/svgreader/svgtoken.cxx 
b/svgio/source/svgreader/svgtoken.cxx
index b019f71c4835..35e2fa1efb30 100644
--- a/svgio/source/svgreader/svgtoken.cxx
+++ b/svgio/source/svgreader/svgtoken.cxx
@@ -120,6 +120,10 @@ constexpr auto aSVGTokenMap = 
frozen::make_unordered_map<std::u16string_view, SV
     { u"title", SVGToken::Title },
     { u"desc", SVGToken::Desc },
     { u"overflow", SVGToken::Overflow },
+    { u"k1", SVGToken::K1 },
+    { u"k2", SVGToken::K2 },
+    { u"k3", SVGToken::K3 },
+    { u"k4", SVGToken::K4 },
     { u"preserveAspectRatio", SVGToken::PreserveAspectRatio },
     { u"defer", SVGToken::Defer },
     { u"none", SVGToken::None },
diff --git a/vcl/Library_vcl.mk b/vcl/Library_vcl.mk
index 7b54123dbed2..9158030749aa 100644
--- a/vcl/Library_vcl.mk
+++ b/vcl/Library_vcl.mk
@@ -341,6 +341,7 @@ $(eval $(call gb_Library_add_exception_objects,vcl,\
     vcl/source/bitmap/bmpfast \
     vcl/source/bitmap/bitmapfilter \
     vcl/source/bitmap/bitmappaint \
+    vcl/source/bitmap/BitmapArithmeticBlendFilter \
     vcl/source/bitmap/BitmapShadowFilter \
     vcl/source/bitmap/BitmapAlphaClampFilter \
     vcl/source/bitmap/BitmapBasicMorphologyFilter \
diff --git a/vcl/qa/cppunit/BitmapFilterTest.cxx 
b/vcl/qa/cppunit/BitmapFilterTest.cxx
index 00ba12b4a925..902d5934f07d 100644
--- a/vcl/qa/cppunit/BitmapFilterTest.cxx
+++ b/vcl/qa/cppunit/BitmapFilterTest.cxx
@@ -15,6 +15,7 @@
 #include <tools/stream.hxx>
 #include <vcl/graphicfilter.hxx>
 
+#include <vcl/BitmapArithmeticBlendFilter.hxx>
 #include <vcl/BitmapScreenBlendFilter.hxx>
 #include <vcl/BitmapBasicMorphologyFilter.hxx>
 #include <vcl/BitmapFilterStackBlur.hxx>
@@ -41,6 +42,7 @@ public:
     void testPerformance();
     void testGenerateStripRanges();
     void testScreenBlendFilter();
+    void testArithmeticBlendFilter();
 
     CPPUNIT_TEST_SUITE(BitmapFilterTest);
     CPPUNIT_TEST(testBlurCorrectness);
@@ -48,6 +50,7 @@ public:
     CPPUNIT_TEST(testPerformance);
     CPPUNIT_TEST(testGenerateStripRanges);
     CPPUNIT_TEST(testScreenBlendFilter);
+    CPPUNIT_TEST(testArithmeticBlendFilter);
     CPPUNIT_TEST_SUITE_END();
 
 private:
@@ -333,6 +336,125 @@ void BitmapFilterTest::testScreenBlendFilter()
     }
 }
 
+void BitmapFilterTest::testArithmeticBlendFilter()
+{
+    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
+    {
+        BitmapArithmeticBlendFilter* pArithmeticFilter
+            = new BitmapArithmeticBlendFilter(aRedBitmapEx, aRedBitmapEx);
+        BitmapEx aResBitmapEx = pArithmeticFilter->execute(0, 0, 0, 0);
+        CPPUNIT_ASSERT_EQUAL(Color(ColorAlpha, 0x00, 0x00, 0x00, 0x00),
+                             aResBitmapEx.GetPixelColor(0, 0));
+        aResBitmapEx = pArithmeticFilter->execute(1, 0, 0, 0);
+        CPPUNIT_ASSERT_EQUAL(COL_LIGHTRED, aResBitmapEx.GetPixelColor(0, 0));
+        aResBitmapEx = pArithmeticFilter->execute(0, 1, 0, 0);
+        CPPUNIT_ASSERT_EQUAL(COL_LIGHTRED, aResBitmapEx.GetPixelColor(0, 0));
+        aResBitmapEx = pArithmeticFilter->execute(0, 0, 1, 0);
+        CPPUNIT_ASSERT_EQUAL(COL_LIGHTRED, aResBitmapEx.GetPixelColor(0, 0));
+        aResBitmapEx = pArithmeticFilter->execute(0, 0, 0, 1);
+        CPPUNIT_ASSERT_EQUAL(Color(ColorAlpha, 0xFF, 0xFF, 0xFF, 0xFF),
+                             aResBitmapEx.GetPixelColor(0, 0));
+        aResBitmapEx = pArithmeticFilter->execute(0.5, 0, 0, 0);
+        CPPUNIT_ASSERT_EQUAL(Color(ColorAlpha, 0x7F, 0xFF, 0x00, 0x00),
+                             aResBitmapEx.GetPixelColor(0, 0));
+        aResBitmapEx = pArithmeticFilter->execute(0, 0.5, 0, 0);
+        CPPUNIT_ASSERT_EQUAL(Color(ColorAlpha, 0x7F, 0xFF, 0x00, 0x00),
+                             aResBitmapEx.GetPixelColor(0, 0));
+        aResBitmapEx = pArithmeticFilter->execute(0, 0, 0.5, 0);
+        CPPUNIT_ASSERT_EQUAL(Color(ColorAlpha, 0x7F, 0xFF, 0x00, 0x00),
+                             aResBitmapEx.GetPixelColor(0, 0));
+        aResBitmapEx = pArithmeticFilter->execute(0, 0, 0, 0.5);
+        CPPUNIT_ASSERT_EQUAL(Color(ColorAlpha, 0x7F, 0xFF, 0xFF, 0xFF),
+                             aResBitmapEx.GetPixelColor(0, 0));
+    }
+
+    // Different colors
+    {
+        BitmapArithmeticBlendFilter* pArithmeticFilter
+            = new BitmapArithmeticBlendFilter(aRedBitmapEx, aGreenBitmapEx);
+        BitmapEx aResBitmapEx = pArithmeticFilter->execute(0, 0, 0, 0);
+        CPPUNIT_ASSERT_EQUAL(Color(ColorAlpha, 0x00, 0x00, 0x00, 0x00),
+                             aResBitmapEx.GetPixelColor(0, 0));
+        aResBitmapEx = pArithmeticFilter->execute(1, 0, 0, 0);
+        CPPUNIT_ASSERT_EQUAL(COL_BLACK, aResBitmapEx.GetPixelColor(0, 0));
+        aResBitmapEx = pArithmeticFilter->execute(0, 1, 0, 0);
+        CPPUNIT_ASSERT_EQUAL(COL_LIGHTRED, aResBitmapEx.GetPixelColor(0, 0));
+        aResBitmapEx = pArithmeticFilter->execute(0, 0, 1, 0);
+        CPPUNIT_ASSERT_EQUAL(COL_GREEN, aResBitmapEx.GetPixelColor(0, 0));
+        aResBitmapEx = pArithmeticFilter->execute(0, 0, 0, 1);
+        CPPUNIT_ASSERT_EQUAL(Color(ColorAlpha, 0xFF, 0xFF, 0xFF, 0xFF),
+                             aResBitmapEx.GetPixelColor(0, 0));
+        aResBitmapEx = pArithmeticFilter->execute(0.5, 0, 0, 0);
+        CPPUNIT_ASSERT_EQUAL(Color(ColorAlpha, 0x7F, 0x00, 0x00, 0x00),
+                             aResBitmapEx.GetPixelColor(0, 0));
+        aResBitmapEx = pArithmeticFilter->execute(0, 0.5, 0, 0);
+        CPPUNIT_ASSERT_EQUAL(Color(ColorAlpha, 0x7F, 0xFF, 0x00, 0x00),
+                             aResBitmapEx.GetPixelColor(0, 0));
+        aResBitmapEx = pArithmeticFilter->execute(0, 0, 0.5, 0);
+        CPPUNIT_ASSERT_EQUAL(Color(ColorAlpha, 0x7F, 0x00, 0x81, 0x00),
+                             aResBitmapEx.GetPixelColor(0, 0));
+        aResBitmapEx = pArithmeticFilter->execute(0, 0, 0, 0.5);
+        CPPUNIT_ASSERT_EQUAL(Color(ColorAlpha, 0x7F, 0xFF, 0xFF, 0xFF),
+                             aResBitmapEx.GetPixelColor(0, 0));
+    }
+
+    // transparent
+    {
+        BitmapArithmeticBlendFilter* pArithmeticFilter
+            = new BitmapArithmeticBlendFilter(aRedBitmapEx, 
aTransparentBitmapEx);
+        BitmapEx aResBitmapEx = pArithmeticFilter->execute(0, 0, 0, 0);
+        CPPUNIT_ASSERT_EQUAL(Color(ColorAlpha, 0x00, 0x00, 0x00, 0x00),
+                             aResBitmapEx.GetPixelColor(0, 0));
+        aResBitmapEx = pArithmeticFilter->execute(1, 0, 0, 0);
+        CPPUNIT_ASSERT_EQUAL(COL_LIGHTRED, aResBitmapEx.GetPixelColor(0, 0));
+        aResBitmapEx = pArithmeticFilter->execute(0, 1, 0, 0);
+        CPPUNIT_ASSERT_EQUAL(COL_LIGHTRED, aResBitmapEx.GetPixelColor(0, 0));
+        aResBitmapEx = pArithmeticFilter->execute(0, 0, 1, 0);
+        CPPUNIT_ASSERT_EQUAL(Color(ColorAlpha, 0xFF, 0xFF, 0xFF, 0xFF),
+                             aResBitmapEx.GetPixelColor(0, 0));
+        aResBitmapEx = pArithmeticFilter->execute(0, 0, 0, 1);
+        CPPUNIT_ASSERT_EQUAL(Color(ColorAlpha, 0xFF, 0xFF, 0xFF, 0xFF),
+                             aResBitmapEx.GetPixelColor(0, 0));
+        aResBitmapEx = pArithmeticFilter->execute(0.5, 0, 0, 0);
+        CPPUNIT_ASSERT_EQUAL(Color(ColorAlpha, 0x7F, 0xFF, 0x00, 0x00),
+                             aResBitmapEx.GetPixelColor(0, 0));
+        aResBitmapEx = pArithmeticFilter->execute(0, 0.5, 0, 0);
+        CPPUNIT_ASSERT_EQUAL(Color(ColorAlpha, 0x7F, 0xFF, 0x00, 0x00),
+                             aResBitmapEx.GetPixelColor(0, 0));
+        aResBitmapEx = pArithmeticFilter->execute(0, 0, 0.5, 0);
+        CPPUNIT_ASSERT_EQUAL(Color(ColorAlpha, 0x7F, 0xFF, 0xFF, 0xFF),
+                             aResBitmapEx.GetPixelColor(0, 0));
+        aResBitmapEx = pArithmeticFilter->execute(0, 0, 0, 0.5);
+        CPPUNIT_ASSERT_EQUAL(Color(ColorAlpha, 0x7F, 0xFF, 0xFF, 0xFF),
+                             aResBitmapEx.GetPixelColor(0, 0));
+    }
+}
+
 } // namespace
 
 CPPUNIT_TEST_SUITE_REGISTRATION(BitmapFilterTest);
diff --git a/vcl/source/bitmap/BitmapArithmeticBlendFilter.cxx 
b/vcl/source/bitmap/BitmapArithmeticBlendFilter.cxx
new file mode 100644
index 000000000000..da52a436b6f6
--- /dev/null
+++ b/vcl/source/bitmap/BitmapArithmeticBlendFilter.cxx
@@ -0,0 +1,105 @@
+/* -*- 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/BitmapArithmeticBlendFilter.hxx>
+#include <vcl/BitmapWriteAccess.hxx>
+#include <vcl/BitmapTools.hxx>
+
+BitmapArithmeticBlendFilter::BitmapArithmeticBlendFilter(BitmapEx const& 
rBitmapEx,
+                                                         BitmapEx const& 
rBitmapEx2)
+    : maBitmapEx(rBitmapEx)
+    , maBitmapEx2(rBitmapEx2)
+{
+}
+
+BitmapArithmeticBlendFilter::~BitmapArithmeticBlendFilter() {}
+
+static sal_uInt8 lcl_calculate(sal_uInt8 aColor, sal_uInt8 aColor2, double 
aK1, double aK2,
+                               double aK3, double aK4)
+{
+    const double i1 = aColor / 255.0;
+    const double i2 = aColor2 / 255.0;
+    const double result = aK1 * i1 * i2 + aK2 * i1 + aK3 * i2 + aK4;
+
+    return std::clamp(result, 0.0, 1.0) * 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 BitmapArithmeticBlendFilter::execute(double aK1, double aK2, double 
aK3, double aK4)
+{
+    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(), i2.GetRed(), aK1, 
aK2, aK3, aK4));
+                    sal_uInt8 g(lcl_calculate(i1.GetGreen(), i2.GetGreen(), 
aK1, aK2, aK3, aK4));
+                    sal_uInt8 b(lcl_calculate(i1.GetBlue(), i2.GetBlue(), aK1, 
aK2, aK3, aK4));
+                    sal_uInt8 a(lcl_calculate(i1.GetAlpha(), i2.GetAlpha(), 
aK1, aK2, aK3, aK4));
+
+                    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, "BitmapScreenBlendFilter: could not access 
bitmap");
+        }
+    }
+
+    return BitmapEx(aDstBitmap, AlphaMask(aDstAlpha));
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */

Reply via email to