1 /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
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/.
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>
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.
27 struct FilterSharedData
29 BitmapReadAccess
* mpReadAccess
;
30 BitmapWriteAccess
* mpWriteAccess
;
32 sal_uInt8 mnOutsideVal
;
35 FilterSharedData(Bitmap::ScopedReadAccess
& rReadAccess
, BitmapScopedWriteAccess
& rWriteAccess
,
36 tools::Long nRadius
, sal_uInt8 nOutsideVal
)
37 : mpReadAccess(rReadAccess
.get())
38 , mpWriteAccess(rWriteAccess
.get())
40 , mnOutsideVal(nOutsideVal
)
41 , maOutsideColor(ColorTransparency
, nOutsideVal
, nOutsideVal
, nOutsideVal
, nOutsideVal
)
46 // Black is foreground, white is background
50 static sal_uInt8
apply(sal_uInt8 v1
, sal_uInt8 v2
) { return std::max(v1
, v2
); }
51 static constexpr sal_uInt8 initVal
= 0;
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
};
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
,
127 nMin
= nCenter
- nRadius
;
128 nMax
= nCenter
+ nRadius
;
129 bool bLookOutside
= false;
135 if (nMax
> nMaxLimit
)
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
++)
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
204 FilterSharedData
& mrShared
;
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
)
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
;
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
);
276 SAL_WARN("vcl.gdi", "threaded bitmap blurring failed");
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
,
309 else if (op
== BasicMorphologyOp::dilate
)
310 runFilter
<DilateOp
, nComponentWidth
>(rBitmap
, nRadius
, bParallel
, bUseValueOutside
,
314 } // end anonymous namespace
316 BitmapBasicMorphologyFilter::BitmapBasicMorphologyFilter(BasicMorphologyOp op
, sal_Int32 nRadius
)
322 BitmapBasicMorphologyFilter::BitmapBasicMorphologyFilter(BasicMorphologyOp op
, sal_Int32 nRadius
,
323 sal_uInt8 nValueOutside
)
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
);
354 case ScanlineFormat::N32BitTcMask
:
355 case ScanlineFormat::N32BitTcBgra
:
356 runFilter
<32>(bitmapCopy
, m_eOp
, m_nRadius
, m_bUseValueOutside
, m_nValueOutside
);
358 case ScanlineFormat::N8BitPal
:
359 runFilter
<8>(bitmapCopy
, m_eOp
, m_nRadius
, m_bUseValueOutside
, m_nValueOutside
);
361 // TODO: handle 1-bit images
363 // Use access' GetColor/SetPixel fallback
364 runFilter
<0>(bitmapCopy
, m_eOp
, m_nRadius
, m_bUseValueOutside
, m_nValueOutside
);
371 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */