summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--include/vcl/BitmapArithmeticBlendFilter.hxx31
-rw-r--r--svgio/inc/svgfecompositenode.hxx6
-rw-r--r--svgio/inc/svgtoken.hxx4
-rw-r--r--svgio/source/svgreader/svgfecompositenode.cxx214
-rw-r--r--svgio/source/svgreader/svgtoken.cxx4
-rw-r--r--vcl/Library_vcl.mk1
-rw-r--r--vcl/qa/cppunit/BitmapFilterTest.cxx122
-rw-r--r--vcl/source/bitmap/BitmapArithmeticBlendFilter.cxx105
8 files changed, 439 insertions, 48 deletions
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: */