[Libreoffice-commits] core.git: include/vcl vcl/Library_vcl.mk vcl/qa vcl/source

Mike Kaganski (via logerrit) logerrit at kemper.freedesktop.org
Thu May 7 19:56:58 UTC 2020


 include/vcl/BitmapBasicMorphologyFilter.hxx                |   60 ++
 vcl/Library_vcl.mk                                         |    1 
 vcl/qa/cppunit/BitmapFilterTest.cxx                        |   78 ++
 vcl/qa/cppunit/data/testBasicMorphology.png                |binary
 vcl/qa/cppunit/data/testBasicMorphologyDilated1.png        |binary
 vcl/qa/cppunit/data/testBasicMorphologyDilated1Eroded1.png |binary
 vcl/qa/cppunit/data/testBasicMorphologyDilated2.png        |binary
 vcl/qa/cppunit/data/testBasicMorphologyDilated2Eroded1.png |binary
 vcl/source/bitmap/BitmapBasicMorphologyFilter.cxx          |  358 +++++++++++++
 9 files changed, 486 insertions(+), 11 deletions(-)

New commits:
commit 84808eed2405ed6ee586e87bb664a816f7b91b70
Author:     Mike Kaganski <mike.kaganski at collabora.com>
AuthorDate: Wed May 6 11:08:22 2020 +0300
Commit:     Mike Kaganski <mike.kaganski at collabora.com>
CommitDate: Thu May 7 21:56:23 2020 +0200

    Add basic morphology (erode/dilate) bitmap filter
    
    Needed for glow effect (tdf#101181)
    
    Change-Id: Id41daa1dc17e3749a30ce75fa3127878b9e0cfd1
    Reviewed-on: https://gerrit.libreoffice.org/c/core/+/93552
    Tested-by: Jenkins
    Reviewed-by: Tomaž Vajngerl <quikee at gmail.com>

diff --git a/include/vcl/BitmapBasicMorphologyFilter.hxx b/include/vcl/BitmapBasicMorphologyFilter.hxx
new file mode 100644
index 000000000000..ade82adaa957
--- /dev/null
+++ b/include/vcl/BitmapBasicMorphologyFilter.hxx
@@ -0,0 +1,60 @@
+/* -*- 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_BITMAPBASICMORPHOLOGYFILTER_HXX
+#define INCLUDED_VCL_BITMAPBASICMORPHOLOGYFILTER_HXX
+
+#include <vcl/bitmap.hxx>
+#include <vcl/bitmapex.hxx>
+#include <vcl/BitmapFilter.hxx>
+
+enum class BasicMorphologyOp
+{
+    erode,
+    dilate
+};
+
+/* Black is foreground, white is background */
+class VCL_DLLPUBLIC BitmapBasicMorphologyFilter : public BitmapFilter
+{
+public:
+    BitmapBasicMorphologyFilter(BasicMorphologyOp op, sal_Int32 nRadius);
+    virtual ~BitmapBasicMorphologyFilter();
+
+    virtual BitmapEx execute(BitmapEx const& rBitmap) const override;
+
+private:
+    Bitmap filter(Bitmap const& rBitmap) const;
+
+    BasicMorphologyOp m_eOp;
+    sal_Int32 m_nRadius;
+};
+
+class BitmapErodeFilter : public BitmapBasicMorphologyFilter
+{
+public:
+    BitmapErodeFilter(sal_Int32 nRadius)
+        : BitmapBasicMorphologyFilter(BasicMorphologyOp::erode, nRadius)
+    {
+    }
+};
+
+class BitmapDilateFilter : public BitmapBasicMorphologyFilter
+{
+public:
+    BitmapDilateFilter(sal_Int32 nRadius)
+        : BitmapBasicMorphologyFilter(BasicMorphologyOp::dilate, nRadius)
+    {
+    }
+};
+
+#endif // INCLUDED_VCL_BITMAPBASICMORPHOLOGYFILTER_HXX
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/Library_vcl.mk b/vcl/Library_vcl.mk
index 9780ca3575a1..ffe065b24fff 100644
--- a/vcl/Library_vcl.mk
+++ b/vcl/Library_vcl.mk
@@ -331,6 +331,7 @@ $(eval $(call gb_Library_add_exception_objects,vcl,\
     vcl/source/bitmap/bitmap \
     vcl/source/bitmap/bitmapfilter \
     vcl/source/bitmap/BitmapAlphaClampFilter \
+    vcl/source/bitmap/BitmapBasicMorphologyFilter \
     vcl/source/bitmap/BitmapMonochromeFilter \
     vcl/source/bitmap/BitmapSmoothenFilter \
     vcl/source/bitmap/BitmapLightenFilter \
diff --git a/vcl/qa/cppunit/BitmapFilterTest.cxx b/vcl/qa/cppunit/BitmapFilterTest.cxx
index a28057a4bf57..fec21fa118f0 100644
--- a/vcl/qa/cppunit/BitmapFilterTest.cxx
+++ b/vcl/qa/cppunit/BitmapFilterTest.cxx
@@ -7,10 +7,7 @@
  * 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 <test/bootstrapfixture.hxx>
 
 #include <vcl/bitmap.hxx>
 #include <vcl/bitmapaccess.hxx>
@@ -19,6 +16,7 @@
 #include <tools/stream.hxx>
 #include <vcl/graphicfilter.hxx>
 
+#include <vcl/BitmapBasicMorphologyFilter.hxx>
 #include <vcl/BitmapFilterStackBlur.hxx>
 #include <BitmapSymmetryCheck.hxx>
 
@@ -29,15 +27,48 @@ namespace
 constexpr bool constWriteResultBitmap(false);
 constexpr bool constEnablePerformanceTest(false);
 
-class BitmapFilterTest : public CppUnit::TestFixture
+class BitmapFilterTest : public test::BootstrapFixture
 {
+public:
+    BitmapFilterTest()
+        : test::BootstrapFixture(true, false)
+    {
+    }
+
     void testBlurCorrectness();
+    void testBasicMorphology();
     void testPerformance();
 
     CPPUNIT_TEST_SUITE(BitmapFilterTest);
     CPPUNIT_TEST(testBlurCorrectness);
+    CPPUNIT_TEST(testBasicMorphology);
     CPPUNIT_TEST(testPerformance);
     CPPUNIT_TEST_SUITE_END();
+
+private:
+    OUString getFullUrl(const OUString& sFileName)
+    {
+        return m_directories.getURLFromSrc("vcl/qa/cppunit/data/") + sFileName;
+    }
+
+    BitmapEx loadBitmap(const OUString& sFileName)
+    {
+        Graphic aGraphic;
+        const OUString aURL(getFullUrl(sFileName));
+        SvFileStream aFileStream(aURL, StreamMode::READ);
+        GraphicFilter& rFilter = GraphicFilter::GetGraphicFilter();
+        ErrCode aResult = rFilter.ImportGraphic(aGraphic, aURL, aFileStream);
+        CPPUNIT_ASSERT_EQUAL(ERRCODE_NONE, aResult);
+        return aGraphic.GetBitmapEx();
+    }
+
+    template <class BitmapT> // handle both Bitmap and BitmapEx
+    void savePNG(const OUString& sWhere, const BitmapT& rBmp)
+    {
+        SvFileStream aStream(sWhere, StreamMode::WRITE | StreamMode::TRUNC);
+        GraphicFilter& rFilter = GraphicFilter::GetGraphicFilter();
+        rFilter.compressAsPNG(rBmp, aStream);
+    }
 };
 
 void BitmapFilterTest::testBlurCorrectness()
@@ -73,9 +104,7 @@ void BitmapFilterTest::testBlurCorrectness()
 
     if (constWriteResultBitmap)
     {
-        SvFileStream aStream("~/blurBefore.png", StreamMode::WRITE | StreamMode::TRUNC);
-        GraphicFilter& rFilter = GraphicFilter::GetGraphicFilter();
-        rFilter.compressAsPNG(aBitmap24Bit, aStream);
+        savePNG("~/blurBefore.png", aBitmap24Bit);
     }
 
     // Perform blur
@@ -86,9 +115,7 @@ void BitmapFilterTest::testBlurCorrectness()
 
     if (constWriteResultBitmap)
     {
-        SvFileStream aStream("~/blurAfter.png", StreamMode::WRITE | StreamMode::TRUNC);
-        GraphicFilter& rFilter = GraphicFilter::GetGraphicFilter();
-        rFilter.compressAsPNG(aBitmap24Bit, aStream);
+        savePNG("~/blurAfter.png", aBitmap24Bit);
     }
 
     // Check blurred bitmap parameters
@@ -106,6 +133,35 @@ void BitmapFilterTest::testBlurCorrectness()
     }
 }
 
+void BitmapFilterTest::testBasicMorphology()
+{
+    const BitmapEx aOrigBitmap = loadBitmap("testBasicMorphology.png");
+    const BitmapEx aRefBitmapDilated1 = loadBitmap("testBasicMorphologyDilated1.png");
+    const BitmapEx aRefBitmapDilated1Eroded1 = loadBitmap("testBasicMorphologyDilated1Eroded1.png");
+    const BitmapEx aRefBitmapDilated2 = loadBitmap("testBasicMorphologyDilated2.png");
+    const BitmapEx aRefBitmapDilated2Eroded1 = loadBitmap("testBasicMorphologyDilated2Eroded1.png");
+
+    BitmapEx aTransformBitmap = aOrigBitmap;
+    BitmapFilter::Filter(aTransformBitmap, BitmapDilateFilter(1));
+    if (constWriteResultBitmap)
+        savePNG("~/Dilated1.png", aTransformBitmap);
+    CPPUNIT_ASSERT_EQUAL(aRefBitmapDilated1.GetChecksum(), aTransformBitmap.GetChecksum());
+    BitmapFilter::Filter(aTransformBitmap, BitmapErodeFilter(1));
+    if (constWriteResultBitmap)
+        savePNG("~/Dilated1Eroded1.png", aTransformBitmap);
+    CPPUNIT_ASSERT_EQUAL(aRefBitmapDilated1Eroded1.GetChecksum(), aTransformBitmap.GetChecksum());
+
+    aTransformBitmap = aOrigBitmap;
+    BitmapFilter::Filter(aTransformBitmap, BitmapDilateFilter(2));
+    if (constWriteResultBitmap)
+        savePNG("~/Dilated2.png", aTransformBitmap);
+    CPPUNIT_ASSERT_EQUAL(aRefBitmapDilated2.GetChecksum(), aTransformBitmap.GetChecksum());
+    BitmapFilter::Filter(aTransformBitmap, BitmapErodeFilter(1));
+    if (constWriteResultBitmap)
+        savePNG("~/Dilated2Eroded1.png", aTransformBitmap);
+    CPPUNIT_ASSERT_EQUAL(aRefBitmapDilated2Eroded1.GetChecksum(), aTransformBitmap.GetChecksum());
+}
+
 void BitmapFilterTest::testPerformance()
 {
     if (!constEnablePerformanceTest)
diff --git a/vcl/qa/cppunit/data/testBasicMorphology.png b/vcl/qa/cppunit/data/testBasicMorphology.png
new file mode 100644
index 000000000000..5db565779f73
Binary files /dev/null and b/vcl/qa/cppunit/data/testBasicMorphology.png differ
diff --git a/vcl/qa/cppunit/data/testBasicMorphologyDilated1.png b/vcl/qa/cppunit/data/testBasicMorphologyDilated1.png
new file mode 100644
index 000000000000..ba335bab3cb5
Binary files /dev/null and b/vcl/qa/cppunit/data/testBasicMorphologyDilated1.png differ
diff --git a/vcl/qa/cppunit/data/testBasicMorphologyDilated1Eroded1.png b/vcl/qa/cppunit/data/testBasicMorphologyDilated1Eroded1.png
new file mode 100644
index 000000000000..3b10a949af67
Binary files /dev/null and b/vcl/qa/cppunit/data/testBasicMorphologyDilated1Eroded1.png differ
diff --git a/vcl/qa/cppunit/data/testBasicMorphologyDilated2.png b/vcl/qa/cppunit/data/testBasicMorphologyDilated2.png
new file mode 100644
index 000000000000..30d90757ea7e
Binary files /dev/null and b/vcl/qa/cppunit/data/testBasicMorphologyDilated2.png differ
diff --git a/vcl/qa/cppunit/data/testBasicMorphologyDilated2Eroded1.png b/vcl/qa/cppunit/data/testBasicMorphologyDilated2Eroded1.png
new file mode 100644
index 000000000000..a506577da49e
Binary files /dev/null and b/vcl/qa/cppunit/data/testBasicMorphologyDilated2Eroded1.png differ
diff --git a/vcl/source/bitmap/BitmapBasicMorphologyFilter.cxx b/vcl/source/bitmap/BitmapBasicMorphologyFilter.cxx
new file mode 100644
index 000000000000..581d65e67770
--- /dev/null
+++ b/vcl/source/bitmap/BitmapBasicMorphologyFilter.cxx
@@ -0,0 +1,358 @@
+/* -*- 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 <sal/config.h>
+
+#include <comphelper/threadpool.hxx>
+#include <sal/log.hxx>
+#include <vcl/bitmapaccess.hxx>
+#include <vcl/BitmapBasicMorphologyFilter.hxx>
+
+#include <bitmapwriteaccess.hxx>
+
+#include <algorithm>
+
+namespace
+{
+struct FilterSharedData
+{
+    BitmapReadAccess* mpReadAccess;
+    BitmapWriteAccess* mpWriteAccess;
+    long mnRadius;
+
+    FilterSharedData(BitmapReadAccess* pReadAccess, BitmapWriteAccess* pWriteAccess, long nRadius)
+        : mpReadAccess(pReadAccess)
+        , mpWriteAccess(pWriteAccess)
+        , mnRadius(nRadius)
+    {
+    }
+};
+
+// Black is foreground, white is background
+
+struct ErodeOp
+{
+    static sal_uInt8 apply(sal_uInt8 v1, sal_uInt8 v2) { return std::max(v1, v2); }
+    static constexpr sal_uInt8 initVal = 0;
+    static constexpr Color initColor = COL_BLACK;
+};
+
+struct DilateOp
+{
+    static sal_uInt8 apply(sal_uInt8 v1, sal_uInt8 v2) { return std::min(v1, v2); }
+    static constexpr sal_uInt8 initVal{ SAL_MAX_UINT8 };
+    static constexpr Color initColor = COL_TRANSPARENT;
+};
+
+template <typename MorphologyOp> struct OpHelper
+{
+    template <int n> static void apply(sal_uInt8 (&rResult)[n], Scanline pSource)
+    {
+        std::transform(pSource, pSource + n, rResult, rResult, MorphologyOp::apply);
+    }
+
+    static void apply(Color& rResult, const Color& rSource)
+    {
+        rResult = Color(MorphologyOp::apply(rSource.GetTransparency(), rResult.GetTransparency()),
+                        MorphologyOp::apply(rSource.GetRed(), rResult.GetRed()),
+                        MorphologyOp::apply(rSource.GetGreen(), rResult.GetGreen()),
+                        MorphologyOp::apply(rSource.GetBlue(), rResult.GetBlue()));
+    }
+
+    template <int n> static void init(sal_uInt8 (&rResult)[n])
+    {
+        std::fill_n(rResult, n, MorphologyOp::initVal);
+    }
+};
+
+// 8 bit per channel case
+
+template <typename MorphologyOp, int nComponentWidth> struct pass
+{
+    static constexpr int nWidthBytes = nComponentWidth / 8;
+    static_assert(nWidthBytes * 8 == nComponentWidth);
+    static void Horizontal(FilterSharedData const& rShared, const long nStart, const long nEnd)
+    {
+        BitmapReadAccess* pReadAccess = rShared.mpReadAccess;
+        BitmapWriteAccess* pWriteAccess = rShared.mpWriteAccess;
+
+        const long nWidth = pReadAccess->Width();
+        const long nLastIndex = nWidth - 1;
+
+        const long nRadius = rShared.mnRadius;
+
+        for (long y = nStart; y <= nEnd; y++)
+        {
+            const Scanline pScanline = pReadAccess->GetScanline(y);
+            for (long x = 0; x < nWidth; x++)
+            {
+                // This processes [nRadius * 2 + 1] pixels of source per resulting pixel
+                // TODO: try to optimize this to not process same pixels repeatedly
+                sal_uInt8 aResult[nWidthBytes];
+                OpHelper<MorphologyOp>::init(aResult);
+                const long iMax = std::min(x + nRadius, nLastIndex);
+                for (long i = std::max(x - nRadius, 0L); i <= iMax; ++i)
+                    OpHelper<MorphologyOp>::apply(aResult, pScanline + nWidthBytes * i);
+
+                Scanline pDestinationPointer = pWriteAccess->GetScanline(y) + nWidthBytes * x;
+                for (const auto& val : aResult)
+                    *pDestinationPointer++ = val;
+            }
+        }
+    }
+
+    static void Vertical(FilterSharedData const& rShared, const long nStart, const long nEnd)
+    {
+        BitmapReadAccess* pReadAccess = rShared.mpReadAccess;
+        BitmapWriteAccess* pWriteAccess = rShared.mpWriteAccess;
+
+        const long nHeight = pReadAccess->Height();
+        const long nLastIndex = nHeight - 1;
+
+        const long nRadius = rShared.mnRadius;
+
+        for (long x = nStart; x <= nEnd; x++)
+        {
+            for (long y = 0; y < nHeight; y++)
+            {
+                // This processes [nRadius * 2 + 1] pixels of source per resulting pixel
+                // TODO: try to optimize this to not process same pixels repeatedly
+                sal_uInt8 aResult[nWidthBytes];
+                OpHelper<MorphologyOp>::init(aResult);
+                const long iMax = std::min(y + nRadius, nLastIndex);
+                for (long i = std::max(y - nRadius, 0L); i <= iMax; ++i)
+                    OpHelper<MorphologyOp>::apply(aResult,
+                                                  pReadAccess->GetScanline(i) + nWidthBytes * x);
+
+                Scanline pDestinationPointer = pWriteAccess->GetScanline(y) + nWidthBytes * x;
+                for (auto& val : aResult)
+                    *pDestinationPointer++ = val;
+            }
+        }
+    }
+};
+
+// Partial specializations for nComponentWidth == 0, using acess' GetColor/SetPixel
+
+template <typename MorphologyOp> struct pass<MorphologyOp, 0>
+{
+    static void Horizontal(FilterSharedData const& rShared, const long nStart, const long nEnd)
+    {
+        BitmapReadAccess* pReadAccess = rShared.mpReadAccess;
+        BitmapWriteAccess* pWriteAccess = rShared.mpWriteAccess;
+
+        const long nWidth = pReadAccess->Width();
+        const long nLastIndex = nWidth - 1;
+
+        const long nRadius = rShared.mnRadius;
+
+        for (long y = nStart; y <= nEnd; y++)
+        {
+            for (long x = 0; x < nWidth; x++)
+            {
+                // This processes [nRadius * 2 + 1] pixels of source per resulting pixel
+                // TODO: try to optimize this to not process same pixels repeatedly
+                Color aResult = MorphologyOp::initColor;
+                const long iMax = std::min(x + nRadius, nLastIndex);
+                for (long i = std::max(x - nRadius, 0L); i <= iMax; ++i)
+                    OpHelper<MorphologyOp>::apply(aResult, pReadAccess->GetColor(y, i));
+
+                pWriteAccess->SetPixel(y, x, aResult);
+            }
+        }
+    }
+
+    static void Vertical(FilterSharedData const& rShared, const long nStart, const long nEnd)
+    {
+        BitmapReadAccess* pReadAccess = rShared.mpReadAccess;
+        BitmapWriteAccess* pWriteAccess = rShared.mpWriteAccess;
+
+        const long nHeight = pReadAccess->Height();
+        const long nLastIndex = nHeight - 1;
+
+        const long nRadius = rShared.mnRadius;
+
+        for (long x = nStart; x <= nEnd; x++)
+        {
+            for (long y = 0; y < nHeight; y++)
+            {
+                // This processes [nRadius * 2 + 1] pixels of source per resulting pixel
+                // TODO: try to optimize this to not process same pixels repeatedly
+                Color aResult = MorphologyOp::initColor;
+                const long iMax = std::min(y + nRadius, nLastIndex);
+                for (long i = std::max(y - nRadius, 0L); i <= iMax; ++i)
+                    OpHelper<MorphologyOp>::apply(aResult, pReadAccess->GetColor(i, x));
+
+                pWriteAccess->SetPixel(y, x, aResult);
+            }
+        }
+    }
+};
+
+typedef void (*passFn)(FilterSharedData const& rShared, long nStart, long nEnd);
+
+class FilterTask : public comphelper::ThreadTask
+{
+    passFn mpFunction;
+    FilterSharedData& mrShared;
+    long mnStart;
+    long mnEnd;
+
+public:
+    explicit FilterTask(const std::shared_ptr<comphelper::ThreadTaskTag>& pTag, passFn pFunction,
+                        FilterSharedData& rShared, long nStart, long nEnd)
+        : comphelper::ThreadTask(pTag)
+        , mpFunction(pFunction)
+        , mrShared(rShared)
+        , mnStart(nStart)
+        , mnEnd(nEnd)
+    {
+    }
+
+    virtual void doWork() override { mpFunction(mrShared, mnStart, mnEnd); }
+};
+
+constexpr long nThreadStrip = 16;
+
+template <typename MorphologyOp, int nComponentWidth>
+void runFilter(Bitmap& rBitmap, const long nRadius, const bool bParallel)
+{
+    using myPass = pass<MorphologyOp, nComponentWidth>;
+    if (bParallel)
+    {
+        try
+        {
+            comphelper::ThreadPool& rShared = comphelper::ThreadPool::getSharedOptimalPool();
+            auto pTag = comphelper::ThreadPool::createThreadTaskTag();
+
+            {
+                Bitmap::ScopedReadAccess pReadAccess(rBitmap);
+                BitmapScopedWriteAccess pWriteAccess(rBitmap);
+                FilterSharedData aSharedData(pReadAccess.get(), pWriteAccess.get(), nRadius);
+
+                const long nLastIndex = pReadAccess->Height() - 1;
+                long nStripStart = 0;
+                for (; nStripStart < nLastIndex - nThreadStrip; nStripStart += nThreadStrip)
+                {
+                    long nStripEnd = nStripStart + nThreadStrip - 1;
+                    auto pTask(std::make_unique<FilterTask>(pTag, myPass::Horizontal, aSharedData,
+                                                            nStripStart, nStripEnd));
+                    rShared.pushTask(std::move(pTask));
+                }
+                // Do the last (or the only) strip in main thread without threading overhead
+                myPass::Horizontal(aSharedData, nStripStart, nLastIndex);
+                rShared.waitUntilDone(pTag);
+            }
+            {
+                Bitmap::ScopedReadAccess pReadAccess(rBitmap);
+                BitmapScopedWriteAccess pWriteAccess(rBitmap);
+                FilterSharedData aSharedData(pReadAccess.get(), pWriteAccess.get(), nRadius);
+
+                const long nLastIndex = pReadAccess->Width() - 1;
+                long nStripStart = 0;
+                for (; nStripStart < nLastIndex - nThreadStrip; nStripStart += nThreadStrip)
+                {
+                    long nStripEnd = nStripStart + nThreadStrip - 1;
+                    auto pTask(std::make_unique<FilterTask>(pTag, myPass::Vertical, aSharedData,
+                                                            nStripStart, nStripEnd));
+                    rShared.pushTask(std::move(pTask));
+                }
+                // Do the last (or the only) strip in main thread without threading overhead
+                myPass::Vertical(aSharedData, nStripStart, nLastIndex);
+                rShared.waitUntilDone(pTag);
+            }
+        }
+        catch (...)
+        {
+            SAL_WARN("vcl.gdi", "threaded bitmap blurring failed");
+        }
+    }
+    else
+    {
+        {
+            Bitmap::ScopedReadAccess pReadAccess(rBitmap);
+            BitmapScopedWriteAccess pWriteAccess(rBitmap);
+            FilterSharedData aSharedData(pReadAccess.get(), pWriteAccess.get(), nRadius);
+            long nFirstIndex = 0;
+            long nLastIndex = pReadAccess->Height() - 1;
+            myPass::Horizontal(aSharedData, nFirstIndex, nLastIndex);
+        }
+        {
+            Bitmap::ScopedReadAccess pReadAccess(rBitmap);
+            BitmapScopedWriteAccess pWriteAccess(rBitmap);
+            FilterSharedData aSharedData(pReadAccess.get(), pWriteAccess.get(), nRadius);
+            long nFirstIndex = 0;
+            long nLastIndex = pReadAccess->Width() - 1;
+            myPass::Vertical(aSharedData, nFirstIndex, nLastIndex);
+        }
+    }
+}
+
+template <int nComponentWidth>
+void runFilter(Bitmap& rBitmap, BasicMorphologyOp op, sal_Int32 nRadius)
+{
+    const bool bParallel = true;
+
+    if (op == BasicMorphologyOp::erode)
+        runFilter<ErodeOp, nComponentWidth>(rBitmap, nRadius, bParallel);
+    else if (op == BasicMorphologyOp::dilate)
+        runFilter<DilateOp, nComponentWidth>(rBitmap, nRadius, bParallel);
+}
+
+} // end anonymous namespace
+
+BitmapBasicMorphologyFilter::BitmapBasicMorphologyFilter(BasicMorphologyOp op, sal_Int32 nRadius)
+    : m_eOp(op)
+    , m_nRadius(nRadius)
+{
+}
+
+BitmapBasicMorphologyFilter::~BitmapBasicMorphologyFilter() = default;
+
+BitmapEx BitmapBasicMorphologyFilter::execute(BitmapEx const& rBitmapEx) const
+{
+    Bitmap aBitmap = rBitmapEx.GetBitmap();
+    Bitmap result = filter(aBitmap);
+    return BitmapEx(result, rBitmapEx.GetMask());
+}
+
+Bitmap BitmapBasicMorphologyFilter::filter(Bitmap const& rBitmap) const
+{
+    Bitmap bitmapCopy(rBitmap);
+    ScanlineFormat nScanlineFormat;
+    {
+        Bitmap::ScopedReadAccess pReadAccess(bitmapCopy);
+        nScanlineFormat = pReadAccess->GetScanlineFormat();
+    }
+
+    switch (nScanlineFormat)
+    {
+        case ScanlineFormat::N24BitTcRgb:
+        case ScanlineFormat::N24BitTcBgr:
+            runFilter<24>(bitmapCopy, m_eOp, m_nRadius);
+            break;
+        case ScanlineFormat::N32BitTcMask:
+        case ScanlineFormat::N32BitTcBgra:
+            runFilter<32>(bitmapCopy, m_eOp, m_nRadius);
+            break;
+        case ScanlineFormat::N8BitPal:
+            runFilter<8>(bitmapCopy, m_eOp, m_nRadius);
+            break;
+        // TODO: handle 1-bit images
+        default:
+            // Use access' GetColor/SetPixel fallback
+            runFilter<0>(bitmapCopy, m_eOp, m_nRadius);
+            break;
+    }
+
+    return bitmapCopy;
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */


More information about the Libreoffice-commits mailing list