File indexing completed on 2024-05-12 15:58:41
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 0019 #define MAX(a, b) ((a) > (b) ? (a) : (b)) 0020 #define MIN(a, b) ((a) < (b) ? (a) : (b)) 0021 #define RINT(x) floor ((x) + 0.5) 0022 0023 KisSelectionFilter::~KisSelectionFilter() 0024 { 0025 } 0026 0027 KUndo2MagicString KisSelectionFilter::name() 0028 { 0029 return KUndo2MagicString(); 0030 } 0031 0032 QRect KisSelectionFilter::changeRect(const QRect &rect, KisDefaultBoundsBaseSP defaultBounds) 0033 { 0034 Q_UNUSED(defaultBounds); 0035 return rect; 0036 } 0037 0038 void KisSelectionFilter::computeBorder(qint32* circ, qint32 xradius, qint32 yradius) 0039 { 0040 qint32 i; 0041 qint32 diameter = xradius * 2 + 1; 0042 double tmp; 0043 0044 for (i = 0; i < diameter; i++) { 0045 if (i > xradius) 0046 tmp = (i - xradius) - 0.5; 0047 else if (i < xradius) 0048 tmp = (xradius - i) - 0.5; 0049 else 0050 tmp = 0.0; 0051 0052 double divisor = (double) xradius; 0053 if (divisor == 0.0) { 0054 divisor = 1.0; 0055 } 0056 circ[i] = (qint32) RINT(yradius * sqrt(xradius * xradius - tmp * tmp) / divisor); 0057 } 0058 } 0059 0060 void KisSelectionFilter::rotatePointers(quint8** p, quint32 n) 0061 { 0062 quint32 i; 0063 quint8 *p0 = p[0]; 0064 for (i = 0; i < n - 1; i++) { 0065 p[i] = p[i + 1]; 0066 } 0067 p[i] = p0; 0068 } 0069 0070 void KisSelectionFilter::computeTransition(quint8* transition, quint8** buf, qint32 width) 0071 { 0072 qint32 x = 0; 0073 0074 if (width == 1) { 0075 if (buf[1][x] > 127 && (buf[0][x] < 128 || buf[2][x] < 128)) 0076 transition[x] = 255; 0077 else 0078 transition[x] = 0; 0079 return; 0080 } 0081 if (buf[1][x] > 127) { 0082 if (buf[0][x] < 128 || buf[0][x + 1] < 128 || 0083 buf[1][x + 1] < 128 || 0084 buf[2][x] < 128 || buf[2][x + 1] < 128) 0085 transition[x] = 255; 0086 else 0087 transition[x] = 0; 0088 } else 0089 transition[x] = 0; 0090 for (qint32 x = 1; x < width - 1; x++) { 0091 if (buf[1][x] >= 128) { 0092 if (buf[0][x - 1] < 128 || buf[0][x] < 128 || buf[0][x + 1] < 128 || 0093 buf[1][x - 1] < 128 || buf[1][x + 1] < 128 || 0094 buf[2][x - 1] < 128 || buf[2][x] < 128 || buf[2][x + 1] < 128) 0095 transition[x] = 255; 0096 else 0097 transition[x] = 0; 0098 } else 0099 transition[x] = 0; 0100 } 0101 if (buf[1][x] >= 128) { 0102 if (buf[0][x - 1] < 128 || buf[0][x] < 128 || 0103 buf[1][x - 1] < 128 || 0104 buf[2][x - 1] < 128 || buf[2][x] < 128) 0105 transition[x] = 255; 0106 else 0107 transition[x] = 0; 0108 } else 0109 transition[x] = 0; 0110 } 0111 0112 0113 KUndo2MagicString KisErodeSelectionFilter::name() 0114 { 0115 return kundo2_i18n("Erode Selection"); 0116 } 0117 0118 QRect KisErodeSelectionFilter::changeRect(const QRect& rect, KisDefaultBoundsBaseSP defaultBounds) 0119 { 0120 Q_UNUSED(defaultBounds); 0121 0122 const qint32 radius = 1; 0123 return rect.adjusted(-radius, -radius, radius, radius); 0124 } 0125 0126 void KisErodeSelectionFilter::process(KisPixelSelectionSP pixelSelection, const QRect& rect) 0127 { 0128 // Erode (radius 1 pixel) a mask (1bpp) 0129 quint8* buf[3]; 0130 0131 qint32 width = rect.width(); 0132 qint32 height = rect.height(); 0133 0134 quint8* out = new quint8[width]; 0135 for (qint32 i = 0; i < 3; i++) 0136 buf[i] = new quint8[width + 2]; 0137 0138 0139 // load top of image 0140 pixelSelection->readBytes(buf[0] + 1, rect.x(), rect.y(), width, 1); 0141 0142 buf[0][0] = buf[0][1]; 0143 buf[0][width + 1] = buf[0][width]; 0144 0145 memcpy(buf[1], buf[0], width + 2); 0146 0147 for (qint32 y = 0; y < height; y++) { 0148 if (y + 1 < height) { 0149 pixelSelection->readBytes(buf[2] + 1, rect.x(), rect.y() + y + 1, width, 1); 0150 0151 buf[2][0] = buf[2][1]; 0152 buf[2][width + 1] = buf[2][width]; 0153 } else { 0154 memcpy(buf[2], buf[1], width + 2); 0155 } 0156 0157 for (qint32 x = 0 ; x < width; x++) { 0158 qint32 min = 255; 0159 0160 if (buf[0][x+1] < min) min = buf[0][x+1]; 0161 if (buf[1][x] < min) min = buf[1][x]; 0162 if (buf[1][x+1] < min) min = buf[1][x+1]; 0163 if (buf[1][x+2] < min) min = buf[1][x+2]; 0164 if (buf[2][x+1] < min) min = buf[2][x+1]; 0165 0166 out[x] = min; 0167 } 0168 0169 pixelSelection->writeBytes(out, rect.x(), rect.y() + y, width, 1); 0170 rotatePointers(buf, 3); 0171 } 0172 0173 for (qint32 i = 0; i < 3; i++) 0174 delete[] buf[i]; 0175 delete[] out; 0176 } 0177 0178 0179 KUndo2MagicString KisDilateSelectionFilter::name() 0180 { 0181 return kundo2_i18n("Dilate Selection"); 0182 } 0183 0184 QRect KisDilateSelectionFilter::changeRect(const QRect& rect, KisDefaultBoundsBaseSP defaultBounds) 0185 { 0186 Q_UNUSED(defaultBounds); 0187 0188 const qint32 radius = 1; 0189 return rect.adjusted(-radius, -radius, radius, radius); 0190 } 0191 0192 void KisDilateSelectionFilter::process(KisPixelSelectionSP pixelSelection, const QRect& rect) 0193 { 0194 // dilate (radius 1 pixel) a mask (1bpp) 0195 quint8* buf[3]; 0196 0197 qint32 width = rect.width(); 0198 qint32 height = rect.height(); 0199 0200 quint8* out = new quint8[width]; 0201 for (qint32 i = 0; i < 3; i++) 0202 buf[i] = new quint8[width + 2]; 0203 0204 0205 // load top of image 0206 pixelSelection->readBytes(buf[0] + 1, rect.x(), rect.y(), width, 1); 0207 0208 buf[0][0] = buf[0][1]; 0209 buf[0][width + 1] = buf[0][width]; 0210 0211 memcpy(buf[1], buf[0], width + 2); 0212 0213 for (qint32 y = 0; y < height; y++) { 0214 if (y + 1 < height) { 0215 pixelSelection->readBytes(buf[2] + 1, rect.x(), rect.y() + y + 1, width, 1); 0216 0217 buf[2][0] = buf[2][1]; 0218 buf[2][width + 1] = buf[2][width]; 0219 } else { 0220 memcpy(buf[2], buf[1], width + 2); 0221 } 0222 0223 for (qint32 x = 0 ; x < width; x++) { 0224 qint32 max = 0; 0225 0226 if (buf[0][x+1] > max) max = buf[0][x+1]; 0227 if (buf[1][x] > max) max = buf[1][x]; 0228 if (buf[1][x+1] > max) max = buf[1][x+1]; 0229 if (buf[1][x+2] > max) max = buf[1][x+2]; 0230 if (buf[2][x+1] > max) max = buf[2][x+1]; 0231 0232 out[x] = max; 0233 } 0234 0235 pixelSelection->writeBytes(out, rect.x(), rect.y() + y, width, 1); 0236 rotatePointers(buf, 3); 0237 } 0238 0239 for (qint32 i = 0; i < 3; i++) 0240 delete[] buf[i]; 0241 delete[] out; 0242 } 0243 0244 0245 KisBorderSelectionFilter::KisBorderSelectionFilter(qint32 xRadius, qint32 yRadius, bool antialiasing) 0246 : m_xRadius(xRadius), 0247 m_yRadius(yRadius), 0248 m_antialiasing(antialiasing) 0249 { 0250 } 0251 0252 KUndo2MagicString KisBorderSelectionFilter::name() 0253 { 0254 return kundo2_i18n("Border Selection"); 0255 } 0256 0257 QRect KisBorderSelectionFilter::changeRect(const QRect& rect, KisDefaultBoundsBaseSP defaultBounds) 0258 { 0259 Q_UNUSED(defaultBounds); 0260 0261 return rect.adjusted(-m_xRadius, -m_yRadius, m_xRadius, m_yRadius); 0262 } 0263 0264 void KisBorderSelectionFilter::process(KisPixelSelectionSP pixelSelection, const QRect& rect) 0265 { 0266 if (m_xRadius <= 0 || m_yRadius <= 0) return; 0267 0268 quint8 *buf[3]; 0269 quint8 **density; 0270 quint8 **transition; 0271 0272 if (m_xRadius == 1 && m_yRadius == 1) { 0273 // optimize this case specifically 0274 quint8* source[3]; 0275 0276 for (qint32 i = 0; i < 3; i++) 0277 source[i] = new quint8[rect.width()]; 0278 0279 quint8* transition = new quint8[rect.width()]; 0280 0281 pixelSelection->readBytes(source[0], rect.x(), rect.y(), rect.width(), 1); 0282 memcpy(source[1], source[0], rect.width()); 0283 if (rect.height() > 1) 0284 pixelSelection->readBytes(source[2], rect.x(), rect.y() + 1, rect.width(), 1); 0285 else 0286 memcpy(source[2], source[1], rect.width()); 0287 0288 computeTransition(transition, source, rect.width()); 0289 pixelSelection->writeBytes(transition, rect.x(), rect.y(), rect.width(), 1); 0290 0291 for (qint32 y = 1; y < rect.height(); y++) { 0292 rotatePointers(source, 3); 0293 if (y + 1 < rect.height()) 0294 pixelSelection->readBytes(source[2], rect.x(), rect.y() + y + 1, rect.width(), 1); 0295 else 0296 memcpy(source[2], source[1], rect.width()); 0297 computeTransition(transition, source, rect.width()); 0298 pixelSelection->writeBytes(transition, rect.x(), rect.y() + y, rect.width(), 1); 0299 } 0300 0301 for (qint32 i = 0; i < 3; i++) 0302 delete[] source[i]; 0303 delete[] transition; 0304 return; 0305 } 0306 0307 qint32* max = new qint32[rect.width() + 2 * m_xRadius]; 0308 for (qint32 i = 0; i < (rect.width() + 2 * m_xRadius); i++) 0309 max[i] = m_yRadius + 2; 0310 max += m_xRadius; 0311 0312 for (qint32 i = 0; i < 3; i++) 0313 buf[i] = new quint8[rect.width()]; 0314 0315 transition = new quint8*[m_yRadius + 1]; 0316 for (qint32 i = 0; i < m_yRadius + 1; i++) { 0317 transition[i] = new quint8[rect.width() + 2 * m_xRadius]; 0318 memset(transition[i], 0, rect.width() + 2 * m_xRadius); 0319 transition[i] += m_xRadius; 0320 } 0321 quint8* out = new quint8[rect.width()]; 0322 density = new quint8*[2 * m_xRadius + 1]; 0323 density += m_xRadius; 0324 0325 for (qint32 x = 0; x < (m_xRadius + 1); x++) { // allocate density[][] 0326 density[ x] = new quint8[2 * m_yRadius + 1]; 0327 density[ x] += m_yRadius; 0328 density[-x] = density[x]; 0329 } 0330 0331 // compute density[][] 0332 if (m_antialiasing) { 0333 KIS_SAFE_ASSERT_RECOVER_NOOP(m_xRadius == m_yRadius && "anisotropic fading is not implemented"); 0334 const qreal maxRadius = 0.5 * (m_xRadius + m_yRadius); 0335 const qreal minRadius = maxRadius - 1.0; 0336 0337 for (qint32 x = 0; x < (m_xRadius + 1); x++) { 0338 double dist; 0339 quint8 a; 0340 0341 for (qint32 y = 0; y < (m_yRadius + 1); y++) { 0342 0343 dist = sqrt(pow2(x) + pow2(y)); 0344 0345 if (dist > maxRadius) { 0346 a = 0; 0347 } else if (dist > minRadius) { 0348 a = qRound((1.0 - dist + minRadius) * 255.0); 0349 } else { 0350 a = 255; 0351 } 0352 0353 density[ x][ y] = a; 0354 density[ x][-y] = a; 0355 density[-x][ y] = a; 0356 density[-x][-y] = a; 0357 } 0358 } 0359 0360 } else { 0361 for (qint32 x = 0; x < (m_xRadius + 1); x++) { 0362 double tmpx, tmpy, dist; 0363 quint8 a; 0364 0365 tmpx = x > 0.0 ? x - 0.5 : 0.0; 0366 0367 for (qint32 y = 0; y < (m_yRadius + 1); y++) { 0368 tmpy = y > 0.0 ? y - 0.5 : 0.0; 0369 0370 dist = (pow2(tmpy) / pow2(m_yRadius) + 0371 pow2(tmpx) / pow2(m_xRadius)); 0372 0373 a = dist <= 1.0 ? 255 : 0; 0374 0375 density[ x][ y] = a; 0376 density[ x][-y] = a; 0377 density[-x][ y] = a; 0378 density[-x][-y] = a; 0379 } 0380 } 0381 } 0382 0383 pixelSelection->readBytes(buf[0], rect.x(), rect.y(), rect.width(), 1); 0384 memcpy(buf[1], buf[0], rect.width()); 0385 if (rect.height() > 1) 0386 pixelSelection->readBytes(buf[2], rect.x(), rect.y() + 1, rect.width(), 1); 0387 else 0388 memcpy(buf[2], buf[1], rect.width()); 0389 computeTransition(transition[1], buf, rect.width()); 0390 0391 for (qint32 y = 1; y < m_yRadius && y + 1 < rect.height(); y++) { // set up top of image 0392 rotatePointers(buf, 3); 0393 pixelSelection->readBytes(buf[2], rect.x(), rect.y() + y + 1, rect.width(), 1); 0394 computeTransition(transition[y + 1], buf, rect.width()); 0395 } 0396 for (qint32 x = 0; x < rect.width(); x++) { // set up max[] for top of image 0397 max[x] = -(m_yRadius + 7); 0398 for (qint32 j = 1; j < m_yRadius + 1; j++) 0399 if (transition[j][x]) { 0400 max[x] = j; 0401 break; 0402 } 0403 } 0404 for (qint32 y = 0; y < rect.height(); y++) { // main calculation loop 0405 rotatePointers(buf, 3); 0406 rotatePointers(transition, m_yRadius + 1); 0407 if (y < rect.height() - (m_yRadius + 1)) { 0408 pixelSelection->readBytes(buf[2], rect.x(), rect.y() + y + m_yRadius + 1, rect.width(), 1); 0409 computeTransition(transition[m_yRadius], buf, rect.width()); 0410 } else 0411 memcpy(transition[m_yRadius], transition[m_yRadius - 1], rect.width()); 0412 0413 for (qint32 x = 0; x < rect.width(); x++) { // update max array 0414 if (max[x] < 1) { 0415 if (max[x] <= -m_yRadius) { 0416 if (transition[m_yRadius][x]) 0417 max[x] = m_yRadius; 0418 else 0419 max[x]--; 0420 } else if (transition[-max[x]][x]) 0421 max[x] = -max[x]; 0422 else if (transition[-max[x] + 1][x]) 0423 max[x] = -max[x] + 1; 0424 else 0425 max[x]--; 0426 } else 0427 max[x]--; 0428 if (max[x] < -m_yRadius - 1) 0429 max[x] = -m_yRadius - 1; 0430 } 0431 quint8 last_max = max[0][density[-1]]; 0432 qint32 last_index = 1; 0433 for (qint32 x = 0 ; x < rect.width(); x++) { // render scan line 0434 last_index--; 0435 if (last_index >= 0) { 0436 last_max = 0; 0437 for (qint32 i = m_xRadius; i >= 0; i--) 0438 if (max[x + i] <= m_yRadius && max[x + i] >= -m_yRadius && density[i][max[x+i]] > last_max) { 0439 last_max = density[i][max[x + i]]; 0440 last_index = i; 0441 } 0442 out[x] = last_max; 0443 } else { 0444 last_max = 0; 0445 for (qint32 i = m_xRadius; i >= -m_xRadius; i--) 0446 if (max[x + i] <= m_yRadius && max[x + i] >= -m_yRadius && density[i][max[x + i]] > last_max) { 0447 last_max = density[i][max[x + i]]; 0448 last_index = i; 0449 } 0450 out[x] = last_max; 0451 } 0452 if (last_max == 0) { 0453 qint32 i; 0454 for (i = x + 1; i < rect.width(); i++) { 0455 if (max[i] >= -m_yRadius) 0456 break; 0457 } 0458 if (i - x > m_xRadius) { 0459 for (; x < i - m_xRadius; x++) 0460 out[x] = 0; 0461 x--; 0462 } 0463 last_index = m_xRadius; 0464 } 0465 } 0466 pixelSelection->writeBytes(out, rect.x(), rect.y() + y, rect.width(), 1); 0467 } 0468 delete [] out; 0469 0470 for (qint32 i = 0; i < 3; i++) 0471 delete[] buf[i]; 0472 0473 max -= m_xRadius; 0474 delete[] max; 0475 0476 for (qint32 i = 0; i < m_yRadius + 1; i++) { 0477 transition[i] -= m_xRadius; 0478 delete transition[i]; 0479 } 0480 delete[] transition; 0481 0482 for (qint32 i = 0; i < m_xRadius + 1 ; i++) { 0483 density[i] -= m_yRadius; 0484 delete density[i]; 0485 } 0486 density -= m_xRadius; 0487 delete[] density; 0488 } 0489 0490 0491 KisFeatherSelectionFilter::KisFeatherSelectionFilter(qint32 radius) 0492 : m_radius(radius) 0493 { 0494 } 0495 0496 KUndo2MagicString KisFeatherSelectionFilter::name() 0497 { 0498 return kundo2_i18n("Feather Selection"); 0499 } 0500 0501 QRect KisFeatherSelectionFilter::changeRect(const QRect& rect, KisDefaultBoundsBaseSP defaultBounds) 0502 { 0503 Q_UNUSED(defaultBounds); 0504 0505 return rect.adjusted(-m_radius, -m_radius, 0506 m_radius, m_radius); 0507 } 0508 0509 void KisFeatherSelectionFilter::process(KisPixelSelectionSP pixelSelection, const QRect& rect) 0510 { 0511 // compute horizontal kernel 0512 const uint kernelSize = m_radius * 2 + 1; 0513 Eigen::Matrix<qreal, Eigen::Dynamic, Eigen::Dynamic> gaussianMatrix(1, kernelSize); 0514 0515 const qreal multiplicand = 1.0 / (2.0 * M_PI * m_radius * m_radius); 0516 const qreal exponentMultiplicand = 1.0 / (2.0 * m_radius * m_radius); 0517 0518 for (uint x = 0; x < kernelSize; x++) { 0519 uint xDistance = qAbs((int)m_radius - (int)x); 0520 gaussianMatrix(0, x) = multiplicand * exp( -(qreal)((xDistance * xDistance) + (m_radius * m_radius)) * exponentMultiplicand ); 0521 } 0522 0523 KisConvolutionKernelSP kernelHoriz = KisConvolutionKernel::fromMatrix(gaussianMatrix, 0, gaussianMatrix.sum()); 0524 KisConvolutionKernelSP kernelVertical = KisConvolutionKernel::fromMatrix(gaussianMatrix.transpose(), 0, gaussianMatrix.sum()); 0525 0526 KisPaintDeviceSP interm = new KisPaintDevice(pixelSelection->colorSpace()); 0527 interm->prepareClone(pixelSelection); 0528 0529 KisConvolutionPainter horizPainter(interm); 0530 horizPainter.setChannelFlags(interm->colorSpace()->channelFlags(false, true)); 0531 horizPainter.applyMatrix(kernelHoriz, pixelSelection, rect.topLeft(), rect.topLeft(), rect.size(), BORDER_REPEAT); 0532 horizPainter.end(); 0533 0534 KisConvolutionPainter verticalPainter(pixelSelection); 0535 verticalPainter.setChannelFlags(pixelSelection->colorSpace()->channelFlags(false, true)); 0536 verticalPainter.applyMatrix(kernelVertical, interm, rect.topLeft(), rect.topLeft(), rect.size(), BORDER_REPEAT); 0537 verticalPainter.end(); 0538 } 0539 0540 0541 KisGrowSelectionFilter::KisGrowSelectionFilter(qint32 xRadius, qint32 yRadius) 0542 : m_xRadius(xRadius) 0543 , m_yRadius(yRadius) 0544 { 0545 } 0546 0547 KUndo2MagicString KisGrowSelectionFilter::name() 0548 { 0549 return kundo2_i18n("Grow Selection"); 0550 } 0551 0552 QRect KisGrowSelectionFilter::changeRect(const QRect& rect, KisDefaultBoundsBaseSP defaultBounds) 0553 { 0554 Q_UNUSED(defaultBounds); 0555 0556 return rect.adjusted(-m_xRadius, -m_yRadius, m_xRadius, m_yRadius); 0557 } 0558 0559 void KisGrowSelectionFilter::process(KisPixelSelectionSP pixelSelection, const QRect& rect) 0560 { 0561 if (m_xRadius <= 0 || m_yRadius <= 0) return; 0562 0563 /** 0564 * Much code resembles Shrink filter, so please fix bugs 0565 * in both filters 0566 */ 0567 0568 quint8 **buf; // caches the region's pixel data 0569 quint8 **max; // caches the largest values for each column 0570 0571 max = new quint8* [rect.width() + 2 * m_xRadius]; 0572 buf = new quint8* [m_yRadius + 1]; 0573 for (qint32 i = 0; i < m_yRadius + 1; i++) { 0574 buf[i] = new quint8[rect.width()]; 0575 } 0576 quint8* buffer = new quint8[(rect.width() + 2 * m_xRadius) *(m_yRadius + 1)]; 0577 for (qint32 i = 0; i < rect.width() + 2 * m_xRadius; i++) { 0578 if (i < m_xRadius) 0579 max[i] = buffer; 0580 else if (i < rect.width() + m_xRadius) 0581 max[i] = &buffer[(m_yRadius + 1) * (i - m_xRadius)]; 0582 else 0583 max[i] = &buffer[(m_yRadius + 1) * (rect.width() + m_xRadius - 1)]; 0584 0585 for (qint32 j = 0; j < m_xRadius + 1; j++) 0586 max[i][j] = 0; 0587 } 0588 /* offset the max pointer by m_xRadius so the range of the array 0589 is [-m_xRadius] to [region->w + m_xRadius] */ 0590 max += m_xRadius; 0591 0592 quint8* out = new quint8[ rect.width()]; // holds the new scan line we are computing 0593 0594 qint32* circ = new qint32[ 2 * m_xRadius + 1 ]; // holds the y coords of the filter's mask 0595 computeBorder(circ, m_xRadius, m_yRadius); 0596 0597 /* offset the circ pointer by m_xRadius so the range of the array 0598 is [-m_xRadius] to [m_xRadius] */ 0599 circ += m_xRadius; 0600 0601 memset(buf[0], 0, rect.width()); 0602 for (qint32 i = 0; i < m_yRadius && i < rect.height(); i++) { // load top of image 0603 pixelSelection->readBytes(buf[i + 1], rect.x(), rect.y() + i, rect.width(), 1); 0604 } 0605 0606 for (qint32 x = 0; x < rect.width() ; x++) { // set up max for top of image 0607 max[x][0] = 0; // buf[0][x] is always 0 0608 max[x][1] = buf[1][x]; // MAX (buf[1][x], max[x][0]) always = buf[1][x] 0609 for (qint32 j = 2; j < m_yRadius + 1; j++) { 0610 max[x][j] = MAX(buf[j][x], max[x][j-1]); 0611 } 0612 } 0613 0614 for (qint32 y = 0; y < rect.height(); y++) { 0615 rotatePointers(buf, m_yRadius + 1); 0616 if (y < rect.height() - (m_yRadius)) 0617 pixelSelection->readBytes(buf[m_yRadius], rect.x(), rect.y() + y + m_yRadius, rect.width(), 1); 0618 else 0619 memset(buf[m_yRadius], 0, rect.width()); 0620 for (qint32 x = 0; x < rect.width(); x++) { /* update max array */ 0621 for (qint32 i = m_yRadius; i > 0; i--) { 0622 max[x][i] = MAX(MAX(max[x][i - 1], buf[i - 1][x]), buf[i][x]); 0623 } 0624 max[x][0] = buf[0][x]; 0625 } 0626 qint32 last_max = max[0][circ[-1]]; 0627 qint32 last_index = 1; 0628 for (qint32 x = 0; x < rect.width(); x++) { /* render scan line */ 0629 last_index--; 0630 if (last_index >= 0) { 0631 if (last_max == 255) 0632 out[x] = 255; 0633 else { 0634 last_max = 0; 0635 for (qint32 i = m_xRadius; i >= 0; i--) 0636 if (last_max < max[x + i][circ[i]]) { 0637 last_max = max[x + i][circ[i]]; 0638 last_index = i; 0639 } 0640 out[x] = last_max; 0641 } 0642 } else { 0643 last_index = m_xRadius; 0644 last_max = max[x + m_xRadius][circ[m_xRadius]]; 0645 for (qint32 i = m_xRadius - 1; i >= -m_xRadius; i--) 0646 if (last_max < max[x + i][circ[i]]) { 0647 last_max = max[x + i][circ[i]]; 0648 last_index = i; 0649 } 0650 out[x] = last_max; 0651 } 0652 } 0653 pixelSelection->writeBytes(out, rect.x(), rect.y() + y, rect.width(), 1); 0654 } 0655 /* undo the offsets to the pointers so we can free the malloced memory */ 0656 circ -= m_xRadius; 0657 max -= m_xRadius; 0658 0659 delete[] circ; 0660 delete[] buffer; 0661 delete[] max; 0662 for (qint32 i = 0; i < m_yRadius + 1; i++) 0663 delete[] buf[i]; 0664 delete[] buf; 0665 delete[] out; 0666 } 0667 0668 0669 KisShrinkSelectionFilter::KisShrinkSelectionFilter(qint32 xRadius, qint32 yRadius, bool edgeLock) 0670 : m_xRadius(xRadius) 0671 , m_yRadius(yRadius) 0672 , m_edgeLock(edgeLock) 0673 { 0674 } 0675 0676 KUndo2MagicString KisShrinkSelectionFilter::name() 0677 { 0678 return kundo2_i18n("Shrink Selection"); 0679 } 0680 0681 QRect KisShrinkSelectionFilter::changeRect(const QRect& rect, KisDefaultBoundsBaseSP defaultBounds) 0682 { 0683 return m_edgeLock ? defaultBounds->imageBorderRect() : rect; 0684 } 0685 0686 void KisShrinkSelectionFilter::process(KisPixelSelectionSP pixelSelection, const QRect& rect) 0687 { 0688 if (m_xRadius <= 0 || m_yRadius <= 0) return; 0689 0690 /* 0691 pretty much the same as fatten_region only different 0692 blame all bugs in this function on jaycox@gimp.org 0693 */ 0694 /* If edge_lock is true we assume that pixels outside the region 0695 we are passed are identical to the edge pixels. 0696 If edge_lock is false, we assume that pixels outside the region are 0 0697 */ 0698 quint8 **buf; // caches the region's pixels 0699 quint8 **max; // caches the smallest values for each column 0700 qint32 last_max, last_index; 0701 0702 max = new quint8* [rect.width() + 2 * m_xRadius]; 0703 buf = new quint8* [m_yRadius + 1]; 0704 for (qint32 i = 0; i < m_yRadius + 1; i++) { 0705 buf[i] = new quint8[rect.width()]; 0706 } 0707 0708 qint32 buffer_size = (rect.width() + 2 * m_xRadius + 1) * (m_yRadius + 1); 0709 quint8* buffer = new quint8[buffer_size]; 0710 0711 if (m_edgeLock) 0712 memset(buffer, 255, buffer_size); 0713 else 0714 memset(buffer, 0, buffer_size); 0715 0716 for (qint32 i = 0; i < rect.width() + 2 * m_xRadius; i++) { 0717 if (i < m_xRadius) 0718 if (m_edgeLock) 0719 max[i] = buffer; 0720 else 0721 max[i] = &buffer[(m_yRadius + 1) * (rect.width() + m_xRadius)]; 0722 else if (i < rect.width() + m_xRadius) 0723 max[i] = &buffer[(m_yRadius + 1) * (i - m_xRadius)]; 0724 else if (m_edgeLock) 0725 max[i] = &buffer[(m_yRadius + 1) * (rect.width() + m_xRadius - 1)]; 0726 else 0727 max[i] = &buffer[(m_yRadius + 1) * (rect.width() + m_xRadius)]; 0728 } 0729 if (!m_edgeLock) 0730 for (qint32 j = 0 ; j < m_xRadius + 1; j++) max[0][j] = 0; 0731 0732 // offset the max pointer by m_xRadius so the range of the array is [-m_xRadius] to [region->w + m_xRadius] 0733 max += m_xRadius; 0734 0735 quint8* out = new quint8[rect.width()]; // holds the new scan line we are computing 0736 0737 qint32* circ = new qint32[2 * m_xRadius + 1]; // holds the y coords of the filter's mask 0738 0739 computeBorder(circ, m_xRadius, m_yRadius); 0740 0741 // offset the circ pointer by m_xRadius so the range of the array is [-m_xRadius] to [m_xRadius] 0742 circ += m_xRadius; 0743 0744 for (qint32 i = 0; i < m_yRadius && i < rect.height(); i++) // load top of image 0745 pixelSelection->readBytes(buf[i + 1], rect.x(), rect.y() + i, rect.width(), 1); 0746 0747 if (m_edgeLock) 0748 memcpy(buf[0], buf[1], rect.width()); 0749 else 0750 memset(buf[0], 0, rect.width()); 0751 0752 0753 for (qint32 x = 0; x < rect.width(); x++) { // set up max for top of image 0754 max[x][0] = buf[0][x]; 0755 for (qint32 j = 1; j < m_yRadius + 1; j++) 0756 max[x][j] = MIN(buf[j][x], max[x][j-1]); 0757 } 0758 0759 for (qint32 y = 0; y < rect.height(); y++) { 0760 rotatePointers(buf, m_yRadius + 1); 0761 if (y < rect.height() - m_yRadius) 0762 pixelSelection->readBytes(buf[m_yRadius], rect.x(), rect.y() + y + m_yRadius, rect.width(), 1); 0763 else if (m_edgeLock) 0764 memcpy(buf[m_yRadius], buf[m_yRadius - 1], rect.width()); 0765 else 0766 memset(buf[m_yRadius], 0, rect.width()); 0767 0768 for (qint32 x = 0 ; x < rect.width(); x++) { // update max array 0769 for (qint32 i = m_yRadius; i > 0; i--) { 0770 max[x][i] = MIN(MIN(max[x][i - 1], buf[i - 1][x]), buf[i][x]); 0771 } 0772 max[x][0] = buf[0][x]; 0773 } 0774 last_max = max[0][circ[-1]]; 0775 last_index = 0; 0776 0777 for (qint32 x = 0 ; x < rect.width(); x++) { // render scan line 0778 last_index--; 0779 if (last_index >= 0) { 0780 if (last_max == 0) 0781 out[x] = 0; 0782 else { 0783 last_max = 255; 0784 for (qint32 i = m_xRadius; i >= 0; i--) 0785 if (last_max > max[x + i][circ[i]]) { 0786 last_max = max[x + i][circ[i]]; 0787 last_index = i; 0788 } 0789 out[x] = last_max; 0790 } 0791 } else { 0792 last_index = m_xRadius; 0793 last_max = max[x + m_xRadius][circ[m_xRadius]]; 0794 for (qint32 i = m_xRadius - 1; i >= -m_xRadius; i--) 0795 if (last_max > max[x + i][circ[i]]) { 0796 last_max = max[x + i][circ[i]]; 0797 last_index = i; 0798 } 0799 out[x] = last_max; 0800 } 0801 } 0802 pixelSelection->writeBytes(out, rect.x(), rect.y() + y, rect.width(), 1); 0803 } 0804 0805 // undo the offsets to the pointers so we can free the malloced memory 0806 circ -= m_xRadius; 0807 max -= m_xRadius; 0808 0809 delete[] circ; 0810 delete[] buffer; 0811 delete[] max; 0812 for (qint32 i = 0; i < m_yRadius + 1; i++) 0813 delete[] buf[i]; 0814 delete[] buf; 0815 delete[] out; 0816 } 0817 0818 0819 KUndo2MagicString KisSmoothSelectionFilter::name() 0820 { 0821 return kundo2_i18n("Smooth Selection"); 0822 } 0823 0824 QRect KisSmoothSelectionFilter::changeRect(const QRect& rect, KisDefaultBoundsBaseSP defaultBounds) 0825 { 0826 Q_UNUSED(defaultBounds); 0827 0828 const qint32 radius = 1; 0829 return rect.adjusted(-radius, -radius, radius, radius); 0830 } 0831 0832 void KisSmoothSelectionFilter::process(KisPixelSelectionSP pixelSelection, const QRect& rect) 0833 { 0834 // Simple convolution filter to smooth a mask (1bpp) 0835 quint8 *buf[3]; 0836 0837 qint32 width = rect.width(); 0838 qint32 height = rect.height(); 0839 0840 0841 quint8* out = new quint8[width]; 0842 for (qint32 i = 0; i < 3; i++) 0843 buf[i] = new quint8[width + 2]; 0844 0845 0846 // load top of image 0847 pixelSelection->readBytes(buf[0] + 1, rect.x(), rect.y(), width, 1); 0848 0849 buf[0][0] = buf[0][1]; 0850 buf[0][width + 1] = buf[0][width]; 0851 0852 memcpy(buf[1], buf[0], width + 2); 0853 0854 for (qint32 y = 0; y < height; y++) { 0855 if (y + 1 < height) { 0856 pixelSelection->readBytes(buf[2] + 1, rect.x(), rect.y() + y + 1, width, 1); 0857 0858 buf[2][0] = buf[2][1]; 0859 buf[2][width + 1] = buf[2][width]; 0860 } else { 0861 memcpy(buf[2], buf[1], width + 2); 0862 } 0863 0864 for (qint32 x = 0 ; x < width; x++) { 0865 qint32 value = (buf[0][x] + buf[0][x+1] + buf[0][x+2] + 0866 buf[1][x] + buf[2][x+1] + buf[1][x+2] + 0867 buf[2][x] + buf[1][x+1] + buf[2][x+2]); 0868 0869 out[x] = value / 9; 0870 } 0871 0872 pixelSelection->writeBytes(out, rect.x(), rect.y() + y, width, 1); 0873 rotatePointers(buf, 3); 0874 } 0875 0876 for (qint32 i = 0; i < 3; i++) 0877 delete[] buf[i]; 0878 delete[] out; 0879 } 0880 0881 0882 KUndo2MagicString KisInvertSelectionFilter::name() 0883 { 0884 return kundo2_i18n("Invert Selection"); 0885 } 0886 0887 QRect KisInvertSelectionFilter::changeRect(const QRect &rect, KisDefaultBoundsBaseSP defaultBounds) 0888 { 0889 Q_UNUSED(rect); 0890 return defaultBounds->bounds(); 0891 } 0892 0893 void KisInvertSelectionFilter::process(KisPixelSelectionSP pixelSelection, const QRect& rect) 0894 { 0895 Q_UNUSED(rect); 0896 pixelSelection->invert(); 0897 } 0898 0899 constexpr qint32 KisAntiAliasSelectionFilter::offsets[numSteps]; 0900 0901 KUndo2MagicString KisAntiAliasSelectionFilter::name() 0902 { 0903 return kundo2_i18n("Anti-Alias Selection"); 0904 } 0905 0906 bool KisAntiAliasSelectionFilter::getInterpolationValue(qint32 negativeSpanEndDistance, 0907 qint32 positiveSpanEndDistance, 0908 qint32 negativePixelDiff, 0909 qint32 positivePixelDiff, 0910 qint32 currentPixelDiff, 0911 bool negativeSpanExtremeValid, 0912 bool positiveSpanExtremeValid, 0913 qint32 *interpolationValue) const 0914 { 0915 // Since we search a limited number of steps in each direction of the 0916 // current pixel, the end pixel of the span may still belong to the edge. 0917 // So we check for that, and if that's the case we must not smooth the 0918 // current pixel 0919 const bool pixelDiffLessThanZero = currentPixelDiff < 0; 0920 quint32 distance; 0921 if (negativeSpanEndDistance < positiveSpanEndDistance) { 0922 if (!negativeSpanExtremeValid) { 0923 return false; 0924 } 0925 // The pixel is closer to the negative end 0926 const bool spanEndPixelDiffLessThanZero = negativePixelDiff < 0; 0927 if (pixelDiffLessThanZero == spanEndPixelDiffLessThanZero) { 0928 return false; 0929 } 0930 distance = negativeSpanEndDistance; 0931 } else { 0932 if (!positiveSpanExtremeValid) { 0933 return false; 0934 } 0935 // The pixel is closer to the positive end 0936 const bool spanEndPixelDiffLessThanZero = positivePixelDiff < 0; 0937 if (pixelDiffLessThanZero == spanEndPixelDiffLessThanZero) { 0938 return false; 0939 } 0940 distance = positiveSpanEndDistance; 0941 } 0942 const qint32 spanLength = positiveSpanEndDistance + negativeSpanEndDistance; 0943 *interpolationValue = ((distance << 8) / spanLength) + 128; 0944 return *interpolationValue >= 0; 0945 } 0946 0947 void KisAntiAliasSelectionFilter::findSpanExtreme(quint8 **scanlines, qint32 x, qint32 pixelOffset, 0948 qint32 rowMultiplier, qint32 colMultiplier, qint32 direction, 0949 qint32 pixelAvg, qint32 scaledGradient, qint32 currentPixelDiff, 0950 qint32 *spanEndDistance, qint32 *pixelDiff, bool *spanExtremeValid) const 0951 { 0952 *spanEndDistance = 0; 0953 *spanExtremeValid = true; 0954 for (qint32 i = 0; i < numSteps; ++i) { 0955 *spanEndDistance += offsets[i]; 0956 const qint32 row1 = currentScanlineIndex + (direction * *spanEndDistance * rowMultiplier); 0957 const qint32 col1 = x + horizontalBorderSize + (direction * *spanEndDistance * colMultiplier); 0958 const qint32 row2 = row1 + pixelOffset * colMultiplier; 0959 const qint32 col2 = col1 + pixelOffset * rowMultiplier; 0960 const quint8 *pixel1 = scanlines[row1] + col1; 0961 const quint8 *pixel2 = scanlines[row2] + col2; 0962 // Get how different are these edge pixels from the current pixels and 0963 // stop searching if they are too different 0964 *pixelDiff = ((*pixel1 + *pixel2) >> 1) - pixelAvg; 0965 if (qAbs(*pixelDiff) > scaledGradient) { 0966 // If this is the end of the span then check if the corner belongs 0967 // to a jagged border or to a right angled part of the shape 0968 qint32 pixelDiff2; 0969 if ((currentPixelDiff < 0 && *pixelDiff < 0) || (currentPixelDiff > 0 && *pixelDiff > 0)) { 0970 const qint32 row3 = row2 + pixelOffset * colMultiplier; 0971 const qint32 col3 = col2 + pixelOffset * rowMultiplier; 0972 const quint8 *pixel3 = scanlines[row3] + col3; 0973 pixelDiff2 = ((*pixel2 + *pixel3) >> 1) - pixelAvg; 0974 } else { 0975 const qint32 row3 = row1 - pixelOffset * colMultiplier; 0976 const qint32 col3 = col1 - pixelOffset * rowMultiplier; 0977 const quint8 *pixel3 = scanlines[row3] + col3; 0978 pixelDiff2 = ((*pixel1 + *pixel3) >> 1) - pixelAvg; 0979 } 0980 *spanExtremeValid = !(qAbs(pixelDiff2) > scaledGradient); 0981 break; 0982 } 0983 } 0984 } 0985 0986 void KisAntiAliasSelectionFilter::findSpanExtremes(quint8 **scanlines, qint32 x, qint32 pixelOffset, 0987 qint32 rowMultiplier, qint32 colMultiplier, 0988 qint32 pixelAvg, qint32 scaledGradient, qint32 currentPixelDiff, 0989 qint32 *negativeSpanEndDistance, qint32 *positiveSpanEndDistance, 0990 qint32 *negativePixelDiff, qint32 *positivePixelDiff, 0991 bool *negativeSpanExtremeValid, bool *positiveSpanExtremeValid) const 0992 { 0993 findSpanExtreme(scanlines, x, pixelOffset, rowMultiplier, colMultiplier, -1, pixelAvg, scaledGradient, 0994 currentPixelDiff, negativeSpanEndDistance, negativePixelDiff, negativeSpanExtremeValid); 0995 findSpanExtreme(scanlines, x, pixelOffset, rowMultiplier, colMultiplier, 1, pixelAvg, scaledGradient, 0996 currentPixelDiff, positiveSpanEndDistance, positivePixelDiff, positiveSpanExtremeValid); 0997 } 0998 0999 void KisAntiAliasSelectionFilter::process(KisPixelSelectionSP pixelSelection, const QRect &rect) 1000 { 1001 const quint8 defaultPixel = *pixelSelection->defaultPixel().data(); 1002 // Size of a scanline 1003 const quint32 bytesPerScanline = rect.width() + 2 * horizontalBorderSize; 1004 // Size of a scanline padded to a multiple of 8 1005 const quint32 bytesPerPaddedScanline = ((bytesPerScanline + 7) / 8) * 8; 1006 1007 // This buffer contains the number of consecutive scanlines needed to 1008 // process the current scanline 1009 QVector<quint8> buffer(bytesPerPaddedScanline * numberOfScanlines); 1010 1011 // These pointers point to the individual scanlines in the buffer 1012 quint8 *scanlines[numberOfScanlines]; 1013 for (quint32 i = 0; i < numberOfScanlines; ++i) { 1014 scanlines[i] = buffer.data() + i * bytesPerPaddedScanline; 1015 } 1016 1017 // Initialize the scanlines 1018 // Set the border scanlines on the top 1019 for (qint32 i = 0; i < verticalBorderSize; ++i) { 1020 memset(scanlines[i], defaultPixel, bytesPerScanline); 1021 } 1022 // Copy the first scanlines of the image 1023 const quint32 numberOfFirstRows = qMin(rect.height(), numberOfScanlines - verticalBorderSize); 1024 for (quint32 i = verticalBorderSize; i < verticalBorderSize + numberOfFirstRows; ++i) { 1025 // Set the border pixels on the left 1026 memset(scanlines[i], defaultPixel, horizontalBorderSize); 1027 // Copy the pixel data 1028 pixelSelection->readBytes(scanlines[i] + horizontalBorderSize, rect.x(), rect.y() + i - verticalBorderSize, rect.width(), 1); 1029 // Set the border pixels on the right 1030 memset(scanlines[i] + horizontalBorderSize + rect.width(), defaultPixel, horizontalBorderSize); 1031 } 1032 // Set the border scanlines on the bottom 1033 if (verticalBorderSize + numberOfFirstRows < numberOfScanlines) { 1034 for (quint32 i = verticalBorderSize + numberOfFirstRows; i < numberOfScanlines; ++i) { 1035 memset(scanlines[i], defaultPixel, bytesPerScanline); 1036 } 1037 } 1038 // Bufffer that contains the current output scanline 1039 QVector<quint8> antialiasedScanline(rect.width()); 1040 // Main loop 1041 for (int y = 0; y < rect.height(); ++y) 1042 { 1043 // Move to the next scanline 1044 if (y > 0) { 1045 // Update scanline pointers 1046 std::rotate(std::begin(scanlines), std::begin(scanlines) + 1, std::end(scanlines)); 1047 // Copy the next scanline 1048 if (y < rect.height() - verticalBorderSize) { 1049 // Set the border pixels on the left 1050 memset(scanlines[numberOfScanlines - 1], defaultPixel, horizontalBorderSize); 1051 // Copy the pixel data 1052 pixelSelection->readBytes(scanlines[numberOfScanlines - 1] + horizontalBorderSize, rect.x(), rect.y() + y + verticalBorderSize, rect.width(), 1); 1053 // Set the border pixels on the right 1054 memset(scanlines[numberOfScanlines - 1] + horizontalBorderSize + rect.width(), defaultPixel, horizontalBorderSize); 1055 } else { 1056 memset(scanlines[numberOfScanlines - 1], defaultPixel, bytesPerScanline); 1057 } 1058 } 1059 // Process the pixels in the current scanline 1060 for (int x = 0; x < rect.width(); ++x) 1061 { 1062 // Get the current pixel and neighbors 1063 quint8 *pixelPtrM = scanlines[currentScanlineIndex ] + x + horizontalBorderSize; 1064 quint8 *pixelPtrN = scanlines[currentScanlineIndex - 1] + x + horizontalBorderSize; 1065 quint8 *pixelPtrS = scanlines[currentScanlineIndex + 1] + x + horizontalBorderSize; 1066 const qint32 pixelNW = *(pixelPtrN - 1); 1067 const qint32 pixelN = *(pixelPtrN ); 1068 const qint32 pixelNE = *(pixelPtrN + 1); 1069 const qint32 pixelW = *(pixelPtrM - 1); 1070 const qint32 pixelM = *(pixelPtrM ); 1071 const qint32 pixelE = *(pixelPtrM + 1); 1072 const qint32 pixelSW = *(pixelPtrS - 1); 1073 const qint32 pixelS = *(pixelPtrS ); 1074 const qint32 pixelSE = *(pixelPtrS + 1); 1075 // Get the gradients 1076 const qint32 rowNSum = (pixelNW >> 2) + (pixelN >> 1) + (pixelNE >> 2); 1077 const qint32 rowMSum = (pixelW >> 2) + (pixelM >> 1) + (pixelE >> 2); 1078 const qint32 rowSSum = (pixelSW >> 2) + (pixelS >> 1) + (pixelSE >> 2); 1079 const qint32 colWSum = (pixelNW >> 2) + (pixelW >> 1) + (pixelSW >> 2); 1080 const qint32 colMSum = (pixelN >> 2) + (pixelM >> 1) + (pixelS >> 2); 1081 const qint32 colESum = (pixelNE >> 2) + (pixelE >> 1) + (pixelSE >> 2); 1082 const qint32 gradientN = qAbs(rowMSum - rowNSum); 1083 const qint32 gradientS = qAbs(rowSSum - rowMSum); 1084 const qint32 gradientW = qAbs(colMSum - colWSum); 1085 const qint32 gradientE = qAbs(colESum - colMSum); 1086 // Get the maximum gradient 1087 const qint32 maxGradientNS = qMax(gradientN, gradientS); 1088 const qint32 maxGradientWE = qMax(gradientW, gradientE); 1089 const qint32 maxGradient = qMax(maxGradientNS, maxGradientWE); 1090 // Return early if the gradient is bellow some threshold (given by 1091 // the value bellow which the jagged edge is not noticeable) 1092 if (maxGradient < edgeThreshold) { 1093 antialiasedScanline[x] = pixelM; 1094 continue; 1095 } 1096 // Collect some info about the pixel and neighborhood 1097 qint32 neighborPixel, gradient; 1098 qint32 pixelOffset, rowMultiplier, colMultiplier; 1099 if (maxGradientNS > maxGradientWE) { 1100 // Horizontal span 1101 if (gradientN > gradientS) { 1102 // The edge is formed with the top pixel 1103 neighborPixel = pixelN; 1104 gradient = gradientN; 1105 pixelOffset = -1; 1106 } else { 1107 // The edge is formed with the bottom pixel 1108 neighborPixel = pixelS; 1109 gradient = gradientS; 1110 pixelOffset = 1; 1111 } 1112 rowMultiplier = 0; 1113 colMultiplier = 1; 1114 } else { 1115 // Vertical span 1116 if (gradientW > gradientE) { 1117 // The edge is formed with the left pixel 1118 neighborPixel = pixelW; 1119 gradient = gradientW; 1120 pixelOffset = -1; 1121 } else { 1122 // The edge is formed with the right pixel 1123 neighborPixel = pixelE; 1124 gradient = gradientE; 1125 pixelOffset = 1; 1126 } 1127 rowMultiplier = 1; 1128 colMultiplier = 0; 1129 } 1130 // Find the span extremes 1131 const qint32 pixelAvg = (neighborPixel + pixelM) >> 1; 1132 const qint32 currentPixelDiff = pixelM - pixelAvg; 1133 qint32 negativePixelDiff, positivePixelDiff; 1134 qint32 negativeSpanEndDistance, positiveSpanEndDistance; 1135 bool negativeSpanExtremeValid, positiveSpanExtremeValid; 1136 findSpanExtremes(scanlines, x, pixelOffset, 1137 rowMultiplier, colMultiplier, 1138 pixelAvg, gradient >> 2, currentPixelDiff, 1139 &negativeSpanEndDistance, &positiveSpanEndDistance, 1140 &negativePixelDiff, &positivePixelDiff, 1141 &negativeSpanExtremeValid, &positiveSpanExtremeValid); 1142 // Get the interpolation value for this pixel given the span extent 1143 // and perform linear interpolation between the current pixel and 1144 // the edge neighbor 1145 qint32 interpolationValue; 1146 if (!getInterpolationValue(negativeSpanEndDistance, positiveSpanEndDistance, 1147 negativePixelDiff, positivePixelDiff, currentPixelDiff, 1148 negativeSpanExtremeValid, positiveSpanExtremeValid, &interpolationValue)) { 1149 antialiasedScanline[x] = pixelM; 1150 } else { 1151 antialiasedScanline[x] = neighborPixel + ((pixelM - neighborPixel) * interpolationValue >> 8); 1152 } 1153 } 1154 // Copy the scanline data to the mask 1155 pixelSelection->writeBytes(antialiasedScanline.data(), rect.x(), rect.y() + y, rect.width(), 1); 1156 } 1157 }