/* -*- 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 #include #include #include #include #include /* TODO: Use round kernel instead of square one. This would make the result more natural, e.g. not making rounded square out of circle. */ namespace { struct FilterSharedData { BitmapReadAccess* mpReadAccess; BitmapWriteAccess* mpWriteAccess; sal_Int32 mnRadius; sal_uInt8 mnOutsideVal; Color maOutsideColor; FilterSharedData(Bitmap::ScopedReadAccess& rReadAccess, BitmapScopedWriteAccess& rWriteAccess, sal_Int32 nRadius, sal_uInt8 nOutsideVal) : mpReadAccess(rReadAccess.get()) , mpWriteAccess(rWriteAccess.get()) , mnRadius(nRadius) , mnOutsideVal(nOutsideVal) , maOutsideColor(ColorTransparency, nOutsideVal, nOutsideVal, nOutsideVal, nOutsideVal) { } }; // 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; }; 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; }; // 8 bit per channel case template struct Value { static constexpr int nWidthBytes = nComponentWidth / 8; static_assert(nWidthBytes * 8 == nComponentWidth); sal_uInt8 aResult[nWidthBytes]; // If we are at the start or at the end of the line, consider outside value Value(FilterSharedData const& rShared, bool bLookOutside) { std::fill_n(aResult, nWidthBytes, bLookOutside ? rShared.mnOutsideVal : MorphologyOp::initVal); } void apply(const BitmapReadAccess* pReadAccess, sal_Int32 x, sal_Int32 y, sal_uInt8* pHint = nullptr) { sal_uInt8* pSource = (pHint ? pHint : pReadAccess->GetScanline(y)) + nWidthBytes * x; std::transform(pSource, pSource + nWidthBytes, aResult, aResult, MorphologyOp::apply); } void copy(const BitmapWriteAccess* pWriteAccess, sal_Int32 x, sal_Int32 y, sal_uInt8* pHint = nullptr) { sal_uInt8* pDest = (pHint ? pHint : pWriteAccess->GetScanline(y)) + nWidthBytes * x; std::copy_n(aResult, nWidthBytes, pDest); } }; // Partial specializations for nComponentWidth == 0, using access' GetColor/SetPixel template struct Value { static constexpr Color initColor{ ColorTransparency, MorphologyOp::initVal, MorphologyOp::initVal, MorphologyOp::initVal, MorphologyOp::initVal }; Color aResult; // If we are at the start or at the end of the line, consider outside value Value(FilterSharedData const& rShared, bool bLookOutside) : aResult(bLookOutside ? rShared.maOutsideColor : initColor) { } void apply(const BitmapReadAccess* pReadAccess, sal_Int32 x, sal_Int32 y, sal_uInt8* /*pHint*/ = nullptr) { const auto& rSource = pReadAccess->GetColor(y, x); aResult = Color(ColorAlpha, MorphologyOp::apply(rSource.GetAlpha(), aResult.GetAlpha()), MorphologyOp::apply(rSource.GetRed(), aResult.GetRed()), MorphologyOp::apply(rSource.GetGreen(), aResult.GetGreen()), MorphologyOp::apply(rSource.GetBlue(), aResult.GetBlue())); } void copy(BitmapWriteAccess* pWriteAccess, sal_Int32 x, sal_Int32 y, sal_uInt8* /*pHint*/ = nullptr) { pWriteAccess->SetPixel(y, x, aResult); } }; bool GetMinMax(sal_Int32 nCenter, sal_Int32 nRadius, sal_Int32 nMaxLimit, sal_Int32& nMin, sal_Int32& nMax) { nMin = nCenter - nRadius; nMax = nCenter + nRadius; bool bLookOutside = false; if (nMin < 0) { bLookOutside = true; nMin = 0; } if (nMax > nMaxLimit) { bLookOutside = true; nMax = nMaxLimit; } return bLookOutside; } template struct pass { static void Horizontal(FilterSharedData const& rShared, const sal_Int32 nStart, const sal_Int32 nEnd) { BitmapReadAccess* pReadAccess = rShared.mpReadAccess; BitmapWriteAccess* pWriteAccess = rShared.mpWriteAccess; const sal_Int32 nLastIndex = pReadAccess->Width() - 1; for (sal_Int32 y = nStart; y <= nEnd; y++) { // Optimization sal_uInt8* const pSourceHint = pReadAccess->GetScanline(y); sal_uInt8* const pDestHint = pWriteAccess->GetScanline(y); for (sal_Int32 x = 0; x <= nLastIndex; x++) { // This processes [nRadius * 2 + 1] pixels of source per resulting pixel // TODO: try to optimize this to not process same pixels repeatedly sal_Int32 iMin, iMax; const bool bLookOutside = GetMinMax(x, rShared.mnRadius, nLastIndex, iMin, iMax); Value aResult(rShared, bLookOutside); for (sal_Int32 i = iMin; i <= iMax; ++i) aResult.apply(pReadAccess, i, y, pSourceHint); aResult.copy(pWriteAccess, x, y, pDestHint); } } } static void Vertical(FilterSharedData const& rShared, const sal_Int32 nStart, const sal_Int32 nEnd) { BitmapReadAccess* pReadAccess = rShared.mpReadAccess; BitmapWriteAccess* pWriteAccess = rShared.mpWriteAccess; const sal_Int32 nLastIndex = pReadAccess->Height() - 1; for (sal_Int32 x = nStart; x <= nEnd; x++) { for (sal_Int32 y = 0; y <= nLastIndex; y++) { // This processes [nRadius * 2 + 1] pixels of source per resulting pixel // TODO: try to optimize this to not process same pixels repeatedly sal_Int32 iMin, iMax; const bool bLookOutside = GetMinMax(y, rShared.mnRadius, nLastIndex, iMin, iMax); Value aResult(rShared, bLookOutside); for (sal_Int32 i = iMin; i <= iMax; ++i) aResult.apply(pReadAccess, x, i); aResult.copy(pWriteAccess, x, y); } } } }; typedef void (*passFn)(FilterSharedData const& rShared, sal_Int32 nStart, sal_Int32 nEnd); class FilterTask : public comphelper::ThreadTask { passFn mpFunction; FilterSharedData& mrShared; sal_Int32 mnStart; sal_Int32 mnEnd; public: explicit FilterTask(const std::shared_ptr& pTag, passFn pFunction, FilterSharedData& rShared, sal_Int32 nStart, sal_Int32 nEnd) : comphelper::ThreadTask(pTag) , mpFunction(pFunction) , mrShared(rShared) , mnStart(nStart) , mnEnd(nEnd) { } virtual void doWork() override { mpFunction(mrShared, mnStart, mnEnd); } }; constexpr sal_Int32 nThreadStrip = 16; template void runFilter(Bitmap& rBitmap, const sal_Int32 nRadius, const bool bParallel, bool bUseValueOutside, sal_uInt8 nValueOutside) { using myPass = pass; const sal_uInt8 nOutsideVal = bUseValueOutside ? nValueOutside : MorphologyOp::initVal; if (bParallel) { try { comphelper::ThreadPool& rShared = comphelper::ThreadPool::getSharedOptimalPool(); auto pTag = comphelper::ThreadPool::createThreadTaskTag(); { Bitmap::ScopedReadAccess pReadAccess(rBitmap); BitmapScopedWriteAccess pWriteAccess(rBitmap); FilterSharedData aSharedData(pReadAccess, pWriteAccess, nRadius, nOutsideVal); const sal_Int32 nLastIndex = pReadAccess->Height() - 1; sal_Int32 nStripStart = 0; for (; nStripStart < nLastIndex - nThreadStrip; nStripStart += nThreadStrip) { sal_Int32 nStripEnd = nStripStart + nThreadStrip - 1; auto pTask(std::make_unique(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, pWriteAccess, nRadius, nOutsideVal); const sal_Int32 nLastIndex = pReadAccess->Width() - 1; sal_Int32 nStripStart = 0; for (; nStripStart < nLastIndex - nThreadStrip; nStripStart += nThreadStrip) { sal_Int32 nStripEnd = nStripStart + nThreadStrip - 1; auto pTask(std::make_unique(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, pWriteAccess, nRadius, nOutsideVal); sal_Int32 nFirstIndex = 0; sal_Int32 nLastIndex = pReadAccess->Height() - 1; myPass::Horizontal(aSharedData, nFirstIndex, nLastIndex); } { Bitmap::ScopedReadAccess pReadAccess(rBitmap); BitmapScopedWriteAccess pWriteAccess(rBitmap); FilterSharedData aSharedData(pReadAccess, pWriteAccess, nRadius, nOutsideVal); sal_Int32 nFirstIndex = 0; sal_Int32 nLastIndex = pReadAccess->Width() - 1; myPass::Vertical(aSharedData, nFirstIndex, nLastIndex); } } } template void runFilter(Bitmap& rBitmap, BasicMorphologyOp op, sal_Int32 nRadius, bool bUseValueOutside, sal_uInt8 nValueOutside) { const bool bParallel = true; if (op == BasicMorphologyOp::erode) runFilter(rBitmap, nRadius, bParallel, bUseValueOutside, nValueOutside); else if (op == BasicMorphologyOp::dilate) runFilter(rBitmap, nRadius, bParallel, bUseValueOutside, nValueOutside); } } // end anonymous namespace BitmapBasicMorphologyFilter::BitmapBasicMorphologyFilter(BasicMorphologyOp op, sal_Int32 nRadius) : m_eOp(op) , m_nRadius(nRadius) { } BitmapBasicMorphologyFilter::BitmapBasicMorphologyFilter(BasicMorphologyOp op, sal_Int32 nRadius, sal_uInt8 nValueOutside) : m_eOp(op) , m_nRadius(nRadius) , m_nValueOutside(nValueOutside) , m_bUseValueOutside(true) { } BitmapBasicMorphologyFilter::~BitmapBasicMorphologyFilter() = default; BitmapEx BitmapBasicMorphologyFilter::execute(BitmapEx const& rBitmapEx) const { Bitmap result = filter(rBitmapEx.GetBitmap()); return BitmapEx(result, rBitmapEx.GetAlpha()); } Bitmap BitmapBasicMorphologyFilter::filter(Bitmap const& rBitmap) const { Bitmap bitmapCopy(rBitmap); ScanlineFormat nScanlineFormat; { Bitmap::ScopedReadAccess pReadAccess(bitmapCopy); nScanlineFormat = pReadAccess ? pReadAccess->GetScanlineFormat() : ScanlineFormat::NONE; } switch (nScanlineFormat) { case ScanlineFormat::N24BitTcRgb: case ScanlineFormat::N24BitTcBgr: runFilter<24>(bitmapCopy, m_eOp, m_nRadius, m_bUseValueOutside, m_nValueOutside); break; case ScanlineFormat::N32BitTcMask: case ScanlineFormat::N32BitTcBgra: runFilter<32>(bitmapCopy, m_eOp, m_nRadius, m_bUseValueOutside, m_nValueOutside); break; case ScanlineFormat::N8BitPal: runFilter<8>(bitmapCopy, m_eOp, m_nRadius, m_bUseValueOutside, m_nValueOutside); break; // TODO: handle 1-bit images default: // Use access' GetColor/SetPixel fallback runFilter<0>(bitmapCopy, m_eOp, m_nRadius, m_bUseValueOutside, m_nValueOutside); break; } return bitmapCopy; } /* vim:set shiftwidth=4 softtabstop=4 expandtab: */