include/vcl/BitmapNormalBlendFilter.hxx | 29 ++++++ svgio/qa/cppunit/SvgImportTest.cxx | 60 ++++++++---- svgio/source/svgreader/svgfeblendnode.cxx | 124 ++++++++++++-------------- vcl/Library_vcl.mk | 1 vcl/qa/cppunit/BitmapFilterTest.cxx | 57 +++++++++++ vcl/source/bitmap/BitmapNormalBlendFilter.cxx | 104 +++++++++++++++++++++ 6 files changed, 294 insertions(+), 81 deletions(-)
New commits: commit 851f60697d32849454528e5f14ed80446b330e0c Author: Xisco Fauli <xiscofa...@libreoffice.org> AuthorDate: Mon Apr 22 16:38:35 2024 +0200 Commit: Xisco Fauli <xiscofa...@libreoffice.org> CommitDate: Mon Apr 22 18:43:08 2024 +0200 tdf#159660: also add normal blend filter Change-Id: I3edc7495975618357f002536857a11dcc72cc0b9 Reviewed-on: https://gerrit.libreoffice.org/c/core/+/166460 Tested-by: Jenkins Reviewed-by: Xisco Fauli <xiscofa...@libreoffice.org> diff --git a/include/vcl/BitmapNormalBlendFilter.hxx b/include/vcl/BitmapNormalBlendFilter.hxx new file mode 100644 index 000000000000..f89ea848d5cc --- /dev/null +++ b/include/vcl/BitmapNormalBlendFilter.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_BITMAPNORMALBLENDFILTER_HXX +#define INCLUDED_VCL_BITMAPNORMALBLENDFILTER_HXX + +#include <vcl/bitmapex.hxx> + +class VCL_DLLPUBLIC BitmapNormalBlendFilter +{ +private: + BitmapEx maBitmapEx; + BitmapEx maBitmapEx2; + +public: + BitmapNormalBlendFilter(BitmapEx const& rBmpEx, BitmapEx const& rBmpEx2); + + virtual ~BitmapNormalBlendFilter(); + BitmapEx execute(); +}; +#endif +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/svgio/qa/cppunit/SvgImportTest.cxx b/svgio/qa/cppunit/SvgImportTest.cxx index 31eb63c6075b..817980d00421 100644 --- a/svgio/qa/cppunit/SvgImportTest.cxx +++ b/svgio/qa/cppunit/SvgImportTest.cxx @@ -176,24 +176,48 @@ CPPUNIT_TEST_FIXTURE(Test, testTdf155819) assertXPath(pDocument, "/primitive2D/transform/transform"_ostr, 4); } -CPPUNIT_TEST_FIXTURE(Test, testFilterFeBlend) -{ - xmlDocUniquePtr pDocument = dumpAndParseSvg(u"/svgio/qa/cppunit/data/filterFeBlend.svg"); - - assertXPath(pDocument, "/primitive2D/transform/transform/polypolygoncolor[1]"_ostr, "color"_ostr, "#8a2be2"); - assertXPath(pDocument, "/primitive2D/transform/transform/polypolygoncolor[1]/polypolygon"_ostr, "height"_ostr, "100"); - assertXPath(pDocument, "/primitive2D/transform/transform/polypolygoncolor[1]/polypolygon"_ostr, "width"_ostr, "100"); - assertXPath(pDocument, "/primitive2D/transform/transform/polypolygoncolor[1]/polypolygon"_ostr, "minx"_ostr, "70"); - assertXPath(pDocument, "/primitive2D/transform/transform/polypolygoncolor[1]/polypolygon"_ostr, "miny"_ostr, "70"); - assertXPath(pDocument, "/primitive2D/transform/transform/polypolygoncolor[1]/polypolygon"_ostr, "maxx"_ostr, "170"); - assertXPath(pDocument, "/primitive2D/transform/transform/polypolygoncolor[1]/polypolygon"_ostr, "maxy"_ostr, "170"); - assertXPath(pDocument, "/primitive2D/transform/transform/polypolygoncolor[2]"_ostr, "color"_ostr, "#ffd700"); - assertXPath(pDocument, "/primitive2D/transform/transform/polypolygoncolor[2]/polypolygon"_ostr, "height"_ostr, "100"); - assertXPath(pDocument, "/primitive2D/transform/transform/polypolygoncolor[2]/polypolygon"_ostr, "width"_ostr, "100"); - assertXPath(pDocument, "/primitive2D/transform/transform/polypolygoncolor[2]/polypolygon"_ostr, "minx"_ostr, "30"); - assertXPath(pDocument, "/primitive2D/transform/transform/polypolygoncolor[2]/polypolygon"_ostr, "miny"_ostr, "30"); - assertXPath(pDocument, "/primitive2D/transform/transform/polypolygoncolor[2]/polypolygon"_ostr, "maxx"_ostr, "130"); - assertXPath(pDocument, "/primitive2D/transform/transform/polypolygoncolor[2]/polypolygon"_ostr, "maxy"_ostr, "130"); +CPPUNIT_TEST_FIXTURE(Test, testNormalBlend) +{ + xmlDocUniquePtr pDocument = dumpAndParseSvg(u"/svgio/qa/cppunit/data/normalBlend.svg"); + + assertXPath(pDocument, + "/primitive2D/transform/transform/bitmap"_ostr, "height"_ostr, "170"); + assertXPath(pDocument, + "/primitive2D/transform/transform/bitmap"_ostr, "width"_ostr, "170"); + assertXPath(pDocument, + "/primitive2D/transform/transform/bitmap/data"_ostr, 170); + + assertXPath(pDocument, + "/primitive2D/transform/transform/bitmap"_ostr, "xy11"_ostr, "170"); + assertXPath(pDocument, + "/primitive2D/transform/transform/bitmap"_ostr, "xy12"_ostr, "0"); + assertXPath(pDocument, + "/primitive2D/transform/transform/bitmap"_ostr, "xy13"_ostr, "0"); + assertXPath(pDocument, + "/primitive2D/transform/transform/bitmap"_ostr, "xy21"_ostr, "0"); + assertXPath(pDocument, + "/primitive2D/transform/transform/bitmap"_ostr, "xy22"_ostr, "170"); + assertXPath(pDocument, + "/primitive2D/transform/transform/bitmap"_ostr, "xy23"_ostr, "0"); + 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[40]"_ostr, "row"_ostr); + std::vector<OUString> aPixels = comphelper::string::split(sDataRow, ','); + CPPUNIT_ASSERT_EQUAL(OUString("ffd700"), aPixels[40]); + + sDataRow = getXPath(pDocument, "/primitive2D/transform/transform/bitmap/data[85]"_ostr, "row"_ostr); + aPixels = comphelper::string::split(sDataRow, ','); + CPPUNIT_ASSERT_EQUAL(OUString("ffd700"), aPixels[85]); + + sDataRow = getXPath(pDocument, "/primitive2D/transform/transform/bitmap/data[130]"_ostr, "row"_ostr); + aPixels = comphelper::string::split(sDataRow, ','); + CPPUNIT_ASSERT_EQUAL(OUString("8a2be2"), aPixels[130]); } CPPUNIT_TEST_FIXTURE(Test, testFeColorMatrix) diff --git a/svgio/qa/cppunit/data/filterFeBlend.svg b/svgio/qa/cppunit/data/normalBlend.svg similarity index 100% rename from svgio/qa/cppunit/data/filterFeBlend.svg rename to svgio/qa/cppunit/data/normalBlend.svg diff --git a/svgio/source/svgreader/svgfeblendnode.cxx b/svgio/source/svgreader/svgfeblendnode.cxx index a5109aa24fe6..fd6bd10ef442 100644 --- a/svgio/source/svgreader/svgfeblendnode.cxx +++ b/svgio/source/svgreader/svgfeblendnode.cxx @@ -27,6 +27,7 @@ #include <vcl/BitmapDarkenBlendFilter.hxx> #include <vcl/BitmapLightenBlendFilter.hxx> #include <vcl/BitmapMultiplyBlendFilter.hxx> +#include <vcl/BitmapNormalBlendFilter.hxx> #include <vcl/BitmapScreenBlendFilter.hxx> #include <vcl/BitmapTools.hxx> @@ -107,95 +108,92 @@ void SvgFeBlendNode::apply(drawinglayer::primitive2d::Primitive2DContainer& rTar const drawinglayer::primitive2d::Primitive2DContainer* pSource2 = pParent->findGraphicSource(maIn2); - if (maMode == Mode::Normal) + basegfx::B2DRange aRange, aRange2; + const drawinglayer::geometry::ViewInformation2D aViewInformation2D; + if (pSource) { - // Process maIn2 first - if (pSource2) - { - rTarget = *pSource2; - } - - if (pSource) - { - rTarget.append(*pSource); - } + aRange = pSource->getB2DRange(aViewInformation2D); } - else - { - basegfx::B2DRange aRange, aRange2; - const drawinglayer::geometry::ViewInformation2D aViewInformation2D; - if (pSource) - { - aRange = pSource->getB2DRange(aViewInformation2D); - } - if (pSource2) - { - aRange2 = pSource2->getB2DRange(aViewInformation2D); - } + if (pSource2) + { + aRange2 = pSource2->getB2DRange(aViewInformation2D); + } - const basegfx::B2DRange aBaseRange(0, 0, std::max(aRange.getMaxX(), aRange2.getMaxX()), - std::max(aRange.getMaxY(), aRange2.getMaxY())); + const basegfx::B2DRange aBaseRange(0, 0, std::max(aRange.getMaxX(), aRange2.getMaxX()), + std::max(aRange.getMaxY(), aRange2.getMaxY())); - BitmapEx aBmpEx, aBmpEx2; + BitmapEx aBmpEx, aBmpEx2; - if (pSource) - { - drawinglayer::primitive2d::Primitive2DContainer aSource(*pSource); - aBmpEx = drawinglayer::convertToBitmapEx( - std::move(aSource), aViewInformation2D, aBaseRange.getWidth(), - aBaseRange.getHeight(), aBaseRange.getWidth() * aBaseRange.getHeight()); - } - else - { - aBmpEx = drawinglayer::convertToBitmapEx( - std::move(rTarget), aViewInformation2D, aBaseRange.getWidth(), - aBaseRange.getHeight(), aBaseRange.getWidth() * aBaseRange.getHeight()); - } + if (pSource) + { + drawinglayer::primitive2d::Primitive2DContainer aSource(*pSource); + aBmpEx = drawinglayer::convertToBitmapEx(std::move(aSource), aViewInformation2D, + aBaseRange.getWidth(), aBaseRange.getHeight(), + aBaseRange.getWidth() * aBaseRange.getHeight()); + } + else + { + aBmpEx = drawinglayer::convertToBitmapEx(std::move(rTarget), aViewInformation2D, + aBaseRange.getWidth(), aBaseRange.getHeight(), + aBaseRange.getWidth() * aBaseRange.getHeight()); + } - if (pSource2) - { - drawinglayer::primitive2d::Primitive2DContainer aSource(*pSource2); - aBmpEx2 = drawinglayer::convertToBitmapEx( - std::move(aSource), aViewInformation2D, aBaseRange.getWidth(), - aBaseRange.getHeight(), aBaseRange.getWidth() * aBaseRange.getHeight()); - } - else - { - aBmpEx2 = drawinglayer::convertToBitmapEx( - std::move(rTarget), aViewInformation2D, aBaseRange.getWidth(), - aBaseRange.getHeight(), aBaseRange.getWidth() * aBaseRange.getHeight()); - } + if (pSource2) + { + drawinglayer::primitive2d::Primitive2DContainer aSource(*pSource2); + aBmpEx2 = drawinglayer::convertToBitmapEx(std::move(aSource), aViewInformation2D, + aBaseRange.getWidth(), aBaseRange.getHeight(), + aBaseRange.getWidth() * aBaseRange.getHeight()); + } + else + { + aBmpEx2 = drawinglayer::convertToBitmapEx(std::move(rTarget), aViewInformation2D, + aBaseRange.getWidth(), aBaseRange.getHeight(), + aBaseRange.getWidth() * aBaseRange.getHeight()); + } - BitmapEx aResBmpEx; - if (maMode == Mode::Screen) + BitmapEx aResBmpEx; + switch (maMode) + { + case Mode::Screen: { BitmapScreenBlendFilter aScreenBlendFilter(aBmpEx, aBmpEx2); aResBmpEx = aScreenBlendFilter.execute(); + break; } - else if (maMode == Mode::Multiply) + case Mode::Multiply: { BitmapMultiplyBlendFilter aMultiplyBlendFilter(aBmpEx, aBmpEx2); aResBmpEx = aMultiplyBlendFilter.execute(); + break; } - else if (maMode == Mode::Darken) + case Mode::Darken: { BitmapDarkenBlendFilter aDarkenBlendFilter(aBmpEx, aBmpEx2); aResBmpEx = aDarkenBlendFilter.execute(); + break; } - else if (maMode == Mode::Lighten) + case Mode::Lighten: { BitmapLightenBlendFilter aLightenBlendFilter(aBmpEx, aBmpEx2); aResBmpEx = aLightenBlendFilter.execute(); + break; + } + case Mode::Normal: + { + BitmapNormalBlendFilter aNormalBlendFilter(aBmpEx, aBmpEx2); + aResBmpEx = aNormalBlendFilter.execute(); + break; } - - const drawinglayer::primitive2d::Primitive2DReference xRef( - new drawinglayer::primitive2d::BitmapPrimitive2D( - aResBmpEx, basegfx::utils::createScaleTranslateB2DHomMatrix( - aBaseRange.getRange(), aBaseRange.getMinimum()))); - rTarget = drawinglayer::primitive2d::Primitive2DContainer{ xRef }; } + const drawinglayer::primitive2d::Primitive2DReference xRef( + new drawinglayer::primitive2d::BitmapPrimitive2D( + aResBmpEx, basegfx::utils::createScaleTranslateB2DHomMatrix(aBaseRange.getRange(), + aBaseRange.getMinimum()))); + rTarget = drawinglayer::primitive2d::Primitive2DContainer{ xRef }; + pParent->addGraphicSourceToMapper(maResult, rTarget); } diff --git a/vcl/Library_vcl.mk b/vcl/Library_vcl.mk index de651cfa1aed..c832e712e22f 100644 --- a/vcl/Library_vcl.mk +++ b/vcl/Library_vcl.mk @@ -350,6 +350,7 @@ $(eval $(call gb_Library_add_exception_objects,vcl,\ vcl/source/bitmap/BitmapMaskToAlphaFilter \ vcl/source/bitmap/BitmapMonochromeFilter \ vcl/source/bitmap/BitmapMultiplyBlendFilter \ + vcl/source/bitmap/BitmapNormalBlendFilter \ 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 ca04956312a4..d59cd331d467 100644 --- a/vcl/qa/cppunit/BitmapFilterTest.cxx +++ b/vcl/qa/cppunit/BitmapFilterTest.cxx @@ -19,6 +19,7 @@ #include <vcl/BitmapDarkenBlendFilter.hxx> #include <vcl/BitmapLightenBlendFilter.hxx> #include <vcl/BitmapMultiplyBlendFilter.hxx> +#include <vcl/BitmapNormalBlendFilter.hxx> #include <vcl/BitmapScreenBlendFilter.hxx> #include <vcl/BitmapBasicMorphologyFilter.hxx> #include <vcl/BitmapFilterStackBlur.hxx> @@ -45,6 +46,7 @@ public: void testPerformance(); void testGenerateStripRanges(); void testMultiplyBlendFilter(); + void testNormalBlendFilter(); void testDarkenBlendFilter(); void testLightenBlendFilter(); void testScreenBlendFilter(); @@ -56,6 +58,7 @@ public: CPPUNIT_TEST(testPerformance); CPPUNIT_TEST(testGenerateStripRanges); CPPUNIT_TEST(testMultiplyBlendFilter); + CPPUNIT_TEST(testNormalBlendFilter); CPPUNIT_TEST(testDarkenBlendFilter); CPPUNIT_TEST(testLightenBlendFilter); CPPUNIT_TEST(testScreenBlendFilter); @@ -345,6 +348,60 @@ void BitmapFilterTest::testMultiplyBlendFilter() } } +void BitmapFilterTest::testNormalBlendFilter() +{ + 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 + { + BitmapNormalBlendFilter* pArithmeticFilter + = new BitmapNormalBlendFilter(aRedBitmapEx, aRedBitmapEx); + BitmapEx aResBitmapEx = pArithmeticFilter->execute(); + CPPUNIT_ASSERT_EQUAL(COL_LIGHTRED, aResBitmapEx.GetPixelColor(2, 2)); + } + + // different color + { + BitmapNormalBlendFilter* pArithmeticFilter + = new BitmapNormalBlendFilter(aRedBitmapEx, aGreenBitmapEx); + BitmapEx aResBitmapEx = pArithmeticFilter->execute(); + CPPUNIT_ASSERT_EQUAL(Color(ColorAlpha, 0xFF, 0xFF, 0x00, 0x00), + aResBitmapEx.GetPixelColor(2, 2)); + } + + // transparent + { + BitmapNormalBlendFilter* pArithmeticFilter + = new BitmapNormalBlendFilter(aRedBitmapEx, aTransparentBitmapEx); + BitmapEx aResBitmapEx = pArithmeticFilter->execute(); + CPPUNIT_ASSERT_EQUAL(Color(ColorAlpha, 0xFF, 0xFF, 0x00, 0x00), + aResBitmapEx.GetPixelColor(2, 2)); + } +} + void BitmapFilterTest::testDarkenBlendFilter() { Bitmap aRedBitmap(Size(4, 4), vcl::PixelFormat::N24_BPP); diff --git a/vcl/source/bitmap/BitmapNormalBlendFilter.cxx b/vcl/source/bitmap/BitmapNormalBlendFilter.cxx new file mode 100644 index 000000000000..5f207d37a756 --- /dev/null +++ b/vcl/source/bitmap/BitmapNormalBlendFilter.cxx @@ -0,0 +1,104 @@ +/* -*- 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/BitmapNormalBlendFilter.hxx> +#include <vcl/BitmapWriteAccess.hxx> +#include <vcl/BitmapTools.hxx> + +BitmapNormalBlendFilter::BitmapNormalBlendFilter(BitmapEx const& rBitmapEx, + BitmapEx const& rBitmapEx2) + : maBitmapEx(rBitmapEx) + , maBitmapEx2(rBitmapEx2) +{ +} + +BitmapNormalBlendFilter::~BitmapNormalBlendFilter() {} + +static sal_uInt8 lcl_calculate(const sal_uInt8 aColor, const sal_uInt8 aAlpha, + const sal_uInt8 aColor2) +{ + const double c1 = aColor / 255.0; + const double c2 = aColor2 / 255.0; + const double a1 = aAlpha / 255.0; + const double result = (1.0 - a1) * c2 + c1; + 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 BitmapNormalBlendFilter::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())); + sal_uInt8 g(lcl_calculate(i1.GetGreen(), i1.GetAlpha(), i2.GetGreen())); + sal_uInt8 b(lcl_calculate(i1.GetBlue(), i1.GetAlpha(), i2.GetBlue())); + sal_uInt8 a(lcl_calculate(i1.GetAlpha(), i1.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, "BitmapNormalBlendFilter: could not access bitmap"); + } + } + + return BitmapEx(aDstBitmap, AlphaMask(aDstAlpha)); +} +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */