File indexing completed on 2024-05-12 15:58:17

0001 /*
0002  *  SPDX-FileCopyrightText: 2014 Dmitry Kazakov <dimula73@gmail.com>
0003  *
0004  *  SPDX-License-Identifier: GPL-2.0-or-later
0005  */
0006 
0007 #include "kis_gaussian_kernel.h"
0008 
0009 #include "kis_global.h"
0010 #include "kis_convolution_kernel.h"
0011 #include <kis_convolution_painter.h>
0012 #include <kis_transaction.h>
0013 #include <QRect>
0014 
0015 
0016 qreal KisGaussianKernel::sigmaFromRadius(qreal radius)
0017 {
0018     return 0.3 * radius + 0.3;
0019 }
0020 
0021 int KisGaussianKernel::kernelSizeFromRadius(qreal radius)
0022 {
0023     return 6 * ceil(sigmaFromRadius(radius)) + 1;
0024 }
0025 
0026 
0027 Eigen::Matrix<qreal, Eigen::Dynamic, Eigen::Dynamic>
0028 KisGaussianKernel::createHorizontalMatrix(qreal radius)
0029 {
0030     int kernelSize = kernelSizeFromRadius(radius);
0031     Eigen::Matrix<qreal, Eigen::Dynamic, Eigen::Dynamic> matrix(1, kernelSize);
0032 
0033     const qreal sigma = sigmaFromRadius(radius);
0034     const qreal multiplicand = 1 / (sqrt(2 * M_PI * sigma * sigma));
0035     const qreal exponentMultiplicand = 1 / (2 * sigma * sigma);
0036 
0037     /**
0038      * The kernel size should always be odd, then the position of the
0039      * central pixel can be easily calculated
0040      */
0041     KIS_ASSERT_RECOVER_NOOP(kernelSize & 0x1);
0042     const int center = kernelSize / 2;
0043 
0044     for (int x = 0; x < kernelSize; x++) {
0045         qreal xDistance = center - x;
0046         matrix(0, x) = multiplicand * exp( -xDistance * xDistance * exponentMultiplicand );
0047     }
0048 
0049     return matrix;
0050 }
0051 
0052 Eigen::Matrix<qreal, Eigen::Dynamic, Eigen::Dynamic>
0053 KisGaussianKernel::createVerticalMatrix(qreal radius)
0054 {
0055     int kernelSize = kernelSizeFromRadius(radius);
0056     Eigen::Matrix<qreal, Eigen::Dynamic, Eigen::Dynamic> matrix(kernelSize, 1);
0057 
0058     const qreal sigma = sigmaFromRadius(radius);
0059     const qreal multiplicand = 1 / (sqrt(2 * M_PI * sigma * sigma));
0060     const qreal exponentMultiplicand = 1 / (2 * sigma * sigma);
0061 
0062     /**
0063      * The kernel size should always be odd, then the position of the
0064      * central pixel can be easily calculated
0065      */
0066     KIS_ASSERT_RECOVER_NOOP(kernelSize & 0x1);
0067     const int center = kernelSize / 2;
0068 
0069     for (int y = 0; y < kernelSize; y++) {
0070         qreal yDistance = center - y;
0071         matrix(y, 0) = multiplicand * exp( -yDistance * yDistance * exponentMultiplicand );
0072     }
0073 
0074     return matrix;
0075 }
0076 
0077 KisConvolutionKernelSP
0078 KisGaussianKernel::createHorizontalKernel(qreal radius)
0079 {
0080     Eigen::Matrix<qreal, Eigen::Dynamic, Eigen::Dynamic> matrix = createHorizontalMatrix(radius);
0081     return KisConvolutionKernel::fromMatrix(matrix, 0, matrix.sum());
0082 }
0083 
0084 KisConvolutionKernelSP
0085 KisGaussianKernel::createVerticalKernel(qreal radius)
0086 {
0087     Eigen::Matrix<qreal, Eigen::Dynamic, Eigen::Dynamic> matrix = createVerticalMatrix(radius);
0088     return KisConvolutionKernel::fromMatrix(matrix, 0, matrix.sum());
0089 }
0090 
0091 KisConvolutionKernelSP
0092 KisGaussianKernel::createUniform2DKernel(qreal xRadius, qreal yRadius)
0093 {
0094     Eigen::Matrix<qreal, Eigen::Dynamic, Eigen::Dynamic> h = createHorizontalMatrix(xRadius);
0095     Eigen::Matrix<qreal, Eigen::Dynamic, Eigen::Dynamic> v = createVerticalMatrix(yRadius);
0096 
0097     Eigen::Matrix<qreal, Eigen::Dynamic, Eigen::Dynamic> uni = v * h;
0098     return KisConvolutionKernel::fromMatrix(uni, 0, uni.sum());
0099 }
0100 
0101 
0102 void KisGaussianKernel::applyGaussian(KisPaintDeviceSP device,
0103                                       const QRect& rect,
0104                                       qreal xRadius, qreal yRadius,
0105                                       const QBitArray &channelFlags,
0106                                       KoUpdater *progressUpdater,
0107                                       bool createTransaction,
0108                                       KisConvolutionBorderOp borderOp)
0109 {
0110     QPoint srcTopLeft = rect.topLeft();
0111 
0112 
0113     if (KisConvolutionPainter::supportsFFTW()) {
0114         KisConvolutionPainter painter(device, KisConvolutionPainter::FFTW);
0115         painter.setChannelFlags(channelFlags);
0116         painter.setProgress(progressUpdater);
0117 
0118         KisConvolutionKernelSP kernel2D = KisGaussianKernel::createUniform2DKernel(xRadius, yRadius);
0119 
0120         QScopedPointer<KisTransaction> transaction;
0121         if (createTransaction && painter.needsTransaction(kernel2D)) {
0122             transaction.reset(new KisTransaction(device));
0123         }
0124 
0125         painter.applyMatrix(kernel2D, device, srcTopLeft, srcTopLeft, rect.size(), borderOp);
0126 
0127     } else if (xRadius > 0.0 && yRadius > 0.0) {
0128         KisPaintDeviceSP interm = new KisPaintDevice(device->colorSpace());
0129         interm->prepareClone(device);
0130 
0131         KisConvolutionKernelSP kernelHoriz = KisGaussianKernel::createHorizontalKernel(xRadius);
0132         KisConvolutionKernelSP kernelVertical = KisGaussianKernel::createVerticalKernel(yRadius);
0133 
0134         qreal verticalCenter = qreal(kernelVertical->height()) / 2.0;
0135 
0136         KisConvolutionPainter horizPainter(interm);
0137         horizPainter.setChannelFlags(channelFlags);
0138         horizPainter.setProgress(progressUpdater);
0139         horizPainter.applyMatrix(kernelHoriz, device,
0140                                  srcTopLeft - QPoint(0, ceil(verticalCenter)),
0141                                  srcTopLeft - QPoint(0, ceil(verticalCenter)),
0142                                  rect.size() + QSize(0, 2 * ceil(verticalCenter)), borderOp);
0143 
0144 
0145         KisConvolutionPainter verticalPainter(device);
0146         verticalPainter.setChannelFlags(channelFlags);
0147         verticalPainter.setProgress(progressUpdater);
0148         verticalPainter.applyMatrix(kernelVertical, interm, srcTopLeft, srcTopLeft, rect.size(), borderOp);
0149 
0150     } else if (xRadius > 0.0) {
0151         KisConvolutionPainter painter(device);
0152         painter.setChannelFlags(channelFlags);
0153         painter.setProgress(progressUpdater);
0154 
0155         KisConvolutionKernelSP kernelHoriz = KisGaussianKernel::createHorizontalKernel(xRadius);
0156 
0157         QScopedPointer<KisTransaction> transaction;
0158         if (createTransaction && painter.needsTransaction(kernelHoriz)) {
0159             transaction.reset(new KisTransaction(device));
0160         }
0161 
0162         painter.applyMatrix(kernelHoriz, device, srcTopLeft, srcTopLeft, rect.size(), borderOp);
0163 
0164     } else if (yRadius > 0.0) {
0165         KisConvolutionPainter painter(device);
0166         painter.setChannelFlags(channelFlags);
0167         painter.setProgress(progressUpdater);
0168 
0169         KisConvolutionKernelSP kernelVertical = KisGaussianKernel::createVerticalKernel(yRadius);
0170 
0171         QScopedPointer<KisTransaction> transaction;
0172         if (createTransaction && painter.needsTransaction(kernelVertical)) {
0173             transaction.reset(new KisTransaction(device));
0174         }
0175 
0176         painter.applyMatrix(kernelVertical, device, srcTopLeft, srcTopLeft, rect.size(), borderOp);
0177     }
0178 }
0179 
0180 Eigen::Matrix<qreal, Eigen::Dynamic, Eigen::Dynamic>
0181 KisGaussianKernel::createLoGMatrix(qreal radius, qreal coeff, bool zeroCentered, bool includeWrappedArea)
0182 {
0183     int kernelSize = 2 * (includeWrappedArea ? 2 : 1) * std::ceil(radius) + 1;
0184     Eigen::Matrix<qreal, Eigen::Dynamic, Eigen::Dynamic> matrix(kernelSize, kernelSize);
0185 
0186     const qreal sigma = radius/* / sqrt(2)*/;
0187     const qreal multiplicand = -1.0 / (M_PI * pow2(pow2(sigma)));
0188     const qreal exponentMultiplicand = 1 / (2 * pow2(sigma));
0189 
0190     /**
0191      * The kernel size should always be odd, then the position of the
0192      * central pixel can be easily calculated
0193      */
0194     KIS_ASSERT_RECOVER_NOOP(kernelSize & 0x1);
0195     const int center = kernelSize / 2;
0196 
0197     for (int y = 0; y < kernelSize; y++) {
0198         const qreal yDistance = center - y;
0199         for (int x = 0; x < kernelSize; x++) {
0200             const qreal xDistance = center - x;
0201             const qreal distance = pow2(xDistance) + pow2(yDistance);
0202             const qreal normalizedDistance = exponentMultiplicand * distance;
0203 
0204             matrix(x, y) = multiplicand *
0205                 (1.0 - normalizedDistance) *
0206                 exp(-normalizedDistance);
0207         }
0208     }
0209 
0210     qreal lateral = matrix.sum() - matrix(center, center);
0211     matrix(center, center) = -lateral;
0212 
0213     qreal totalSum = 0;
0214 
0215     if (zeroCentered) {
0216         for (int y = 0; y < kernelSize; y++) {
0217             for (int x = 0; x < kernelSize; x++) {
0218                 const qreal value = matrix(x, y);
0219                 totalSum += value;
0220             }
0221         }
0222     }
0223 
0224     qreal positiveSum = 0;
0225     qreal sideSum = 0;
0226     qreal quarterSum = 0;
0227     totalSum = 0;
0228 
0229     const qreal offset = totalSum / pow2(qreal(kernelSize));
0230 
0231     for (int y = 0; y < kernelSize; y++) {
0232         for (int x = 0; x < kernelSize; x++) {
0233             qreal value = matrix(x, y);
0234             value -= offset;
0235             matrix(x, y) = value;
0236 
0237             if (value > 0) {
0238                 positiveSum += value;
0239             }
0240             if (x > center) {
0241                 sideSum += value;
0242             }
0243             if (x > center && y > center) {
0244                 quarterSum += value;
0245             }
0246             totalSum += value;
0247         }
0248     }
0249 
0250 
0251     const qreal scale = coeff * 2.0 / positiveSum;
0252     matrix *= scale;
0253     positiveSum *= scale;
0254     sideSum *= scale;
0255     quarterSum *= scale;
0256 
0257     //qDebug() << ppVar(positiveSum) << ppVar(sideSum) << ppVar(quarterSum);
0258     Q_UNUSED(sideSum);
0259     Q_UNUSED(quarterSum);
0260 
0261     return matrix;
0262 }
0263 
0264 void KisGaussianKernel::applyLoG(KisPaintDeviceSP device,
0265                                  const QRect& rect,
0266                                  qreal radius, qreal coeff,
0267                                  const QBitArray &channelFlags,
0268                                  KoUpdater *progressUpdater)
0269 {
0270     QPoint srcTopLeft = rect.topLeft();
0271 
0272     KisConvolutionPainter painter(device);
0273     painter.setChannelFlags(channelFlags);
0274     painter.setProgress(progressUpdater);
0275 
0276     Eigen::Matrix<qreal, Eigen::Dynamic, Eigen::Dynamic> matrix =
0277         createLoGMatrix(radius, coeff, false, true);
0278     KisConvolutionKernelSP kernel =
0279         KisConvolutionKernel::fromMatrix(matrix,
0280                                          0,
0281                                          0);
0282 
0283     painter.applyMatrix(kernel, device, srcTopLeft, srcTopLeft, rect.size(), BORDER_REPEAT);
0284 }
0285 
0286 void KisGaussianKernel::applyTightLoG(KisPaintDeviceSP device,
0287                                       const QRect& rect,
0288                                       qreal radius, qreal coeff,
0289                                       const QBitArray &channelFlags,
0290                                       KoUpdater *progressUpdater)
0291 {
0292     QPoint srcTopLeft = rect.topLeft();
0293 
0294     KisConvolutionPainter painter(device);
0295     painter.setChannelFlags(channelFlags);
0296     painter.setProgress(progressUpdater);
0297 
0298     Eigen::Matrix<qreal, Eigen::Dynamic, Eigen::Dynamic> matrix =
0299         createLoGMatrix(radius, coeff, true, false);
0300     KisConvolutionKernelSP kernel =
0301         KisConvolutionKernel::fromMatrix(matrix,
0302                                          0,
0303                                          0);
0304 
0305     painter.applyMatrix(kernel, device, srcTopLeft, srcTopLeft, rect.size(), BORDER_REPEAT);
0306 }
0307 
0308 Eigen::Matrix<qreal, Eigen::Dynamic, Eigen::Dynamic> KisGaussianKernel::createDilateMatrix(qreal radius)
0309 {
0310     const int kernelSize = 2 * std::ceil(radius) + 1;
0311     Eigen::Matrix<qreal, Eigen::Dynamic, Eigen::Dynamic> matrix(kernelSize, kernelSize);
0312 
0313     const qreal fadeStart = qMax(1.0, radius - 1.0);
0314 
0315     /**
0316      * The kernel size should always be odd, then the position of the
0317      * central pixel can be easily calculated
0318      */
0319     KIS_ASSERT_RECOVER_NOOP(kernelSize & 0x1);
0320     const int center = kernelSize / 2;
0321 
0322     for (int y = 0; y < kernelSize; y++) {
0323         const qreal yDistance = center - y;
0324         for (int x = 0; x < kernelSize; x++) {
0325             const qreal xDistance = center - x;
0326 
0327             const qreal distance = std::sqrt(pow2(xDistance) + pow2(yDistance));
0328 
0329             qreal value = 1.0;
0330 
0331             if (distance > radius + 1e-3) {
0332                 value = 0.0;
0333             } else if (distance > fadeStart) {
0334                 value = qMax(0.0, radius - distance);
0335             }
0336 
0337             matrix(x, y) = value;
0338         }
0339     }
0340 
0341     return matrix;
0342 }
0343 
0344 void KisGaussianKernel::applyDilate(KisPaintDeviceSP device, const QRect &rect, qreal radius, const QBitArray &channelFlags, KoUpdater *progressUpdater, bool createTransaction)
0345 {
0346     KIS_SAFE_ASSERT_RECOVER_RETURN(device->colorSpace()->pixelSize() == 1);
0347 
0348     QPoint srcTopLeft = rect.topLeft();
0349 
0350     KisConvolutionPainter painter(device);
0351     painter.setChannelFlags(channelFlags);
0352     painter.setProgress(progressUpdater);
0353 
0354     Eigen::Matrix<qreal, Eigen::Dynamic, Eigen::Dynamic> matrix = createDilateMatrix(radius);
0355     KisConvolutionKernelSP kernel =
0356         KisConvolutionKernel::fromMatrix(matrix,
0357                                          0,
0358                                          1.0);
0359 
0360     QScopedPointer<KisTransaction> transaction;
0361     if (createTransaction && painter.needsTransaction(kernel)) {
0362         transaction.reset(new KisTransaction(device));
0363     }
0364 
0365     painter.applyMatrix(kernel, device, srcTopLeft, srcTopLeft, rect.size(), BORDER_REPEAT);
0366 }
0367 
0368 #include "kis_sequential_iterator.h"
0369 
0370 void KisGaussianKernel::applyErodeU8(KisPaintDeviceSP device, const QRect &rect, qreal radius, const QBitArray &channelFlags, KoUpdater *progressUpdater, bool createTransaction)
0371 {
0372     KIS_SAFE_ASSERT_RECOVER_RETURN(device->colorSpace()->pixelSize() == 1);
0373 
0374     {
0375         KisSequentialIterator dstIt(device, rect);
0376         while (dstIt.nextPixel()) {
0377             quint8 *dstPtr = dstIt.rawData();
0378             *dstPtr = 255 - *dstPtr;
0379         }
0380     }
0381 
0382     applyDilate(device, rect, radius, channelFlags, progressUpdater, createTransaction);
0383 
0384     {
0385         KisSequentialIterator dstIt(device, rect);
0386         while (dstIt.nextPixel()) {
0387             quint8 *dstPtr = dstIt.rawData();
0388             *dstPtr = 255 - *dstPtr;
0389         }
0390     }
0391 }