diff options
-rw-r--r-- | include/vcl/BitmapArithmeticBlendFilter.hxx | 31 | ||||
-rw-r--r-- | svgio/inc/svgfecompositenode.hxx | 6 | ||||
-rw-r--r-- | svgio/inc/svgtoken.hxx | 4 | ||||
-rw-r--r-- | svgio/source/svgreader/svgfecompositenode.cxx | 214 | ||||
-rw-r--r-- | svgio/source/svgreader/svgtoken.cxx | 4 | ||||
-rw-r--r-- | vcl/Library_vcl.mk | 1 | ||||
-rw-r--r-- | vcl/qa/cppunit/BitmapFilterTest.cxx | 122 | ||||
-rw-r--r-- | vcl/source/bitmap/BitmapArithmeticBlendFilter.cxx | 105 |
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: */ |