Krita Source Code Documentation
Loading...
Searching...
No Matches
KisWatershedWorker.cpp
Go to the documentation of this file.
1/*
2 * SPDX-FileCopyrightText: 2017 Dmitry Kazakov <dimula73@gmail.com>
3 *
4 * SPDX-License-Identifier: GPL-2.0-or-later
5 */
6
8
10#include <KoColorSpace.h>
11#include <KoColor.h>
12#include <KoAlwaysInline.h>
13#include <KoUpdater.h>
14
15#include "kis_lazy_fill_tools.h"
16
18#include "kis_paint_device.h"
19#include "kis_painter.h"
21#include "kis_scanline_fill.h"
22
24
25#include <boost/heap/fibonacci_heap.hpp>
26#include <set>
27
28using namespace KisLazyFillTools;
29
30namespace {
31
32struct CompareQPoints
33{
34 bool operator() (const QPoint &p1, const QPoint &p2) const {
35 return p1.y() < p2.y() || (p1.y() == p2.y() && p1.x() < p2.x());
36 }
37};
38
39struct FillGroup {
40 FillGroup() {}
41 FillGroup(int _colorIndex) : colorIndex(_colorIndex) {}
42
43 int colorIndex = -1;
44
45 struct LevelData {
46 int positiveEdgeSize = 0;
47 int negativeEdgeSize = 0;
48 int foreignEdgeSize = 0;
49 int allyEdgeSize = 0;
50 int numFilledPixels = 0;
51
52 bool narrowRegion = false;
53
54 int totalEdgeSize() const {
55 return positiveEdgeSize + negativeEdgeSize + foreignEdgeSize + allyEdgeSize;
56 }
57
58 QMap<qint32, std::multiset<QPoint, CompareQPoints>> conflictWithGroup;
59 };
60
61 QMap<int, LevelData> levels;
62};
63
64using GroupLevelPair = QPair<qint32, quint8>;
65
66enum PrevDirections
67{
68 FROM_NOWHERE = 0,
69 FROM_RIGHT,
70 FROM_LEFT,
71 FROM_TOP,
72 FROM_BOTTOM
73};
74
75struct NeighbourStaticOffset
76{
77 const quint8 from;
78 const bool statsOnly;
79 const QPoint offset;
80};
81
82static NeighbourStaticOffset staticOffsets[5][4] =
83{
84 { // FROM_NOWHERE
85 { FROM_RIGHT, false, QPoint(-1, 0) },
86 { FROM_LEFT, false, QPoint( 1, 0) },
87 { FROM_BOTTOM, false, QPoint( 0, -1) },
88 { FROM_TOP, false, QPoint( 0, 1) },
89 },
90 { // FROM_RIGHT
91 { FROM_RIGHT, false, QPoint(-1, 0) },
92 { FROM_LEFT, true, QPoint( 1, 0) },
93 { FROM_BOTTOM, false, QPoint( 0, -1) },
94 { FROM_TOP, false, QPoint( 0, 1) },
95 },
96 { // FROM_LEFT
97 { FROM_RIGHT, true, QPoint(-1, 0) },
98 { FROM_LEFT, false, QPoint( 1, 0) },
99 { FROM_BOTTOM, false, QPoint( 0, -1) },
100 { FROM_TOP, false, QPoint( 0, 1) },
101 },
102 { // FROM_TOP
103 { FROM_RIGHT, false, QPoint(-1, 0) },
104 { FROM_LEFT, false, QPoint( 1, 0) },
105 { FROM_BOTTOM, true, QPoint( 0, -1) },
106 { FROM_TOP, false, QPoint( 0, 1) },
107 },
108 { // FROM_BOTTOM
109 { FROM_RIGHT, false, QPoint(-1, 0) },
110 { FROM_LEFT, false, QPoint( 1, 0) },
111 { FROM_BOTTOM, false, QPoint( 0, -1) },
112 { FROM_TOP, true, QPoint( 0, 1) },
113 }
114};
115
116struct TaskPoint {
117 int x = 0;
118 int y = 0;
119 int distance = 0;
120 qint32 group = 0;
121 quint8 prevDirection = FROM_NOWHERE;
122 quint8 level = 0;
123};
124
125struct CompareTaskPoints {
126 bool operator()(const TaskPoint &pt1, const TaskPoint &pt2) const {
127 return
128 pt1.level > pt2.level || (pt1.level == pt2.level && pt1.distance > pt2.distance);
129 }
130};
131
137void mergeHeightmapOntoStroke(KisPaintDeviceSP stroke, KisPaintDeviceSP heightMap, const QRect &rc)
138{
139 KisSequentialIterator dstIt(stroke, rc);
140 KisSequentialConstIterator mapIt(heightMap, rc);
141
142 while (dstIt.nextPixel() && mapIt.nextPixel()) {
143 quint8 *dstPtr = dstIt.rawData();
144
145 if (*dstPtr > 0) {
146 const quint8 *mapPtr = mapIt.rawDataConst();
147 *dstPtr = qMax(quint8(1), *mapPtr);
148 } else {
149 *dstPtr = 0;
150 }
151
152 }
153}
154
155void parseColorIntoGroups(QVector<FillGroup> &groups,
156 KisPaintDeviceSP groupMap,
157 KisPaintDeviceSP heightMap,
158 int colorIndex,
159 KisPaintDeviceSP stroke,
160 const QRect &boundingRect)
161{
162 const QRect strokeRect = stroke->exactBounds();
163 mergeHeightmapOntoStroke(stroke, heightMap, strokeRect);
164
165 KisSequentialIterator dstIt(stroke, strokeRect);
166
167 while (dstIt.nextPixel()) {
168 quint8 *dstPtr = dstIt.rawData();
169
170 if (*dstPtr > 0) {
171 const QPoint pt(dstIt.x(), dstIt.y());
172 KisScanlineFill fill(stroke, pt, boundingRect);
178 fill.setThreshold(0);
179 fill.fillContiguousGroup(groupMap, groups.size());
180
181 groups << FillGroup(colorIndex);
182 }
183
184 }
185}
186
187using PointsPriorityQueue = boost::heap::fibonacci_heap<TaskPoint, boost::heap::compare<CompareTaskPoints>>;
188
189}
190
191/***********************************************************************/
192/* KisWatershedWorker::Private */
193/***********************************************************************/
194
196{
198
201
204
207
208 CompareTaskPoints pointsComparator;
209 PointsPriorityQueue pointsQueue;
210
211 // temporary "global" variables for the processing routines
216 bool recolorMode = false;
217
218 quint64 totalPixelsToFill = 0;
219 quint64 numFilledPixels = 0;
220
222
223 void initializeQueueFromGroupMap(const QRect &rc);
224
225 ALWAYS_INLINE void visitNeighbour(const QPoint &currPt, const QPoint &prevPt, quint8 fromDirection, int prevDistance, quint8 prevLevel, qint32 prevGroupId, FillGroup &prevGroup, FillGroup::LevelData &prevLevelData, qint32 prevPrevGroupId, FillGroup &prevPrevGroup, bool statsOnly = false);
226 ALWAYS_INLINE void updateGroupLastDistance(FillGroup::LevelData &levelData, int distance);
227 void processQueue(qint32 _backgroundGroupId);
228 void writeColoring();
229
230 QVector<TaskPoint> tryRemoveConflictingPlane(qint32 group, quint8 level);
231
233
235 void cleanupForeignEdgeGroups(qreal cleanUpAmount);
236
237 void dumpGroupMaps();
238 void calcNumGroupMaps();
239 void dumpGroupInfo(qint32 groupIndex, quint8 levelIndex);
240
241
242};
243
244/***********************************************************************/
245/* KisWatershedWorker */
246/***********************************************************************/
247
248
249
250KisWatershedWorker::KisWatershedWorker(KisPaintDeviceSP heightMap, KisPaintDeviceSP dst, const QRect &boundingRect, KoUpdater *progress)
251 : m_d(new Private)
252{
254
255 m_d->progressUpdater = progress;
256 m_d->heightMap = heightMap;
257 m_d->dstDevice = dst;
258 m_d->boundingRect = boundingRect;
259
260 // Just the simplest color space with 4 bytes per pixel. We use it as
261 // a storage for qint32-indexed group ids
262 m_d->groupsMap = new KisPaintDevice(KoColorSpaceRegistry::instance()->rgb8());
263}
264
268
270{
271 m_d->keyStrokes << KeyStroke(new KisPaintDevice(*dev), color);
272
273 KisPaintDeviceSP lastDev = m_d->keyStrokes.back().dev;
274
275 for (auto it = m_d->keyStrokes.begin(); it != m_d->keyStrokes.end() - 1; ++it) {
276 KisPaintDeviceSP dev = it->dev;
277 const QRect rc = dev->exactBounds() & lastDev->exactBounds();
278 if (rc.isEmpty()) continue;
279
280 KisSequentialIterator devIt(dev, rc);
281 KisSequentialConstIterator lastDevIt(lastDev, rc);
282
283 while (devIt.nextPixel() &&
284 lastDevIt.nextPixel()) {
285
286 quint8 *devPtr = devIt.rawData();
287 const quint8 *lastDevPtr = lastDevIt.rawDataConst();
288
289 if (*devPtr > 0 && *lastDevPtr > 0) {
290 *devPtr = 0;
291 }
292
293 }
294 }
295}
296
297void KisWatershedWorker::run(qreal cleanUpAmount)
298{
299 if (!m_d->heightMap) return;
300
301 m_d->groups << FillGroup(-1);
302
303 for (int i = 0; i < m_d->keyStrokes.size(); i++) {
304 parseColorIntoGroups(m_d->groups, m_d->groupsMap,
305 m_d->heightMap,
306 i, m_d->keyStrokes[i].dev,
307 m_d->boundingRect);
308 }
309
310// m_d->dumpGroupMaps();
311// m_d->calcNumGroupMaps();
312
313 const QRect initRect =
314 m_d->boundingRect & m_d->groupsMap->nonDefaultPixelArea();
315
316 m_d->initializeQueueFromGroupMap(initRect);
317 m_d->processQueue(0);
318
319 if (!m_d->progressUpdater || !m_d->progressUpdater->interrupted()) {
320 // m_d->dumpGroupMaps();
321 // m_d->calcNumGroupMaps();
322
323 if (cleanUpAmount > 0) {
324 m_d->cleanupForeignEdgeGroups(cleanUpAmount);
325 }
326
327 // m_d->calcNumGroupMaps();
328
329 m_d->writeColoring();
330 }
331}
332
333int KisWatershedWorker::testingGroupPositiveEdge(qint32 group, quint8 level)
334{
335 return m_d->groups[group].levels[level].positiveEdgeSize;
336}
337
338int KisWatershedWorker::testingGroupNegativeEdge(qint32 group, quint8 level)
339{
340 return m_d->groups[group].levels[level].negativeEdgeSize;
341}
342
343int KisWatershedWorker::testingGroupForeignEdge(qint32 group, quint8 level)
344{
345 return m_d->groups[group].levels[level].foreignEdgeSize;
346}
347
348int KisWatershedWorker::testingGroupAllyEdge(qint32 group, quint8 level)
349{
350 return m_d->groups[group].levels[level].allyEdgeSize;
351}
352
353int KisWatershedWorker::testingGroupConflicts(qint32 group, quint8 level, qint32 withGroup)
354{
355 return m_d->groups[group].levels[level].conflictWithGroup[withGroup].size();
356}
357
358void KisWatershedWorker::testingTryRemoveGroup(qint32 group, quint8 levelIndex)
359{
360 QVector<TaskPoint> taskPoints =
361 m_d->tryRemoveConflictingPlane(group, levelIndex);
362
363 if (!taskPoints.isEmpty()) {
364 Q_FOREACH (const TaskPoint &pt, taskPoints) {
365 m_d->pointsQueue.push(pt);
366 }
367 m_d->processQueue(group);
368 }
369 m_d->dumpGroupMaps();
370 m_d->calcNumGroupMaps();
371}
372
374{
375 KisSequentialIterator groupMapIt(groupsMap, rc);
376 KisSequentialConstIterator heightMapIt(heightMap, rc);
377
378 while (groupMapIt.nextPixel() &&
379 heightMapIt.nextPixel()) {
380
381 qint32 *groupPtr = reinterpret_cast<qint32*>(groupMapIt.rawData());
382 const quint8 *heightPtr = heightMapIt.rawDataConst();
383
384 if (*groupPtr > 0) {
385 TaskPoint pt;
386 pt.x = groupMapIt.x();
387 pt.y = groupMapIt.y();
388 pt.group = *groupPtr;
389 pt.level = *heightPtr;
390
391 pointsQueue.push(pt);
392
393 // we must clear the pixel to make sure foreign metric is calculated correctly
394 *groupPtr = 0;
395 }
396
397 }
398}
399
400ALWAYS_INLINE void addForeignAlly(qint32 currGroupId,
401 qint32 prevGroupId,
402 FillGroup &currGroup,
403 FillGroup &prevGroup,
404 FillGroup::LevelData &currLevelData,
405 FillGroup::LevelData &prevLevelData,
406 const QPoint &currPt, const QPoint &prevPt,
407 bool sameLevel)
408{
409 if (currGroup.colorIndex != prevGroup.colorIndex || !sameLevel) {
410 prevLevelData.foreignEdgeSize++;
411 currLevelData.foreignEdgeSize++;
412
413 if (sameLevel) {
414 currLevelData.conflictWithGroup[prevGroupId].insert(currPt);
415 prevLevelData.conflictWithGroup[currGroupId].insert(prevPt);
416 }
417
418 } else {
419 prevLevelData.allyEdgeSize++;
420 currLevelData.allyEdgeSize++;
421 }
422
423
424}
425
426ALWAYS_INLINE void removeForeignAlly(qint32 currGroupId,
427 qint32 prevGroupId,
428 FillGroup &currGroup,
429 FillGroup &prevGroup,
430 FillGroup::LevelData &currLevelData,
431 FillGroup::LevelData &prevLevelData,
432 const QPoint &currPt, const QPoint &prevPt,
433 bool sameLevel)
434{
435 if (currGroup.colorIndex != prevGroup.colorIndex || !sameLevel) {
436 prevLevelData.foreignEdgeSize--;
437 currLevelData.foreignEdgeSize--;
438
439 if (sameLevel) {
440 std::multiset<QPoint, CompareQPoints> &currSet = currLevelData.conflictWithGroup[prevGroupId];
441 currSet.erase(currSet.find(currPt));
442
443 std::multiset<QPoint, CompareQPoints> &prevSet = prevLevelData.conflictWithGroup[currGroupId];
444 prevSet.erase(prevSet.find(prevPt));
445 }
446
447 } else {
448 prevLevelData.allyEdgeSize--;
449 currLevelData.allyEdgeSize--;
450 }
451
452
453}
454
455ALWAYS_INLINE void incrementLevelEdge(FillGroup::LevelData &currLevelData,
456 FillGroup::LevelData &prevLevelData,
457 quint8 currLevel,
458 quint8 prevLevel)
459{
460 Q_ASSERT(currLevel != prevLevel);
461
462 if (currLevel > prevLevel) {
463 currLevelData.negativeEdgeSize++;
464 prevLevelData.positiveEdgeSize++;
465 } else {
466 currLevelData.positiveEdgeSize++;
467 prevLevelData.negativeEdgeSize++;
468 }
469}
470
471ALWAYS_INLINE void decrementLevelEdge(FillGroup::LevelData &currLevelData,
472 FillGroup::LevelData &prevLevelData,
473 quint8 currLevel,
474 quint8 prevLevel)
475{
476 Q_ASSERT(currLevel != prevLevel);
477
478 if (currLevel > prevLevel) {
479 currLevelData.negativeEdgeSize--;
480 prevLevelData.positiveEdgeSize--;
481 } else {
482 currLevelData.positiveEdgeSize--;
483 prevLevelData.negativeEdgeSize--;
484 }
485}
486
487void KisWatershedWorker::Private::visitNeighbour(const QPoint &currPt, const QPoint &prevPt,
488 quint8 fromDirection, int prevDistance, quint8 prevLevel,
489 qint32 prevGroupId, FillGroup &prevGroup, FillGroup::LevelData &prevLevelData,
490 qint32 prevPrevGroupId, FillGroup &prevPrevGroup,
491 bool statsOnly)
492{
493 if (!boundingRect.contains(currPt)) {
494 prevLevelData.positiveEdgeSize++;
495
496 if (prevPrevGroupId > 0) {
497 FillGroup::LevelData &prevPrevLevelData = prevPrevGroup.levels[prevLevel];
498 prevPrevLevelData.positiveEdgeSize--;
499 }
500 return;
501 }
502
503 KIS_SAFE_ASSERT_RECOVER_RETURN(prevGroupId != backgroundGroupId);
504
505 groupIt->moveTo(currPt.x(), currPt.y());
506 levelIt->moveTo(currPt.x(), currPt.y());
507
508 const qint32 currGroupId = *reinterpret_cast<const qint32*>(groupIt->rawDataConst());
509 const quint8 newLevel = *levelIt->rawDataConst();
510
511 FillGroup &currGroup = groups[currGroupId];
512 FillGroup::LevelData &currLevelData = currGroup.levels[newLevel];
513
514 const bool needsAddTaskPoint =
515 !currGroupId ||
516 (recolorMode &&
517 ((newLevel == prevLevel &&
518 currGroupId == backgroundGroupId) ||
519 (newLevel >= prevLevel &&
520 currGroup.colorIndex == backgroundGroupColor &&
521 currLevelData.narrowRegion)));
522
523 if (needsAddTaskPoint && !statsOnly) {
524 TaskPoint pt;
525 pt.x = currPt.x();
526 pt.y = currPt.y();
527 pt.group = prevGroupId;
528 pt.level = newLevel;
529 pt.distance = newLevel == prevLevel ? prevDistance + 1 : 0;
530 pt.prevDirection = fromDirection;
531
532 pointsQueue.push(pt);
533 }
534
535 // we can never clear the pixel!
536 KIS_SAFE_ASSERT_RECOVER_RETURN(prevGroupId > 0);
537 KIS_SAFE_ASSERT_RECOVER_RETURN(prevGroupId != prevPrevGroupId);
538
539 if (currGroupId) {
540 const bool isSameLevel = prevLevel == newLevel;
541
542
543 if ((!prevPrevGroupId ||
544 prevPrevGroupId == currGroupId) &&
545 prevGroupId != currGroupId) {
546
547 // we have added a foreign/ally group
548
549 FillGroup::LevelData &currLevelData = currGroup.levels[newLevel];
550
551 addForeignAlly(currGroupId, prevGroupId,
552 currGroup, prevGroup,
553 currLevelData, prevLevelData,
554 currPt, prevPt,
555 isSameLevel);
556
557 } else if (prevPrevGroupId &&
558 prevPrevGroupId != currGroupId &&
559 prevGroupId == currGroupId) {
560
561 // we have removed a foreign/ally group
562
563 FillGroup::LevelData &currLevelData = currGroup.levels[newLevel];
564 FillGroup::LevelData &prevPrevLevelData = prevPrevGroup.levels[prevLevel];
565
566 removeForeignAlly(currGroupId, prevPrevGroupId,
567 currGroup, prevPrevGroup,
568 currLevelData, prevPrevLevelData,
569 currPt, prevPt,
570 isSameLevel);
571
572 } else if (prevPrevGroupId &&
573 prevPrevGroupId != currGroupId &&
574 prevGroupId != currGroupId) {
575
576 // this pixel has become an foreign/ally pixel of a different group
577
578 FillGroup::LevelData &currLevelData = currGroup.levels[newLevel];
579
580 FillGroup &prevPrevGroup = groups[prevPrevGroupId];
581 FillGroup::LevelData &prevPrevLevelData = prevPrevGroup.levels[prevLevel];
582
583 removeForeignAlly(currGroupId, prevPrevGroupId,
584 currGroup, prevPrevGroup,
585 currLevelData, prevPrevLevelData,
586 currPt, prevPt,
587 isSameLevel);
588
589 addForeignAlly(currGroupId, prevGroupId,
590 currGroup, prevGroup,
591 currLevelData, prevLevelData,
592 currPt, prevPt,
593 isSameLevel);
594 }
595
596 if (!isSameLevel) {
597
598 if (prevGroupId == currGroupId) {
599 // we connected with our own disjoint area
600
601 FillGroup::LevelData &currLevelData = currGroup.levels[newLevel];
602
603 incrementLevelEdge(currLevelData, prevLevelData,
604 newLevel, prevLevel);
605 }
606
607 if (prevPrevGroupId == currGroupId) {
608 // we removed a pixel for the borderline
609 // (now it is registered as foreign/ally pixel)
610
611 FillGroup::LevelData &currLevelData = currGroup.levels[newLevel];
612 FillGroup::LevelData &prevPrevLevelData = currGroup.levels[prevLevel];
613
614 decrementLevelEdge(currLevelData, prevPrevLevelData,
615 newLevel, prevLevel);
616 }
617 }
618 }
619}
620
621#include <QElapsedTimer>
622
623void KisWatershedWorker::Private::processQueue(qint32 _backgroundGroupId)
624{
625 QElapsedTimer tt; tt.start();
626
627
628 // TODO: lazy initialization of the iterator's position
629 // TODO: reuse iterators if possible!
630 groupIt = groupsMap->createRandomAccessorNG();
631 levelIt = heightMap->createRandomConstAccessorNG();
632 backgroundGroupId = _backgroundGroupId;
633 backgroundGroupColor = groups[backgroundGroupId].colorIndex;
634 recolorMode = backgroundGroupId > 1;
635
636 totalPixelsToFill = qint64(boundingRect.width()) * boundingRect.height();
637 numFilledPixels = 0;
638 const int progressReportingMask = (1 << 18) - 1; // report every 512x512 patch
639
640
641 if (recolorMode) {
642 updateNarrowRegionMetrics();
643 }
644
645 while (!pointsQueue.empty()) {
646 TaskPoint pt = pointsQueue.top();
647 pointsQueue.pop();
648
649 groupIt->moveTo(pt.x, pt.y);
650 qint32 *groupPtr = reinterpret_cast<qint32*>(groupIt->rawData());
651
652 const qint32 prevGroupId = *groupPtr;
653 FillGroup &prevGroup = groups[prevGroupId];
654
655 if (prevGroupId == backgroundGroupId ||
656 (recolorMode &&
657 prevGroup.colorIndex == backgroundGroupColor)) {
658
659 FillGroup &currGroup = groups[pt.group];
660 FillGroup::LevelData &currLevelData = currGroup.levels[pt.level];
661 currLevelData.numFilledPixels++;
662
663 if (prevGroupId > 0) {
664 FillGroup::LevelData &prevLevelData = prevGroup.levels[pt.level];
665 prevLevelData.numFilledPixels--;
666 } else {
667 numFilledPixels++;
668 }
669
670 const NeighbourStaticOffset *offsets = staticOffsets[pt.prevDirection];
671 const QPoint currPt(pt.x, pt.y);
672
673 for (int i = 0; i < 4; i++) {
674 const NeighbourStaticOffset &offset = offsets[i];
675
676 const QPoint nextPt = currPt + offset.offset;
677 visitNeighbour(nextPt, currPt,
678 offset.from, pt.distance, pt.level,
679 pt.group, currGroup, currLevelData,
680 prevGroupId, prevGroup,
681 offset.statsOnly);
682 }
683
684 *groupPtr = pt.group;
685
686 if (progressUpdater && !(numFilledPixels & progressReportingMask)) {
687 const int progressPercent =
688 qBound(0, qRound(100.0 * numFilledPixels / totalPixelsToFill), 100);
689 progressUpdater->setProgress(progressPercent);
690 if (progressUpdater->interrupted()) {
691 break;
692 }
693
694 }
695
696 } else {
697 // nothing to do?
698 }
699
700 }
701
702 // cleanup iterators
703 groupIt.clear();
704 levelIt.clear();
705 backgroundGroupId = 0;
706 backgroundGroupColor = -1;
707 recolorMode = false;
708
709// ENTER_FUNCTION() << ppVar(tt.elapsed());
710}
711
713{
714 KisSequentialConstIterator srcIt(groupsMap, boundingRect);
715 KisSequentialIterator dstIt(dstDevice, boundingRect);
716
717 QVector<KoColor> colors;
718 for (auto it = keyStrokes.begin(); it != keyStrokes.end(); ++it) {
719 KoColor color = it->color;
720 color.convertTo(dstDevice->colorSpace());
721 colors << color;
722 }
723 const int colorPixelSize = dstDevice->pixelSize();
724
725
726 while (srcIt.nextPixel() && dstIt.nextPixel()) {
727 const qint32 *srcPtr = reinterpret_cast<const qint32*>(srcIt.rawDataConst());
728
729 const int colorIndex = groups[*srcPtr].colorIndex;
730 if (colorIndex >= 0) {
731 memcpy(dstIt.rawData(), colors[colorIndex].data(), colorPixelSize);
732 }
733
734 }
735}
736
738{
739 QVector<TaskPoint> result;
740
741 FillGroup &g = groups[group];
742 FillGroup::LevelData &l = g.levels[level];
743
744 for (auto conflictIt = l.conflictWithGroup.begin(); conflictIt != l.conflictWithGroup.end(); ++conflictIt) {
745
746 std::vector<QPoint> uniquePoints;
747 std::unique_copy(conflictIt->begin(), conflictIt->end(), std::back_inserter(uniquePoints));
748
749 for (auto pointIt = uniquePoints.begin(); pointIt != uniquePoints.end(); ++pointIt) {
750 TaskPoint pt;
751 pt.x = pointIt->x();
752 pt.y = pointIt->y();
753 pt.group = conflictIt.key();
754 pt.level = level;
755
756 result.append(pt);
757 // no writing to the group map!
758 }
759 }
760
761 return result;
762}
763
765{
766 for (qint32 i = 0; i < groups.size(); i++) {
767 FillGroup &group = groups[i];
768
769 for (auto levelIt = group.levels.begin(); levelIt != group.levels.end(); ++levelIt) {
770 FillGroup::LevelData &l = levelIt.value();
771
772 const qreal areaToPerimeterRatio = qreal(l.numFilledPixels) / l.totalEdgeSize();
773 l.narrowRegion = areaToPerimeterRatio < 2.0;
774 }
775 }
776}
777
779{
781
782
783 for (qint32 i = 0; i < groups.size(); i++) {
784 FillGroup &group = groups[i];
785
786 for (auto levelIt = group.levels.begin(); levelIt != group.levels.end(); ++levelIt) {
787 FillGroup::LevelData &l = levelIt.value();
788
789 for (auto conflictIt = l.conflictWithGroup.begin(); conflictIt != l.conflictWithGroup.end(); ++conflictIt) {
790 if (!conflictIt->empty()) {
791 result.append(GroupLevelPair(i, levelIt.key()));
792 break;
793 }
794 }
795 }
796 }
797
798 return result;
799}
800
801#include <boost/accumulators/accumulators.hpp>
802#include <boost/accumulators/statistics/stats.hpp>
803#include <boost/accumulators/statistics/mean.hpp>
804#include <boost/accumulators/statistics/min.hpp>
805
807{
808 KIS_SAFE_ASSERT_RECOVER_NOOP(cleanUpAmount > 0.0);
809
810 // convert into the threshold range [0.05...0.5]
811 const qreal foreignEdgePortionThreshold = 0.05 + 0.45 * (1.0 - qBound(0.0, cleanUpAmount, 1.0));
812
813 QVector<GroupLevelPair> conflicts = calculateConflictingPairs();
814
815 // sort the pairs by the total edge size
816 QMap<qreal, GroupLevelPair> sortedPairs;
817 Q_FOREACH (const GroupLevelPair &pair, conflicts) {
818 const qint32 groupIndex = pair.first;
819 const quint8 levelIndex = pair.second;
820 FillGroup::LevelData &level = groups[groupIndex].levels[levelIndex];
821
822 sortedPairs.insert(level.totalEdgeSize(), pair);
823 }
824
825 // remove sequentially from the smallest to the biggest
826 for (auto pairIt = sortedPairs.begin(); pairIt != sortedPairs.end(); ++pairIt) {
827 const qint32 groupIndex = pairIt->first;
828 const quint8 levelIndex = pairIt->second;
829 FillGroup::LevelData &level = groups[groupIndex].levels[levelIndex];
830
831 const int thisLength = pairIt.key();
832 const qreal thisForeignPortion = qreal(level.foreignEdgeSize) / thisLength;
833
834 using namespace boost::accumulators;
835 accumulator_set<int, stats<tag::count, tag::mean, tag::min>> lengthStats;
836
837 for (auto it = level.conflictWithGroup.begin(); it != level.conflictWithGroup.end(); ++it) {
838 const FillGroup::LevelData &otherLevel = groups[it.key()].levels[levelIndex];
839 lengthStats(otherLevel.totalEdgeSize());
840 }
841
842 KIS_SAFE_ASSERT_RECOVER_BREAK(count(lengthStats));
843
844 const qreal minMetric = min(lengthStats) / qreal(thisLength);
845 const qreal meanMetric = mean(lengthStats) / qreal(thisLength);
846
847// qDebug() << "G" << groupIndex
848// << "L" << levelIndex
849// << "con" << level.conflictWithGroup.size()
850// << "FRP" << thisForeignPortion
851// << "S" << level.numFilledPixels
852// << ppVar(thisLength)
853// << ppVar(min(lengthStats))
854// << ppVar(mean(lengthStats))
855// << ppVar(minMetric)
856// << ppVar(meanMetric);
857
858 if (!(thisForeignPortion > foreignEdgePortionThreshold)) continue;
859
860 if (minMetric > 1.0 && meanMetric > 1.2) {
861// qDebug() << " * removing...";
862
863 QVector<TaskPoint> taskPoints =
864 tryRemoveConflictingPlane(groupIndex, levelIndex);
865
866 if (!taskPoints.isEmpty()) {
867 // dump before
868 // dumpGroupInfo(groupIndex, levelIndex);
869
870 Q_FOREACH (const TaskPoint &pt, taskPoints) {
871 pointsQueue.push(pt);
872 }
873 processQueue(groupIndex);
874
875 // dump after: should become empty!
876 // dumpGroupInfo(groupIndex, levelIndex);
877
878 // the areas might be disjoint, so that removing one "conflicting"
879 // part will not remove the whole group+level pair
880
881 // KIS_SAFE_ASSERT_RECOVER_NOOP(level.totalEdgeSize() == 0);
882 }
883
884 //dumpGroupMaps();
885 //calcNumGroupMaps();
886
887 }
888 }
889
890}
891
893{
899
900 KisSequentialConstIterator heightIt(heightMap, boundingRect);
901 KisSequentialConstIterator srcIt(groupsMap, boundingRect);
902 KisSequentialIterator dstGroupIt(groupDevice, boundingRect);
903 KisSequentialIterator dstColorIt(colorDevice, boundingRect);
904 KisSequentialIterator dstPedgeIt(pedgeDevice, boundingRect);
905 KisSequentialIterator dstNedgeIt(nedgeDevice, boundingRect);
906 KisSequentialIterator dstFedgeIt(fedgeDevice, boundingRect);
907
908
909 QVector<KoColor> colors;
910 for (auto it = keyStrokes.begin(); it != keyStrokes.end(); ++it) {
911 KoColor color = it->color;
912 color.convertTo(colorDevice->colorSpace());
913 colors << color;
914 }
915 const int colorPixelSize = colorDevice->pixelSize();
916
917
918
919 while (dstGroupIt.nextPixel() &&
920 heightIt.nextPixel() &&
921 srcIt.nextPixel() &&
922 dstColorIt.nextPixel() &&
923 dstPedgeIt.nextPixel() &&
924 dstNedgeIt.nextPixel() &&
925 dstFedgeIt.nextPixel()) {
926
927 const qint32 *srcPtr = reinterpret_cast<const qint32*>(srcIt.rawDataConst());
928
929 *dstGroupIt.rawData() = quint8(*srcPtr);
930 memcpy(dstColorIt.rawData(), colors[groups[*srcPtr].colorIndex].data(), colorPixelSize);
931
932 quint8 level = *heightIt.rawDataConst();
933
934 if (groups[*srcPtr].levels.contains(level)) {
935 const FillGroup::LevelData &l = groups[*srcPtr].levels[level];
936
937 const int edgeLength = l.totalEdgeSize();
938
939 *dstPedgeIt.rawData() = 255.0 * qreal(l.positiveEdgeSize) / (edgeLength);
940 *dstNedgeIt.rawData() = 255.0 * qreal(l.negativeEdgeSize) / (edgeLength);
941 *dstFedgeIt.rawData() = 255.0 * qreal(l.foreignEdgeSize) / (edgeLength);
942 } else {
943 *dstPedgeIt.rawData() = 0;
944 *dstNedgeIt.rawData() = 0;
945 *dstFedgeIt.rawData() = 0;
946 }
947 }
948
949
950 KIS_DUMP_DEVICE_2(groupDevice, boundingRect, "01_groupMap", "dd");
951 KIS_DUMP_DEVICE_2(colorDevice, boundingRect, "02_colorMap", "dd");
952 KIS_DUMP_DEVICE_2(pedgeDevice, boundingRect, "03_pedgeMap", "dd");
953 KIS_DUMP_DEVICE_2(nedgeDevice, boundingRect, "04_nedgeMap", "dd");
954 KIS_DUMP_DEVICE_2(fedgeDevice, boundingRect, "05_fedgeMap", "dd");
955}
956
958{
959 KisSequentialConstIterator groupIt(groupsMap, boundingRect);
960 KisSequentialConstIterator levelIt(heightMap, boundingRect);
961
962 QSet<QPair<qint32, quint8>> groups;
963
964 while (groupIt.nextPixel() && levelIt.nextPixel()) {
965
966 const qint32 group = *reinterpret_cast<const qint32*>(groupIt.rawDataConst());
967 const quint8 level = *reinterpret_cast<const quint8*>(levelIt.rawDataConst());
968
969 groups.insert(qMakePair(group, level));
970 }
971
972 for (auto it = groups.begin(); it != groups.end(); ++it) {
973 dumpGroupInfo(it->first, it->second);
974 }
975
976 ENTER_FUNCTION() << ppVar(groups.size());
977}
978
979void KisWatershedWorker::Private::dumpGroupInfo(qint32 groupIndex, quint8 levelIndex)
980{
981 FillGroup::LevelData &level = groups[groupIndex].levels[levelIndex];
982
983 qDebug() << "G" << groupIndex << "L" << levelIndex << "CI" << this->groups[groupIndex].colorIndex;
984 qDebug() << " P" << level.positiveEdgeSize;
985 qDebug() << " N" << level.negativeEdgeSize;
986 qDebug() << " F" << level.foreignEdgeSize;
987 qDebug() << " A" << level.allyEdgeSize;
988 qDebug() << " (S)" << level.numFilledPixels;
989
990 auto &c = level.conflictWithGroup;
991
992 for (auto cIt = c.begin(); cIt != c.end(); ++cIt) {
993 qDebug() << " C" << cIt.key() << cIt.value().size();
994 }
995}
QPointF p2
QPointF p1
ALWAYS_INLINE void removeForeignAlly(qint32 currGroupId, qint32 prevGroupId, FillGroup &currGroup, FillGroup &prevGroup, FillGroup::LevelData &currLevelData, FillGroup::LevelData &prevLevelData, const QPoint &currPt, const QPoint &prevPt, bool sameLevel)
ALWAYS_INLINE void addForeignAlly(qint32 currGroupId, qint32 prevGroupId, FillGroup &currGroup, FillGroup &prevGroup, FillGroup::LevelData &currLevelData, FillGroup::LevelData &prevLevelData, const QPoint &currPt, const QPoint &prevPt, bool sameLevel)
ALWAYS_INLINE void decrementLevelEdge(FillGroup::LevelData &currLevelData, FillGroup::LevelData &prevLevelData, quint8 currLevel, quint8 prevLevel)
ALWAYS_INLINE void incrementLevelEdge(FillGroup::LevelData &currLevelData, FillGroup::LevelData &prevLevelData, quint8 currLevel, quint8 prevLevel)
#define ALWAYS_INLINE
qreal distance(const QPointF &p1, const QPointF &p2)
quint32 pixelSize() const
KisRandomConstAccessorSP createRandomConstAccessorNG() const
QRect exactBounds() const
const KoColorSpace * colorSpace() const
ALWAYS_INLINE quint8 * rawData()
ALWAYS_INLINE int x() const
ALWAYS_INLINE int y() const
ALWAYS_INLINE const quint8 * rawDataConst() const
int testingGroupPositiveEdge(qint32 group, quint8 level)
KisWatershedWorker(KisPaintDeviceSP heightMap, KisPaintDeviceSP dst, const QRect &boundingRect, KoUpdater *progress=0)
void testingTryRemoveGroup(qint32 group, quint8 level)
int testingGroupConflicts(qint32 group, quint8 level, qint32 withGroup)
void run(qreal cleanUpAmount=0.0)
run the filling process using the passes height map, strokes, and write the result coloring into the ...
int testingGroupAllyEdge(qint32 group, quint8 level)
int testingGroupNegativeEdge(qint32 group, quint8 level)
int testingGroupForeignEdge(qint32 group, quint8 level)
const QScopedPointer< Private > m_d
void addKeyStroke(KisPaintDeviceSP dev, const KoColor &color)
Adds a key stroke to the worker.
virtual quint32 pixelSize() const =0
void convertTo(const KoColorSpace *cs, KoColorConversionTransformation::Intent renderingIntent, KoColorConversionTransformation::ConversionFlags conversionFlags)
Definition KoColor.cpp:136
#define KIS_SAFE_ASSERT_RECOVER_BREAK(cond)
Definition kis_assert.h:127
#define KIS_SAFE_ASSERT_RECOVER_RETURN(cond)
Definition kis_assert.h:128
#define KIS_SAFE_ASSERT_RECOVER_NOOP(cond)
Definition kis_assert.h:130
#define ENTER_FUNCTION()
Definition kis_debug.h:178
#define ppVar(var)
Definition kis_debug.h:155
#define KIS_DUMP_DEVICE_2(device, rc, suffix, prefix)
void initializeQueueFromGroupMap(const QRect &rc)
void cleanupForeignEdgeGroups(qreal cleanUpAmount)
KisRandomConstAccessorSP levelIt
ALWAYS_INLINE void visitNeighbour(const QPoint &currPt, const QPoint &prevPt, quint8 fromDirection, int prevDistance, quint8 prevLevel, qint32 prevGroupId, FillGroup &prevGroup, FillGroup::LevelData &prevLevelData, qint32 prevPrevGroupId, FillGroup &prevPrevGroup, bool statsOnly=false)
QVector< GroupLevelPair > calculateConflictingPairs()
void processQueue(qint32 _backgroundGroupId)
void dumpGroupInfo(qint32 groupIndex, quint8 levelIndex)
QVector< TaskPoint > tryRemoveConflictingPlane(qint32 group, quint8 level)
ALWAYS_INLINE void updateGroupLastDistance(FillGroup::LevelData &levelData, int distance)
static KoColorSpaceRegistry * instance()