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 }