summaryrefslogtreecommitdiff
path: root/vcl/source/bitmap/BitmapBasicMorphologyFilter.cxx
diff options
context:
space:
mode:
authorMike Kaganski <mike.kaganski@collabora.com>2020-05-12 20:05:54 +0300
committerMike Kaganski <mike.kaganski@collabora.com>2020-05-13 23:29:29 +0200
commitcbc13ac0624685582ebd4634812681274db803aa (patch)
treef9a5e856161f4ea5290cc31ea892843a75d8bcc0 /vcl/source/bitmap/BitmapBasicMorphologyFilter.cxx
parent4907531966880f2bb4bb14b1c159865909000842 (diff)
tdf#49247: draw soft edges
This factors out the common code for blurring used both in glow and soft edges into ProcessAndBlurAlphaMask. Also this reverts commit a98bdbae459ad7341bf7f484c402e77e4062cd16, since its use was removed from VclPixelProcessor2D. Change-Id: Icd7fdb06bef3932ff3b9ce7e283b515b15d246a5 Reviewed-on: https://gerrit.libreoffice.org/c/core/+/94087 Tested-by: Jenkins Reviewed-by: Mike Kaganski <mike.kaganski@collabora.com>
Diffstat (limited to 'vcl/source/bitmap/BitmapBasicMorphologyFilter.cxx')
-rw-r--r--vcl/source/bitmap/BitmapBasicMorphologyFilter.cxx218
1 files changed, 110 insertions, 108 deletions
diff --git a/vcl/source/bitmap/BitmapBasicMorphologyFilter.cxx b/vcl/source/bitmap/BitmapBasicMorphologyFilter.cxx
index b570b33e6495..fe4f63f90d16 100644
--- a/vcl/source/bitmap/BitmapBasicMorphologyFilter.cxx
+++ b/vcl/source/bitmap/BitmapBasicMorphologyFilter.cxx
@@ -26,11 +26,16 @@ struct FilterSharedData
BitmapReadAccess* mpReadAccess;
BitmapWriteAccess* mpWriteAccess;
long mnRadius;
+ sal_uInt8 mnOutsideVal;
+ Color maOutsideColor;
- FilterSharedData(BitmapReadAccess* pReadAccess, BitmapWriteAccess* pWriteAccess, long nRadius)
- : mpReadAccess(pReadAccess)
- , mpWriteAccess(pWriteAccess)
+ FilterSharedData(Bitmap::ScopedReadAccess& rReadAccess, BitmapScopedWriteAccess& rWriteAccess,
+ long nRadius, sal_uInt8 nOutsideVal)
+ : mpReadAccess(rReadAccess.get())
+ , mpWriteAccess(rWriteAccess.get())
, mnRadius(nRadius)
+ , mnOutsideVal(nOutsideVal)
+ , maOutsideColor(nOutsideVal, nOutsideVal, nOutsideVal, nOutsideVal)
{
}
};
@@ -41,130 +46,116 @@ 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;
+ static constexpr sal_uInt8 initVal = SAL_MAX_UINT8;
};
-template <typename MorphologyOp> struct OpHelper
+// 8 bit per channel case
+
+template <typename MorphologyOp, int nComponentWidth> struct Value
{
- template <int n> static void apply(sal_uInt8 (&rResult)[n], Scanline pSource)
+ 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::transform(pSource, pSource + n, rResult, rResult, MorphologyOp::apply);
+ std::fill_n(aResult, nWidthBytes,
+ bLookOutside ? rShared.mnOutsideVal : MorphologyOp::initVal);
}
- static void apply(Color& rResult, const Color& rSource)
+ void apply(BitmapReadAccess* pReadAccess, long x, long y, sal_uInt8* pHint = nullptr)
{
- 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()));
+ sal_uInt8* pSource = (pHint ? pHint : pReadAccess->GetScanline(y)) + nWidthBytes * x;
+ std::transform(pSource, pSource + nWidthBytes, aResult, aResult, MorphologyOp::apply);
}
- template <int n> static void init(sal_uInt8 (&rResult)[n])
+ void copy(BitmapWriteAccess* pWriteAccess, long x, long y, sal_uInt8* pHint = nullptr)
{
- std::fill_n(rResult, n, MorphologyOp::initVal);
+ sal_uInt8* pDest = (pHint ? pHint : pWriteAccess->GetScanline(y)) + nWidthBytes * x;
+ std::copy_n(aResult, nWidthBytes, pDest);
}
};
-// 8 bit per channel case
+// Partial specializations for nComponentWidth == 0, using access' GetColor/SetPixel
-template <typename MorphologyOp, int nComponentWidth> struct pass
+template <typename MorphologyOp> struct Value<MorphologyOp, 0>
{
- 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;
+ static constexpr Color initColor{ MorphologyOp::initVal, MorphologyOp::initVal,
+ MorphologyOp::initVal, MorphologyOp::initVal };
- const long nRadius = rShared.mnRadius;
+ Color aResult;
- 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;
- }
- }
+ // 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)
+ {
}
- static void Vertical(FilterSharedData const& rShared, const long nStart, const long nEnd)
+ void apply(BitmapReadAccess* pReadAccess, long x, long y, sal_uInt8* /*pHint*/ = nullptr)
{
- BitmapReadAccess* pReadAccess = rShared.mpReadAccess;
- BitmapWriteAccess* pWriteAccess = rShared.mpWriteAccess;
-
- const long nHeight = pReadAccess->Height();
- const long nLastIndex = nHeight - 1;
-
- const long nRadius = rShared.mnRadius;
+ const auto& rSource = pReadAccess->GetColor(y, x);
+ aResult = Color(MorphologyOp::apply(rSource.GetTransparency(), aResult.GetTransparency()),
+ MorphologyOp::apply(rSource.GetRed(), aResult.GetRed()),
+ MorphologyOp::apply(rSource.GetGreen(), aResult.GetGreen()),
+ MorphologyOp::apply(rSource.GetBlue(), aResult.GetBlue()));
+ }
- 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;
- }
- }
+ void copy(BitmapWriteAccess* pWriteAccess, long x, long y, sal_uInt8* /*pHint*/ = nullptr)
+ {
+ pWriteAccess->SetPixel(y, x, aResult);
}
};
-// Partial specializations for nComponentWidth == 0, using access' GetColor/SetPixel
+bool GetMinMax(long nCenter, long nRadius, long nMaxLimit, long& nMin, long& 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 <typename MorphologyOp> struct pass<MorphologyOp, 0>
+template <typename MorphologyOp, int nComponentWidth> struct pass
{
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;
+ const long nLastIndex = pReadAccess->Width() - 1;
for (long y = nStart; y <= nEnd; y++)
{
- for (long x = 0; x < nWidth; x++)
+ // Optimization
+ sal_uInt8* const pSourceHint = pReadAccess->GetScanline(y);
+ sal_uInt8* const pDestHint = pWriteAccess->GetScanline(y);
+ for (long 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
- 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));
+ long iMin, iMax;
+ const bool bLookOutside = GetMinMax(x, rShared.mnRadius, nLastIndex, iMin, iMax);
+ Value<MorphologyOp, nComponentWidth> aResult(rShared, bLookOutside);
+ for (long i = iMin; i <= iMax; ++i)
+ aResult.apply(pReadAccess, i, y, pSourceHint);
- pWriteAccess->SetPixel(y, x, aResult);
+ aResult.copy(pWriteAccess, x, y, pDestHint);
}
}
}
@@ -174,23 +165,21 @@ template <typename MorphologyOp> struct pass<MorphologyOp, 0>
BitmapReadAccess* pReadAccess = rShared.mpReadAccess;
BitmapWriteAccess* pWriteAccess = rShared.mpWriteAccess;
- const long nHeight = pReadAccess->Height();
- const long nLastIndex = nHeight - 1;
-
- const long nRadius = rShared.mnRadius;
+ const long nLastIndex = pReadAccess->Height() - 1;
for (long x = nStart; x <= nEnd; x++)
{
- for (long y = 0; y < nHeight; y++)
+ for (long 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
- 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));
+ long iMin, iMax;
+ const bool bLookOutside = GetMinMax(y, rShared.mnRadius, nLastIndex, iMin, iMax);
+ Value<MorphologyOp, nComponentWidth> aResult(rShared, bLookOutside);
+ for (long i = iMin; i <= iMax; ++i)
+ aResult.apply(pReadAccess, x, i);
- pWriteAccess->SetPixel(y, x, aResult);
+ aResult.copy(pWriteAccess, x, y);
}
}
}
@@ -222,9 +211,11 @@ public:
constexpr long nThreadStrip = 16;
template <typename MorphologyOp, int nComponentWidth>
-void runFilter(Bitmap& rBitmap, const long nRadius, const bool bParallel)
+void runFilter(Bitmap& rBitmap, const long nRadius, const bool bParallel, bool bUseValueOutside,
+ sal_uInt8 nValueOutside)
{
using myPass = pass<MorphologyOp, nComponentWidth>;
+ const sal_uInt8 nOutsideVal = bUseValueOutside ? nValueOutside : MorphologyOp::initVal;
if (bParallel)
{
try
@@ -235,7 +226,7 @@ void runFilter(Bitmap& rBitmap, const long nRadius, const bool bParallel)
{
Bitmap::ScopedReadAccess pReadAccess(rBitmap);
BitmapScopedWriteAccess pWriteAccess(rBitmap);
- FilterSharedData aSharedData(pReadAccess.get(), pWriteAccess.get(), nRadius);
+ FilterSharedData aSharedData(pReadAccess, pWriteAccess, nRadius, nOutsideVal);
const long nLastIndex = pReadAccess->Height() - 1;
long nStripStart = 0;
@@ -253,7 +244,7 @@ void runFilter(Bitmap& rBitmap, const long nRadius, const bool bParallel)
{
Bitmap::ScopedReadAccess pReadAccess(rBitmap);
BitmapScopedWriteAccess pWriteAccess(rBitmap);
- FilterSharedData aSharedData(pReadAccess.get(), pWriteAccess.get(), nRadius);
+ FilterSharedData aSharedData(pReadAccess, pWriteAccess, nRadius, nOutsideVal);
const long nLastIndex = pReadAccess->Width() - 1;
long nStripStart = 0;
@@ -279,7 +270,7 @@ void runFilter(Bitmap& rBitmap, const long nRadius, const bool bParallel)
{
Bitmap::ScopedReadAccess pReadAccess(rBitmap);
BitmapScopedWriteAccess pWriteAccess(rBitmap);
- FilterSharedData aSharedData(pReadAccess.get(), pWriteAccess.get(), nRadius);
+ FilterSharedData aSharedData(pReadAccess, pWriteAccess, nRadius, nOutsideVal);
long nFirstIndex = 0;
long nLastIndex = pReadAccess->Height() - 1;
myPass::Horizontal(aSharedData, nFirstIndex, nLastIndex);
@@ -287,7 +278,7 @@ void runFilter(Bitmap& rBitmap, const long nRadius, const bool bParallel)
{
Bitmap::ScopedReadAccess pReadAccess(rBitmap);
BitmapScopedWriteAccess pWriteAccess(rBitmap);
- FilterSharedData aSharedData(pReadAccess.get(), pWriteAccess.get(), nRadius);
+ FilterSharedData aSharedData(pReadAccess, pWriteAccess, nRadius, nOutsideVal);
long nFirstIndex = 0;
long nLastIndex = pReadAccess->Width() - 1;
myPass::Vertical(aSharedData, nFirstIndex, nLastIndex);
@@ -296,14 +287,17 @@ void runFilter(Bitmap& rBitmap, const long nRadius, const bool bParallel)
}
template <int nComponentWidth>
-void runFilter(Bitmap& rBitmap, BasicMorphologyOp op, sal_Int32 nRadius)
+void runFilter(Bitmap& rBitmap, BasicMorphologyOp op, sal_Int32 nRadius, bool bUseValueOutside,
+ sal_uInt8 nValueOutside)
{
const bool bParallel = true;
if (op == BasicMorphologyOp::erode)
- runFilter<ErodeOp, nComponentWidth>(rBitmap, nRadius, bParallel);
+ runFilter<ErodeOp, nComponentWidth>(rBitmap, nRadius, bParallel, bUseValueOutside,
+ nValueOutside);
else if (op == BasicMorphologyOp::dilate)
- runFilter<DilateOp, nComponentWidth>(rBitmap, nRadius, bParallel);
+ runFilter<DilateOp, nComponentWidth>(rBitmap, nRadius, bParallel, bUseValueOutside,
+ nValueOutside);
}
} // end anonymous namespace
@@ -314,12 +308,20 @@ BitmapBasicMorphologyFilter::BitmapBasicMorphologyFilter(BasicMorphologyOp op, s
{
}
+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 aBitmap = rBitmapEx.GetBitmap();
- Bitmap result = filter(aBitmap);
+ Bitmap result = filter(rBitmapEx.GetBitmap());
return BitmapEx(result, rBitmapEx.GetMask());
}
@@ -336,19 +338,19 @@ Bitmap BitmapBasicMorphologyFilter::filter(Bitmap const& rBitmap) const
{
case ScanlineFormat::N24BitTcRgb:
case ScanlineFormat::N24BitTcBgr:
- runFilter<24>(bitmapCopy, m_eOp, m_nRadius);
+ 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);
+ runFilter<32>(bitmapCopy, m_eOp, m_nRadius, m_bUseValueOutside, m_nValueOutside);
break;
case ScanlineFormat::N8BitPal:
- runFilter<8>(bitmapCopy, m_eOp, m_nRadius);
+ 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);
+ runFilter<0>(bitmapCopy, m_eOp, m_nRadius, m_bUseValueOutside, m_nValueOutside);
break;
}