vcl/qa/cppunit/BitmapFilterTest.cxx | 4 vcl/source/bitmap/BitmapFilterStackBlur.cxx | 189 ++++++++-------------------- 2 files changed, 57 insertions(+), 136 deletions(-)
New commits: commit 2ef907868db1b57e64ac7f9cda1396b6f39b0fe9 Author: Tomaž Vajngerl <[email protected]> AuthorDate: Sat Feb 21 13:39:27 2026 +0900 Commit: Tomaž Vajngerl <[email protected]> CommitDate: Sun Feb 22 16:20:47 2026 +0100 vcl: simplify 2 horiz. and vert. functions into 1 in stack blur impl. Makes the code simpler and should still perform the same. Change-Id: I17854eaf06770933fdd6122863aa5415bc52c7a3 Reviewed-on: https://gerrit.libreoffice.org/c/core/+/199920 Reviewed-by: Tomaž Vajngerl <[email protected]> Tested-by: Jenkins CollaboraOffice <[email protected]> diff --git a/vcl/qa/cppunit/BitmapFilterTest.cxx b/vcl/qa/cppunit/BitmapFilterTest.cxx index 4c48d25fd69f..06032dbcea40 100644 --- a/vcl/qa/cppunit/BitmapFilterTest.cxx +++ b/vcl/qa/cppunit/BitmapFilterTest.cxx @@ -220,14 +220,14 @@ void BitmapFilterTest::testPerformance() } int nIterations = 10; - auto start = std::chrono::high_resolution_clock::now(); + auto start = std::chrono::steady_clock::now(); Bitmap aResult; for (int i = 0; i < nIterations; i++) { BitmapFilterStackBlur aBlurFilter(250); aResult = aBlurFilter.execute(aBigBitmap); } - auto end = std::chrono::high_resolution_clock::now(); + auto end = std::chrono::steady_clock::now(); auto elapsed = (end - start) / nIterations; if (constWriteResultBitmap) diff --git a/vcl/source/bitmap/BitmapFilterStackBlur.cxx b/vcl/source/bitmap/BitmapFilterStackBlur.cxx index 1db2cb9768f9..427bc47b3fba 100644 --- a/vcl/source/bitmap/BitmapFilterStackBlur.cxx +++ b/vcl/source/bitmap/BitmapFilterStackBlur.cxx @@ -66,6 +66,22 @@ struct BlurSharedData , mnColorChannels(nColorChannels) { } + + template <bool Horizontal> Scanline readPixel(sal_Int32 nOuter, sal_Int32 nInner) const + { + if constexpr (Horizontal) + return mpReadAccess->GetScanline(nOuter) + mnComponentWidth * nInner; + else + return mpReadAccess->GetScanline(nInner) + mnComponentWidth * nOuter; + } + + template <bool Horizontal> Scanline writePixel(sal_Int32 nOuter, sal_Int32 nInner) const + { + if constexpr (Horizontal) + return mpWriteAccess->GetScanline(nOuter) + mnComponentWidth * nInner; + else + return mpWriteAccess->GetScanline(nInner) + mnComponentWidth * nOuter; + } }; struct BlurArrays @@ -236,19 +252,17 @@ struct SumFunction8 } }; -template <typename SumFunction> -void stackBlurHorizontal(BlurSharedData const& rShared, sal_Int32 nStart, sal_Int32 nEnd) +template <typename SumFunction, bool Horizontal> +void stackBlur(BlurSharedData const& rShared, sal_Int32 nStart, sal_Int32 nEnd) { - BitmapReadAccess* pReadAccess = rShared.mpReadAccess; - BitmapWriteAccess* pWriteAccess = rShared.mpWriteAccess; - BlurArrays aArrays(rShared); sal_uInt8* pStack = aArrays.maStackBuffer.data(); sal_uInt8* pStackPtr; - const sal_Int32 nWidth = pReadAccess->Width(); - const sal_Int32 nLastIndexX = nWidth - 1; + const sal_Int32 nLength + = Horizontal ? rShared.mpReadAccess->Width() : rShared.mpReadAccess->Height(); + const sal_Int32 nLastIndex = nLength - 1; const sal_Int32 nMultiplyValue = aArrays.getMultiplyValue(); const sal_Int32 nShiftValue = aArrays.getShiftValue(); @@ -260,7 +274,7 @@ void stackBlurHorizontal(BlurSharedData const& rShared, sal_Int32 nStart, sal_In Scanline pSourcePointer; Scanline pDestinationPointer; - aArrays.initializeWeightAndPositions(nLastIndexX); + aArrays.initializeWeightAndPositions(nLastIndex); sal_Int32* nSum = aArrays.mnSumVector.data(); sal_Int32* nInSum = aArrays.mnInSumVector.data(); @@ -269,7 +283,7 @@ void stackBlurHorizontal(BlurSharedData const& rShared, sal_Int32 nStart, sal_In sal_Int32* pPositionPointer = aArrays.maPositionTable.data(); sal_Int32* pWeightPointer = aArrays.maWeightTable.data(); - for (sal_Int32 y = nStart; y <= nEnd; y++) + for (sal_Int32 nOuter = nStart; nOuter <= nEnd; nOuter++) { SumFunction::set(nSum, 0); SumFunction::set(nInSum, 0); @@ -277,13 +291,13 @@ void stackBlurHorizontal(BlurSharedData const& rShared, sal_Int32 nStart, sal_In // Pre-initialize blur data for first pixel. // aArrays.maPositionTable contains values like (for radius of 5): [0,0,0,0,0,0,1,2,3,4,5], - // which are used as pixels indices in the current row that we use to prepare information - // for the first pixel; aArrays.maWeightTable has [1,2,3,4,5,6,5,4,3,2,1]. Before looking at - // the first row pixel, we pretend to have processed fake previous pixels, as if the row was - // extended to the left with the same color as that of the first pixel. + // which are used as pixel indices along the blur direction that we use to prepare + // information for the first pixel; aArrays.maWeightTable has [1,2,3,4,5,6,5,4,3,2,1]. + // Before looking at the first pixel, we pretend to have processed fake previous pixels, + // as if the line was extended with the same color as that of the first pixel. for (sal_Int32 i = 0; i < nDiv; i++) { - pSourcePointer = pReadAccess->GetScanline(y) + nComponentWidth * pPositionPointer[i]; + pSourcePointer = rShared.readPixel<Horizontal>(nOuter, pPositionPointer[i]); pStackPtr = &pStack[nComponentWidth * i]; @@ -302,13 +316,13 @@ void stackBlurHorizontal(BlurSharedData const& rShared, sal_Int32 nStart, sal_In } sal_Int32 nStackIndex = nRadius; - sal_Int32 nXPosition = std::min(nRadius, nLastIndexX); + sal_Int32 nPosition = std::min(nRadius, nLastIndex); - pSourcePointer = pReadAccess->GetScanline(y) + nComponentWidth * nXPosition; + pSourcePointer = rShared.readPixel<Horizontal>(nOuter, nPosition); - for (sal_Int32 x = 0; x < nWidth; x++) + for (sal_Int32 nInner = 0; nInner < nLength; nInner++) { - pDestinationPointer = pWriteAccess->GetScanline(y) + nComponentWidth * x; + pDestinationPointer = rShared.writePixel<Horizontal>(nOuter, nInner); SumFunction::assignMulAndShr(pDestinationPointer, nSum, nMultiplyValue, nShiftValue); @@ -323,10 +337,10 @@ void stackBlurHorizontal(BlurSharedData const& rShared, sal_Int32 nStart, sal_In SumFunction::sub(nOutSum, pStackPtr); - if (nXPosition < nLastIndexX) + if (nPosition < nLastIndex) { - nXPosition++; - pSourcePointer = pReadAccess->GetScanline(y) + nComponentWidth * nXPosition; + nPosition++; + pSourcePointer = rShared.readPixel<Horizontal>(nOuter, nPosition); } SumFunction::assignPtr(pStackPtr, pSourcePointer); @@ -350,111 +364,15 @@ void stackBlurHorizontal(BlurSharedData const& rShared, sal_Int32 nStart, sal_In } template <typename SumFunction> -void stackBlurVertical(BlurSharedData const& rShared, sal_Int32 nStart, sal_Int32 nEnd) +void stackBlurHorizontal(BlurSharedData const& rShared, sal_Int32 nStart, sal_Int32 nEnd) { - BitmapReadAccess* pReadAccess = rShared.mpReadAccess; - BitmapWriteAccess* pWriteAccess = rShared.mpWriteAccess; - - BlurArrays aArrays(rShared); - - sal_uInt8* pStack = aArrays.maStackBuffer.data(); - sal_uInt8* pStackPtr; - - sal_Int32 nHeight = pReadAccess->Height(); - sal_Int32 nLastIndexY = nHeight - 1; - - sal_Int32 nMultiplyValue = aArrays.getMultiplyValue(); - sal_Int32 nShiftValue = aArrays.getShiftValue(); - - sal_Int32 nRadius = rShared.mnRadius; - sal_Int32 nComponentWidth = rShared.mnComponentWidth; - sal_Int32 nDiv = rShared.mnDiv; - - Scanline pSourcePointer; - Scanline pDestinationPointer; - - aArrays.initializeWeightAndPositions(nLastIndexY); - - sal_Int32* nSum = aArrays.mnSumVector.data(); - sal_Int32* nInSum = aArrays.mnInSumVector.data(); - sal_Int32* nOutSum = aArrays.mnOutSumVector.data(); - sal_Int32* pPositionPointer = aArrays.maPositionTable.data(); - sal_Int32* pWeightPointer = aArrays.maWeightTable.data(); - - for (sal_Int32 x = nStart; x <= nEnd; x++) - { - SumFunction::set(nSum, 0); - SumFunction::set(nInSum, 0); - SumFunction::set(nOutSum, 0); - - // Pre-initialize blur data for first pixel. - // aArrays.maPositionTable contains values like (for radius of 5): [0,0,0,0,0,0,1,2,3,4,5], - // which are used as pixels indices in the current column that we use to prepare information - // for the first pixel; aArrays.maWeightTable has [1,2,3,4,5,6,5,4,3,2,1]. Before looking at - // the first column pixels, we pretend to have processed fake previous pixels, as if the - // column was extended to the top with the same color as that of the first pixel. - for (sal_Int32 i = 0; i < nDiv; i++) - { - pSourcePointer = pReadAccess->GetScanline(pPositionPointer[i]) + nComponentWidth * x; - - pStackPtr = &pStack[nComponentWidth * i]; - - SumFunction::assignPtr(pStackPtr, pSourcePointer); - - SumFunction::mulAndAdd(nSum, pSourcePointer, pWeightPointer[i]); - - if (i - nRadius > 0) - { - SumFunction::add(nInSum, pSourcePointer); - } - else - { - SumFunction::add(nOutSum, pSourcePointer); - } - } - - sal_Int32 nStackIndex = nRadius; - sal_Int32 nYPosition = std::min(nRadius, nLastIndexY); - - pSourcePointer = pReadAccess->GetScanline(nYPosition) + nComponentWidth * x; - - for (sal_Int32 y = 0; y < nHeight; y++) - { - pDestinationPointer = pWriteAccess->GetScanline(y) + nComponentWidth * x; - - SumFunction::assignMulAndShr(pDestinationPointer, nSum, nMultiplyValue, nShiftValue); - - SumFunction::sub(nSum, nOutSum); - - sal_Int32 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]; + stackBlur<SumFunction, true>(rShared, nStart, nEnd); +} - SumFunction::add(nOutSum, pStackPtr); - SumFunction::sub(nInSum, pStackPtr); - } - } +template <typename SumFunction> +void stackBlurVertical(BlurSharedData const& rShared, sal_Int32 nStart, sal_Int32 nEnd) +{ + stackBlur<SumFunction, false>(rShared, nStart, nEnd); } constexpr sal_Int32 nThreadStrip = 16; commit b90d690da8086ec91b279ec6987a231b0775d24e Author: Tomaž Vajngerl <[email protected]> AuthorDate: Sat Feb 21 12:48:29 2026 +0900 Commit: Tomaž Vajngerl <[email protected]> CommitDate: Sun Feb 22 16:20:35 2026 +0100 sc: Fix bug in stack blur algorithm In RGB case we only multiplied the first component instead of all. This introduces mulAndAdd function to multiply each component with the constant and adds to the array for each component. This should fix the issue. The add() function that takes a constant was removed. Change-Id: I7e2620f643b49498a69437e4b572f9417f1ef6af Reviewed-on: https://gerrit.libreoffice.org/c/core/+/199919 Tested-by: Jenkins CollaboraOffice <[email protected]> Reviewed-by: Tomaž Vajngerl <[email protected]> diff --git a/vcl/source/bitmap/BitmapFilterStackBlur.cxx b/vcl/source/bitmap/BitmapFilterStackBlur.cxx index 0f8c2b602bbd..1db2cb9768f9 100644 --- a/vcl/source/bitmap/BitmapFilterStackBlur.cxx +++ b/vcl/source/bitmap/BitmapFilterStackBlur.cxx @@ -137,13 +137,6 @@ public: struct SumFunction24 { - static inline void add(sal_Int32*& pValue1, sal_Int32 nConstant) - { - pValue1[0] += nConstant; - pValue1[1] += nConstant; - pValue1[2] += nConstant; - } - static inline void set(sal_Int32*& pValue1, sal_Int32 nConstant) { pValue1[0] = nConstant; @@ -151,6 +144,13 @@ struct SumFunction24 pValue1[2] = nConstant; } + static inline void mulAndAdd(sal_Int32*& pValue1, const sal_uInt8* pValue2, sal_Int32 nConstant) + { + pValue1[0] += pValue2[0] * nConstant; + pValue1[1] += pValue2[1] * nConstant; + pValue1[2] += pValue2[2] * nConstant; + } + static inline void add(sal_Int32*& pValue1, const sal_uInt8* pValue2) { pValue1[0] += pValue2[0]; @@ -197,10 +197,13 @@ struct SumFunction24 struct SumFunction8 { - static inline void add(sal_Int32*& pValue1, sal_Int32 nConstant) { pValue1[0] += nConstant; } - static inline void set(sal_Int32*& pValue1, sal_Int32 nConstant) { pValue1[0] = nConstant; } + static inline void mulAndAdd(sal_Int32*& pValue1, const sal_uInt8* pValue2, sal_Int32 nConstant) + { + pValue1[0] += pValue2[0] * nConstant; + } + static inline void add(sal_Int32*& pValue1, const sal_uInt8* pValue2) { pValue1[0] += pValue2[0]; @@ -286,7 +289,7 @@ void stackBlurHorizontal(BlurSharedData const& rShared, sal_Int32 nStart, sal_In SumFunction::assignPtr(pStackPtr, pSourcePointer); - SumFunction::add(nSum, pSourcePointer[0] * pWeightPointer[i]); + SumFunction::mulAndAdd(nSum, pSourcePointer, pWeightPointer[i]); if (i - nRadius > 0) { @@ -398,7 +401,7 @@ void stackBlurVertical(BlurSharedData const& rShared, sal_Int32 nStart, sal_Int3 SumFunction::assignPtr(pStackPtr, pSourcePointer); - SumFunction::add(nSum, pSourcePointer[0] * pWeightPointer[i]); + SumFunction::mulAndAdd(nSum, pSourcePointer, pWeightPointer[i]); if (i - nRadius > 0) {
