File indexing completed on 2024-05-12 15:58:13
0001 /* 0002 * SPDX-FileCopyrightText: 2005, 2008, 2010 Cyrille Berger <cberger@cberger.net> 0003 * SPDX-FileCopyrightText: 2009, 2010 Edward Apap <schumifer@hotmail.com> 0004 * 0005 * SPDX-License-Identifier: GPL-2.0-or-later 0006 */ 0007 0008 #ifndef KIS_CONVOLUTION_WORKER_SPATIAL_H 0009 #define KIS_CONVOLUTION_WORKER_SPATIAL_H 0010 0011 #include "kis_convolution_worker.h" 0012 #include "kis_math_toolbox.h" 0013 0014 template <class _IteratorFactory_> 0015 class KisConvolutionWorkerSpatial : public KisConvolutionWorker<_IteratorFactory_> 0016 { 0017 public: 0018 KisConvolutionWorkerSpatial(KisPainter *painter, KoUpdater *progress) 0019 : KisConvolutionWorker<_IteratorFactory_>(painter, progress) 0020 , m_alphaCachePos(-1) 0021 , m_alphaRealPos(-1) 0022 , m_pixelPtrCache(0) 0023 , m_pixelPtrCacheCopy(0) 0024 , m_minClamp(0) 0025 , m_maxClamp(0) 0026 , m_absoluteOffset(0) 0027 { 0028 } 0029 0030 ~KisConvolutionWorkerSpatial() override { 0031 } 0032 0033 inline void loadPixelToCache(qreal **cache, const quint8 *data, int index) { 0034 // no alpha is rare case, so just multiply by 1.0 in that case 0035 qreal alphaValue = m_alphaRealPos >= 0 ? 0036 m_toDoubleFuncPtr[m_alphaCachePos](data, m_alphaRealPos) : 1.0; 0037 0038 for (quint32 k = 0; k < m_convolveChannelsNo; ++k) { 0039 if (k != (quint32)m_alphaCachePos) { 0040 const quint32 channelPos = m_convChannelList[k]->pos(); 0041 cache[index][k] = m_toDoubleFuncPtr[k](data, channelPos) * alphaValue; 0042 } else { 0043 cache[index][k] = alphaValue; 0044 } 0045 } 0046 0047 } 0048 0049 void execute(const KisConvolutionKernelSP kernel, const KisPaintDeviceSP src, QPoint srcPos, QPoint dstPos, QSize areaSize, const QRect& dataRect) override { 0050 // store some kernel characteristics 0051 m_kw = kernel->width(); 0052 m_kh = kernel->height(); 0053 m_khalfWidth = (m_kw > 0) ? (m_kw - 1) / 2 : m_kw; 0054 m_khalfHeight = (m_kh > 0) ? (m_kh - 1) / 2 : m_kh; 0055 m_cacheSize = m_kw * m_kh; 0056 m_pixelSize = src->colorSpace()->pixelSize(); 0057 quint32 channelCount = src->colorSpace()->channelCount(); 0058 0059 m_kernelData = new qreal[m_cacheSize]; 0060 qreal *kernelDataPtr = m_kernelData; 0061 0062 // fill in data 0063 for (quint32 r = 0; r < kernel->height(); r++) { 0064 for (quint32 c = 0; c < kernel->width(); c++) { 0065 *kernelDataPtr = (*(kernel->data()))(r, c); 0066 kernelDataPtr++; 0067 } 0068 } 0069 0070 // Make the area we cover as small as possible 0071 if (this->m_painter->selection()) { 0072 QRect r = this->m_painter->selection()->selectedRect().intersected(QRect(srcPos, areaSize)); 0073 dstPos += r.topLeft() - srcPos; 0074 srcPos = r.topLeft(); 0075 areaSize = r.size(); 0076 } 0077 0078 if (areaSize.width() == 0 || areaSize.height() == 0) 0079 return; 0080 0081 // Don't convolve with an even sized kernel 0082 Q_ASSERT((m_kw & 0x01) == 1 || (m_kh & 0x01) == 1 || kernel->factor() != 0); 0083 0084 // find out which channels need be convolved 0085 m_convChannelList = this->convolvableChannelList(src); 0086 m_convolveChannelsNo = m_convChannelList.count(); 0087 0088 for (int i = 0; i < m_convChannelList.size(); i++) { 0089 if (m_convChannelList[i]->channelType() == KoChannelInfo::ALPHA) { 0090 m_alphaCachePos = i; 0091 m_alphaRealPos = m_convChannelList[i]->pos(); 0092 } 0093 } 0094 0095 bool hasProgressUpdater = this->m_progress; 0096 if (hasProgressUpdater) 0097 this->m_progress->setProgress(0); 0098 0099 // Iterate over all pixels in our rect, create a cache of pixels around the current pixel and convolve them. 0100 m_pixelPtrCache = new qreal*[m_cacheSize]; 0101 m_pixelPtrCacheCopy = new qreal*[m_cacheSize]; 0102 for (quint32 c = 0; c < m_cacheSize; ++c) { 0103 m_pixelPtrCache[c] = new qreal[channelCount]; 0104 m_pixelPtrCacheCopy[c] = new qreal[channelCount]; 0105 } 0106 0107 // decide caching strategy 0108 enum TraversingDirection { Horizontal, Vertical }; 0109 TraversingDirection traversingDirection = Vertical; 0110 if (m_kw > m_kh) { 0111 traversingDirection = Horizontal; 0112 } 0113 0114 KisMathToolbox mathToolbox; 0115 m_toDoubleFuncPtr = QVector<PtrToDouble>(m_convolveChannelsNo); 0116 if (!mathToolbox.getToDoubleChannelPtr(m_convChannelList, m_toDoubleFuncPtr)) 0117 return; 0118 0119 m_fromDoubleFuncPtr = QVector<PtrFromDouble>(m_convolveChannelsNo); 0120 if (!mathToolbox.getFromDoubleChannelPtr(m_convChannelList, m_fromDoubleFuncPtr)) 0121 return; 0122 0123 m_kernelFactor = kernel->factor() ? 1.0 / kernel->factor() : 1; 0124 m_maxClamp = new qreal[m_convChannelList.count()]; 0125 m_minClamp = new qreal[m_convChannelList.count()]; 0126 m_absoluteOffset = new qreal[m_convChannelList.count()]; 0127 for (quint16 i = 0; i < m_convChannelList.count(); ++i) { 0128 m_minClamp[i] = mathToolbox.minChannelValue(m_convChannelList[i]); 0129 m_maxClamp[i] = mathToolbox.maxChannelValue(m_convChannelList[i]); 0130 m_absoluteOffset[i] = (m_maxClamp[i] - m_minClamp[i]) * kernel->offset(); 0131 } 0132 0133 qint32 row = srcPos.y(); 0134 qint32 col = srcPos.x(); 0135 0136 // populate pixelPtrCacheCopy for starting position (0, 0) 0137 qint32 i = 0; 0138 typename _IteratorFactory_::HLineConstIterator hitInitSrc = _IteratorFactory_::createHLineConstIterator(src, col - m_khalfWidth, row - m_khalfHeight, m_kw, dataRect); 0139 0140 for (quint32 krow = 0; krow < m_kh; ++krow) { 0141 do { 0142 const quint8* data = hitInitSrc->oldRawData(); 0143 loadPixelToCache(m_pixelPtrCacheCopy, data, i); 0144 ++i; 0145 } while (hitInitSrc->nextPixel()); 0146 hitInitSrc->nextRow(); 0147 } 0148 0149 0150 if (traversingDirection == Horizontal) { 0151 if(hasProgressUpdater) { 0152 this->m_progress->setRange(0, areaSize.height()); 0153 } 0154 typename _IteratorFactory_::HLineIterator hitDst = _IteratorFactory_::createHLineIterator(this->m_painter->device(), dstPos.x(), dstPos.y(), areaSize.width(), dataRect); 0155 typename _IteratorFactory_::HLineConstIterator hitSrc = _IteratorFactory_::createHLineConstIterator(src, srcPos.x(), srcPos.y(), areaSize.width(), dataRect); 0156 0157 typename _IteratorFactory_::HLineConstIterator khitSrc = _IteratorFactory_::createHLineConstIterator(src, col - m_khalfWidth, row + m_khalfHeight, m_kw, dataRect); 0158 for (int prow = 0; prow < areaSize.height(); ++prow) { 0159 // reload cache from copy 0160 for (quint32 i = 0; i < m_cacheSize; ++i) 0161 memcpy(m_pixelPtrCache[i], m_pixelPtrCacheCopy[i], channelCount * sizeof(qreal)); 0162 0163 typename _IteratorFactory_::VLineConstIterator kitSrc = _IteratorFactory_::createVLineConstIterator(src, col + m_khalfWidth, row - m_khalfHeight, m_kh, dataRect); 0164 for (int pcol = 0; pcol < areaSize.width(); ++pcol) { 0165 // write original channel values 0166 memcpy(hitDst->rawData(), hitSrc->oldRawData(), m_pixelSize); 0167 convolveCache(hitDst->rawData()); 0168 0169 ++col; 0170 kitSrc->nextColumn(); 0171 hitDst->nextPixel(); 0172 hitSrc->nextPixel(); 0173 moveKernelRight(kitSrc, m_pixelPtrCache); 0174 } 0175 0176 row++; 0177 khitSrc->nextRow(); 0178 hitDst->nextRow(); 0179 hitSrc->nextRow(); 0180 col = srcPos.x(); 0181 0182 moveKernelDown(khitSrc, m_pixelPtrCacheCopy); 0183 0184 if (hasProgressUpdater) { 0185 this->m_progress->setValue(prow); 0186 0187 if (this->m_progress->interrupted()) { 0188 cleanUp(); 0189 return; 0190 } 0191 } 0192 0193 } 0194 } else /* if (traversingDirection == Vertical) */ { 0195 if(hasProgressUpdater) { 0196 this->m_progress->setRange(0, areaSize.width()); 0197 } 0198 typename _IteratorFactory_::VLineIterator vitDst = _IteratorFactory_::createVLineIterator(this->m_painter->device(), dstPos.x(), dstPos.y(), areaSize.height(), dataRect); 0199 typename _IteratorFactory_::VLineConstIterator vitSrc = _IteratorFactory_::createVLineConstIterator(src, srcPos.x(), srcPos.y(), areaSize.height(), dataRect); 0200 0201 typename _IteratorFactory_::VLineConstIterator kitSrc = _IteratorFactory_::createVLineConstIterator(src, col + m_khalfWidth, row - m_khalfHeight, m_kh, dataRect); 0202 for (int pcol = 0; pcol < areaSize.width(); pcol++) { 0203 // reload cache from copy 0204 for (quint32 i = 0; i < m_cacheSize; ++i) 0205 memcpy(m_pixelPtrCache[i], m_pixelPtrCacheCopy[i], channelCount * sizeof(qreal)); 0206 0207 typename _IteratorFactory_::HLineConstIterator khitSrc = _IteratorFactory_::createHLineConstIterator(src, col - m_khalfWidth, row + m_khalfHeight, m_kw, dataRect); 0208 for (int prow = 0; prow < areaSize.height(); prow++) { 0209 // write original channel values 0210 memcpy(vitDst->rawData(), vitSrc->oldRawData(), m_pixelSize); 0211 convolveCache(vitDst->rawData()); 0212 0213 ++row; 0214 khitSrc->nextRow(); 0215 vitDst->nextPixel(); 0216 vitSrc->nextPixel(); 0217 moveKernelDown(khitSrc, m_pixelPtrCache); 0218 } 0219 0220 ++col; 0221 kitSrc->nextColumn(); 0222 vitDst->nextColumn(); 0223 vitSrc->nextColumn(); 0224 row = srcPos.y(); 0225 0226 moveKernelRight(kitSrc, m_pixelPtrCacheCopy); 0227 0228 if (hasProgressUpdater) { 0229 this->m_progress->setValue(pcol); 0230 0231 if (this->m_progress->interrupted()) { 0232 cleanUp(); 0233 return; 0234 } 0235 } 0236 } 0237 } 0238 cleanUp(); 0239 } 0240 0241 inline void limitValue(qreal *value, qreal lowBound, qreal highBound) { 0242 if (*value > highBound) { 0243 *value = highBound; 0244 } else if (!(*value >= lowBound)) { // value < lowBound or value == NaN 0245 // IEEE compliant comparisons with NaN are always false 0246 *value = lowBound; 0247 } 0248 } 0249 0250 template <bool additionalMultiplierActive> 0251 inline qreal convolveOneChannelFromCache(quint8* dstPtr, quint32 channel, qreal additionalMultiplier = 0.0) { 0252 qreal interimConvoResult = 0; 0253 0254 for (quint32 pIndex = 0; pIndex < m_cacheSize; ++pIndex) { 0255 qreal cacheValue = m_pixelPtrCache[pIndex][channel]; 0256 interimConvoResult += m_kernelData[m_cacheSize - pIndex - 1] * cacheValue; 0257 } 0258 0259 qreal channelPixelValue; 0260 if (additionalMultiplierActive) { 0261 channelPixelValue = interimConvoResult * m_kernelFactor * additionalMultiplier + m_absoluteOffset[channel]; 0262 } else { 0263 channelPixelValue = interimConvoResult * m_kernelFactor + m_absoluteOffset[channel]; 0264 } 0265 0266 limitValue(&channelPixelValue, m_minClamp[channel], m_maxClamp[channel]); 0267 0268 const quint32 channelPos = m_convChannelList[channel]->pos(); 0269 m_fromDoubleFuncPtr[channel](dstPtr, channelPos, channelPixelValue); 0270 0271 return channelPixelValue; 0272 } 0273 0274 inline void convolveCache(quint8* dstPtr) { 0275 if (m_alphaCachePos >= 0) { 0276 qreal alphaValue = convolveOneChannelFromCache<false>(dstPtr, m_alphaCachePos); 0277 0278 // TODO: we need a special case for applying LoG filter, 0279 // when the alpha i suniform and therefore should not be 0280 // filtered! 0281 //alphaValue = 255.0; 0282 0283 if (alphaValue != 0.0) { 0284 qreal alphaValueInv = 1.0 / alphaValue; 0285 0286 for (quint32 k = 0; k < m_convolveChannelsNo; ++k) { 0287 if (k == (quint32)m_alphaCachePos) continue; 0288 convolveOneChannelFromCache<true>(dstPtr, k, alphaValueInv); 0289 } 0290 } else { 0291 for (quint32 k = 0; k < m_convolveChannelsNo; ++k) { 0292 if (k == (quint32)m_alphaCachePos) continue; 0293 0294 const qreal zeroValue = 0.0; 0295 const quint32 channelPos = m_convChannelList[k]->pos(); 0296 m_fromDoubleFuncPtr[k](dstPtr, channelPos, zeroValue); 0297 } 0298 } 0299 } else { 0300 for (quint32 k = 0; k < m_convolveChannelsNo; ++k) { 0301 convolveOneChannelFromCache<false>(dstPtr, k); 0302 } 0303 } 0304 } 0305 0306 inline void moveKernelRight(typename _IteratorFactory_::VLineConstIterator& kitSrc, qreal **pixelPtrCache) { 0307 qreal** d = pixelPtrCache; 0308 0309 for (quint32 krow = 0; krow < m_kh; ++krow) { 0310 qreal* first = *d; 0311 memmove(d, d + 1, (m_kw - 1) * sizeof(qreal *)); 0312 *(d + m_kw - 1) = first; 0313 d += m_kw; 0314 } 0315 0316 qint32 i = m_kw - 1; 0317 do { 0318 const quint8* data = kitSrc->oldRawData(); 0319 loadPixelToCache(pixelPtrCache, data, i); 0320 i += m_kw; 0321 } while (kitSrc->nextPixel()); 0322 } 0323 0324 inline void moveKernelDown(typename _IteratorFactory_::HLineConstIterator& kitSrc, qreal **pixelPtrCache) { 0325 quint8 **tmp = new quint8*[m_kw]; 0326 memcpy(tmp, pixelPtrCache, m_kw * sizeof(qreal *)); 0327 memmove(pixelPtrCache, pixelPtrCache + m_kw, (m_kw * m_kh - m_kw) * sizeof(quint8 *)); 0328 memcpy(pixelPtrCache + m_kw *(m_kh - 1), tmp, m_kw * sizeof(quint8 *)); 0329 delete[] tmp; 0330 0331 qint32 i = m_kw * (m_kh - 1); 0332 do { 0333 const quint8* data = kitSrc->oldRawData(); 0334 loadPixelToCache(pixelPtrCache, data, i); 0335 i++; 0336 } while (kitSrc->nextPixel()); 0337 } 0338 0339 void cleanUp() { 0340 for (quint32 c = 0; c < m_cacheSize; ++c) { 0341 delete[] m_pixelPtrCache[c]; 0342 delete[] m_pixelPtrCacheCopy[c]; 0343 } 0344 0345 delete[] m_kernelData; 0346 delete[] m_pixelPtrCache; 0347 delete[] m_pixelPtrCacheCopy; 0348 0349 delete[] m_minClamp; 0350 delete[] m_maxClamp; 0351 delete[] m_absoluteOffset; 0352 } 0353 0354 private: 0355 quint32 m_kw, m_kh; 0356 quint32 m_khalfWidth, m_khalfHeight; 0357 quint32 m_convolveChannelsNo; 0358 quint32 m_cacheSize, m_pixelSize; 0359 0360 int m_alphaCachePos; 0361 int m_alphaRealPos; 0362 0363 qreal *m_kernelData; 0364 qreal** m_pixelPtrCache, ** m_pixelPtrCacheCopy; 0365 qreal* m_minClamp, *m_maxClamp, *m_absoluteOffset; 0366 0367 qreal m_kernelFactor; 0368 QList<KoChannelInfo *> m_convChannelList; 0369 QVector<PtrToDouble> m_toDoubleFuncPtr; 0370 QVector<PtrFromDouble> m_fromDoubleFuncPtr; 0371 }; 0372 0373 0374 #endif