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