summaryrefslogtreecommitdiff
path: root/vcl
diff options
context:
space:
mode:
authorMike Kaganski <mike.kaganski@collabora.com>2020-05-06 11:08:22 +0300
committerMike Kaganski <mike.kaganski@collabora.com>2020-05-07 21:56:23 +0200
commit84808eed2405ed6ee586e87bb664a816f7b91b70 (patch)
tree38bfad43c2fff3e4185f7e9720f88e84090e1dfe /vcl
parent547b2891d9fe97dee9df14106e91dc4df659d4d5 (diff)
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@gmail.com>
Diffstat (limited to 'vcl')
-rw-r--r--vcl/Library_vcl.mk1
-rw-r--r--vcl/qa/cppunit/BitmapFilterTest.cxx78
-rw-r--r--vcl/qa/cppunit/data/testBasicMorphology.pngbin0 -> 226 bytes
-rw-r--r--vcl/qa/cppunit/data/testBasicMorphologyDilated1.pngbin0 -> 219 bytes
-rw-r--r--vcl/qa/cppunit/data/testBasicMorphologyDilated1Eroded1.pngbin0 -> 224 bytes
-rw-r--r--vcl/qa/cppunit/data/testBasicMorphologyDilated2.pngbin0 -> 174 bytes
-rw-r--r--vcl/qa/cppunit/data/testBasicMorphologyDilated2Eroded1.pngbin0 -> 178 bytes
-rw-r--r--vcl/source/bitmap/BitmapBasicMorphologyFilter.cxx358
8 files changed, 426 insertions, 11 deletions
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
--- /dev/null
+++ b/vcl/qa/cppunit/data/testBasicMorphology.png
Binary files differ
diff --git a/vcl/qa/cppunit/data/testBasicMorphologyDilated1.png b/vcl/qa/cppunit/data/testBasicMorphologyDilated1.png
new file mode 100644
index 000000000000..ba335bab3cb5
--- /dev/null
+++ b/vcl/qa/cppunit/data/testBasicMorphologyDilated1.png
Binary files differ
diff --git a/vcl/qa/cppunit/data/testBasicMorphologyDilated1Eroded1.png b/vcl/qa/cppunit/data/testBasicMorphologyDilated1Eroded1.png
new file mode 100644
index 000000000000..3b10a949af67
--- /dev/null
+++ b/vcl/qa/cppunit/data/testBasicMorphologyDilated1Eroded1.png
Binary files differ
diff --git a/vcl/qa/cppunit/data/testBasicMorphologyDilated2.png b/vcl/qa/cppunit/data/testBasicMorphologyDilated2.png
new file mode 100644
index 000000000000..30d90757ea7e
--- /dev/null
+++ b/vcl/qa/cppunit/data/testBasicMorphologyDilated2.png
Binary files differ
diff --git a/vcl/qa/cppunit/data/testBasicMorphologyDilated2Eroded1.png b/vcl/qa/cppunit/data/testBasicMorphologyDilated2Eroded1.png
new file mode 100644
index 000000000000..a506577da49e
--- /dev/null
+++ b/vcl/qa/cppunit/data/testBasicMorphologyDilated2Eroded1.png
Binary files 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: */