File indexing completed on 2024-05-19 04:26:34
0001 /* 0002 * SPDX-FileCopyrightText: 2005 Michael Thaler 0003 * SPDX-FileCopyrightText: 2011 Dmitry Kazakov <dimula73@gmail.com> 0004 * 0005 * SPDX-License-Identifier: GPL-2.0-or-later 0006 */ 0007 0008 #include "kis_selection_filters.h" 0009 0010 #include <algorithm> 0011 0012 #include <klocalizedstring.h> 0013 0014 #include <KoColorSpace.h> 0015 #include "kis_convolution_painter.h" 0016 #include "kis_convolution_kernel.h" 0017 #include "kis_pixel_selection.h" 0018 #include <kis_sequential_iterator.h> 0019 0020 #define MAX(a, b) ((a) > (b) ? (a) : (b)) 0021 #define MIN(a, b) ((a) < (b) ? (a) : (b)) 0022 #define RINT(x) floor ((x) + 0.5) 0023 0024 KisSelectionFilter::~KisSelectionFilter() 0025 { 0026 } 0027 0028 KUndo2MagicString KisSelectionFilter::name() 0029 { 0030 return KUndo2MagicString(); 0031 } 0032 0033 QRect KisSelectionFilter::changeRect(const QRect &rect, KisDefaultBoundsBaseSP defaultBounds) 0034 { 0035 Q_UNUSED(defaultBounds); 0036 return rect; 0037 } 0038 0039 void KisSelectionFilter::computeBorder(qint32* circ, qint32 xradius, qint32 yradius) 0040 { 0041 qint32 i; 0042 qint32 diameter = xradius * 2 + 1; 0043 double tmp; 0044 0045 for (i = 0; i < diameter; i++) { 0046 if (i > xradius) 0047 tmp = (i - xradius) - 0.5; 0048 else if (i < xradius) 0049 tmp = (xradius - i) - 0.5; 0050 else 0051 tmp = 0.0; 0052 0053 double divisor = (double) xradius; 0054 if (divisor == 0.0) { 0055 divisor = 1.0; 0056 } 0057 circ[i] = (qint32) RINT(yradius * sqrt(xradius * xradius - tmp * tmp) / divisor); 0058 } 0059 } 0060 0061 void KisSelectionFilter::rotatePointers(quint8** p, quint32 n) 0062 { 0063 quint32 i; 0064 quint8 *p0 = p[0]; 0065 for (i = 0; i < n - 1; i++) { 0066 p[i] = p[i + 1]; 0067 } 0068 p[i] = p0; 0069 } 0070 0071 void KisSelectionFilter::computeTransition(quint8* transition, quint8** buf, qint32 width) 0072 { 0073 qint32 x = 0; 0074 0075 if (width == 1) { 0076 if (buf[1][x] > 127 && (buf[0][x] < 128 || buf[2][x] < 128)) 0077 transition[x] = 255; 0078 else 0079 transition[x] = 0; 0080 return; 0081 } 0082 if (buf[1][x] > 127) { 0083 if (buf[0][x] < 128 || buf[0][x + 1] < 128 || 0084 buf[1][x + 1] < 128 || 0085 buf[2][x] < 128 || buf[2][x + 1] < 128) 0086 transition[x] = 255; 0087 else 0088 transition[x] = 0; 0089 } else 0090 transition[x] = 0; 0091 for (qint32 x = 1; x < width - 1; x++) { 0092 if (buf[1][x] >= 128) { 0093 if (buf[0][x - 1] < 128 || buf[0][x] < 128 || buf[0][x + 1] < 128 || 0094 buf[1][x - 1] < 128 || buf[1][x + 1] < 128 || 0095 buf[2][x - 1] < 128 || buf[2][x] < 128 || buf[2][x + 1] < 128) 0096 transition[x] = 255; 0097 else 0098 transition[x] = 0; 0099 } else 0100 transition[x] = 0; 0101 } 0102 if (buf[1][x] >= 128) { 0103 if (buf[0][x - 1] < 128 || buf[0][x] < 128 || 0104 buf[1][x - 1] < 128 || 0105 buf[2][x - 1] < 128 || buf[2][x] < 128) 0106 transition[x] = 255; 0107 else 0108 transition[x] = 0; 0109 } else 0110 transition[x] = 0; 0111 } 0112 0113 0114 KUndo2MagicString KisErodeSelectionFilter::name() 0115 { 0116 return kundo2_i18n("Erode Selection"); 0117 } 0118 0119 QRect KisErodeSelectionFilter::changeRect(const QRect& rect, KisDefaultBoundsBaseSP defaultBounds) 0120 { 0121 Q_UNUSED(defaultBounds); 0122 0123 const qint32 radius = 1; 0124 return rect.adjusted(-radius, -radius, radius, radius); 0125 } 0126 0127 void KisErodeSelectionFilter::process(KisPixelSelectionSP pixelSelection, const QRect& rect) 0128 { 0129 // Erode (radius 1 pixel) a mask (1bpp) 0130 quint8* buf[3]; 0131 0132 qint32 width = rect.width(); 0133 qint32 height = rect.height(); 0134 0135 quint8* out = new quint8[width]; 0136 for (qint32 i = 0; i < 3; i++) 0137 buf[i] = new quint8[width + 2]; 0138 0139 0140 // load top of image 0141 pixelSelection->readBytes(buf[0] + 1, rect.x(), rect.y(), width, 1); 0142 0143 buf[0][0] = buf[0][1]; 0144 buf[0][width + 1] = buf[0][width]; 0145 0146 memcpy(buf[1], buf[0], width + 2); 0147 0148 for (qint32 y = 0; y < height; y++) { 0149 if (y + 1 < height) { 0150 pixelSelection->readBytes(buf[2] + 1, rect.x(), rect.y() + y + 1, width, 1); 0151 0152 buf[2][0] = buf[2][1]; 0153 buf[2][width + 1] = buf[2][width]; 0154 } else { 0155 memcpy(buf[2], buf[1], width + 2); 0156 } 0157 0158 for (qint32 x = 0 ; x < width; x++) { 0159 qint32 min = 255; 0160 0161 if (buf[0][x+1] < min) min = buf[0][x+1]; 0162 if (buf[1][x] < min) min = buf[1][x]; 0163 if (buf[1][x+1] < min) min = buf[1][x+1]; 0164 if (buf[1][x+2] < min) min = buf[1][x+2]; 0165 if (buf[2][x+1] < min) min = buf[2][x+1]; 0166 0167 out[x] = min; 0168 } 0169 0170 pixelSelection->writeBytes(out, rect.x(), rect.y() + y, width, 1); 0171 rotatePointers(buf, 3); 0172 } 0173 0174 for (qint32 i = 0; i < 3; i++) 0175 delete[] buf[i]; 0176 delete[] out; 0177 } 0178 0179 0180 KUndo2MagicString KisDilateSelectionFilter::name() 0181 { 0182 return kundo2_i18n("Dilate Selection"); 0183 } 0184 0185 QRect KisDilateSelectionFilter::changeRect(const QRect& rect, KisDefaultBoundsBaseSP defaultBounds) 0186 { 0187 Q_UNUSED(defaultBounds); 0188 0189 const qint32 radius = 1; 0190 return rect.adjusted(-radius, -radius, radius, radius); 0191 } 0192 0193 void KisDilateSelectionFilter::process(KisPixelSelectionSP pixelSelection, const QRect& rect) 0194 { 0195 // dilate (radius 1 pixel) a mask (1bpp) 0196 quint8* buf[3]; 0197 0198 qint32 width = rect.width(); 0199 qint32 height = rect.height(); 0200 0201 quint8* out = new quint8[width]; 0202 for (qint32 i = 0; i < 3; i++) 0203 buf[i] = new quint8[width + 2]; 0204 0205 0206 // load top of image 0207 pixelSelection->readBytes(buf[0] + 1, rect.x(), rect.y(), width, 1); 0208 0209 buf[0][0] = buf[0][1]; 0210 buf[0][width + 1] = buf[0][width]; 0211 0212 memcpy(buf[1], buf[0], width + 2); 0213 0214 for (qint32 y = 0; y < height; y++) { 0215 if (y + 1 < height) { 0216 pixelSelection->readBytes(buf[2] + 1, rect.x(), rect.y() + y + 1, width, 1); 0217 0218 buf[2][0] = buf[2][1]; 0219 buf[2][width + 1] = buf[2][width]; 0220 } else { 0221 memcpy(buf[2], buf[1], width + 2); 0222 } 0223 0224 for (qint32 x = 0 ; x < width; x++) { 0225 qint32 max = 0; 0226 0227 if (buf[0][x+1] > max) max = buf[0][x+1]; 0228 if (buf[1][x] > max) max = buf[1][x]; 0229 if (buf[1][x+1] > max) max = buf[1][x+1]; 0230 if (buf[1][x+2] > max) max = buf[1][x+2]; 0231 if (buf[2][x+1] > max) max = buf[2][x+1]; 0232 0233 out[x] = max; 0234 } 0235 0236 pixelSelection->writeBytes(out, rect.x(), rect.y() + y, width, 1); 0237 rotatePointers(buf, 3); 0238 } 0239 0240 for (qint32 i = 0; i < 3; i++) 0241 delete[] buf[i]; 0242 delete[] out; 0243 } 0244 0245 0246 KisBorderSelectionFilter::KisBorderSelectionFilter(qint32 xRadius, qint32 yRadius, bool antialiasing) 0247 : m_xRadius(xRadius), 0248 m_yRadius(yRadius), 0249 m_antialiasing(antialiasing) 0250 { 0251 } 0252 0253 KUndo2MagicString KisBorderSelectionFilter::name() 0254 { 0255 return kundo2_i18n("Border Selection"); 0256 } 0257 0258 QRect KisBorderSelectionFilter::changeRect(const QRect& rect, KisDefaultBoundsBaseSP defaultBounds) 0259 { 0260 Q_UNUSED(defaultBounds); 0261 0262 return rect.adjusted(-m_xRadius, -m_yRadius, m_xRadius, m_yRadius); 0263 } 0264 0265 void KisBorderSelectionFilter::process(KisPixelSelectionSP pixelSelection, const QRect& rect) 0266 { 0267 if (m_xRadius <= 0 || m_yRadius <= 0) return; 0268 0269 quint8 *buf[3]; 0270 quint8 **density; 0271 quint8 **transition; 0272 0273 if (m_xRadius == 1 && m_yRadius == 1) { 0274 // optimize this case specifically 0275 quint8* source[3]; 0276 0277 for (qint32 i = 0; i < 3; i++) 0278 source[i] = new quint8[rect.width()]; 0279 0280 quint8* transition = new quint8[rect.width()]; 0281 0282 pixelSelection->readBytes(source[0], rect.x(), rect.y(), rect.width(), 1); 0283 memcpy(source[1], source[0], rect.width()); 0284 if (rect.height() > 1) 0285 pixelSelection->readBytes(source[2], rect.x(), rect.y() + 1, rect.width(), 1); 0286 else 0287 memcpy(source[2], source[1], rect.width()); 0288 0289 computeTransition(transition, source, rect.width()); 0290 pixelSelection->writeBytes(transition, rect.x(), rect.y(), rect.width(), 1); 0291 0292 for (qint32 y = 1; y < rect.height(); y++) { 0293 rotatePointers(source, 3); 0294 if (y + 1 < rect.height()) 0295 pixelSelection->readBytes(source[2], rect.x(), rect.y() + y + 1, rect.width(), 1); 0296 else 0297 memcpy(source[2], source[1], rect.width()); 0298 computeTransition(transition, source, rect.width()); 0299 pixelSelection->writeBytes(transition, rect.x(), rect.y() + y, rect.width(), 1); 0300 } 0301 0302 for (qint32 i = 0; i < 3; i++) 0303 delete[] source[i]; 0304 delete[] transition; 0305 return; 0306 } 0307 0308 qint32* max = new qint32[rect.width() + 2 * m_xRadius]; 0309 for (qint32 i = 0; i < (rect.width() + 2 * m_xRadius); i++) 0310 max[i] = m_yRadius + 2; 0311 max += m_xRadius; 0312 0313 for (qint32 i = 0; i < 3; i++) 0314 buf[i] = new quint8[rect.width()]; 0315 0316 transition = new quint8*[m_yRadius + 1]; 0317 for (qint32 i = 0; i < m_yRadius + 1; i++) { 0318 transition[i] = new quint8[rect.width() + 2 * m_xRadius]; 0319 memset(transition[i], 0, rect.width() + 2 * m_xRadius); 0320 transition[i] += m_xRadius; 0321 } 0322 quint8* out = new quint8[rect.width()]; 0323 density = new quint8*[2 * m_xRadius + 1]; 0324 density += m_xRadius; 0325 0326 for (qint32 x = 0; x < (m_xRadius + 1); x++) { // allocate density[][] 0327 density[ x] = new quint8[2 * m_yRadius + 1]; 0328 density[ x] += m_yRadius; 0329 density[-x] = density[x]; 0330 } 0331 0332 // compute density[][] 0333 if (m_antialiasing) { 0334 KIS_SAFE_ASSERT_RECOVER_NOOP(m_xRadius == m_yRadius && "anisotropic fading is not implemented"); 0335 const qreal maxRadius = 0.5 * (m_xRadius + m_yRadius); 0336 const qreal minRadius = maxRadius - 1.0; 0337 0338 for (qint32 x = 0; x < (m_xRadius + 1); x++) { 0339 double dist; 0340 quint8 a; 0341 0342 for (qint32 y = 0; y < (m_yRadius + 1); y++) { 0343 0344 dist = sqrt(pow2(x) + pow2(y)); 0345 0346 if (dist > maxRadius) { 0347 a = 0; 0348 } else if (dist > minRadius) { 0349 a = qRound((1.0 - dist + minRadius) * 255.0); 0350 } else { 0351 a = 255; 0352 } 0353 0354 density[ x][ y] = a; 0355 density[ x][-y] = a; 0356 density[-x][ y] = a; 0357 density[-x][-y] = a; 0358 } 0359 } 0360 0361 } else { 0362 for (qint32 x = 0; x < (m_xRadius + 1); x++) { 0363 double tmpx, tmpy, dist; 0364 quint8 a; 0365 0366 tmpx = x > 0.0 ? x - 0.5 : 0.0; 0367 0368 for (qint32 y = 0; y < (m_yRadius + 1); y++) { 0369 tmpy = y > 0.0 ? y - 0.5 : 0.0; 0370 0371 dist = (pow2(tmpy) / pow2(m_yRadius) + 0372 pow2(tmpx) / pow2(m_xRadius)); 0373 0374 a = dist <= 1.0 ? 255 : 0; 0375 0376 density[ x][ y] = a; 0377 density[ x][-y] = a; 0378 density[-x][ y] = a; 0379 density[-x][-y] = a; 0380 } 0381 } 0382 } 0383 0384 pixelSelection->readBytes(buf[0], rect.x(), rect.y(), rect.width(), 1); 0385 memcpy(buf[1], buf[0], rect.width()); 0386 if (rect.height() > 1) 0387 pixelSelection->readBytes(buf[2], rect.x(), rect.y() + 1, rect.width(), 1); 0388 else 0389 memcpy(buf[2], buf[1], rect.width()); 0390 computeTransition(transition[1], buf, rect.width()); 0391 0392 for (qint32 y = 1; y < m_yRadius && y + 1 < rect.height(); y++) { // set up top of image 0393 rotatePointers(buf, 3); 0394 pixelSelection->readBytes(buf[2], rect.x(), rect.y() + y + 1, rect.width(), 1); 0395 computeTransition(transition[y + 1], buf, rect.width()); 0396 } 0397 for (qint32 x = 0; x < rect.width(); x++) { // set up max[] for top of image 0398 max[x] = -(m_yRadius + 7); 0399 for (qint32 j = 1; j < m_yRadius + 1; j++) 0400 if (transition[j][x]) { 0401 max[x] = j; 0402 break; 0403 } 0404 } 0405 for (qint32 y = 0; y < rect.height(); y++) { // main calculation loop 0406 rotatePointers(buf, 3); 0407 rotatePointers(transition, m_yRadius + 1); 0408 if (y < rect.height() - (m_yRadius + 1)) { 0409 pixelSelection->readBytes(buf[2], rect.x(), rect.y() + y + m_yRadius + 1, rect.width(), 1); 0410 computeTransition(transition[m_yRadius], buf, rect.width()); 0411 } else 0412 memcpy(transition[m_yRadius], transition[m_yRadius - 1], rect.width()); 0413 0414 for (qint32 x = 0; x < rect.width(); x++) { // update max array 0415 if (max[x] < 1) { 0416 if (max[x] <= -m_yRadius) { 0417 if (transition[m_yRadius][x]) 0418 max[x] = m_yRadius; 0419 else 0420 max[x]--; 0421 } else if (transition[-max[x]][x]) 0422 max[x] = -max[x]; 0423 else if (transition[-max[x] + 1][x]) 0424 max[x] = -max[x] + 1; 0425 else 0426 max[x]--; 0427 } else 0428 max[x]--; 0429 if (max[x] < -m_yRadius - 1) 0430 max[x] = -m_yRadius - 1; 0431 } 0432 quint8 last_max = max[0][density[-1]]; 0433 qint32 last_index = 1; 0434 for (qint32 x = 0 ; x < rect.width(); x++) { // render scan line 0435 last_index--; 0436 if (last_index >= 0) { 0437 last_max = 0; 0438 for (qint32 i = m_xRadius; i >= 0; i--) 0439 if (max[x + i] <= m_yRadius && max[x + i] >= -m_yRadius && density[i][max[x+i]] > last_max) { 0440 last_max = density[i][max[x + i]]; 0441 last_index = i; 0442 } 0443 out[x] = last_max; 0444 } else { 0445 last_max = 0; 0446 for (qint32 i = m_xRadius; i >= -m_xRadius; i--) 0447 if (max[x + i] <= m_yRadius && max[x + i] >= -m_yRadius && density[i][max[x + i]] > last_max) { 0448 last_max = density[i][max[x + i]]; 0449 last_index = i; 0450 } 0451 out[x] = last_max; 0452 } 0453 if (last_max == 0) { 0454 qint32 i; 0455 for (i = x + 1; i < rect.width(); i++) { 0456 if (max[i] >= -m_yRadius) 0457 break; 0458 } 0459 if (i - x > m_xRadius) { 0460 for (; x < i - m_xRadius; x++) 0461 out[x] = 0; 0462 x--; 0463 } 0464 last_index = m_xRadius; 0465 } 0466 } 0467 pixelSelection->writeBytes(out, rect.x(), rect.y() + y, rect.width(), 1); 0468 } 0469 delete [] out; 0470 0471 for (qint32 i = 0; i < 3; i++) 0472 delete[] buf[i]; 0473 0474 max -= m_xRadius; 0475 delete[] max; 0476 0477 for (qint32 i = 0; i < m_yRadius + 1; i++) { 0478 transition[i] -= m_xRadius; 0479 delete transition[i]; 0480 } 0481 delete[] transition; 0482 0483 for (qint32 i = 0; i < m_xRadius + 1 ; i++) { 0484 density[i] -= m_yRadius; 0485 delete density[i]; 0486 } 0487 density -= m_xRadius; 0488 delete[] density; 0489 } 0490 0491 0492 KisFeatherSelectionFilter::KisFeatherSelectionFilter(qint32 radius) 0493 : m_radius(radius) 0494 { 0495 } 0496 0497 KUndo2MagicString KisFeatherSelectionFilter::name() 0498 { 0499 return kundo2_i18n("Feather Selection"); 0500 } 0501 0502 QRect KisFeatherSelectionFilter::changeRect(const QRect& rect, KisDefaultBoundsBaseSP defaultBounds) 0503 { 0504 Q_UNUSED(defaultBounds); 0505 0506 return rect.adjusted(-m_radius, -m_radius, 0507 m_radius, m_radius); 0508 } 0509 0510 void KisFeatherSelectionFilter::process(KisPixelSelectionSP pixelSelection, const QRect& rect) 0511 { 0512 // compute horizontal kernel 0513 const uint kernelSize = m_radius * 2 + 1; 0514 Eigen::Matrix<qreal, Eigen::Dynamic, Eigen::Dynamic> gaussianMatrix(1, kernelSize); 0515 0516 const qreal multiplicand = 1.0 / (2.0 * M_PI * m_radius * m_radius); 0517 const qreal exponentMultiplicand = 1.0 / (2.0 * m_radius * m_radius); 0518 0519 for (uint x = 0; x < kernelSize; x++) { 0520 uint xDistance = qAbs((int)m_radius - (int)x); 0521 gaussianMatrix(0, x) = multiplicand * exp( -(qreal)((xDistance * xDistance) + (m_radius * m_radius)) * exponentMultiplicand ); 0522 } 0523 0524 KisConvolutionKernelSP kernelHoriz = KisConvolutionKernel::fromMatrix(gaussianMatrix, 0, gaussianMatrix.sum()); 0525 KisConvolutionKernelSP kernelVertical = KisConvolutionKernel::fromMatrix(gaussianMatrix.transpose(), 0, gaussianMatrix.sum()); 0526 0527 KisPaintDeviceSP interm = new KisPaintDevice(pixelSelection->colorSpace()); 0528 interm->prepareClone(pixelSelection); 0529 0530 KisConvolutionPainter horizPainter(interm); 0531 horizPainter.setChannelFlags(interm->colorSpace()->channelFlags(false, true)); 0532 horizPainter.applyMatrix(kernelHoriz, pixelSelection, rect.topLeft(), rect.topLeft(), rect.size(), BORDER_REPEAT); 0533 horizPainter.end(); 0534 0535 KisConvolutionPainter verticalPainter(pixelSelection); 0536 verticalPainter.setChannelFlags(pixelSelection->colorSpace()->channelFlags(false, true)); 0537 verticalPainter.applyMatrix(kernelVertical, interm, rect.topLeft(), rect.topLeft(), rect.size(), BORDER_REPEAT); 0538 verticalPainter.end(); 0539 } 0540 0541 0542 KisGrowSelectionFilter::KisGrowSelectionFilter(qint32 xRadius, qint32 yRadius) 0543 : m_xRadius(xRadius) 0544 , m_yRadius(yRadius) 0545 { 0546 } 0547 0548 KUndo2MagicString KisGrowSelectionFilter::name() 0549 { 0550 return kundo2_i18n("Grow Selection"); 0551 } 0552 0553 QRect KisGrowSelectionFilter::changeRect(const QRect& rect, KisDefaultBoundsBaseSP defaultBounds) 0554 { 0555 Q_UNUSED(defaultBounds); 0556 0557 return rect.adjusted(-m_xRadius, -m_yRadius, m_xRadius, m_yRadius); 0558 } 0559 0560 void KisGrowSelectionFilter::process(KisPixelSelectionSP pixelSelection, const QRect& rect) 0561 { 0562 if (m_xRadius <= 0 || m_yRadius <= 0) return; 0563 0564 /** 0565 * Much code resembles Shrink filter, so please fix bugs 0566 * in both filters 0567 */ 0568 0569 quint8 **buf; // caches the region's pixel data 0570 quint8 **max; // caches the largest values for each column 0571 0572 max = new quint8* [rect.width() + 2 * m_xRadius]; 0573 buf = new quint8* [m_yRadius + 1]; 0574 for (qint32 i = 0; i < m_yRadius + 1; i++) { 0575 buf[i] = new quint8[rect.width()]; 0576 } 0577 quint8* buffer = new quint8[(rect.width() + 2 * m_xRadius) *(m_yRadius + 1)]; 0578 for (qint32 i = 0; i < rect.width() + 2 * m_xRadius; i++) { 0579 if (i < m_xRadius) 0580 max[i] = buffer; 0581 else if (i < rect.width() + m_xRadius) 0582 max[i] = &buffer[(m_yRadius + 1) * (i - m_xRadius)]; 0583 else 0584 max[i] = &buffer[(m_yRadius + 1) * (rect.width() + m_xRadius - 1)]; 0585 0586 for (qint32 j = 0; j < m_xRadius + 1; j++) 0587 max[i][j] = 0; 0588 } 0589 /* offset the max pointer by m_xRadius so the range of the array 0590 is [-m_xRadius] to [region->w + m_xRadius] */ 0591 max += m_xRadius; 0592 0593 quint8* out = new quint8[ rect.width()]; // holds the new scan line we are computing 0594 0595 qint32* circ = new qint32[ 2 * m_xRadius + 1 ]; // holds the y coords of the filter's mask 0596 computeBorder(circ, m_xRadius, m_yRadius); 0597 0598 /* offset the circ pointer by m_xRadius so the range of the array 0599 is [-m_xRadius] to [m_xRadius] */ 0600 circ += m_xRadius; 0601 0602 memset(buf[0], 0, rect.width()); 0603 for (qint32 i = 0; i < m_yRadius && i < rect.height(); i++) { // load top of image 0604 pixelSelection->readBytes(buf[i + 1], rect.x(), rect.y() + i, rect.width(), 1); 0605 } 0606 0607 for (qint32 x = 0; x < rect.width() ; x++) { // set up max for top of image 0608 max[x][0] = 0; // buf[0][x] is always 0 0609 max[x][1] = buf[1][x]; // MAX (buf[1][x], max[x][0]) always = buf[1][x] 0610 for (qint32 j = 2; j < m_yRadius + 1; j++) { 0611 max[x][j] = MAX(buf[j][x], max[x][j-1]); 0612 } 0613 } 0614 0615 for (qint32 y = 0; y < rect.height(); y++) { 0616 rotatePointers(buf, m_yRadius + 1); 0617 if (y < rect.height() - (m_yRadius)) 0618 pixelSelection->readBytes(buf[m_yRadius], rect.x(), rect.y() + y + m_yRadius, rect.width(), 1); 0619 else 0620 memset(buf[m_yRadius], 0, rect.width()); 0621 for (qint32 x = 0; x < rect.width(); x++) { /* update max array */ 0622 for (qint32 i = m_yRadius; i > 0; i--) { 0623 max[x][i] = MAX(MAX(max[x][i - 1], buf[i - 1][x]), buf[i][x]); 0624 } 0625 max[x][0] = buf[0][x]; 0626 } 0627 qint32 last_max = max[0][circ[-1]]; 0628 qint32 last_index = 1; 0629 for (qint32 x = 0; x < rect.width(); x++) { /* render scan line */ 0630 last_index--; 0631 if (last_index >= 0) { 0632 if (last_max == 255) 0633 out[x] = 255; 0634 else { 0635 last_max = 0; 0636 for (qint32 i = m_xRadius; i >= 0; i--) 0637 if (last_max < max[x + i][circ[i]]) { 0638 last_max = max[x + i][circ[i]]; 0639 last_index = i; 0640 } 0641 out[x] = last_max; 0642 } 0643 } else { 0644 last_index = m_xRadius; 0645 last_max = max[x + m_xRadius][circ[m_xRadius]]; 0646 for (qint32 i = m_xRadius - 1; i >= -m_xRadius; i--) 0647 if (last_max < max[x + i][circ[i]]) { 0648 last_max = max[x + i][circ[i]]; 0649 last_index = i; 0650 } 0651 out[x] = last_max; 0652 } 0653 } 0654 pixelSelection->writeBytes(out, rect.x(), rect.y() + y, rect.width(), 1); 0655 } 0656 /* undo the offsets to the pointers so we can free the malloced memory */ 0657 circ -= m_xRadius; 0658 max -= m_xRadius; 0659 0660 delete[] circ; 0661 delete[] buffer; 0662 delete[] max; 0663 for (qint32 i = 0; i < m_yRadius + 1; i++) 0664 delete[] buf[i]; 0665 delete[] buf; 0666 delete[] out; 0667 } 0668 0669 0670 KisShrinkSelectionFilter::KisShrinkSelectionFilter(qint32 xRadius, qint32 yRadius, bool edgeLock) 0671 : m_xRadius(xRadius) 0672 , m_yRadius(yRadius) 0673 , m_edgeLock(edgeLock) 0674 { 0675 } 0676 0677 KUndo2MagicString KisShrinkSelectionFilter::name() 0678 { 0679 return kundo2_i18n("Shrink Selection"); 0680 } 0681 0682 QRect KisShrinkSelectionFilter::changeRect(const QRect& rect, KisDefaultBoundsBaseSP defaultBounds) 0683 { 0684 return m_edgeLock ? defaultBounds->imageBorderRect() : rect; 0685 } 0686 0687 void KisShrinkSelectionFilter::process(KisPixelSelectionSP pixelSelection, const QRect& rect) 0688 { 0689 if (m_xRadius <= 0 || m_yRadius <= 0) return; 0690 0691 /* 0692 pretty much the same as fatten_region only different 0693 blame all bugs in this function on jaycox@gimp.org 0694 */ 0695 /* If edge_lock is true we assume that pixels outside the region 0696 we are passed are identical to the edge pixels. 0697 If edge_lock is false, we assume that pixels outside the region are 0 0698 */ 0699 quint8 **buf; // caches the region's pixels 0700 quint8 **max; // caches the smallest values for each column 0701 qint32 last_max, last_index; 0702 0703 max = new quint8* [rect.width() + 2 * m_xRadius]; 0704 buf = new quint8* [m_yRadius + 1]; 0705 for (qint32 i = 0; i < m_yRadius + 1; i++) { 0706 buf[i] = new quint8[rect.width()]; 0707 } 0708 0709 qint32 buffer_size = (rect.width() + 2 * m_xRadius + 1) * (m_yRadius + 1); 0710 quint8* buffer = new quint8[buffer_size]; 0711 0712 if (m_edgeLock) 0713 memset(buffer, 255, buffer_size); 0714 else 0715 memset(buffer, 0, buffer_size); 0716 0717 for (qint32 i = 0; i < rect.width() + 2 * m_xRadius; i++) { 0718 if (i < m_xRadius) 0719 if (m_edgeLock) 0720 max[i] = buffer; 0721 else 0722 max[i] = &buffer[(m_yRadius + 1) * (rect.width() + m_xRadius)]; 0723 else if (i < rect.width() + m_xRadius) 0724 max[i] = &buffer[(m_yRadius + 1) * (i - m_xRadius)]; 0725 else if (m_edgeLock) 0726 max[i] = &buffer[(m_yRadius + 1) * (rect.width() + m_xRadius - 1)]; 0727 else 0728 max[i] = &buffer[(m_yRadius + 1) * (rect.width() + m_xRadius)]; 0729 } 0730 if (!m_edgeLock) 0731 for (qint32 j = 0 ; j < m_xRadius + 1; j++) max[0][j] = 0; 0732 0733 // offset the max pointer by m_xRadius so the range of the array is [-m_xRadius] to [region->w + m_xRadius] 0734 max += m_xRadius; 0735 0736 quint8* out = new quint8[rect.width()]; // holds the new scan line we are computing 0737 0738 qint32* circ = new qint32[2 * m_xRadius + 1]; // holds the y coords of the filter's mask 0739 0740 computeBorder(circ, m_xRadius, m_yRadius); 0741 0742 // offset the circ pointer by m_xRadius so the range of the array is [-m_xRadius] to [m_xRadius] 0743 circ += m_xRadius; 0744 0745 for (qint32 i = 0; i < m_yRadius && i < rect.height(); i++) // load top of image 0746 pixelSelection->readBytes(buf[i + 1], rect.x(), rect.y() + i, rect.width(), 1); 0747 0748 if (m_edgeLock) 0749 memcpy(buf[0], buf[1], rect.width()); 0750 else 0751 memset(buf[0], 0, rect.width()); 0752 0753 0754 for (qint32 x = 0; x < rect.width(); x++) { // set up max for top of image 0755 max[x][0] = buf[0][x]; 0756 for (qint32 j = 1; j < m_yRadius + 1; j++) 0757 max[x][j] = MIN(buf[j][x], max[x][j-1]); 0758 } 0759 0760 for (qint32 y = 0; y < rect.height(); y++) { 0761 rotatePointers(buf, m_yRadius + 1); 0762 if (y < rect.height() - m_yRadius) 0763 pixelSelection->readBytes(buf[m_yRadius], rect.x(), rect.y() + y + m_yRadius, rect.width(), 1); 0764 else if (m_edgeLock) 0765 memcpy(buf[m_yRadius], buf[m_yRadius - 1], rect.width()); 0766 else 0767 memset(buf[m_yRadius], 0, rect.width()); 0768 0769 for (qint32 x = 0 ; x < rect.width(); x++) { // update max array 0770 for (qint32 i = m_yRadius; i > 0; i--) { 0771 max[x][i] = MIN(MIN(max[x][i - 1], buf[i - 1][x]), buf[i][x]); 0772 } 0773 max[x][0] = buf[0][x]; 0774 } 0775 last_max = max[0][circ[-1]]; 0776 last_index = 0; 0777 0778 for (qint32 x = 0 ; x < rect.width(); x++) { // render scan line 0779 last_index--; 0780 if (last_index >= 0) { 0781 if (last_max == 0) 0782 out[x] = 0; 0783 else { 0784 last_max = 255; 0785 for (qint32 i = m_xRadius; i >= 0; i--) 0786 if (last_max > max[x + i][circ[i]]) { 0787 last_max = max[x + i][circ[i]]; 0788 last_index = i; 0789 } 0790 out[x] = last_max; 0791 } 0792 } else { 0793 last_index = m_xRadius; 0794 last_max = max[x + m_xRadius][circ[m_xRadius]]; 0795 for (qint32 i = m_xRadius - 1; i >= -m_xRadius; i--) 0796 if (last_max > max[x + i][circ[i]]) { 0797 last_max = max[x + i][circ[i]]; 0798 last_index = i; 0799 } 0800 out[x] = last_max; 0801 } 0802 } 0803 pixelSelection->writeBytes(out, rect.x(), rect.y() + y, rect.width(), 1); 0804 } 0805 0806 // undo the offsets to the pointers so we can free the malloced memory 0807 circ -= m_xRadius; 0808 max -= m_xRadius; 0809 0810 delete[] circ; 0811 delete[] buffer; 0812 delete[] max; 0813 for (qint32 i = 0; i < m_yRadius + 1; i++) 0814 delete[] buf[i]; 0815 delete[] buf; 0816 delete[] out; 0817 } 0818 0819 0820 KUndo2MagicString KisSmoothSelectionFilter::name() 0821 { 0822 return kundo2_i18n("Smooth Selection"); 0823 } 0824 0825 QRect KisSmoothSelectionFilter::changeRect(const QRect& rect, KisDefaultBoundsBaseSP defaultBounds) 0826 { 0827 Q_UNUSED(defaultBounds); 0828 0829 const qint32 radius = 1; 0830 return rect.adjusted(-radius, -radius, radius, radius); 0831 } 0832 0833 void KisSmoothSelectionFilter::process(KisPixelSelectionSP pixelSelection, const QRect& rect) 0834 { 0835 // Simple convolution filter to smooth a mask (1bpp) 0836 quint8 *buf[3]; 0837 0838 qint32 width = rect.width(); 0839 qint32 height = rect.height(); 0840 0841 0842 quint8* out = new quint8[width]; 0843 for (qint32 i = 0; i < 3; i++) 0844 buf[i] = new quint8[width + 2]; 0845 0846 0847 // load top of image 0848 pixelSelection->readBytes(buf[0] + 1, rect.x(), rect.y(), width, 1); 0849 0850 buf[0][0] = buf[0][1]; 0851 buf[0][width + 1] = buf[0][width]; 0852 0853 memcpy(buf[1], buf[0], width + 2); 0854 0855 for (qint32 y = 0; y < height; y++) { 0856 if (y + 1 < height) { 0857 pixelSelection->readBytes(buf[2] + 1, rect.x(), rect.y() + y + 1, width, 1); 0858 0859 buf[2][0] = buf[2][1]; 0860 buf[2][width + 1] = buf[2][width]; 0861 } else { 0862 memcpy(buf[2], buf[1], width + 2); 0863 } 0864 0865 for (qint32 x = 0 ; x < width; x++) { 0866 qint32 value = (buf[0][x] + buf[0][x+1] + buf[0][x+2] + 0867 buf[1][x] + buf[2][x+1] + buf[1][x+2] + 0868 buf[2][x] + buf[1][x+1] + buf[2][x+2]); 0869 0870 out[x] = value / 9; 0871 } 0872 0873 pixelSelection->writeBytes(out, rect.x(), rect.y() + y, width, 1); 0874 rotatePointers(buf, 3); 0875 } 0876 0877 for (qint32 i = 0; i < 3; i++) 0878 delete[] buf[i]; 0879 delete[] out; 0880 } 0881 0882 0883 KUndo2MagicString KisInvertSelectionFilter::name() 0884 { 0885 return kundo2_i18n("Invert Selection"); 0886 } 0887 0888 QRect KisInvertSelectionFilter::changeRect(const QRect &rect, KisDefaultBoundsBaseSP defaultBounds) 0889 { 0890 Q_UNUSED(rect); 0891 return defaultBounds->bounds(); 0892 } 0893 0894 void KisInvertSelectionFilter::process(KisPixelSelectionSP pixelSelection, const QRect& rect) 0895 { 0896 Q_UNUSED(rect); 0897 0898 const QRect imageRect = pixelSelection->defaultBounds()->bounds(); 0899 const QRect selectionRect = pixelSelection->selectedExactRect(); 0900 0901 /** 0902 * A special treatment for the user-visible selection inversion: 0903 * 0904 * If the selection is fully contained inside the image, then 0905 * just invert it pixel-by-pixel without changing the default 0906 * pixel. It makes it selectedExactRect() work a little bit more 0907 * expected for the user (see bug 457820). 0908 * 0909 * If the selection spreads outside the image bounds, then 0910 * just invert it in a mathematical way adjusting the default 0911 * pixel. 0912 */ 0913 0914 if (!imageRect.contains(selectionRect)) { 0915 pixelSelection->invert(); 0916 } else { 0917 KisSequentialIterator it(pixelSelection, imageRect); 0918 while(it.nextPixel()) { 0919 *(it.rawData()) = MAX_SELECTED - *(it.rawData()); 0920 } 0921 pixelSelection->crop(imageRect); 0922 pixelSelection->invalidateOutlineCache(); 0923 } 0924 } 0925 0926 constexpr qint32 KisAntiAliasSelectionFilter::offsets[numSteps]; 0927 0928 KUndo2MagicString KisAntiAliasSelectionFilter::name() 0929 { 0930 return kundo2_i18n("Anti-Alias Selection"); 0931 } 0932 0933 bool KisAntiAliasSelectionFilter::getInterpolationValue(qint32 negativeSpanEndDistance, 0934 qint32 positiveSpanEndDistance, 0935 qint32 negativePixelDiff, 0936 qint32 positivePixelDiff, 0937 qint32 currentPixelDiff, 0938 bool negativeSpanExtremeValid, 0939 bool positiveSpanExtremeValid, 0940 qint32 *interpolationValue) const 0941 { 0942 // Since we search a limited number of steps in each direction of the 0943 // current pixel, the end pixel of the span may still belong to the edge. 0944 // So we check for that, and if that's the case we must not smooth the 0945 // current pixel 0946 const bool pixelDiffLessThanZero = currentPixelDiff < 0; 0947 quint32 distance; 0948 if (negativeSpanEndDistance < positiveSpanEndDistance) { 0949 if (!negativeSpanExtremeValid) { 0950 return false; 0951 } 0952 // The pixel is closer to the negative end 0953 const bool spanEndPixelDiffLessThanZero = negativePixelDiff < 0; 0954 if (pixelDiffLessThanZero == spanEndPixelDiffLessThanZero) { 0955 return false; 0956 } 0957 distance = negativeSpanEndDistance; 0958 } else { 0959 if (!positiveSpanExtremeValid) { 0960 return false; 0961 } 0962 // The pixel is closer to the positive end 0963 const bool spanEndPixelDiffLessThanZero = positivePixelDiff < 0; 0964 if (pixelDiffLessThanZero == spanEndPixelDiffLessThanZero) { 0965 return false; 0966 } 0967 distance = positiveSpanEndDistance; 0968 } 0969 const qint32 spanLength = positiveSpanEndDistance + negativeSpanEndDistance; 0970 *interpolationValue = ((distance << 8) / spanLength) + 128; 0971 return *interpolationValue >= 0; 0972 } 0973 0974 void KisAntiAliasSelectionFilter::findSpanExtreme(quint8 **scanlines, qint32 x, qint32 pixelOffset, 0975 qint32 rowMultiplier, qint32 colMultiplier, qint32 direction, 0976 qint32 pixelAvg, qint32 scaledGradient, qint32 currentPixelDiff, 0977 qint32 *spanEndDistance, qint32 *pixelDiff, bool *spanExtremeValid) const 0978 { 0979 *spanEndDistance = 0; 0980 *spanExtremeValid = true; 0981 for (qint32 i = 0; i < numSteps; ++i) { 0982 *spanEndDistance += offsets[i]; 0983 const qint32 row1 = currentScanlineIndex + (direction * *spanEndDistance * rowMultiplier); 0984 const qint32 col1 = x + horizontalBorderSize + (direction * *spanEndDistance * colMultiplier); 0985 const qint32 row2 = row1 + pixelOffset * colMultiplier; 0986 const qint32 col2 = col1 + pixelOffset * rowMultiplier; 0987 const quint8 *pixel1 = scanlines[row1] + col1; 0988 const quint8 *pixel2 = scanlines[row2] + col2; 0989 // Get how different are these edge pixels from the current pixels and 0990 // stop searching if they are too different 0991 *pixelDiff = ((*pixel1 + *pixel2) >> 1) - pixelAvg; 0992 if (qAbs(*pixelDiff) > scaledGradient) { 0993 // If this is the end of the span then check if the corner belongs 0994 // to a jagged border or to a right angled part of the shape 0995 qint32 pixelDiff2; 0996 if ((currentPixelDiff < 0 && *pixelDiff < 0) || (currentPixelDiff > 0 && *pixelDiff > 0)) { 0997 const qint32 row3 = row2 + pixelOffset * colMultiplier; 0998 const qint32 col3 = col2 + pixelOffset * rowMultiplier; 0999 const quint8 *pixel3 = scanlines[row3] + col3; 1000 pixelDiff2 = ((*pixel2 + *pixel3) >> 1) - pixelAvg; 1001 } else { 1002 const qint32 row3 = row1 - pixelOffset * colMultiplier; 1003 const qint32 col3 = col1 - pixelOffset * rowMultiplier; 1004 const quint8 *pixel3 = scanlines[row3] + col3; 1005 pixelDiff2 = ((*pixel1 + *pixel3) >> 1) - pixelAvg; 1006 } 1007 *spanExtremeValid = !(qAbs(pixelDiff2) > scaledGradient); 1008 break; 1009 } 1010 } 1011 } 1012 1013 void KisAntiAliasSelectionFilter::findSpanExtremes(quint8 **scanlines, qint32 x, qint32 pixelOffset, 1014 qint32 rowMultiplier, qint32 colMultiplier, 1015 qint32 pixelAvg, qint32 scaledGradient, qint32 currentPixelDiff, 1016 qint32 *negativeSpanEndDistance, qint32 *positiveSpanEndDistance, 1017 qint32 *negativePixelDiff, qint32 *positivePixelDiff, 1018 bool *negativeSpanExtremeValid, bool *positiveSpanExtremeValid) const 1019 { 1020 findSpanExtreme(scanlines, x, pixelOffset, rowMultiplier, colMultiplier, -1, pixelAvg, scaledGradient, 1021 currentPixelDiff, negativeSpanEndDistance, negativePixelDiff, negativeSpanExtremeValid); 1022 findSpanExtreme(scanlines, x, pixelOffset, rowMultiplier, colMultiplier, 1, pixelAvg, scaledGradient, 1023 currentPixelDiff, positiveSpanEndDistance, positivePixelDiff, positiveSpanExtremeValid); 1024 } 1025 1026 void KisAntiAliasSelectionFilter::process(KisPixelSelectionSP pixelSelection, const QRect &rect) 1027 { 1028 const quint8 defaultPixel = *pixelSelection->defaultPixel().data(); 1029 // Size of a scanline 1030 const quint32 bytesPerScanline = rect.width() + 2 * horizontalBorderSize; 1031 // Size of a scanline padded to a multiple of 8 1032 const quint32 bytesPerPaddedScanline = ((bytesPerScanline + 7) / 8) * 8; 1033 1034 // This buffer contains the number of consecutive scanlines needed to 1035 // process the current scanline 1036 QVector<quint8> buffer(bytesPerPaddedScanline * numberOfScanlines); 1037 1038 // These pointers point to the individual scanlines in the buffer 1039 quint8 *scanlines[numberOfScanlines]; 1040 for (quint32 i = 0; i < numberOfScanlines; ++i) { 1041 scanlines[i] = buffer.data() + i * bytesPerPaddedScanline; 1042 } 1043 1044 // Initialize the scanlines 1045 // Set the border scanlines on the top 1046 for (qint32 i = 0; i < verticalBorderSize; ++i) { 1047 memset(scanlines[i], defaultPixel, bytesPerScanline); 1048 } 1049 // Copy the first scanlines of the image 1050 const quint32 numberOfFirstRows = qMin(rect.height(), numberOfScanlines - verticalBorderSize); 1051 for (quint32 i = verticalBorderSize; i < verticalBorderSize + numberOfFirstRows; ++i) { 1052 // Set the border pixels on the left 1053 memset(scanlines[i], defaultPixel, horizontalBorderSize); 1054 // Copy the pixel data 1055 pixelSelection->readBytes(scanlines[i] + horizontalBorderSize, rect.x(), rect.y() + i - verticalBorderSize, rect.width(), 1); 1056 // Set the border pixels on the right 1057 memset(scanlines[i] + horizontalBorderSize + rect.width(), defaultPixel, horizontalBorderSize); 1058 } 1059 // Set the border scanlines on the bottom 1060 if (verticalBorderSize + numberOfFirstRows < numberOfScanlines) { 1061 for (quint32 i = verticalBorderSize + numberOfFirstRows; i < numberOfScanlines; ++i) { 1062 memset(scanlines[i], defaultPixel, bytesPerScanline); 1063 } 1064 } 1065 // Buffer that contains the current output scanline 1066 QVector<quint8> antialiasedScanline(rect.width()); 1067 // Main loop 1068 for (int y = 0; y < rect.height(); ++y) 1069 { 1070 // Move to the next scanline 1071 if (y > 0) { 1072 // Update scanline pointers 1073 std::rotate(std::begin(scanlines), std::begin(scanlines) + 1, std::end(scanlines)); 1074 // Copy the next scanline 1075 if (y < rect.height() - verticalBorderSize) { 1076 // Set the border pixels on the left 1077 memset(scanlines[numberOfScanlines - 1], defaultPixel, horizontalBorderSize); 1078 // Copy the pixel data 1079 pixelSelection->readBytes(scanlines[numberOfScanlines - 1] + horizontalBorderSize, rect.x(), rect.y() + y + verticalBorderSize, rect.width(), 1); 1080 // Set the border pixels on the right 1081 memset(scanlines[numberOfScanlines - 1] + horizontalBorderSize + rect.width(), defaultPixel, horizontalBorderSize); 1082 } else { 1083 memset(scanlines[numberOfScanlines - 1], defaultPixel, bytesPerScanline); 1084 } 1085 } 1086 // Process the pixels in the current scanline 1087 for (int x = 0; x < rect.width(); ++x) 1088 { 1089 // Get the current pixel and neighbors 1090 quint8 *pixelPtrM = scanlines[currentScanlineIndex ] + x + horizontalBorderSize; 1091 quint8 *pixelPtrN = scanlines[currentScanlineIndex - 1] + x + horizontalBorderSize; 1092 quint8 *pixelPtrS = scanlines[currentScanlineIndex + 1] + x + horizontalBorderSize; 1093 const qint32 pixelNW = *(pixelPtrN - 1); 1094 const qint32 pixelN = *(pixelPtrN ); 1095 const qint32 pixelNE = *(pixelPtrN + 1); 1096 const qint32 pixelW = *(pixelPtrM - 1); 1097 const qint32 pixelM = *(pixelPtrM ); 1098 const qint32 pixelE = *(pixelPtrM + 1); 1099 const qint32 pixelSW = *(pixelPtrS - 1); 1100 const qint32 pixelS = *(pixelPtrS ); 1101 const qint32 pixelSE = *(pixelPtrS + 1); 1102 // Get the gradients 1103 const qint32 rowNSum = (pixelNW >> 2) + (pixelN >> 1) + (pixelNE >> 2); 1104 const qint32 rowMSum = (pixelW >> 2) + (pixelM >> 1) + (pixelE >> 2); 1105 const qint32 rowSSum = (pixelSW >> 2) + (pixelS >> 1) + (pixelSE >> 2); 1106 const qint32 colWSum = (pixelNW >> 2) + (pixelW >> 1) + (pixelSW >> 2); 1107 const qint32 colMSum = (pixelN >> 2) + (pixelM >> 1) + (pixelS >> 2); 1108 const qint32 colESum = (pixelNE >> 2) + (pixelE >> 1) + (pixelSE >> 2); 1109 const qint32 gradientN = qAbs(rowMSum - rowNSum); 1110 const qint32 gradientS = qAbs(rowSSum - rowMSum); 1111 const qint32 gradientW = qAbs(colMSum - colWSum); 1112 const qint32 gradientE = qAbs(colESum - colMSum); 1113 // Get the maximum gradient 1114 const qint32 maxGradientNS = qMax(gradientN, gradientS); 1115 const qint32 maxGradientWE = qMax(gradientW, gradientE); 1116 const qint32 maxGradient = qMax(maxGradientNS, maxGradientWE); 1117 // Return early if the gradient is bellow some threshold (given by 1118 // the value bellow which the jagged edge is not noticeable) 1119 if (maxGradient < edgeThreshold) { 1120 antialiasedScanline[x] = pixelM; 1121 continue; 1122 } 1123 // Collect some info about the pixel and neighborhood 1124 qint32 neighborPixel, gradient; 1125 qint32 pixelOffset, rowMultiplier, colMultiplier; 1126 if (maxGradientNS > maxGradientWE) { 1127 // Horizontal span 1128 if (gradientN > gradientS) { 1129 // The edge is formed with the top pixel 1130 neighborPixel = pixelN; 1131 gradient = gradientN; 1132 pixelOffset = -1; 1133 } else { 1134 // The edge is formed with the bottom pixel 1135 neighborPixel = pixelS; 1136 gradient = gradientS; 1137 pixelOffset = 1; 1138 } 1139 rowMultiplier = 0; 1140 colMultiplier = 1; 1141 } else { 1142 // Vertical span 1143 if (gradientW > gradientE) { 1144 // The edge is formed with the left pixel 1145 neighborPixel = pixelW; 1146 gradient = gradientW; 1147 pixelOffset = -1; 1148 } else { 1149 // The edge is formed with the right pixel 1150 neighborPixel = pixelE; 1151 gradient = gradientE; 1152 pixelOffset = 1; 1153 } 1154 rowMultiplier = 1; 1155 colMultiplier = 0; 1156 } 1157 // Find the span extremes 1158 const qint32 pixelAvg = (neighborPixel + pixelM) >> 1; 1159 const qint32 currentPixelDiff = pixelM - pixelAvg; 1160 qint32 negativePixelDiff, positivePixelDiff; 1161 qint32 negativeSpanEndDistance, positiveSpanEndDistance; 1162 bool negativeSpanExtremeValid, positiveSpanExtremeValid; 1163 findSpanExtremes(scanlines, x, pixelOffset, 1164 rowMultiplier, colMultiplier, 1165 pixelAvg, gradient >> 2, currentPixelDiff, 1166 &negativeSpanEndDistance, &positiveSpanEndDistance, 1167 &negativePixelDiff, &positivePixelDiff, 1168 &negativeSpanExtremeValid, &positiveSpanExtremeValid); 1169 // Get the interpolation value for this pixel given the span extent 1170 // and perform linear interpolation between the current pixel and 1171 // the edge neighbor 1172 qint32 interpolationValue; 1173 if (!getInterpolationValue(negativeSpanEndDistance, positiveSpanEndDistance, 1174 negativePixelDiff, positivePixelDiff, currentPixelDiff, 1175 negativeSpanExtremeValid, positiveSpanExtremeValid, &interpolationValue)) { 1176 antialiasedScanline[x] = pixelM; 1177 } else { 1178 antialiasedScanline[x] = neighborPixel + ((pixelM - neighborPixel) * interpolationValue >> 8); 1179 } 1180 } 1181 // Copy the scanline data to the mask 1182 pixelSelection->writeBytes(antialiasedScanline.data(), rect.x(), rect.y() + y, rect.width(), 1); 1183 } 1184 } 1185 1186 KisGrowUntilDarkestPixelSelectionFilter::KisGrowUntilDarkestPixelSelectionFilter(qint32 radius, 1187 KisPaintDeviceSP referenceDevice) 1188 : m_radius(radius) 1189 , m_referenceDevice(referenceDevice) 1190 { 1191 } 1192 1193 KUndo2MagicString KisGrowUntilDarkestPixelSelectionFilter::name() 1194 { 1195 return kundo2_i18n("Grow Selection Until Darkest Pixel"); 1196 } 1197 1198 QRect KisGrowUntilDarkestPixelSelectionFilter::changeRect(const QRect &rect, KisDefaultBoundsBaseSP defaultBounds) 1199 { 1200 Q_UNUSED(defaultBounds); 1201 1202 return rect.adjusted(-m_radius, -m_radius, m_radius, m_radius); 1203 } 1204 1205 void KisGrowUntilDarkestPixelSelectionFilter::process(KisPixelSelectionSP pixelSelection, const QRect &rect) 1206 { 1207 // Copy the original selection. We will grow this adaptively until the 1208 // darkest or mor opaque pixels or until the maximum grow is reached. 1209 KisPixelSelectionSP mask = new KisPixelSelection(*pixelSelection); 1210 // Grow the original selection normally. At the end this selection will be 1211 // masked with the adaptively grown mask. We cannot grow adaptively this 1212 // selection directly since it may have semi-transparent or soft edges. 1213 // Those need to be retained in the final selection. This normally grown 1214 // selection is also used as a stop condition for the adaptive mask, which 1215 // cannot grow pass the limits of the this normally grown selection. 1216 KisGrowSelectionFilter growFilter(m_radius, m_radius); 1217 growFilter.process(pixelSelection, rect); 1218 1219 const qint32 maskScanLineSize = rect.width(); 1220 const KoColorSpace *referenceColorSpace = m_referenceDevice->colorSpace(); 1221 const qint32 referencePixelSize = referenceColorSpace->pixelSize(); 1222 const qint32 referenceScanLineSize = maskScanLineSize * referencePixelSize; 1223 // Some buffers to store the working scanlines 1224 QVector<quint8> maskBuffer(maskScanLineSize * 2); 1225 QVector<quint8> referenceBuffer(referenceScanLineSize * 2); 1226 QVector<quint8> selectionBuffer(maskScanLineSize); 1227 quint8 *maskScanLines[2] = {maskBuffer.data(), maskBuffer.data() + maskScanLineSize}; 1228 quint8 *referenceScanLines[2] = {referenceBuffer.data(), referenceBuffer.data() + referenceScanLineSize}; 1229 quint8 *selectionScanLine = selectionBuffer.data(); 1230 // Helper function to test if a pixel can be selected 1231 auto testSelectPixel = 1232 [referenceColorSpace] 1233 (quint8 pixelOpacity, quint8 pixelIntensity, 1234 const quint8 *testMaskPixel, const quint8 *testReferencePixel) -> bool 1235 { 1236 if (*testMaskPixel) { 1237 const quint8 testOpacity = referenceColorSpace->opacityU8(testReferencePixel); 1238 if (pixelOpacity >= testOpacity) { 1239 // Special case for when the neighbor pixel is fully transparent. 1240 // In that case do not compare the intensity 1241 if (testOpacity == MIN_SELECTED) { 1242 return true; 1243 } 1244 // If the opacity test passes we still have to perform the 1245 // intensity test 1246 const quint8 testIntensity = referenceColorSpace->intensity8(testReferencePixel); 1247 if (pixelIntensity <= testIntensity) { 1248 return true; 1249 } 1250 } 1251 } 1252 return false; 1253 }; 1254 1255 // Top-left to bottom-right pass 1256 // First row 1257 { 1258 mask->readBytes(maskScanLines[1], rect.left(), rect.top(), maskScanLineSize, 1); 1259 m_referenceDevice->readBytes(referenceScanLines[1], rect.left(), rect.top(), maskScanLineSize, 1); 1260 pixelSelection->readBytes(selectionScanLine, rect.left(), rect.top(), maskScanLineSize, 1); 1261 quint8 *currentMaskScanLineBegin = maskScanLines[1]; 1262 quint8 *currentMaskScanLineEnd = maskScanLines[1] + maskScanLineSize; 1263 quint8 *currentReferenceScanLineBegin = referenceScanLines[1]; 1264 quint8 *currentSelectionScanLineBegin = selectionScanLine; 1265 // First pixel 1266 ++currentMaskScanLineBegin; 1267 currentReferenceScanLineBegin += referencePixelSize; 1268 ++currentSelectionScanLineBegin; 1269 // Rest of pixels 1270 while (currentMaskScanLineBegin != currentMaskScanLineEnd) { 1271 if (*currentMaskScanLineBegin == MIN_SELECTED && *currentSelectionScanLineBegin != MIN_SELECTED) { 1272 const quint8 currentOpacity = referenceColorSpace->opacityU8(currentReferenceScanLineBegin); 1273 const quint8 currentIntensity = referenceColorSpace->intensity8(currentReferenceScanLineBegin); 1274 1275 bool pixelIsSelected = testSelectPixel(currentOpacity, currentIntensity, 1276 currentMaskScanLineBegin - 1, 1277 currentReferenceScanLineBegin - referencePixelSize); 1278 if (pixelIsSelected) { 1279 *currentMaskScanLineBegin = MAX_SELECTED; 1280 } 1281 } 1282 ++currentMaskScanLineBegin; 1283 currentReferenceScanLineBegin += referencePixelSize; 1284 ++currentSelectionScanLineBegin; 1285 } 1286 mask->writeBytes(maskScanLines[1], rect.left(), rect.top(), maskScanLineSize, 1); 1287 } 1288 // Rest of rows 1289 for (qint32 y = rect.top() + 1; y <= rect.bottom(); ++y) { 1290 rotatePointers(maskScanLines, 2); 1291 rotatePointers(referenceScanLines, 2); 1292 mask->readBytes(maskScanLines[1], rect.left(), y, maskScanLineSize, 1); 1293 m_referenceDevice->readBytes(referenceScanLines[1], rect.left(), y, maskScanLineSize, 1); 1294 pixelSelection->readBytes(selectionScanLine, rect.left(), y, maskScanLineSize, 1); 1295 quint8 *currentMaskScanLineBegin = maskScanLines[1]; 1296 quint8 *currentMaskScanLineEnd = maskScanLines[1] + maskScanLineSize; 1297 quint8 *currentReferenceScanLineBegin = referenceScanLines[1]; 1298 quint8 *topMaskScanLineBegin = maskScanLines[0]; 1299 quint8 *topReferenceScanLineBegin = referenceScanLines[0]; 1300 quint8 *currentSelectionScanLineBegin = selectionScanLine; 1301 // First pixel 1302 { 1303 if (*currentMaskScanLineBegin == MIN_SELECTED && *currentSelectionScanLineBegin != MIN_SELECTED) { 1304 const quint8 currentOpacity = referenceColorSpace->opacityU8(currentReferenceScanLineBegin); 1305 const quint8 currentIntensity = referenceColorSpace->intensity8(currentReferenceScanLineBegin); 1306 1307 bool pixelIsSelected = testSelectPixel(currentOpacity, currentIntensity, 1308 topMaskScanLineBegin, 1309 topReferenceScanLineBegin); 1310 if (!pixelIsSelected) { 1311 pixelIsSelected = testSelectPixel(currentOpacity, currentIntensity, 1312 topMaskScanLineBegin + 1, 1313 topReferenceScanLineBegin + referencePixelSize); 1314 } 1315 if (pixelIsSelected) { 1316 *currentMaskScanLineBegin = MAX_SELECTED; 1317 } 1318 } 1319 ++currentMaskScanLineBegin; 1320 currentReferenceScanLineBegin += referencePixelSize; 1321 ++topMaskScanLineBegin; 1322 topReferenceScanLineBegin += referencePixelSize; 1323 ++currentSelectionScanLineBegin; 1324 } 1325 // Rest of pixels 1326 while (currentMaskScanLineBegin != (currentMaskScanLineEnd - 1)) { 1327 if (*currentMaskScanLineBegin == MIN_SELECTED && *currentSelectionScanLineBegin != MIN_SELECTED) { 1328 const quint8 currentOpacity = referenceColorSpace->opacityU8(currentReferenceScanLineBegin); 1329 const quint8 currentIntensity = referenceColorSpace->intensity8(currentReferenceScanLineBegin); 1330 1331 bool pixelIsSelected = testSelectPixel(currentOpacity, currentIntensity, 1332 topMaskScanLineBegin - 1, 1333 topReferenceScanLineBegin - referencePixelSize); 1334 if (!pixelIsSelected) { 1335 pixelIsSelected = testSelectPixel(currentOpacity, currentIntensity, 1336 topMaskScanLineBegin, 1337 topReferenceScanLineBegin); 1338 if (!pixelIsSelected) { 1339 pixelIsSelected = testSelectPixel(currentOpacity, currentIntensity, 1340 topMaskScanLineBegin + 1, 1341 topReferenceScanLineBegin + referencePixelSize); 1342 if (!pixelIsSelected) { 1343 pixelIsSelected = testSelectPixel(currentOpacity, currentIntensity, 1344 currentMaskScanLineBegin - 1, 1345 currentReferenceScanLineBegin - referencePixelSize); 1346 } 1347 } 1348 } 1349 if (pixelIsSelected) { 1350 *currentMaskScanLineBegin = MAX_SELECTED; 1351 } 1352 } 1353 ++currentMaskScanLineBegin; 1354 currentReferenceScanLineBegin += referencePixelSize; 1355 ++topMaskScanLineBegin; 1356 topReferenceScanLineBegin += referencePixelSize; 1357 ++currentSelectionScanLineBegin; 1358 } 1359 // Last pixel 1360 { 1361 if (*currentMaskScanLineBegin == MIN_SELECTED && *currentSelectionScanLineBegin != MIN_SELECTED) { 1362 const quint8 currentOpacity = referenceColorSpace->opacityU8(currentReferenceScanLineBegin); 1363 const quint8 currentIntensity = referenceColorSpace->intensity8(currentReferenceScanLineBegin); 1364 1365 bool pixelIsSelected = testSelectPixel(currentOpacity, currentIntensity, 1366 topMaskScanLineBegin - 1, 1367 topReferenceScanLineBegin - referencePixelSize); 1368 if (!pixelIsSelected) { 1369 pixelIsSelected = testSelectPixel(currentOpacity, currentIntensity, 1370 topMaskScanLineBegin, 1371 topReferenceScanLineBegin); 1372 if (!pixelIsSelected) { 1373 pixelIsSelected = testSelectPixel(currentOpacity, currentIntensity, 1374 currentMaskScanLineBegin - 1, 1375 currentReferenceScanLineBegin - referencePixelSize); 1376 } 1377 } 1378 if (pixelIsSelected) { 1379 *currentMaskScanLineBegin = MAX_SELECTED; 1380 } 1381 } 1382 } 1383 mask->writeBytes(maskScanLines[1], rect.left(), y, maskScanLineSize, 1); 1384 } 1385 1386 // Bottom-right to top-left pass 1387 // Last row 1388 { 1389 mask->readBytes(maskScanLines[1], rect.left(), rect.bottom(), maskScanLineSize, 1); 1390 m_referenceDevice->readBytes(referenceScanLines[1], rect.left(), rect.bottom(), maskScanLineSize, 1); 1391 pixelSelection->readBytes(selectionScanLine, rect.left(), rect.bottom(), maskScanLineSize, 1); 1392 quint8 *currentMaskScanLineBegin = maskScanLines[1] + maskScanLineSize - 1; 1393 quint8 *currentMaskScanLineEnd = maskScanLines[1] - 1; 1394 quint8 *currentReferenceScanLineBegin = referenceScanLines[1] + referenceScanLineSize - referencePixelSize; 1395 quint8 *currentSelectionScanLineBegin = selectionScanLine + maskScanLineSize - 1; 1396 // Last pixel 1397 --currentMaskScanLineBegin; 1398 currentReferenceScanLineBegin -= referencePixelSize; 1399 --currentSelectionScanLineBegin; 1400 // Rest of pixels 1401 while (currentMaskScanLineBegin != currentMaskScanLineEnd) { 1402 if (*currentMaskScanLineBegin == MIN_SELECTED && *currentSelectionScanLineBegin != MIN_SELECTED) { 1403 const quint8 currentOpacity = referenceColorSpace->opacityU8(currentReferenceScanLineBegin); 1404 const quint8 currentIntensity = referenceColorSpace->intensity8(currentReferenceScanLineBegin); 1405 1406 bool pixelIsSelected = testSelectPixel(currentOpacity, currentIntensity, 1407 currentMaskScanLineBegin + 1, 1408 currentReferenceScanLineBegin + referencePixelSize); 1409 if (pixelIsSelected) { 1410 *currentMaskScanLineBegin = MAX_SELECTED; 1411 } 1412 } 1413 --currentMaskScanLineBegin; 1414 currentReferenceScanLineBegin -= referencePixelSize; 1415 --currentSelectionScanLineBegin; 1416 } 1417 mask->writeBytes(maskScanLines[1], rect.left(), rect.top(), maskScanLineSize, 1); 1418 } 1419 // Rest of rows 1420 for (qint32 y = rect.bottom() - 1; y >= rect.top(); --y) { 1421 rotatePointers(maskScanLines, 2); 1422 rotatePointers(referenceScanLines, 2); 1423 mask->readBytes(maskScanLines[1], rect.left(), y, maskScanLineSize, 1); 1424 m_referenceDevice->readBytes(referenceScanLines[1], rect.left(), y, maskScanLineSize, 1); 1425 pixelSelection->readBytes(selectionScanLine, rect.left(), y, maskScanLineSize, 1); 1426 quint8 *currentMaskScanLineBegin = maskScanLines[1] + maskScanLineSize - 1; 1427 quint8 *currentMaskScanLineEnd = maskScanLines[1] - 1; 1428 quint8 *currentReferenceScanLineBegin = referenceScanLines[1] + referenceScanLineSize - referencePixelSize; 1429 quint8 *bottomMaskScanLineBegin = maskScanLines[0] + maskScanLineSize - 1; 1430 quint8 *bottomReferenceScanLineBegin = referenceScanLines[0] + referenceScanLineSize - referencePixelSize; 1431 quint8 *currentSelectionScanLineBegin = selectionScanLine + maskScanLineSize - 1; 1432 // Last pixel 1433 { 1434 if (*currentMaskScanLineBegin == MIN_SELECTED && *currentSelectionScanLineBegin != MIN_SELECTED) { 1435 const quint8 currentOpacity = referenceColorSpace->opacityU8(currentReferenceScanLineBegin); 1436 const quint8 currentIntensity = referenceColorSpace->intensity8(currentReferenceScanLineBegin); 1437 1438 bool pixelIsSelected = testSelectPixel(currentOpacity, currentIntensity, 1439 bottomMaskScanLineBegin, 1440 bottomReferenceScanLineBegin); 1441 if (!pixelIsSelected) { 1442 pixelIsSelected = testSelectPixel(currentOpacity, currentIntensity, 1443 bottomMaskScanLineBegin - 1, 1444 bottomReferenceScanLineBegin - referencePixelSize); 1445 } 1446 if (pixelIsSelected) { 1447 *currentMaskScanLineBegin = MAX_SELECTED; 1448 } 1449 } 1450 --currentMaskScanLineBegin; 1451 currentReferenceScanLineBegin -= referencePixelSize; 1452 --bottomMaskScanLineBegin; 1453 bottomReferenceScanLineBegin -= referencePixelSize; 1454 --currentSelectionScanLineBegin; 1455 } 1456 // Rest of pixels 1457 while (currentMaskScanLineBegin != (currentMaskScanLineEnd + 1)) { 1458 if (*currentMaskScanLineBegin == MIN_SELECTED && *currentSelectionScanLineBegin != MIN_SELECTED) { 1459 const quint8 currentOpacity = referenceColorSpace->opacityU8(currentReferenceScanLineBegin); 1460 const quint8 currentIntensity = referenceColorSpace->intensity8(currentReferenceScanLineBegin); 1461 1462 bool pixelIsSelected = testSelectPixel(currentOpacity, currentIntensity, 1463 bottomMaskScanLineBegin + 1, 1464 bottomReferenceScanLineBegin + referencePixelSize); 1465 if (!pixelIsSelected) { 1466 pixelIsSelected = testSelectPixel(currentOpacity, currentIntensity, 1467 bottomMaskScanLineBegin, 1468 bottomReferenceScanLineBegin); 1469 if (!pixelIsSelected) { 1470 pixelIsSelected = testSelectPixel(currentOpacity, currentIntensity, 1471 bottomMaskScanLineBegin - 1, 1472 bottomReferenceScanLineBegin - referencePixelSize); 1473 if (!pixelIsSelected) { 1474 pixelIsSelected = testSelectPixel(currentOpacity, currentIntensity, 1475 currentMaskScanLineBegin + 1, 1476 currentReferenceScanLineBegin + referencePixelSize); 1477 } 1478 } 1479 } 1480 if (pixelIsSelected) { 1481 *currentMaskScanLineBegin = MAX_SELECTED; 1482 } 1483 } 1484 --currentMaskScanLineBegin; 1485 currentReferenceScanLineBegin -= referencePixelSize; 1486 --bottomMaskScanLineBegin; 1487 bottomReferenceScanLineBegin -= referencePixelSize; 1488 --currentSelectionScanLineBegin; 1489 } 1490 // First pixel 1491 { 1492 if (*currentMaskScanLineBegin == MIN_SELECTED && *currentSelectionScanLineBegin != MIN_SELECTED) { 1493 const quint8 currentOpacity = referenceColorSpace->opacityU8(currentReferenceScanLineBegin); 1494 const quint8 currentIntensity = referenceColorSpace->intensity8(currentReferenceScanLineBegin); 1495 1496 bool pixelIsSelected = testSelectPixel(currentOpacity, currentIntensity, 1497 bottomMaskScanLineBegin + 1, 1498 bottomReferenceScanLineBegin + referencePixelSize); 1499 if (!pixelIsSelected) { 1500 pixelIsSelected = testSelectPixel(currentOpacity, currentIntensity, 1501 bottomMaskScanLineBegin, 1502 bottomReferenceScanLineBegin); 1503 if (!pixelIsSelected) { 1504 pixelIsSelected = testSelectPixel(currentOpacity, currentIntensity, 1505 currentMaskScanLineBegin + 1, 1506 currentReferenceScanLineBegin + referencePixelSize); 1507 } 1508 } 1509 if (pixelIsSelected) { 1510 *currentMaskScanLineBegin = MAX_SELECTED; 1511 } 1512 } 1513 } 1514 mask->writeBytes(maskScanLines[1], rect.left(), y, maskScanLineSize, 1); 1515 } 1516 1517 // Combine the adaptively grown mask with the normally grown mask. The 1518 // adaptively grown mask is used as a binary mask to erase some of the 1519 // pixels of the normally grown mask 1520 { 1521 KisSequentialConstIterator it1(mask, rect); 1522 KisSequentialIterator it2(pixelSelection, rect); 1523 while (it1.nextPixel() && it2.nextPixel()) { 1524 *it2.rawData() *= (*it1.rawDataConst() != MIN_SELECTED); 1525 } 1526 } 1527 }