25#include <boost/heap/fibonacci_heap.hpp>
34 bool operator() (
const QPoint &
p1,
const QPoint &
p2)
const {
35 return p1.y() <
p2.y() || (
p1.y() ==
p2.y() &&
p1.x() <
p2.x());
41 FillGroup(
int _colorIndex) : colorIndex(_colorIndex) {}
46 int positiveEdgeSize = 0;
47 int negativeEdgeSize = 0;
48 int foreignEdgeSize = 0;
50 int numFilledPixels = 0;
52 bool narrowRegion =
false;
54 int totalEdgeSize()
const {
55 return positiveEdgeSize + negativeEdgeSize + foreignEdgeSize + allyEdgeSize;
58 QMap<qint32, std::multiset<QPoint, CompareQPoints>> conflictWithGroup;
61 QMap<int, LevelData> levels;
64using GroupLevelPair = QPair<qint32, quint8>;
75struct NeighbourStaticOffset
82static NeighbourStaticOffset staticOffsets[5][4] =
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) },
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) },
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) },
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) },
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) },
121 quint8 prevDirection = FROM_NOWHERE;
125struct CompareTaskPoints {
126 bool operator()(
const TaskPoint &pt1,
const TaskPoint &pt2)
const {
128 pt1.level > pt2.level || (pt1.level == pt2.level && pt1.distance > pt2.distance);
142 while (dstIt.nextPixel() && mapIt.nextPixel()) {
143 quint8 *dstPtr = dstIt.rawData();
146 const quint8 *mapPtr = mapIt.rawDataConst();
147 *dstPtr = qMax(quint8(1), *mapPtr);
160 const QRect &boundingRect)
163 mergeHeightmapOntoStroke(stroke, heightMap, strokeRect);
167 while (dstIt.nextPixel()) {
168 quint8 *dstPtr = dstIt.rawData();
171 const QPoint pt(dstIt.x(), dstIt.y());
178 fill.setThreshold(0);
179 fill.fillContiguousGroup(groupMap, groups.size());
181 groups << FillGroup(colorIndex);
187using PointsPriorityQueue = boost::heap::fibonacci_heap<TaskPoint, boost::heap::compare<CompareTaskPoints>>;
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);
255 m_d->progressUpdater = progress;
256 m_d->heightMap = heightMap;
257 m_d->dstDevice = dst;
258 m_d->boundingRect = boundingRect;
275 for (
auto it =
m_d->keyStrokes.begin(); it !=
m_d->keyStrokes.end() - 1; ++it) {
278 if (rc.isEmpty())
continue;
286 quint8 *devPtr = devIt.
rawData();
289 if (*devPtr > 0 && *lastDevPtr > 0) {
299 if (!
m_d->heightMap)
return;
301 m_d->groups << FillGroup(-1);
303 for (
int i = 0; i <
m_d->keyStrokes.size(); i++) {
304 parseColorIntoGroups(
m_d->groups,
m_d->groupsMap,
306 i,
m_d->keyStrokes[i].dev,
313 const QRect initRect =
314 m_d->boundingRect &
m_d->groupsMap->nonDefaultPixelArea();
316 m_d->initializeQueueFromGroupMap(initRect);
317 m_d->processQueue(0);
319 if (!
m_d->progressUpdater || !
m_d->progressUpdater->interrupted()) {
323 if (cleanUpAmount > 0) {
324 m_d->cleanupForeignEdgeGroups(cleanUpAmount);
329 m_d->writeColoring();
335 return m_d->groups[group].levels[level].positiveEdgeSize;
340 return m_d->groups[group].levels[level].negativeEdgeSize;
345 return m_d->groups[group].levels[level].foreignEdgeSize;
350 return m_d->groups[group].levels[level].allyEdgeSize;
355 return m_d->groups[group].levels[level].conflictWithGroup[withGroup].size();
361 m_d->tryRemoveConflictingPlane(group, levelIndex);
363 if (!taskPoints.isEmpty()) {
364 Q_FOREACH (
const TaskPoint &pt, taskPoints) {
365 m_d->pointsQueue.push(pt);
367 m_d->processQueue(group);
369 m_d->dumpGroupMaps();
370 m_d->calcNumGroupMaps();
381 qint32 *groupPtr =
reinterpret_cast<qint32*
>(groupMapIt.
rawData());
386 pt.x = groupMapIt.
x();
387 pt.y = groupMapIt.
y();
388 pt.group = *groupPtr;
389 pt.level = *heightPtr;
402 FillGroup &currGroup,
403 FillGroup &prevGroup,
404 FillGroup::LevelData &currLevelData,
405 FillGroup::LevelData &prevLevelData,
406 const QPoint &currPt,
const QPoint &prevPt,
409 if (currGroup.colorIndex != prevGroup.colorIndex || !sameLevel) {
410 prevLevelData.foreignEdgeSize++;
411 currLevelData.foreignEdgeSize++;
414 currLevelData.conflictWithGroup[prevGroupId].insert(currPt);
415 prevLevelData.conflictWithGroup[currGroupId].insert(prevPt);
419 prevLevelData.allyEdgeSize++;
420 currLevelData.allyEdgeSize++;
428 FillGroup &currGroup,
429 FillGroup &prevGroup,
430 FillGroup::LevelData &currLevelData,
431 FillGroup::LevelData &prevLevelData,
432 const QPoint &currPt,
const QPoint &prevPt,
435 if (currGroup.colorIndex != prevGroup.colorIndex || !sameLevel) {
436 prevLevelData.foreignEdgeSize--;
437 currLevelData.foreignEdgeSize--;
440 std::multiset<QPoint, CompareQPoints> &currSet = currLevelData.conflictWithGroup[prevGroupId];
441 currSet.erase(currSet.find(currPt));
443 std::multiset<QPoint, CompareQPoints> &prevSet = prevLevelData.conflictWithGroup[currGroupId];
444 prevSet.erase(prevSet.find(prevPt));
448 prevLevelData.allyEdgeSize--;
449 currLevelData.allyEdgeSize--;
456 FillGroup::LevelData &prevLevelData,
460 Q_ASSERT(currLevel != prevLevel);
462 if (currLevel > prevLevel) {
463 currLevelData.negativeEdgeSize++;
464 prevLevelData.positiveEdgeSize++;
466 currLevelData.positiveEdgeSize++;
467 prevLevelData.negativeEdgeSize++;
472 FillGroup::LevelData &prevLevelData,
476 Q_ASSERT(currLevel != prevLevel);
478 if (currLevel > prevLevel) {
479 currLevelData.negativeEdgeSize--;
480 prevLevelData.positiveEdgeSize--;
482 currLevelData.positiveEdgeSize--;
483 prevLevelData.negativeEdgeSize--;
488 quint8 fromDirection,
int prevDistance, quint8 prevLevel,
489 qint32 prevGroupId, FillGroup &prevGroup, FillGroup::LevelData &prevLevelData,
490 qint32 prevPrevGroupId, FillGroup &prevPrevGroup,
493 if (!boundingRect.contains(currPt)) {
494 prevLevelData.positiveEdgeSize++;
496 if (prevPrevGroupId > 0) {
497 FillGroup::LevelData &prevPrevLevelData = prevPrevGroup.levels[prevLevel];
498 prevPrevLevelData.positiveEdgeSize--;
505 groupIt->moveTo(currPt.x(), currPt.y());
506 levelIt->moveTo(currPt.x(), currPt.y());
508 const qint32 currGroupId = *
reinterpret_cast<const qint32*
>(groupIt->rawDataConst());
509 const quint8 newLevel = *levelIt->rawDataConst();
511 FillGroup &currGroup = groups[currGroupId];
512 FillGroup::LevelData &currLevelData = currGroup.levels[newLevel];
514 const bool needsAddTaskPoint =
517 ((newLevel == prevLevel &&
518 currGroupId == backgroundGroupId) ||
519 (newLevel >= prevLevel &&
520 currGroup.colorIndex == backgroundGroupColor &&
521 currLevelData.narrowRegion)));
523 if (needsAddTaskPoint && !statsOnly) {
527 pt.group = prevGroupId;
529 pt.distance = newLevel == prevLevel ? prevDistance + 1 : 0;
530 pt.prevDirection = fromDirection;
532 pointsQueue.push(pt);
540 const bool isSameLevel = prevLevel == newLevel;
543 if ((!prevPrevGroupId ||
544 prevPrevGroupId == currGroupId) &&
545 prevGroupId != currGroupId) {
549 FillGroup::LevelData &currLevelData = currGroup.levels[newLevel];
552 currGroup, prevGroup,
553 currLevelData, prevLevelData,
557 }
else if (prevPrevGroupId &&
558 prevPrevGroupId != currGroupId &&
559 prevGroupId == currGroupId) {
563 FillGroup::LevelData &currLevelData = currGroup.levels[newLevel];
564 FillGroup::LevelData &prevPrevLevelData = prevPrevGroup.levels[prevLevel];
567 currGroup, prevPrevGroup,
568 currLevelData, prevPrevLevelData,
572 }
else if (prevPrevGroupId &&
573 prevPrevGroupId != currGroupId &&
574 prevGroupId != currGroupId) {
578 FillGroup::LevelData &currLevelData = currGroup.levels[newLevel];
580 FillGroup &prevPrevGroup = groups[prevPrevGroupId];
581 FillGroup::LevelData &prevPrevLevelData = prevPrevGroup.levels[prevLevel];
584 currGroup, prevPrevGroup,
585 currLevelData, prevPrevLevelData,
590 currGroup, prevGroup,
591 currLevelData, prevLevelData,
598 if (prevGroupId == currGroupId) {
601 FillGroup::LevelData &currLevelData = currGroup.levels[newLevel];
604 newLevel, prevLevel);
607 if (prevPrevGroupId == currGroupId) {
611 FillGroup::LevelData &currLevelData = currGroup.levels[newLevel];
612 FillGroup::LevelData &prevPrevLevelData = currGroup.levels[prevLevel];
615 newLevel, prevLevel);
621#include <QElapsedTimer>
625 QElapsedTimer tt; tt.start();
630 groupIt = groupsMap->createRandomAccessorNG();
632 backgroundGroupId = _backgroundGroupId;
633 backgroundGroupColor = groups[backgroundGroupId].colorIndex;
634 recolorMode = backgroundGroupId > 1;
636 totalPixelsToFill = qint64(boundingRect.width()) * boundingRect.height();
638 const int progressReportingMask = (1 << 18) - 1;
642 updateNarrowRegionMetrics();
645 while (!pointsQueue.empty()) {
646 TaskPoint pt = pointsQueue.top();
649 groupIt->moveTo(pt.x, pt.y);
650 qint32 *groupPtr =
reinterpret_cast<qint32*
>(groupIt->rawData());
652 const qint32 prevGroupId = *groupPtr;
653 FillGroup &prevGroup = groups[prevGroupId];
655 if (prevGroupId == backgroundGroupId ||
657 prevGroup.colorIndex == backgroundGroupColor)) {
659 FillGroup &currGroup = groups[pt.group];
660 FillGroup::LevelData &currLevelData = currGroup.levels[pt.level];
661 currLevelData.numFilledPixels++;
663 if (prevGroupId > 0) {
664 FillGroup::LevelData &prevLevelData = prevGroup.levels[pt.level];
665 prevLevelData.numFilledPixels--;
670 const NeighbourStaticOffset *offsets = staticOffsets[pt.prevDirection];
671 const QPoint currPt(pt.x, pt.y);
673 for (
int i = 0; i < 4; i++) {
674 const NeighbourStaticOffset &offset = offsets[i];
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,
684 *groupPtr = pt.group;
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()) {
705 backgroundGroupId = 0;
706 backgroundGroupColor = -1;
718 for (
auto it = keyStrokes.begin(); it != keyStrokes.end(); ++it) {
720 color.
convertTo(dstDevice->colorSpace());
723 const int colorPixelSize = dstDevice->pixelSize();
727 const qint32 *srcPtr =
reinterpret_cast<const qint32*
>(srcIt.
rawDataConst());
729 const int colorIndex = groups[*srcPtr].colorIndex;
730 if (colorIndex >= 0) {
731 memcpy(dstIt.
rawData(), colors[colorIndex].data(), colorPixelSize);
741 FillGroup &g = groups[group];
742 FillGroup::LevelData &l = g.levels[level];
744 for (
auto conflictIt = l.conflictWithGroup.begin(); conflictIt != l.conflictWithGroup.end(); ++conflictIt) {
746 std::vector<QPoint> uniquePoints;
747 std::unique_copy(conflictIt->begin(), conflictIt->end(), std::back_inserter(uniquePoints));
749 for (
auto pointIt = uniquePoints.begin(); pointIt != uniquePoints.end(); ++pointIt) {
753 pt.group = conflictIt.key();
766 for (qint32 i = 0; i < groups.size(); i++) {
767 FillGroup &group = groups[i];
769 for (
auto levelIt = group.levels.begin(); levelIt != group.levels.end(); ++levelIt) {
770 FillGroup::LevelData &l = levelIt.value();
772 const qreal areaToPerimeterRatio = qreal(l.numFilledPixels) / l.totalEdgeSize();
773 l.narrowRegion = areaToPerimeterRatio < 2.0;
783 for (qint32 i = 0; i < groups.size(); i++) {
784 FillGroup &group = groups[i];
786 for (
auto levelIt = group.levels.begin(); levelIt != group.levels.end(); ++levelIt) {
787 FillGroup::LevelData &l = levelIt.value();
789 for (
auto conflictIt = l.conflictWithGroup.begin(); conflictIt != l.conflictWithGroup.end(); ++conflictIt) {
790 if (!conflictIt->empty()) {
791 result.append(GroupLevelPair(i, levelIt.key()));
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>
811 const qreal foreignEdgePortionThreshold = 0.05 + 0.45 * (1.0 - qBound(0.0, cleanUpAmount, 1.0));
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];
822 sortedPairs.insert(level.totalEdgeSize(), pair);
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];
831 const int thisLength = pairIt.key();
832 const qreal thisForeignPortion = qreal(level.foreignEdgeSize) / thisLength;
834 using namespace boost::accumulators;
835 accumulator_set<int, stats<tag::count, tag::mean, tag::min>> lengthStats;
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());
844 const qreal minMetric = min(lengthStats) / qreal(thisLength);
845 const qreal meanMetric = mean(lengthStats) / qreal(thisLength);
858 if (!(thisForeignPortion > foreignEdgePortionThreshold))
continue;
860 if (minMetric > 1.0 && meanMetric > 1.2) {
864 tryRemoveConflictingPlane(groupIndex, levelIndex);
866 if (!taskPoints.isEmpty()) {
870 Q_FOREACH (
const TaskPoint &pt, taskPoints) {
871 pointsQueue.push(pt);
873 processQueue(groupIndex);
910 for (
auto it = keyStrokes.begin(); it != keyStrokes.end(); ++it) {
915 const int colorPixelSize = colorDevice->
pixelSize();
927 const qint32 *srcPtr =
reinterpret_cast<const qint32*
>(srcIt.
rawDataConst());
929 *dstGroupIt.
rawData() = quint8(*srcPtr);
930 memcpy(dstColorIt.
rawData(), colors[groups[*srcPtr].colorIndex].data(), colorPixelSize);
934 if (groups[*srcPtr].levels.contains(level)) {
935 const FillGroup::LevelData &l = groups[*srcPtr].levels[level];
937 const int edgeLength = l.totalEdgeSize();
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);
962 QSet<QPair<qint32, quint8>> groups;
966 const qint32 group = *
reinterpret_cast<const qint32*
>(groupIt.
rawDataConst());
967 const quint8 level = *
reinterpret_cast<const quint8*
>(levelIt.
rawDataConst());
969 groups.insert(qMakePair(group, level));
972 for (
auto it = groups.begin(); it != groups.end(); ++it) {
973 dumpGroupInfo(it->first, it->second);
981 FillGroup::LevelData &level = groups[groupIndex].levels[levelIndex];
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;
990 auto &c = level.conflictWithGroup;
992 for (
auto cIt = c.begin(); cIt != c.end(); ++cIt) {
993 qDebug() <<
" C" << cIt.key() << cIt.value().size();
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)
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)
#define KIS_SAFE_ASSERT_RECOVER_BREAK(cond)
#define KIS_SAFE_ASSERT_RECOVER_RETURN(cond)
#define KIS_SAFE_ASSERT_RECOVER_NOOP(cond)
#define KIS_DUMP_DEVICE_2(device, rc, suffix, prefix)
void initializeQueueFromGroupMap(const QRect &rc)
KisPaintDeviceSP groupsMap
QVector< KeyStroke > keyStrokes
void cleanupForeignEdgeGroups(qreal cleanUpAmount)
quint64 totalPixelsToFill
KisPaintDeviceSP dstDevice
KisRandomAccessorSP groupIt
PointsPriorityQueue pointsQueue
KoUpdater * progressUpdater
void updateNarrowRegionMetrics()
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()
CompareTaskPoints pointsComparator
void processQueue(qint32 _backgroundGroupId)
KisPaintDeviceSP heightMap
void dumpGroupInfo(qint32 groupIndex, quint8 levelIndex)
QVector< TaskPoint > tryRemoveConflictingPlane(qint32 group, quint8 level)
ALWAYS_INLINE void updateGroupLastDistance(FillGroup::LevelData &levelData, int distance)
QVector< FillGroup > groups
static KoColorSpaceRegistry * instance()