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
898 const QRect imageRect = pixelSelection->defaultBounds()->bounds();
899 const QRect selectionRect = pixelSelection->selectedExactRect();
900
914 if (!imageRect.contains(selectionRect)) {
915 pixelSelection->invert();
916 } else {
917 KisSequentialIterator it(pixelSelection, imageRect);
918 while(it.nextPixel()) {
919 *(it.rawData()) = MAX_SELECTED - *(it.rawData());
920 }
921 pixelSelection->crop(imageRect);
922 pixelSelection->invalidateOutlineCache();
923 }
924}
925
926constexpr qint32 KisAntiAliasSelectionFilter::offsets[numSteps];
927
929{
930 return kundo2_i18n("Anti-Alias Selection");
931}
932
933bool KisAntiAliasSelectionFilter::getInterpolationValue(qint32 negativeSpanEndDistance,
934 qint32 positiveSpanEndDistance,
935 qint32 negativePixelDiff,
936 qint32 positivePixelDiff,
937 qint32 currentPixelDiff,
938 bool negativeSpanExtremeValid,
939 bool positiveSpanExtremeValid,
940 qint32 *interpolationValue) const
941{
942 // Since we search a limited number of steps in each direction of the
943 // current pixel, the end pixel of the span may still belong to the edge.
944 // So we check for that, and if that's the case we must not smooth the
945 // current pixel
946 const bool pixelDiffLessThanZero = currentPixelDiff < 0;
947 quint32 distance;
948 if (negativeSpanEndDistance < positiveSpanEndDistance) {
949 if (!negativeSpanExtremeValid) {
950 return false;
951 }
952 // The pixel is closer to the negative end
953 const bool spanEndPixelDiffLessThanZero = negativePixelDiff < 0;
954 if (pixelDiffLessThanZero == spanEndPixelDiffLessThanZero) {
955 return false;
956 }
957 distance = negativeSpanEndDistance;
958 } else {
959 if (!positiveSpanExtremeValid) {
960 return false;
961 }
962 // The pixel is closer to the positive end
963 const bool spanEndPixelDiffLessThanZero = positivePixelDiff < 0;
964 if (pixelDiffLessThanZero == spanEndPixelDiffLessThanZero) {
965 return false;
966 }
967 distance = positiveSpanEndDistance;
968 }
969 const qint32 spanLength = positiveSpanEndDistance + negativeSpanEndDistance;
970 *interpolationValue = ((distance << 8) / spanLength) + 128;
971 return *interpolationValue >= 0;
972}
973
974void KisAntiAliasSelectionFilter::findSpanExtreme(quint8 **scanlines, qint32 x, qint32 pixelOffset,
975 qint32 rowMultiplier, qint32 colMultiplier, qint32 direction,
976 qint32 pixelAvg, qint32 scaledGradient, qint32 currentPixelDiff,
977 qint32 *spanEndDistance, qint32 *pixelDiff, bool *spanExtremeValid) const
978{
979 *spanEndDistance = 0;
980 *spanExtremeValid = true;
981 for (qint32 i = 0; i < numSteps; ++i) {
982 *spanEndDistance += offsets[i];
983 const qint32 row1 = currentScanlineIndex + (direction * *spanEndDistance * rowMultiplier);
984 const qint32 col1 = x + horizontalBorderSize + (direction * *spanEndDistance * colMultiplier);
985 const qint32 row2 = row1 + pixelOffset * colMultiplier;
986 const qint32 col2 = col1 + pixelOffset * rowMultiplier;
987 const quint8 *pixel1 = scanlines[row1] + col1;
988 const quint8 *pixel2 = scanlines[row2] + col2;
989 // Get how different are these edge pixels from the current pixels and
990 // stop searching if they are too different
991 *pixelDiff = ((*pixel1 + *pixel2) >> 1) - pixelAvg;
992 if (qAbs(*pixelDiff) > scaledGradient) {
993 // If this is the end of the span then check if the corner belongs
994 // to a jagged border or to a right angled part of the shape
995 qint32 pixelDiff2;
996 if ((currentPixelDiff < 0 && *pixelDiff < 0) || (currentPixelDiff > 0 && *pixelDiff > 0)) {
997 const qint32 row3 = row2 + pixelOffset * colMultiplier;
998 const qint32 col3 = col2 + pixelOffset * rowMultiplier;
999 const quint8 *pixel3 = scanlines[row3] + col3;
1000 pixelDiff2 = ((*pixel2 + *pixel3) >> 1) - pixelAvg;
1001 } else {
1002 const qint32 row3 = row1 - pixelOffset * colMultiplier;
1003 const qint32 col3 = col1 - pixelOffset * rowMultiplier;
1004 const quint8 *pixel3 = scanlines[row3] + col3;
1005 pixelDiff2 = ((*pixel1 + *pixel3) >> 1) - pixelAvg;
1006 }
1007 *spanExtremeValid = !(qAbs(pixelDiff2) > scaledGradient);
1008 break;
1009 }
1010 }
1011}
1012
1013void KisAntiAliasSelectionFilter::findSpanExtremes(quint8 **scanlines, qint32 x, qint32 pixelOffset,
1014 qint32 rowMultiplier, qint32 colMultiplier,
1015 qint32 pixelAvg, qint32 scaledGradient, qint32 currentPixelDiff,
1016 qint32 *negativeSpanEndDistance, qint32 *positiveSpanEndDistance,
1017 qint32 *negativePixelDiff, qint32 *positivePixelDiff,
1018 bool *negativeSpanExtremeValid, bool *positiveSpanExtremeValid) const
1019{
1020 findSpanExtreme(scanlines, x, pixelOffset, rowMultiplier, colMultiplier, -1, pixelAvg, scaledGradient,
1021 currentPixelDiff, negativeSpanEndDistance, negativePixelDiff, negativeSpanExtremeValid);
1022 findSpanExtreme(scanlines, x, pixelOffset, rowMultiplier, colMultiplier, 1, pixelAvg, scaledGradient,
1023 currentPixelDiff, positiveSpanEndDistance, positivePixelDiff, positiveSpanExtremeValid);
1024}
1025
1027{
1028 const quint8 defaultPixel = *pixelSelection->defaultPixel().data();
1029 // Size of a scanline
1030 const quint32 bytesPerScanline = rect.width() + 2 * horizontalBorderSize;
1031 // Size of a scanline padded to a multiple of 8
1032 const quint32 bytesPerPaddedScanline = ((bytesPerScanline + 7) / 8) * 8;
1033
1034 // This buffer contains the number of consecutive scanlines needed to
1035 // process the current scanline
1036 QVector<quint8> buffer(bytesPerPaddedScanline * numberOfScanlines);
1037
1038 // These pointers point to the individual scanlines in the buffer
1039 quint8 *scanlines[numberOfScanlines];
1040 for (quint32 i = 0; i < numberOfScanlines; ++i) {
1041 scanlines[i] = buffer.data() + i * bytesPerPaddedScanline;
1042 }
1043
1044 // Initialize the scanlines
1045 // Set the border scanlines on the top
1046 for (qint32 i = 0; i < verticalBorderSize; ++i) {
1047 memset(scanlines[i], defaultPixel, bytesPerScanline);
1048 }
1049 // Copy the first scanlines of the image
1050 const quint32 numberOfFirstRows = qMin(rect.height(), numberOfScanlines - verticalBorderSize);
1051 for (quint32 i = verticalBorderSize; i < verticalBorderSize + numberOfFirstRows; ++i) {
1052 // Set the border pixels on the left
1053 memset(scanlines[i], defaultPixel, horizontalBorderSize);
1054 // Copy the pixel data
1055 pixelSelection->readBytes(scanlines[i] + horizontalBorderSize, rect.x(), rect.y() + i - verticalBorderSize, rect.width(), 1);
1056 // Set the border pixels on the right
1057 memset(scanlines[i] + horizontalBorderSize + rect.width(), defaultPixel, horizontalBorderSize);
1058 }
1059 // Set the border scanlines on the bottom
1060 if (verticalBorderSize + numberOfFirstRows < numberOfScanlines) {
1061 for (quint32 i = verticalBorderSize + numberOfFirstRows; i < numberOfScanlines; ++i) {
1062 memset(scanlines[i], defaultPixel, bytesPerScanline);
1063 }
1064 }
1065 // Buffer that contains the current output scanline
1066 QVector<quint8> antialiasedScanline(rect.width());
1067 // Main loop
1068 for (int y = 0; y < rect.height(); ++y)
1069 {
1070 // Move to the next scanline
1071 if (y > 0) {
1072 // Update scanline pointers
1073 std::rotate(std::begin(scanlines), std::begin(scanlines) + 1, std::end(scanlines));
1074 // Copy the next scanline
1075 if (y < rect.height() - verticalBorderSize) {
1076 // Set the border pixels on the left
1077 memset(scanlines[numberOfScanlines - 1], defaultPixel, horizontalBorderSize);
1078 // Copy the pixel data
1079 pixelSelection->readBytes(scanlines[numberOfScanlines - 1] + horizontalBorderSize, rect.x(), rect.y() + y + verticalBorderSize, rect.width(), 1);
1080 // Set the border pixels on the right
1081 memset(scanlines[numberOfScanlines - 1] + horizontalBorderSize + rect.width(), defaultPixel, horizontalBorderSize);
1082 } else {
1083 memset(scanlines[numberOfScanlines - 1], defaultPixel, bytesPerScanline);
1084 }
1085 }
1086 // Process the pixels in the current scanline
1087 for (int x = 0; x < rect.width(); ++x)
1088 {
1089 // Get the current pixel and neighbors
1090 quint8 *pixelPtrM = scanlines[currentScanlineIndex ] + x + horizontalBorderSize;
1091 quint8 *pixelPtrN = scanlines[currentScanlineIndex - 1] + x + horizontalBorderSize;
1092 quint8 *pixelPtrS = scanlines[currentScanlineIndex + 1] + x + horizontalBorderSize;
1093 const qint32 pixelNW = *(pixelPtrN - 1);
1094 const qint32 pixelN = *(pixelPtrN );
1095 const qint32 pixelNE = *(pixelPtrN + 1);
1096 const qint32 pixelW = *(pixelPtrM - 1);
1097 const qint32 pixelM = *(pixelPtrM );
1098 const qint32 pixelE = *(pixelPtrM + 1);
1099 const qint32 pixelSW = *(pixelPtrS - 1);
1100 const qint32 pixelS = *(pixelPtrS );
1101 const qint32 pixelSE = *(pixelPtrS + 1);
1102 // Get the gradients
1103 const qint32 rowNSum = (pixelNW >> 2) + (pixelN >> 1) + (pixelNE >> 2);
1104 const qint32 rowMSum = (pixelW >> 2) + (pixelM >> 1) + (pixelE >> 2);
1105 const qint32 rowSSum = (pixelSW >> 2) + (pixelS >> 1) + (pixelSE >> 2);
1106 const qint32 colWSum = (pixelNW >> 2) + (pixelW >> 1) + (pixelSW >> 2);
1107 const qint32 colMSum = (pixelN >> 2) + (pixelM >> 1) + (pixelS >> 2);
1108 const qint32 colESum = (pixelNE >> 2) + (pixelE >> 1) + (pixelSE >> 2);
1109 const qint32 gradientN = qAbs(rowMSum - rowNSum);
1110 const qint32 gradientS = qAbs(rowSSum - rowMSum);
1111 const qint32 gradientW = qAbs(colMSum - colWSum);
1112 const qint32 gradientE = qAbs(colESum - colMSum);
1113 // Get the maximum gradient
1114 const qint32 maxGradientNS = qMax(gradientN, gradientS);
1115 const qint32 maxGradientWE = qMax(gradientW, gradientE);
1116 const qint32 maxGradient = qMax(maxGradientNS, maxGradientWE);
1117 // Return early if the gradient is bellow some threshold (given by
1118 // the value bellow which the jagged edge is not noticeable)
1119 if (maxGradient < edgeThreshold) {
1120 antialiasedScanline[x] = pixelM;
1121 continue;
1122 }
1123 // Collect some info about the pixel and neighborhood
1124 qint32 neighborPixel, gradient;
1125 qint32 pixelOffset, rowMultiplier, colMultiplier;
1126 if (maxGradientNS > maxGradientWE) {
1127 // Horizontal span
1128 if (gradientN > gradientS) {
1129 // The edge is formed with the top pixel
1130 neighborPixel = pixelN;
1131 gradient = gradientN;
1132 pixelOffset = -1;
1133 } else {
1134 // The edge is formed with the bottom pixel
1135 neighborPixel = pixelS;
1136 gradient = gradientS;
1137 pixelOffset = 1;
1138 }
1139 rowMultiplier = 0;
1140 colMultiplier = 1;
1141 } else {
1142 // Vertical span
1143 if (gradientW > gradientE) {
1144 // The edge is formed with the left pixel
1145 neighborPixel = pixelW;
1146 gradient = gradientW;
1147 pixelOffset = -1;
1148 } else {
1149 // The edge is formed with the right pixel
1150 neighborPixel = pixelE;
1151 gradient = gradientE;
1152 pixelOffset = 1;
1153 }
1154 rowMultiplier = 1;
1155 colMultiplier = 0;
1156 }
1157 // Find the span extremes
1158 const qint32 pixelAvg = (neighborPixel + pixelM) >> 1;
1159 const qint32 currentPixelDiff = pixelM - pixelAvg;
1160 qint32 negativePixelDiff, positivePixelDiff;
1161 qint32 negativeSpanEndDistance, positiveSpanEndDistance;
1162 bool negativeSpanExtremeValid, positiveSpanExtremeValid;
1163 findSpanExtremes(scanlines, x, pixelOffset,
1164 rowMultiplier, colMultiplier,
1165 pixelAvg, gradient >> 2, currentPixelDiff,
1166 &negativeSpanEndDistance, &positiveSpanEndDistance,
1167 &negativePixelDiff, &positivePixelDiff,
1168 &negativeSpanExtremeValid, &positiveSpanExtremeValid);
1169 // Get the interpolation value for this pixel given the span extent
1170 // and perform linear interpolation between the current pixel and
1171 // the edge neighbor
1172 qint32 interpolationValue;
1173 if (!getInterpolationValue(negativeSpanEndDistance, positiveSpanEndDistance,
1174 negativePixelDiff, positivePixelDiff, currentPixelDiff,
1175 negativeSpanExtremeValid, positiveSpanExtremeValid, &interpolationValue)) {
1176 antialiasedScanline[x] = pixelM;
1177 } else {
1178 antialiasedScanline[x] = neighborPixel + ((pixelM - neighborPixel) * interpolationValue >> 8);
1179 }
1180 }
1181 // Copy the scanline data to the mask
1182 pixelSelection->writeBytes(antialiasedScanline.data(), rect.x(), rect.y() + y, rect.width(), 1);
1183 }
1184}
1185
1187 KisPaintDeviceSP referenceDevice)
1188 : m_radius(radius)
1189 , m_referenceDevice(referenceDevice)
1190{
1191}
1192
1194{
1195 return kundo2_i18n("Grow Selection Until Darkest Pixel");
1196}
1197
1199{
1200 Q_UNUSED(defaultBounds);
1201
1202 return rect.adjusted(-m_radius, -m_radius, m_radius, m_radius);
1203}
1204
1206{
1207 // Copy the original selection. We will grow this adaptively until the
1208 // darkest or more opaque pixels or until the maximum grow is reached.
1209 KisPixelSelectionSP mask = new KisPixelSelection(*pixelSelection);
1210 // Grow the original selection normally. At the end this selection will be
1211 // masked with the adaptively grown mask. We cannot grow adaptively this
1212 // selection directly since it may have semi-transparent or soft edges.
1213 // Those need to be retained in the final selection. This normally grown
1214 // selection is also used as a stop condition for the adaptive mask, which
1215 // cannot grow pass the limits of the this normally grown selection.
1217 growFilter.process(pixelSelection, rect);
1218
1219 const qint32 maskScanLineSize = rect.width();
1220 const KoColorSpace *referenceColorSpace = m_referenceDevice->colorSpace();
1221 const qint32 referencePixelSize = referenceColorSpace->pixelSize();
1222 const qint32 referenceScanLineSize = maskScanLineSize * referencePixelSize;
1223 // Some buffers to store the working scanlines
1224 QVector<quint8> maskBuffer(maskScanLineSize * 2);
1225 QVector<quint8> referenceBuffer(referenceScanLineSize * 2);
1226 QVector<quint8> selectionBuffer(maskScanLineSize);
1227 quint8 *maskScanLines[2] = {maskBuffer.data(), maskBuffer.data() + maskScanLineSize};
1228 quint8 *referenceScanLines[2] = {referenceBuffer.data(), referenceBuffer.data() + referenceScanLineSize};
1229 quint8 *selectionScanLine = selectionBuffer.data();
1230 // Helper function to test if a pixel can be selected
1231 auto testSelectPixel =
1232 [referenceColorSpace]
1233 (quint8 pixelOpacity, quint8 pixelIntensity,
1234 const quint8 *testMaskPixel, const quint8 *testReferencePixel) -> bool
1235 {
1236 if (*testMaskPixel) {
1237 const quint8 testOpacity = referenceColorSpace->opacityU8(testReferencePixel);
1238 if (pixelOpacity >= testOpacity) {
1239 // Special case for when the neighbor pixel is fully transparent.
1240 // In that case do not compare the intensity
1241 if (testOpacity == MIN_SELECTED) {
1242 return true;
1243 }
1244 // If the opacity test passes we still have to perform the
1245 // intensity test
1246 const quint8 testIntensity = referenceColorSpace->intensity8(testReferencePixel);
1247 if (pixelIntensity <= testIntensity) {
1248 return true;
1249 }
1250 }
1251 }
1252 return false;
1253 };
1254
1255 // Top-left to bottom-right pass
1256 // First row
1257 {
1258 mask->readBytes(maskScanLines[1], rect.left(), rect.top(), maskScanLineSize, 1);
1259 m_referenceDevice->readBytes(referenceScanLines[1], rect.left(), rect.top(), maskScanLineSize, 1);
1260 pixelSelection->readBytes(selectionScanLine, rect.left(), rect.top(), maskScanLineSize, 1);
1261 quint8 *currentMaskScanLineBegin = maskScanLines[1];
1262 quint8 *currentMaskScanLineEnd = maskScanLines[1] + maskScanLineSize;
1263 quint8 *currentReferenceScanLineBegin = referenceScanLines[1];
1264 quint8 *currentSelectionScanLineBegin = selectionScanLine;
1265 // First pixel
1266 ++currentMaskScanLineBegin;
1267 currentReferenceScanLineBegin += referencePixelSize;
1268 ++currentSelectionScanLineBegin;
1269 // Rest of pixels
1270 while (currentMaskScanLineBegin != currentMaskScanLineEnd) {
1271 if (*currentMaskScanLineBegin == MIN_SELECTED && *currentSelectionScanLineBegin != MIN_SELECTED) {
1272 const quint8 currentOpacity = referenceColorSpace->opacityU8(currentReferenceScanLineBegin);
1273 const quint8 currentIntensity = referenceColorSpace->intensity8(currentReferenceScanLineBegin);
1274
1275 bool pixelIsSelected = testSelectPixel(currentOpacity, currentIntensity,
1276 currentMaskScanLineBegin - 1,
1277 currentReferenceScanLineBegin - referencePixelSize);
1278 if (pixelIsSelected) {
1279 *currentMaskScanLineBegin = MAX_SELECTED;
1280 }
1281 }
1282 ++currentMaskScanLineBegin;
1283 currentReferenceScanLineBegin += referencePixelSize;
1284 ++currentSelectionScanLineBegin;
1285 }
1286 mask->writeBytes(maskScanLines[1], rect.left(), rect.top(), maskScanLineSize, 1);
1287 }
1288 // Rest of rows
1289 for (qint32 y = rect.top() + 1; y <= rect.bottom(); ++y) {
1290 rotatePointers(maskScanLines, 2);
1291 rotatePointers(referenceScanLines, 2);
1292 mask->readBytes(maskScanLines[1], rect.left(), y, maskScanLineSize, 1);
1293 m_referenceDevice->readBytes(referenceScanLines[1], rect.left(), y, maskScanLineSize, 1);
1294 pixelSelection->readBytes(selectionScanLine, rect.left(), y, maskScanLineSize, 1);
1295 quint8 *currentMaskScanLineBegin = maskScanLines[1];
1296 quint8 *currentMaskScanLineEnd = maskScanLines[1] + maskScanLineSize;
1297 quint8 *currentReferenceScanLineBegin = referenceScanLines[1];
1298 quint8 *topMaskScanLineBegin = maskScanLines[0];
1299 quint8 *topReferenceScanLineBegin = referenceScanLines[0];
1300 quint8 *currentSelectionScanLineBegin = selectionScanLine;
1301 // First pixel
1302 {
1303 if (*currentMaskScanLineBegin == MIN_SELECTED && *currentSelectionScanLineBegin != MIN_SELECTED) {
1304 const quint8 currentOpacity = referenceColorSpace->opacityU8(currentReferenceScanLineBegin);
1305 const quint8 currentIntensity = referenceColorSpace->intensity8(currentReferenceScanLineBegin);
1306
1307 bool pixelIsSelected = testSelectPixel(currentOpacity, currentIntensity,
1308 topMaskScanLineBegin,
1309 topReferenceScanLineBegin);
1310 if (!pixelIsSelected) {
1311 pixelIsSelected = testSelectPixel(currentOpacity, currentIntensity,
1312 topMaskScanLineBegin + 1,
1313 topReferenceScanLineBegin + referencePixelSize);
1314 }
1315 if (pixelIsSelected) {
1316 *currentMaskScanLineBegin = MAX_SELECTED;
1317 }
1318 }
1319 ++currentMaskScanLineBegin;
1320 currentReferenceScanLineBegin += referencePixelSize;
1321 ++topMaskScanLineBegin;
1322 topReferenceScanLineBegin += referencePixelSize;
1323 ++currentSelectionScanLineBegin;
1324 }
1325 // Rest of pixels
1326 while (currentMaskScanLineBegin != (currentMaskScanLineEnd - 1)) {
1327 if (*currentMaskScanLineBegin == MIN_SELECTED && *currentSelectionScanLineBegin != MIN_SELECTED) {
1328 const quint8 currentOpacity = referenceColorSpace->opacityU8(currentReferenceScanLineBegin);
1329 const quint8 currentIntensity = referenceColorSpace->intensity8(currentReferenceScanLineBegin);
1330
1331 bool pixelIsSelected = testSelectPixel(currentOpacity, currentIntensity,
1332 topMaskScanLineBegin - 1,
1333 topReferenceScanLineBegin - referencePixelSize);
1334 if (!pixelIsSelected) {
1335 pixelIsSelected = testSelectPixel(currentOpacity, currentIntensity,
1336 topMaskScanLineBegin,
1337 topReferenceScanLineBegin);
1338 if (!pixelIsSelected) {
1339 pixelIsSelected = testSelectPixel(currentOpacity, currentIntensity,
1340 topMaskScanLineBegin + 1,
1341 topReferenceScanLineBegin + referencePixelSize);
1342 if (!pixelIsSelected) {
1343 pixelIsSelected = testSelectPixel(currentOpacity, currentIntensity,
1344 currentMaskScanLineBegin - 1,
1345 currentReferenceScanLineBegin - referencePixelSize);
1346 }
1347 }
1348 }
1349 if (pixelIsSelected) {
1350 *currentMaskScanLineBegin = MAX_SELECTED;
1351 }
1352 }
1353 ++currentMaskScanLineBegin;
1354 currentReferenceScanLineBegin += referencePixelSize;
1355 ++topMaskScanLineBegin;
1356 topReferenceScanLineBegin += referencePixelSize;
1357 ++currentSelectionScanLineBegin;
1358 }
1359 // Last pixel
1360 {
1361 if (*currentMaskScanLineBegin == MIN_SELECTED && *currentSelectionScanLineBegin != MIN_SELECTED) {
1362 const quint8 currentOpacity = referenceColorSpace->opacityU8(currentReferenceScanLineBegin);
1363 const quint8 currentIntensity = referenceColorSpace->intensity8(currentReferenceScanLineBegin);
1364
1365 bool pixelIsSelected = testSelectPixel(currentOpacity, currentIntensity,
1366 topMaskScanLineBegin - 1,
1367 topReferenceScanLineBegin - referencePixelSize);
1368 if (!pixelIsSelected) {
1369 pixelIsSelected = testSelectPixel(currentOpacity, currentIntensity,
1370 topMaskScanLineBegin,
1371 topReferenceScanLineBegin);
1372 if (!pixelIsSelected) {
1373 pixelIsSelected = testSelectPixel(currentOpacity, currentIntensity,
1374 currentMaskScanLineBegin - 1,
1375 currentReferenceScanLineBegin - referencePixelSize);
1376 }
1377 }
1378 if (pixelIsSelected) {
1379 *currentMaskScanLineBegin = MAX_SELECTED;
1380 }
1381 }
1382 }
1383 mask->writeBytes(maskScanLines[1], rect.left(), y, maskScanLineSize, 1);
1384 }
1385
1386 // Bottom-right to top-left pass
1387 // Last row
1388 {
1389 mask->readBytes(maskScanLines[1], rect.left(), rect.bottom(), maskScanLineSize, 1);
1390 m_referenceDevice->readBytes(referenceScanLines[1], rect.left(), rect.bottom(), maskScanLineSize, 1);
1391 pixelSelection->readBytes(selectionScanLine, rect.left(), rect.bottom(), maskScanLineSize, 1);
1392 quint8 *currentMaskScanLineBegin = maskScanLines[1] + maskScanLineSize - 1;
1393 quint8 *currentMaskScanLineEnd = maskScanLines[1] - 1;
1394 quint8 *currentReferenceScanLineBegin = referenceScanLines[1] + referenceScanLineSize - referencePixelSize;
1395 quint8 *currentSelectionScanLineBegin = selectionScanLine + maskScanLineSize - 1;
1396 // Last pixel
1397 --currentMaskScanLineBegin;
1398 currentReferenceScanLineBegin -= referencePixelSize;
1399 --currentSelectionScanLineBegin;
1400 // Rest of pixels
1401 while (currentMaskScanLineBegin != currentMaskScanLineEnd) {
1402 if (*currentMaskScanLineBegin == MIN_SELECTED && *currentSelectionScanLineBegin != MIN_SELECTED) {
1403 const quint8 currentOpacity = referenceColorSpace->opacityU8(currentReferenceScanLineBegin);
1404 const quint8 currentIntensity = referenceColorSpace->intensity8(currentReferenceScanLineBegin);
1405
1406 bool pixelIsSelected = testSelectPixel(currentOpacity, currentIntensity,
1407 currentMaskScanLineBegin + 1,
1408 currentReferenceScanLineBegin + referencePixelSize);
1409 if (pixelIsSelected) {
1410 *currentMaskScanLineBegin = MAX_SELECTED;
1411 }
1412 }
1413 --currentMaskScanLineBegin;
1414 currentReferenceScanLineBegin -= referencePixelSize;
1415 --currentSelectionScanLineBegin;
1416 }
1417 mask->writeBytes(maskScanLines[1], rect.left(), rect.top(), maskScanLineSize, 1);
1418 }
1419 // Rest of rows
1420 for (qint32 y = rect.bottom() - 1; y >= rect.top(); --y) {
1421 rotatePointers(maskScanLines, 2);
1422 rotatePointers(referenceScanLines, 2);
1423 mask->readBytes(maskScanLines[1], rect.left(), y, maskScanLineSize, 1);
1424 m_referenceDevice->readBytes(referenceScanLines[1], rect.left(), y, maskScanLineSize, 1);
1425 pixelSelection->readBytes(selectionScanLine, rect.left(), y, maskScanLineSize, 1);
1426 quint8 *currentMaskScanLineBegin = maskScanLines[1] + maskScanLineSize - 1;
1427 quint8 *currentMaskScanLineEnd = maskScanLines[1] - 1;
1428 quint8 *currentReferenceScanLineBegin = referenceScanLines[1] + referenceScanLineSize - referencePixelSize;
1429 quint8 *bottomMaskScanLineBegin = maskScanLines[0] + maskScanLineSize - 1;
1430 quint8 *bottomReferenceScanLineBegin = referenceScanLines[0] + referenceScanLineSize - referencePixelSize;
1431 quint8 *currentSelectionScanLineBegin = selectionScanLine + maskScanLineSize - 1;
1432 // Last pixel
1433 {
1434 if (*currentMaskScanLineBegin == MIN_SELECTED && *currentSelectionScanLineBegin != MIN_SELECTED) {
1435 const quint8 currentOpacity = referenceColorSpace->opacityU8(currentReferenceScanLineBegin);
1436 const quint8 currentIntensity = referenceColorSpace->intensity8(currentReferenceScanLineBegin);
1437
1438 bool pixelIsSelected = testSelectPixel(currentOpacity, currentIntensity,
1439 bottomMaskScanLineBegin,
1440 bottomReferenceScanLineBegin);
1441 if (!pixelIsSelected) {
1442 pixelIsSelected = testSelectPixel(currentOpacity, currentIntensity,
1443 bottomMaskScanLineBegin - 1,
1444 bottomReferenceScanLineBegin - referencePixelSize);
1445 }
1446 if (pixelIsSelected) {
1447 *currentMaskScanLineBegin = MAX_SELECTED;
1448 }
1449 }
1450 --currentMaskScanLineBegin;
1451 currentReferenceScanLineBegin -= referencePixelSize;
1452 --bottomMaskScanLineBegin;
1453 bottomReferenceScanLineBegin -= referencePixelSize;
1454 --currentSelectionScanLineBegin;
1455 }
1456 // Rest of pixels
1457 while (currentMaskScanLineBegin != (currentMaskScanLineEnd + 1)) {
1458 if (*currentMaskScanLineBegin == MIN_SELECTED && *currentSelectionScanLineBegin != MIN_SELECTED) {
1459 const quint8 currentOpacity = referenceColorSpace->opacityU8(currentReferenceScanLineBegin);
1460 const quint8 currentIntensity = referenceColorSpace->intensity8(currentReferenceScanLineBegin);
1461
1462 bool pixelIsSelected = testSelectPixel(currentOpacity, currentIntensity,
1463 bottomMaskScanLineBegin + 1,
1464 bottomReferenceScanLineBegin + referencePixelSize);
1465 if (!pixelIsSelected) {
1466 pixelIsSelected = testSelectPixel(currentOpacity, currentIntensity,
1467 bottomMaskScanLineBegin,
1468 bottomReferenceScanLineBegin);
1469 if (!pixelIsSelected) {
1470 pixelIsSelected = testSelectPixel(currentOpacity, currentIntensity,
1471 bottomMaskScanLineBegin - 1,
1472 bottomReferenceScanLineBegin - referencePixelSize);
1473 if (!pixelIsSelected) {
1474 pixelIsSelected = testSelectPixel(currentOpacity, currentIntensity,
1475 currentMaskScanLineBegin + 1,
1476 currentReferenceScanLineBegin + referencePixelSize);
1477 }
1478 }
1479 }
1480 if (pixelIsSelected) {
1481 *currentMaskScanLineBegin = MAX_SELECTED;
1482 }
1483 }
1484 --currentMaskScanLineBegin;
1485 currentReferenceScanLineBegin -= referencePixelSize;
1486 --bottomMaskScanLineBegin;
1487 bottomReferenceScanLineBegin -= referencePixelSize;
1488 --currentSelectionScanLineBegin;
1489 }
1490 // First pixel
1491 {
1492 if (*currentMaskScanLineBegin == MIN_SELECTED && *currentSelectionScanLineBegin != MIN_SELECTED) {
1493 const quint8 currentOpacity = referenceColorSpace->opacityU8(currentReferenceScanLineBegin);
1494 const quint8 currentIntensity = referenceColorSpace->intensity8(currentReferenceScanLineBegin);
1495
1496 bool pixelIsSelected = testSelectPixel(currentOpacity, currentIntensity,
1497 bottomMaskScanLineBegin + 1,
1498 bottomReferenceScanLineBegin + referencePixelSize);
1499 if (!pixelIsSelected) {
1500 pixelIsSelected = testSelectPixel(currentOpacity, currentIntensity,
1501 bottomMaskScanLineBegin,
1502 bottomReferenceScanLineBegin);
1503 if (!pixelIsSelected) {
1504 pixelIsSelected = testSelectPixel(currentOpacity, currentIntensity,
1505 currentMaskScanLineBegin + 1,
1506 currentReferenceScanLineBegin + referencePixelSize);
1507 }
1508 }
1509 if (pixelIsSelected) {
1510 *currentMaskScanLineBegin = MAX_SELECTED;
1511 }
1512 }
1513 }
1514 mask->writeBytes(maskScanLines[1], rect.left(), y, maskScanLineSize, 1);
1515 }
1516
1517 // Combine the adaptively grown mask with the normally grown mask. The
1518 // adaptively grown mask is used as a binary mask to erase some of the
1519 // pixels of the normally grown mask
1520 {
1522 KisSequentialIterator it2(pixelSelection, rect);
1523 while (it1.nextPixel() && it2.nextPixel()) {
1524 *it2.rawData() *= (*it1.rawDataConst() != MIN_SELECTED);
1525 }
1526 }
1527}
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