diff options
-rw-r--r-- | include/vcl/BitmapFilterStackBlur.hxx | 34 | ||||
-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 | 175 | ||||
-rw-r--r-- | vcl/source/bitmap/BitmapFilterStackBlur.cxx | 590 |
5 files changed, 801 insertions, 0 deletions
diff --git a/include/vcl/BitmapFilterStackBlur.hxx b/include/vcl/BitmapFilterStackBlur.hxx new file mode 100644 index 000000000000..9b400c812c11 --- /dev/null +++ b/include/vcl/BitmapFilterStackBlur.hxx @@ -0,0 +1,34 @@ +/* -*- 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_BITMAPFILTERSTACKBLUR_HXX +#define INCLUDED_VCL_BITMAPFILTERSTACKBLUR_HXX + +#include <vcl/bitmap.hxx> +#include <vcl/bitmapex.hxx> +#include <vcl/bitmapfilter.hxx> + +class VCL_DLLPUBLIC BitmapFilterStackBlur : BitmapFilter +{ + sal_Int32 mnRadius; + bool mbExtend; + +public: + BitmapFilterStackBlur(sal_Int32 nRadius, bool bExtend = true); + virtual ~BitmapFilterStackBlur(); + + virtual bool filter(Bitmap& rBitmap) SAL_OVERRIDE; + + bool filter(BitmapEx& rBitmap); +}; + +#endif // INCLUDED_VCL_BITMAPFILTERSTACKBLUR_HXX + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/vcl/CppunitTest_vcl_bitmap_test.mk b/vcl/CppunitTest_vcl_bitmap_test.mk index 527f5efe421e..5927b6974b44 100644 --- a/vcl/CppunitTest_vcl_bitmap_test.mk +++ b/vcl/CppunitTest_vcl_bitmap_test.mk @@ -11,6 +11,7 @@ $(eval $(call gb_CppunitTest_CppunitTest,vcl_bitmap_test)) $(eval $(call gb_CppunitTest_add_exception_objects,vcl_bitmap_test, \ vcl/qa/cppunit/BitmapTest \ + 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 4507f5ea427f..89ed3bb0c133 100644 --- a/vcl/Library_vcl.mk +++ b/vcl/Library_vcl.mk @@ -316,6 +316,7 @@ $(eval $(call gb_Library_add_exception_objects,vcl,\ vcl/source/bitmap/bitmapfilter \ vcl/source/bitmap/bitmapscalesuper \ vcl/source/bitmap/BitmapSymmetryCheck \ + vcl/source/bitmap/BitmapFilterStackBlur \ vcl/source/helper/canvasbitmap \ vcl/source/helper/canvastools \ vcl/source/helper/evntpost \ diff --git a/vcl/qa/cppunit/BitmapFilterTest.cxx b/vcl/qa/cppunit/BitmapFilterTest.cxx new file mode 100644 index 000000000000..e0cf3ef1ca8a --- /dev/null +++ b/vcl/qa/cppunit/BitmapFilterTest.cxx @@ -0,0 +1,175 @@ +/* -*- 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/bmpacc.hxx> + +#include <tools/stream.hxx> +#include <vcl/graphicfilter.hxx> + +#include <vcl/BitmapFilterStackBlur.hxx> +#include "BitmapSymmetryCheck.hxx" + +#include <chrono> + +namespace +{ + +const bool constWriteResultBitmap(false); +const 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); + + sal_uLong nScanlineFormat = 0; + sal_uInt16 nBPP = aBitmap24Bit.GetBitCount(); + + { + long aMargin1 = 1; + long aMargin2 = 3; + Bitmap::ScopedWriteAccess aWriteAccess(aBitmap24Bit); + nScanlineFormat = aWriteAccess->GetScanlineFormat(); + aWriteAccess->Erase(COL_WHITE); + aWriteAccess->SetLineColor(COL_BLACK); + + Rectangle aRectangle1( + aMargin1, + aMargin1, + aSize.Width() - 1 - aMargin1, + aSize.Height() - 1 - aMargin1); + + Rectangle aRectangle2( + aMargin2, + aMargin2, + aSize.Width() - 1 - aMargin2, + aSize.Height() - 1 - aMargin2); + + 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(OUString("~/blurBefore.png"), StreamMode::WRITE | StreamMode::TRUNC); + GraphicFilter& rFilter = GraphicFilter::GetGraphicFilter(); + rFilter.compressAsPNG(aBitmap24Bit, aStream, 1); + } + + // Perform blur + BitmapFilterStackBlur aBlurFilter(2); + aBlurFilter.filter(aBitmap24Bit); + + // Check the result + + if (constWriteResultBitmap) + { + SvFileStream aStream(OUString("~/blurAfter.png"), StreamMode::WRITE | StreamMode::TRUNC); + GraphicFilter& rFilter = GraphicFilter::GetGraphicFilter(); + rFilter.compressAsPNG(aBitmap24Bit, aStream, 1); + } + + // 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 + BitmapSymmetryCheck symmetryCheck; + CPPUNIT_ASSERT(symmetryCheck.check(aBitmap24Bit)); + + { + Bitmap::ScopedReadAccess aReadAccess(aBitmap24Bit); + CPPUNIT_ASSERT_EQUAL(nScanlineFormat, 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; + Bitmap::ScopedWriteAccess aWriteAccess(aBigBitmap); + aWriteAccess->Erase(COL_WHITE); + aWriteAccess->SetLineColor(COL_BLACK); + aWriteAccess->SetFillColor(COL_BLACK); + 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(OUString("~/BlurBigPerformance.png"), StreamMode::WRITE | StreamMode::TRUNC)); + GraphicFilter& rFilter = GraphicFilter::GetGraphicFilter(); + rFilter.compressAsPNG(aBigBitmap, *pStream, 1); + + pStream.reset( + new SvFileStream(OUString("~/BlurBigPerformance.txt"), StreamMode::WRITE)); + pStream->WriteOString(OString("Blur average time: ")); + pStream->WriteOString(OString::number(std::chrono::duration_cast<std::chrono::milliseconds>(elapsed).count())); + pStream->WriteOString(OString("\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..126eee0c1f13 --- /dev/null +++ b/vcl/source/bitmap/BitmapFilterStackBlur.cxx @@ -0,0 +1,590 @@ +/* -*- 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/bmpacc.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::min(254, std::max(2, nRadius)); + const long nColorChannels = 3; // 3 color channel + BlurSharedData aData(nRadius, nComponentWidth, nColorChannels); + + { + Bitmap::ScopedReadAccess pReadAccess(rBitmap); + Bitmap::ScopedWriteAccess pWriteAccess(rBitmap); + + stackBlurHorizontal<SumFunction24>(pReadAccess.get(), pWriteAccess.get(), aData); + } + + { + Bitmap::ScopedReadAccess pReadAccess(rBitmap); + Bitmap::ScopedWriteAccess pWriteAccess(rBitmap); + + stackBlurVertical<SumFunction24>(pReadAccess.get(), pWriteAccess.get(), aData); + } +} + +void stackBlur8(Bitmap& rBitmap, sal_Int32 nRadius, sal_Int32 nComponentWidth) +{ + // Limit radius + nRadius = std::min(254, std::max(2, nRadius)); + const long nColorChannels = 1; // 1 color channel + BlurSharedData aData(nRadius, nComponentWidth, nColorChannels); + + { + Bitmap::ScopedReadAccess pReadAccess(rBitmap); + Bitmap::ScopedWriteAccess pWriteAccess(rBitmap); + + stackBlurHorizontal<SumFunction8>(pReadAccess.get(), pWriteAccess.get(), aData); + } + + { + Bitmap::ScopedReadAccess pReadAccess(rBitmap); + Bitmap::ScopedWriteAccess 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); + Bitmap::ScopedWriteAccess pWriteAccess(aNewBitmap); + + long nWidthBorder = nExtendSize + rSize.Width(); + long nHeightBorder = nExtendSize + rSize.Height(); + + for (int y = 0; y < aNewSize.Height(); y++) + { + for (int 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() +{} + +bool BitmapFilterStackBlur::filter(Bitmap& rBitmap) +{ + sal_uLong nScanlineFormat; + { + Bitmap::ScopedReadAccess pReadAccess(rBitmap); + nScanlineFormat = pReadAccess->GetScanlineFormat(); + } + + if (nScanlineFormat == BMP_FORMAT_24BIT_TC_RGB || + nScanlineFormat == BMP_FORMAT_24BIT_TC_BGR || + nScanlineFormat == BMP_FORMAT_32BIT_TC_MASK) + { + int nComponentWidth = (nScanlineFormat == BMP_FORMAT_32BIT_TC_MASK) ? 4 : 3; + + if (mbExtend) + { + centerExtendBitmap(rBitmap, mnRadius, COL_WHITE); + } + + stackBlur24(rBitmap, mnRadius, nComponentWidth); + } + else if (nScanlineFormat == BMP_FORMAT_8BIT_PAL) + { + int nComponentWidth = 1; + + if (mbExtend) + { + centerExtendBitmap(rBitmap, mnRadius, COL_WHITE); + } + + stackBlur8(rBitmap, mnRadius, nComponentWidth); + } + + return true; +} + +bool BitmapFilterStackBlur::filter(BitmapEx& rBitmapEx) +{ + Bitmap aBitmap = rBitmapEx.GetBitmap(); + filter(aBitmap); + rBitmapEx = BitmapEx(aBitmap); + + return true; +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ |