diff --git a/libs/image/kis_selection_filters.cpp b/libs/image/kis_selection_filters.cpp index 4dca85c2e5..7bd0025661 100644 --- a/libs/image/kis_selection_filters.cpp +++ b/libs/image/kis_selection_filters.cpp @@ -1,874 +1,910 @@ /* * Copyright (c) 2005 Michael Thaler * Copyright (c) 2011 Dmitry Kazakov * * 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_selection_filters.h" #include #include #include "kis_convolution_painter.h" #include "kis_convolution_kernel.h" #include "kis_pixel_selection.h" #define MAX(a, b) ((a) > (b) ? (a) : (b)) #define MIN(a, b) ((a) < (b) ? (a) : (b)) #define RINT(x) floor ((x) + 0.5) KisSelectionFilter::~KisSelectionFilter() { } KUndo2MagicString KisSelectionFilter::name() { return KUndo2MagicString(); } QRect KisSelectionFilter::changeRect(const QRect &rect, KisDefaultBoundsBaseSP defaultBounds) { Q_UNUSED(defaultBounds); return rect; } void KisSelectionFilter::computeBorder(qint32* circ, qint32 xradius, qint32 yradius) { qint32 i; qint32 diameter = xradius * 2 + 1; double tmp; for (i = 0; i < diameter; i++) { if (i > xradius) tmp = (i - xradius) - 0.5; else if (i < xradius) tmp = (xradius - i) - 0.5; else tmp = 0.0; double divisor = (double) xradius; if (divisor == 0.0) { divisor = 1.0; } circ[i] = (qint32) RINT(yradius * sqrt(xradius * xradius - tmp * tmp) / divisor); } } void KisSelectionFilter::rotatePointers(quint8** p, quint32 n) { quint32 i; quint8 *p0 = p[0]; for (i = 0; i < n - 1; i++) { p[i] = p[i + 1]; } p[i] = p0; } void KisSelectionFilter::computeTransition(quint8* transition, quint8** buf, qint32 width) { qint32 x = 0; if (width == 1) { if (buf[1][x] > 127 && (buf[0][x] < 128 || buf[2][x] < 128)) transition[x] = 255; else transition[x] = 0; return; } if (buf[1][x] > 127) { if (buf[0][x] < 128 || buf[0][x + 1] < 128 || buf[1][x + 1] < 128 || buf[2][x] < 128 || buf[2][x + 1] < 128) transition[x] = 255; else transition[x] = 0; } else transition[x] = 0; for (qint32 x = 1; x < width - 1; x++) { if (buf[1][x] >= 128) { if (buf[0][x - 1] < 128 || buf[0][x] < 128 || buf[0][x + 1] < 128 || buf[1][x - 1] < 128 || buf[1][x + 1] < 128 || buf[2][x - 1] < 128 || buf[2][x] < 128 || buf[2][x + 1] < 128) transition[x] = 255; else transition[x] = 0; } else transition[x] = 0; } if (buf[1][x] >= 128) { if (buf[0][x - 1] < 128 || buf[0][x] < 128 || buf[1][x - 1] < 128 || buf[2][x - 1] < 128 || buf[2][x] < 128) transition[x] = 255; else transition[x] = 0; } else transition[x] = 0; } KUndo2MagicString KisErodeSelectionFilter::name() { return kundo2_i18n("Erode Selection"); } QRect KisErodeSelectionFilter::changeRect(const QRect& rect, KisDefaultBoundsBaseSP defaultBounds) { Q_UNUSED(defaultBounds); const qint32 radius = 1; return rect.adjusted(-radius, -radius, radius, radius); } void KisErodeSelectionFilter::process(KisPixelSelectionSP pixelSelection, const QRect& rect) { // Erode (radius 1 pixel) a mask (1bpp) quint8* buf[3]; qint32 width = rect.width(); qint32 height = rect.height(); quint8* out = new quint8[width]; for (qint32 i = 0; i < 3; i++) buf[i] = new quint8[width + 2]; // load top of image pixelSelection->readBytes(buf[0] + 1, rect.x(), rect.y(), width, 1); buf[0][0] = buf[0][1]; buf[0][width + 1] = buf[0][width]; memcpy(buf[1], buf[0], width + 2); for (qint32 y = 0; y < height; y++) { if (y + 1 < height) { pixelSelection->readBytes(buf[2] + 1, rect.x(), rect.y() + y + 1, width, 1); buf[2][0] = buf[2][1]; buf[2][width + 1] = buf[2][width]; } else { memcpy(buf[2], buf[1], width + 2); } for (qint32 x = 0 ; x < width; x++) { qint32 min = 255; if (buf[0][x+1] < min) min = buf[0][x+1]; if (buf[1][x] < min) min = buf[1][x]; if (buf[1][x+1] < min) min = buf[1][x+1]; if (buf[1][x+2] < min) min = buf[1][x+2]; if (buf[2][x+1] < min) min = buf[2][x+1]; out[x] = min; } pixelSelection->writeBytes(out, rect.x(), rect.y() + y, width, 1); rotatePointers(buf, 3); } for (qint32 i = 0; i < 3; i++) delete[] buf[i]; delete[] out; } KUndo2MagicString KisDilateSelectionFilter::name() { return kundo2_i18n("Dilate Selection"); } QRect KisDilateSelectionFilter::changeRect(const QRect& rect, KisDefaultBoundsBaseSP defaultBounds) { Q_UNUSED(defaultBounds); const qint32 radius = 1; return rect.adjusted(-radius, -radius, radius, radius); } void KisDilateSelectionFilter::process(KisPixelSelectionSP pixelSelection, const QRect& rect) { // dilate (radius 1 pixel) a mask (1bpp) quint8* buf[3]; qint32 width = rect.width(); qint32 height = rect.height(); quint8* out = new quint8[width]; for (qint32 i = 0; i < 3; i++) buf[i] = new quint8[width + 2]; // load top of image pixelSelection->readBytes(buf[0] + 1, rect.x(), rect.y(), width, 1); buf[0][0] = buf[0][1]; buf[0][width + 1] = buf[0][width]; memcpy(buf[1], buf[0], width + 2); for (qint32 y = 0; y < height; y++) { if (y + 1 < height) { pixelSelection->readBytes(buf[2] + 1, rect.x(), rect.y() + y + 1, width, 1); buf[2][0] = buf[2][1]; buf[2][width + 1] = buf[2][width]; } else { memcpy(buf[2], buf[1], width + 2); } for (qint32 x = 0 ; x < width; x++) { qint32 max = 0; if (buf[0][x+1] > max) max = buf[0][x+1]; if (buf[1][x] > max) max = buf[1][x]; if (buf[1][x+1] > max) max = buf[1][x+1]; if (buf[1][x+2] > max) max = buf[1][x+2]; if (buf[2][x+1] > max) max = buf[2][x+1]; out[x] = max; } pixelSelection->writeBytes(out, rect.x(), rect.y() + y, width, 1); rotatePointers(buf, 3); } for (qint32 i = 0; i < 3; i++) delete[] buf[i]; delete[] out; } -KisBorderSelectionFilter::KisBorderSelectionFilter(qint32 xRadius, qint32 yRadius) +KisBorderSelectionFilter::KisBorderSelectionFilter(qint32 xRadius, qint32 yRadius, bool antialiasing) : m_xRadius(xRadius), - m_yRadius(yRadius) + m_yRadius(yRadius), + m_antialiasing(antialiasing) { } KUndo2MagicString KisBorderSelectionFilter::name() { return kundo2_i18n("Border Selection"); } QRect KisBorderSelectionFilter::changeRect(const QRect& rect, KisDefaultBoundsBaseSP defaultBounds) { Q_UNUSED(defaultBounds); return rect.adjusted(-m_xRadius, -m_yRadius, m_xRadius, m_yRadius); } void KisBorderSelectionFilter::process(KisPixelSelectionSP pixelSelection, const QRect& rect) { if (m_xRadius <= 0 || m_yRadius <= 0) return; quint8 *buf[3]; quint8 **density; quint8 **transition; if (m_xRadius == 1 && m_yRadius == 1) { // optimize this case specifically quint8* source[3]; for (qint32 i = 0; i < 3; i++) source[i] = new quint8[rect.width()]; quint8* transition = new quint8[rect.width()]; pixelSelection->readBytes(source[0], rect.x(), rect.y(), rect.width(), 1); memcpy(source[1], source[0], rect.width()); if (rect.height() > 1) pixelSelection->readBytes(source[2], rect.x(), rect.y() + 1, rect.width(), 1); else memcpy(source[2], source[1], rect.width()); computeTransition(transition, source, rect.width()); pixelSelection->writeBytes(transition, rect.x(), rect.y(), rect.width(), 1); for (qint32 y = 1; y < rect.height(); y++) { rotatePointers(source, 3); if (y + 1 < rect.height()) pixelSelection->readBytes(source[2], rect.x(), rect.y() + y + 1, rect.width(), 1); else memcpy(source[2], source[1], rect.width()); computeTransition(transition, source, rect.width()); pixelSelection->writeBytes(transition, rect.x(), rect.y() + y, rect.width(), 1); } for (qint32 i = 0; i < 3; i++) delete[] source[i]; delete[] transition; return; } qint32* max = new qint32[rect.width() + 2 * m_xRadius]; for (qint32 i = 0; i < (rect.width() + 2 * m_xRadius); i++) max[i] = m_yRadius + 2; max += m_xRadius; for (qint32 i = 0; i < 3; i++) buf[i] = new quint8[rect.width()]; transition = new quint8*[m_yRadius + 1]; for (qint32 i = 0; i < m_yRadius + 1; i++) { transition[i] = new quint8[rect.width() + 2 * m_xRadius]; memset(transition[i], 0, rect.width() + 2 * m_xRadius); transition[i] += m_xRadius; } quint8* out = new quint8[rect.width()]; density = new quint8*[2 * m_xRadius + 1]; density += m_xRadius; for (qint32 x = 0; x < (m_xRadius + 1); x++) { // allocate density[][] density[ x] = new quint8[2 * m_yRadius + 1]; density[ x] += m_yRadius; density[-x] = density[x]; } - for (qint32 x = 0; x < (m_xRadius + 1); x++) { // compute density[][] - double tmpx, tmpy, dist; - quint8 a; - tmpx = x > 0.0 ? x - 0.5 : 0.0; + // compute density[][] + if (m_antialiasing) { + KIS_SAFE_ASSERT_RECOVER_NOOP(m_xRadius == m_yRadius && "anisotropic fading is not implemented"); + const qreal maxRadius = 0.5 * (m_xRadius + m_yRadius); + const qreal minRadius = maxRadius - 1.0; - for (qint32 y = 0; y < (m_yRadius + 1); y++) { - tmpy = y > 0.0 ? y - 0.5 : 0.0; + for (qint32 x = 0; x < (m_xRadius + 1); x++) { + double tmpx, tmpy, dist; + quint8 a; - dist = ((tmpy * tmpy) / (m_yRadius * m_yRadius) + - (tmpx * tmpx) / (m_xRadius * m_xRadius)); - if (dist < 1.0) - a = (quint8)(255 * (1.0 - sqrt(dist))); - else - a = 0; - density[ x][ y] = a; - density[ x][-y] = a; - density[-x][ y] = a; - density[-x][-y] = a; + tmpx = x > 0.0 ? x - 0.5 : 0.0; + + for (qint32 y = 0; y < (m_yRadius + 1); y++) { + tmpy = y > 0.0 ? y - 0.5 : 0.0; + + dist = sqrt(pow2(x) + pow2(y)); + + if (dist > maxRadius) { + a = 0; + } else if (dist > minRadius) { + a = qRound((1.0 - dist + minRadius) * 255.0); + } else { + a = 255; + } + + density[ x][ y] = a; + density[ x][-y] = a; + density[-x][ y] = a; + density[-x][-y] = a; + } + } + + } else { + for (qint32 x = 0; x < (m_xRadius + 1); x++) { + double tmpx, tmpy, dist; + quint8 a; + + tmpx = x > 0.0 ? x - 0.5 : 0.0; + + for (qint32 y = 0; y < (m_yRadius + 1); y++) { + tmpy = y > 0.0 ? y - 0.5 : 0.0; + + dist = (pow2(tmpy) / pow2(m_yRadius) + + pow2(tmpx) / pow2(m_xRadius)); + + a = dist <= 1.0 ? 255 : 0; + + density[ x][ y] = a; + density[ x][-y] = a; + density[-x][ y] = a; + density[-x][-y] = a; + } } } + pixelSelection->readBytes(buf[0], rect.x(), rect.y(), rect.width(), 1); memcpy(buf[1], buf[0], rect.width()); if (rect.height() > 1) pixelSelection->readBytes(buf[2], rect.x(), rect.y() + 1, rect.width(), 1); else memcpy(buf[2], buf[1], rect.width()); computeTransition(transition[1], buf, rect.width()); for (qint32 y = 1; y < m_yRadius && y + 1 < rect.height(); y++) { // set up top of image rotatePointers(buf, 3); pixelSelection->readBytes(buf[2], rect.x(), rect.y() + y + 1, rect.width(), 1); computeTransition(transition[y + 1], buf, rect.width()); } for (qint32 x = 0; x < rect.width(); x++) { // set up max[] for top of image max[x] = -(m_yRadius + 7); for (qint32 j = 1; j < m_yRadius + 1; j++) if (transition[j][x]) { max[x] = j; break; } } for (qint32 y = 0; y < rect.height(); y++) { // main calculation loop rotatePointers(buf, 3); rotatePointers(transition, m_yRadius + 1); if (y < rect.height() - (m_yRadius + 1)) { pixelSelection->readBytes(buf[2], rect.x(), rect.y() + y + m_yRadius + 1, rect.width(), 1); computeTransition(transition[m_yRadius], buf, rect.width()); } else memcpy(transition[m_yRadius], transition[m_yRadius - 1], rect.width()); for (qint32 x = 0; x < rect.width(); x++) { // update max array if (max[x] < 1) { if (max[x] <= -m_yRadius) { if (transition[m_yRadius][x]) max[x] = m_yRadius; else max[x]--; } else if (transition[-max[x]][x]) max[x] = -max[x]; else if (transition[-max[x] + 1][x]) max[x] = -max[x] + 1; else max[x]--; } else max[x]--; if (max[x] < -m_yRadius - 1) max[x] = -m_yRadius - 1; } quint8 last_max = max[0][density[-1]]; qint32 last_index = 1; for (qint32 x = 0 ; x < rect.width(); x++) { // render scan line last_index--; if (last_index >= 0) { last_max = 0; for (qint32 i = m_xRadius; i >= 0; i--) if (max[x + i] <= m_yRadius && max[x + i] >= -m_yRadius && density[i][max[x+i]] > last_max) { last_max = density[i][max[x + i]]; last_index = i; } out[x] = last_max; } else { last_max = 0; for (qint32 i = m_xRadius; i >= -m_xRadius; i--) if (max[x + i] <= m_yRadius && max[x + i] >= -m_yRadius && density[i][max[x + i]] > last_max) { last_max = density[i][max[x + i]]; last_index = i; } out[x] = last_max; } if (last_max == 0) { qint32 i; for (i = x + 1; i < rect.width(); i++) { if (max[i] >= -m_yRadius) break; } if (i - x > m_xRadius) { for (; x < i - m_xRadius; x++) out[x] = 0; x--; } last_index = m_xRadius; } } pixelSelection->writeBytes(out, rect.x(), rect.y() + y, rect.width(), 1); } delete [] out; for (qint32 i = 0; i < 3; i++) delete[] buf[i]; max -= m_xRadius; delete[] max; for (qint32 i = 0; i < m_yRadius + 1; i++) { transition[i] -= m_xRadius; delete transition[i]; } delete[] transition; for (qint32 i = 0; i < m_xRadius + 1 ; i++) { density[i] -= m_yRadius; delete density[i]; } density -= m_xRadius; delete[] density; } KisFeatherSelectionFilter::KisFeatherSelectionFilter(qint32 radius) : m_radius(radius) { } KUndo2MagicString KisFeatherSelectionFilter::name() { return kundo2_i18n("Feather Selection"); } QRect KisFeatherSelectionFilter::changeRect(const QRect& rect, KisDefaultBoundsBaseSP defaultBounds) { Q_UNUSED(defaultBounds); return rect.adjusted(-m_radius, -m_radius, m_radius, m_radius); } void KisFeatherSelectionFilter::process(KisPixelSelectionSP pixelSelection, const QRect& rect) { // compute horizontal kernel const uint kernelSize = m_radius * 2 + 1; Eigen::Matrix gaussianMatrix(1, kernelSize); const qreal multiplicand = 1.0 / (2.0 * M_PI * m_radius * m_radius); const qreal exponentMultiplicand = 1.0 / (2.0 * m_radius * m_radius); for (uint x = 0; x < kernelSize; x++) { uint xDistance = qAbs((int)m_radius - (int)x); gaussianMatrix(0, x) = multiplicand * exp( -(qreal)((xDistance * xDistance) + (m_radius * m_radius)) * exponentMultiplicand ); } KisConvolutionKernelSP kernelHoriz = KisConvolutionKernel::fromMatrix(gaussianMatrix, 0, gaussianMatrix.sum()); KisConvolutionKernelSP kernelVertical = KisConvolutionKernel::fromMatrix(gaussianMatrix.transpose(), 0, gaussianMatrix.sum()); KisPaintDeviceSP interm = new KisPaintDevice(pixelSelection->colorSpace()); interm->prepareClone(pixelSelection); KisConvolutionPainter horizPainter(interm); horizPainter.setChannelFlags(interm->colorSpace()->channelFlags(false, true)); horizPainter.applyMatrix(kernelHoriz, pixelSelection, rect.topLeft(), rect.topLeft(), rect.size(), BORDER_REPEAT); horizPainter.end(); KisConvolutionPainter verticalPainter(pixelSelection); verticalPainter.setChannelFlags(pixelSelection->colorSpace()->channelFlags(false, true)); verticalPainter.applyMatrix(kernelVertical, interm, rect.topLeft(), rect.topLeft(), rect.size(), BORDER_REPEAT); verticalPainter.end(); } KisGrowSelectionFilter::KisGrowSelectionFilter(qint32 xRadius, qint32 yRadius) : m_xRadius(xRadius), m_yRadius(yRadius) { } KUndo2MagicString KisGrowSelectionFilter::name() { return kundo2_i18n("Grow Selection"); } QRect KisGrowSelectionFilter::changeRect(const QRect& rect, KisDefaultBoundsBaseSP defaultBounds) { Q_UNUSED(defaultBounds); return rect.adjusted(-m_xRadius, -m_yRadius, m_xRadius, m_yRadius); } void KisGrowSelectionFilter::process(KisPixelSelectionSP pixelSelection, const QRect& rect) { if (m_xRadius <= 0 || m_yRadius <= 0) return; /** * Much code resembles Shrink filter, so please fix bugs * in both filters */ quint8 **buf; // caches the region's pixel data quint8 **max; // caches the largest values for each column max = new quint8* [rect.width() + 2 * m_xRadius]; buf = new quint8* [m_yRadius + 1]; for (qint32 i = 0; i < m_yRadius + 1; i++) { buf[i] = new quint8[rect.width()]; } quint8* buffer = new quint8[(rect.width() + 2 * m_xRadius) *(m_yRadius + 1)]; for (qint32 i = 0; i < rect.width() + 2 * m_xRadius; i++) { if (i < m_xRadius) max[i] = buffer; else if (i < rect.width() + m_xRadius) max[i] = &buffer[(m_yRadius + 1) * (i - m_xRadius)]; else max[i] = &buffer[(m_yRadius + 1) * (rect.width() + m_xRadius - 1)]; for (qint32 j = 0; j < m_xRadius + 1; j++) max[i][j] = 0; } /* offset the max pointer by m_xRadius so the range of the array is [-m_xRadius] to [region->w + m_xRadius] */ max += m_xRadius; quint8* out = new quint8[ rect.width()]; // holds the new scan line we are computing qint32* circ = new qint32[ 2 * m_xRadius + 1 ]; // holds the y coords of the filter's mask computeBorder(circ, m_xRadius, m_yRadius); /* offset the circ pointer by m_xRadius so the range of the array is [-m_xRadius] to [m_xRadius] */ circ += m_xRadius; memset(buf[0], 0, rect.width()); for (qint32 i = 0; i < m_yRadius && i < rect.height(); i++) { // load top of image pixelSelection->readBytes(buf[i + 1], rect.x(), rect.y() + i, rect.width(), 1); } for (qint32 x = 0; x < rect.width() ; x++) { // set up max for top of image max[x][0] = 0; // buf[0][x] is always 0 max[x][1] = buf[1][x]; // MAX (buf[1][x], max[x][0]) always = buf[1][x] for (qint32 j = 2; j < m_yRadius + 1; j++) { max[x][j] = MAX(buf[j][x], max[x][j-1]); } } for (qint32 y = 0; y < rect.height(); y++) { rotatePointers(buf, m_yRadius + 1); if (y < rect.height() - (m_yRadius)) pixelSelection->readBytes(buf[m_yRadius], rect.x(), rect.y() + y + m_yRadius, rect.width(), 1); else memset(buf[m_yRadius], 0, rect.width()); for (qint32 x = 0; x < rect.width(); x++) { /* update max array */ for (qint32 i = m_yRadius; i > 0; i--) { max[x][i] = MAX(MAX(max[x][i - 1], buf[i - 1][x]), buf[i][x]); } max[x][0] = buf[0][x]; } qint32 last_max = max[0][circ[-1]]; qint32 last_index = 1; for (qint32 x = 0; x < rect.width(); x++) { /* render scan line */ last_index--; if (last_index >= 0) { if (last_max == 255) out[x] = 255; else { last_max = 0; for (qint32 i = m_xRadius; i >= 0; i--) if (last_max < max[x + i][circ[i]]) { last_max = max[x + i][circ[i]]; last_index = i; } out[x] = last_max; } } else { last_index = m_xRadius; last_max = max[x + m_xRadius][circ[m_xRadius]]; for (qint32 i = m_xRadius - 1; i >= -m_xRadius; i--) if (last_max < max[x + i][circ[i]]) { last_max = max[x + i][circ[i]]; last_index = i; } out[x] = last_max; } } pixelSelection->writeBytes(out, rect.x(), rect.y() + y, rect.width(), 1); } /* undo the offsets to the pointers so we can free the malloced memory */ circ -= m_xRadius; max -= m_xRadius; delete[] circ; delete[] buffer; delete[] max; for (qint32 i = 0; i < m_yRadius + 1; i++) delete[] buf[i]; delete[] buf; delete[] out; } KisShrinkSelectionFilter::KisShrinkSelectionFilter(qint32 xRadius, qint32 yRadius, bool edgeLock) : m_xRadius(xRadius), m_yRadius(yRadius), m_edgeLock(edgeLock) { } KUndo2MagicString KisShrinkSelectionFilter::name() { return kundo2_i18n("Shrink Selection"); } QRect KisShrinkSelectionFilter::changeRect(const QRect& rect, KisDefaultBoundsBaseSP defaultBounds) { return m_edgeLock ? defaultBounds->imageBorderRect() : rect; } void KisShrinkSelectionFilter::process(KisPixelSelectionSP pixelSelection, const QRect& rect) { if (m_xRadius <= 0 || m_yRadius <= 0) return; /* pretty much the same as fatten_region only different blame all bugs in this function on jaycox@gimp.org */ /* If edge_lock is true we assume that pixels outside the region we are passed are identical to the edge pixels. If edge_lock is false, we assume that pixels outside the region are 0 */ quint8 **buf; // caches the region's pixels quint8 **max; // caches the smallest values for each column qint32 last_max, last_index; max = new quint8* [rect.width() + 2 * m_xRadius]; buf = new quint8* [m_yRadius + 1]; for (qint32 i = 0; i < m_yRadius + 1; i++) { buf[i] = new quint8[rect.width()]; } qint32 buffer_size = (rect.width() + 2 * m_xRadius + 1) * (m_yRadius + 1); quint8* buffer = new quint8[buffer_size]; if (m_edgeLock) memset(buffer, 255, buffer_size); else memset(buffer, 0, buffer_size); for (qint32 i = 0; i < rect.width() + 2 * m_xRadius; i++) { if (i < m_xRadius) if (m_edgeLock) max[i] = buffer; else max[i] = &buffer[(m_yRadius + 1) * (rect.width() + m_xRadius)]; else if (i < rect.width() + m_xRadius) max[i] = &buffer[(m_yRadius + 1) * (i - m_xRadius)]; else if (m_edgeLock) max[i] = &buffer[(m_yRadius + 1) * (rect.width() + m_xRadius - 1)]; else max[i] = &buffer[(m_yRadius + 1) * (rect.width() + m_xRadius)]; } if (!m_edgeLock) for (qint32 j = 0 ; j < m_xRadius + 1; j++) max[0][j] = 0; // offset the max pointer by m_xRadius so the range of the array is [-m_xRadius] to [region->w + m_xRadius] max += m_xRadius; quint8* out = new quint8[rect.width()]; // holds the new scan line we are computing qint32* circ = new qint32[2 * m_xRadius + 1]; // holds the y coords of the filter's mask computeBorder(circ, m_xRadius, m_yRadius); // offset the circ pointer by m_xRadius so the range of the array is [-m_xRadius] to [m_xRadius] circ += m_xRadius; for (qint32 i = 0; i < m_yRadius && i < rect.height(); i++) // load top of image pixelSelection->readBytes(buf[i + 1], rect.x(), rect.y() + i, rect.width(), 1); if (m_edgeLock) memcpy(buf[0], buf[1], rect.width()); else memset(buf[0], 0, rect.width()); for (qint32 x = 0; x < rect.width(); x++) { // set up max for top of image max[x][0] = buf[0][x]; for (qint32 j = 1; j < m_yRadius + 1; j++) max[x][j] = MIN(buf[j][x], max[x][j-1]); } for (qint32 y = 0; y < rect.height(); y++) { rotatePointers(buf, m_yRadius + 1); if (y < rect.height() - m_yRadius) pixelSelection->readBytes(buf[m_yRadius], rect.x(), rect.y() + y + m_yRadius, rect.width(), 1); else if (m_edgeLock) memcpy(buf[m_yRadius], buf[m_yRadius - 1], rect.width()); else memset(buf[m_yRadius], 0, rect.width()); for (qint32 x = 0 ; x < rect.width(); x++) { // update max array for (qint32 i = m_yRadius; i > 0; i--) { max[x][i] = MIN(MIN(max[x][i - 1], buf[i - 1][x]), buf[i][x]); } max[x][0] = buf[0][x]; } last_max = max[0][circ[-1]]; last_index = 0; for (qint32 x = 0 ; x < rect.width(); x++) { // render scan line last_index--; if (last_index >= 0) { if (last_max == 0) out[x] = 0; else { last_max = 255; for (qint32 i = m_xRadius; i >= 0; i--) if (last_max > max[x + i][circ[i]]) { last_max = max[x + i][circ[i]]; last_index = i; } out[x] = last_max; } } else { last_index = m_xRadius; last_max = max[x + m_xRadius][circ[m_xRadius]]; for (qint32 i = m_xRadius - 1; i >= -m_xRadius; i--) if (last_max > max[x + i][circ[i]]) { last_max = max[x + i][circ[i]]; last_index = i; } out[x] = last_max; } } pixelSelection->writeBytes(out, rect.x(), rect.y() + y, rect.width(), 1); } // undo the offsets to the pointers so we can free the malloced memory circ -= m_xRadius; max -= m_xRadius; delete[] circ; delete[] buffer; delete[] max; for (qint32 i = 0; i < m_yRadius + 1; i++) delete[] buf[i]; delete[] buf; delete[] out; } KUndo2MagicString KisSmoothSelectionFilter::name() { return kundo2_i18n("Smooth Selection"); } QRect KisSmoothSelectionFilter::changeRect(const QRect& rect, KisDefaultBoundsBaseSP defaultBounds) { Q_UNUSED(defaultBounds); const qint32 radius = 1; return rect.adjusted(-radius, -radius, radius, radius); } void KisSmoothSelectionFilter::process(KisPixelSelectionSP pixelSelection, const QRect& rect) { // Simple convolution filter to smooth a mask (1bpp) quint8 *buf[3]; qint32 width = rect.width(); qint32 height = rect.height(); quint8* out = new quint8[width]; for (qint32 i = 0; i < 3; i++) buf[i] = new quint8[width + 2]; // load top of image pixelSelection->readBytes(buf[0] + 1, rect.x(), rect.y(), width, 1); buf[0][0] = buf[0][1]; buf[0][width + 1] = buf[0][width]; memcpy(buf[1], buf[0], width + 2); for (qint32 y = 0; y < height; y++) { if (y + 1 < height) { pixelSelection->readBytes(buf[2] + 1, rect.x(), rect.y() + y + 1, width, 1); buf[2][0] = buf[2][1]; buf[2][width + 1] = buf[2][width]; } else { memcpy(buf[2], buf[1], width + 2); } for (qint32 x = 0 ; x < width; x++) { qint32 value = (buf[0][x] + buf[0][x+1] + buf[0][x+2] + buf[1][x] + buf[2][x+1] + buf[1][x+2] + buf[2][x] + buf[1][x+1] + buf[2][x+2]); out[x] = value / 9; } pixelSelection->writeBytes(out, rect.x(), rect.y() + y, width, 1); rotatePointers(buf, 3); } for (qint32 i = 0; i < 3; i++) delete[] buf[i]; delete[] out; } KUndo2MagicString KisInvertSelectionFilter::name() { return kundo2_i18n("Invert Selection"); } QRect KisInvertSelectionFilter::changeRect(const QRect &rect, KisDefaultBoundsBaseSP defaultBounds) { Q_UNUSED(rect); return defaultBounds->bounds(); } void KisInvertSelectionFilter::process(KisPixelSelectionSP pixelSelection, const QRect& rect) { Q_UNUSED(rect); pixelSelection->invert(); } diff --git a/libs/image/kis_selection_filters.h b/libs/image/kis_selection_filters.h index a2bb555c9d..3ea68bec33 100644 --- a/libs/image/kis_selection_filters.h +++ b/libs/image/kis_selection_filters.h @@ -1,156 +1,157 @@ /* * Copyright (c) 2005 Michael Thaler * Copyright (c) 2011 Dmitry Kazakov * * 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. */ #ifndef KIS_SELECTION_FILTERS_H #define KIS_SELECTION_FILTERS_H #include "kis_types.h" #include "kritaimage_export.h" #include "kis_default_bounds_base.h" #include #include class KUndo2MagicString; class KRITAIMAGE_EXPORT KisSelectionFilter { public: virtual ~KisSelectionFilter(); virtual void process(KisPixelSelectionSP pixelSelection, const QRect &rect) = 0; virtual KUndo2MagicString name(); virtual QRect changeRect(const QRect &rect, KisDefaultBoundsBaseSP defaultBounds); protected: void computeBorder(qint32 *circ, qint32 xradius, qint32 yradius); void rotatePointers(quint8 **p, quint32 n); void computeTransition(quint8* transition, quint8** buf, qint32 width); }; class KRITAIMAGE_EXPORT KisErodeSelectionFilter : public KisSelectionFilter { public: KUndo2MagicString name() override; QRect changeRect(const QRect &rect, KisDefaultBoundsBaseSP defaultBounds) override; void process(KisPixelSelectionSP pixelSelection, const QRect &rect) override; }; class KRITAIMAGE_EXPORT KisDilateSelectionFilter : public KisSelectionFilter { public: KUndo2MagicString name() override; QRect changeRect(const QRect &rect, KisDefaultBoundsBaseSP defaultBounds) override; void process(KisPixelSelectionSP pixelSelection, const QRect &rect) override; }; class KRITAIMAGE_EXPORT KisBorderSelectionFilter : public KisSelectionFilter { public: - KisBorderSelectionFilter(qint32 xRadius, qint32 yRadius); + KisBorderSelectionFilter(qint32 xRadius, qint32 yRadius, bool fade); KUndo2MagicString name() override; QRect changeRect(const QRect &rect, KisDefaultBoundsBaseSP defaultBounds) override; void process(KisPixelSelectionSP pixelSelection, const QRect &rect) override; private: qint32 m_xRadius; qint32 m_yRadius; + bool m_antialiasing; }; class KRITAIMAGE_EXPORT KisFeatherSelectionFilter : public KisSelectionFilter { public: KisFeatherSelectionFilter(qint32 radius); KUndo2MagicString name() override; QRect changeRect(const QRect &rect, KisDefaultBoundsBaseSP defaultBounds) override; void process(KisPixelSelectionSP pixelSelection, const QRect &rect) override; private: qint32 m_radius; }; class KRITAIMAGE_EXPORT KisGrowSelectionFilter : public KisSelectionFilter { public: KisGrowSelectionFilter(qint32 xRadius, qint32 yRadius); KUndo2MagicString name() override; QRect changeRect(const QRect &rect, KisDefaultBoundsBaseSP defaultBounds) override; void process(KisPixelSelectionSP pixelSelection, const QRect &rect) override; private: qint32 m_xRadius; qint32 m_yRadius; }; class KRITAIMAGE_EXPORT KisShrinkSelectionFilter : public KisSelectionFilter { public: KisShrinkSelectionFilter(qint32 xRadius, qint32 yRadius, bool edgeLock); KUndo2MagicString name() override; QRect changeRect(const QRect &rect, KisDefaultBoundsBaseSP defaultBounds) override; void process(KisPixelSelectionSP pixelSelection, const QRect &rect) override; private: qint32 m_xRadius; qint32 m_yRadius; qint32 m_edgeLock; }; class KRITAIMAGE_EXPORT KisSmoothSelectionFilter : public KisSelectionFilter { public: KUndo2MagicString name() override; QRect changeRect(const QRect &rect, KisDefaultBoundsBaseSP defaultBounds) override; void process(KisPixelSelectionSP pixelSelection, const QRect &rect) override; }; class KRITAIMAGE_EXPORT KisInvertSelectionFilter : public KisSelectionFilter { public: KUndo2MagicString name() override; QRect changeRect(const QRect &rect, KisDefaultBoundsBaseSP defaultBounds) override; void process(KisPixelSelectionSP pixelSelection, const QRect &rect) override; }; #endif // KIS_SELECTION_FILTERS_H diff --git a/libs/libkis/Selection.cpp b/libs/libkis/Selection.cpp index 3c4989c758..00c913aef3 100644 --- a/libs/libkis/Selection.cpp +++ b/libs/libkis/Selection.cpp @@ -1,334 +1,334 @@ /* * Copyright (c) 2016 Boudewijn Rempt * * This program 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; 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 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 "Selection.h" #include #include "kis_iterator_ng.h" #include #include #include #include #include #include #include #include struct Selection::Private { Private() {} KisSelectionSP selection; }; Selection::Selection(KisSelectionSP selection, QObject *parent) : QObject(parent) , d(new Private) { d->selection = selection; } Selection::Selection(QObject *parent) : QObject(parent) , d(new Private) { d->selection = new KisSelection(); } Selection::~Selection() { delete d; } bool Selection::operator==(const Selection &other) const { return (d->selection == other.d->selection); } bool Selection::operator!=(const Selection &other) const { return !(operator==(other)); } Selection *Selection::duplicate() const { return new Selection(d->selection ? new KisSelection(*d->selection) : new KisSelection()); } int Selection::width() const { if (!d->selection) return 0; return d->selection->selectedExactRect().width(); } int Selection::height() const { if (!d->selection) return 0; return d->selection->selectedExactRect().height(); } int Selection::x() const { if (!d->selection) return 0; int xPos = d->selection->x(); if (d->selection->hasNonEmptyPixelSelection()) { xPos = d->selection->selectedExactRect().x(); } return xPos; } int Selection::y() const { if (!d->selection) return 0; int yPos = d->selection->y(); if (d->selection->hasNonEmptyPixelSelection()) { yPos = d->selection->selectedExactRect().y(); } return yPos; } void Selection::move(int x, int y) { if (!d->selection) return; d->selection->pixelSelection()->moveTo(QPoint(x, y)); } void Selection::clear() { if (!d->selection) return; d->selection->clear(); } void Selection::contract(int value) { if (!d->selection) return; d->selection->pixelSelection()->select(QRect(x(), y(), width() - value, height() - value)); } void Selection::copy(Node *node) { if (!node) return; if (!d->selection) return; if (node->node()->exactBounds().isEmpty()) return; if (!node->node()->hasEditablePaintDevice()) return; KisPaintDeviceSP dev = node->node()->paintDevice(); KisPaintDeviceSP clip = new KisPaintDevice(dev->colorSpace()); KisPaintDeviceSP selectionProjection = d->selection->projection(); const KoColorSpace *cs = clip->colorSpace(); const KoColorSpace *selCs = d->selection->projection()->colorSpace(); QRect rc = d->selection->selectedExactRect(); KisPainter::copyAreaOptimized(QPoint(), dev, clip, rc); KisHLineIteratorSP layerIt = clip->createHLineIteratorNG(0, 0, rc.width()); KisHLineConstIteratorSP selectionIt = selectionProjection->createHLineIteratorNG(rc.x(), rc.y(), rc.width()); for (qint32 y = 0; y < rc.height(); y++) { for (qint32 x = 0; x < rc.width(); x++) { qreal dstAlpha = cs->opacityF(layerIt->rawData()); qreal sel = selCs->opacityF(selectionIt->oldRawData()); qreal newAlpha = sel * dstAlpha / (1.0 - dstAlpha + sel * dstAlpha); float mask = newAlpha / dstAlpha; cs->applyAlphaNormedFloatMask(layerIt->rawData(), &mask, 1); layerIt->nextPixel(); selectionIt->nextPixel(); } layerIt->nextRow(); selectionIt->nextRow(); } KisClipboard::instance()->setClip(clip, rc.topLeft()); } void Selection::cut(Node* node) { if (!node) return; if (!d->selection) return; if (node->node()->exactBounds().isEmpty()) return; if (!node->node()->hasEditablePaintDevice()) return; KisPaintDeviceSP dev = node->node()->paintDevice(); copy(node); dev->clearSelection(d->selection); node->node()->setDirty(d->selection->selectedExactRect()); } void Selection::paste(Node *destination, int x, int y) { if (!destination) return; if (!d->selection) return; if (!KisClipboard::instance()->hasClip()) return; KisPaintDeviceSP src = KisClipboard::instance()->clip(QRect(), false); KisPaintDeviceSP dst = destination->node()->paintDevice(); if (!dst) return; KisPainter::copyAreaOptimized(QPoint(x, y), src, dst, src->exactBounds(), d->selection); destination->node()->setDirty(); } void Selection::erode() { if (!d->selection) return; KisErodeSelectionFilter esf; QRect rc = esf.changeRect(d->selection->selectedExactRect(), d->selection->pixelSelection()->defaultBounds()); esf.process(d->selection->pixelSelection(), rc); } void Selection::dilate() { if (!d->selection) return; KisDilateSelectionFilter dsf; QRect rc = dsf.changeRect(d->selection->selectedExactRect(), d->selection->pixelSelection()->defaultBounds()); dsf.process(d->selection->pixelSelection(), rc); } void Selection::border(int xRadius, int yRadius) { if (!d->selection) return; - KisBorderSelectionFilter sf(xRadius, yRadius); + KisBorderSelectionFilter sf(xRadius, yRadius, true); QRect rc = sf.changeRect(d->selection->selectedExactRect(), d->selection->pixelSelection()->defaultBounds()); sf.process(d->selection->pixelSelection(), rc); } void Selection::feather(int radius) { if (!d->selection) return; KisFeatherSelectionFilter fsf(radius); QRect rc = fsf.changeRect(d->selection->selectedExactRect(), d->selection->pixelSelection()->defaultBounds()); fsf.process(d->selection->pixelSelection(), rc); } void Selection::grow(int xradius, int yradius) { if (!d->selection) return; KisGrowSelectionFilter gsf(xradius, yradius); QRect rc = gsf.changeRect(d->selection->selectedExactRect(), d->selection->pixelSelection()->defaultBounds()); gsf.process(d->selection->pixelSelection(), rc); } void Selection::shrink(int xRadius, int yRadius, bool edgeLock) { if (!d->selection) return; KisShrinkSelectionFilter sf(xRadius, yRadius, edgeLock); QRect rc = sf.changeRect(d->selection->selectedExactRect(), d->selection->pixelSelection()->defaultBounds()); sf.process(d->selection->pixelSelection(), rc); } void Selection::smooth() { if (!d->selection) return; KisSmoothSelectionFilter sf; QRect rc = sf.changeRect(d->selection->selectedExactRect(), d->selection->pixelSelection()->defaultBounds()); sf.process(d->selection->pixelSelection(), rc); } void Selection::invert() { if (!d->selection) return; KisInvertSelectionFilter sf; QRect rc = sf.changeRect(d->selection->selectedExactRect(), d->selection->pixelSelection()->defaultBounds()); sf.process(d->selection->pixelSelection(), rc); } void Selection::resize(int w, int h) { if (!d->selection) return; d->selection->pixelSelection()->select(QRect(x(), y(), w, h)); } void Selection::select(int x, int y, int w, int h, int value) { if (!d->selection) return; d->selection->pixelSelection()->select(QRect(x, y, w, h), value); } void Selection::selectAll(Node *node, int value) { if (!d->selection) return; d->selection->pixelSelection()->select(node->node()->exactBounds(), value); } void Selection::replace(Selection *selection) { if (!d->selection) return; d->selection->pixelSelection()->applySelection(selection->selection()->pixelSelection(), SELECTION_REPLACE); } void Selection::add(Selection *selection) { if (!d->selection) return; d->selection->pixelSelection()->applySelection(selection->selection()->pixelSelection(), SELECTION_ADD); } void Selection::subtract(Selection *selection) { if (!d->selection) return; d->selection->pixelSelection()->applySelection(selection->selection()->pixelSelection(), SELECTION_SUBTRACT); } void Selection::intersect(Selection *selection) { if (!d->selection) return; d->selection->pixelSelection()->applySelection(selection->selection()->pixelSelection(), SELECTION_INTERSECT); } void Selection::symmetricdifference(Selection *selection) { if (!d->selection) return; d->selection->pixelSelection()->applySelection(selection->selection()->pixelSelection(), SELECTION_SYMMETRICDIFFERENCE); } QByteArray Selection::pixelData(int x, int y, int w, int h) const { QByteArray ba; if (!d->selection) return ba; KisPaintDeviceSP dev = d->selection->projection(); quint8 *data = new quint8[w * h]; dev->readBytes(data, x, y, w, h); ba = QByteArray((const char*)data, (int)(w * h)); delete[] data; return ba; } void Selection::setPixelData(QByteArray value, int x, int y, int w, int h) { if (!d->selection) return; KisPixelSelectionSP dev = d->selection->pixelSelection(); if (!dev) return; dev->writeBytes((const quint8*)value.constData(), x, y, w, h); } KisSelectionSP Selection::selection() const { return d->selection; } diff --git a/libs/libqml/KisSelectionExtras.cpp b/libs/libqml/KisSelectionExtras.cpp index dc7d7045a5..a2629ae33c 100644 --- a/libs/libqml/KisSelectionExtras.cpp +++ b/libs/libqml/KisSelectionExtras.cpp @@ -1,62 +1,62 @@ /* This file is part of the KDE project * Copyright (C) 2013 Camilla Boemann * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * 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 * Library General Public License for more details. * * You should have received a copy of the GNU Library General Public License * along with this library; see the file COPYING.LIB. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #include "KisSelectionExtras.h" #include #include #include KisSelectionExtras::KisSelectionExtras(KisViewManager *view) : m_view(view) { } KisSelectionExtras::~KisSelectionExtras() { } void KisSelectionExtras::grow(qint32 xradius, qint32 yradius) { KisSelectionFilter *filter = new KisGrowSelectionFilter(xradius, yradius); KisFilterSelectionOperation opr("grow-oper"); opr.runFilter(filter, m_view, KisOperationConfiguration()); } void KisSelectionExtras::shrink(qint32 xradius, qint32 yradius, bool edge_lock) { KisSelectionFilter *filter = new KisShrinkSelectionFilter(xradius, yradius, edge_lock); KisFilterSelectionOperation opr("shrink-oper"); opr.runFilter(filter, m_view, KisOperationConfiguration()); } void KisSelectionExtras::border(qint32 xradius, qint32 yradius) { - KisSelectionFilter *filter = new KisBorderSelectionFilter(xradius, yradius); + KisSelectionFilter *filter = new KisBorderSelectionFilter(xradius, yradius, true); KisFilterSelectionOperation opr("border-oper"); opr.runFilter(filter, m_view, KisOperationConfiguration()); } void KisSelectionExtras::feather(qint32 radius) { KisSelectionFilter *filter = new KisFeatherSelectionFilter(radius); KisFilterSelectionOperation opr("feather-oper"); opr.runFilter(filter, m_view, KisOperationConfiguration()); } diff --git a/plugins/extensions/modify_selection/dlg_border_selection.cc b/plugins/extensions/modify_selection/dlg_border_selection.cc index 50640acc55..ba41139043 100644 --- a/plugins/extensions/modify_selection/dlg_border_selection.cc +++ b/plugins/extensions/modify_selection/dlg_border_selection.cc @@ -1,105 +1,129 @@ /* * dlg_border_selection.cc - part of Krita * * Copyright (c) 2006 Michael Thaler * Copyright (c) 2013 Juan Palacios * * 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 "dlg_border_selection.h" #include #include #include #include #include WdgBorderSelection::WdgBorderSelection(QWidget* parent, KisViewManager *view) : KisOperationUIWidget(i18n("Border Selection"), parent) , m_width(1) { Q_ASSERT(view); KisImageWSP image = view->image(); Q_ASSERT(image); m_resolution = image->yRes(); setupUi(this); spbWidth->setValue(m_width); spbWidth->setFocus(); spbWidth->setVisible(true); spbWidthDouble->setVisible(false); cmbUnit->addItems(KoUnit::listOfUnitNameForUi()); cmbUnit->setCurrentIndex(KoUnit(KoUnit::Pixel).indexInListForUi()); // ensure that both spinboxes request the same horizontal size KisSizeGroup *spbGroup = new KisSizeGroup(this); spbGroup->addWidget(spbWidth); spbGroup->addWidget(spbWidthDouble); connect(spbWidth, SIGNAL(valueChanged(int)), this, SLOT(slotWidthChanged(int))); connect(spbWidthDouble, SIGNAL(valueChanged(double)), this, SLOT(slotWidthChanged(double))); connect(cmbUnit, SIGNAL(currentIndexChanged(int)), this, SLOT(slotUnitChanged(int))); + connect(chkAntialiasing, SIGNAL(toggled(bool)), this, SLOT(slotAntialiasingChanged(bool))); + slotUpdateAntialiasingAvailability(); } void WdgBorderSelection::slotWidthChanged(int width) { slotWidthChanged((double) width); } void WdgBorderSelection::slotWidthChanged(double width) { const KoUnit selectedUnit = KoUnit::fromListForUi(cmbUnit->currentIndex()); const double resWidth = (selectedUnit == KoUnit(KoUnit::Pixel)) ? width : (width * m_resolution); m_width = qRound(selectedUnit.fromUserValue(resWidth)); + slotUpdateAntialiasingAvailability(); } void WdgBorderSelection::slotUnitChanged(int index) { updateWidthUIValue(m_width); const KoUnit selectedUnit = KoUnit::fromListForUi(index); if (selectedUnit != KoUnit(KoUnit::Pixel)) { spbWidth->setVisible(false); spbWidthDouble->setVisible(true); } else { spbWidth->setVisible(true); spbWidthDouble->setVisible(false); } } +void WdgBorderSelection::slotAntialiasingChanged(bool value) +{ + m_antialiasing = value; +} + +void WdgBorderSelection::slotUpdateAntialiasingAvailability() +{ + const bool antialiasingEnabled = m_width > 1; + + if (antialiasingEnabled && !chkAntialiasing->isEnabled()) { + chkAntialiasing->setChecked(m_savedAntialiasing); + } else if (!antialiasingEnabled && chkAntialiasing->isEnabled()) { + m_savedAntialiasing = chkAntialiasing->isChecked(); + chkAntialiasing->setChecked(false); + } + + chkAntialiasing->setEnabled(antialiasingEnabled); + slotAntialiasingChanged(chkAntialiasing->isChecked()); +} + void WdgBorderSelection::updateWidthUIValue(double value) { const KoUnit selectedUnit = KoUnit::fromListForUi(cmbUnit->currentIndex()); if (selectedUnit != KoUnit(KoUnit::Pixel)) { spbWidthDouble->blockSignals(true); spbWidthDouble->setValue(selectedUnit.toUserValue(value / m_resolution)); spbWidthDouble->blockSignals(false); } else { const int finalValue = (selectedUnit == KoUnit(KoUnit::Point)) ? qRound(value / m_resolution) : value; spbWidth->blockSignals(true); spbWidth->setValue(selectedUnit.toUserValue(finalValue)); spbWidth->blockSignals(false); } } void WdgBorderSelection::getConfiguration(KisOperationConfigurationSP config) { config->setProperty("x-radius", m_width); config->setProperty("y-radius", m_width); + config->setProperty("antialiasing", m_antialiasing); } diff --git a/plugins/extensions/modify_selection/dlg_border_selection.h b/plugins/extensions/modify_selection/dlg_border_selection.h index 6592207063..3370251f17 100644 --- a/plugins/extensions/modify_selection/dlg_border_selection.h +++ b/plugins/extensions/modify_selection/dlg_border_selection.h @@ -1,49 +1,53 @@ /* * dlg_border_selection.h -- part of Krita * * Copyright (c) 2006 Michael Thaler * Copyright (c) 2013 Juan Palacios * * 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. */ #ifndef DLG_BORDER_SELECTION_H #define DLG_BORDER_SELECTION_H #include "ui_wdg_border_selection.h" #include class KisViewManager; class WdgBorderSelection : public KisOperationUIWidget, public Ui::WdgBorderSelection { Q_OBJECT public: WdgBorderSelection(QWidget *parent, KisViewManager* view); void getConfiguration(KisOperationConfigurationSP config) override; private Q_SLOTS: void slotWidthChanged(int width); void slotWidthChanged(double width); void slotUnitChanged(int index); + void slotAntialiasingChanged(bool value); + void slotUpdateAntialiasingAvailability(); private: void updateWidthUIValue(double value); double m_resolution; int m_width; + bool m_antialiasing = false; + bool m_savedAntialiasing = false; }; #endif // DLG_BORDER_SELECTION_H diff --git a/plugins/extensions/modify_selection/modify_selection_operations.cpp b/plugins/extensions/modify_selection/modify_selection_operations.cpp index 572049fcc9..2d3185d519 100644 --- a/plugins/extensions/modify_selection/modify_selection_operations.cpp +++ b/plugins/extensions/modify_selection/modify_selection_operations.cpp @@ -1,61 +1,62 @@ /* * Copyright (c) 2012 Dmitry Kazakov * Copyright (c) 2013 Sven Langkamp * * 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 "modify_selection_operations.h" #include void GrowSelectionOperation::runFromXML(KisViewManager* view, const KisOperationConfiguration& config) { int xradius = config.getInt("x-radius", 1); int yradius = config.getInt("y-radius", 1); KisSelectionFilter* filter = new KisGrowSelectionFilter(xradius, yradius); runFilter(filter, view, config); } void ShrinkSelectionOperation::runFromXML(KisViewManager* view, const KisOperationConfiguration& config) { int xradius = config.getInt("x-radius", 1); int yradius = config.getInt("y-radius", 1); bool edgeLock = config.getBool("edgeLock", false); KisSelectionFilter* filter = new KisShrinkSelectionFilter(xradius, yradius, edgeLock); runFilter(filter, view, config); } void BorderSelectionOperation::runFromXML(KisViewManager* view, const KisOperationConfiguration& config) { int xradius = config.getInt("x-radius", 1); int yradius = config.getInt("y-radius", 1); - KisSelectionFilter* filter = new KisBorderSelectionFilter(xradius, yradius); + bool antialiasing = config.getInt("antialiasing", false); + KisSelectionFilter* filter = new KisBorderSelectionFilter(xradius, yradius, antialiasing); runFilter(filter, view, config); } void FeatherSelectionOperation::runFromXML(KisViewManager* view, const KisOperationConfiguration& config) { int radius = config.getInt("radius", 1); KisSelectionFilter* filter = new KisFeatherSelectionFilter(radius); runFilter(filter, view, config); } void SmoothSelectionOperation::runFromXML(KisViewManager* view, const KisOperationConfiguration& config) { Q_UNUSED(config); KisSelectionFilter* filter = new KisSmoothSelectionFilter(); runFilter(filter, view, config); } diff --git a/plugins/extensions/modify_selection/wdg_border_selection.ui b/plugins/extensions/modify_selection/wdg_border_selection.ui index f53f94bd8e..a2a7a9a2ce 100644 --- a/plugins/extensions/modify_selection/wdg_border_selection.ui +++ b/plugins/extensions/modify_selection/wdg_border_selection.ui @@ -1,136 +1,143 @@ WdgBorderSelection 0 0 - 364 - 89 + 384 + 116 - - - - 1 - - - 100000 - - - 1 - - - - - - - 4 - - - 0.000100000000000 + + + + Qt::Vertical - - 10000.000000000000000 + + QSizePolicy::MinimumExpanding - - 0.100000000000000 + + + 20 + 16 + - + - - + + - Qt::Vertical + Qt::Horizontal - QSizePolicy::Fixed + QSizePolicy::MinimumExpanding - 20 - 16 + 16 + 20 + + + + Border width: + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + Qt::Horizontal QSizePolicy::Fixed 16 20 - - + + Qt::Vertical - QSizePolicy::MinimumExpanding + QSizePolicy::Fixed 20 16 - - - - Qt::Horizontal + + + + + + + 1 - - QSizePolicy::MinimumExpanding + + 100000 - - - 16 - 20 - + + 1 - + - - - - Border width: + + + + 4 - - Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + 0.000100000000000 + + + 10000.000000000000000 + + + 0.100000000000000 - - + + + + Anti-aliasing + + KisIntParseSpinBox QSpinBox
kis_int_parse_spin_box.h
KisDoubleParseSpinBox QDoubleSpinBox
kis_double_parse_spin_box.h