diff options
author | Xisco Fauli <xiscofauli@libreoffice.org> | 2024-04-22 13:06:36 +0200 |
---|---|---|
committer | Xisco Fauli <xiscofauli@libreoffice.org> | 2024-04-22 14:55:37 +0200 |
commit | fdf3027a0eb8270d527b3e64c6157917a6718787 (patch) | |
tree | 52fcdf523db0ed223cc0e6a43a68a870ba6c95de | |
parent | 07137ccd7fbbf3afb1cabfc6ca71375293910a3a (diff) |
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 <xiscofauli@libreoffice.org>
-rw-r--r-- | include/vcl/BitmapMultiplyBlendFilter.hxx | 29 | ||||
-rw-r--r-- | svgio/inc/svgfeblendnode.hxx | 1 | ||||
-rw-r--r-- | svgio/qa/cppunit/SvgImportTest.cxx | 44 | ||||
-rw-r--r-- | svgio/qa/cppunit/data/multiplyBlend.svg | 8 | ||||
-rw-r--r-- | svgio/source/svgreader/svgfeblendnode.cxx | 20 | ||||
-rw-r--r-- | vcl/Library_vcl.mk | 1 | ||||
-rw-r--r-- | vcl/qa/cppunit/BitmapFilterTest.cxx | 57 | ||||
-rw-r--r-- | vcl/source/bitmap/BitmapMultiplyBlendFilter.cxx | 109 |
8 files changed, 266 insertions, 3 deletions
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: */ |