summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorXisco Fauli <xiscofauli@libreoffice.org>2024-04-22 13:49:45 +0200
committerXisco Fauli <xiscofauli@libreoffice.org>2024-04-22 15:20:16 +0200
commit41368edc6e2b16d4e0ea89b822064bb75dfadc01 (patch)
tree152c4bbf75b9700a6c016dc46974e1dc329e258a
parenta9f1b705275ef9abbcf17366d8f3713235dd4675 (diff)
tdf#159660: Add support for lighten mode in feBlend
Change-Id: I17471a9c70a38d05de5ad476f817285fb2438e5a Reviewed-on: https://gerrit.libreoffice.org/c/core/+/166429 Tested-by: Jenkins Reviewed-by: Xisco Fauli <xiscofauli@libreoffice.org>
-rw-r--r--include/vcl/BitmapLightenBlendFilter.hxx29
-rw-r--r--svgio/inc/svgfeblendnode.hxx1
-rw-r--r--svgio/qa/cppunit/SvgImportTest.cxx44
-rw-r--r--svgio/qa/cppunit/data/lightenBlend.svg8
-rw-r--r--svgio/source/svgreader/svgfeblendnode.cxx10
-rw-r--r--vcl/Library_vcl.mk1
-rw-r--r--vcl/qa/cppunit/BitmapFilterTest.cxx57
-rw-r--r--vcl/source/bitmap/BitmapLightenBlendFilter.cxx109
8 files changed, 259 insertions, 0 deletions
diff --git a/include/vcl/BitmapLightenBlendFilter.hxx b/include/vcl/BitmapLightenBlendFilter.hxx
new file mode 100644
index 000000000000..e17a34dc8efd
--- /dev/null
+++ b/include/vcl/BitmapLightenBlendFilter.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_BITMAPLIGHTENBLENDFILTER_HXX
+#define INCLUDED_VCL_BITMAPLIGHTENBLENDFILTER_HXX
+
+#include <vcl/bitmapex.hxx>
+
+class VCL_DLLPUBLIC BitmapLightenBlendFilter
+{
+private:
+ BitmapEx maBitmapEx;
+ BitmapEx maBitmapEx2;
+
+public:
+ BitmapLightenBlendFilter(BitmapEx const& rBmpEx, BitmapEx const& rBmpEx2);
+
+ virtual ~BitmapLightenBlendFilter();
+ BitmapEx execute();
+};
+#endif
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/svgio/inc/svgfeblendnode.hxx b/svgio/inc/svgfeblendnode.hxx
index 7e6a6c747ea4..ef0a1952b1e6 100644
--- a/svgio/inc/svgfeblendnode.hxx
+++ b/svgio/inc/svgfeblendnode.hxx
@@ -27,6 +27,7 @@ namespace svgio::svgreader
enum class Mode
{
Darken,
+ Lighten,
Multiply,
Normal,
Screen
diff --git a/svgio/qa/cppunit/SvgImportTest.cxx b/svgio/qa/cppunit/SvgImportTest.cxx
index 87aa04887d2f..abf7f58c848b 100644
--- a/svgio/qa/cppunit/SvgImportTest.cxx
+++ b/svgio/qa/cppunit/SvgImportTest.cxx
@@ -1909,6 +1909,50 @@ CPPUNIT_TEST_FIXTURE(Test, testDarkenBlend)
CPPUNIT_ASSERT_EQUAL(OUString("008000"), aPixels[125]);
}
+CPPUNIT_TEST_FIXTURE(Test, testLightenBlend)
+{
+ xmlDocUniquePtr pDocument = dumpAndParseSvg(u"/svgio/qa/cppunit/data/lightenBlend.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("ff8000"), 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/lightenBlend.svg b/svgio/qa/cppunit/data/lightenBlend.svg
new file mode 100644
index 000000000000..62e9254da597
--- /dev/null
+++ b/svgio/qa/cppunit/data/lightenBlend.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="lighten"></feBlend>
+ </filter>
+ <use style="filter: url(#filter)"></use>
+</svg>
diff --git a/svgio/source/svgreader/svgfeblendnode.cxx b/svgio/source/svgreader/svgfeblendnode.cxx
index ad545dc8856f..af6406ebf58b 100644
--- a/svgio/source/svgreader/svgfeblendnode.cxx
+++ b/svgio/source/svgreader/svgfeblendnode.cxx
@@ -25,6 +25,7 @@
#include <drawinglayer/converters.hxx>
#include <basegfx/matrix/b2dhommatrixtools.hxx>
#include <vcl/BitmapDarkenBlendFilter.hxx>
+#include <vcl/BitmapLightenBlendFilter.hxx>
#include <vcl/BitmapMultiplyBlendFilter.hxx>
#include <vcl/BitmapScreenBlendFilter.hxx>
#include <vcl/BitmapTools.hxx>
@@ -84,6 +85,10 @@ void SvgFeBlendNode::parseAttribute(SVGToken aSVGToken, const OUString& aContent
{
maMode = Mode::Darken;
}
+ else if (o3tl::equalsIgnoreAsciiCase(o3tl::trim(aContent), u"lighten"))
+ {
+ maMode = Mode::Lighten;
+ }
}
break;
}
@@ -182,6 +187,11 @@ void SvgFeBlendNode::apply(drawinglayer::primitive2d::Primitive2DContainer& rTar
BitmapDarkenBlendFilter aDarkenBlendFilter(aBmpEx, aBmpEx2);
aResBmpEx = aDarkenBlendFilter.execute();
}
+ else if (maMode == Mode::Lighten)
+ {
+ BitmapLightenBlendFilter aLightenBlendFilter(aBmpEx, aBmpEx2);
+ aResBmpEx = aLightenBlendFilter.execute();
+ }
const drawinglayer::primitive2d::Primitive2DReference xRef(
new drawinglayer::primitive2d::BitmapPrimitive2D(
diff --git a/vcl/Library_vcl.mk b/vcl/Library_vcl.mk
index d45fce3296ee..de651cfa1aed 100644
--- a/vcl/Library_vcl.mk
+++ b/vcl/Library_vcl.mk
@@ -346,6 +346,7 @@ $(eval $(call gb_Library_add_exception_objects,vcl,\
vcl/source/bitmap/BitmapAlphaClampFilter \
vcl/source/bitmap/BitmapBasicMorphologyFilter \
vcl/source/bitmap/BitmapDarkenBlendFilter \
+ vcl/source/bitmap/BitmapLightenBlendFilter \
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 dcb7bd587a5e..ca04956312a4 100644
--- a/vcl/qa/cppunit/BitmapFilterTest.cxx
+++ b/vcl/qa/cppunit/BitmapFilterTest.cxx
@@ -17,6 +17,7 @@
#include <vcl/BitmapArithmeticBlendFilter.hxx>
#include <vcl/BitmapDarkenBlendFilter.hxx>
+#include <vcl/BitmapLightenBlendFilter.hxx>
#include <vcl/BitmapMultiplyBlendFilter.hxx>
#include <vcl/BitmapScreenBlendFilter.hxx>
#include <vcl/BitmapBasicMorphologyFilter.hxx>
@@ -45,6 +46,7 @@ public:
void testGenerateStripRanges();
void testMultiplyBlendFilter();
void testDarkenBlendFilter();
+ void testLightenBlendFilter();
void testScreenBlendFilter();
void testArithmeticBlendFilter();
@@ -55,6 +57,7 @@ public:
CPPUNIT_TEST(testGenerateStripRanges);
CPPUNIT_TEST(testMultiplyBlendFilter);
CPPUNIT_TEST(testDarkenBlendFilter);
+ CPPUNIT_TEST(testLightenBlendFilter);
CPPUNIT_TEST(testScreenBlendFilter);
CPPUNIT_TEST(testArithmeticBlendFilter);
CPPUNIT_TEST_SUITE_END();
@@ -396,6 +399,60 @@ void BitmapFilterTest::testDarkenBlendFilter()
}
}
+void BitmapFilterTest::testLightenBlendFilter()
+{
+ 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
+ {
+ BitmapLightenBlendFilter* pArithmeticFilter
+ = new BitmapLightenBlendFilter(aRedBitmapEx, aRedBitmapEx);
+ BitmapEx aResBitmapEx = pArithmeticFilter->execute();
+ CPPUNIT_ASSERT_EQUAL(COL_LIGHTRED, aResBitmapEx.GetPixelColor(2, 2));
+ }
+
+ // different color
+ {
+ BitmapLightenBlendFilter* pArithmeticFilter
+ = new BitmapLightenBlendFilter(aRedBitmapEx, aGreenBitmapEx);
+ BitmapEx aResBitmapEx = pArithmeticFilter->execute();
+ CPPUNIT_ASSERT_EQUAL(Color(ColorAlpha, 0xFF, 0xFF, 0x80, 0x00),
+ aResBitmapEx.GetPixelColor(2, 2));
+ }
+
+ // transparent
+ {
+ BitmapLightenBlendFilter* pArithmeticFilter
+ = new BitmapLightenBlendFilter(aRedBitmapEx, aTransparentBitmapEx);
+ BitmapEx aResBitmapEx = pArithmeticFilter->execute();
+ CPPUNIT_ASSERT_EQUAL(Color(ColorAlpha, 0xFF, 0xFF, 0xFF, 0xFF),
+ aResBitmapEx.GetPixelColor(2, 2));
+ }
+}
+
void BitmapFilterTest::testScreenBlendFilter()
{
Bitmap aRedBitmap(Size(4, 4), vcl::PixelFormat::N24_BPP);
diff --git a/vcl/source/bitmap/BitmapLightenBlendFilter.cxx b/vcl/source/bitmap/BitmapLightenBlendFilter.cxx
new file mode 100644
index 000000000000..3d2fe07a2357
--- /dev/null
+++ b/vcl/source/bitmap/BitmapLightenBlendFilter.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/BitmapLightenBlendFilter.hxx>
+#include <vcl/BitmapWriteAccess.hxx>
+#include <vcl/BitmapTools.hxx>
+
+BitmapLightenBlendFilter::BitmapLightenBlendFilter(BitmapEx const& rBitmapEx,
+ BitmapEx const& rBitmapEx2)
+ : maBitmapEx(rBitmapEx)
+ , maBitmapEx2(rBitmapEx2)
+{
+}
+
+BitmapLightenBlendFilter::~BitmapLightenBlendFilter() {}
+
+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::max((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 BitmapLightenBlendFilter::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, "BitmapLightenBlendFilter: could not access bitmap");
+ }
+ }
+
+ return BitmapEx(aDstBitmap, AlphaMask(aDstAlpha));
+}
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */