diff --git a/libs/image/kis_edge_detection_kernel.cpp b/libs/image/kis_edge_detection_kernel.cpp index 0695e9262b..3cb998e010 100644 --- a/libs/image/kis_edge_detection_kernel.cpp +++ b/libs/image/kis_edge_detection_kernel.cpp @@ -1,644 +1,652 @@ /* * Copyright (c) 2017 Wolthera van Hövell tot Westerflier * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "kis_edge_detection_kernel.h" #include "kis_global.h" #include "kis_convolution_kernel.h" #include #include #include #include #include #include #include KisEdgeDetectionKernel::KisEdgeDetectionKernel() { } /* * This code is very similar to the gaussian kernel code, except unlike the gaussian code, * edge-detection kernels DO use the diagonals. * Except for the simple mode. We implement the simple mode because it is an analog to * the old sobel filter. */ Eigen::Matrix KisEdgeDetectionKernel::createHorizontalMatrix(qreal radius, FilterType type, bool reverse) { int kernelSize = kernelSizeFromRadius(radius); Eigen::Matrix matrix(kernelSize, kernelSize); KIS_ASSERT_RECOVER_NOOP(kernelSize & 0x1); const int center = kernelSize / 2; if (type==Prewit) { for (int x = 0; x < kernelSize; x++) { for (int y=0; y KisEdgeDetectionKernel::createVerticalMatrix(qreal radius, FilterType type, bool reverse) { int kernelSize = kernelSizeFromRadius(radius); Eigen::Matrix matrix(kernelSize, kernelSize); KIS_ASSERT_RECOVER_NOOP(kernelSize & 0x1); const int center = kernelSize / 2; if (type==Prewit) { for (int y = 0; y < kernelSize; y++) { for (int x=0; x matrix = createHorizontalMatrix(radius, type, reverse); if (denormalize) { return KisConvolutionKernel::fromMatrix(matrix, 0.5, 1); } else { return KisConvolutionKernel::fromMatrix(matrix, 0, matrix.sum()); } } KisConvolutionKernelSP KisEdgeDetectionKernel::createVerticalKernel(qreal radius, KisEdgeDetectionKernel::FilterType type, bool denormalize, bool reverse) { Eigen::Matrix matrix = createVerticalMatrix(radius, type, reverse); if (denormalize) { return KisConvolutionKernel::fromMatrix(matrix, 0.5, 1); } else { return KisConvolutionKernel::fromMatrix(matrix, 0, matrix.sum()); } } int KisEdgeDetectionKernel::kernelSizeFromRadius(qreal radius) { return qMax((int)(2 * ceil(sigmaFromRadius(radius)) + 1), 3); } qreal KisEdgeDetectionKernel::sigmaFromRadius(qreal radius) { return 0.3 * radius + 0.3; } void KisEdgeDetectionKernel::applyEdgeDetection(KisPaintDeviceSP device, const QRect &rect, qreal xRadius, qreal yRadius, KisEdgeDetectionKernel::FilterType type, const QBitArray &channelFlags, KoUpdater *progressUpdater, FilterOutput output, bool writeToAlpha) { QPoint srcTopLeft = rect.topLeft(); KisPainter finalPainter(device); finalPainter.setChannelFlags(channelFlags); finalPainter.setProgress(progressUpdater); if (output == pythagorean || output == radian) { KisPaintDeviceSP x_denormalised = new KisPaintDevice(device->colorSpace()); KisPaintDeviceSP y_denormalised = new KisPaintDevice(device->colorSpace()); x_denormalised->prepareClone(device); y_denormalised->prepareClone(device); KisConvolutionKernelSP kernelHorizLeftRight = KisEdgeDetectionKernel::createHorizontalKernel(xRadius, type); KisConvolutionKernelSP kernelVerticalTopBottom = KisEdgeDetectionKernel::createVerticalKernel(yRadius, type); qreal horizontalCenter = qreal(kernelHorizLeftRight->width()) / 2.0; qreal verticalCenter = qreal(kernelVerticalTopBottom->height()) / 2.0; KisConvolutionPainter horizPainterLR(x_denormalised); horizPainterLR.setChannelFlags(channelFlags); horizPainterLR.setProgress(progressUpdater); horizPainterLR.applyMatrix(kernelHorizLeftRight, device, srcTopLeft - QPoint(0, ceil(horizontalCenter)), srcTopLeft - QPoint(0, ceil(horizontalCenter)), rect.size() + QSize(0, 2 * ceil(horizontalCenter)), BORDER_REPEAT); KisConvolutionPainter verticalPainterTB(y_denormalised); verticalPainterTB.setChannelFlags(channelFlags); verticalPainterTB.setProgress(progressUpdater); verticalPainterTB.applyMatrix(kernelVerticalTopBottom, device, srcTopLeft - QPoint(0, ceil(verticalCenter)), srcTopLeft - QPoint(0, ceil(verticalCenter)), rect.size() + QSize(0, 2 * ceil(verticalCenter)), BORDER_REPEAT); KisSequentialIterator yItterator(y_denormalised, rect); KisSequentialIterator xItterator(x_denormalised, rect); KisSequentialIterator finalIt(device, rect); const int pixelSize = device->colorSpace()->pixelSize(); const int channels = device->colorSpace()->channelCount(); const int alphaPos = device->colorSpace()->alphaPos(); KIS_SAFE_ASSERT_RECOVER_RETURN(alphaPos >= 0); QVector yNormalised(channels); QVector xNormalised(channels); QVector finalNorm(channels); while(yItterator.nextPixel() && xItterator.nextPixel() && finalIt.nextPixel()) { device->colorSpace()->normalisedChannelsValue(yItterator.rawData(), yNormalised); device->colorSpace()->normalisedChannelsValue(xItterator.rawData(), xNormalised); device->colorSpace()->normalisedChannelsValue(finalIt.rawData(), finalNorm); if (output == pythagorean) { for (int c = 0; ccolorSpace()); qreal alpha = 0; for (int c = 0; c<(channels-1); c++) { alpha = alpha+finalNorm[c]; } alpha = qMin(alpha/(channels-1), col.opacityF()); col.setOpacity(alpha); memcpy(finalIt.rawData(), col.data(), pixelSize); } else { quint8* f = finalIt.rawData(); finalNorm[alphaPos] = 1.0; device->colorSpace()->fromNormalisedChannelsValue(f, finalNorm); memcpy(finalIt.rawData(), f, pixelSize); } } } else { KisConvolutionKernelSP kernel; qreal center = 0; bool denormalize = !writeToAlpha; if (output == xGrowth) { kernel = KisEdgeDetectionKernel::createHorizontalKernel(xRadius, type, denormalize); center = qreal(kernel->width()) / 2.0; } else if (output == xFall) { kernel = KisEdgeDetectionKernel::createHorizontalKernel(xRadius, type, denormalize, true); center = qreal(kernel->width()) / 2.0; } else if (output == yGrowth) { kernel = KisEdgeDetectionKernel::createVerticalKernel(yRadius, type, denormalize); center = qreal(kernel->height()) / 2.0; } else { //yFall kernel = KisEdgeDetectionKernel::createVerticalKernel(yRadius, type, denormalize, true); center = qreal(kernel->height()) / 2.0; } if (writeToAlpha) { KisPaintDeviceSP denormalised = new KisPaintDevice(device->colorSpace()); denormalised->prepareClone(device); KisConvolutionPainter kernelP(denormalised); kernelP.setChannelFlags(channelFlags); kernelP.setProgress(progressUpdater); kernelP.applyMatrix(kernel, device, srcTopLeft - QPoint(0, ceil(center)), srcTopLeft - QPoint(0, ceil(center)), rect.size() + QSize(0, 2 * ceil(center)), BORDER_REPEAT); KisSequentialIterator iterator(denormalised, rect); KisSequentialIterator finalIt(device, rect); const int pixelSize = device->colorSpace()->pixelSize(); const int channels = device->colorSpace()->colorChannelCount(); QVector normalised(channels); while (iterator.nextPixel() && finalIt.nextPixel()) { device->colorSpace()->normalisedChannelsValue(iterator.rawData(), normalised); KoColor col(finalIt.rawData(), device->colorSpace()); qreal alpha = 0; for (int c = 0; ccolorSpace()->setOpacity(finalIt.rawData(), 1.0, numConseqPixels); } } } } void KisEdgeDetectionKernel::applyCannyEdgeDetection(KisPaintDeviceSP _device, - const QRect& rect, - qreal xRadius, qreal yRadius, - const QBitArray &channelFlags, - KoUpdater *progressUpdater, - bool) + const QRect& rect, + qreal xRadius, + qreal yRadius, + const QBitArray& channelFlags, + KoUpdater* progressUpdater, + bool) { QPoint srcTopLeft = rect.topLeft(); auto bounds = _device->defaultBounds(); - auto device = KisPainter::convertToAlphaAsAlpha(_device); + auto device = KisPainter::convertToAlphaAsGray(_device); device->setDefaultBounds(bounds); KisPaintDeviceSP x_denormalised = new KisPaintDevice(device->colorSpace()); KisPaintDeviceSP y_denormalised = new KisPaintDevice(device->colorSpace()); x_denormalised->prepareClone(device); y_denormalised->prepareClone(device); - KisConvolutionKernelSP kernelHorizLeftRight = KisEdgeDetectionKernel::createHorizontalKernel(xRadius, KisEdgeDetectionKernel::SobelVector); - KisConvolutionKernelSP kernelVerticalTopBottom = KisEdgeDetectionKernel::createVerticalKernel(yRadius, KisEdgeDetectionKernel::SobelVector); + KisConvolutionKernelSP kernelHorizLeftRight = + KisEdgeDetectionKernel::createHorizontalKernel(xRadius, KisEdgeDetectionKernel::SobelVector); + KisConvolutionKernelSP kernelVerticalTopBottom = + KisEdgeDetectionKernel::createVerticalKernel(yRadius, KisEdgeDetectionKernel::SobelVector); qreal horizontalCenter = qreal(kernelHorizLeftRight->width()) / 2.0; qreal verticalCenter = qreal(kernelVerticalTopBottom->height()) / 2.0; KisConvolutionPainter horizPainterLR(x_denormalised); horizPainterLR.setChannelFlags(channelFlags); horizPainterLR.setProgress(progressUpdater); - horizPainterLR.applyMatrix(kernelHorizLeftRight, device, + horizPainterLR.applyMatrix(kernelHorizLeftRight, + device, srcTopLeft - QPoint(0, ceil(horizontalCenter)), srcTopLeft - QPoint(0, ceil(horizontalCenter)), - rect.size() + QSize(0, 2 * ceil(horizontalCenter)), BORDER_REPEAT); + rect.size() + QSize(0, 2 * ceil(horizontalCenter)), + BORDER_REPEAT); KisConvolutionPainter verticalPainterTB(y_denormalised); verticalPainterTB.setChannelFlags(channelFlags); verticalPainterTB.setProgress(progressUpdater); - verticalPainterTB.applyMatrix(kernelVerticalTopBottom, device, + verticalPainterTB.applyMatrix(kernelVerticalTopBottom, + device, srcTopLeft - QPoint(0, ceil(verticalCenter)), srcTopLeft - QPoint(0, ceil(verticalCenter)), - rect.size() + QSize(0, 2 * ceil(verticalCenter)), BORDER_REPEAT); + rect.size() + QSize(0, 2 * ceil(verticalCenter)), + BORDER_REPEAT); KisSequentialIterator yIterator(y_denormalised, rect); KisSequentialIterator xIterator(x_denormalised, rect); KisSequentialIterator sobelIterator(device, rect); QVector yNormalised(1); QVector xNormalised(1); QVector finalNorm(1); - while(yIterator.nextPixel() && xIterator.nextPixel() &&sobelIterator.nextPixel()) { + while (yIterator.nextPixel() && xIterator.nextPixel() && sobelIterator.nextPixel()) { device->colorSpace()->normalisedChannelsValue(yIterator.rawData(), yNormalised); device->colorSpace()->normalisedChannelsValue(xIterator.rawData(), xNormalised); device->colorSpace()->normalisedChannelsValue(sobelIterator.rawData(), finalNorm); - finalNorm[0] = 2 * sqrt( ((xNormalised[0]-0.5)*(xNormalised[0]-0.5)) + ((yNormalised[0]-0.5)*(yNormalised[0]-0.5))); + finalNorm[0] = 2 * sqrt(((xNormalised[0] - 0.5) * (xNormalised[0] - 0.5)) + + ((yNormalised[0] - 0.5) * (yNormalised[0] - 0.5))); quint8* f = sobelIterator.rawData(); device->colorSpace()->fromNormalisedChannelsValue(f, finalNorm); memcpy(sobelIterator.rawData(), f, device->colorSpace()->pixelSize()); } KisRandomAccessorSP randomSobel = device->createRandomAccessorNG(rect.x(), rect.y()); KisRandomConstAccessorSP gx_iter = x_denormalised->createRandomConstAccessorNG(rect.x(), rect.y()); KisRandomConstAccessorSP gy_iter = x_denormalised->createRandomConstAccessorNG(rect.x(), rect.y()); - for(int x = rect.x() + 1; x < rect.x() + rect.width() - 1; x++){ - for(int y = rect.y() + 1; y < rect.y() + rect.height() - 1; y++){ + for (int x = rect.x() + 1; x < rect.x() + rect.width() - 1; x++) { + for (int y = rect.y() + 1; y < rect.y() + rect.height() - 1; y++) { randomSobel->moveTo(x, y); const float current = *randomSobel->rawData(); float firstIntensity, secondIntensity; gx_iter->moveTo(x, y); gy_iter->moveTo(x, y); const float direction = atan2(*gx_iter->rawDataConst(), *gy_iter->rawDataConst()); - if(direction <= 1 || direction > 7){ - randomSobel->moveTo(x+1, y); + if (direction <= 1 || direction > 7) { + randomSobel->moveTo(x + 1, y); firstIntensity = *randomSobel->rawData(); - randomSobel->moveTo(x-1, y); + randomSobel->moveTo(x - 1, y); secondIntensity = *randomSobel->rawData(); } - if(direction > 1 || direction <= 3){ - randomSobel->moveTo(x+1, y+1); + if (direction > 1 || direction <= 3) { + randomSobel->moveTo(x + 1, y + 1); firstIntensity = *randomSobel->rawData(); - randomSobel->moveTo(x-1, y-1); + randomSobel->moveTo(x - 1, y - 1); secondIntensity = *randomSobel->rawData(); } - if(direction > 3 || direction <= 5){ - randomSobel->moveTo(x, y+1); + if (direction > 3 || direction <= 5) { + randomSobel->moveTo(x, y + 1); firstIntensity = *randomSobel->rawData(); - randomSobel->moveTo(x, y-1); + randomSobel->moveTo(x, y - 1); secondIntensity = *randomSobel->rawData(); } - if(direction > 5 || direction <= 7){ - randomSobel->moveTo(x+1, y-1); + if (direction > 5 || direction <= 7) { + randomSobel->moveTo(x + 1, y - 1); firstIntensity = *randomSobel->rawData(); - randomSobel->moveTo(x-1, y+1); + randomSobel->moveTo(x - 1, y + 1); secondIntensity = *randomSobel->rawData(); } randomSobel->moveTo(x, y); - if(2 * current <= (firstIntensity + secondIntensity)){ - quint8 *temp = new quint8(); + if (2 * current <= (firstIntensity + secondIntensity)) { + quint8* temp = new quint8(); finalNorm[0] = 0.0; device->colorSpace()->fromNormalisedChannelsValue(temp, finalNorm); memcpy(randomSobel->rawData(), temp, device->pixelSize()); } } } quint8 thresholdMax = 200; quint8 thresholdMin = 100; - for(int x = rect.x() + 1; x < rect.x() + rect.width() - 1; x++){ - for(int y = rect.y() + 1; y < rect.y() + rect.height() - 1; y++){ + for (int x = rect.x() + 1; x < rect.x() + rect.width() - 1; x++) { + for (int y = rect.y() + 1; y < rect.y() + rect.height() - 1; y++) { randomSobel->moveTo(x, y); quint8 intensity = *randomSobel->rawData(); - if(intensity < thresholdMin){ - quint8 *temp = new quint8(); + if (intensity < thresholdMin) { + quint8* temp = new quint8(); finalNorm[0] = 0.0; device->colorSpace()->fromNormalisedChannelsValue(temp, finalNorm); memcpy(randomSobel->rawData(), temp, device->pixelSize()); - }else if(intensity < thresholdMax){ - randomSobel->moveTo(x+1, y); + } else if (intensity < thresholdMax) { + randomSobel->moveTo(x + 1, y); quint8 intensity = *randomSobel->rawData(); - if(intensity > thresholdMax){ - quint8 *temp = new quint8(); + if (intensity > thresholdMax) { + quint8* temp = new quint8(); finalNorm[0] = 1.0; device->colorSpace()->fromNormalisedChannelsValue(temp, finalNorm); memcpy(randomSobel->rawData(), temp, device->pixelSize()); continue; } - randomSobel->moveTo(x-1, y); + randomSobel->moveTo(x - 1, y); intensity = *randomSobel->rawData(); - if(intensity > thresholdMax){ - quint8 *temp = new quint8(); + if (intensity > thresholdMax) { + quint8* temp = new quint8(); finalNorm[0] = 1.0; device->colorSpace()->fromNormalisedChannelsValue(temp, finalNorm); memcpy(randomSobel->rawData(), temp, device->pixelSize()); continue; } - randomSobel->moveTo(x, y+1); + randomSobel->moveTo(x, y + 1); intensity = *randomSobel->rawData(); - if(intensity > thresholdMax){ - quint8 *temp = new quint8(); + if (intensity > thresholdMax) { + quint8* temp = new quint8(); finalNorm[0] = 1.0; device->colorSpace()->fromNormalisedChannelsValue(temp, finalNorm); memcpy(randomSobel->rawData(), temp, device->pixelSize()); continue; } - randomSobel->moveTo(x, y-1); + randomSobel->moveTo(x, y - 1); intensity = *randomSobel->rawData(); - if(intensity > thresholdMax){ - quint8 *temp = new quint8(); + if (intensity > thresholdMax) { + quint8* temp = new quint8(); finalNorm[0] = 1.0; device->colorSpace()->fromNormalisedChannelsValue(temp, finalNorm); memcpy(randomSobel->rawData(), temp, device->pixelSize()); continue; } - randomSobel->moveTo(x+1, y+1); + randomSobel->moveTo(x + 1, y + 1); intensity = *randomSobel->rawData(); - if(intensity > thresholdMax){ - quint8 *temp = new quint8(); + if (intensity > thresholdMax) { + quint8* temp = new quint8(); finalNorm[0] = 1.0; device->colorSpace()->fromNormalisedChannelsValue(temp, finalNorm); memcpy(randomSobel->rawData(), temp, device->pixelSize()); continue; } - randomSobel->moveTo(x-1, y-1); + randomSobel->moveTo(x - 1, y - 1); intensity = *randomSobel->rawData(); - if(intensity > thresholdMax){ - quint8 *temp = new quint8(); + if (intensity > thresholdMax) { + quint8* temp = new quint8(); finalNorm[0] = 1.0; device->colorSpace()->fromNormalisedChannelsValue(temp, finalNorm); memcpy(randomSobel->rawData(), temp, device->pixelSize()); continue; } - randomSobel->moveTo(x+1, y-1); + randomSobel->moveTo(x + 1, y - 1); intensity = *randomSobel->rawData(); - if(intensity > thresholdMax){ - quint8 *temp = new quint8(); + if (intensity > thresholdMax) { + quint8* temp = new quint8(); finalNorm[0] = 1.0; device->colorSpace()->fromNormalisedChannelsValue(temp, finalNorm); memcpy(randomSobel->rawData(), temp, device->pixelSize()); continue; } - randomSobel->moveTo(x-1, y+1); + randomSobel->moveTo(x - 1, y + 1); intensity = *randomSobel->rawData(); - if(intensity > thresholdMax){ - quint8 *temp = new quint8(); + if (intensity > thresholdMax) { + quint8* temp = new quint8(); finalNorm[0] = 1.0; device->colorSpace()->fromNormalisedChannelsValue(temp, finalNorm); memcpy(randomSobel->rawData(), temp, device->pixelSize()); continue; } - }else{ - quint8 *temp = new quint8(); + } else { + quint8* temp = new quint8(); finalNorm[0] = 1.0; device->colorSpace()->fromNormalisedChannelsValue(temp, finalNorm); memcpy(randomSobel->rawData(), temp, device->pixelSize()); } } } - _device->makeCloneFrom(device, rect); + KisPainter::copyAreaOptimized(rect.topLeft(), device, _device, rect); } void KisEdgeDetectionKernel::convertToNormalMap(KisPaintDeviceSP device, const QRect &rect, qreal xRadius, qreal yRadius, KisEdgeDetectionKernel::FilterType type, int channelToConvert, QVector channelOrder, QVector channelFlip, const QBitArray &channelFlags, KoUpdater *progressUpdater) { QPoint srcTopLeft = rect.topLeft(); KisPainter finalPainter(device); finalPainter.setChannelFlags(channelFlags); finalPainter.setProgress(progressUpdater); KisPaintDeviceSP x_denormalised = new KisPaintDevice(device->colorSpace()); KisPaintDeviceSP y_denormalised = new KisPaintDevice(device->colorSpace()); x_denormalised->prepareClone(device); y_denormalised->prepareClone(device); KisConvolutionKernelSP kernelHorizLeftRight = KisEdgeDetectionKernel::createHorizontalKernel(yRadius, type, true, !channelFlip[1]); KisConvolutionKernelSP kernelVerticalTopBottom = KisEdgeDetectionKernel::createVerticalKernel(xRadius, type, true, !channelFlip[0]); qreal horizontalCenter = qreal(kernelHorizLeftRight->width()) / 2.0; qreal verticalCenter = qreal(kernelVerticalTopBottom->height()) / 2.0; KisConvolutionPainter horizPainterLR(y_denormalised); horizPainterLR.setChannelFlags(channelFlags); horizPainterLR.setProgress(progressUpdater); horizPainterLR.applyMatrix(kernelHorizLeftRight, device, srcTopLeft - QPoint(ceil(horizontalCenter), 0), srcTopLeft - QPoint(ceil(horizontalCenter), 0), rect.size() + QSize(2 * ceil(horizontalCenter), 0), BORDER_REPEAT); KisConvolutionPainter verticalPainterTB(x_denormalised); verticalPainterTB.setChannelFlags(channelFlags); verticalPainterTB.setProgress(progressUpdater); verticalPainterTB.applyMatrix(kernelVerticalTopBottom, device, srcTopLeft - QPoint(0, ceil(verticalCenter)), srcTopLeft - QPoint(0, ceil(verticalCenter)), rect.size() + QSize(0, 2 * ceil(verticalCenter)), BORDER_REPEAT); KisSequentialIterator yItterator(y_denormalised, rect); KisSequentialIterator xItterator(x_denormalised, rect); KisSequentialIterator finalIt(device, rect); const int pixelSize = device->colorSpace()->pixelSize(); const int channels = device->colorSpace()->channelCount(); const int alphaPos = device->colorSpace()->alphaPos(); KIS_SAFE_ASSERT_RECOVER_RETURN(alphaPos >= 0); QVector yNormalised(channels); QVector xNormalised(channels); QVector finalNorm(channels); while(yItterator.nextPixel() && xItterator.nextPixel() && finalIt.nextPixel()) { device->colorSpace()->normalisedChannelsValue(yItterator.rawData(), yNormalised); device->colorSpace()->normalisedChannelsValue(xItterator.rawData(), xNormalised); qreal z = 1.0; if (channelFlip[2]==true){ z=-1.0; } QVector3D normal = QVector3D((xNormalised[channelToConvert]-0.5)*2, (yNormalised[channelToConvert]-0.5)*2, z); normal.normalize(); finalNorm.fill(1.0); for (int c = 0; c<3; c++) { finalNorm[device->colorSpace()->channels().at(channelOrder[c])->displayPosition()] = (normal[channelOrder[c]]/2)+0.5; } finalNorm[alphaPos]= 1.0; quint8* pixel = finalIt.rawData(); device->colorSpace()->fromNormalisedChannelsValue(pixel, finalNorm); memcpy(finalIt.rawData(), pixel, pixelSize); } } diff --git a/plugins/filters/edgedetection/kis_wdg_edge_detection.cpp b/plugins/filters/edgedetection/kis_wdg_edge_detection.cpp index 2828216ebe..be81fcd094 100644 --- a/plugins/filters/edgedetection/kis_wdg_edge_detection.cpp +++ b/plugins/filters/edgedetection/kis_wdg_edge_detection.cpp @@ -1,129 +1,129 @@ /* * Copyright (c) 2017 Wolthera van Hövell tot Westerflier * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "kis_wdg_edge_detection.h" #include #include #include KisWdgEdgeDetection::KisWdgEdgeDetection(QWidget *parent) : KisConfigWidget(parent), ui(new Ui_WidgetEdgeDetection) { ui->setupUi(this); - m_types << "prewitt"<< "sobol"<< "simple"; - m_types_translatable << i18n("Prewitt") << i18n("Sobel") << i18n("Simple"); + m_types << "prewitt"<< "sobol"<< "simple" << "canny"; + m_types_translatable << i18n("Prewitt") << i18n("Sobel") << i18n("Simple") << i18n("Canny"); m_output << "pythagorean" << "xGrowth" << "xFall" << "yGrowth" << "yFall" << "radian"; m_output_translatable << i18n("All sides") << i18n("Top Edge") << i18n("Bottom Edge") << i18n("Right Edge") << i18n("Left Edge") << i18n("Direction in Radians"); ui->cmbType->addItems(m_types_translatable); ui->cmbOutput->addItems(m_output_translatable); ui->btnAspect->setKeepAspectRatio(false); ui->sldHorizontalRadius->setRange(1.0, 100.0, 2); ui->sldHorizontalRadius->setPrefix(i18n("Horizontal Radius:")); connect(ui->sldHorizontalRadius, SIGNAL(valueChanged(qreal)), this, SLOT(horizontalRadiusChanged(qreal))); ui->sldVerticalRadius->setRange(1.0, 100.0, 2); ui->sldVerticalRadius->setPrefix(i18n("Vertical Radius:")); connect(ui->sldVerticalRadius, SIGNAL(valueChanged(qreal)), this, SLOT(verticalRadiusChanged(qreal))); connect(ui->btnAspect, SIGNAL(keepAspectRatioChanged(bool)), this, SLOT(aspectLockChanged(bool))); connect(ui->cmbType, SIGNAL(currentIndexChanged(int)), this, SIGNAL(sigConfigurationItemChanged())); connect(ui->cmbOutput, SIGNAL(currentIndexChanged(int)), this, SIGNAL(sigConfigurationItemChanged())); connect(ui->sldHorizontalRadius, SIGNAL(valueChanged(qreal)), this, SIGNAL(sigConfigurationItemChanged())); connect(ui->sldVerticalRadius, SIGNAL(valueChanged(qreal)), this, SIGNAL(sigConfigurationItemChanged())); connect(ui->chkTransparent, SIGNAL(clicked()), this, SIGNAL(sigConfigurationItemChanged())); } KisWdgEdgeDetection::~KisWdgEdgeDetection() { delete ui; } KisPropertiesConfigurationSP KisWdgEdgeDetection::configuration() const { KisFilterConfigurationSP config = new KisFilterConfiguration("edge detection", 1); config->setProperty("horizRadius", ui->sldHorizontalRadius->value()); config->setProperty("vertRadius", ui->sldVerticalRadius->value()); config->setProperty("type", m_types.at(ui->cmbType->currentIndex())); config->setProperty("output", m_output.at(ui->cmbOutput->currentIndex())); config->setProperty("lockAspect", ui->btnAspect->keepAspectRatio()); config->setProperty("transparency", ui->chkTransparent->isChecked()); return config; } void KisWdgEdgeDetection::setConfiguration(const KisPropertiesConfigurationSP config) { ui->sldHorizontalRadius->setValue(config->getFloat("horizRadius", 1.0)); ui->sldVerticalRadius->setValue(config->getFloat("vertRadius", 1.0)); int index = 0; if (m_types.contains(config->getString("type", "prewitt"))){ index = m_types.indexOf(config->getString("type", "prewitt")); } ui->cmbType->setCurrentIndex(index); index = 0; if (m_output.contains(config->getString("output", "pythagorean"))){ index = m_output.indexOf(config->getString("output", "pythagorean")); } ui->cmbOutput->setCurrentIndex(index); ui->chkTransparent->setChecked(config->getBool("transparency", false)); ui->btnAspect->setKeepAspectRatio(config->getBool("lockAspect", false)); } void KisWdgEdgeDetection::horizontalRadiusChanged(qreal r) { ui->sldHorizontalRadius->blockSignals(true); ui->sldHorizontalRadius->setValue(r); ui->sldHorizontalRadius->blockSignals(false); if (ui->btnAspect->keepAspectRatio()) { ui->sldVerticalRadius->blockSignals(true); ui->sldVerticalRadius->setValue(r); ui->sldVerticalRadius->blockSignals(false); } } void KisWdgEdgeDetection::verticalRadiusChanged(qreal r) { ui->sldVerticalRadius->blockSignals(true); ui->sldVerticalRadius->setValue(r); ui->sldVerticalRadius->blockSignals(false); if (ui->btnAspect->keepAspectRatio()) { ui->sldHorizontalRadius->blockSignals(true); ui->sldHorizontalRadius->setValue(r); ui->sldHorizontalRadius->blockSignals(false); } } void KisWdgEdgeDetection::aspectLockChanged(bool v) { if (v) { ui->sldVerticalRadius->setValue( ui->sldHorizontalRadius->value() ); } } diff --git a/plugins/tools/selectiontools/KisMagneticWorker.cc b/plugins/tools/selectiontools/KisMagneticWorker.cc index 8a21ac8069..e1013a1b5f 100644 --- a/plugins/tools/selectiontools/KisMagneticWorker.cc +++ b/plugins/tools/selectiontools/KisMagneticWorker.cc @@ -1,282 +1,289 @@ /* * Copyright (c) 2019 Kuntal Majumder * * This library is free software; you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as published by * the Free Software Foundation; version 2.1 of the License. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "KisMagneticWorker.h" #include #include #include #include +#include +#include +#include #include #include #include #include #include #include "KisMagneticGraph.h" struct DistanceMap { typedef VertexDescriptor key_type; typedef double data_type; typedef std::pair value_type; explicit DistanceMap(double const &dval) : m_default(dval) { } data_type &operator [] (key_type const &k) { if (m.find(k) == m.end()) m[k] = m_default; return m[k]; } private: std::map m; data_type const m_default; }; struct PredecessorMap { PredecessorMap() = default; PredecessorMap(PredecessorMap const &that) = default; typedef VertexDescriptor key_type; typedef VertexDescriptor value_type; typedef boost::read_write_property_map_tag category; VertexDescriptor &operator [] (VertexDescriptor v) { return m_map[v]; } std::map m_map; }; VertexDescriptor get(PredecessorMap const &m, VertexDescriptor v) { auto found = m.m_map.find(v); return found != m.m_map.end() ? found->second : v; } void put(PredecessorMap &m, VertexDescriptor key, VertexDescriptor value) { m.m_map[key] = value; } double EuclideanDistance(VertexDescriptor p1, VertexDescriptor p2) { return std::sqrt(std::pow(p1.y - p2.y, 2) + std::pow(p1.x - p2.x, 2)); } class AStarHeuristic : public boost::astar_heuristic { private: VertexDescriptor m_goal; public: explicit AStarHeuristic(VertexDescriptor goal) : m_goal(goal) { } double operator () (VertexDescriptor v) { return EuclideanDistance(v, m_goal); } }; struct GoalFound { }; class AStarGoalVisitor : public boost::default_astar_visitor { public: explicit AStarGoalVisitor(VertexDescriptor goal) : m_goal(goal){ } void examine_vertex(VertexDescriptor u, KisMagneticGraph const &g) { Q_UNUSED(g) if (u == m_goal) { throw GoalFound(); } } private: VertexDescriptor m_goal; }; struct WeightMap { typedef std::pair key_type; typedef double data_type; typedef std::pair value_type; WeightMap() = default; explicit WeightMap(const KisMagneticGraph &g) : m_graph(g) { } data_type &operator [] (key_type const &k) { if (m_map.find(k) == m_map.end()) { double edge_gradient = (m_graph.getIntensity(k.first) + m_graph.getIntensity(k.second)) / 2; m_map[k] = EuclideanDistance(k.first, k.second) + 255.0 - edge_gradient; } return m_map[k]; } private: std::map m_map; KisMagneticGraph m_graph; }; KisMagneticLazyTiles::KisMagneticLazyTiles(KisPaintDeviceSP dev) { m_dev = KisPainter::convertToAlphaAsGray(dev); QSize s = dev->defaultBounds()->bounds().size(); m_tileSize = KritaUtils::optimalPatchSize(); m_tilesPerRow = (int) std::ceil((double) s.width() / (double) m_tileSize.width()); int tilesPerColumn = (int) std::ceil((double) s.height() / (double) m_tileSize.height()); m_dev->setDefaultBounds(dev->defaultBounds()); for (int i = 0; i < tilesPerColumn; i++) { for (int j = 0; j < m_tilesPerRow; j++) { int width = std::min(s.width() - j * m_tileSize.width(), m_tileSize.width()); int height = std::min(s.height() - i * m_tileSize.height(), m_tileSize.height()); QRect temp(j *m_tileSize.width(), i *m_tileSize.height(), width, height); m_tiles.push_back(temp); } } m_radiusRecord = QVector(m_tiles.size(), -1); } void KisMagneticLazyTiles::filter(qreal radius, QRect &rect) { auto divide = [](QPoint p, QSize s){ return QPoint(p.x() / s.width(), p.y() / s.height()); }; QPoint firstTile = divide(rect.topLeft(), m_tileSize); QPoint lastTile = divide(rect.bottomRight(), m_tileSize); for (int i = firstTile.y(); i <= lastTile.y(); i++) { for (int j = firstTile.x(); j <= lastTile.x(); j++) { int currentTile = i * m_tilesPerRow + j; if (radius != m_radiusRecord[currentTile]) { QRect bounds = m_tiles[currentTile]; - KisGaussianKernel::applyTightLoG(m_dev, bounds, radius, -1.0, QBitArray(), nullptr); + //KisGaussianKernel::applyTightLoG(m_dev, bounds, radius, -1.0, QBitArray(), nullptr); + KisFilterSP filter = KisFilterRegistry::instance()->value("edge detection"); + KisFilterConfigurationSP conf = filter->defaultConfiguration(); + conf->setProperty("type", "canny"); + filter->process(m_dev, bounds, conf); KisLazyFillTools::normalizeAlpha8Device(m_dev, bounds); m_radiusRecord[currentTile] = radius; } } } } KisMagneticWorker::KisMagneticWorker(const KisPaintDeviceSP &dev) : m_lazyTileFilter(dev) { } QVector KisMagneticWorker::computeEdge(int bounds, QPoint begin, QPoint end, qreal radius) { QRect rect; KisAlgebra2D::accumulateBounds(QVector { begin, end }, &rect); rect = kisGrowRect(rect, bounds); m_lazyTileFilter.filter(radius, rect); VertexDescriptor goal(end); VertexDescriptor start(begin); m_graph = new KisMagneticGraph(m_lazyTileFilter.device(), rect); // How many maps does it require? // Take a look here, if it doesn't make sense, https://www.boost.org/doc/libs/1_70_0/libs/graph/doc/astar_search.html PredecessorMap pmap; DistanceMap dmap(std::numeric_limits::max()); dmap[start] = 0; std::map rmap; std::map cmap; std::map imap; WeightMap wmap(*m_graph); AStarHeuristic heuristic(goal); QVector result; try { boost::astar_search_no_init( *m_graph, start, heuristic, boost::visitor(AStarGoalVisitor(goal)) .distance_map(boost::associative_property_map(dmap)) .predecessor_map(boost::ref(pmap)) .weight_map(boost::associative_property_map(wmap)) .vertex_index_map(boost::associative_property_map >(imap)) .rank_map(boost::associative_property_map >(rmap)) .color_map(boost::associative_property_map > (cmap)) .distance_combine(std::plus()) .distance_compare(std::less()) ); } catch (GoalFound const &) { for (VertexDescriptor u = goal; u != start; u = pmap[u]) { result.push_front(QPointF(u.x, u.y)); } } result.push_front(QPoint(start.x, start.y)); return result; } // KisMagneticWorker::computeEdge qreal KisMagneticWorker::intensity(QPoint pt) { return m_graph->getIntensity(VertexDescriptor(pt)); } void KisMagneticWorker::saveTheImage(vQPointF points) { QImage img = m_lazyTileFilter.device()->convertToQImage(nullptr, m_lazyTileFilter.device()->exactBounds()); const QPointF offset = m_lazyTileFilter.device()->exactBounds().topLeft(); for (QPointF &pt : points) { pt -= offset; } img = img.convertToFormat(QImage::Format_ARGB32); QPainter gc(&img); QPainterPath path; for (int i = 0; i < points.size(); i++) { if (i == 0) { path.moveTo(points[i]); } else { path.lineTo(points[i]); } } gc.setPen(Qt::blue); gc.drawPath(path); gc.setPen(Qt::green); gc.drawEllipse(points[0], 3, 3); gc.setPen(Qt::red); gc.drawEllipse(points[points.count() - 1], 2, 2); for (QRect &r : m_lazyTileFilter.tiles() ) { gc.drawRect(r); } img.save("result.png"); } // KisMagneticWorker::saveTheImage