Krita Source Code Documentation
Loading...
Searching...
No Matches
kis_selection_filters.cpp
Go to the documentation of this file.
1/*
2 * SPDX-FileCopyrightText: 2005 Michael Thaler
3 * SPDX-FileCopyrightText: 2011 Dmitry Kazakov <dimula73@gmail.com>
4 *
5 * SPDX-License-Identifier: GPL-2.0-or-later
6 */
7
9
10#include <algorithm>
11
12#include <klocalizedstring.h>
13
14#include <KoColorSpace.h>
17#include "kis_pixel_selection.h"
19
20#define MAX(a, b) ((a) > (b) ? (a) : (b))
21#define MIN(a, b) ((a) < (b) ? (a) : (b))
22#define RINT(x) floor ((x) + 0.5)
23
27
32
34{
35 Q_UNUSED(defaultBounds);
36 return rect;
37}
38
39void KisSelectionFilter::computeBorder(qint32* circ, qint32 xradius, qint32 yradius)
40{
41 qint32 i;
42 qint32 diameter = xradius * 2 + 1;
43 double tmp;
44
45 for (i = 0; i < diameter; i++) {
46 if (i > xradius)
47 tmp = (i - xradius) - 0.5;
48 else if (i < xradius)
49 tmp = (xradius - i) - 0.5;
50 else
51 tmp = 0.0;
52
53 double divisor = (double) xradius;
54 if (divisor == 0.0) {
55 divisor = 1.0;
56 }
57 circ[i] = (qint32) RINT(yradius * sqrt(xradius * xradius - tmp * tmp) / divisor);
58 }
59}
60
61void KisSelectionFilter::rotatePointers(quint8** p, quint32 n)
62{
63 quint32 i;
64 quint8 *p0 = p[0];
65 for (i = 0; i < n - 1; i++) {
66 p[i] = p[i + 1];
67 }
68 p[i] = p0;
69}
70
71void KisSelectionFilter::computeTransition(quint8* transition, quint8** buf, qint32 width)
72{
73 qint32 x = 0;
74
75 if (width == 1) {
76 if (buf[1][x] > 127 && (buf[0][x] < 128 || buf[2][x] < 128))
77 transition[x] = 255;
78 else
79 transition[x] = 0;
80 return;
81 }
82 if (buf[1][x] > 127) {
83 if (buf[0][x] < 128 || buf[0][x + 1] < 128 ||
84 buf[1][x + 1] < 128 ||
85 buf[2][x] < 128 || buf[2][x + 1] < 128)
86 transition[x] = 255;
87 else
88 transition[x] = 0;
89 } else
90 transition[x] = 0;
91 for (qint32 x = 1; x < width - 1; x++) {
92 if (buf[1][x] >= 128) {
93 if (buf[0][x - 1] < 128 || buf[0][x] < 128 || buf[0][x + 1] < 128 ||
94 buf[1][x - 1] < 128 || buf[1][x + 1] < 128 ||
95 buf[2][x - 1] < 128 || buf[2][x] < 128 || buf[2][x + 1] < 128)
96 transition[x] = 255;
97 else
98 transition[x] = 0;
99 } else
100 transition[x] = 0;
101 }
102 if (buf[1][x] >= 128) {
103 if (buf[0][x - 1] < 128 || buf[0][x] < 128 ||
104 buf[1][x - 1] < 128 ||
105 buf[2][x - 1] < 128 || buf[2][x] < 128)
106 transition[x] = 255;
107 else
108 transition[x] = 0;
109 } else
110 transition[x] = 0;
111}
112
113
115{
116 return kundo2_i18n("Erode Selection");
117}
118
120{
121 Q_UNUSED(defaultBounds);
122
123 const qint32 radius = 1;
124 return rect.adjusted(-radius, -radius, radius, radius);
125}
126
128{
129 // Erode (radius 1 pixel) a mask (1bpp)
130 quint8* buf[3];
131
132 qint32 width = rect.width();
133 qint32 height = rect.height();
134
135 quint8* out = new quint8[width];
136 for (qint32 i = 0; i < 3; i++)
137 buf[i] = new quint8[width + 2];
138
139
140 // load top of image
141 pixelSelection->readBytes(buf[0] + 1, rect.x(), rect.y(), width, 1);
142
143 buf[0][0] = buf[0][1];
144 buf[0][width + 1] = buf[0][width];
145
146 memcpy(buf[1], buf[0], width + 2);
147
148 for (qint32 y = 0; y < height; y++) {
149 if (y + 1 < height) {
150 pixelSelection->readBytes(buf[2] + 1, rect.x(), rect.y() + y + 1, width, 1);
151
152 buf[2][0] = buf[2][1];
153 buf[2][width + 1] = buf[2][width];
154 } else {
155 memcpy(buf[2], buf[1], width + 2);
156 }
157
158 for (qint32 x = 0 ; x < width; x++) {
159 qint32 min = 255;
160
161 if (buf[0][x+1] < min) min = buf[0][x+1];
162 if (buf[1][x] < min) min = buf[1][x];
163 if (buf[1][x+1] < min) min = buf[1][x+1];
164 if (buf[1][x+2] < min) min = buf[1][x+2];
165 if (buf[2][x+1] < min) min = buf[2][x+1];
166
167 out[x] = min;
168 }
169
170 pixelSelection->writeBytes(out, rect.x(), rect.y() + y, width, 1);
171 rotatePointers(buf, 3);
172 }
173
174 for (qint32 i = 0; i < 3; i++)
175 delete[] buf[i];
176 delete[] out;
177}
178
179
181{
182 return kundo2_i18n("Dilate Selection");
183}
184
186{
187 Q_UNUSED(defaultBounds);
188
189 const qint32 radius = 1;
190 return rect.adjusted(-radius, -radius, radius, radius);
191}
192
194 {
195 // dilate (radius 1 pixel) a mask (1bpp)
196 quint8* buf[3];
197
198 qint32 width = rect.width();
199 qint32 height = rect.height();
200
201 quint8* out = new quint8[width];
202 for (qint32 i = 0; i < 3; i++)
203 buf[i] = new quint8[width + 2];
204
205
206 // load top of image
207 pixelSelection->readBytes(buf[0] + 1, rect.x(), rect.y(), width, 1);
208
209 buf[0][0] = buf[0][1];
210 buf[0][width + 1] = buf[0][width];
211
212 memcpy(buf[1], buf[0], width + 2);
213
214 for (qint32 y = 0; y < height; y++) {
215 if (y + 1 < height) {
216 pixelSelection->readBytes(buf[2] + 1, rect.x(), rect.y() + y + 1, width, 1);
217
218 buf[2][0] = buf[2][1];
219 buf[2][width + 1] = buf[2][width];
220 } else {
221 memcpy(buf[2], buf[1], width + 2);
222 }
223
224 for (qint32 x = 0 ; x < width; x++) {
225 qint32 max = 0;
226
227 if (buf[0][x+1] > max) max = buf[0][x+1];
228 if (buf[1][x] > max) max = buf[1][x];
229 if (buf[1][x+1] > max) max = buf[1][x+1];
230 if (buf[1][x+2] > max) max = buf[1][x+2];
231 if (buf[2][x+1] > max) max = buf[2][x+1];
232
233 out[x] = max;
234 }
235
236 pixelSelection->writeBytes(out, rect.x(), rect.y() + y, width, 1);
237 rotatePointers(buf, 3);
238 }
239
240 for (qint32 i = 0; i < 3; i++)
241 delete[] buf[i];
242 delete[] out;
243}
244
245
246KisBorderSelectionFilter::KisBorderSelectionFilter(qint32 xRadius, qint32 yRadius, bool antialiasing)
247 : m_xRadius(xRadius),
248 m_yRadius(yRadius),
249 m_antialiasing(antialiasing)
250{
251}
252
254{
255 return kundo2_i18n("Border Selection");
256}
257
259{
260 Q_UNUSED(defaultBounds);
261
262 return rect.adjusted(-m_xRadius, -m_yRadius, m_xRadius, m_yRadius);
263}
264
266{
267 if (m_xRadius <= 0 || m_yRadius <= 0) return;
268
269 quint8 *buf[3];
270 quint8 **density;
271 quint8 **transition;
272
273 if (m_xRadius == 1 && m_yRadius == 1) {
274 // optimize this case specifically
275 quint8* source[3];
276
277 for (qint32 i = 0; i < 3; i++)
278 source[i] = new quint8[rect.width()];
279
280 quint8* transition = new quint8[rect.width()];
281
282 pixelSelection->readBytes(source[0], rect.x(), rect.y(), rect.width(), 1);
283 memcpy(source[1], source[0], rect.width());
284 if (rect.height() > 1)
285 pixelSelection->readBytes(source[2], rect.x(), rect.y() + 1, rect.width(), 1);
286 else
287 memcpy(source[2], source[1], rect.width());
288
289 computeTransition(transition, source, rect.width());
290 pixelSelection->writeBytes(transition, rect.x(), rect.y(), rect.width(), 1);
291
292 for (qint32 y = 1; y < rect.height(); y++) {
294 if (y + 1 < rect.height())
295 pixelSelection->readBytes(source[2], rect.x(), rect.y() + y + 1, rect.width(), 1);
296 else
297 memcpy(source[2], source[1], rect.width());
298 computeTransition(transition, source, rect.width());
299 pixelSelection->writeBytes(transition, rect.x(), rect.y() + y, rect.width(), 1);
300 }
301
302 for (qint32 i = 0; i < 3; i++)
303 delete[] source[i];
304 delete[] transition;
305 return;
306 }
307
308 qint32* max = new qint32[rect.width() + 2 * m_xRadius];
309 for (qint32 i = 0; i < (rect.width() + 2 * m_xRadius); i++)
310 max[i] = m_yRadius + 2;
311 max += m_xRadius;
312
313 for (qint32 i = 0; i < 3; i++)
314 buf[i] = new quint8[rect.width()];
315
316 transition = new quint8*[m_yRadius + 1];
317 for (qint32 i = 0; i < m_yRadius + 1; i++) {
318 transition[i] = new quint8[rect.width() + 2 * m_xRadius];
319 memset(transition[i], 0, rect.width() + 2 * m_xRadius);
320 transition[i] += m_xRadius;
321 }
322 quint8* out = new quint8[rect.width()];
323 density = new quint8*[2 * m_xRadius + 1];
324 density += m_xRadius;
325
326 for (qint32 x = 0; x < (m_xRadius + 1); x++) { // allocate density[][]
327 density[ x] = new quint8[2 * m_yRadius + 1];
328 density[ x] += m_yRadius;
329 density[-x] = density[x];
330 }
331
332 // compute density[][]
333 if (m_antialiasing) {
334 KIS_SAFE_ASSERT_RECOVER_NOOP(m_xRadius == m_yRadius && "anisotropic fading is not implemented");
335 const qreal maxRadius = 0.5 * (m_xRadius + m_yRadius);
336 const qreal minRadius = maxRadius - 1.0;
337
338 for (qint32 x = 0; x < (m_xRadius + 1); x++) {
339 double dist;
340 quint8 a;
341
342 for (qint32 y = 0; y < (m_yRadius + 1); y++) {
343
344 dist = sqrt(pow2(x) + pow2(y));
345
346 if (dist > maxRadius) {
347 a = 0;
348 } else if (dist > minRadius) {
349 a = qRound((1.0 - dist + minRadius) * 255.0);
350 } else {
351 a = 255;
352 }
353
354 density[ x][ y] = a;
355 density[ x][-y] = a;
356 density[-x][ y] = a;
357 density[-x][-y] = a;
358 }
359 }
360
361 } else {
362 for (qint32 x = 0; x < (m_xRadius + 1); x++) {
363 double tmpx, tmpy, dist;
364 quint8 a;
365
366 tmpx = x > 0.0 ? x - 0.5 : 0.0;
367
368 for (qint32 y = 0; y < (m_yRadius + 1); y++) {
369 tmpy = y > 0.0 ? y - 0.5 : 0.0;
370
371 dist = (pow2(tmpy) / pow2(m_yRadius) +
372 pow2(tmpx) / pow2(m_xRadius));
373
374 a = dist <= 1.0 ? 255 : 0;
375
376 density[ x][ y] = a;
377 density[ x][-y] = a;
378 density[-x][ y] = a;
379 density[-x][-y] = a;
380 }
381 }
382 }
383
384 pixelSelection->readBytes(buf[0], rect.x(), rect.y(), rect.width(), 1);
385 memcpy(buf[1], buf[0], rect.width());
386 if (rect.height() > 1)
387 pixelSelection->readBytes(buf[2], rect.x(), rect.y() + 1, rect.width(), 1);
388 else
389 memcpy(buf[2], buf[1], rect.width());
390 computeTransition(transition[1], buf, rect.width());
391
392 for (qint32 y = 1; y < m_yRadius && y + 1 < rect.height(); y++) { // set up top of image
393 rotatePointers(buf, 3);
394 pixelSelection->readBytes(buf[2], rect.x(), rect.y() + y + 1, rect.width(), 1);
395 computeTransition(transition[y + 1], buf, rect.width());
396 }
397 for (qint32 x = 0; x < rect.width(); x++) { // set up max[] for top of image
398 max[x] = -(m_yRadius + 7);
399 for (qint32 j = 1; j < m_yRadius + 1; j++)
400 if (transition[j][x]) {
401 max[x] = j;
402 break;
403 }
404 }
405 for (qint32 y = 0; y < rect.height(); y++) { // main calculation loop
406 rotatePointers(buf, 3);
407 rotatePointers(transition, m_yRadius + 1);
408 if (y < rect.height() - (m_yRadius + 1)) {
409 pixelSelection->readBytes(buf[2], rect.x(), rect.y() + y + m_yRadius + 1, rect.width(), 1);
410 computeTransition(transition[m_yRadius], buf, rect.width());
411 } else
412 memcpy(transition[m_yRadius], transition[m_yRadius - 1], rect.width());
413
414 for (qint32 x = 0; x < rect.width(); x++) { // update max array
415 if (max[x] < 1) {
416 if (max[x] <= -m_yRadius) {
417 if (transition[m_yRadius][x])
418 max[x] = m_yRadius;
419 else
420 max[x]--;
421 } else if (transition[-max[x]][x])
422 max[x] = -max[x];
423 else if (transition[-max[x] + 1][x])
424 max[x] = -max[x] + 1;
425 else
426 max[x]--;
427 } else
428 max[x]--;
429 if (max[x] < -m_yRadius - 1)
430 max[x] = -m_yRadius - 1;
431 }
432 quint8 last_max = max[0][density[-1]];
433 qint32 last_index = 1;
434 for (qint32 x = 0 ; x < rect.width(); x++) { // render scan line
435 last_index--;
436 if (last_index >= 0) {
437 last_max = 0;
438 for (qint32 i = m_xRadius; i >= 0; i--)
439 if (max[x + i] <= m_yRadius && max[x + i] >= -m_yRadius && density[i][max[x+i]] > last_max) {
440 last_max = density[i][max[x + i]];
441 last_index = i;
442 }
443 out[x] = last_max;
444 } else {
445 last_max = 0;
446 for (qint32 i = m_xRadius; i >= -m_xRadius; i--)
447 if (max[x + i] <= m_yRadius && max[x + i] >= -m_yRadius && density[i][max[x + i]] > last_max) {
448 last_max = density[i][max[x + i]];
449 last_index = i;
450 }
451 out[x] = last_max;
452 }
453 if (last_max == 0) {
454 qint32 i;
455 for (i = x + 1; i < rect.width(); i++) {
456 if (max[i] >= -m_yRadius)
457 break;
458 }
459 if (i - x > m_xRadius) {
460 for (; x < i - m_xRadius; x++)
461 out[x] = 0;
462 x--;
463 }
464 last_index = m_xRadius;
465 }
466 }
467 pixelSelection->writeBytes(out, rect.x(), rect.y() + y, rect.width(), 1);
468 }
469 delete [] out;
470
471 for (qint32 i = 0; i < 3; i++)
472 delete[] buf[i];
473
474 max -= m_xRadius;
475 delete[] max;
476
477 for (qint32 i = 0; i < m_yRadius + 1; i++) {
478 transition[i] -= m_xRadius;
479 delete transition[i];
480 }
481 delete[] transition;
482
483 for (qint32 i = 0; i < m_xRadius + 1 ; i++) {
484 density[i] -= m_yRadius;
485 delete density[i];
486 }
487 density -= m_xRadius;
488 delete[] density;
489}
490
491
493 : m_radius(radius)
494{
495}
496
498{
499 return kundo2_i18n("Feather Selection");
500}
501
503{
504 Q_UNUSED(defaultBounds);
505
506 return rect.adjusted(-m_radius, -m_radius,
508}
509
511{
512 // compute horizontal kernel
513 const uint kernelSize = m_radius * 2 + 1;
514 Eigen::Matrix<qreal, Eigen::Dynamic, Eigen::Dynamic> gaussianMatrix(1, kernelSize);
515
516 const qreal multiplicand = 1.0 / (2.0 * M_PI * m_radius * m_radius);
517 const qreal exponentMultiplicand = 1.0 / (2.0 * m_radius * m_radius);
518
519 for (uint x = 0; x < kernelSize; x++) {
520 uint xDistance = qAbs((int)m_radius - (int)x);
521 gaussianMatrix(0, x) = multiplicand * exp( -(qreal)((xDistance * xDistance) + (m_radius * m_radius)) * exponentMultiplicand );
522 }
523
524 KisConvolutionKernelSP kernelHoriz = KisConvolutionKernel::fromMatrix(gaussianMatrix, 0, gaussianMatrix.sum());
525 KisConvolutionKernelSP kernelVertical = KisConvolutionKernel::fromMatrix(gaussianMatrix.transpose(), 0, gaussianMatrix.sum());
526
527 KisPaintDeviceSP interm = new KisPaintDevice(pixelSelection->colorSpace());
528 interm->prepareClone(pixelSelection);
529
530 KisConvolutionPainter horizPainter(interm);
531 horizPainter.setChannelFlags(interm->colorSpace()->channelFlags(false, true));
532 horizPainter.applyMatrix(kernelHoriz, pixelSelection, rect.topLeft(), rect.topLeft(), rect.size(), BORDER_REPEAT);
533 horizPainter.end();
534
535 KisConvolutionPainter verticalPainter(pixelSelection);
536 verticalPainter.setChannelFlags(pixelSelection->colorSpace()->channelFlags(false, true));
537 verticalPainter.applyMatrix(kernelVertical, interm, rect.topLeft(), rect.topLeft(), rect.size(), BORDER_REPEAT);
538 verticalPainter.end();
539}
540
541
543 : m_xRadius(xRadius)
544 , m_yRadius(yRadius)
545{
546}
547
549{
550 return kundo2_i18n("Grow Selection");
551}
552
554{
555 Q_UNUSED(defaultBounds);
556
557 return rect.adjusted(-m_xRadius, -m_yRadius, m_xRadius, m_yRadius);
558}
559
561{
562 if (m_xRadius <= 0 || m_yRadius <= 0) return;
563
569 quint8 **buf; // caches the region's pixel data
570 quint8 **max; // caches the largest values for each column
571
572 max = new quint8* [rect.width() + 2 * m_xRadius];
573 buf = new quint8* [m_yRadius + 1];
574 for (qint32 i = 0; i < m_yRadius + 1; i++) {
575 buf[i] = new quint8[rect.width()];
576 }
577 quint8* buffer = new quint8[(rect.width() + 2 * m_xRadius) *(m_yRadius + 1)];
578 for (qint32 i = 0; i < rect.width() + 2 * m_xRadius; i++) {
579 if (i < m_xRadius)
580 max[i] = buffer;
581 else if (i < rect.width() + m_xRadius)
582 max[i] = &buffer[(m_yRadius + 1) * (i - m_xRadius)];
583 else
584 max[i] = &buffer[(m_yRadius + 1) * (rect.width() + m_xRadius - 1)];
585
586 for (qint32 j = 0; j < m_xRadius + 1; j++)
587 max[i][j] = 0;
588 }
589 /* offset the max pointer by m_xRadius so the range of the array
590 is [-m_xRadius] to [region->w + m_xRadius] */
591 max += m_xRadius;
592
593 quint8* out = new quint8[ rect.width()]; // holds the new scan line we are computing
594
595 qint32* circ = new qint32[ 2 * m_xRadius + 1 ]; // holds the y coords of the filter's mask
597
598 /* offset the circ pointer by m_xRadius so the range of the array
599 is [-m_xRadius] to [m_xRadius] */
600 circ += m_xRadius;
601
602 memset(buf[0], 0, rect.width());
603 for (qint32 i = 0; i < m_yRadius && i < rect.height(); i++) { // load top of image
604 pixelSelection->readBytes(buf[i + 1], rect.x(), rect.y() + i, rect.width(), 1);
605 }
606
607 for (qint32 x = 0; x < rect.width() ; x++) { // set up max for top of image
608 max[x][0] = 0; // buf[0][x] is always 0
609 max[x][1] = buf[1][x]; // MAX (buf[1][x], max[x][0]) always = buf[1][x]
610 for (qint32 j = 2; j < m_yRadius + 1; j++) {
611 max[x][j] = MAX(buf[j][x], max[x][j-1]);
612 }
613 }
614
615 for (qint32 y = 0; y < rect.height(); y++) {
616 rotatePointers(buf, m_yRadius + 1);
617 if (y < rect.height() - (m_yRadius))
618 pixelSelection->readBytes(buf[m_yRadius], rect.x(), rect.y() + y + m_yRadius, rect.width(), 1);
619 else
620 memset(buf[m_yRadius], 0, rect.width());
621 for (qint32 x = 0; x < rect.width(); x++) { /* update max array */
622 for (qint32 i = m_yRadius; i > 0; i--) {
623 max[x][i] = MAX(MAX(max[x][i - 1], buf[i - 1][x]), buf[i][x]);
624 }
625 max[x][0] = buf[0][x];
626 }
627 qint32 last_max = max[0][circ[-1]];
628 qint32 last_index = 1;
629 for (qint32 x = 0; x < rect.width(); x++) { /* render scan line */
630 last_index--;
631 if (last_index >= 0) {
632 if (last_max == 255)
633 out[x] = 255;
634 else {
635 last_max = 0;
636 for (qint32 i = m_xRadius; i >= 0; i--)
637 if (last_max < max[x + i][circ[i]]) {
638 last_max = max[x + i][circ[i]];
639 last_index = i;
640 }
641 out[x] = last_max;
642 }
643 } else {
644 last_index = m_xRadius;
645 last_max = max[x + m_xRadius][circ[m_xRadius]];
646 for (qint32 i = m_xRadius - 1; i >= -m_xRadius; i--)
647 if (last_max < max[x + i][circ[i]]) {
648 last_max = max[x + i][circ[i]];
649 last_index = i;
650 }
651 out[x] = last_max;
652 }
653 }
654 pixelSelection->writeBytes(out, rect.x(), rect.y() + y, rect.width(), 1);
655 }
656 /* undo the offsets to the pointers so we can free the malloced memory */
657 circ -= m_xRadius;
658 max -= m_xRadius;
659
660 delete[] circ;
661 delete[] buffer;
662 delete[] max;
663 for (qint32 i = 0; i < m_yRadius + 1; i++)
664 delete[] buf[i];
665 delete[] buf;
666 delete[] out;
667}
668
669
670KisShrinkSelectionFilter::KisShrinkSelectionFilter(qint32 xRadius, qint32 yRadius, bool edgeLock)
671 : m_xRadius(xRadius)
672 , m_yRadius(yRadius)
673 , m_edgeLock(edgeLock)
674{
675}
676
678{
679 return kundo2_i18n("Shrink Selection");
680}
681
683{
684 return m_edgeLock ? defaultBounds->imageBorderRect() : rect;
685}
686
688{
689 if (m_xRadius <= 0 || m_yRadius <= 0) return;
690
691 /*
692 pretty much the same as fatten_region only different
693 blame all bugs in this function on jaycox@gimp.org
694 */
695 /* If edge_lock is true we assume that pixels outside the region
696 we are passed are identical to the edge pixels.
697 If edge_lock is false, we assume that pixels outside the region are 0
698 */
699 quint8 **buf; // caches the region's pixels
700 quint8 **max; // caches the smallest values for each column
701 qint32 last_max, last_index;
702
703 max = new quint8* [rect.width() + 2 * m_xRadius];
704 buf = new quint8* [m_yRadius + 1];
705 for (qint32 i = 0; i < m_yRadius + 1; i++) {
706 buf[i] = new quint8[rect.width()];
707 }
708
709 qint32 buffer_size = (rect.width() + 2 * m_xRadius + 1) * (m_yRadius + 1);
710 quint8* buffer = new quint8[buffer_size];
711
712 if (m_edgeLock)
713 memset(buffer, 255, buffer_size);
714 else
715 memset(buffer, 0, buffer_size);
716
717 for (qint32 i = 0; i < rect.width() + 2 * m_xRadius; i++) {
718 if (i < m_xRadius)
719 if (m_edgeLock)
720 max[i] = buffer;
721 else
722 max[i] = &buffer[(m_yRadius + 1) * (rect.width() + m_xRadius)];
723 else if (i < rect.width() + m_xRadius)
724 max[i] = &buffer[(m_yRadius + 1) * (i - m_xRadius)];
725 else if (m_edgeLock)
726 max[i] = &buffer[(m_yRadius + 1) * (rect.width() + m_xRadius - 1)];
727 else
728 max[i] = &buffer[(m_yRadius + 1) * (rect.width() + m_xRadius)];
729 }
730 if (!m_edgeLock)
731 for (qint32 j = 0 ; j < m_xRadius + 1; j++) max[0][j] = 0;
732
733 // offset the max pointer by m_xRadius so the range of the array is [-m_xRadius] to [region->w + m_xRadius]
734 max += m_xRadius;
735
736 quint8* out = new quint8[rect.width()]; // holds the new scan line we are computing
737
738 qint32* circ = new qint32[2 * m_xRadius + 1]; // holds the y coords of the filter's mask
739
741
742 // offset the circ pointer by m_xRadius so the range of the array is [-m_xRadius] to [m_xRadius]
743 circ += m_xRadius;
744
745 for (qint32 i = 0; i < m_yRadius && i < rect.height(); i++) // load top of image
746 pixelSelection->readBytes(buf[i + 1], rect.x(), rect.y() + i, rect.width(), 1);
747
748 if (m_edgeLock)
749 memcpy(buf[0], buf[1], rect.width());
750 else
751 memset(buf[0], 0, rect.width());
752
753
754 for (qint32 x = 0; x < rect.width(); x++) { // set up max for top of image
755 max[x][0] = buf[0][x];
756 for (qint32 j = 1; j < m_yRadius + 1; j++)
757 max[x][j] = MIN(buf[j][x], max[x][j-1]);
758 }
759
760 for (qint32 y = 0; y < rect.height(); y++) {
761 rotatePointers(buf, m_yRadius + 1);
762 if (y < rect.height() - m_yRadius)
763 pixelSelection->readBytes(buf[m_yRadius], rect.x(), rect.y() + y + m_yRadius, rect.width(), 1);
764 else if (m_edgeLock)
765 memcpy(buf[m_yRadius], buf[m_yRadius - 1], rect.width());
766 else
767 memset(buf[m_yRadius], 0, rect.width());
768
769 for (qint32 x = 0 ; x < rect.width(); x++) { // update max array
770 for (qint32 i = m_yRadius; i > 0; i--) {
771 max[x][i] = MIN(MIN(max[x][i - 1], buf[i - 1][x]), buf[i][x]);
772 }
773 max[x][0] = buf[0][x];
774 }
775 last_max = max[0][circ[-1]];
776 last_index = 0;
777
778 for (qint32 x = 0 ; x < rect.width(); x++) { // render scan line
779 last_index--;
780 if (last_index >= 0) {
781 if (last_max == 0)
782 out[x] = 0;
783 else {
784 last_max = 255;
785 for (qint32 i = m_xRadius; i >= 0; i--)
786 if (last_max > max[x + i][circ[i]]) {
787 last_max = max[x + i][circ[i]];
788 last_index = i;
789 }
790 out[x] = last_max;
791 }
792 } else {
793 last_index = m_xRadius;
794 last_max = max[x + m_xRadius][circ[m_xRadius]];
795 for (qint32 i = m_xRadius - 1; i >= -m_xRadius; i--)
796 if (last_max > max[x + i][circ[i]]) {
797 last_max = max[x + i][circ[i]];
798 last_index = i;
799 }
800 out[x] = last_max;
801 }
802 }
803 pixelSelection->writeBytes(out, rect.x(), rect.y() + y, rect.width(), 1);
804 }
805
806 // undo the offsets to the pointers so we can free the malloced memory
807 circ -= m_xRadius;
808 max -= m_xRadius;
809
810 delete[] circ;
811 delete[] buffer;
812 delete[] max;
813 for (qint32 i = 0; i < m_yRadius + 1; i++)
814 delete[] buf[i];
815 delete[] buf;
816 delete[] out;
817}
818
819
821{
822 return kundo2_i18n("Smooth Selection");
823}
824
826{
827 Q_UNUSED(defaultBounds);
828
829 const qint32 radius = 1;
830 return rect.adjusted(-radius, -radius, radius, radius);
831}
832
834{
835 // Simple convolution filter to smooth a mask (1bpp)
836 quint8 *buf[3];
837
838 qint32 width = rect.width();
839 qint32 height = rect.height();
840
841
842 quint8* out = new quint8[width];
843 for (qint32 i = 0; i < 3; i++)
844 buf[i] = new quint8[width + 2];
845
846
847 // load top of image
848 pixelSelection->readBytes(buf[0] + 1, rect.x(), rect.y(), width, 1);
849
850 buf[0][0] = buf[0][1];
851 buf[0][width + 1] = buf[0][width];
852
853 memcpy(buf[1], buf[0], width + 2);
854
855 for (qint32 y = 0; y < height; y++) {
856 if (y + 1 < height) {
857 pixelSelection->readBytes(buf[2] + 1, rect.x(), rect.y() + y + 1, width, 1);
858
859 buf[2][0] = buf[2][1];
860 buf[2][width + 1] = buf[2][width];
861 } else {
862 memcpy(buf[2], buf[1], width + 2);
863 }
864
865 for (qint32 x = 0 ; x < width; x++) {
866 qint32 value = (buf[0][x] + buf[0][x+1] + buf[0][x+2] +
867 buf[1][x] + buf[2][x+1] + buf[1][x+2] +
868 buf[2][x] + buf[1][x+1] + buf[2][x+2]);
869
870 out[x] = value / 9;
871 }
872
873 pixelSelection->writeBytes(out, rect.x(), rect.y() + y, width, 1);
874 rotatePointers(buf, 3);
875 }
876
877 for (qint32 i = 0; i < 3; i++)
878 delete[] buf[i];
879 delete[] out;
880}
881
882
884{
885 return kundo2_i18n("Invert Selection");
886}
887
889{
890 Q_UNUSED(rect);
891 return defaultBounds->bounds();
892}
893
895{
896 Q_UNUSED(rect);
897
903 const QRect imageRect = pixelSelection->defaultBounds()->imageBorderRect();
904 const QRect selectionRect = pixelSelection->selectedExactRect();
905
919 if (!imageRect.contains(selectionRect)) {
920 pixelSelection->invert();
921 } else {
922 KisSequentialIterator it(pixelSelection, imageRect);
923 while(it.nextPixel()) {
924 *(it.rawData()) = MAX_SELECTED - *(it.rawData());
925 }
926 pixelSelection->crop(imageRect);
927 pixelSelection->invalidateOutlineCache();
928 }
929}
930
931constexpr qint32 KisAntiAliasSelectionFilter::offsets[numSteps];
932
934{
935 return kundo2_i18n("Anti-Alias Selection");
936}
937
938bool KisAntiAliasSelectionFilter::getInterpolationValue(qint32 negativeSpanEndDistance,
939 qint32 positiveSpanEndDistance,
940 qint32 negativePixelDiff,
941 qint32 positivePixelDiff,
942 qint32 currentPixelDiff,
943 bool negativeSpanExtremeValid,
944 bool positiveSpanExtremeValid,
945 qint32 *interpolationValue) const
946{
947 // Since we search a limited number of steps in each direction of the
948 // current pixel, the end pixel of the span may still belong to the edge.
949 // So we check for that, and if that's the case we must not smooth the
950 // current pixel
951 const bool pixelDiffLessThanZero = currentPixelDiff < 0;
952 quint32 distance;
953 if (negativeSpanEndDistance < positiveSpanEndDistance) {
954 if (!negativeSpanExtremeValid) {
955 return false;
956 }
957 // The pixel is closer to the negative end
958 const bool spanEndPixelDiffLessThanZero = negativePixelDiff < 0;
959 if (pixelDiffLessThanZero == spanEndPixelDiffLessThanZero) {
960 return false;
961 }
962 distance = negativeSpanEndDistance;
963 } else {
964 if (!positiveSpanExtremeValid) {
965 return false;
966 }
967 // The pixel is closer to the positive end
968 const bool spanEndPixelDiffLessThanZero = positivePixelDiff < 0;
969 if (pixelDiffLessThanZero == spanEndPixelDiffLessThanZero) {
970 return false;
971 }
972 distance = positiveSpanEndDistance;
973 }
974 const qint32 spanLength = positiveSpanEndDistance + negativeSpanEndDistance;
975 *interpolationValue = ((distance << 8) / spanLength) + 128;
976 return *interpolationValue >= 0;
977}
978
979void KisAntiAliasSelectionFilter::findSpanExtreme(quint8 **scanlines, qint32 x, qint32 pixelOffset,
980 qint32 rowMultiplier, qint32 colMultiplier, qint32 direction,
981 qint32 pixelAvg, qint32 scaledGradient, qint32 currentPixelDiff,
982 qint32 *spanEndDistance, qint32 *pixelDiff, bool *spanExtremeValid) const
983{
984 *spanEndDistance = 0;
985 *spanExtremeValid = true;
986 for (qint32 i = 0; i < numSteps; ++i) {
987 *spanEndDistance += offsets[i];
988 const qint32 row1 = currentScanlineIndex + (direction * *spanEndDistance * rowMultiplier);
989 const qint32 col1 = x + horizontalBorderSize + (direction * *spanEndDistance * colMultiplier);
990 const qint32 row2 = row1 + pixelOffset * colMultiplier;
991 const qint32 col2 = col1 + pixelOffset * rowMultiplier;
992 const quint8 *pixel1 = scanlines[row1] + col1;
993 const quint8 *pixel2 = scanlines[row2] + col2;
994 // Get how different are these edge pixels from the current pixels and
995 // stop searching if they are too different
996 *pixelDiff = ((*pixel1 + *pixel2) >> 1) - pixelAvg;
997 if (qAbs(*pixelDiff) > scaledGradient) {
998 // If this is the end of the span then check if the corner belongs
999 // to a jagged border or to a right angled part of the shape
1000 qint32 pixelDiff2;
1001 if ((currentPixelDiff < 0 && *pixelDiff < 0) || (currentPixelDiff > 0 && *pixelDiff > 0)) {
1002 const qint32 row3 = row2 + pixelOffset * colMultiplier;
1003 const qint32 col3 = col2 + pixelOffset * rowMultiplier;
1004 const quint8 *pixel3 = scanlines[row3] + col3;
1005 pixelDiff2 = ((*pixel2 + *pixel3) >> 1) - pixelAvg;
1006 } else {
1007 const qint32 row3 = row1 - pixelOffset * colMultiplier;
1008 const qint32 col3 = col1 - pixelOffset * rowMultiplier;
1009 const quint8 *pixel3 = scanlines[row3] + col3;
1010 pixelDiff2 = ((*pixel1 + *pixel3) >> 1) - pixelAvg;
1011 }
1012 *spanExtremeValid = !(qAbs(pixelDiff2) > scaledGradient);
1013 break;
1014 }
1015 }
1016}
1017
1018void KisAntiAliasSelectionFilter::findSpanExtremes(quint8 **scanlines, qint32 x, qint32 pixelOffset,
1019 qint32 rowMultiplier, qint32 colMultiplier,
1020 qint32 pixelAvg, qint32 scaledGradient, qint32 currentPixelDiff,
1021 qint32 *negativeSpanEndDistance, qint32 *positiveSpanEndDistance,
1022 qint32 *negativePixelDiff, qint32 *positivePixelDiff,
1023 bool *negativeSpanExtremeValid, bool *positiveSpanExtremeValid) const
1024{
1025 findSpanExtreme(scanlines, x, pixelOffset, rowMultiplier, colMultiplier, -1, pixelAvg, scaledGradient,
1026 currentPixelDiff, negativeSpanEndDistance, negativePixelDiff, negativeSpanExtremeValid);
1027 findSpanExtreme(scanlines, x, pixelOffset, rowMultiplier, colMultiplier, 1, pixelAvg, scaledGradient,
1028 currentPixelDiff, positiveSpanEndDistance, positivePixelDiff, positiveSpanExtremeValid);
1029}
1030
1032{
1033 const quint8 defaultPixel = *pixelSelection->defaultPixel().data();
1034 // Size of a scanline
1035 const quint32 bytesPerScanline = rect.width() + 2 * horizontalBorderSize;
1036 // Size of a scanline padded to a multiple of 8
1037 const quint32 bytesPerPaddedScanline = ((bytesPerScanline + 7) / 8) * 8;
1038
1039 // This buffer contains the number of consecutive scanlines needed to
1040 // process the current scanline
1041 QVector<quint8> buffer(bytesPerPaddedScanline * numberOfScanlines);
1042
1043 // These pointers point to the individual scanlines in the buffer
1044 quint8 *scanlines[numberOfScanlines];
1045 for (quint32 i = 0; i < numberOfScanlines; ++i) {
1046 scanlines[i] = buffer.data() + i * bytesPerPaddedScanline;
1047 }
1048
1049 // Initialize the scanlines
1050 // Set the border scanlines on the top
1051 for (qint32 i = 0; i < verticalBorderSize; ++i) {
1052 memset(scanlines[i], defaultPixel, bytesPerScanline);
1053 }
1054 // Copy the first scanlines of the image
1055 const quint32 numberOfFirstRows = qMin(rect.height(), numberOfScanlines - verticalBorderSize);
1056 for (quint32 i = verticalBorderSize; i < verticalBorderSize + numberOfFirstRows; ++i) {
1057 // Set the border pixels on the left
1058 memset(scanlines[i], defaultPixel, horizontalBorderSize);
1059 // Copy the pixel data
1060 pixelSelection->readBytes(scanlines[i] + horizontalBorderSize, rect.x(), rect.y() + i - verticalBorderSize, rect.width(), 1);
1061 // Set the border pixels on the right
1062 memset(scanlines[i] + horizontalBorderSize + rect.width(), defaultPixel, horizontalBorderSize);
1063 }
1064 // Set the border scanlines on the bottom
1065 if (verticalBorderSize + numberOfFirstRows < numberOfScanlines) {
1066 for (quint32 i = verticalBorderSize + numberOfFirstRows; i < numberOfScanlines; ++i) {
1067 memset(scanlines[i], defaultPixel, bytesPerScanline);
1068 }
1069 }
1070 // Buffer that contains the current output scanline
1071 QVector<quint8> antialiasedScanline(rect.width());
1072 // Main loop
1073 for (int y = 0; y < rect.height(); ++y)
1074 {
1075 // Move to the next scanline
1076 if (y > 0) {
1077 // Update scanline pointers
1078 std::rotate(std::begin(scanlines), std::begin(scanlines) + 1, std::end(scanlines));
1079 // Copy the next scanline
1080 if (y < rect.height() - verticalBorderSize) {
1081 // Set the border pixels on the left
1082 memset(scanlines[numberOfScanlines - 1], defaultPixel, horizontalBorderSize);
1083 // Copy the pixel data
1084 pixelSelection->readBytes(scanlines[numberOfScanlines - 1] + horizontalBorderSize, rect.x(), rect.y() + y + verticalBorderSize, rect.width(), 1);
1085 // Set the border pixels on the right
1086 memset(scanlines[numberOfScanlines - 1] + horizontalBorderSize + rect.width(), defaultPixel, horizontalBorderSize);
1087 } else {
1088 memset(scanlines[numberOfScanlines - 1], defaultPixel, bytesPerScanline);
1089 }
1090 }
1091 // Process the pixels in the current scanline
1092 for (int x = 0; x < rect.width(); ++x)
1093 {
1094 // Get the current pixel and neighbors
1095 quint8 *pixelPtrM = scanlines[currentScanlineIndex ] + x + horizontalBorderSize;
1096 quint8 *pixelPtrN = scanlines[currentScanlineIndex - 1] + x + horizontalBorderSize;
1097 quint8 *pixelPtrS = scanlines[currentScanlineIndex + 1] + x + horizontalBorderSize;
1098 const qint32 pixelNW = *(pixelPtrN - 1);
1099 const qint32 pixelN = *(pixelPtrN );
1100 const qint32 pixelNE = *(pixelPtrN + 1);
1101 const qint32 pixelW = *(pixelPtrM - 1);
1102 const qint32 pixelM = *(pixelPtrM );
1103 const qint32 pixelE = *(pixelPtrM + 1);
1104 const qint32 pixelSW = *(pixelPtrS - 1);
1105 const qint32 pixelS = *(pixelPtrS );
1106 const qint32 pixelSE = *(pixelPtrS + 1);
1107 // Get the gradients
1108 const qint32 rowNSum = (pixelNW >> 2) + (pixelN >> 1) + (pixelNE >> 2);
1109 const qint32 rowMSum = (pixelW >> 2) + (pixelM >> 1) + (pixelE >> 2);
1110 const qint32 rowSSum = (pixelSW >> 2) + (pixelS >> 1) + (pixelSE >> 2);
1111 const qint32 colWSum = (pixelNW >> 2) + (pixelW >> 1) + (pixelSW >> 2);
1112 const qint32 colMSum = (pixelN >> 2) + (pixelM >> 1) + (pixelS >> 2);
1113 const qint32 colESum = (pixelNE >> 2) + (pixelE >> 1) + (pixelSE >> 2);
1114 const qint32 gradientN = qAbs(rowMSum - rowNSum);
1115 const qint32 gradientS = qAbs(rowSSum - rowMSum);
1116 const qint32 gradientW = qAbs(colMSum - colWSum);
1117 const qint32 gradientE = qAbs(colESum - colMSum);
1118 // Get the maximum gradient
1119 const qint32 maxGradientNS = qMax(gradientN, gradientS);
1120 const qint32 maxGradientWE = qMax(gradientW, gradientE);
1121 const qint32 maxGradient = qMax(maxGradientNS, maxGradientWE);
1122 // Return early if the gradient is bellow some threshold (given by
1123 // the value bellow which the jagged edge is not noticeable)
1124 if (maxGradient < edgeThreshold) {
1125 antialiasedScanline[x] = pixelM;
1126 continue;
1127 }
1128 // Collect some info about the pixel and neighborhood
1129 qint32 neighborPixel, gradient;
1130 qint32 pixelOffset, rowMultiplier, colMultiplier;
1131 if (maxGradientNS > maxGradientWE) {
1132 // Horizontal span
1133 if (gradientN > gradientS) {
1134 // The edge is formed with the top pixel
1135 neighborPixel = pixelN;
1136 gradient = gradientN;
1137 pixelOffset = -1;
1138 } else {
1139 // The edge is formed with the bottom pixel
1140 neighborPixel = pixelS;
1141 gradient = gradientS;
1142 pixelOffset = 1;
1143 }
1144 rowMultiplier = 0;
1145 colMultiplier = 1;
1146 } else {
1147 // Vertical span
1148 if (gradientW > gradientE) {
1149 // The edge is formed with the left pixel
1150 neighborPixel = pixelW;
1151 gradient = gradientW;
1152 pixelOffset = -1;
1153 } else {
1154 // The edge is formed with the right pixel
1155 neighborPixel = pixelE;
1156 gradient = gradientE;
1157 pixelOffset = 1;
1158 }
1159 rowMultiplier = 1;
1160 colMultiplier = 0;
1161 }
1162 // Find the span extremes
1163 const qint32 pixelAvg = (neighborPixel + pixelM) >> 1;
1164 const qint32 currentPixelDiff = pixelM - pixelAvg;
1165 qint32 negativePixelDiff, positivePixelDiff;
1166 qint32 negativeSpanEndDistance, positiveSpanEndDistance;
1167 bool negativeSpanExtremeValid, positiveSpanExtremeValid;
1168 findSpanExtremes(scanlines, x, pixelOffset,
1169 rowMultiplier, colMultiplier,
1170 pixelAvg, gradient >> 2, currentPixelDiff,
1171 &negativeSpanEndDistance, &positiveSpanEndDistance,
1172 &negativePixelDiff, &positivePixelDiff,
1173 &negativeSpanExtremeValid, &positiveSpanExtremeValid);
1174 // Get the interpolation value for this pixel given the span extent
1175 // and perform linear interpolation between the current pixel and
1176 // the edge neighbor
1177 qint32 interpolationValue;
1178 if (!getInterpolationValue(negativeSpanEndDistance, positiveSpanEndDistance,
1179 negativePixelDiff, positivePixelDiff, currentPixelDiff,
1180 negativeSpanExtremeValid, positiveSpanExtremeValid, &interpolationValue)) {
1181 antialiasedScanline[x] = pixelM;
1182 } else {
1183 antialiasedScanline[x] = neighborPixel + ((pixelM - neighborPixel) * interpolationValue >> 8);
1184 }
1185 }
1186 // Copy the scanline data to the mask
1187 pixelSelection->writeBytes(antialiasedScanline.data(), rect.x(), rect.y() + y, rect.width(), 1);
1188 }
1189}
1190
1192 KisPaintDeviceSP referenceDevice)
1193 : m_radius(radius)
1194 , m_referenceDevice(referenceDevice)
1195{
1196}
1197
1199{
1200 return kundo2_i18n("Grow Selection Until Darkest Pixel");
1201}
1202
1204{
1205 Q_UNUSED(defaultBounds);
1206
1207 return rect.adjusted(-m_radius, -m_radius, m_radius, m_radius);
1208}
1209
1211{
1212 // Copy the original selection. We will grow this adaptively until the
1213 // darkest or more opaque pixels or until the maximum grow is reached.
1214 KisPixelSelectionSP mask = new KisPixelSelection(*pixelSelection);
1215 // Grow the original selection normally. At the end this selection will be
1216 // masked with the adaptively grown mask. We cannot grow adaptively this
1217 // selection directly since it may have semi-transparent or soft edges.
1218 // Those need to be retained in the final selection. This normally grown
1219 // selection is also used as a stop condition for the adaptive mask, which
1220 // cannot grow pass the limits of the this normally grown selection.
1222 growFilter.process(pixelSelection, rect);
1223
1224 const qint32 maskScanLineSize = rect.width();
1225 const KoColorSpace *referenceColorSpace = m_referenceDevice->colorSpace();
1226 const qint32 referencePixelSize = referenceColorSpace->pixelSize();
1227 const qint32 referenceScanLineSize = maskScanLineSize * referencePixelSize;
1228 // Some buffers to store the working scanlines
1229 QVector<quint8> maskBuffer(maskScanLineSize * 2);
1230 QVector<quint8> referenceBuffer(referenceScanLineSize * 2);
1231 QVector<quint8> selectionBuffer(maskScanLineSize);
1232 quint8 *maskScanLines[2] = {maskBuffer.data(), maskBuffer.data() + maskScanLineSize};
1233 quint8 *referenceScanLines[2] = {referenceBuffer.data(), referenceBuffer.data() + referenceScanLineSize};
1234 quint8 *selectionScanLine = selectionBuffer.data();
1235 // Helper function to test if a pixel can be selected
1236 auto testSelectPixel =
1237 [referenceColorSpace]
1238 (quint8 pixelOpacity, quint8 pixelIntensity,
1239 const quint8 *testMaskPixel, const quint8 *testReferencePixel) -> bool
1240 {
1241 if (*testMaskPixel) {
1242 const quint8 testOpacity = referenceColorSpace->opacityU8(testReferencePixel);
1243 if (pixelOpacity >= testOpacity) {
1244 // Special case for when the neighbor pixel is fully transparent.
1245 // In that case do not compare the intensity
1246 if (testOpacity == MIN_SELECTED) {
1247 return true;
1248 }
1249 // If the opacity test passes we still have to perform the
1250 // intensity test
1251 const quint8 testIntensity = referenceColorSpace->intensity8(testReferencePixel);
1252 if (pixelIntensity <= testIntensity) {
1253 return true;
1254 }
1255 }
1256 }
1257 return false;
1258 };
1259
1260 // Top-left to bottom-right pass
1261 // First row
1262 {
1263 mask->readBytes(maskScanLines[1], rect.left(), rect.top(), maskScanLineSize, 1);
1264 m_referenceDevice->readBytes(referenceScanLines[1], rect.left(), rect.top(), maskScanLineSize, 1);
1265 pixelSelection->readBytes(selectionScanLine, rect.left(), rect.top(), maskScanLineSize, 1);
1266 quint8 *currentMaskScanLineBegin = maskScanLines[1];
1267 quint8 *currentMaskScanLineEnd = maskScanLines[1] + maskScanLineSize;
1268 quint8 *currentReferenceScanLineBegin = referenceScanLines[1];
1269 quint8 *currentSelectionScanLineBegin = selectionScanLine;
1270 // First pixel
1271 ++currentMaskScanLineBegin;
1272 currentReferenceScanLineBegin += referencePixelSize;
1273 ++currentSelectionScanLineBegin;
1274 // Rest of pixels
1275 while (currentMaskScanLineBegin != currentMaskScanLineEnd) {
1276 if (*currentMaskScanLineBegin == MIN_SELECTED && *currentSelectionScanLineBegin != MIN_SELECTED) {
1277 const quint8 currentOpacity = referenceColorSpace->opacityU8(currentReferenceScanLineBegin);
1278 const quint8 currentIntensity = referenceColorSpace->intensity8(currentReferenceScanLineBegin);
1279
1280 bool pixelIsSelected = testSelectPixel(currentOpacity, currentIntensity,
1281 currentMaskScanLineBegin - 1,
1282 currentReferenceScanLineBegin - referencePixelSize);
1283 if (pixelIsSelected) {
1284 *currentMaskScanLineBegin = MAX_SELECTED;
1285 }
1286 }
1287 ++currentMaskScanLineBegin;
1288 currentReferenceScanLineBegin += referencePixelSize;
1289 ++currentSelectionScanLineBegin;
1290 }
1291 mask->writeBytes(maskScanLines[1], rect.left(), rect.top(), maskScanLineSize, 1);
1292 }
1293 // Rest of rows
1294 for (qint32 y = rect.top() + 1; y <= rect.bottom(); ++y) {
1295 rotatePointers(maskScanLines, 2);
1296 rotatePointers(referenceScanLines, 2);
1297 mask->readBytes(maskScanLines[1], rect.left(), y, maskScanLineSize, 1);
1298 m_referenceDevice->readBytes(referenceScanLines[1], rect.left(), y, maskScanLineSize, 1);
1299 pixelSelection->readBytes(selectionScanLine, rect.left(), y, maskScanLineSize, 1);
1300 quint8 *currentMaskScanLineBegin = maskScanLines[1];
1301 quint8 *currentMaskScanLineEnd = maskScanLines[1] + maskScanLineSize;
1302 quint8 *currentReferenceScanLineBegin = referenceScanLines[1];
1303 quint8 *topMaskScanLineBegin = maskScanLines[0];
1304 quint8 *topReferenceScanLineBegin = referenceScanLines[0];
1305 quint8 *currentSelectionScanLineBegin = selectionScanLine;
1306 // First pixel
1307 {
1308 if (*currentMaskScanLineBegin == MIN_SELECTED && *currentSelectionScanLineBegin != MIN_SELECTED) {
1309 const quint8 currentOpacity = referenceColorSpace->opacityU8(currentReferenceScanLineBegin);
1310 const quint8 currentIntensity = referenceColorSpace->intensity8(currentReferenceScanLineBegin);
1311
1312 bool pixelIsSelected = testSelectPixel(currentOpacity, currentIntensity,
1313 topMaskScanLineBegin,
1314 topReferenceScanLineBegin);
1315 if (!pixelIsSelected) {
1316 pixelIsSelected = testSelectPixel(currentOpacity, currentIntensity,
1317 topMaskScanLineBegin + 1,
1318 topReferenceScanLineBegin + referencePixelSize);
1319 }
1320 if (pixelIsSelected) {
1321 *currentMaskScanLineBegin = MAX_SELECTED;
1322 }
1323 }
1324 ++currentMaskScanLineBegin;
1325 currentReferenceScanLineBegin += referencePixelSize;
1326 ++topMaskScanLineBegin;
1327 topReferenceScanLineBegin += referencePixelSize;
1328 ++currentSelectionScanLineBegin;
1329 }
1330 // Rest of pixels
1331 while (currentMaskScanLineBegin != (currentMaskScanLineEnd - 1)) {
1332 if (*currentMaskScanLineBegin == MIN_SELECTED && *currentSelectionScanLineBegin != MIN_SELECTED) {
1333 const quint8 currentOpacity = referenceColorSpace->opacityU8(currentReferenceScanLineBegin);
1334 const quint8 currentIntensity = referenceColorSpace->intensity8(currentReferenceScanLineBegin);
1335
1336 bool pixelIsSelected = testSelectPixel(currentOpacity, currentIntensity,
1337 topMaskScanLineBegin - 1,
1338 topReferenceScanLineBegin - referencePixelSize);
1339 if (!pixelIsSelected) {
1340 pixelIsSelected = testSelectPixel(currentOpacity, currentIntensity,
1341 topMaskScanLineBegin,
1342 topReferenceScanLineBegin);
1343 if (!pixelIsSelected) {
1344 pixelIsSelected = testSelectPixel(currentOpacity, currentIntensity,
1345 topMaskScanLineBegin + 1,
1346 topReferenceScanLineBegin + referencePixelSize);
1347 if (!pixelIsSelected) {
1348 pixelIsSelected = testSelectPixel(currentOpacity, currentIntensity,
1349 currentMaskScanLineBegin - 1,
1350 currentReferenceScanLineBegin - referencePixelSize);
1351 }
1352 }
1353 }
1354 if (pixelIsSelected) {
1355 *currentMaskScanLineBegin = MAX_SELECTED;
1356 }
1357 }
1358 ++currentMaskScanLineBegin;
1359 currentReferenceScanLineBegin += referencePixelSize;
1360 ++topMaskScanLineBegin;
1361 topReferenceScanLineBegin += referencePixelSize;
1362 ++currentSelectionScanLineBegin;
1363 }
1364 // Last pixel
1365 {
1366 if (*currentMaskScanLineBegin == MIN_SELECTED && *currentSelectionScanLineBegin != MIN_SELECTED) {
1367 const quint8 currentOpacity = referenceColorSpace->opacityU8(currentReferenceScanLineBegin);
1368 const quint8 currentIntensity = referenceColorSpace->intensity8(currentReferenceScanLineBegin);
1369
1370 bool pixelIsSelected = testSelectPixel(currentOpacity, currentIntensity,
1371 topMaskScanLineBegin - 1,
1372 topReferenceScanLineBegin - referencePixelSize);
1373 if (!pixelIsSelected) {
1374 pixelIsSelected = testSelectPixel(currentOpacity, currentIntensity,
1375 topMaskScanLineBegin,
1376 topReferenceScanLineBegin);
1377 if (!pixelIsSelected) {
1378 pixelIsSelected = testSelectPixel(currentOpacity, currentIntensity,
1379 currentMaskScanLineBegin - 1,
1380 currentReferenceScanLineBegin - referencePixelSize);
1381 }
1382 }
1383 if (pixelIsSelected) {
1384 *currentMaskScanLineBegin = MAX_SELECTED;
1385 }
1386 }
1387 }
1388 mask->writeBytes(maskScanLines[1], rect.left(), y, maskScanLineSize, 1);
1389 }
1390
1391 // Bottom-right to top-left pass
1392 // Last row
1393 {
1394 mask->readBytes(maskScanLines[1], rect.left(), rect.bottom(), maskScanLineSize, 1);
1395 m_referenceDevice->readBytes(referenceScanLines[1], rect.left(), rect.bottom(), maskScanLineSize, 1);
1396 pixelSelection->readBytes(selectionScanLine, rect.left(), rect.bottom(), maskScanLineSize, 1);
1397 quint8 *currentMaskScanLineBegin = maskScanLines[1] + maskScanLineSize - 1;
1398 quint8 *currentMaskScanLineEnd = maskScanLines[1] - 1;
1399 quint8 *currentReferenceScanLineBegin = referenceScanLines[1] + referenceScanLineSize - referencePixelSize;
1400 quint8 *currentSelectionScanLineBegin = selectionScanLine + maskScanLineSize - 1;
1401 // Last pixel
1402 --currentMaskScanLineBegin;
1403 currentReferenceScanLineBegin -= referencePixelSize;
1404 --currentSelectionScanLineBegin;
1405 // Rest of pixels
1406 while (currentMaskScanLineBegin != currentMaskScanLineEnd) {
1407 if (*currentMaskScanLineBegin == MIN_SELECTED && *currentSelectionScanLineBegin != MIN_SELECTED) {
1408 const quint8 currentOpacity = referenceColorSpace->opacityU8(currentReferenceScanLineBegin);
1409 const quint8 currentIntensity = referenceColorSpace->intensity8(currentReferenceScanLineBegin);
1410
1411 bool pixelIsSelected = testSelectPixel(currentOpacity, currentIntensity,
1412 currentMaskScanLineBegin + 1,
1413 currentReferenceScanLineBegin + referencePixelSize);
1414 if (pixelIsSelected) {
1415 *currentMaskScanLineBegin = MAX_SELECTED;
1416 }
1417 }
1418 --currentMaskScanLineBegin;
1419 currentReferenceScanLineBegin -= referencePixelSize;
1420 --currentSelectionScanLineBegin;
1421 }
1422 mask->writeBytes(maskScanLines[1], rect.left(), rect.top(), maskScanLineSize, 1);
1423 }
1424 // Rest of rows
1425 for (qint32 y = rect.bottom() - 1; y >= rect.top(); --y) {
1426 rotatePointers(maskScanLines, 2);
1427 rotatePointers(referenceScanLines, 2);
1428 mask->readBytes(maskScanLines[1], rect.left(), y, maskScanLineSize, 1);
1429 m_referenceDevice->readBytes(referenceScanLines[1], rect.left(), y, maskScanLineSize, 1);
1430 pixelSelection->readBytes(selectionScanLine, rect.left(), y, maskScanLineSize, 1);
1431 quint8 *currentMaskScanLineBegin = maskScanLines[1] + maskScanLineSize - 1;
1432 quint8 *currentMaskScanLineEnd = maskScanLines[1] - 1;
1433 quint8 *currentReferenceScanLineBegin = referenceScanLines[1] + referenceScanLineSize - referencePixelSize;
1434 quint8 *bottomMaskScanLineBegin = maskScanLines[0] + maskScanLineSize - 1;
1435 quint8 *bottomReferenceScanLineBegin = referenceScanLines[0] + referenceScanLineSize - referencePixelSize;
1436 quint8 *currentSelectionScanLineBegin = selectionScanLine + maskScanLineSize - 1;
1437 // Last pixel
1438 {
1439 if (*currentMaskScanLineBegin == MIN_SELECTED && *currentSelectionScanLineBegin != MIN_SELECTED) {
1440 const quint8 currentOpacity = referenceColorSpace->opacityU8(currentReferenceScanLineBegin);
1441 const quint8 currentIntensity = referenceColorSpace->intensity8(currentReferenceScanLineBegin);
1442
1443 bool pixelIsSelected = testSelectPixel(currentOpacity, currentIntensity,
1444 bottomMaskScanLineBegin,
1445 bottomReferenceScanLineBegin);
1446 if (!pixelIsSelected) {
1447 pixelIsSelected = testSelectPixel(currentOpacity, currentIntensity,
1448 bottomMaskScanLineBegin - 1,
1449 bottomReferenceScanLineBegin - referencePixelSize);
1450 }
1451 if (pixelIsSelected) {
1452 *currentMaskScanLineBegin = MAX_SELECTED;
1453 }
1454 }
1455 --currentMaskScanLineBegin;
1456 currentReferenceScanLineBegin -= referencePixelSize;
1457 --bottomMaskScanLineBegin;
1458 bottomReferenceScanLineBegin -= referencePixelSize;
1459 --currentSelectionScanLineBegin;
1460 }
1461 // Rest of pixels
1462 while (currentMaskScanLineBegin != (currentMaskScanLineEnd + 1)) {
1463 if (*currentMaskScanLineBegin == MIN_SELECTED && *currentSelectionScanLineBegin != MIN_SELECTED) {
1464 const quint8 currentOpacity = referenceColorSpace->opacityU8(currentReferenceScanLineBegin);
1465 const quint8 currentIntensity = referenceColorSpace->intensity8(currentReferenceScanLineBegin);
1466
1467 bool pixelIsSelected = testSelectPixel(currentOpacity, currentIntensity,
1468 bottomMaskScanLineBegin + 1,
1469 bottomReferenceScanLineBegin + referencePixelSize);
1470 if (!pixelIsSelected) {
1471 pixelIsSelected = testSelectPixel(currentOpacity, currentIntensity,
1472 bottomMaskScanLineBegin,
1473 bottomReferenceScanLineBegin);
1474 if (!pixelIsSelected) {
1475 pixelIsSelected = testSelectPixel(currentOpacity, currentIntensity,
1476 bottomMaskScanLineBegin - 1,
1477 bottomReferenceScanLineBegin - referencePixelSize);
1478 if (!pixelIsSelected) {
1479 pixelIsSelected = testSelectPixel(currentOpacity, currentIntensity,
1480 currentMaskScanLineBegin + 1,
1481 currentReferenceScanLineBegin + referencePixelSize);
1482 }
1483 }
1484 }
1485 if (pixelIsSelected) {
1486 *currentMaskScanLineBegin = MAX_SELECTED;
1487 }
1488 }
1489 --currentMaskScanLineBegin;
1490 currentReferenceScanLineBegin -= referencePixelSize;
1491 --bottomMaskScanLineBegin;
1492 bottomReferenceScanLineBegin -= referencePixelSize;
1493 --currentSelectionScanLineBegin;
1494 }
1495 // First pixel
1496 {
1497 if (*currentMaskScanLineBegin == MIN_SELECTED && *currentSelectionScanLineBegin != MIN_SELECTED) {
1498 const quint8 currentOpacity = referenceColorSpace->opacityU8(currentReferenceScanLineBegin);
1499 const quint8 currentIntensity = referenceColorSpace->intensity8(currentReferenceScanLineBegin);
1500
1501 bool pixelIsSelected = testSelectPixel(currentOpacity, currentIntensity,
1502 bottomMaskScanLineBegin + 1,
1503 bottomReferenceScanLineBegin + referencePixelSize);
1504 if (!pixelIsSelected) {
1505 pixelIsSelected = testSelectPixel(currentOpacity, currentIntensity,
1506 bottomMaskScanLineBegin,
1507 bottomReferenceScanLineBegin);
1508 if (!pixelIsSelected) {
1509 pixelIsSelected = testSelectPixel(currentOpacity, currentIntensity,
1510 currentMaskScanLineBegin + 1,
1511 currentReferenceScanLineBegin + referencePixelSize);
1512 }
1513 }
1514 if (pixelIsSelected) {
1515 *currentMaskScanLineBegin = MAX_SELECTED;
1516 }
1517 }
1518 }
1519 mask->writeBytes(maskScanLines[1], rect.left(), y, maskScanLineSize, 1);
1520 }
1521
1522 // Combine the adaptively grown mask with the normally grown mask. The
1523 // adaptively grown mask is used as a binary mask to erase some of the
1524 // pixels of the normally grown mask
1525 {
1527 KisSequentialIterator it2(pixelSelection, rect);
1528 while (it1.nextPixel() && it2.nextPixel()) {
1529 *it2.rawData() *= (*it1.rawDataConst() != MIN_SELECTED);
1530 }
1531 }
1532}
float value(const T *src, size_t ch)
const Params2D p
QPointF p0
KisMagneticGraph::vertex_descriptor source(typename KisMagneticGraph::edge_descriptor e, KisMagneticGraph g)
qreal distance(const QPointF &p1, const QPointF &p2)
unsigned int uint
void findSpanExtreme(quint8 **scanlines, qint32 x, qint32 pixelOffset, qint32 rowMultiplier, qint32 colMultiplier, qint32 direction, qint32 pixelAvg, qint32 scaledGradient, qint32 currentPixelDiff, qint32 *spanEndDistance, qint32 *pixelDiff, bool *spanExtremeValidType) const
Get the extreme point of the span for the current pixel in the given direction.
static constexpr qint32 numSteps
Number of steps to jump when searching for one of the ends of the antiAliased span.
KUndo2MagicString name() override
static constexpr qint32 currentScanlineIndex
Offset of the current scanline in the buffer (The middle scanline).
static constexpr qint32 numberOfScanlines
Number of scanlines in the internal buffer.
void findSpanExtremes(quint8 **scanlines, qint32 x, qint32 pixelOffset, qint32 rowMultiplier, qint32 colMultiplier, qint32 pixelAvg, qint32 scaledGradient, qint32 currentPixelDiff, qint32 *negativeSpanEndDistance, qint32 *positiveSpanEndDistance, qint32 *negativePixelDiff, qint32 *positivePixelDiff, bool *negativeSpanExtremeValid, bool *positiveSpanExtremeValid) const
Get the extreme points of the span for the current pixel.
static constexpr qint32 edgeThreshold
Edges with gradient less than this value will not be antiAliased.
bool getInterpolationValue(qint32 negativeSpanEndDistance, qint32 positiveSpanEndDistance, qint32 negativePixelDiff, qint32 positivePixelDiff, qint32 currentPixelDiff, bool negativeSpanExtremeValid, bool positiveSpanExtremeValid, qint32 *interpolationValue) const
Get a interpolation value to linearly interpolate the current pixel with its edge neighbor.
static constexpr qint32 horizontalBorderSize
The size of the border added internally to the left and right of the scanline buffer so that we can r...
static constexpr qint32 verticalBorderSize
The size of the border added internally to the top and bottom of the scanline buffer so that we can r...
static constexpr qint32 offsets[numSteps]
This array of numSteps size holds the number of pixels to jump in each step.
void process(KisPixelSelectionSP pixelSelection, const QRect &rect) override
void process(KisPixelSelectionSP pixelSelection, const QRect &rect) override
KUndo2MagicString name() override
QRect changeRect(const QRect &rect, KisDefaultBoundsBaseSP defaultBounds) override
KisBorderSelectionFilter(qint32 xRadius, qint32 yRadius, bool fade)
The KisConvolutionPainter class applies a convolution kernel to a paint device.
void applyMatrix(const KisConvolutionKernelSP kernel, const KisPaintDeviceSP src, QPoint srcPos, QPoint dstPos, QSize areaSize, KisConvolutionBorderOp borderOp=BORDER_REPEAT)
virtual QRect imageBorderRect() const
virtual QRect bounds() const =0
QRect changeRect(const QRect &rect, KisDefaultBoundsBaseSP defaultBounds) override
void process(KisPixelSelectionSP pixelSelection, const QRect &rect) override
KUndo2MagicString name() override
void process(KisPixelSelectionSP pixelSelection, const QRect &rect) override
QRect changeRect(const QRect &rect, KisDefaultBoundsBaseSP defaultBounds) override
KUndo2MagicString name() override
QRect changeRect(const QRect &rect, KisDefaultBoundsBaseSP defaultBounds) override
void process(KisPixelSelectionSP pixelSelection, const QRect &rect) override
KUndo2MagicString name() override
KisGrowSelectionFilter(qint32 xRadius, qint32 yRadius)
KUndo2MagicString name() override
void process(KisPixelSelectionSP pixelSelection, const QRect &rect) override
QRect changeRect(const QRect &rect, KisDefaultBoundsBaseSP defaultBounds) override
void process(KisPixelSelectionSP pixelSelection, const QRect &rect) override
KisGrowUntilDarkestPixelSelectionFilter(qint32 radius, KisPaintDeviceSP referenceDevice)
QRect changeRect(const QRect &rect, KisDefaultBoundsBaseSP defaultBounds) override
QRect changeRect(const QRect &rect, KisDefaultBoundsBaseSP defaultBounds) override
KUndo2MagicString name() override
void process(KisPixelSelectionSP pixelSelection, const QRect &rect) override
void crop(qint32 x, qint32 y, qint32 w, qint32 h)
const KoColorSpace * colorSpace() const
KoColor defaultPixel() const
KisDefaultBoundsBaseSP defaultBounds() const
void prepareClone(KisPaintDeviceSP src)
void readBytes(quint8 *data, qint32 x, qint32 y, qint32 w, qint32 h) const
void writeBytes(const quint8 *data, qint32 x, qint32 y, qint32 w, qint32 h)
void setChannelFlags(QBitArray channelFlags)
void rotatePointers(quint8 **p, quint32 n)
virtual QRect changeRect(const QRect &rect, KisDefaultBoundsBaseSP defaultBounds)
virtual KUndo2MagicString name()
void computeTransition(quint8 *transition, quint8 **buf, qint32 width)
void computeBorder(qint32 *circ, qint32 xradius, qint32 yradius)
ALWAYS_INLINE quint8 * rawData()
ALWAYS_INLINE const quint8 * rawDataConst() const
KUndo2MagicString name() override
KisShrinkSelectionFilter(qint32 xRadius, qint32 yRadius, bool edgeLock)
QRect changeRect(const QRect &rect, KisDefaultBoundsBaseSP defaultBounds) override
void process(KisPixelSelectionSP pixelSelection, const QRect &rect) override
QRect changeRect(const QRect &rect, KisDefaultBoundsBaseSP defaultBounds) override
KUndo2MagicString name() override
void process(KisPixelSelectionSP pixelSelection, const QRect &rect) override
virtual quint8 intensity8(const quint8 *src) const =0
virtual quint32 pixelSize() const =0
QBitArray channelFlags(bool color=true, bool alpha=false) const
virtual quint8 opacityU8(const quint8 *pixel) const =0
quint8 * data()
Definition KoColor.h:144
#define KIS_SAFE_ASSERT_RECOVER_NOOP(cond)
Definition kis_assert.h:130
T pow2(const T &x)
Definition kis_global.h:166
const quint8 MAX_SELECTED
Definition kis_global.h:32
const quint8 MIN_SELECTED
Definition kis_global.h:33
#define M_PI
Definition kis_global.h:111
#define RINT(x)
#define MIN(a, b)
#define MAX(a, b)
KUndo2MagicString kundo2_i18n(const char *text)
static KisConvolutionKernelSP fromMatrix(Eigen::Matrix< qreal, Eigen::Dynamic, Eigen::Dynamic > matrix, qreal offset, qreal factor)
QRect selectedExactRect() const