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 }