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 
25 namespace
26 {
27 struct FilterSharedData
28 {
29  BitmapReadAccess* mpReadAccess;
30  BitmapWriteAccess* mpWriteAccess;
31  tools::Long mnRadius;
32  sal_uInt8 mnOutsideVal;
33  Color maOutsideColor;
34 
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)
42  {
43  }
44 };
45 
46 // Black is foreground, white is background
47 
48 struct 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 
54 struct 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 
62 template <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, tools::Long x, tools::Long 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, tools::Long x, tools::Long 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 
93 template <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, tools::Long x, tools::Long 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, tools::Long x, tools::Long y,
118  sal_uInt8* /*pHint*/ = nullptr)
119  {
120  pWriteAccess->SetPixel(y, x, aResult);
121  }
122 };
123 
124 bool GetMinMax(tools::Long nCenter, tools::Long nRadius, tools::Long nMaxLimit, tools::Long& nMin,
125  tools::Long& 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 
143 template <typename MorphologyOp, int nComponentWidth> struct pass
144 {
145  static void Horizontal(FilterSharedData const& rShared, const tools::Long nStart,
146  const tools::Long nEnd)
147  {
148  BitmapReadAccess* pReadAccess = rShared.mpReadAccess;
149  BitmapWriteAccess* pWriteAccess = rShared.mpWriteAccess;
150 
151  const tools::Long nLastIndex = pReadAccess->Width() - 1;
152 
153  for (tools::Long 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 (tools::Long 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  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);
167 
168  aResult.copy(pWriteAccess, x, y, pDestHint);
169  }
170  }
171  }
172 
173  static void Vertical(FilterSharedData const& rShared, const tools::Long nStart,
174  const tools::Long nEnd)
175  {
176  BitmapReadAccess* pReadAccess = rShared.mpReadAccess;
177  BitmapWriteAccess* pWriteAccess = rShared.mpWriteAccess;
178 
179  const tools::Long nLastIndex = pReadAccess->Height() - 1;
180 
181  for (tools::Long x = nStart; x <= nEnd; x++)
182  {
183  for (tools::Long 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  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);
192 
193  aResult.copy(pWriteAccess, x, y);
194  }
195  }
196  }
197 };
198 
199 typedef void (*passFn)(FilterSharedData const& rShared, tools::Long nStart, tools::Long nEnd);
200 
201 class FilterTask : public comphelper::ThreadTask
202 {
203  passFn mpFunction;
204  FilterSharedData& mrShared;
207 
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)
216  {
217  }
218 
219  virtual void doWork() override { mpFunction(mrShared, mnStart, mnEnd); }
220 };
221 
222 constexpr tools::Long nThreadStrip = 16;
223 
224 template <typename MorphologyOp, int nComponentWidth>
225 void runFilter(Bitmap& rBitmap, const tools::Long 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 tools::Long nLastIndex = pReadAccess->Height() - 1;
243  tools::Long nStripStart = 0;
244  for (; nStripStart < nLastIndex - nThreadStrip; nStripStart += nThreadStrip)
245  {
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));
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 tools::Long nLastIndex = pReadAccess->Width() - 1;
261  tools::Long nStripStart = 0;
262  for (; nStripStart < nLastIndex - nThreadStrip; nStripStart += nThreadStrip)
263  {
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));
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  tools::Long nFirstIndex = 0;
286  tools::Long 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  tools::Long nFirstIndex = 0;
294  tools::Long nLastIndex = pReadAccess->Width() - 1;
295  myPass::Vertical(aSharedData, nFirstIndex, nLastIndex);
296  }
297  }
298 }
299 
300 template <int nComponentWidth>
301 void 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.GetAlpha());
337 }
338 
340 {
341  Bitmap bitmapCopy(rBitmap);
342  ScanlineFormat nScanlineFormat;
343  {
344  Bitmap::ScopedReadAccess pReadAccess(bitmapCopy);
345  nScanlineFormat = pReadAccess->GetScanlineFormat();
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: */
css::uno::Reference< css::linguistic2::XProofreadingIterator > get(css::uno::Reference< css::uno::XComponentContext > const &context)
tools::Long Height() const
sal_uInt8 GetAlpha() const
sal_uInt8 GetRed() const
BitmapBasicMorphologyFilter(BasicMorphologyOp op, sal_Int32 nRadius)
sal_Int32 mnStart
size_t mnEnd
long Long
virtual void doWork()=0
void waitUntilDone(const std::shared_ptr< ThreadTaskTag > &, bool bJoin=true)
Value
ScanlineFormat
Definition: Scanline.hxx:29
Scanline GetScanline(tools::Long nY) const
void SetPixel(tools::Long nY, tools::Long nX, const BitmapColor &rBitmapColor)
sal_uInt8 GetBlue() const
static ThreadPool & getSharedOptimalPool()
int i
void pushTask(std::unique_ptr< ThreadTask > pTask)
ScanlineFormat GetScanlineFormat() const
BitmapColor GetColor(tools::Long nY, tools::Long nX) const
tools::Long Width() const
ColorTransparency
pass
virtual ~BitmapBasicMorphologyFilter()
Bitmap GetBitmap(Color aTransparentReplaceColor) const
Definition: BitmapEx.cxx:203
sal_uInt8 GetGreen() const
Bitmap filter(Bitmap const &rBitmap) const
virtual BitmapEx execute(BitmapEx const &rBitmap) const override
AlphaMask GetAlpha() const
Definition: BitmapEx.cxx:215
unsigned char sal_uInt8
#define SAL_MAX_UINT8
void copy(const fs::path &src, const fs::path &dest)
static std::shared_ptr< ThreadTaskTag > createThreadTaskTag()
Any result
#define SAL_WARN(area, stream)
typedef void(CALLTYPE *GetFuncDataPtr)(sal_uInt16 &nNo