vcl: ImplSeparableBlurFilter() -> BitmapGaussianSeparableBlurFilter

Change-Id: I996c9fcb0524e14e0093142be0749f0e5836426b
Reviewed-on: https://gerrit.libreoffice.org/53071
Tested-by: Jenkins <ci@libreoffice.org>
Reviewed-by: Tomaž Vajngerl <quikee@gmail.com>
diff --git a/include/vcl/BitmapGaussianSeparableBlurFilter.hxx b/include/vcl/BitmapGaussianSeparableBlurFilter.hxx
new file mode 100644
index 0000000..6e6a3a0
--- /dev/null
+++ b/include/vcl/BitmapGaussianSeparableBlurFilter.hxx
@@ -0,0 +1,50 @@
/* -*- 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_BITMAPGAUSSIANSEPARABLEBLURFILTER_HXX
#define INCLUDED_VCL_BITMAPGAUSSIANSEPARABLEBLURFILTER_HXX

#include <vcl/BitmapFilter.hxx>

class BitmapEx;

class VCL_DLLPUBLIC BitmapGaussianSeparableBlurFilter : public BitmapFilter
{
public:
    BitmapGaussianSeparableBlurFilter(double fRadius)
        : mfRadius(fRadius)
    {
    }

    /** Separable Gaussian Blur filter and accepts a blur radius
        as a parameter so the user can change the strength of the blur.
        Radius of 1.0 is 3 * standard deviation of gauss function.

        Separable Blur implementation uses 2x separable 1D convolution
        to process the image.
    */
    virtual BitmapEx execute(BitmapEx const& rBitmapEx) override;

private:
    double mfRadius;

    bool convolutionPass(Bitmap& rBitmap, Bitmap& aNewBitmap, BitmapReadAccess const* pReadAcc,
                         int aNumberOfContributions, const double* pWeights, int const* pPixels,
                         const int* pCount);

    static double* makeBlurKernel(const double radius, int& rows);
    static void blurContributions(const int aSize, const int aNumberOfContributions,
                                  const double* pBlurVector, double*& pWeights, int*& pPixels,
                                  int*& pCount);
};

#endif

/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/include/vcl/bitmap.hxx b/include/vcl/bitmap.hxx
index cc50a6d..6370a63 100644
--- a/include/vcl/bitmap.hxx
+++ b/include/vcl/bitmap.hxx
@@ -676,14 +676,6 @@ public:
    SAL_DLLPRIVATE bool     ImplScaleFast( const double& rScaleX, const double& rScaleY );
    SAL_DLLPRIVATE bool     ImplScaleInterpolate( const double& rScaleX, const double& rScaleY );

    SAL_DLLPRIVATE bool     ImplConvolutionPass(
                                Bitmap& aNewBitmap,
                                BitmapReadAccess const * pReadAcc,
                                int aNumberOfContributions,
                                const double* pWeights,
                                int const * pPixels,
                                const int* pCount );

    SAL_DLLPRIVATE bool     ImplMakeGreyscales( sal_uInt16 nGreyscales );
    SAL_DLLPRIVATE bool     ImplDitherMatrix();
    SAL_DLLPRIVATE bool     ImplDitherFloyd();
@@ -707,16 +699,8 @@ public:
    SAL_DLLPRIVATE bool     ImplMosaic( const BmpFilterParam* pFilterParam );
    SAL_DLLPRIVATE bool     ImplPopArt();

    SAL_DLLPRIVATE bool     ImplSeparableBlurFilter( const double aRadius );
    SAL_DLLPRIVATE bool     ImplSeparableUnsharpenFilter( const double aRadius );
    SAL_DLLPRIVATE bool     ImplDuotoneFilter( const sal_uLong nColorOne,  sal_uLong nColorTwo );
    SAL_DLLPRIVATE static void ImplBlurContributions(
                                const int aSize,
                                const int aNumberOfContributions,
                                const double* pBlurVector,
                                double*& pWeights,
                                int*& pPixels,
                                int*& pCount );

public:

