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 <xiscofauli@libreoffice.org>
diff --git a/include/vcl/BitmapArithmeticBlendFilter.hxx b/include/vcl/BitmapArithmeticBlendFilter.hxx
new file mode 100644
index 0000000..a2de3ae
--- /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 fcbc24e..8a34851 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 9e69458..401a466 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 88ba5c6..28f161d 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;
if (maOperator != Operator::Arithmetic)
{
basegfx::B2DPolyPolygon aPolyPolygon, aPolyPolygon2;
// 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);
}
// 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);
}
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);
}
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));
}
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))
};
rTarget = drawinglayer::primitive2d::Primitive2DContainer{
new drawinglayer::primitive2d::MaskPrimitive2D(std::move(aResult), std::move(rTarget))
};
pParent->addGraphicSourceToMapper(maResult, rTarget);
pParent->addGraphicSourceToMapper(maResult, rTarget);
}
else
{
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());
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()));
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 b019f71..35e2fa1 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 7b54123..91580307 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 00ba12b..902d5934 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 0000000..da52a43
--- /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: */