diff options
author | Tamas Bunth <tamas.bunth@collabora.co.uk> | 2020-01-21 19:04:13 +0100 |
---|---|---|
committer | Tamás Bunth <btomi96@gmail.com> | 2020-03-03 15:52:47 +0100 |
commit | f9fc420dceb1ece2c98767da16a21aaff771f140 (patch) | |
tree | 299b9c856a3567ee85af11b7b314d2d02a03420b /vcl | |
parent | 224ab38f747dcafe711c10b54ad53c52bda9e41d (diff) |
tdf#101181 Implement glow effect on shapes
Glow effect is a color-blurred outline outside of the shape. In ooxml
document it is specified with the <a:glow> element.
The commit contains the following:
- Add support for importing and exporting <a:glow> from ooxml documents.
- Assign new properties to XShape which stores glow-related attributes.
- A new 2D primitive is introduced in module 'drawinglayer' which is
responsible for representing the glow primitive which is to be rendered.
+ A glow primitive is a clone of the original shape which has been
scaled up slightly and a new color has been assigned to it. The
radius of the glow effect and the color is defined in the <a:glow>
element being imported.
- A blur algorithm is introduced in module 'vcl', which is called during
rendering the primitive.
+ The blur algorithm works on a bitmap.
+ Since the algorithm is CPU-intensive, the result is cached in the
processor and it is recalculated only if needed.
- Add support for importing and exporting glow effect to ODF format. For
that, new attributes of element <style:graphic-properties> has been
added:
+ loext:glow, which can have the values "visible" or "hidden"
+ loext:glow-radius: which holds the radius of the glow effect in cm.
+ loext:glow-color: holds the color of the glow effect
- Tests have been added to assert properties after pptx import and
export.
Change-Id: I836aeb5e0f24e2c8d5725834c8c0f98083bc82e7
Reviewed-on: https://gerrit.libreoffice.org/c/core/+/89125
Tested-by: Jenkins
Reviewed-by: Tamás Bunth <btomi96@gmail.com>
Diffstat (limited to 'vcl')
-rw-r--r-- | vcl/CppunitTest_vcl_bitmap_test.mk | 1 | ||||
-rw-r--r-- | vcl/Library_vcl.mk | 1 | ||||
-rw-r--r-- | vcl/qa/cppunit/BitmapFilterTest.cxx | 159 | ||||
-rw-r--r-- | vcl/source/bitmap/BitmapFilterStackBlur.cxx | 554 | ||||
-rw-r--r-- | vcl/source/outdev/bitmap.cxx | 4 |
5 files changed, 719 insertions, 0 deletions
diff --git a/vcl/CppunitTest_vcl_bitmap_test.mk b/vcl/CppunitTest_vcl_bitmap_test.mk index 9ebef12dcfb2..dca63852a8af 100644 --- a/vcl/CppunitTest_vcl_bitmap_test.mk +++ b/vcl/CppunitTest_vcl_bitmap_test.mk @@ -15,6 +15,7 @@ $(eval $(call gb_CppunitTest_add_exception_objects,vcl_bitmap_test, \ vcl/qa/cppunit/bitmapcolor \ vcl/qa/cppunit/ScanlineToolsTest \ vcl/qa/cppunit/BitmapScaleTest \ + vcl/qa/cppunit/BitmapFilterTest \ )) $(eval $(call gb_CppunitTest_use_externals,vcl_bitmap_test,\ diff --git a/vcl/Library_vcl.mk b/vcl/Library_vcl.mk index 79f2002c8c9d..70c23d0d1a71 100644 --- a/vcl/Library_vcl.mk +++ b/vcl/Library_vcl.mk @@ -367,6 +367,7 @@ $(eval $(call gb_Library_add_exception_objects,vcl,\ vcl/source/image/ImageRepository \ vcl/source/image/ImplImage \ vcl/source/image/ImplImageTree \ + vcl/source/bitmap/BitmapFilterStackBlur \ vcl/source/helper/canvasbitmap \ vcl/source/helper/canvastools \ vcl/source/helper/commandinfoprovider \ diff --git a/vcl/qa/cppunit/BitmapFilterTest.cxx b/vcl/qa/cppunit/BitmapFilterTest.cxx new file mode 100644 index 000000000000..732c0c2dfb0b --- /dev/null +++ b/vcl/qa/cppunit/BitmapFilterTest.cxx @@ -0,0 +1,159 @@ +/* -*- 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 <cppunit/TestAssert.h> +#include <cppunit/TestFixture.h> +#include <cppunit/extensions/HelperMacros.h> +#include <cppunit/plugin/TestPlugIn.h> + +#include <vcl/bitmap.hxx> +#include <vcl/bitmapaccess.hxx> +#include <bitmapwriteaccess.hxx> + +#include <tools/stream.hxx> +#include <vcl/graphicfilter.hxx> + +#include <vcl/BitmapFilterStackBlur.hxx> +#include <BitmapSymmetryCheck.hxx> + +#include <chrono> + +namespace +{ +constexpr bool constWriteResultBitmap(false); +constexpr bool constEnablePerformanceTest(false); + +class BitmapFilterTest : public CppUnit::TestFixture +{ + void testBlurCorrectness(); + void testPerformance(); + + CPPUNIT_TEST_SUITE(BitmapFilterTest); + CPPUNIT_TEST(testBlurCorrectness); + CPPUNIT_TEST(testPerformance); + CPPUNIT_TEST_SUITE_END(); +}; + +void BitmapFilterTest::testBlurCorrectness() +{ + // Setup test bitmap + Size aSize(41, 31); + Bitmap aBitmap24Bit(aSize, 24); + + ScanlineFormat scanlineFormat = ScanlineFormat::NONE; + sal_uInt16 nBPP = aBitmap24Bit.GetBitCount(); + + { + long aMargin1 = 1; + long aMargin2 = 3; + BitmapScopedWriteAccess aWriteAccess(aBitmap24Bit); + scanlineFormat = aWriteAccess->GetScanlineFormat(); + aWriteAccess->Erase(COL_WHITE); + aWriteAccess->SetLineColor(COL_BLACK); + + tools::Rectangle aRectangle1(aMargin1, aMargin1, aSize.Width() - 1 - aMargin1, + aSize.Height() - 1 - aMargin1); + + tools::Rectangle aRectangle2(aMargin2, aMargin2, aSize.Width() - 1 - aMargin2, + aSize.Height() - 1 - aMargin2); + + tools::Rectangle aRectangle3(aSize.Width() / 2, aSize.Height() / 2, aSize.Width() / 2, + aSize.Height() / 2); + + aWriteAccess->DrawRect(aRectangle1); + aWriteAccess->DrawRect(aRectangle2); + aWriteAccess->DrawRect(aRectangle3); + } + + if (constWriteResultBitmap) + { + SvFileStream aStream("~/blurBefore.png", StreamMode::WRITE | StreamMode::TRUNC); + GraphicFilter& rFilter = GraphicFilter::GetGraphicFilter(); + rFilter.compressAsPNG(aBitmap24Bit, aStream); + } + + // Perform blur + BitmapFilterStackBlur aBlurFilter(2); + aBitmap24Bit = aBlurFilter.filter(aBitmap24Bit); + + // Check the result + + if (constWriteResultBitmap) + { + SvFileStream aStream("~/blurAfter.png", StreamMode::WRITE | StreamMode::TRUNC); + GraphicFilter& rFilter = GraphicFilter::GetGraphicFilter(); + rFilter.compressAsPNG(aBitmap24Bit, aStream); + } + + // Check blurred bitmap parameters + CPPUNIT_ASSERT_EQUAL(static_cast<long>(45), aBitmap24Bit.GetSizePixel().Width()); + CPPUNIT_ASSERT_EQUAL(static_cast<long>(35), aBitmap24Bit.GetSizePixel().Height()); + + CPPUNIT_ASSERT_EQUAL(nBPP, aBitmap24Bit.GetBitCount()); + + // Check that the bitmap is horizontally and vertically symmetrical + CPPUNIT_ASSERT(BitmapSymmetryCheck::check(aBitmap24Bit)); + + { + Bitmap::ScopedReadAccess aReadAccess(aBitmap24Bit); + CPPUNIT_ASSERT_EQUAL(scanlineFormat, aReadAccess->GetScanlineFormat()); + } +} + +void BitmapFilterTest::testPerformance() +{ + if (!constEnablePerformanceTest) + return; + + Size aSize(4000, 3000); // A rather common picture size + + // Prepare bitmap + Bitmap aBigBitmap(aSize, 24); + { + long aMargin = 500; + BitmapScopedWriteAccess aWriteAccess(aBigBitmap); + aWriteAccess->Erase(COL_WHITE); + aWriteAccess->SetLineColor(COL_BLACK); + aWriteAccess->SetFillColor(COL_BLACK); + tools::Rectangle aRectangle(aMargin, aMargin, aSize.Width() - 1 - aMargin, + aSize.Height() - 1 - aMargin); + + aWriteAccess->DrawRect(aRectangle); + } + + int nIterations = 10; + auto start = std::chrono::high_resolution_clock::now(); + for (int i = 0; i < nIterations; i++) + { + BitmapFilterStackBlur aBlurFilter(250, false); // don't extend the image + aBlurFilter.filter(aBigBitmap); + } + auto end = std::chrono::high_resolution_clock::now(); + auto elapsed = (end - start) / nIterations; + + if (constWriteResultBitmap) + { + std::unique_ptr<SvFileStream> pStream( + new SvFileStream("~/BlurBigPerformance.png", StreamMode::WRITE | StreamMode::TRUNC)); + GraphicFilter& rFilter = GraphicFilter::GetGraphicFilter(); + rFilter.compressAsPNG(aBigBitmap, *pStream); + + pStream.reset(new SvFileStream("~/BlurBigPerformance.txt", StreamMode::WRITE)); + pStream->WriteOString("Blur average time: "); + pStream->WriteOString(OString::number( + std::chrono::duration_cast<std::chrono::milliseconds>(elapsed).count())); + pStream->WriteOString("\n"); + } +} + +} // namespace + +CPPUNIT_TEST_SUITE_REGISTRATION(BitmapFilterTest); + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/vcl/source/bitmap/BitmapFilterStackBlur.cxx b/vcl/source/bitmap/BitmapFilterStackBlur.cxx new file mode 100644 index 000000000000..d88480787b8b --- /dev/null +++ b/vcl/source/bitmap/BitmapFilterStackBlur.cxx @@ -0,0 +1,554 @@ +/* -*- 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 <vcl/BitmapFilterStackBlur.hxx> +#include <vcl/bitmapaccess.hxx> +#include <bitmapwriteaccess.hxx> + +namespace +{ +static const sal_Int16 constMultiplyTable[255] + = { 512, 512, 456, 512, 328, 456, 335, 512, 405, 328, 271, 456, 388, 335, 292, 512, 454, + 405, 364, 328, 298, 271, 496, 456, 420, 388, 360, 335, 312, 292, 273, 512, 482, 454, + 428, 405, 383, 364, 345, 328, 312, 298, 284, 271, 259, 496, 475, 456, 437, 420, 404, + 388, 374, 360, 347, 335, 323, 312, 302, 292, 282, 273, 265, 512, 497, 482, 468, 454, + 441, 428, 417, 405, 394, 383, 373, 364, 354, 345, 337, 328, 320, 312, 305, 298, 291, + 284, 278, 271, 265, 259, 507, 496, 485, 475, 465, 456, 446, 437, 428, 420, 412, 404, + 396, 388, 381, 374, 367, 360, 354, 347, 341, 335, 329, 323, 318, 312, 307, 302, 297, + 292, 287, 282, 278, 273, 269, 265, 261, 512, 505, 497, 489, 482, 475, 468, 461, 454, + 447, 441, 435, 428, 422, 417, 411, 405, 399, 394, 389, 383, 378, 373, 368, 364, 359, + 354, 350, 345, 341, 337, 332, 328, 324, 320, 316, 312, 309, 305, 301, 298, 294, 291, + 287, 284, 281, 278, 274, 271, 268, 265, 262, 259, 257, 507, 501, 496, 491, 485, 480, + 475, 470, 465, 460, 456, 451, 446, 442, 437, 433, 428, 424, 420, 416, 412, 408, 404, + 400, 396, 392, 388, 385, 381, 377, 374, 370, 367, 363, 360, 357, 354, 350, 347, 344, + 341, 338, 335, 332, 329, 326, 323, 320, 318, 315, 312, 310, 307, 304, 302, 299, 297, + 294, 292, 289, 287, 285, 282, 280, 278, 275, 273, 271, 269, 267, 265, 263, 261, 259 }; + +static const sal_Int16 constShiftTable[255] + = { 9, 11, 12, 13, 13, 14, 14, 15, 15, 15, 15, 16, 16, 16, 16, 17, 17, 17, 17, 17, 17, 17, + 18, 18, 18, 18, 18, 18, 18, 18, 18, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, + 19, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 21, 21, 21, + 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, + 21, 21, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, + 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 23, 23, 23, 23, 23, + 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, + 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, + 23, 23, 23, 23, 23, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, + 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, + 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, + 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24 }; + +class BlurSharedData +{ +public: + long mnRadius; + long mnComponentWidth; + long mnColorChannels; + long mnDiv; + std::vector<sal_uInt8> maStackBuffer; + std::vector<long> maPositionTable; + std::vector<long> maWeightTable; + + std::vector<long> mnSumVector; + std::vector<long> mnInSumVector; + std::vector<long> mnOutSumVector; + + BlurSharedData(long aRadius, long nComponentWidth, long nColorChannels) + : mnRadius(aRadius) + , mnComponentWidth(nComponentWidth) + , mnColorChannels(nColorChannels) + , mnDiv(aRadius + aRadius + 1) + , maStackBuffer(mnDiv * mnComponentWidth) + , maPositionTable(mnDiv) + , maWeightTable(mnDiv) + , mnSumVector(mnColorChannels) + , mnInSumVector(mnColorChannels) + , mnOutSumVector(mnColorChannels) + { + } + + void calculateWeightAndPositions(long nLastIndex) + { + for (long i = 0; i < mnDiv; i++) + { + maPositionTable[i] = std::min(nLastIndex, std::max(0L, i - mnRadius)); + maWeightTable[i] = mnRadius + 1 - std::abs(i - mnRadius); + } + } + + long getMultiplyValue() { return static_cast<long>(constMultiplyTable[mnRadius]); } + + long getShiftValue() { return static_cast<long>(constShiftTable[mnRadius]); } +}; + +struct SumFunction24 +{ + static inline void add(long*& pValue1, long nConstant) + { + pValue1[0] += nConstant; + pValue1[1] += nConstant; + pValue1[2] += nConstant; + } + + static inline void set(long*& pValue1, long nConstant) + { + pValue1[0] = nConstant; + pValue1[1] = nConstant; + pValue1[2] = nConstant; + } + + static inline void add(long*& pValue1, sal_uInt8*& pValue2) + { + pValue1[0] += pValue2[0]; + pValue1[1] += pValue2[1]; + pValue1[2] += pValue2[2]; + } + + static inline void add(long*& pValue1, long*& pValue2) + { + pValue1[0] += pValue2[0]; + pValue1[1] += pValue2[1]; + pValue1[2] += pValue2[2]; + } + + static inline void sub(long*& pValue1, sal_uInt8*& pValue2) + { + pValue1[0] -= pValue2[0]; + pValue1[1] -= pValue2[1]; + pValue1[2] -= pValue2[2]; + } + + static inline void sub(long*& pValue1, long*& pValue2) + { + pValue1[0] -= pValue2[0]; + pValue1[1] -= pValue2[1]; + pValue1[2] -= pValue2[2]; + } + + static inline void assignPtr(sal_uInt8*& pValue1, sal_uInt8*& pValue2) + { + pValue1[0] = pValue2[0]; + pValue1[1] = pValue2[1]; + pValue1[2] = pValue2[2]; + } + + static inline void assignMulAndShr(sal_uInt8*& result, long*& sum, long multiply, long shift) + { + result[0] = (multiply * sum[0]) >> shift; + result[1] = (multiply * sum[1]) >> shift; + result[2] = (multiply * sum[2]) >> shift; + } +}; + +struct SumFunction8 +{ + static inline void add(long*& pValue1, long nConstant) { pValue1[0] += nConstant; } + + static inline void set(long*& pValue1, long nConstant) { pValue1[0] = nConstant; } + + static inline void add(long*& pValue1, sal_uInt8*& pValue2) { pValue1[0] += pValue2[0]; } + + static inline void add(long*& pValue1, long*& pValue2) { pValue1[0] += pValue2[0]; } + + static inline void sub(long*& pValue1, sal_uInt8*& pValue2) { pValue1[0] -= pValue2[0]; } + + static inline void sub(long*& pValue1, long*& pValue2) { pValue1[0] -= pValue2[0]; } + + static inline void assignPtr(sal_uInt8*& pValue1, sal_uInt8*& pValue2) + { + pValue1[0] = pValue2[0]; + } + + static inline void assignMulAndShr(sal_uInt8*& result, long*& sum, long multiply, long shift) + { + result[0] = (multiply * sum[0]) >> shift; + } +}; + +template <typename SumFunction> +void stackBlurHorizontal(BitmapReadAccess* pReadAccess, BitmapWriteAccess* pWriteAccess, + BlurSharedData& rShared) +{ + long nWidth = pReadAccess->Width(); + long nHeight = pReadAccess->Height(); + + sal_uInt8* pStack = rShared.maStackBuffer.data(); + sal_uInt8* pStackPtr; + + long nLastIndexX = nWidth - 1; + + long nMultiplyValue = rShared.getMultiplyValue(); + long nShiftValue = rShared.getShiftValue(); + + long nRadius = rShared.mnRadius; + long nComponentWidth = rShared.mnComponentWidth; + long nDiv = rShared.mnDiv; + + Scanline pSourcePointer; + Scanline pDestinationPointer; + + long nXPosition; + long nStackIndex; + long nStackIndexStart; + long nWeight; + + long* nSum = rShared.mnSumVector.data(); + long* nInSum = rShared.mnInSumVector.data(); + long* nOutSum = rShared.mnOutSumVector.data(); + + rShared.calculateWeightAndPositions(nLastIndexX); + long* pPositionPointer = rShared.maPositionTable.data(); + long* pWeightPointer = rShared.maWeightTable.data(); + + for (long y = 0; y < nHeight; y++) + { + SumFunction::set(nSum, 0L); + SumFunction::set(nInSum, 0L); + SumFunction::set(nOutSum, 0L); + + for (long i = 0; i < nDiv; i++) + { + pSourcePointer = pReadAccess->GetScanline(pPositionPointer[i]); + + pStackPtr = &pStack[nComponentWidth * i]; + + SumFunction::assignPtr(pStackPtr, pSourcePointer); + + nWeight = pWeightPointer[i]; + + SumFunction::add(nSum, pSourcePointer[0] * nWeight); + + if (i - nRadius > 0) + { + SumFunction::add(nInSum, pSourcePointer); + } + else + { + SumFunction::add(nOutSum, pSourcePointer); + } + } + + nStackIndex = nRadius; + nXPosition = std::min(nRadius, nLastIndexX); + + pSourcePointer = pReadAccess->GetScanline(y) + nComponentWidth * nXPosition; + + for (long x = 0; x < nWidth; x++) + { + pDestinationPointer = pWriteAccess->GetScanline(y) + nComponentWidth * x; + + SumFunction::assignMulAndShr(pDestinationPointer, nSum, nMultiplyValue, nShiftValue); + + SumFunction::sub(nSum, nOutSum); + + nStackIndexStart = nStackIndex + nDiv - nRadius; + if (nStackIndexStart >= nDiv) + { + nStackIndexStart -= nDiv; + } + pStackPtr = &pStack[nComponentWidth * nStackIndexStart]; + + SumFunction::sub(nOutSum, pStackPtr); + + if (nXPosition < nLastIndexX) + { + nXPosition++; + pSourcePointer = pReadAccess->GetScanline(y) + nComponentWidth * nXPosition; + } + + SumFunction::assignPtr(pStackPtr, pSourcePointer); + + SumFunction::add(nInSum, pSourcePointer); + + SumFunction::add(nSum, nInSum); + + nStackIndex++; + if (nStackIndex >= nDiv) + { + nStackIndex = 0; + } + + pStackPtr = &pStack[nStackIndex * nComponentWidth]; + + SumFunction::add(nOutSum, pStackPtr); + SumFunction::sub(nInSum, pStackPtr); + } + } +} + +template <typename SumFunction> +void stackBlurVertical(BitmapReadAccess* pReadAccess, BitmapWriteAccess* pWriteAccess, + BlurSharedData& rShared) +{ + long nWidth = pReadAccess->Width(); + long nHeight = pReadAccess->Height(); + + sal_uInt8* pStack = rShared.maStackBuffer.data(); + sal_uInt8* pStackPtr; + + long nLastIndexY = nHeight - 1; + + long nMultiplyValue = rShared.getMultiplyValue(); + long nShiftValue = rShared.getShiftValue(); + + long nRadius = rShared.mnRadius; + long nComponentWidth = rShared.mnComponentWidth; + long nDiv = rShared.mnDiv; + + Scanline pSourcePointer; + Scanline pDestinationPointer; + + long nYPosition; + long nStackIndex; + long nStackIndexStart; + long nWeight; + + long* nSum = rShared.mnSumVector.data(); + long* nInSum = rShared.mnInSumVector.data(); + long* nOutSum = rShared.mnOutSumVector.data(); + + rShared.calculateWeightAndPositions(nLastIndexY); + long* pPositionPointer = rShared.maPositionTable.data(); + long* pWeightPointer = rShared.maWeightTable.data(); + + for (long x = 0; x < nWidth; x++) + { + SumFunction::set(nSum, 0L); + SumFunction::set(nInSum, 0L); + SumFunction::set(nOutSum, 0L); + + for (long i = 0; i < nDiv; i++) + { + pSourcePointer = pReadAccess->GetScanline(pPositionPointer[i]); + + pStackPtr = &pStack[nComponentWidth * i]; + + SumFunction::assignPtr(pStackPtr, pSourcePointer); + + nWeight = pWeightPointer[i]; + + SumFunction::add(nSum, pSourcePointer[0] * nWeight); + + if (i - nRadius > 0) + { + SumFunction::add(nInSum, pSourcePointer); + } + else + { + SumFunction::add(nOutSum, pSourcePointer); + } + } + + nStackIndex = nRadius; + nYPosition = std::min(nRadius, nLastIndexY); + + pSourcePointer = pReadAccess->GetScanline(nYPosition) + nComponentWidth * x; + + for (long y = 0; y < nHeight; y++) + { + pDestinationPointer = pWriteAccess->GetScanline(y) + nComponentWidth * x; + + SumFunction::assignMulAndShr(pDestinationPointer, nSum, nMultiplyValue, nShiftValue); + + SumFunction::sub(nSum, nOutSum); + + nStackIndexStart = nStackIndex + nDiv - nRadius; + if (nStackIndexStart >= nDiv) + { + nStackIndexStart -= nDiv; + } + pStackPtr = &pStack[nComponentWidth * nStackIndexStart]; + + SumFunction::sub(nOutSum, pStackPtr); + + if (nYPosition < nLastIndexY) + { + nYPosition++; + pSourcePointer = pReadAccess->GetScanline(nYPosition) + nComponentWidth * x; + } + + SumFunction::assignPtr(pStackPtr, pSourcePointer); + + SumFunction::add(nInSum, pSourcePointer); + + SumFunction::add(nSum, nInSum); + + nStackIndex++; + if (nStackIndex >= nDiv) + { + nStackIndex = 0; + } + + pStackPtr = &pStack[nStackIndex * nComponentWidth]; + + SumFunction::add(nOutSum, pStackPtr); + + SumFunction::sub(nInSum, pStackPtr); + } + } +} + +void stackBlur24(Bitmap& rBitmap, sal_Int32 nRadius, sal_Int32 nComponentWidth) +{ + // Limit radius + nRadius = std::clamp<sal_Int32>(nRadius, 2, 254); + const long nColorChannels = 3; // 3 color channel + BlurSharedData aData(nRadius, nComponentWidth, nColorChannels); + + { + Bitmap::ScopedReadAccess pReadAccess(rBitmap); + BitmapScopedWriteAccess pWriteAccess(rBitmap); + + stackBlurHorizontal<SumFunction24>(pReadAccess.get(), pWriteAccess.get(), aData); + } + + { + Bitmap::ScopedReadAccess pReadAccess(rBitmap); + BitmapScopedWriteAccess pWriteAccess(rBitmap); + + stackBlurVertical<SumFunction24>(pReadAccess.get(), pWriteAccess.get(), aData); + } +} + +void stackBlur8(Bitmap& rBitmap, sal_Int32 nRadius, sal_Int32 nComponentWidth) +{ + // Limit radius + nRadius = std::clamp<sal_Int32>(nRadius, 2, 254); + const long nColorChannels = 1; // 1 color channel + BlurSharedData aData(nRadius, nComponentWidth, nColorChannels); + + { + Bitmap::ScopedReadAccess pReadAccess(rBitmap); + BitmapScopedWriteAccess pWriteAccess(rBitmap); + + stackBlurHorizontal<SumFunction8>(pReadAccess.get(), pWriteAccess.get(), aData); + } + + { + Bitmap::ScopedReadAccess pReadAccess(rBitmap); + BitmapScopedWriteAccess pWriteAccess(rBitmap); + + stackBlurVertical<SumFunction8>(pReadAccess.get(), pWriteAccess.get(), aData); + } +} + +void centerExtendBitmap(Bitmap& rBitmap, sal_Int32 nExtendSize, Color aColor) +{ + const Size& rSize = rBitmap.GetSizePixel(); + const Size aNewSize(rSize.Width() + nExtendSize * 2, rSize.Height() + nExtendSize * 2); + + Bitmap aNewBitmap(aNewSize, rBitmap.GetBitCount()); + + { + Bitmap::ScopedReadAccess pReadAccess(rBitmap); + BitmapScopedWriteAccess pWriteAccess(aNewBitmap); + + long nWidthBorder = nExtendSize + rSize.Width(); + long nHeightBorder = nExtendSize + rSize.Height(); + + for (long y = 0; y < aNewSize.Height(); y++) + { + for (long x = 0; x < aNewSize.Width(); x++) + { + if (y < nExtendSize || y >= nHeightBorder || x < nExtendSize || x >= nWidthBorder) + { + pWriteAccess->SetPixel(y, x, aColor); + } + else + { + pWriteAccess->SetPixel(y, x, + pReadAccess->GetPixel(y - nExtendSize, x - nExtendSize)); + } + } + } + } + rBitmap = aNewBitmap; +} + +} // end anonymous namespace + +/** + * Implementation of stack blur - a fast Gaussian blur approximation. + * nRadius - blur radious, valid values are between 2 and 254 + * bExtend - extend the bitmap in all directions by the radius + * + * Stack Blur Algorithm by Mario Klingemann <mario@quasimondo.com> + * (http://www.quasimondo.com/StackBlurForCanvas/StackBlurDemo.html) + * + * Additionally eferences and implementations: + * - Blur.js by Jacob Kelley + * (http://www.blurjs.com) + * - BlurEffectForAndroidDesign by Nicolas Pomepuy + * (https://github.com/PomepuyN/BlurEffectForAndroidDesign) + * - StackBluriOS by Thomas Landspurg + * (https://github.com/tomsoft1/StackBluriOS) + * - stackblur.cpp by Benjamin Yates + * (https://gist.github.com/benjamin9999/3809142) + * - stack blur in fog 2D graphic library by Petr Kobalicek + * (https://code.google.com/p/fog/) + * + */ +BitmapFilterStackBlur::BitmapFilterStackBlur(sal_Int32 nRadius, bool bExtend) + : mnRadius(nRadius) + , mbExtend(bExtend) +{ +} + +BitmapFilterStackBlur::~BitmapFilterStackBlur() {} + +BitmapEx BitmapFilterStackBlur::execute(BitmapEx const& rBitmapEx) const +{ + Bitmap aBitmap = rBitmapEx.GetBitmap(); + Bitmap result = filter(aBitmap); + return BitmapEx(result, rBitmapEx.GetMask()); +} + +Bitmap BitmapFilterStackBlur::filter(Bitmap const& rBitmap) const +{ + Bitmap bitmapCopy(rBitmap); + ScanlineFormat nScanlineFormat; + { + Bitmap::ScopedReadAccess pReadAccess(bitmapCopy); + nScanlineFormat = pReadAccess->GetScanlineFormat(); + } + + if (nScanlineFormat == ScanlineFormat::N24BitTcRgb + || nScanlineFormat == ScanlineFormat::N24BitTcBgr + || nScanlineFormat == ScanlineFormat::N32BitTcMask + || nScanlineFormat == ScanlineFormat::N32BitTcBgra) + { + int nComponentWidth = (nScanlineFormat == ScanlineFormat::N32BitTcMask + || nScanlineFormat == ScanlineFormat::N32BitTcBgra) + ? 4 + : 3; + + if (mbExtend) + { + centerExtendBitmap(bitmapCopy, mnRadius, COL_WHITE); + } + + stackBlur24(bitmapCopy, mnRadius, nComponentWidth); + } + else if (nScanlineFormat == ScanlineFormat::N8BitPal) + { + int nComponentWidth = 1; + + if (mbExtend) + { + centerExtendBitmap(bitmapCopy, mnRadius, COL_WHITE); + } + + stackBlur8(bitmapCopy, mnRadius, nComponentWidth); + } + + return bitmapCopy; +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/vcl/source/outdev/bitmap.cxx b/vcl/source/outdev/bitmap.cxx index 0e1f3979e5ba..6e707e953ab1 100644 --- a/vcl/source/outdev/bitmap.cxx +++ b/vcl/source/outdev/bitmap.cxx @@ -21,6 +21,7 @@ #include <vcl/bitmap.hxx> #include <vcl/bitmapex.hxx> +#include <vcl/BitmapFilterStackBlur.hxx> #include <vcl/bitmapaccess.hxx> #include <vcl/canvastools.hxx> #include <vcl/gdimtf.hxx> @@ -48,6 +49,9 @@ #include <tools/helpers.hxx> #include <tools/debug.hxx> +#include <vcl/dibtools.hxx> +#include <tools/stream.hxx> + void OutputDevice::DrawBitmap( const Point& rDestPt, const Bitmap& rBitmap ) { assert(!is_double_buffered_window()); |