Krita Source Code Documentation
Loading...
Searching...
No Matches
kis_convolution_worker_spatial.h
Go to the documentation of this file.
1/*
2 * SPDX-FileCopyrightText: 2005, 2008, 2010 Cyrille Berger <cberger@cberger.net>
3 * SPDX-FileCopyrightText: 2009, 2010 Edward Apap <schumifer@hotmail.com>
4 *
5 * SPDX-License-Identifier: GPL-2.0-or-later
6 */
7
8#ifndef KIS_CONVOLUTION_WORKER_SPATIAL_H
9#define KIS_CONVOLUTION_WORKER_SPATIAL_H
10
12#include "kis_math_toolbox.h"
13
14template <class _IteratorFactory_>
15class KisConvolutionWorkerSpatial : public KisConvolutionWorker<_IteratorFactory_>
16{
17public:
19 : KisConvolutionWorker<_IteratorFactory_>(painter, progress)
20 , m_alphaCachePos(-1)
21 , m_alphaRealPos(-1)
24 , m_minClamp(0)
25 , m_maxClamp(0)
27 {
28 }
29
32
33 inline void loadPixelToCache(qreal **cache, const quint8 *data, int index) {
34 // no alpha is rare case, so just multiply by 1.0 in that case
35 qreal alphaValue = m_alphaRealPos >= 0 ?
37
38 for (quint32 k = 0; k < m_convolveChannelsNo; ++k) {
39 if (k != (quint32)m_alphaCachePos) {
40 const quint32 channelPos = m_convChannelList[k]->pos();
41 cache[index][k] = m_toDoubleFuncPtr[k](data, channelPos) * alphaValue;
42 } else {
43 cache[index][k] = alphaValue;
44 }
45 }
46
47 }
48
49 void execute(const KisConvolutionKernelSP kernel, const KisPaintDeviceSP src, QPoint srcPos, QPoint dstPos, QSize areaSize, const QRect& dataRect) override {
50 // store some kernel characteristics
51 m_kw = kernel->width();
52 m_kh = kernel->height();
53 m_khalfWidth = (m_kw > 0) ? (m_kw - 1) / 2 : m_kw;
54 m_khalfHeight = (m_kh > 0) ? (m_kh - 1) / 2 : m_kh;
56 m_pixelSize = src->colorSpace()->pixelSize();
57 quint32 channelCount = src->colorSpace()->channelCount();
58
59 m_kernelData = new qreal[m_cacheSize];
60 qreal *kernelDataPtr = m_kernelData;
61
62 // fill in data
63 for (quint32 r = 0; r < kernel->height(); r++) {
64 for (quint32 c = 0; c < kernel->width(); c++) {
65 *kernelDataPtr = (*(kernel->data()))(r, c);
66 kernelDataPtr++;
67 }
68 }
69
70 // Make the area we cover as small as possible
71 if (this->m_painter->selection()) {
72 QRect r = this->m_painter->selection()->selectedRect().intersected(QRect(srcPos, areaSize));
73 dstPos += r.topLeft() - srcPos;
74 srcPos = r.topLeft();
75 areaSize = r.size();
76 }
77
78 if (areaSize.width() == 0 || areaSize.height() == 0)
79 return;
80
81 // Don't convolve with an even sized kernel
82 Q_ASSERT((m_kw & 0x01) == 1 || (m_kh & 0x01) == 1 || kernel->factor() != 0);
83
84 // find out which channels need be convolved
87
88 for (int i = 0; i < m_convChannelList.size(); i++) {
89 if (m_convChannelList[i]->channelType() == KoChannelInfo::ALPHA) {
92 }
93 }
94
95 bool hasProgressUpdater = this->m_progress;
96 if (hasProgressUpdater)
97 this->m_progress->setProgress(0);
98
99 // Iterate over all pixels in our rect, create a cache of pixels around the current pixel and convolve them.
100 m_pixelPtrCache = new qreal*[m_cacheSize];
101 m_pixelPtrCacheCopy = new qreal*[m_cacheSize];
102 for (quint32 c = 0; c < m_cacheSize; ++c) {
103 m_pixelPtrCache[c] = new qreal[channelCount];
104 m_pixelPtrCacheCopy[c] = new qreal[channelCount];
105 }
106
107 // decide caching strategy
108 enum TraversingDirection { Horizontal, Vertical };
109 TraversingDirection traversingDirection = Vertical;
110 if (m_kw > m_kh) {
111 traversingDirection = Horizontal;
112 }
113
114 KisMathToolbox mathToolbox;
117 return;
118
121 return;
122
123 m_kernelFactor = kernel->factor() ? 1.0 / kernel->factor() : 1;
124 m_maxClamp = new qreal[m_convChannelList.count()];
125 m_minClamp = new qreal[m_convChannelList.count()];
126 m_absoluteOffset = new qreal[m_convChannelList.count()];
127 for (quint16 i = 0; i < m_convChannelList.count(); ++i) {
128 m_minClamp[i] = mathToolbox.minChannelValue(m_convChannelList[i]);
129 m_maxClamp[i] = mathToolbox.maxChannelValue(m_convChannelList[i]);
130 m_absoluteOffset[i] = (m_maxClamp[i] - m_minClamp[i]) * kernel->offset();
131 }
132
133 qint32 row = srcPos.y();
134 qint32 col = srcPos.x();
135
136 // populate pixelPtrCacheCopy for starting position (0, 0)
137 qint32 i = 0;
138 typename _IteratorFactory_::HLineConstIterator hitInitSrc = _IteratorFactory_::createHLineConstIterator(src, col - m_khalfWidth, row - m_khalfHeight, m_kw, dataRect);
139
140 for (quint32 krow = 0; krow < m_kh; ++krow) {
141 do {
142 const quint8* data = hitInitSrc->oldRawData();
144 ++i;
145 } while (hitInitSrc->nextPixel());
146 hitInitSrc->nextRow();
147 }
148
149
150 if (traversingDirection == Horizontal) {
151 if(hasProgressUpdater) {
152 this->m_progress->setRange(0, areaSize.height());
153 }
154 typename _IteratorFactory_::HLineIterator hitDst = _IteratorFactory_::createHLineIterator(this->m_painter->device(), dstPos.x(), dstPos.y(), areaSize.width(), dataRect);
155 typename _IteratorFactory_::HLineConstIterator hitSrc = _IteratorFactory_::createHLineConstIterator(src, srcPos.x(), srcPos.y(), areaSize.width(), dataRect);
156
157 typename _IteratorFactory_::HLineConstIterator khitSrc = _IteratorFactory_::createHLineConstIterator(src, col - m_khalfWidth, row + m_khalfHeight, m_kw, dataRect);
158 for (int prow = 0; prow < areaSize.height(); ++prow) {
159 // reload cache from copy
160 for (quint32 i = 0; i < m_cacheSize; ++i)
161 memcpy(m_pixelPtrCache[i], m_pixelPtrCacheCopy[i], channelCount * sizeof(qreal));
162
163 typename _IteratorFactory_::VLineConstIterator kitSrc = _IteratorFactory_::createVLineConstIterator(src, col + m_khalfWidth, row - m_khalfHeight, m_kh, dataRect);
164 for (int pcol = 0; pcol < areaSize.width(); ++pcol) {
165 // write original channel values
166 memcpy(hitDst->rawData(), hitSrc->oldRawData(), m_pixelSize);
167 convolveCache(hitDst->rawData());
168
169 ++col;
170 kitSrc->nextColumn();
171 hitDst->nextPixel();
172 hitSrc->nextPixel();
174 }
175
176 row++;
177 khitSrc->nextRow();
178 hitDst->nextRow();
179 hitSrc->nextRow();
180 col = srcPos.x();
181
183
184 if (hasProgressUpdater) {
185 this->m_progress->setValue(prow);
186
187 if (this->m_progress->interrupted()) {
188 cleanUp();
189 return;
190 }
191 }
192
193 }
194 } else /* if (traversingDirection == Vertical) */ {
195 if(hasProgressUpdater) {
196 this->m_progress->setRange(0, areaSize.width());
197 }
198 typename _IteratorFactory_::VLineIterator vitDst = _IteratorFactory_::createVLineIterator(this->m_painter->device(), dstPos.x(), dstPos.y(), areaSize.height(), dataRect);
199 typename _IteratorFactory_::VLineConstIterator vitSrc = _IteratorFactory_::createVLineConstIterator(src, srcPos.x(), srcPos.y(), areaSize.height(), dataRect);
200
201 typename _IteratorFactory_::VLineConstIterator kitSrc = _IteratorFactory_::createVLineConstIterator(src, col + m_khalfWidth, row - m_khalfHeight, m_kh, dataRect);
202 for (int pcol = 0; pcol < areaSize.width(); pcol++) {
203 // reload cache from copy
204 for (quint32 i = 0; i < m_cacheSize; ++i)
205 memcpy(m_pixelPtrCache[i], m_pixelPtrCacheCopy[i], channelCount * sizeof(qreal));
206
207 typename _IteratorFactory_::HLineConstIterator khitSrc = _IteratorFactory_::createHLineConstIterator(src, col - m_khalfWidth, row + m_khalfHeight, m_kw, dataRect);
208 for (int prow = 0; prow < areaSize.height(); prow++) {
209 // write original channel values
210 memcpy(vitDst->rawData(), vitSrc->oldRawData(), m_pixelSize);
211 convolveCache(vitDst->rawData());
212
213 ++row;
214 khitSrc->nextRow();
215 vitDst->nextPixel();
216 vitSrc->nextPixel();
218 }
219
220 ++col;
221 kitSrc->nextColumn();
222 vitDst->nextColumn();
223 vitSrc->nextColumn();
224 row = srcPos.y();
225
227
228 if (hasProgressUpdater) {
229 this->m_progress->setValue(pcol);
230
231 if (this->m_progress->interrupted()) {
232 cleanUp();
233 return;
234 }
235 }
236 }
237 }
238 cleanUp();
239 }
240
241 inline void limitValue(qreal *value, qreal lowBound, qreal highBound) {
242 if (*value > highBound) {
243 *value = highBound;
244 } else if (!(*value >= lowBound)) { // value < lowBound or value == NaN
245 // IEEE compliant comparisons with NaN are always false
246 *value = lowBound;
247 }
248 }
249
250 template <bool additionalMultiplierActive>
251 inline qreal convolveOneChannelFromCache(quint8* dstPtr, quint32 channel, qreal additionalMultiplier = 0.0) {
252 qreal interimConvoResult = 0;
253
254 for (quint32 pIndex = 0; pIndex < m_cacheSize; ++pIndex) {
255 qreal cacheValue = m_pixelPtrCache[pIndex][channel];
256 interimConvoResult += m_kernelData[m_cacheSize - pIndex - 1] * cacheValue;
257 }
258
259 qreal channelPixelValue;
260 if (additionalMultiplierActive) {
261 channelPixelValue = interimConvoResult * m_kernelFactor * additionalMultiplier + m_absoluteOffset[channel];
262 } else {
263 channelPixelValue = interimConvoResult * m_kernelFactor + m_absoluteOffset[channel];
264 }
265
266 limitValue(&channelPixelValue, m_minClamp[channel], m_maxClamp[channel]);
267
268 const quint32 channelPos = m_convChannelList[channel]->pos();
269 m_fromDoubleFuncPtr[channel](dstPtr, channelPos, channelPixelValue);
270
271 return channelPixelValue;
272 }
273
274 inline void convolveCache(quint8* dstPtr) {
275 if (m_alphaCachePos >= 0) {
276 qreal alphaValue = convolveOneChannelFromCache<false>(dstPtr, m_alphaCachePos);
277
278 // TODO: we need a special case for applying LoG filter,
279 // when the alpha i suniform and therefore should not be
280 // filtered!
281 //alphaValue = 255.0;
282
283 if (alphaValue != 0.0) {
284 qreal alphaValueInv = 1.0 / alphaValue;
285
286 for (quint32 k = 0; k < m_convolveChannelsNo; ++k) {
287 if (k == (quint32)m_alphaCachePos) continue;
288 convolveOneChannelFromCache<true>(dstPtr, k, alphaValueInv);
289 }
290 } else {
291 for (quint32 k = 0; k < m_convolveChannelsNo; ++k) {
292 if (k == (quint32)m_alphaCachePos) continue;
293
294 const qreal zeroValue = 0.0;
295 const quint32 channelPos = m_convChannelList[k]->pos();
296 m_fromDoubleFuncPtr[k](dstPtr, channelPos, zeroValue);
297 }
298 }
299 } else {
300 for (quint32 k = 0; k < m_convolveChannelsNo; ++k) {
301 convolveOneChannelFromCache<false>(dstPtr, k);
302 }
303 }
304 }
305
306 inline void moveKernelRight(typename _IteratorFactory_::VLineConstIterator& kitSrc, qreal **pixelPtrCache) {
307 qreal** d = pixelPtrCache;
308
309 for (quint32 krow = 0; krow < m_kh; ++krow) {
310 qreal* first = *d;
311 memmove(d, d + 1, (m_kw - 1) * sizeof(qreal *));
312 *(d + m_kw - 1) = first;
313 d += m_kw;
314 }
315
316 qint32 i = m_kw - 1;
317 do {
318 const quint8* data = kitSrc->oldRawData();
319 loadPixelToCache(pixelPtrCache, data, i);
320 i += m_kw;
321 } while (kitSrc->nextPixel());
322 }
323
324 inline void moveKernelDown(typename _IteratorFactory_::HLineConstIterator& kitSrc, qreal **pixelPtrCache) {
325 quint8 **tmp = new quint8*[m_kw];
326 memcpy(tmp, pixelPtrCache, m_kw * sizeof(qreal *));
327 memmove(pixelPtrCache, pixelPtrCache + m_kw, (m_kw * m_kh - m_kw) * sizeof(quint8 *));
328 memcpy(pixelPtrCache + m_kw *(m_kh - 1), tmp, m_kw * sizeof(quint8 *));
329 delete[] tmp;
330
331 qint32 i = m_kw * (m_kh - 1);
332 do {
333 const quint8* data = kitSrc->oldRawData();
334 loadPixelToCache(pixelPtrCache, data, i);
335 i++;
336 } while (kitSrc->nextPixel());
337 }
338
339 void cleanUp() {
340 for (quint32 c = 0; c < m_cacheSize; ++c) {
341 delete[] m_pixelPtrCache[c];
342 delete[] m_pixelPtrCacheCopy[c];
343 }
344
345 delete[] m_kernelData;
346 delete[] m_pixelPtrCache;
347 delete[] m_pixelPtrCacheCopy;
348
349 delete[] m_minClamp;
350 delete[] m_maxClamp;
351 delete[] m_absoluteOffset;
352 }
353
354private:
355 quint32 m_kw, m_kh;
359
362
366
371};
372
373
374#endif
float value(const T *src, size_t ch)
KisConvolutionWorkerSpatial(KisPainter *painter, KoUpdater *progress)
void loadPixelToCache(qreal **cache, const quint8 *data, int index)
void moveKernelRight(typename _IteratorFactory_::VLineConstIterator &kitSrc, qreal **pixelPtrCache)
qreal convolveOneChannelFromCache(quint8 *dstPtr, quint32 channel, qreal additionalMultiplier=0.0)
void limitValue(qreal *value, qreal lowBound, qreal highBound)
void moveKernelDown(typename _IteratorFactory_::HLineConstIterator &kitSrc, qreal **pixelPtrCache)
void execute(const KisConvolutionKernelSP kernel, const KisPaintDeviceSP src, QPoint srcPos, QPoint dstPos, QSize areaSize, const QRect &dataRect) override
QList< KoChannelInfo * > convolvableChannelList(const KisPaintDeviceSP src)
bool getToDoubleChannelPtr(QList< KoChannelInfo * > cis, QVector< PtrToDouble > &f)
bool getFromDoubleChannelPtr(QList< KoChannelInfo * > cis, QVector< PtrFromDouble > &f)
double minChannelValue(KoChannelInfo *)
double maxChannelValue(KoChannelInfo *)
KisSelectionSP selection
KisPaintDeviceSP device
@ ALPHA
The channel represents the opacity of a pixel.
bool interrupted() const
Definition KoUpdater.cpp:54
void setProgress(int percent)
Definition KoUpdater.cpp:38
void setValue(int value) override
Definition KoUpdater.cpp:64
void setRange(int minimum, int maximum) override
Definition KoUpdater.cpp:79
Eigen::Matrix< qreal, Eigen::Dynamic, Eigen::Dynamic > data
QRect selectedRect() const