LibreOffice Module vcl (master) 1
BitmapBasicMorphologyFilter.cxx
Go to the documentation of this file.
1/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
2/*
3 * This file is part of the LibreOffice project.
4 *
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/.
8 *
9 */
10
11#include <sal/config.h>
12
14#include <sal/log.hxx>
16
18
19#include <algorithm>
20
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.
23 */
24
25namespace
26{
27struct FilterSharedData
28{
29 BitmapReadAccess* mpReadAccess;
30 BitmapWriteAccess* mpWriteAccess;
31 sal_Int32 mnRadius;
32 sal_uInt8 mnOutsideVal;
33 Color maOutsideColor;
34
35 FilterSharedData(Bitmap::ScopedReadAccess& rReadAccess, BitmapScopedWriteAccess& rWriteAccess,
36 sal_Int32 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)
42 {
43 }
44};
45
46// Black is foreground, white is background
47
48struct ErodeOp
49{
50 static sal_uInt8 apply(sal_uInt8 v1, sal_uInt8 v2) { return std::max(v1, v2); }
51 static constexpr sal_uInt8 initVal = 0;
52};
53
54struct DilateOp
55{
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;
58};
59
60// 8 bit per channel case
61
62template <typename MorphologyOp, int nComponentWidth> struct Value
63{
64 static constexpr int nWidthBytes = nComponentWidth / 8;
65 static_assert(nWidthBytes * 8 == nComponentWidth);
66
67 sal_uInt8 aResult[nWidthBytes];
68
69 // If we are at the start or at the end of the line, consider outside value
70 Value(FilterSharedData const& rShared, bool bLookOutside)
71 {
72 std::fill_n(aResult, nWidthBytes,
73 bLookOutside ? rShared.mnOutsideVal : MorphologyOp::initVal);
74 }
75
76 void apply(const BitmapReadAccess* pReadAccess, sal_Int32 x, sal_Int32 y,
77 sal_uInt8* pHint = nullptr)
78 {
79 sal_uInt8* pSource = (pHint ? pHint : pReadAccess->GetScanline(y)) + nWidthBytes * x;
80 std::transform(pSource, pSource + nWidthBytes, aResult, aResult, MorphologyOp::apply);
81 }
82
83 void copy(const BitmapWriteAccess* pWriteAccess, sal_Int32 x, sal_Int32 y,
84 sal_uInt8* pHint = nullptr)
85 {
86 sal_uInt8* pDest = (pHint ? pHint : pWriteAccess->GetScanline(y)) + nWidthBytes * x;
87 std::copy_n(aResult, nWidthBytes, pDest);
88 }
89};
90
91// Partial specializations for nComponentWidth == 0, using access' GetColor/SetPixel
92
93template <typename MorphologyOp> struct Value<MorphologyOp, 0>
94{
95 static constexpr Color initColor{ ColorTransparency, MorphologyOp::initVal,
96 MorphologyOp::initVal, MorphologyOp::initVal,
97 MorphologyOp::initVal };
98
99 Color aResult;
100
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)
104 {
105 }
106
107 void apply(const BitmapReadAccess* pReadAccess, sal_Int32 x, sal_Int32 y,
108 sal_uInt8* /*pHint*/ = nullptr)
109 {
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()));
115 }
116
117 void copy(BitmapWriteAccess* pWriteAccess, sal_Int32 x, sal_Int32 y,
118 sal_uInt8* /*pHint*/ = nullptr)
119 {
120 pWriteAccess->SetPixel(y, x, aResult);
121 }
122};
123
124bool GetMinMax(sal_Int32 nCenter, sal_Int32 nRadius, sal_Int32 nMaxLimit, sal_Int32& nMin,
125 sal_Int32& nMax)
126{
127 nMin = nCenter - nRadius;
128 nMax = nCenter + nRadius;
129 bool bLookOutside = false;
130 if (nMin < 0)
131 {
132 bLookOutside = true;
133 nMin = 0;
134 }
135 if (nMax > nMaxLimit)
136 {
137 bLookOutside = true;
138 nMax = nMaxLimit;
139 }
140 return bLookOutside;
141}
142
143template <typename MorphologyOp, int nComponentWidth> struct pass
144{
145 static void Horizontal(FilterSharedData const& rShared, const sal_Int32 nStart,
146 const sal_Int32 nEnd)
147 {
148 BitmapReadAccess* pReadAccess = rShared.mpReadAccess;
149 BitmapWriteAccess* pWriteAccess = rShared.mpWriteAccess;
150
151 const sal_Int32 nLastIndex = pReadAccess->Width() - 1;
152
153 for (sal_Int32 y = nStart; y <= nEnd; y++)
154 {
155 // Optimization
156 sal_uInt8* const pSourceHint = pReadAccess->GetScanline(y);
157 sal_uInt8* const pDestHint = pWriteAccess->GetScanline(y);
158 for (sal_Int32 x = 0; x <= nLastIndex; x++)
159 {
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 sal_Int32 iMin, iMax;
163 const bool bLookOutside = GetMinMax(x, rShared.mnRadius, nLastIndex, iMin, iMax);
164 Value<MorphologyOp, nComponentWidth> aResult(rShared, bLookOutside);
165 for (sal_Int32 i = iMin; i <= iMax; ++i)
166 aResult.apply(pReadAccess, i, y, pSourceHint);
167
168 aResult.copy(pWriteAccess, x, y, pDestHint);
169 }
170 }
171 }
172
173 static void Vertical(FilterSharedData const& rShared, const sal_Int32 nStart,
174 const sal_Int32 nEnd)
175 {
176 BitmapReadAccess* pReadAccess = rShared.mpReadAccess;
177 BitmapWriteAccess* pWriteAccess = rShared.mpWriteAccess;
178
179 const sal_Int32 nLastIndex = pReadAccess->Height() - 1;
180
181 for (sal_Int32 x = nStart; x <= nEnd; x++)
182 {
183 for (sal_Int32 y = 0; y <= nLastIndex; y++)
184 {
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 sal_Int32 iMin, iMax;
188 const bool bLookOutside = GetMinMax(y, rShared.mnRadius, nLastIndex, iMin, iMax);
189 Value<MorphologyOp, nComponentWidth> aResult(rShared, bLookOutside);
190 for (sal_Int32 i = iMin; i <= iMax; ++i)
191 aResult.apply(pReadAccess, x, i);
192
193 aResult.copy(pWriteAccess, x, y);
194 }
195 }
196 }
197};
198
199typedef void (*passFn)(FilterSharedData const& rShared, sal_Int32 nStart, sal_Int32 nEnd);
200
201class FilterTask : public comphelper::ThreadTask
202{
203 passFn mpFunction;
204 FilterSharedData& mrShared;
205 sal_Int32 mnStart;
206 sal_Int32 mnEnd;
207
208public:
209 explicit FilterTask(const std::shared_ptr<comphelper::ThreadTaskTag>& pTag, passFn pFunction,
210 FilterSharedData& rShared, sal_Int32 nStart, sal_Int32 nEnd)
211 : comphelper::ThreadTask(pTag)
212 , mpFunction(pFunction)
213 , mrShared(rShared)
214 , mnStart(nStart)
215 , mnEnd(nEnd)
216 {
217 }
218
219 virtual void doWork() override { mpFunction(mrShared, mnStart, mnEnd); }
220};
221
222constexpr sal_Int32 nThreadStrip = 16;
223
224template <typename MorphologyOp, int nComponentWidth>
225void runFilter(Bitmap& rBitmap, const sal_Int32 nRadius, const bool bParallel,
226 bool bUseValueOutside, sal_uInt8 nValueOutside)
227{
228 using myPass = pass<MorphologyOp, nComponentWidth>;
229 const sal_uInt8 nOutsideVal = bUseValueOutside ? nValueOutside : MorphologyOp::initVal;
230 if (bParallel)
231 {
232 try
233 {
236
237 {
238 Bitmap::ScopedReadAccess pReadAccess(rBitmap);
239 BitmapScopedWriteAccess pWriteAccess(rBitmap);
240 FilterSharedData aSharedData(pReadAccess, pWriteAccess, nRadius, nOutsideVal);
241
242 const sal_Int32 nLastIndex = pReadAccess->Height() - 1;
243 sal_Int32 nStripStart = 0;
244 for (; nStripStart < nLastIndex - nThreadStrip; nStripStart += nThreadStrip)
245 {
246 sal_Int32 nStripEnd = nStripStart + nThreadStrip - 1;
247 auto pTask(std::make_unique<FilterTask>(pTag, myPass::Horizontal, aSharedData,
248 nStripStart, nStripEnd));
249 rShared.pushTask(std::move(pTask));
250 }
251 // Do the last (or the only) strip in main thread without threading overhead
252 myPass::Horizontal(aSharedData, nStripStart, nLastIndex);
253 rShared.waitUntilDone(pTag);
254 }
255 {
256 Bitmap::ScopedReadAccess pReadAccess(rBitmap);
257 BitmapScopedWriteAccess pWriteAccess(rBitmap);
258 FilterSharedData aSharedData(pReadAccess, pWriteAccess, nRadius, nOutsideVal);
259
260 const sal_Int32 nLastIndex = pReadAccess->Width() - 1;
261 sal_Int32 nStripStart = 0;
262 for (; nStripStart < nLastIndex - nThreadStrip; nStripStart += nThreadStrip)
263 {
264 sal_Int32 nStripEnd = nStripStart + nThreadStrip - 1;
265 auto pTask(std::make_unique<FilterTask>(pTag, myPass::Vertical, aSharedData,
266 nStripStart, nStripEnd));
267 rShared.pushTask(std::move(pTask));
268 }
269 // Do the last (or the only) strip in main thread without threading overhead
270 myPass::Vertical(aSharedData, nStripStart, nLastIndex);
271 rShared.waitUntilDone(pTag);
272 }
273 }
274 catch (...)
275 {
276 SAL_WARN("vcl.gdi", "threaded bitmap blurring failed");
277 }
278 }
279 else
280 {
281 {
282 Bitmap::ScopedReadAccess pReadAccess(rBitmap);
283 BitmapScopedWriteAccess pWriteAccess(rBitmap);
284 FilterSharedData aSharedData(pReadAccess, pWriteAccess, nRadius, nOutsideVal);
285 sal_Int32 nFirstIndex = 0;
286 sal_Int32 nLastIndex = pReadAccess->Height() - 1;
287 myPass::Horizontal(aSharedData, nFirstIndex, nLastIndex);
288 }
289 {
290 Bitmap::ScopedReadAccess pReadAccess(rBitmap);
291 BitmapScopedWriteAccess pWriteAccess(rBitmap);
292 FilterSharedData aSharedData(pReadAccess, pWriteAccess, nRadius, nOutsideVal);
293 sal_Int32 nFirstIndex = 0;
294 sal_Int32 nLastIndex = pReadAccess->Width() - 1;
295 myPass::Vertical(aSharedData, nFirstIndex, nLastIndex);
296 }
297 }
298}
299
300template <int nComponentWidth>
301void runFilter(Bitmap& rBitmap, BasicMorphologyOp op, sal_Int32 nRadius, bool bUseValueOutside,
302 sal_uInt8 nValueOutside)
303{
304 const bool bParallel = true;
305
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);
312}
313
314} // end anonymous namespace
315
317 : m_eOp(op)
318 , m_nRadius(nRadius)
319{
320}
321
323 sal_uInt8 nValueOutside)
324 : m_eOp(op)
325 , m_nRadius(nRadius)
326 , m_nValueOutside(nValueOutside)
327 , m_bUseValueOutside(true)
328{
329}
330
332
334{
335 Bitmap result = filter(rBitmapEx.GetBitmap());
336 return BitmapEx(result, rBitmapEx.GetAlphaMask());
337}
338
340{
341 Bitmap bitmapCopy(rBitmap);
342 ScanlineFormat nScanlineFormat;
343 {
344 Bitmap::ScopedReadAccess pReadAccess(bitmapCopy);
345 nScanlineFormat = pReadAccess ? pReadAccess->GetScanlineFormat() : ScanlineFormat::NONE;
346 }
347
348 switch (nScanlineFormat)
349 {
352 runFilter<24>(bitmapCopy, m_eOp, m_nRadius, m_bUseValueOutside, m_nValueOutside);
353 break;
356 runFilter<32>(bitmapCopy, m_eOp, m_nRadius, m_bUseValueOutside, m_nValueOutside);
357 break;
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;
366 }
367
368 return bitmapCopy;
369}
370
371/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
ScanlineFormat
Definition: Scanline.hxx:29
size_t mnEnd
virtual BitmapEx execute(BitmapEx const &rBitmap) const override
Bitmap filter(Bitmap const &rBitmap) const
BitmapBasicMorphologyFilter(BasicMorphologyOp op, sal_Int32 nRadius)
virtual ~BitmapBasicMorphologyFilter()
const AlphaMask & GetAlphaMask() const
Definition: bitmapex.hxx:71
Bitmap GetBitmap(Color aTransparentReplaceColor) const
Definition: BitmapEx.cxx:217
tools::Long Height() const
tools::Long Width() const
ScanlineFormat GetScanlineFormat() const
BitmapColor GetColor(tools::Long nY, tools::Long nX) const
Scanline GetScanline(tools::Long nY) const
void SetPixel(tools::Long nY, tools::Long nX, const BitmapColor &rBitmapColor)
sal_uInt8 GetBlue() const
sal_uInt8 GetAlpha() const
sal_uInt8 GetRed() const
sal_uInt8 GetGreen() const
static ThreadPool & getSharedOptimalPool()
void waitUntilDone(const std::shared_ptr< ThreadTaskTag > &, bool bJoin=true)
static std::shared_ptr< ThreadTaskTag > createThreadTaskTag()
void pushTask(std::unique_ptr< ThreadTask > pTask)
virtual void doWork()=0
ColorTransparency
float y
float x
#define SAL_WARN(area, stream)
Value
void copy(const fs::path &src, const fs::path &dest)
int i
css::uno::Reference< css::linguistic2::XProofreadingIterator > get(css::uno::Reference< css::uno::XComponentContext > const &context)
pass
#define SAL_MAX_UINT8
unsigned char sal_uInt8
Any result
sal_Int32 mnStart