File indexing completed on 2024-05-19 04:26:11

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==Prewitt) {
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==Prewitt) {
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 yIterator(y_denormalised, rect);
0225         KisSequentialIterator xIterator(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(yIterator.nextPixel() && xIterator.nextPixel() && finalIt.nextPixel()) {
0237             device->colorSpace()->normalisedChannelsValue(yIterator.rawData(), yNormalised);
0238             device->colorSpace()->normalisedChannelsValue(xIterator.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     KIS_ASSERT_RECOVER_RETURN(device->colorSpace()->channelCount() > 3);
0342 
0343     QPoint srcTopLeft = rect.topLeft();
0344     KisPainter finalPainter(device);
0345     finalPainter.setChannelFlags(channelFlags);
0346     finalPainter.setProgress(progressUpdater);
0347     KisPaintDeviceSP x_denormalised = new KisPaintDevice(device->colorSpace());
0348     KisPaintDeviceSP y_denormalised = new KisPaintDevice(device->colorSpace());
0349     x_denormalised->prepareClone(device);
0350     y_denormalised->prepareClone(device);
0351 
0352     KisConvolutionKernelSP kernelHorizLeftRight = KisEdgeDetectionKernel::createHorizontalKernel(yRadius, type, true, !channelFlip[1]);
0353     KisConvolutionKernelSP kernelVerticalTopBottom = KisEdgeDetectionKernel::createVerticalKernel(xRadius, type, true, !channelFlip[0]);
0354 
0355     KisConvolutionPainter horizPainterLR(y_denormalised);
0356 
0357     if (useFftw) {
0358         horizPainterLR.setEnginePreference(*useFftw ? KisConvolutionPainter::FFTW : KisConvolutionPainter::SPATIAL);
0359     }
0360 
0361     horizPainterLR.setChannelFlags(channelFlags);
0362     horizPainterLR.setProgress(progressUpdater);
0363     horizPainterLR.applyMatrix(kernelHorizLeftRight, device,
0364                                srcTopLeft, srcTopLeft,
0365                                rect.size(), BORDER_REPEAT);
0366 
0367 
0368     KisConvolutionPainter verticalPainterTB(x_denormalised);
0369 
0370     if (useFftw) {
0371         verticalPainterTB.setEnginePreference(*useFftw ? KisConvolutionPainter::FFTW : KisConvolutionPainter::SPATIAL);
0372     }
0373 
0374     verticalPainterTB.setChannelFlags(channelFlags);
0375     verticalPainterTB.setProgress(progressUpdater);
0376     verticalPainterTB.applyMatrix(kernelVerticalTopBottom, device,
0377                                   srcTopLeft,
0378                                   srcTopLeft,
0379                                   rect.size(), BORDER_REPEAT);
0380 
0381     KisSequentialIterator yIterator(y_denormalised, rect);
0382     KisSequentialIterator xIterator(x_denormalised, rect);
0383     KisSequentialIterator finalIt(device, rect);
0384     const int pixelSize = device->colorSpace()->pixelSize();
0385     const int channels = device->colorSpace()->channelCount();
0386     const int alphaPos = device->colorSpace()->alphaPos();
0387     KIS_SAFE_ASSERT_RECOVER_RETURN(alphaPos >= 0);
0388 
0389     QVector<float> yNormalised(channels);
0390     QVector<float> xNormalised(channels);
0391     QVector<float> finalNorm(channels);
0392 
0393     const QList<KoChannelInfo *> channelInfo = device->colorSpace()->channels();
0394 
0395     while(yIterator.nextPixel() && xIterator.nextPixel() && finalIt.nextPixel()) {
0396         device->colorSpace()->normalisedChannelsValue(yIterator.rawData(), yNormalised);
0397         device->colorSpace()->normalisedChannelsValue(xIterator.rawData(), xNormalised);
0398 
0399         qreal z = 1.0;
0400         if (channelFlip[2]==true){
0401             z=-1.0;
0402         }
0403         QVector3D normal = QVector3D((xNormalised[channelToConvert]-0.5)*2, (yNormalised[channelToConvert]-0.5)*2, z);
0404         normal.normalize();
0405         finalNorm.fill(1.0);
0406         for (int c = 0; c < 3; c++) {
0407             finalNorm[channelInfo.at(channelOrder[c])->displayPosition()] = (normal[channelOrder[c]]/2)+0.5;
0408         }
0409 
0410         finalNorm[alphaPos]= 1.0;
0411 
0412         quint8* pixel = finalIt.rawData();
0413         device->colorSpace()->fromNormalisedChannelsValue(pixel, finalNorm);
0414         memcpy(finalIt.rawData(), pixel, pixelSize);
0415 
0416     }
0417 }