Krita Source Code Documentation
Loading...
Searching...
No Matches
kis_scanline_fill.cpp
Go to the documentation of this file.
1/*
2 * SPDX-FileCopyrightText: 2014 Dmitry Kazakov <dimula73@gmail.com>
3 *
4 * SPDX-License-Identifier: GPL-2.0-or-later
5 */
6
7#include "kis_scanline_fill.h"
8
9#include <KoAlwaysInline.h>
10
11#include <QStack>
12#include <KoColor.h>
13#include <KoColorSpace.h>
15#include "kis_image.h"
17#include "kis_pixel_selection.h"
21#include "kis_gap_map.h"
22#include <queue>
23
24#define MEASURE_FILL_TIME 0
25#if MEASURE_FILL_TIME
26#include <QElapsedTimer>
27#endif
28
29namespace {
30
31typedef KisSharedPtr<KisGapMap> KisGapMapSP;
32
37struct CloseGapFillPoint
38{
39 int x;
40 int y;
41
42 quint32 distance;
43 bool allowExpand;
44
45 // Used with the priority queue.
46 // Use the "greater" operator to place the smallest element on top.
47 // std::make_tuple instead of std::tie because we need to negate allowExpand.
48 friend bool operator>(const CloseGapFillPoint& a, const CloseGapFillPoint& b) {
49 return std::make_tuple(!a.allowExpand, a.distance, a.y, a.x) >
50 std::make_tuple(!b.allowExpand, b.distance, b.y, b.x);
51 }
52};
53
54class BasePixelAccessPolicy
55{
56public:
57 using SourceAccessorType = KisRandomAccessorSP;
58
59 SourceAccessorType m_srcIt;
60
61 BasePixelAccessPolicy(KisPaintDeviceSP sourceDevice)
62 : m_srcIt(sourceDevice->createRandomAccessorNG())
63 {}
64};
65
66class ConstBasePixelAccessPolicy
67{
68public:
69 using SourceAccessorType = KisRandomConstAccessorSP;
70
71 SourceAccessorType m_srcIt;
72
73 ConstBasePixelAccessPolicy(KisPaintDeviceSP sourceDevice)
74 : m_srcIt(sourceDevice->createRandomConstAccessorNG())
75 {}
76};
77
78class CopyToSelectionPixelAccessPolicy : public ConstBasePixelAccessPolicy
79{
80public:
81 CopyToSelectionPixelAccessPolicy(KisPaintDeviceSP sourceDevice, KisPaintDeviceSP pixelSelection)
82 : ConstBasePixelAccessPolicy(sourceDevice)
83 , m_pixelSelection(pixelSelection)
84 , m_selectionIterator(m_pixelSelection->createRandomAccessorNG())
85 {}
86
87 ALWAYS_INLINE void fillPixel(quint8 *dstPtr, quint8 opacity, int x, int y)
88 {
89 Q_UNUSED(dstPtr);
90 m_selectionIterator->moveTo(x, y);
91 *m_selectionIterator->rawData() = opacity;
92 }
93
94private:
95 KisPaintDeviceSP m_pixelSelection;
96 KisRandomAccessorSP m_selectionIterator;
97};
98
99class FillWithColorPixelAccessPolicy : public BasePixelAccessPolicy
100{
101public:
102 FillWithColorPixelAccessPolicy(KisPaintDeviceSP sourceDevice, const KoColor &fillColor)
103 : BasePixelAccessPolicy(sourceDevice)
104 , m_fillColor(fillColor)
105 , m_fillColorPtr(m_fillColor.data())
106 , m_pixelSize(m_fillColor.colorSpace()->pixelSize())
107 {}
108
109 ALWAYS_INLINE void fillPixel(quint8 *dstPtr, quint8 opacity, int x, int y)
110 {
111 Q_UNUSED(x);
112 Q_UNUSED(y);
113
114 if (opacity == MAX_SELECTED) {
115 memcpy(dstPtr, m_fillColorPtr, m_pixelSize);
116 }
117 }
118
119private:
120 KoColor m_fillColor;
121 const quint8 *m_fillColorPtr;
122 int m_pixelSize;
123};
124
125class FillWithColorExternalPixelAccessPolicy : public ConstBasePixelAccessPolicy
126{
127public:
128 FillWithColorExternalPixelAccessPolicy(KisPaintDeviceSP sourceDevice,
129 const KoColor &fillColor,
130 KisPaintDeviceSP externalDevice)
131 : ConstBasePixelAccessPolicy(sourceDevice)
132 , m_externalDevice(externalDevice)
133 , m_externalDeviceIterator(m_externalDevice->createRandomAccessorNG())
134 , m_fillColor(fillColor)
135 , m_fillColorPtr(m_fillColor.data())
136 , m_pixelSize(m_fillColor.colorSpace()->pixelSize())
137 {}
138
139 ALWAYS_INLINE void fillPixel(quint8 *dstPtr, quint8 opacity, int x, int y)
140 {
141 Q_UNUSED(dstPtr);
142
143 m_externalDeviceIterator->moveTo(x, y);
144 if (opacity == MAX_SELECTED) {
145 memcpy(m_externalDeviceIterator->rawData(), m_fillColorPtr, m_pixelSize);
146 }
147 }
148
149private:
150 KisPaintDeviceSP m_externalDevice;
151 KisRandomAccessorSP m_externalDeviceIterator;
152 KoColor m_fillColor;
153 const quint8 *m_fillColorPtr;
154 int m_pixelSize;
155};
156
157template <typename BaseSelectionPolicy>
158class SelectionPolicy
159{
160public:
161 SelectionPolicy(BaseSelectionPolicy baseSelectionPolicy)
162 : m_baseSelectionPolicy(baseSelectionPolicy)
163 {}
164
165 ALWAYS_INLINE quint8 opacityFromDifference(quint8 difference, int x, int y)
166 {
167 Q_UNUSED(x);
168 Q_UNUSED(y);
169
170 return m_baseSelectionPolicy.opacityFromDifference(difference);
171 }
172
173private:
174 BaseSelectionPolicy m_baseSelectionPolicy;
175};
176
177template <typename BaseSelectionPolicy>
178class MaskedSelectionPolicy
179{
180public:
181 MaskedSelectionPolicy(BaseSelectionPolicy baseSelectionPolicy,
182 KisPaintDeviceSP maskDevice)
183 : m_baseSelectionPolicy(baseSelectionPolicy)
184 , m_maskIterator(maskDevice->createRandomConstAccessorNG())
185 {}
186
187 ALWAYS_INLINE quint8 opacityFromDifference(quint8 difference, int x, int y)
188 {
189 m_maskIterator->moveTo(x, y);
190 const quint8* maskPtr = m_maskIterator->rawDataConst();
191
192 if (*maskPtr == MIN_SELECTED) {
193 return MIN_SELECTED;
194 }
195
196 return m_baseSelectionPolicy.opacityFromDifference(difference);
197 }
198
199private:
200 BaseSelectionPolicy m_baseSelectionPolicy;
201 KisRandomConstAccessorSP m_maskIterator;
202};
203
204class GroupSplitDifferencePolicy
205{
206public:
207 GroupSplitDifferencePolicy(int referenceValue)
208 : m_referenceValue(referenceValue)
209 {}
210
211 ALWAYS_INLINE quint8 difference(const quint8 *colorPtr) const
212 {
213 return qAbs(*colorPtr - m_referenceValue);
214 }
215
216private:
217 int m_referenceValue;
218};
219
220class GroupSplitSelectionPolicy : public KisColorSelectionPolicies::HardSelectionPolicy
221{
222public:
223 GroupSplitSelectionPolicy(int threshold)
225 {}
226
227 ALWAYS_INLINE quint8 opacityFromDifference(quint8 difference, int x, int y) {
228 // TODO: either threshold should always be null, or there should be a special
229 // case for *pixelPtr == 0, which is different from all the other groups,
230 // whatever the threshold is
231 Q_UNUSED(x);
232 Q_UNUSED(y);
234 }
235};
236
237class GroupSplitPixelAccessPolicy : public BasePixelAccessPolicy
238{
239public:
240 GroupSplitPixelAccessPolicy(KisPaintDeviceSP scribbleDevice,
241 KisPaintDeviceSP groupMapDevice,
242 qint32 groupIndex)
243 : BasePixelAccessPolicy(scribbleDevice)
244 , m_groupIndex(groupIndex)
245 , m_groupMapIt(groupMapDevice->createRandomAccessorNG())
246 {
247 KIS_SAFE_ASSERT_RECOVER_NOOP(m_groupIndex > 0);
248 }
249
250 ALWAYS_INLINE void fillPixel(quint8 *dstPtr, quint8 opacity, int x, int y)
251 {
252 Q_UNUSED(opacity);
253
254 // erase the scribble
255 *dstPtr = 0;
256
257 // write group index into the map
258 m_groupMapIt->moveTo(x, y);
259 qint32 *groupMapPtr = reinterpret_cast<qint32*>(m_groupMapIt->rawData());
260
261 if (*groupMapPtr != 0) {
262 dbgImage << ppVar(*groupMapPtr) << ppVar(m_groupIndex);
263 }
264
265 KIS_SAFE_ASSERT_RECOVER_NOOP(*groupMapPtr == 0);
266
267 *groupMapPtr = m_groupIndex;
268 }
269
270private:
271 qint32 m_groupIndex;
272 KisRandomAccessorSP m_groupMapIt;
273};
274
275} // anonymous namespace
276
277struct Q_DECL_HIDDEN KisScanlineFill::Private
278{
284
288
290 KisGapMapSP gapMapSp;
291
293
294 // The priority queue is required to correctly handle the fill "expansion" case
295 // (starting in a corner and filling towards open areas, where distance is DISTANCE_INFINITE).
296 // Holds the next pixel to consider for filling, among with the contextual information.
297 template<class T> using GreaterPriorityQueue = std::priority_queue<T, std::vector<T>, std::greater<T>>;
299
300 // Gap closing flood fill must check the currently filled selection in order to finish.
301 // Otherwise, it would attempt to fill the same pixels in an infinite loop.
303
304
305 inline void swapDirection() {
306 rowIncrement *= -1;
307 KIS_SAFE_ASSERT_RECOVER_NOOP(forwardStack.isEmpty() &&
308 "FATAL: the forward stack must be empty "
309 "on a direction swap");
310
311 forwardStack = QStack<KisFillInterval>(backwardMap.fetchAllIntervals(rowIncrement));
312 backwardMap.clear();
313 }
314};
315
316
317KisScanlineFill::KisScanlineFill(KisPaintDeviceSP device, const QPoint &startPoint, const QRect &boundingRect)
318 : m_d(new Private)
319{
320 m_d->device = device;
321 m_d->startPoint = startPoint;
322 m_d->boundingRect = boundingRect;
323
324 m_d->rowIncrement = 1;
325
326 m_d->threshold = 0;
327 m_d->opacitySpread = 0;
328 m_d->closeGap = 0;
329}
330
334
336{
337 m_d->threshold = threshold;
338}
339
341{
342 m_d->opacitySpread = opacitySpread;
343}
344
346{
347 m_d->closeGap = closeGap;
348}
349
350QRect KisScanlineFill::fillExtent() const
351{
352 return m_d->fillExtent;
353}
354
355
368bool KisScanlineFill::tryPushingCloseGapSeed(int x, int y, bool allowExpand)
369{
370 bool pushed = false;
371
372 if (m_d->gapMapSp && m_d->gapMapSp->gapSize() > 0) {
373 const quint32 distance = m_d->gapMapSp->distance(x, y);
374
376 pushed = true;
377
378 CloseGapFillPoint seed;
379 seed.x = x;
380 seed.y = y;
381 seed.distance = distance;
382 seed.allowExpand = allowExpand;
383
384 m_d->closeGapQueue.push(seed);
385 }
386 }
387
388 return pushed;
389}
390
391template <typename DifferencePolicy, typename SelectionPolicy, typename PixelAccessPolicy>
392void KisScanlineFill::extendedPass(KisFillInterval *currentInterval, int srcRow, bool extendRight,
393 DifferencePolicy &differencePolicy,
394 SelectionPolicy &selectionPolicy,
395 PixelAccessPolicy &pixelAccessPolicy)
396{
397 int x;
398 int endX;
399 int columnIncrement;
400 int *intervalBorder;
401 int *backwardIntervalBorder;
402 KisFillInterval backwardInterval(currentInterval->start, currentInterval->end, srcRow);
403
404 if (extendRight) {
405 x = currentInterval->end;
406 endX = m_d->boundingRect.right();
407 if (x >= endX) return;
408 columnIncrement = 1;
409 intervalBorder = &currentInterval->end;
410
411 backwardInterval.start = currentInterval->end + 1;
412 backwardIntervalBorder = &backwardInterval.end;
413 } else {
414 x = currentInterval->start;
415 endX = m_d->boundingRect.left();
416 if (x <= endX) return;
417 columnIncrement = -1;
418 intervalBorder = &currentInterval->start;
419
420 backwardInterval.end = currentInterval->start - 1;
421 backwardIntervalBorder = &backwardInterval.start;
422 }
423
424 do {
425 x += columnIncrement;
426
427 pixelAccessPolicy.m_srcIt->moveTo(x, srcRow);
428 quint8 *pixelPtr = const_cast<quint8*>(pixelAccessPolicy.m_srcIt->rawDataConst()); // TODO: avoid doing const_cast
429 const quint8 difference = differencePolicy.difference(pixelPtr);
430 const quint8 opacity = selectionPolicy.opacityFromDifference(difference, x, srcRow);
431 const bool stopOnGap = tryPushingCloseGapSeed(x, srcRow, false);
432
433 if (!stopOnGap && opacity) {
434 *intervalBorder = x;
435 *backwardIntervalBorder = x;
436 pixelAccessPolicy.fillPixel(pixelPtr, opacity, x, srcRow);
437 if (extendRight) {
438 m_d->fillExtent.setRight(qMax(m_d->fillExtent.right(), x));
439 } else {
440 m_d->fillExtent.setLeft(qMin(m_d->fillExtent.left(), x));
441 }
442 } else {
443 break;
444 }
445 } while (x != endX);
446
447 if (backwardInterval.isValid()) {
448 m_d->backwardMap.insertInterval(backwardInterval);
449 }
450}
451
452template <typename DifferencePolicy, typename SelectionPolicy, typename PixelAccessPolicy>
453void KisScanlineFill::processLine(KisFillInterval interval, const int rowIncrement,
454 DifferencePolicy &differencePolicy,
455 SelectionPolicy &selectionPolicy,
456 PixelAccessPolicy &pixelAccessPolicy)
457{
458 m_d->backwardMap.cropInterval(&interval);
459
460 if (!interval.isValid()) return;
461
462 int firstX = interval.start;
463 int lastX = interval.end;
464 int x = firstX;
465 int row = interval.row;
466 int nextRow = row + rowIncrement;
467
468 KisFillInterval currentForwardInterval;
469
470 int numPixelsLeft = 0;
471 quint8 *dataPtr = 0;
472 const int pixelSize = m_d->device->pixelSize();
473
474 while(x <= lastX) {
475 // a bit of optimization for not calling slow random accessor
476 // methods too often
477 if (numPixelsLeft <= 0) {
478 pixelAccessPolicy.m_srcIt->moveTo(x, row);
479 numPixelsLeft = pixelAccessPolicy.m_srcIt->numContiguousColumns(x) - 1;
480 dataPtr = const_cast<quint8*>(pixelAccessPolicy.m_srcIt->rawDataConst());
481 } else {
482 numPixelsLeft--;
483 dataPtr += pixelSize;
484 }
485
486 quint8 *pixelPtr = dataPtr;
487 const quint8 difference = differencePolicy.difference(pixelPtr);
488 const quint8 opacity = selectionPolicy.opacityFromDifference(difference, x, row);
489 const bool stopOnGap = tryPushingCloseGapSeed(x, row, false);
490
491 if (!stopOnGap && opacity) {
492 if (!currentForwardInterval.isValid()) {
493 currentForwardInterval.start = x;
494 currentForwardInterval.end = x;
495 currentForwardInterval.row = nextRow;
496 } else {
497 currentForwardInterval.end = x;
498 }
499
500 pixelAccessPolicy.fillPixel(pixelPtr, opacity, x, row);
501 m_d->fillExtent = m_d->fillExtent.united(QRect(x, row, 1, 1));
502
503 if (x == firstX) {
504 extendedPass(&currentForwardInterval, row, false,
505 differencePolicy, selectionPolicy, pixelAccessPolicy);
506 }
507
508 if (x == lastX) {
509 extendedPass(&currentForwardInterval, row, true,
510 differencePolicy, selectionPolicy, pixelAccessPolicy);
511 }
512
513 } else {
514 if (currentForwardInterval.isValid()) {
515 m_d->forwardStack.push(currentForwardInterval);
516 currentForwardInterval.invalidate();
517 }
518 }
519
520 x++;
521 }
522
523 if (currentForwardInterval.isValid()) {
524 m_d->forwardStack.push(currentForwardInterval);
525 }
526}
527
528template <typename DifferencePolicy, typename SelectionPolicy, typename PixelAccessPolicy>
529KisFillInterval KisScanlineFill::closeGapPass(DifferencePolicy &differencePolicy,
530 SelectionPolicy &selectionPolicy,
531 PixelAccessPolicy &pixelAccessPolicy)
532{
533 KisFillInterval interval;
534
535 while (!m_d->closeGapQueue.empty()) {
536 const CloseGapFillPoint p = m_d->closeGapQueue.top();
537 m_d->closeGapQueue.pop();
538
539 // KisGapMap enforces x/y start at (0, 0).
540 if ((p.x < 0) || (p.x >= m_d->boundingRect.width()) || (p.y < 0) || (p.y >= m_d->boundingRect.height())) {
541 continue;
542 }
543
544 pixelAccessPolicy.m_srcIt->moveTo(p.x, p.y);
545 quint8 *pixelPtr = const_cast<quint8*>(pixelAccessPolicy.m_srcIt->rawDataConst());
546 const quint8 difference = differencePolicy.difference(pixelPtr);
547 const quint8 opacity = selectionPolicy.opacityFromDifference(difference, p.x, p.y);
548
549 // The initial pixel (before the fill) is non-opaque, so we try to fill it.
550 if (opacity) {
551 // However, check if it has already been filled during the ongoing operation.
552 // If the test below is true, then the pixel is already in the selection.
553 m_d->filledSelectionIterator->moveTo(p.x, p.y);
554 if (*m_d->filledSelectionIterator->rawDataConst() == opacity) {
555 continue;
556 }
557
558 const quint32 previousDistance = p.distance;
559 const quint32 distance = m_d->gapMapSp->distance(p.x, p.y);
560 const bool allowExpand = p.allowExpand && (distance >= previousDistance);
561 const float relativeDiff = static_cast<float>(distance) / previousDistance;
562
563 // This ratio determines whether the fill can spread to pixels with
564 // a different gap distance compared to the previous pixel.
565 //
566 // <1.0 is a pixel closer to a gap, or more in a corner of lineart.
567 // =1.0 is the same distance.
568 // >1.0 is a pixel that is farther from a gap, or away from tight corners.
569 //
570 // At high gap sizes, the distance map can vary slightly and the fill could
571 // leave out empty pixels. To prevent that, we allow spilling to a more distant
572 // pixels as well, up to a given tolerance. If the value is too high,
573 // the fill will spill too much.
574
575 static constexpr float SpreadTolerance = 1.3f;
576
577 if ((relativeDiff < SpreadTolerance) || allowExpand) {
579 // Only return one interval at a time. Otherwise, just skip this pixel.
580 if (!interval.isValid()) {
581 interval.start = p.x;
582 interval.end = p.x;
583 interval.row = p.y;
584 }
585 // We still continue the loop, to process all the pixels we can fill.
586 } else {
587 pixelAccessPolicy.fillPixel(pixelPtr, opacity, p.x, p.y);
588 m_d->fillExtent = m_d->fillExtent.united(QRect(p.x, p.y, 1, 1));
589
590 // Forward the context information to the next pixels.
591 // Spread the fill in four directions: up, down, left, right.
592
593 CloseGapFillPoint next;
594 next.distance = distance;
595 next.allowExpand = allowExpand;
596
597 next.x = p.x - 1;
598 next.y = p.y;
599 m_d->closeGapQueue.push(next);
600
601 next.x = p.x + 1;
602 next.y = p.y;
603 m_d->closeGapQueue.push(next);
604
605 next.x = p.x;
606 next.y = p.y - 1;
607 m_d->closeGapQueue.push(next);
608
609 next.x = p.x;
610 next.y = p.y + 1;
611 m_d->closeGapQueue.push(next);
612 }
613 }
614 }
615 }
616
617 // If valid, the scanline fill will take over next.
618 return interval;
619}
620
621template <typename DifferencePolicy, typename SelectionPolicy, typename PixelAccessPolicy>
622void KisScanlineFill::runImpl(DifferencePolicy &differencePolicy,
623 SelectionPolicy &selectionPolicy,
624 PixelAccessPolicy &pixelAccessPolicy)
625{
626 KIS_ASSERT_RECOVER_RETURN(m_d->forwardStack.isEmpty());
627
628 int gapSize = m_d->closeGap;
629 KIS_SAFE_ASSERT_RECOVER(gapSize == 0 || (gapSize > 0 && m_d->filledSelectionIterator)) {
630 gapSize = 0;
631 }
632
633#if MEASURE_FILL_TIME
634 QElapsedTimer timerTotal;
635 QElapsedTimer timerScanlineFill;
636 QElapsedTimer timerGapClosingFill;
637 quint64 totalScanlineFillNanos = 0;
638 quint64 totalGapClosingFillNanos = 0;
639
640 timerTotal.start();
641#endif
642
643 if (gapSize > 0) {
644 // We need to reuse the complex policies used by this class and only provide the final
645 // "projection" of opacity for the distance map calculation.
646 auto opacityFunc = [&](KisPaintDevice* devicePtr, const QRect& rect) {
647 return fillOpacity(differencePolicy, selectionPolicy, pixelAccessPolicy, devicePtr, rect);
648 };
649
650 // Prime the resources. The computations are made lazily, when distance at a pixel is requested.
651 // Resources are freed automatically when the object is destroyed, that is together with the KisScanlineFill object.
652 m_d->gapMapSp = KisGapMapSP(new KisGapMap(gapSize, m_d->boundingRect, opacityFunc));
653 }
654
655 m_d->fillExtent = QRect();
656
657 KisFillInterval startInterval(m_d->startPoint.x(), m_d->startPoint.x(), m_d->startPoint.y());
658
659 // Decide if we should start with a scanline fill or a gap closing fill.
660 if (!tryPushingCloseGapSeed(startInterval.start, startInterval.row, true)) {
661 m_d->forwardStack.push(startInterval);
662 }
663
664 // The outer loop is to continue scanline filling after a gap closing fill.
665 do {
672 bool firstPass = true;
673
674#if MEASURE_FILL_TIME
675 timerScanlineFill.start();
676#endif
677
678 // The scanline fill can only fill the pixels that are "in the open",
679 // that is have DISTANCE_INFINITE. Gap pixels will be pushed to
680 // the gap closing fill's queue.
681
682 while (!m_d->forwardStack.isEmpty()) {
683 while (!m_d->forwardStack.isEmpty()) {
684 KisFillInterval interval = m_d->forwardStack.pop();
685
686 if (interval.row > m_d->boundingRect.bottom() ||
687 interval.row < m_d->boundingRect.top()) {
688
689 continue;
690 }
691
692 processLine(interval, m_d->rowIncrement, differencePolicy, selectionPolicy, pixelAccessPolicy);
693 }
694 m_d->swapDirection();
695
696 if (firstPass) {
697 startInterval.row--;
698 m_d->forwardStack.push(startInterval);
699 firstPass = false;
700 }
701 }
702
703#if MEASURE_FILL_TIME
704 totalScanlineFillNanos += timerScanlineFill.nsecsElapsed();
705 timerGapClosingFill.start();
706#endif
707
708 // Perform a gap closing pass. This pass can in turn feed the scanline fill's
709 // forward stack. It can only fiill pixels with distance < DISTANCE_INFINITE.
710 // This way, both passes complement each other to complete the fill.
711
712 if (!m_d->closeGapQueue.empty()) {
713 startInterval = closeGapPass(differencePolicy, selectionPolicy, pixelAccessPolicy);
714 if (startInterval.isValid()) {
715 m_d->forwardStack.push(startInterval);
716 }
717 }
718
719#if MEASURE_FILL_TIME
720 totalGapClosingFillNanos += timerGapClosingFill.nsecsElapsed();
721#endif
722 } while (!m_d->forwardStack.isEmpty());
723
724#if MEASURE_FILL_TIME
725 static constexpr quint64 MillisDivisor = 1000000ull;
726 const quint64 totalTime = timerTotal.nsecsElapsed();
727 const quint64 overheadTime = (totalTime - totalScanlineFillNanos - totalGapClosingFillNanos);
728 qDebug() << "init overhead =" << qSetRealNumberPrecision(3)
729 << static_cast<double>(overheadTime) / MillisDivisor << "ms ("
730 << static_cast<double>(overheadTime) / totalTime << ")";
731 qDebug() << "fill (scanline) =" << (totalScanlineFillNanos / MillisDivisor) << "ms ("
732 << qSetRealNumberPrecision(3) << static_cast<double>(totalScanlineFillNanos) / totalTime << ")";
733 qDebug() << "fill (gap) =" << (totalGapClosingFillNanos / MillisDivisor) << "ms ("
734 << qSetRealNumberPrecision(3) << static_cast<double>(totalGapClosingFillNanos) / totalTime << ")";
735 qDebug() << "fill total =" << (totalTime / MillisDivisor) << "ms";
736#if KIS_GAP_MAP_MEASURE_ELAPSED_TIME
737 if (gapSize > 0) {
738 qDebug() << "incl. opacity load" << m_d->gapMapSp->opacityElapsedMillis() << "ms";
739 qDebug() << "incl. distance load" << m_d->gapMapSp->distanceElapsedMillis() << "ms";
740 }
741#endif
742 qDebug() << "----------------------------------------";
743#endif
744}
745
746template <template <typename SrcPixelType> typename OptimizedDifferencePolicy,
747 typename SlowDifferencePolicy,
748 typename SelectionPolicy, typename PixelAccessPolicy>
750 SelectionPolicy &selectionPolicy,
751 PixelAccessPolicy &pixelAccessPolicy)
752{
753 const int pixelSize = srcColor.colorSpace()->pixelSize();
754
755 if (pixelSize == 1) {
756 OptimizedDifferencePolicy<quint8> dp(srcColor, m_d->threshold);
757 runImpl(dp, selectionPolicy, pixelAccessPolicy);
758 } else if (pixelSize == 2) {
759 OptimizedDifferencePolicy<quint16> dp(srcColor, m_d->threshold);
760 runImpl(dp, selectionPolicy, pixelAccessPolicy);
761 } else if (pixelSize == 4) {
762 OptimizedDifferencePolicy<quint32> dp(srcColor, m_d->threshold);
763 runImpl(dp, selectionPolicy, pixelAccessPolicy);
764 } else if (pixelSize == 8) {
765 OptimizedDifferencePolicy<quint64> dp(srcColor, m_d->threshold);
766 runImpl(dp, selectionPolicy, pixelAccessPolicy);
767 } else {
768 SlowDifferencePolicy dp(srcColor, m_d->threshold);
769 runImpl(dp, selectionPolicy, pixelAccessPolicy);
770 }
771}
772
773void KisScanlineFill::fill(const KoColor &originalFillColor)
774{
775 KoColor srcColor(m_d->device->pixel(m_d->startPoint));
776 KoColor fillColor(originalFillColor);
777 fillColor.convertTo(m_d->device->colorSpace());
778
779 using namespace KisColorSelectionPolicies;
780
781 SelectionPolicy<HardSelectionPolicy> sp(HardSelectionPolicy(m_d->threshold));
782 FillWithColorPixelAccessPolicy pap(m_d->device, fillColor);
783
784 selectDifferencePolicyAndRun<OptimizedDifferencePolicy, SlowDifferencePolicy>
785 (srcColor, sp, pap);
786}
787
788void KisScanlineFill::fillUntilColor(const KoColor &originalFillColor, const KoColor &boundaryColor)
789{
790 KoColor srcColor(boundaryColor);
791 srcColor.convertTo(m_d->device->colorSpace());
792 KoColor fillColor(originalFillColor);
793 fillColor.convertTo(m_d->device->colorSpace());
794
795 using namespace KisColorSelectionPolicies;
796
797 SelectionPolicy<SelectAllUntilColorHardSelectionPolicy> sp(SelectAllUntilColorHardSelectionPolicy(m_d->threshold));
798 FillWithColorPixelAccessPolicy pap(m_d->device, fillColor);
799
800 selectDifferencePolicyAndRun<OptimizedDifferencePolicy, SlowDifferencePolicy>
801 (srcColor, sp, pap);
802}
803
804void KisScanlineFill::fill(const KoColor &originalFillColor, KisPaintDeviceSP externalDevice)
805{
806 KoColor srcColor(m_d->device->pixel(m_d->startPoint));
807 KoColor fillColor(originalFillColor);
808 fillColor.convertTo(m_d->device->colorSpace());
809
810 using namespace KisColorSelectionPolicies;
811
812 SelectionPolicy<HardSelectionPolicy> sp(HardSelectionPolicy(m_d->threshold));
813 FillWithColorExternalPixelAccessPolicy pap(m_d->device, fillColor, externalDevice);
814
815 selectDifferencePolicyAndRun<OptimizedDifferencePolicy, SlowDifferencePolicy>
816 (srcColor, sp, pap);
817}
818
819void KisScanlineFill::fillUntilColor(const KoColor &originalFillColor, const KoColor &boundaryColor, KisPaintDeviceSP externalDevice)
820{
821 KoColor srcColor(boundaryColor);
822 srcColor.convertTo(m_d->device->colorSpace());
823 KoColor fillColor(originalFillColor);
824 fillColor.convertTo(m_d->device->colorSpace());
825
826 using namespace KisColorSelectionPolicies;
827
828 SelectionPolicy<SelectAllUntilColorHardSelectionPolicy> sp(SelectAllUntilColorHardSelectionPolicy(m_d->threshold));
829 FillWithColorExternalPixelAccessPolicy pap(m_d->device, fillColor, externalDevice);
830
831 selectDifferencePolicyAndRun<OptimizedDifferencePolicy, SlowDifferencePolicy>
832 (srcColor, sp, pap);
833}
834
836{
837 KoColor srcColor(m_d->device->pixel(m_d->startPoint));
838
839 const int softness = 100 - m_d->opacitySpread;
840
841 using namespace KisColorSelectionPolicies;
842
843 CopyToSelectionPixelAccessPolicy pap(m_d->device, pixelSelection);
844
845 if (m_d->closeGap > 0) {
846 m_d->filledSelectionIterator = pixelSelection->createRandomAccessorNG();
847 }
848
849 if (softness == 0) {
850 MaskedSelectionPolicy<HardSelectionPolicy>
851 sp(HardSelectionPolicy(m_d->threshold), boundarySelection);
852 selectDifferencePolicyAndRun<OptimizedDifferencePolicy, SlowDifferencePolicy>
853 (srcColor, sp, pap);
854 } else {
855 MaskedSelectionPolicy<SoftSelectionPolicy>
856 sp(SoftSelectionPolicy(m_d->threshold, softness), boundarySelection);
857 selectDifferencePolicyAndRun<OptimizedDifferencePolicy, SlowDifferencePolicy>
858 (srcColor, sp, pap);
859 }
860}
861
863{
864 KoColor srcColor(m_d->device->pixel(m_d->startPoint));
865
866 const int softness = 100 - m_d->opacitySpread;
867
868 using namespace KisColorSelectionPolicies;
869
870 CopyToSelectionPixelAccessPolicy pap(m_d->device, pixelSelection);
871
872 if (m_d->closeGap > 0) {
873 m_d->filledSelectionIterator = pixelSelection->createRandomAccessorNG();
874 }
875
876 if (softness == 0) {
877 SelectionPolicy<HardSelectionPolicy> sp(HardSelectionPolicy(m_d->threshold));
878 selectDifferencePolicyAndRun<OptimizedDifferencePolicy, SlowDifferencePolicy>
879 (srcColor, sp, pap);
880 } else {
881 SelectionPolicy<SoftSelectionPolicy> sp(SoftSelectionPolicy(m_d->threshold, softness));
882 selectDifferencePolicyAndRun<OptimizedDifferencePolicy, SlowDifferencePolicy>
883 (srcColor, sp, pap);
884 }
885}
886
887void KisScanlineFill::fillSelectionUntilColor(KisPixelSelectionSP pixelSelection, const KoColor &boundaryColor, KisPaintDeviceSP boundarySelection)
888{
889 KoColor srcColor(boundaryColor);
890 srcColor.convertTo(m_d->device->colorSpace());
891
892 const int softness = 100 - m_d->opacitySpread;
893
894 using namespace KisColorSelectionPolicies;
895
896 CopyToSelectionPixelAccessPolicy pap(m_d->device, pixelSelection);
897
898 if (m_d->closeGap > 0) {
899 m_d->filledSelectionIterator = pixelSelection->createRandomAccessorNG();
900 }
901
902 if (softness == 0) {
903 MaskedSelectionPolicy<SelectAllUntilColorHardSelectionPolicy>
904 sp(SelectAllUntilColorHardSelectionPolicy(m_d->threshold), boundarySelection);
905 selectDifferencePolicyAndRun<OptimizedDifferencePolicy, SlowDifferencePolicy>
906 (srcColor, sp, pap);
907 } else {
908 MaskedSelectionPolicy<SelectAllUntilColorSoftSelectionPolicy>
909 sp(SelectAllUntilColorSoftSelectionPolicy(m_d->threshold, softness), boundarySelection);
910 selectDifferencePolicyAndRun<OptimizedDifferencePolicy, SlowDifferencePolicy>
911 (srcColor, sp, pap);
912 }
913}
914
916{
917 KoColor srcColor(boundaryColor);
918 srcColor.convertTo(m_d->device->colorSpace());
919
920 const int softness = 100 - m_d->opacitySpread;
921
922 using namespace KisColorSelectionPolicies;
923
924 CopyToSelectionPixelAccessPolicy pap(m_d->device, pixelSelection);
925
926 if (m_d->closeGap > 0) {
927 m_d->filledSelectionIterator = pixelSelection->createRandomAccessorNG();
928 }
929
930 if (softness == 0) {
931 SelectionPolicy<SelectAllUntilColorHardSelectionPolicy>
933 selectDifferencePolicyAndRun<OptimizedDifferencePolicy, SlowDifferencePolicy>
934 (srcColor, sp, pap);
935 } else {
936 SelectionPolicy<SelectAllUntilColorSoftSelectionPolicy>
937 sp(SelectAllUntilColorSoftSelectionPolicy(m_d->threshold, softness));
938 selectDifferencePolicyAndRun<OptimizedDifferencePolicy, SlowDifferencePolicy>
939 (srcColor, sp, pap);
940 }
941}
942
944{
945 KoColor srcColor(boundaryColor);
946 srcColor.convertTo(m_d->device->colorSpace());
947
948 const int softness = 100 - m_d->opacitySpread;
949
950 using namespace KisColorSelectionPolicies;
951
952 CopyToSelectionPixelAccessPolicy pap(m_d->device, pixelSelection);
953
954 if (m_d->closeGap > 0) {
955 m_d->filledSelectionIterator = pixelSelection->createRandomAccessorNG();
956 }
957
958 if (softness == 0) {
959 MaskedSelectionPolicy<SelectAllUntilColorHardSelectionPolicy>
960 sp(SelectAllUntilColorHardSelectionPolicy(m_d->threshold), boundarySelection);
963 (srcColor, sp, pap);
964 } else {
965 MaskedSelectionPolicy<SelectAllUntilColorSoftSelectionPolicy>
966 sp(SelectAllUntilColorSoftSelectionPolicy(m_d->threshold, softness), boundarySelection);
969 (srcColor, sp, pap);
970 }
971}
972
974{
975 KoColor srcColor(boundaryColor);
976 srcColor.convertTo(m_d->device->colorSpace());
977
978 const int softness = 100 - m_d->opacitySpread;
979
980 using namespace KisColorSelectionPolicies;
981
982 CopyToSelectionPixelAccessPolicy pap(m_d->device, pixelSelection);
983
984 if (m_d->closeGap > 0) {
985 m_d->filledSelectionIterator = pixelSelection->createRandomAccessorNG();
986 }
987
988 if (softness == 0) {
989 SelectionPolicy<SelectAllUntilColorHardSelectionPolicy>
993 (srcColor, sp, pap);
994 } else {
995 SelectionPolicy<SelectAllUntilColorSoftSelectionPolicy>
996 sp(SelectAllUntilColorSoftSelectionPolicy(m_d->threshold, softness));
999 (srcColor, sp, pap);
1000 }
1001}
1002
1004{
1005 const int pixelSize = m_d->device->pixelSize();
1006 KoColor srcColor = KoColor::createTransparent(m_d->device->colorSpace());
1007
1008 using namespace KisColorSelectionPolicies;
1009
1010 FillWithColorPixelAccessPolicy pap(m_d->device, srcColor);
1011 SelectionPolicy<HardSelectionPolicy> sp(HardSelectionPolicy(m_d->threshold));
1012
1013 if (m_d->closeGap > 0) {
1014 m_d->filledSelectionIterator = m_d->device->createRandomAccessorNG();
1015 }
1016
1017 if (pixelSize == 1) {
1019 runImpl(dp, sp, pap);
1020 } else if (pixelSize == 2) {
1022 runImpl(dp, sp, pap);
1023 } else if (pixelSize == 4) {
1025 runImpl(dp, sp, pap);
1026 } else if (pixelSize == 8) {
1028 runImpl(dp, sp, pap);
1029 } else {
1030 SlowIsNonNullDifferencePolicy dp(pixelSize);
1031 runImpl(dp, sp, pap);
1032 }
1033}
1034
1035void KisScanlineFill::fillContiguousGroup(KisPaintDeviceSP groupMapDevice, qint32 groupIndex)
1036{
1037 KIS_SAFE_ASSERT_RECOVER_RETURN(m_d->device->pixelSize() == 1);
1038 KIS_SAFE_ASSERT_RECOVER_RETURN(groupMapDevice->pixelSize() == 4);
1039
1040 const quint8 referenceValue = *m_d->device->pixel(m_d->startPoint).data();
1041
1042 using namespace KisColorSelectionPolicies;
1043
1044 GroupSplitDifferencePolicy dp(referenceValue);
1045 GroupSplitSelectionPolicy sp(m_d->threshold);
1046 GroupSplitPixelAccessPolicy pap(m_d->device, groupMapDevice, groupIndex);
1047
1048 runImpl(dp, sp, pap);
1049}
1050
1059template <typename DifferencePolicy, typename SelectionPolicy, typename PixelAccessPolicy>
1060bool KisScanlineFill::fillOpacity(DifferencePolicy &differencePolicy,
1061 SelectionPolicy &selectionPolicy,
1062 PixelAccessPolicy &pixelAccessPolicy,
1063 KisPaintDevice* const devicePtr,
1064 const QRect& rect) const
1065{
1066#if 0
1067 // These asserts affect the performance.
1068 KIS_SAFE_ASSERT_RECOVER_RETURN((m_d->boundingRect.left() == 0) && (m_d->boundingRect.top() == 0) &&
1069 "FATAL: The fill bounds must start at (0,0)");
1070 KIS_SAFE_ASSERT_RECOVER_RETURN(m_d->boundingRect.contains(rect) &&
1071 "FATAL: The rect is not fully inside the fill bounds");
1072#endif
1073 KisRandomAccessorSP accessor = devicePtr->createRandomAccessorNG();
1074
1075 const int pixelSize = m_d->device->pixelSize();
1076 bool hasOpaquePixels = false;
1077
1078 // We are relying on the knowledge of KisGapMap's paint device data organization.
1079 constexpr int OpacityOffset = 2;
1080
1081 for (int y = rect.top(); y <= rect.bottom(); ++y) {
1082 int numPixelsLeft = 0;
1083 quint8* dataPtr;
1084
1085 for (int x = rect.left(); x <= rect.right(); ++x) {
1086 if (numPixelsLeft <= 0) {
1087 pixelAccessPolicy.m_srcIt->moveTo(x, y);
1088 numPixelsLeft = pixelAccessPolicy.m_srcIt->numContiguousColumns(x) - 1;
1089 dataPtr = const_cast<quint8*>(pixelAccessPolicy.m_srcIt->rawDataConst());
1090 } else {
1091 numPixelsLeft--;
1092 dataPtr += pixelSize;
1093 }
1094
1095 const quint8 difference = differencePolicy.difference(dataPtr);
1096 const quint8 opacity = selectionPolicy.opacityFromDifference(difference, x, y);
1097
1098 if (opacity == MIN_SELECTED) {
1099 hasOpaquePixels = true;
1100
1101 accessor->moveTo(x, y);
1102 quint8* outPtr = accessor->rawData() + OpacityOffset;
1103
1104 *outPtr = opacity;
1105 }
1106 }
1107 }
1108
1109 return hasOpaquePixels;
1110}
1111
1113{
1114 KoColor srcColor(QColor(0,0,0,0), m_d->device->colorSpace());
1115 KoColor fillColor(QColor(200,200,200,200), m_d->device->colorSpace());
1116
1117 using namespace KisColorSelectionPolicies;
1118
1119 OptimizedDifferencePolicy<quint32> dp(srcColor, m_d->threshold);
1120 SelectionPolicy<HardSelectionPolicy> sp(HardSelectionPolicy(m_d->threshold));
1121 FillWithColorPixelAccessPolicy pap(m_d->device, fillColor);
1122
1123 processLine(processInterval, 1, dp, sp, pap);
1124}
1125
1130
1132{
1133 return &m_d->backwardMap;
1134}
const Params2D p
#define ALWAYS_INLINE
qreal distance(const QPointF &p1, const QPointF &p2)
bool operator>(const KoID &v1, const KoID &v2)
Definition KoID.h:113
virtual quint8 * rawData()=0
ALWAYS_INLINE quint8 opacityFromDifference(quint8 difference) const
QStack< KisFillInterval > fetchAllIntervals(int rowCorrection=0) const
bool isValid() const
static constexpr quint16 DISTANCE_INFINITE
Definition kis_gap_map.h:84
quint32 pixelSize() const
KisRandomConstAccessorSP createRandomConstAccessorNG() const
KisRandomAccessorSP createRandomAccessorNG()
virtual void moveTo(qint32 x, qint32 y)=0
virtual quint32 pixelSize() const =0
void convertTo(const KoColorSpace *cs, KoColorConversionTransformation::Intent renderingIntent, KoColorConversionTransformation::ConversionFlags conversionFlags)
Definition KoColor.cpp:136
static KoColor createTransparent(const KoColorSpace *cs)
Definition KoColor.cpp:681
const KoColorSpace * colorSpace() const
return the current colorSpace
Definition KoColor.h:82
#define KIS_SAFE_ASSERT_RECOVER(cond)
Definition kis_assert.h:126
#define KIS_SAFE_ASSERT_RECOVER_RETURN(cond)
Definition kis_assert.h:128
#define KIS_ASSERT_RECOVER_RETURN(cond)
Definition kis_assert.h:75
#define KIS_SAFE_ASSERT_RECOVER_NOOP(cond)
Definition kis_assert.h:130
#define ppVar(var)
Definition kis_debug.h:155
#define dbgImage
Definition kis_debug.h:46
const quint8 MAX_SELECTED
Definition kis_global.h:32
const quint8 MIN_SELECTED
Definition kis_global.h:33
KisSharedPtr< KisRandomAccessorNG > KisRandomAccessorSP
Definition kis_types.h:225
KisSharedPtr< KisRandomConstAccessorNG > KisRandomConstAccessorSP
Definition kis_types.h:222
void fillSelectionUntilColor(KisPixelSelectionSP pixelSelection, const KoColor &boundaryColor, KisPaintDeviceSP boundarySelection)
bool fillOpacity(DifferencePolicy &differencePolicy, SelectionPolicy &selectionPolicy, PixelAccessPolicy &pixelAccessPolicy, KisPaintDevice *const devicePtr, const QRect &rect) const
GreaterPriorityQueue< CloseGapFillPoint > closeGapQueue
void fillUntilColor(const KoColor &fillColor, const KoColor &boundaryColor)
KisFillInterval closeGapPass(DifferencePolicy &differencePolicy, SelectionPolicy &selectionPolicy, PixelAccessPolicy &pixelAccessPolicy)
void setCloseGap(int closeGap)
void processLine(KisFillInterval interval, const int rowIncrement, DifferencePolicy &differencePolicy, SelectionPolicy &selectionPolicy, PixelAccessPolicy &pixelAccessPolicy)
KisGapMapSP gapMapSp
maintains the distance and opacity maps required for the algorithm
int closeGap
try to close gaps up to this size in pixels
void runImpl(DifferencePolicy &differencePolicy, SelectionPolicy &selectionPolicy, PixelAccessPolicy &pixelAccessPolicy)
KisRandomAccessorSP filledSelectionIterator
void testingProcessLine(const KisFillInterval &processInterval)
void fill(const KoColor &fillColor)
void fillContiguousGroup(KisPaintDeviceSP groupMapDevice, qint32 groupIndex)
void fillSelection(KisPixelSelectionSP pixelSelection, KisPaintDeviceSP boundarySelection)
void selectDifferencePolicyAndRun(const KoColor &srcColor, SelectionPolicy &selectionPolicy, PixelAccessPolicy &pixelAccessPolicy)
KisScanlineFill(KisPaintDeviceSP device, const QPoint &startPoint, const QRect &boundingRect)
bool tryPushingCloseGapSeed(int x, int y, bool allowExpand)
QStack< KisFillInterval > forwardStack
KisPaintDeviceSP device
KisFillIntervalMap backwardMap
std::priority_queue< T, std::vector< T >, std::greater< T > > GreaterPriorityQueue
void fillSelectionUntilColorOrTransparent(KisPixelSelectionSP pixelSelection, const KoColor &boundaryColor, KisPaintDeviceSP boundarySelection)
void setThreshold(int threshold)
KisFillIntervalMap * testingGetBackwardIntervals() const
QVector< KisFillInterval > testingGetForwardIntervals() const
void extendedPass(KisFillInterval *currentInterval, int srcRow, bool extendRight, DifferencePolicy &differencePolicy, SelectionPolicy &selectionPolicy, PixelAccessPolicy &pixelAccessPolicy)
const QScopedPointer< Private > m_d
void setOpacitySpread(int opacitySpread)