diff --git a/vcl/Library_vcl.mk b/vcl/Library_vcl.mk
index 128541e..03c705a 100644
--- a/vcl/Library_vcl.mk
+++ b/vcl/Library_vcl.mk
@@ -317,6 +317,7 @@ $(eval $(call gb_Library_add_exception_objects,vcl,\
    vcl/source/bitmap/BitmapDisabledImageFilter \
    vcl/source/bitmap/BitmapColorizeFilter \
    vcl/source/bitmap/bitmappaint \
    vcl/source/bitmap/BitmapGaussianSeparableBlurFilter \
	vcl/source/bitmap/BitmapFastScaleFilter \
    vcl/source/bitmap/BitmapScaleSuperFilter \
    vcl/source/bitmap/BitmapScaleConvolutionFilter \
diff --git a/vcl/source/bitmap/BitmapGaussianSeparableBlurFilter.cxx b/vcl/source/bitmap/BitmapGaussianSeparableBlurFilter.cxx
new file mode 100644
index 0000000..b28ce99
--- /dev/null
+++ b/vcl/source/bitmap/BitmapGaussianSeparableBlurFilter.cxx
@@ -0,0 +1,217 @@
/* -*- 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 <basegfx/color/bcolortools.hxx>

#include <vcl/bitmap.hxx>
#include <vcl/bitmapex.hxx>
#include <vcl/bitmapaccess.hxx>
#include <vcl/BitmapGaussianSeparableBlurFilter.hxx>

#include <bitmapwriteaccess.hxx>

BitmapEx BitmapGaussianSeparableBlurFilter::execute(BitmapEx const& rBitmapEx)
{
    Bitmap aBitmap(rBitmapEx.GetBitmap());

    const long nWidth = aBitmap.GetSizePixel().Width();
    const long nHeight = aBitmap.GetSizePixel().Height();

    // Prepare Blur Vector
    int aNumberOfContributions;
    double* pBlurVector = makeBlurKernel(mfRadius, aNumberOfContributions);

    double* pWeights;
    int* pPixels;
    int* pCount;

    // Do horizontal filtering
    blurContributions(nWidth, aNumberOfContributions, pBlurVector, pWeights, pPixels, pCount);

    Bitmap::ScopedReadAccess pReadAcc(aBitmap);

    // switch coordinates as convolution pass transposes result
    Bitmap aNewBitmap(Size(nHeight, nWidth), 24);

    bool bResult = convolutionPass(aBitmap, aNewBitmap, pReadAcc.get(), aNumberOfContributions,
                                   pWeights, pPixels, pCount);

    // Cleanup
    pReadAcc.reset();
    delete[] pWeights;
    delete[] pPixels;
    delete[] pCount;

    if (!bResult)
    {
        delete[] pBlurVector;
    }
    else
    {
        // Swap current bitmap with new bitmap
        aBitmap.ReassignWithSize(aNewBitmap);

        // Do vertical filtering
        blurContributions(nHeight, aNumberOfContributions, pBlurVector, pWeights, pPixels, pCount);

        pReadAcc = Bitmap::ScopedReadAccess(aBitmap);
        aNewBitmap = Bitmap(Size(nWidth, nHeight), 24);
        bResult = convolutionPass(aBitmap, aNewBitmap, pReadAcc.get(), aNumberOfContributions,
                                  pWeights, pPixels, pCount);

        // Cleanup
        pReadAcc.reset();
        delete[] pWeights;
        delete[] pCount;
        delete[] pPixels;
        delete[] pBlurVector;

        if (bResult)
            aBitmap.ReassignWithSize(aNewBitmap); // swap current bitmap with new bitmap
    }

    if (bResult)
        return BitmapEx(aBitmap);

    return BitmapEx();
}

bool BitmapGaussianSeparableBlurFilter::convolutionPass(Bitmap& rBitmap, Bitmap& aNewBitmap,
                                                        BitmapReadAccess const* pReadAcc,
                                                        int aNumberOfContributions,
                                                        const double* pWeights, int const* pPixels,
                                                        const int* pCount)
{
    if (!pReadAcc)
        return false;

    BitmapScopedWriteAccess pWriteAcc(aNewBitmap);
    if (!pWriteAcc)
        return false;

    const int nHeight = rBitmap.GetSizePixel().Height();
    assert(rBitmap.GetSizePixel().Height() == aNewBitmap.GetSizePixel().Width());
    const int nWidth = rBitmap.GetSizePixel().Width();
    assert(rBitmap.GetSizePixel().Width() == aNewBitmap.GetSizePixel().Height());

    BitmapColor aColor;
    double aValueRed, aValueGreen, aValueBlue;
    double aSum, aWeight;
    int aBaseIndex, aIndex;

    for (int nSourceY = 0; nSourceY < nHeight; ++nSourceY)
    {
        for (int nSourceX = 0; nSourceX < nWidth; ++nSourceX)
        {
            aBaseIndex = nSourceX * aNumberOfContributions;
            aSum = aValueRed = aValueGreen = aValueBlue = 0.0;

            for (int j = 0; j < pCount[nSourceX]; ++j)
            {
                aIndex = aBaseIndex + j;
                aSum += aWeight = pWeights[aIndex];

                aColor = pReadAcc->GetColor(nSourceY, pPixels[aIndex]);

                aValueRed += aWeight * aColor.GetRed();
                aValueGreen += aWeight * aColor.GetGreen();
                aValueBlue += aWeight * aColor.GetBlue();
            }

            BitmapColor aResultColor(static_cast<sal_uInt8>(MinMax(aValueRed / aSum, 0, 255)),
                                     static_cast<sal_uInt8>(MinMax(aValueGreen / aSum, 0, 255)),
                                     static_cast<sal_uInt8>(MinMax(aValueBlue / aSum, 0, 255)));

            int nDestX = nSourceY;
            int nDestY = nSourceX;

            pWriteAcc->SetPixel(nDestY, nDestX, aResultColor);
        }
    }
    return true;
}

double* BitmapGaussianSeparableBlurFilter::makeBlurKernel(const double radius, int& rows)
{
    int intRadius = static_cast<int>(radius + 1.0);
    rows = intRadius * 2 + 1;
    double* matrix = new double[rows];

    double sigma = radius / 3;
    double radius2 = radius * radius;
    int index = 0;
    for (int row = -intRadius; row <= intRadius; row++)
    {
        double distance = row * row;
        if (distance > radius2)
        {
            matrix[index] = 0.0;
        }
        else
        {
            matrix[index] = exp(-distance / (2.0 * sigma * sigma)) / sqrt(2.0 * M_PI * sigma);
        }
        index++;
    }
    return matrix;
}

void BitmapGaussianSeparableBlurFilter::blurContributions(const int aSize,
                                                          const int aNumberOfContributions,
                                                          const double* pBlurVector,
                                                          double*& pWeights, int*& pPixels,
                                                          int*& pCount)
{
    pWeights = new double[aSize * aNumberOfContributions];
    pPixels = new int[aSize * aNumberOfContributions];
    pCount = new int[aSize];

    int aLeft, aRight, aCurrentCount, aPixelIndex;
    double aWeight;

    for (int i = 0; i < aSize; i++)
    {
        aLeft = i - aNumberOfContributions / 2;
        aRight = i + aNumberOfContributions / 2;
        aCurrentCount = 0;
        for (int j = aLeft; j <= aRight; j++)
        {
            aWeight = pBlurVector[aCurrentCount];

            // Mirror edges
            if (j < 0)
            {
                aPixelIndex = -j;
            }
            else if (j >= aSize)
            {
                aPixelIndex = (aSize - j) + aSize - 1;
            }
            else
            {
                aPixelIndex = j;
            }

            // Edge case for small bitmaps
            if (aPixelIndex < 0 || aPixelIndex >= aSize)
            {
                aWeight = 0.0;
            }

            pWeights[i * aNumberOfContributions + aCurrentCount] = aWeight;
            pPixels[i * aNumberOfContributions + aCurrentCount] = aPixelIndex;

            aCurrentCount++;
        }
        pCount[i] = aCurrentCount;
    }
}

/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/source/gdi/bitmap3.cxx b/vcl/source/gdi/bitmap3.cxx
index b553130..3bf068c 100644
--- a/vcl/source/gdi/bitmap3.cxx
+++ b/vcl/source/gdi/bitmap3.cxx
@@ -1854,56 +1854,4 @@ bool Bitmap::Adjust( short nLuminancePercent, short nContrastPercent,
    return bRet;
}

bool Bitmap::ImplConvolutionPass(Bitmap& aNewBitmap, BitmapReadAccess const * pReadAcc, int aNumberOfContributions, const double* pWeights, int const * pPixels, const int* pCount)
{
    if (!pReadAcc)
        return false;

    BitmapScopedWriteAccess pWriteAcc(aNewBitmap);
    if (!pWriteAcc)
        return false;

    const int nHeight = GetSizePixel().Height();
    assert(GetSizePixel().Height() == aNewBitmap.GetSizePixel().Width());
    const int nWidth = GetSizePixel().Width();
    assert(GetSizePixel().Width() == aNewBitmap.GetSizePixel().Height());

    BitmapColor aColor;
    double aValueRed, aValueGreen, aValueBlue;
    double aSum, aWeight;
    int aBaseIndex, aIndex;

    for (int nSourceY = 0; nSourceY < nHeight; ++nSourceY)
    {
        for (int nSourceX = 0; nSourceX < nWidth; ++nSourceX)
        {
            aBaseIndex = nSourceX * aNumberOfContributions;
            aSum = aValueRed = aValueGreen = aValueBlue = 0.0;

            for (int j = 0; j < pCount[nSourceX]; ++j)
            {
                aIndex = aBaseIndex + j;
                aSum += aWeight = pWeights[ aIndex ];

                aColor = pReadAcc->GetColor(nSourceY, pPixels[aIndex]);

                aValueRed += aWeight * aColor.GetRed();
                aValueGreen += aWeight * aColor.GetGreen();
                aValueBlue += aWeight * aColor.GetBlue();
            }

            BitmapColor aResultColor(
                static_cast<sal_uInt8>(MinMax( aValueRed / aSum, 0, 255 )),
                static_cast<sal_uInt8>(MinMax( aValueGreen / aSum, 0, 255 )),
                static_cast<sal_uInt8>(MinMax( aValueBlue / aSum, 0, 255 )) );

            int nDestX = nSourceY;
            int nDestY = nSourceX;

            pWriteAcc->SetPixel(nDestY, nDestX, aResultColor);
        }
    }
    return true;
}

/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/source/gdi/bitmap4.cxx b/vcl/source/gdi/bitmap4.cxx
index 71fd484..e48a2ed 100644
--- a/vcl/source/gdi/bitmap4.cxx
+++ b/vcl/source/gdi/bitmap4.cxx
@@ -17,13 +17,16 @@
 *   the License at http://www.apache.org/licenses/LICENSE-2.0 .
 */

#include <memory>
#include <stdlib.h>
#include <osl/diagnose.h>
#include <vcl/bitmapaccess.hxx>
#include <vcl/bitmap.hxx>
#include <vcl/BitmapGaussianSeparableBlurFilter.hxx>

#include <bitmapwriteaccess.hxx>

#include <memory>
#include <stdlib.h>

#define S2(a,b)             { long t; if( ( t = b - a ) < 0 ) { a += t; b -= t; } }
#define MN3(a,b,c)          S2(a,b); S2(a,c);
#define MX3(a,b,c)          S2(b,c); S2(a,c);
@@ -51,7 +54,9 @@ bool Bitmap::Filter( BmpFilter eFilter, const BmpFilterParam* pFilterParam )
            // Blur for positive values of mnRadius
            if (pFilterParam->mnRadius > 0.0)
            {
                bRet = ImplSeparableBlurFilter(pFilterParam->mnRadius);
                BitmapEx aBmpEx(*this);
                bRet = BitmapFilter::Filter(aBmpEx, BitmapGaussianSeparableBlurFilter(pFilterParam->mnRadius));
                *this = aBmpEx.GetBitmap();
            }
            // Unsharpen Mask for negative values of mnRadius
            else if (pFilterParam->mnRadius < 0.0)
@@ -1062,144 +1067,6 @@ bool Bitmap::ImplPopArt()
    return bRet;
}

double* MakeBlurKernel(const double radius, int& rows) {
    int intRadius = static_cast<int>(radius + 1.0);
    rows = intRadius * 2 + 1;
    double* matrix = new double[rows];

    double sigma = radius / 3;
    double radius2 = radius * radius;
    int index = 0;
    for (int row = -intRadius; row <= intRadius; row++)
    {
        double distance = row*row;
        if (distance > radius2) {
            matrix[index] = 0.0;
        }else {
            matrix[index] = exp( -distance / (2.0 * sigma * sigma) ) / sqrt( 2.0 * M_PI * sigma );
        }
        index++;
    }
    return matrix;
}

void Bitmap::ImplBlurContributions( const int aSize, const int aNumberOfContributions,
                                    const double* pBlurVector, double*& pWeights, int*& pPixels, int*& pCount )
{
    pWeights = new double[ aSize*aNumberOfContributions ];
    pPixels = new int[ aSize*aNumberOfContributions ];
    pCount = new int[ aSize ];

    int aLeft, aRight, aCurrentCount, aPixelIndex;
    double aWeight;

    for ( int i = 0; i < aSize; i++ )
    {
        aLeft  = i - aNumberOfContributions / 2;
        aRight = i + aNumberOfContributions / 2;
        aCurrentCount = 0;
        for ( int j = aLeft; j <= aRight; j++ )
        {
            aWeight = pBlurVector[aCurrentCount];

            // Mirror edges
            if (j < 0)
            {
                aPixelIndex = -j;
            }
            else if ( j >= aSize )
            {
                aPixelIndex = (aSize - j) + aSize - 1;
            }
            else
            {
                aPixelIndex = j;
            }

            // Edge case for small bitmaps
            if ( aPixelIndex < 0 || aPixelIndex >= aSize )
            {
                aWeight = 0.0;
            }

            pWeights[ i*aNumberOfContributions + aCurrentCount ] = aWeight;
            pPixels[ i*aNumberOfContributions + aCurrentCount ] = aPixelIndex;

            aCurrentCount++;
        }
        pCount[ i ] = aCurrentCount;
    }
}

// Separable Gaussian Blur

// Separable Gaussian Blur filter and accepts a blur radius
// as a parameter so the user can change the strength of the blur.
// Radius of 1.0 is 3 * standard deviation of gauss function.

// Separable Blur implementation uses 2x separable 1D convolution
// to process the image.
bool Bitmap::ImplSeparableBlurFilter(const double radius)
{
    const long  nWidth = GetSizePixel().Width();
    const long  nHeight = GetSizePixel().Height();

    // Prepare Blur Vector
    int aNumberOfContributions;
    double* pBlurVector = MakeBlurKernel(radius, aNumberOfContributions);

    double* pWeights;
    int* pPixels;
    int* pCount;

    // Do horizontal filtering
    ImplBlurContributions( nWidth, aNumberOfContributions, pBlurVector, pWeights, pPixels, pCount);

    ScopedReadAccess pReadAcc(*this);

    // switch coordinates as convolution pass transposes result
    Bitmap aNewBitmap( Size( nHeight, nWidth ), 24 );

    bool bResult = ImplConvolutionPass( aNewBitmap, pReadAcc.get(), aNumberOfContributions, pWeights, pPixels, pCount );

    // Cleanup
    pReadAcc.reset();
    delete[] pWeights;
    delete[] pPixels;
    delete[] pCount;

    if ( !bResult )
    {
        delete[] pBlurVector;
        return bResult;
    }

    // Swap current bitmap with new bitmap
    ReassignWithSize(aNewBitmap);

    // Do vertical filtering
    ImplBlurContributions(nHeight, aNumberOfContributions, pBlurVector, pWeights, pPixels, pCount );

    pReadAcc = ScopedReadAccess(*this);
    aNewBitmap = Bitmap( Size( nWidth, nHeight ), 24 );
    bResult = ImplConvolutionPass( aNewBitmap, pReadAcc.get(), aNumberOfContributions, pWeights, pPixels, pCount );

    // Cleanup
    pReadAcc.reset();
    delete[] pWeights;
    delete[] pCount;
    delete[] pPixels;
    delete[] pBlurVector;

    if ( !bResult )
        return bResult;

    // Swap current bitmap with new bitmap
    ReassignWithSize(aNewBitmap);

    return true;
}

// Separable Unsharpen Mask filter is actually a subtracted blurred
// image from the original image.
bool Bitmap::ImplSeparableUnsharpenFilter(const double radius) {
@@ -1207,7 +1074,10 @@ bool Bitmap::ImplSeparableUnsharpenFilter(const double radius) {
    const long  nHeight = GetSizePixel().Height();

    Bitmap aBlur( *this );
    aBlur.ImplSeparableBlurFilter(-radius);
    BitmapEx aBlurEx(aBlur);

    BitmapFilter::Filter(aBlurEx, BitmapGaussianSeparableBlurFilter(-radius));
    aBlur = aBlurEx.GetBitmap();

    // Amount of unsharpening effect on image - currently set to a fixed value
    double aAmount = 2.0;