File indexing completed on 2024-05-12 15:58:15
0001 /* 0002 * SPDX-FileCopyrightText: 2017 Wolthera van Hövell tot Westerflier <griffinvalley@gmail.com> 0003 * 0004 * SPDX-License-Identifier: GPL-2.0-or-later 0005 */ 0006 0007 #include "kis_edge_detection_kernel.h" 0008 #include "kis_global.h" 0009 #include "kis_convolution_kernel.h" 0010 #include <kis_convolution_painter.h> 0011 #include <KoCompositeOpRegistry.h> 0012 #include <QRect> 0013 #include <KoColorSpace.h> 0014 #include <kis_iterator_ng.h> 0015 #include <QVector3D> 0016 0017 KisEdgeDetectionKernel::KisEdgeDetectionKernel() 0018 { 0019 0020 } 0021 /* 0022 * This code is very similar to the gaussian kernel code, except unlike the gaussian code, 0023 * edge-detection kernels DO use the diagonals. 0024 * Except for the simple mode. We implement the simple mode because it is an analog to 0025 * the old sobel filter. 0026 */ 0027 0028 Eigen::Matrix<qreal, Eigen::Dynamic, Eigen::Dynamic> KisEdgeDetectionKernel::createHorizontalMatrix(qreal radius, 0029 FilterType type, 0030 bool reverse) 0031 { 0032 int kernelSize = kernelSizeFromRadius(radius); 0033 Eigen::Matrix<qreal, Eigen::Dynamic, Eigen::Dynamic> matrix(kernelSize, kernelSize); 0034 0035 KIS_ASSERT_RECOVER_NOOP(kernelSize & 0x1); 0036 const int center = kernelSize / 2; 0037 0038 if (type==Prewit) { 0039 for (int x = 0; x < kernelSize; x++) { 0040 for (int y=0; y<kernelSize; y++) { 0041 qreal xDistance; 0042 if (reverse) { 0043 xDistance = x - center; 0044 } else { 0045 xDistance = center - x; 0046 } 0047 matrix(x, y) = xDistance; 0048 } 0049 } 0050 } else if(type==Simple) { 0051 matrix.resize(kernelSize, 1); 0052 for (int x = 0; x < kernelSize; x++) { 0053 qreal xDistance; 0054 if (reverse) { 0055 xDistance = x - center; 0056 } else { 0057 xDistance = center - x; 0058 } 0059 if (x==center) { 0060 matrix(x, 0) = 0; 0061 } else { 0062 matrix(x, 0) = (1/xDistance); 0063 } 0064 } 0065 } else { 0066 for (int x = 0; x < kernelSize; x++) { 0067 for (int y=0; y<kernelSize; y++) { 0068 if (x==center && y==center) { 0069 matrix(x, y) = 0; 0070 } else { 0071 qreal xD, yD; 0072 if (reverse) { 0073 xD = x - center; 0074 yD = y - center; 0075 } else { 0076 xD = center - x; 0077 yD = center - y; 0078 } 0079 matrix(x, y) = xD / (xD*xD + yD*yD); 0080 } 0081 } 0082 } 0083 } 0084 return matrix; 0085 } 0086 0087 Eigen::Matrix<qreal, Eigen::Dynamic, Eigen::Dynamic> KisEdgeDetectionKernel::createVerticalMatrix(qreal radius, 0088 FilterType type, 0089 bool reverse) 0090 { 0091 int kernelSize = kernelSizeFromRadius(radius); 0092 0093 Eigen::Matrix<qreal, Eigen::Dynamic, Eigen::Dynamic> matrix(kernelSize, kernelSize); 0094 KIS_ASSERT_RECOVER_NOOP(kernelSize & 0x1); 0095 const int center = kernelSize / 2; 0096 0097 if (type==Prewit) { 0098 for (int y = 0; y < kernelSize; y++) { 0099 for (int x=0; x<kernelSize; x++) { 0100 qreal yDistance; 0101 if (reverse) { 0102 yDistance = y - center; 0103 } else { 0104 yDistance = center - y; 0105 } 0106 matrix(x, y) = yDistance; 0107 } 0108 } 0109 } else if(type==Simple) { 0110 matrix.resize(1, kernelSize); 0111 for (int y = 0; y < kernelSize; y++) { 0112 qreal yDistance; 0113 if (reverse) { 0114 yDistance = y - center; 0115 } else { 0116 yDistance = center - y; 0117 } 0118 if (y==center) { 0119 matrix(0, y) = 0; 0120 } else { 0121 matrix(0, y) = (1/yDistance); 0122 } 0123 } 0124 } else { 0125 for (int y = 0; y < kernelSize; y++) { 0126 for (int x=0; x<kernelSize; x++) { 0127 if (x==center && y==center) { 0128 matrix(x, y) = 0; 0129 } else { 0130 qreal xD, yD; 0131 if (reverse) { 0132 xD = x - center; 0133 yD = y - center; 0134 } else { 0135 xD = center - x; 0136 yD = center - y; 0137 } 0138 matrix(x, y) = yD / (xD*xD + yD*yD); 0139 } 0140 } 0141 } 0142 } 0143 return matrix; 0144 } 0145 0146 KisConvolutionKernelSP KisEdgeDetectionKernel::createHorizontalKernel(qreal radius, 0147 KisEdgeDetectionKernel::FilterType type, 0148 bool denormalize, 0149 bool reverse) 0150 { 0151 Eigen::Matrix<qreal, Eigen::Dynamic, Eigen::Dynamic> matrix = createHorizontalMatrix(radius, type, reverse); 0152 if (denormalize) { 0153 return KisConvolutionKernel::fromMatrix(matrix, 0.5, 1); 0154 } else { 0155 return KisConvolutionKernel::fromMatrix(matrix, 0, matrix.sum()); 0156 } 0157 } 0158 0159 KisConvolutionKernelSP KisEdgeDetectionKernel::createVerticalKernel(qreal radius, 0160 KisEdgeDetectionKernel::FilterType type, 0161 bool denormalize, 0162 bool reverse) 0163 { 0164 Eigen::Matrix<qreal, Eigen::Dynamic, Eigen::Dynamic> matrix = createVerticalMatrix(radius, type, reverse); 0165 if (denormalize) { 0166 return KisConvolutionKernel::fromMatrix(matrix, 0.5, 1); 0167 } else { 0168 return KisConvolutionKernel::fromMatrix(matrix, 0, matrix.sum()); 0169 } 0170 } 0171 0172 int KisEdgeDetectionKernel::kernelSizeFromRadius(qreal radius) 0173 { 0174 return qMax((int)(2 * ceil(sigmaFromRadius(radius)) + 1), 3); 0175 } 0176 0177 qreal KisEdgeDetectionKernel::sigmaFromRadius(qreal radius) 0178 { 0179 return 0.3 * radius + 0.3; 0180 } 0181 0182 void KisEdgeDetectionKernel::applyEdgeDetection(KisPaintDeviceSP device, 0183 const QRect &rect, 0184 qreal xRadius, 0185 qreal yRadius, 0186 KisEdgeDetectionKernel::FilterType type, 0187 const QBitArray &channelFlags, 0188 KoUpdater *progressUpdater, 0189 FilterOutput output, 0190 bool writeToAlpha) 0191 { 0192 QPoint srcTopLeft = rect.topLeft(); 0193 KisPainter finalPainter(device); 0194 finalPainter.setChannelFlags(channelFlags); 0195 finalPainter.setProgress(progressUpdater); 0196 if (output == pythagorean || output == radian) { 0197 KisPaintDeviceSP x_denormalised = new KisPaintDevice(device->colorSpace()); 0198 KisPaintDeviceSP y_denormalised = new KisPaintDevice(device->colorSpace()); 0199 0200 x_denormalised->prepareClone(device); 0201 y_denormalised->prepareClone(device); 0202 0203 0204 KisConvolutionKernelSP kernelHorizLeftRight = KisEdgeDetectionKernel::createHorizontalKernel(xRadius, type); 0205 KisConvolutionKernelSP kernelVerticalTopBottom = KisEdgeDetectionKernel::createVerticalKernel(yRadius, type); 0206 0207 KisConvolutionPainter horizPainterLR(x_denormalised); 0208 horizPainterLR.setChannelFlags(channelFlags); 0209 horizPainterLR.setProgress(progressUpdater); 0210 horizPainterLR.applyMatrix(kernelHorizLeftRight, device, 0211 srcTopLeft, 0212 srcTopLeft, 0213 rect.size(), BORDER_REPEAT); 0214 0215 0216 KisConvolutionPainter verticalPainterTB(y_denormalised); 0217 verticalPainterTB.setChannelFlags(channelFlags); 0218 verticalPainterTB.setProgress(progressUpdater); 0219 verticalPainterTB.applyMatrix(kernelVerticalTopBottom, device, 0220 srcTopLeft, 0221 srcTopLeft, 0222 rect.size(), BORDER_REPEAT); 0223 0224 KisSequentialIterator yItterator(y_denormalised, rect); 0225 KisSequentialIterator xItterator(x_denormalised, rect); 0226 KisSequentialIterator finalIt(device, rect); 0227 const int pixelSize = device->colorSpace()->pixelSize(); 0228 const int channels = device->colorSpace()->channelCount(); 0229 const int alphaPos = device->colorSpace()->alphaPos(); 0230 KIS_SAFE_ASSERT_RECOVER_RETURN(alphaPos >= 0); 0231 0232 QVector<float> yNormalised(channels); 0233 QVector<float> xNormalised(channels); 0234 QVector<float> finalNorm(channels); 0235 0236 while(yItterator.nextPixel() && xItterator.nextPixel() && finalIt.nextPixel()) { 0237 device->colorSpace()->normalisedChannelsValue(yItterator.rawData(), yNormalised); 0238 device->colorSpace()->normalisedChannelsValue(xItterator.rawData(), xNormalised); 0239 device->colorSpace()->normalisedChannelsValue(finalIt.rawData(), finalNorm); 0240 0241 if (output == pythagorean) { 0242 for (int c = 0; c<channels; c++) { 0243 finalNorm[c] = 2 * sqrt( ((xNormalised[c]-0.5)*(xNormalised[c]-0.5)) + ((yNormalised[c]-0.5)*(yNormalised[c]-0.5))); 0244 } 0245 } else { //radian 0246 for (int c = 0; c<channels; c++) { 0247 finalNorm[c] = atan2(xNormalised[c]-0.5, yNormalised[c]-0.5); 0248 } 0249 } 0250 0251 if (writeToAlpha) { 0252 KoColor col(finalIt.rawData(), device->colorSpace()); 0253 qreal alpha = 0; 0254 0255 for (int c = 0; c<(channels-1); c++) { 0256 alpha = alpha+finalNorm[c]; 0257 } 0258 0259 alpha = qMin(alpha/(channels-1), col.opacityF()); 0260 col.setOpacity(alpha); 0261 memcpy(finalIt.rawData(), col.data(), pixelSize); 0262 } else { 0263 quint8* f = finalIt.rawData(); 0264 finalNorm[alphaPos] = 1.0; 0265 device->colorSpace()->fromNormalisedChannelsValue(f, finalNorm); 0266 memcpy(finalIt.rawData(), f, pixelSize); 0267 } 0268 0269 } 0270 } else { 0271 KisConvolutionKernelSP kernel; 0272 bool denormalize = !writeToAlpha; 0273 if (output == xGrowth) { 0274 kernel = KisEdgeDetectionKernel::createHorizontalKernel(xRadius, type, denormalize); 0275 } else if (output == xFall) { 0276 kernel = KisEdgeDetectionKernel::createHorizontalKernel(xRadius, type, denormalize, true); 0277 } else if (output == yGrowth) { 0278 kernel = KisEdgeDetectionKernel::createVerticalKernel(yRadius, type, denormalize); 0279 } else { //yFall 0280 kernel = KisEdgeDetectionKernel::createVerticalKernel(yRadius, type, denormalize, true); 0281 } 0282 0283 if (writeToAlpha) { 0284 KisPaintDeviceSP denormalised = new KisPaintDevice(device->colorSpace()); 0285 denormalised->prepareClone(device); 0286 0287 KisConvolutionPainter kernelP(denormalised); 0288 kernelP.setChannelFlags(channelFlags); 0289 kernelP.setProgress(progressUpdater); 0290 kernelP.applyMatrix(kernel, device, 0291 srcTopLeft, srcTopLeft, 0292 rect.size(), BORDER_REPEAT); 0293 KisSequentialIterator iterator(denormalised, rect); 0294 KisSequentialIterator finalIt(device, rect); 0295 const int pixelSize = device->colorSpace()->pixelSize(); 0296 const int channels = device->colorSpace()->colorChannelCount(); 0297 QVector<float> normalised(channels); 0298 while (iterator.nextPixel() && finalIt.nextPixel()) { 0299 device->colorSpace()->normalisedChannelsValue(iterator.rawData(), normalised); 0300 KoColor col(finalIt.rawData(), device->colorSpace()); 0301 qreal alpha = 0; 0302 for (int c = 0; c<channels; c++) { 0303 alpha = alpha+normalised[c]; 0304 } 0305 alpha = qMin(alpha/channels, col.opacityF()); 0306 col.setOpacity(alpha); 0307 memcpy(finalIt.rawData(), col.data(), pixelSize); 0308 0309 } 0310 0311 } else { 0312 KisConvolutionPainter kernelP(device); 0313 kernelP.setChannelFlags(channelFlags); 0314 kernelP.setProgress(progressUpdater); 0315 kernelP.applyMatrix(kernel, device, 0316 srcTopLeft, srcTopLeft, 0317 rect.size(), BORDER_REPEAT); 0318 0319 KisSequentialIterator finalIt(device, rect); 0320 int numConseqPixels = finalIt.nConseqPixels(); 0321 while (finalIt.nextPixels(numConseqPixels)) { 0322 numConseqPixels = finalIt.nConseqPixels(); 0323 device->colorSpace()->setOpacity(finalIt.rawData(), 1.0, numConseqPixels); 0324 } 0325 } 0326 } 0327 } 0328 0329 void KisEdgeDetectionKernel::convertToNormalMap(KisPaintDeviceSP device, 0330 const QRect &rect, 0331 qreal xRadius, 0332 qreal yRadius, 0333 KisEdgeDetectionKernel::FilterType type, 0334 int channelToConvert, 0335 QVector<int> channelOrder, 0336 QVector<bool> channelFlip, 0337 const QBitArray &channelFlags, 0338 KoUpdater *progressUpdater, 0339 boost::optional<bool> useFftw) 0340 { 0341 QPoint srcTopLeft = rect.topLeft(); 0342 KisPainter finalPainter(device); 0343 finalPainter.setChannelFlags(channelFlags); 0344 finalPainter.setProgress(progressUpdater); 0345 KisPaintDeviceSP x_denormalised = new KisPaintDevice(device->colorSpace()); 0346 KisPaintDeviceSP y_denormalised = new KisPaintDevice(device->colorSpace()); 0347 x_denormalised->prepareClone(device); 0348 y_denormalised->prepareClone(device); 0349 0350 KisConvolutionKernelSP kernelHorizLeftRight = KisEdgeDetectionKernel::createHorizontalKernel(yRadius, type, true, !channelFlip[1]); 0351 KisConvolutionKernelSP kernelVerticalTopBottom = KisEdgeDetectionKernel::createVerticalKernel(xRadius, type, true, !channelFlip[0]); 0352 0353 KisConvolutionPainter horizPainterLR(y_denormalised); 0354 0355 if (useFftw) { 0356 horizPainterLR.setEnginePreference(*useFftw ? KisConvolutionPainter::FFTW : KisConvolutionPainter::SPATIAL); 0357 } 0358 0359 horizPainterLR.setChannelFlags(channelFlags); 0360 horizPainterLR.setProgress(progressUpdater); 0361 horizPainterLR.applyMatrix(kernelHorizLeftRight, device, 0362 srcTopLeft, srcTopLeft, 0363 rect.size(), BORDER_REPEAT); 0364 0365 0366 KisConvolutionPainter verticalPainterTB(x_denormalised); 0367 0368 if (useFftw) { 0369 verticalPainterTB.setEnginePreference(*useFftw ? KisConvolutionPainter::FFTW : KisConvolutionPainter::SPATIAL); 0370 } 0371 0372 verticalPainterTB.setChannelFlags(channelFlags); 0373 verticalPainterTB.setProgress(progressUpdater); 0374 verticalPainterTB.applyMatrix(kernelVerticalTopBottom, device, 0375 srcTopLeft, 0376 srcTopLeft, 0377 rect.size(), BORDER_REPEAT); 0378 0379 KisSequentialIterator yItterator(y_denormalised, rect); 0380 KisSequentialIterator xItterator(x_denormalised, rect); 0381 KisSequentialIterator finalIt(device, rect); 0382 const int pixelSize = device->colorSpace()->pixelSize(); 0383 const int channels = device->colorSpace()->channelCount(); 0384 const int alphaPos = device->colorSpace()->alphaPos(); 0385 KIS_SAFE_ASSERT_RECOVER_RETURN(alphaPos >= 0); 0386 0387 QVector<float> yNormalised(channels); 0388 QVector<float> xNormalised(channels); 0389 QVector<float> finalNorm(channels); 0390 0391 while(yItterator.nextPixel() && xItterator.nextPixel() && finalIt.nextPixel()) { 0392 device->colorSpace()->normalisedChannelsValue(yItterator.rawData(), yNormalised); 0393 device->colorSpace()->normalisedChannelsValue(xItterator.rawData(), xNormalised); 0394 0395 qreal z = 1.0; 0396 if (channelFlip[2]==true){ 0397 z=-1.0; 0398 } 0399 QVector3D normal = QVector3D((xNormalised[channelToConvert]-0.5)*2, (yNormalised[channelToConvert]-0.5)*2, z); 0400 normal.normalize(); 0401 finalNorm.fill(1.0); 0402 for (int c = 0; c<3; c++) { 0403 finalNorm[device->colorSpace()->channels().at(channelOrder[c])->displayPosition()] = (normal[channelOrder[c]]/2)+0.5; 0404 } 0405 0406 finalNorm[alphaPos]= 1.0; 0407 0408 quint8* pixel = finalIt.rawData(); 0409 device->colorSpace()->fromNormalisedChannelsValue(pixel, finalNorm); 0410 memcpy(finalIt.rawData(), pixel, pixelSize); 0411 0412 } 0413 }