bump product version to 7.2.5.1
[LibreOffice.git] / vcl / source / bitmap / BitmapBasicMorphologyFilter.cxx
blob3cd0bb87e4eb3689891ed3a9233e3becdbb31dd9
1 /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
2 /*
3 * This file is part of the LibreOffice project.
5 * This Source Code Form is subject to the terms of the Mozilla Public
6 * License, v. 2.0. If a copy of the MPL was not distributed with this
7 * file, You can obtain one at http://mozilla.org/MPL/2.0/.
9 */
11 #include <sal/config.h>
13 #include <comphelper/threadpool.hxx>
14 #include <sal/log.hxx>
15 #include <vcl/BitmapBasicMorphologyFilter.hxx>
17 #include <bitmap/BitmapWriteAccess.hxx>
19 #include <algorithm>
21 /* TODO: Use round kernel instead of square one.
22 This would make the result more natural, e.g. not making rounded square out of circle.
25 namespace
27 struct FilterSharedData
29 BitmapReadAccess* mpReadAccess;
30 BitmapWriteAccess* mpWriteAccess;
31 tools::Long mnRadius;
32 sal_uInt8 mnOutsideVal;
33 Color maOutsideColor;
35 FilterSharedData(Bitmap::ScopedReadAccess& rReadAccess, BitmapScopedWriteAccess& rWriteAccess,
36 tools::Long nRadius, sal_uInt8 nOutsideVal)
37 : mpReadAccess(rReadAccess.get())
38 , mpWriteAccess(rWriteAccess.get())
39 , mnRadius(nRadius)
40 , mnOutsideVal(nOutsideVal)
41 , maOutsideColor(ColorTransparency, nOutsideVal, nOutsideVal, nOutsideVal, nOutsideVal)
46 // Black is foreground, white is background
48 struct ErodeOp
50 static sal_uInt8 apply(sal_uInt8 v1, sal_uInt8 v2) { return std::max(v1, v2); }
51 static constexpr sal_uInt8 initVal = 0;
54 struct DilateOp
56 static sal_uInt8 apply(sal_uInt8 v1, sal_uInt8 v2) { return std::min(v1, v2); }
57 static constexpr sal_uInt8 initVal = SAL_MAX_UINT8;
60 // 8 bit per channel case
62 template <typename MorphologyOp, int nComponentWidth> struct Value
64 static constexpr int nWidthBytes = nComponentWidth / 8;
65 static_assert(nWidthBytes * 8 == nComponentWidth);
67 sal_uInt8 aResult[nWidthBytes];
69 // If we are at the start or at the end of the line, consider outside value
70 Value(FilterSharedData const& rShared, bool bLookOutside)
72 std::fill_n(aResult, nWidthBytes,
73 bLookOutside ? rShared.mnOutsideVal : MorphologyOp::initVal);
76 void apply(const BitmapReadAccess* pReadAccess, tools::Long x, tools::Long y,
77 sal_uInt8* pHint = nullptr)
79 sal_uInt8* pSource = (pHint ? pHint : pReadAccess->GetScanline(y)) + nWidthBytes * x;
80 std::transform(pSource, pSource + nWidthBytes, aResult, aResult, MorphologyOp::apply);
83 void copy(const BitmapWriteAccess* pWriteAccess, tools::Long x, tools::Long y,
84 sal_uInt8* pHint = nullptr)
86 sal_uInt8* pDest = (pHint ? pHint : pWriteAccess->GetScanline(y)) + nWidthBytes * x;
87 std::copy_n(aResult, nWidthBytes, pDest);
91 // Partial specializations for nComponentWidth == 0, using access' GetColor/SetPixel
93 template <typename MorphologyOp> struct Value<MorphologyOp, 0>
95 static constexpr Color initColor{ ColorTransparency, MorphologyOp::initVal,
96 MorphologyOp::initVal, MorphologyOp::initVal,
97 MorphologyOp::initVal };
99 Color aResult;
101 // If we are at the start or at the end of the line, consider outside value
102 Value(FilterSharedData const& rShared, bool bLookOutside)
103 : aResult(bLookOutside ? rShared.maOutsideColor : initColor)
107 void apply(const BitmapReadAccess* pReadAccess, tools::Long x, tools::Long y,
108 sal_uInt8* /*pHint*/ = nullptr)
110 const auto& rSource = pReadAccess->GetColor(y, x);
111 aResult = Color(ColorAlpha, MorphologyOp::apply(rSource.GetAlpha(), aResult.GetAlpha()),
112 MorphologyOp::apply(rSource.GetRed(), aResult.GetRed()),
113 MorphologyOp::apply(rSource.GetGreen(), aResult.GetGreen()),
114 MorphologyOp::apply(rSource.GetBlue(), aResult.GetBlue()));
117 void copy(BitmapWriteAccess* pWriteAccess, tools::Long x, tools::Long y,
118 sal_uInt8* /*pHint*/ = nullptr)
120 pWriteAccess->SetPixel(y, x, aResult);
124 bool GetMinMax(tools::Long nCenter, tools::Long nRadius, tools::Long nMaxLimit, tools::Long& nMin,
125 tools::Long& nMax)
127 nMin = nCenter - nRadius;
128 nMax = nCenter + nRadius;
129 bool bLookOutside = false;
130 if (nMin < 0)
132 bLookOutside = true;
133 nMin = 0;
135 if (nMax > nMaxLimit)
137 bLookOutside = true;
138 nMax = nMaxLimit;
140 return bLookOutside;
143 template <typename MorphologyOp, int nComponentWidth> struct pass
145 static void Horizontal(FilterSharedData const& rShared, const tools::Long nStart,
146 const tools::Long nEnd)
148 BitmapReadAccess* pReadAccess = rShared.mpReadAccess;
149 BitmapWriteAccess* pWriteAccess = rShared.mpWriteAccess;
151 const tools::Long nLastIndex = pReadAccess->Width() - 1;
153 for (tools::Long y = nStart; y <= nEnd; y++)
155 // Optimization
156 sal_uInt8* const pSourceHint = pReadAccess->GetScanline(y);
157 sal_uInt8* const pDestHint = pWriteAccess->GetScanline(y);
158 for (tools::Long x = 0; x <= nLastIndex; x++)
160 // This processes [nRadius * 2 + 1] pixels of source per resulting pixel
161 // TODO: try to optimize this to not process same pixels repeatedly
162 tools::Long iMin, iMax;
163 const bool bLookOutside = GetMinMax(x, rShared.mnRadius, nLastIndex, iMin, iMax);
164 Value<MorphologyOp, nComponentWidth> aResult(rShared, bLookOutside);
165 for (tools::Long i = iMin; i <= iMax; ++i)
166 aResult.apply(pReadAccess, i, y, pSourceHint);
168 aResult.copy(pWriteAccess, x, y, pDestHint);
173 static void Vertical(FilterSharedData const& rShared, const tools::Long nStart,
174 const tools::Long nEnd)
176 BitmapReadAccess* pReadAccess = rShared.mpReadAccess;
177 BitmapWriteAccess* pWriteAccess = rShared.mpWriteAccess;
179 const tools::Long nLastIndex = pReadAccess->Height() - 1;
181 for (tools::Long x = nStart; x <= nEnd; x++)
183 for (tools::Long y = 0; y <= nLastIndex; y++)
185 // This processes [nRadius * 2 + 1] pixels of source per resulting pixel
186 // TODO: try to optimize this to not process same pixels repeatedly
187 tools::Long iMin, iMax;
188 const bool bLookOutside = GetMinMax(y, rShared.mnRadius, nLastIndex, iMin, iMax);
189 Value<MorphologyOp, nComponentWidth> aResult(rShared, bLookOutside);
190 for (tools::Long i = iMin; i <= iMax; ++i)
191 aResult.apply(pReadAccess, x, i);
193 aResult.copy(pWriteAccess, x, y);
199 typedef void (*passFn)(FilterSharedData const& rShared, tools::Long nStart, tools::Long nEnd);
201 class FilterTask : public comphelper::ThreadTask
203 passFn mpFunction;
204 FilterSharedData& mrShared;
205 tools::Long mnStart;
206 tools::Long mnEnd;
208 public:
209 explicit FilterTask(const std::shared_ptr<comphelper::ThreadTaskTag>& pTag, passFn pFunction,
210 FilterSharedData& rShared, tools::Long nStart, tools::Long nEnd)
211 : comphelper::ThreadTask(pTag)
212 , mpFunction(pFunction)
213 , mrShared(rShared)
214 , mnStart(nStart)
215 , mnEnd(nEnd)
219 virtual void doWork() override { mpFunction(mrShared, mnStart, mnEnd); }
222 constexpr tools::Long nThreadStrip = 16;
224 template <typename MorphologyOp, int nComponentWidth>
225 void runFilter(Bitmap& rBitmap, const tools::Long nRadius, const bool bParallel,
226 bool bUseValueOutside, sal_uInt8 nValueOutside)
228 using myPass = pass<MorphologyOp, nComponentWidth>;
229 const sal_uInt8 nOutsideVal = bUseValueOutside ? nValueOutside : MorphologyOp::initVal;
230 if (bParallel)
234 comphelper::ThreadPool& rShared = comphelper::ThreadPool::getSharedOptimalPool();
235 auto pTag = comphelper::ThreadPool::createThreadTaskTag();
238 Bitmap::ScopedReadAccess pReadAccess(rBitmap);
239 BitmapScopedWriteAccess pWriteAccess(rBitmap);
240 FilterSharedData aSharedData(pReadAccess, pWriteAccess, nRadius, nOutsideVal);
242 const tools::Long nLastIndex = pReadAccess->Height() - 1;
243 tools::Long nStripStart = 0;
244 for (; nStripStart < nLastIndex - nThreadStrip; nStripStart += nThreadStrip)
246 tools::Long nStripEnd = nStripStart + nThreadStrip - 1;
247 auto pTask(std::make_unique<FilterTask>(pTag, myPass::Horizontal, aSharedData,
248 nStripStart, nStripEnd));
249 rShared.pushTask(std::move(pTask));
251 // Do the last (or the only) strip in main thread without threading overhead
252 myPass::Horizontal(aSharedData, nStripStart, nLastIndex);
253 rShared.waitUntilDone(pTag);
256 Bitmap::ScopedReadAccess pReadAccess(rBitmap);
257 BitmapScopedWriteAccess pWriteAccess(rBitmap);
258 FilterSharedData aSharedData(pReadAccess, pWriteAccess, nRadius, nOutsideVal);
260 const tools::Long nLastIndex = pReadAccess->Width() - 1;
261 tools::Long nStripStart = 0;
262 for (; nStripStart < nLastIndex - nThreadStrip; nStripStart += nThreadStrip)
264 tools::Long nStripEnd = nStripStart + nThreadStrip - 1;
265 auto pTask(std::make_unique<FilterTask>(pTag, myPass::Vertical, aSharedData,
266 nStripStart, nStripEnd));
267 rShared.pushTask(std::move(pTask));
269 // Do the last (or the only) strip in main thread without threading overhead
270 myPass::Vertical(aSharedData, nStripStart, nLastIndex);
271 rShared.waitUntilDone(pTag);
274 catch (...)
276 SAL_WARN("vcl.gdi", "threaded bitmap blurring failed");
279 else
282 Bitmap::ScopedReadAccess pReadAccess(rBitmap);
283 BitmapScopedWriteAccess pWriteAccess(rBitmap);
284 FilterSharedData aSharedData(pReadAccess, pWriteAccess, nRadius, nOutsideVal);
285 tools::Long nFirstIndex = 0;
286 tools::Long nLastIndex = pReadAccess->Height() - 1;
287 myPass::Horizontal(aSharedData, nFirstIndex, nLastIndex);
290 Bitmap::ScopedReadAccess pReadAccess(rBitmap);
291 BitmapScopedWriteAccess pWriteAccess(rBitmap);
292 FilterSharedData aSharedData(pReadAccess, pWriteAccess, nRadius, nOutsideVal);
293 tools::Long nFirstIndex = 0;
294 tools::Long nLastIndex = pReadAccess->Width() - 1;
295 myPass::Vertical(aSharedData, nFirstIndex, nLastIndex);
300 template <int nComponentWidth>
301 void runFilter(Bitmap& rBitmap, BasicMorphologyOp op, sal_Int32 nRadius, bool bUseValueOutside,
302 sal_uInt8 nValueOutside)
304 const bool bParallel = true;
306 if (op == BasicMorphologyOp::erode)
307 runFilter<ErodeOp, nComponentWidth>(rBitmap, nRadius, bParallel, bUseValueOutside,
308 nValueOutside);
309 else if (op == BasicMorphologyOp::dilate)
310 runFilter<DilateOp, nComponentWidth>(rBitmap, nRadius, bParallel, bUseValueOutside,
311 nValueOutside);
314 } // end anonymous namespace
316 BitmapBasicMorphologyFilter::BitmapBasicMorphologyFilter(BasicMorphologyOp op, sal_Int32 nRadius)
317 : m_eOp(op)
318 , m_nRadius(nRadius)
322 BitmapBasicMorphologyFilter::BitmapBasicMorphologyFilter(BasicMorphologyOp op, sal_Int32 nRadius,
323 sal_uInt8 nValueOutside)
324 : m_eOp(op)
325 , m_nRadius(nRadius)
326 , m_nValueOutside(nValueOutside)
327 , m_bUseValueOutside(true)
331 BitmapBasicMorphologyFilter::~BitmapBasicMorphologyFilter() = default;
333 BitmapEx BitmapBasicMorphologyFilter::execute(BitmapEx const& rBitmapEx) const
335 Bitmap result = filter(rBitmapEx.GetBitmap());
336 return BitmapEx(result, rBitmapEx.GetAlpha());
339 Bitmap BitmapBasicMorphologyFilter::filter(Bitmap const& rBitmap) const
341 Bitmap bitmapCopy(rBitmap);
342 ScanlineFormat nScanlineFormat;
344 Bitmap::ScopedReadAccess pReadAccess(bitmapCopy);
345 nScanlineFormat = pReadAccess->GetScanlineFormat();
348 switch (nScanlineFormat)
350 case ScanlineFormat::N24BitTcRgb:
351 case ScanlineFormat::N24BitTcBgr:
352 runFilter<24>(bitmapCopy, m_eOp, m_nRadius, m_bUseValueOutside, m_nValueOutside);
353 break;
354 case ScanlineFormat::N32BitTcMask:
355 case ScanlineFormat::N32BitTcBgra:
356 runFilter<32>(bitmapCopy, m_eOp, m_nRadius, m_bUseValueOutside, m_nValueOutside);
357 break;
358 case ScanlineFormat::N8BitPal:
359 runFilter<8>(bitmapCopy, m_eOp, m_nRadius, m_bUseValueOutside, m_nValueOutside);
360 break;
361 // TODO: handle 1-bit images
362 default:
363 // Use access' GetColor/SetPixel fallback
364 runFilter<0>(bitmapCopy, m_eOp, m_nRadius, m_bUseValueOutside, m_nValueOutside);
365 break;
368 return bitmapCopy;
371 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */