File indexing completed on 2024-06-09 04:22:00

0001 /*
0002  *  SPDX-FileCopyrightText: 2017 Dmitry Kazakov <dimula73@gmail.com>
0003  *
0004  *  SPDX-License-Identifier: GPL-2.0-or-later
0005  */
0006 
0007 #include "KisWatershedWorker.h"
0008 
0009 #include <KoColorSpaceRegistry.h>
0010 #include <KoColorSpace.h>
0011 #include <KoColor.h>
0012 #include <KoAlwaysInline.h>
0013 #include <KoUpdater.h>
0014 
0015 #include "kis_lazy_fill_tools.h"
0016 
0017 #include "kis_paint_device_debug_utils.h"
0018 #include "kis_paint_device.h"
0019 #include "kis_painter.h"
0020 #include "kis_sequential_iterator.h"
0021 #include "kis_scanline_fill.h"
0022 
0023 #include "kis_random_accessor_ng.h"
0024 
0025 #include <boost/heap/fibonacci_heap.hpp>
0026 #include <set>
0027 
0028 using namespace KisLazyFillTools;
0029 
0030 namespace {
0031 
0032 struct CompareQPoints
0033 {
0034     bool operator() (const QPoint &p1, const QPoint &p2) const {
0035         return p1.y() < p2.y() || (p1.y() == p2.y() && p1.x() < p2.x());
0036     }
0037 };
0038 
0039 struct FillGroup {
0040     FillGroup() {}
0041     FillGroup(int _colorIndex) : colorIndex(_colorIndex) {}
0042 
0043     int colorIndex = -1;
0044 
0045     struct LevelData {
0046         int positiveEdgeSize = 0;
0047         int negativeEdgeSize = 0;
0048         int foreignEdgeSize = 0;
0049         int allyEdgeSize = 0;
0050         int numFilledPixels = 0;
0051 
0052         bool narrowRegion = false;
0053 
0054         int totalEdgeSize() const {
0055             return positiveEdgeSize + negativeEdgeSize + foreignEdgeSize + allyEdgeSize;
0056         }
0057 
0058         QMap<qint32, std::multiset<QPoint, CompareQPoints>> conflictWithGroup;
0059     };
0060 
0061     QMap<int, LevelData> levels;
0062 };
0063 
0064 using GroupLevelPair = QPair<qint32, quint8>;
0065 
0066 enum PrevDirections
0067 {
0068     FROM_NOWHERE = 0,
0069     FROM_RIGHT,
0070     FROM_LEFT,
0071     FROM_TOP,
0072     FROM_BOTTOM
0073 };
0074 
0075 struct NeighbourStaticOffset
0076 {
0077     const quint8 from;
0078     const bool statsOnly;
0079     const QPoint offset;
0080 };
0081 
0082 static NeighbourStaticOffset staticOffsets[5][4] =
0083 {
0084     { // FROM_NOWHERE
0085         { FROM_RIGHT,  false, QPoint(-1,  0) },
0086         { FROM_LEFT,   false, QPoint( 1,  0) },
0087         { FROM_BOTTOM, false, QPoint( 0, -1) },
0088         { FROM_TOP,    false, QPoint( 0,  1) },
0089     },
0090     { // FROM_RIGHT
0091         { FROM_RIGHT,  false, QPoint(-1,  0) },
0092         { FROM_LEFT,   true,  QPoint( 1,  0) },
0093         { FROM_BOTTOM, false, QPoint( 0, -1) },
0094         { FROM_TOP,    false, QPoint( 0,  1) },
0095     },
0096     { // FROM_LEFT
0097         { FROM_RIGHT,  true,  QPoint(-1,  0) },
0098         { FROM_LEFT,   false, QPoint( 1,  0) },
0099         { FROM_BOTTOM, false, QPoint( 0, -1) },
0100         { FROM_TOP,    false, QPoint( 0,  1) },
0101     },
0102     { // FROM_TOP
0103         { FROM_RIGHT,  false, QPoint(-1,  0) },
0104         { FROM_LEFT,   false, QPoint( 1,  0) },
0105         { FROM_BOTTOM, true,  QPoint( 0, -1) },
0106         { FROM_TOP,    false, QPoint( 0,  1) },
0107     },
0108     { // FROM_BOTTOM
0109         { FROM_RIGHT,  false, QPoint(-1,  0) },
0110         { FROM_LEFT,   false, QPoint( 1,  0) },
0111         { FROM_BOTTOM, false, QPoint( 0, -1) },
0112         { FROM_TOP,    true,  QPoint( 0,  1) },
0113     }
0114 };
0115 
0116 struct TaskPoint {
0117     int x = 0;
0118     int y = 0;
0119     int distance = 0;
0120     qint32 group = 0;
0121     quint8 prevDirection = FROM_NOWHERE;
0122     quint8 level = 0;
0123 };
0124 
0125 struct CompareTaskPoints {
0126     bool operator()(const TaskPoint &pt1, const TaskPoint &pt2) const {
0127         return
0128             pt1.level > pt2.level || (pt1.level == pt2.level && pt1.distance > pt2.distance);
0129     }
0130 };
0131 
0132 /**
0133  * Adjusts the stroke device in a way that all the stroke's pixels
0134  * are set to the range 1...255, according to the height of this pixel
0135  * in the heightmap. The pixels not having any stroke have value 0
0136  */
0137 void mergeHeightmapOntoStroke(KisPaintDeviceSP stroke, KisPaintDeviceSP heightMap, const QRect &rc)
0138 {
0139     KisSequentialIterator dstIt(stroke, rc);
0140     KisSequentialConstIterator mapIt(heightMap, rc);
0141 
0142     while (dstIt.nextPixel() && mapIt.nextPixel()) {
0143         quint8 *dstPtr = dstIt.rawData();
0144 
0145         if (*dstPtr > 0) {
0146             const quint8 *mapPtr = mapIt.rawDataConst();
0147             *dstPtr = qMax(quint8(1), *mapPtr);
0148         } else {
0149             *dstPtr = 0;
0150         }
0151 
0152     }
0153 }
0154 
0155 void parseColorIntoGroups(QVector<FillGroup> &groups,
0156                           KisPaintDeviceSP groupMap,
0157                           KisPaintDeviceSP heightMap,
0158                           int colorIndex,
0159                           KisPaintDeviceSP stroke,
0160                           const QRect &boundingRect)
0161 {
0162     const QRect strokeRect = stroke->exactBounds();
0163     mergeHeightmapOntoStroke(stroke, heightMap, strokeRect);
0164 
0165     KisSequentialIterator dstIt(stroke, strokeRect);
0166 
0167     while (dstIt.nextPixel()) {
0168         quint8 *dstPtr = dstIt.rawData();
0169 
0170         if (*dstPtr > 0) {
0171             const QPoint pt(dstIt.x(), dstIt.y());
0172             KisScanlineFill fill(stroke, pt, boundingRect);
0173             /**
0174              * The threshold is set explicitly. If you want to raise it,
0175              * don't forget to add a distinction between 0 and >0 in
0176              * the fill strategy. Otherwise the algorithm will not work.
0177              */
0178             fill.setThreshold(0);
0179             fill.fillContiguousGroup(groupMap, groups.size());
0180 
0181             groups << FillGroup(colorIndex);
0182         }
0183 
0184     }
0185 }
0186 
0187 using PointsPriorityQueue = boost::heap::fibonacci_heap<TaskPoint, boost::heap::compare<CompareTaskPoints>>;
0188 
0189 }
0190 
0191 /***********************************************************************/
0192 /*           KisWatershedWorker::Private                               */
0193 /***********************************************************************/
0194 
0195 struct KisWatershedWorker::Private
0196 {
0197     Private() : pointsQueue(pointsComparator) {}
0198 
0199     KisPaintDeviceSP heightMap;
0200     KisPaintDeviceSP dstDevice;
0201 
0202     QRect boundingRect;
0203     QVector<KeyStroke> keyStrokes;
0204 
0205     QVector<FillGroup> groups;
0206     KisPaintDeviceSP groupsMap;
0207 
0208     CompareTaskPoints pointsComparator;
0209     PointsPriorityQueue pointsQueue;
0210 
0211     // temporary "global" variables for the processing routines
0212     KisRandomAccessorSP groupIt;
0213     KisRandomConstAccessorSP levelIt;
0214     qint32 backgroundGroupId = 0;
0215     int backgroundGroupColor = -1;
0216     bool recolorMode = false;
0217 
0218     quint64 totalPixelsToFill = 0;
0219     quint64 numFilledPixels = 0;
0220 
0221     KoUpdater *progressUpdater = 0;
0222 
0223     void initializeQueueFromGroupMap(const QRect &rc);
0224 
0225     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);
0226     ALWAYS_INLINE void updateGroupLastDistance(FillGroup::LevelData &levelData, int distance);
0227     void processQueue(qint32 _backgroundGroupId);
0228     void writeColoring();
0229 
0230     QVector<TaskPoint> tryRemoveConflictingPlane(qint32 group, quint8 level);
0231 
0232     void updateNarrowRegionMetrics();
0233 
0234     QVector<GroupLevelPair> calculateConflictingPairs();
0235     void cleanupForeignEdgeGroups(qreal cleanUpAmount);
0236 
0237     void dumpGroupMaps();
0238     void calcNumGroupMaps();
0239     void dumpGroupInfo(qint32 groupIndex, quint8 levelIndex);
0240 
0241 
0242 };
0243 
0244 /***********************************************************************/
0245 /*           KisWatershedWorker                                        */
0246 /***********************************************************************/
0247 
0248 
0249 
0250 KisWatershedWorker::KisWatershedWorker(KisPaintDeviceSP heightMap, KisPaintDeviceSP dst, const QRect &boundingRect, KoUpdater *progress)
0251     : m_d(new Private)
0252 {
0253     KIS_SAFE_ASSERT_RECOVER_RETURN(heightMap->colorSpace()->pixelSize() == 1);
0254 
0255     m_d->progressUpdater = progress;
0256     m_d->heightMap = heightMap;
0257     m_d->dstDevice = dst;
0258     m_d->boundingRect = boundingRect;
0259 
0260     // Just the simplest color space with 4 bytes per pixel. We use it as
0261     // a storage for qint32-indexed group ids
0262     m_d->groupsMap = new KisPaintDevice(KoColorSpaceRegistry::instance()->rgb8());
0263 }
0264 
0265 KisWatershedWorker::~KisWatershedWorker()
0266 {
0267 }
0268 
0269 void KisWatershedWorker::addKeyStroke(KisPaintDeviceSP dev, const KoColor &color)
0270 {
0271     m_d->keyStrokes << KeyStroke(new KisPaintDevice(*dev), color);
0272 
0273     KisPaintDeviceSP lastDev = m_d->keyStrokes.back().dev;
0274 
0275     for (auto it = m_d->keyStrokes.begin(); it != m_d->keyStrokes.end() - 1; ++it) {
0276         KisPaintDeviceSP dev = it->dev;
0277         const QRect rc = dev->exactBounds() & lastDev->exactBounds();
0278         if (rc.isEmpty()) continue;
0279 
0280         KisSequentialIterator devIt(dev, rc);
0281         KisSequentialConstIterator lastDevIt(lastDev, rc);
0282 
0283         while (devIt.nextPixel() &&
0284                lastDevIt.nextPixel()) {
0285 
0286             quint8 *devPtr = devIt.rawData();
0287             const quint8 *lastDevPtr = lastDevIt.rawDataConst();
0288 
0289             if (*devPtr > 0 && *lastDevPtr > 0) {
0290                 *devPtr = 0;
0291             }
0292 
0293         }
0294     }
0295 }
0296 
0297 void KisWatershedWorker::run(qreal cleanUpAmount)
0298 {
0299     if (!m_d->heightMap) return;
0300 
0301     m_d->groups << FillGroup(-1);
0302 
0303     for (int i = 0; i < m_d->keyStrokes.size(); i++) {
0304         parseColorIntoGroups(m_d->groups, m_d->groupsMap,
0305                              m_d->heightMap,
0306                              i, m_d->keyStrokes[i].dev,
0307                              m_d->boundingRect);
0308     }
0309 
0310 //    m_d->dumpGroupMaps();
0311 //    m_d->calcNumGroupMaps();
0312 
0313     const QRect initRect =
0314         m_d->boundingRect & m_d->groupsMap->nonDefaultPixelArea();
0315 
0316     m_d->initializeQueueFromGroupMap(initRect);
0317     m_d->processQueue(0);
0318 
0319     if (!m_d->progressUpdater || !m_d->progressUpdater->interrupted()) {
0320         //    m_d->dumpGroupMaps();
0321         //    m_d->calcNumGroupMaps();
0322 
0323         if (cleanUpAmount > 0) {
0324             m_d->cleanupForeignEdgeGroups(cleanUpAmount);
0325         }
0326 
0327         //    m_d->calcNumGroupMaps();
0328 
0329         m_d->writeColoring();
0330     }
0331 }
0332 
0333 int KisWatershedWorker::testingGroupPositiveEdge(qint32 group, quint8 level)
0334 {
0335     return m_d->groups[group].levels[level].positiveEdgeSize;
0336 }
0337 
0338 int KisWatershedWorker::testingGroupNegativeEdge(qint32 group, quint8 level)
0339 {
0340     return m_d->groups[group].levels[level].negativeEdgeSize;
0341 }
0342 
0343 int KisWatershedWorker::testingGroupForeignEdge(qint32 group, quint8 level)
0344 {
0345     return m_d->groups[group].levels[level].foreignEdgeSize;
0346 }
0347 
0348 int KisWatershedWorker::testingGroupAllyEdge(qint32 group, quint8 level)
0349 {
0350     return m_d->groups[group].levels[level].allyEdgeSize;
0351 }
0352 
0353 int KisWatershedWorker::testingGroupConflicts(qint32 group, quint8 level, qint32 withGroup)
0354 {
0355     return m_d->groups[group].levels[level].conflictWithGroup[withGroup].size();
0356 }
0357 
0358 void KisWatershedWorker::testingTryRemoveGroup(qint32 group, quint8 levelIndex)
0359 {
0360     QVector<TaskPoint> taskPoints =
0361         m_d->tryRemoveConflictingPlane(group, levelIndex);
0362 
0363     if (!taskPoints.isEmpty()) {
0364         Q_FOREACH (const TaskPoint &pt, taskPoints) {
0365             m_d->pointsQueue.push(pt);
0366         }
0367         m_d->processQueue(group);
0368     }
0369     m_d->dumpGroupMaps();
0370     m_d->calcNumGroupMaps();
0371 }
0372 
0373 void KisWatershedWorker::Private::initializeQueueFromGroupMap(const QRect &rc)
0374 {
0375     KisSequentialIterator groupMapIt(groupsMap, rc);
0376     KisSequentialConstIterator heightMapIt(heightMap, rc);
0377 
0378     while (groupMapIt.nextPixel() &&
0379            heightMapIt.nextPixel()) {
0380 
0381         qint32 *groupPtr = reinterpret_cast<qint32*>(groupMapIt.rawData());
0382         const quint8 *heightPtr = heightMapIt.rawDataConst();
0383 
0384         if (*groupPtr > 0) {
0385             TaskPoint pt;
0386             pt.x = groupMapIt.x();
0387             pt.y = groupMapIt.y();
0388             pt.group = *groupPtr;
0389             pt.level = *heightPtr;
0390 
0391             pointsQueue.push(pt);
0392 
0393             // we must clear the pixel to make sure foreign metric is calculated correctly
0394             *groupPtr = 0;
0395         }
0396 
0397     }
0398 }
0399 
0400 ALWAYS_INLINE void addForeignAlly(qint32 currGroupId,
0401                                   qint32 prevGroupId,
0402                                   FillGroup &currGroup,
0403                                   FillGroup &prevGroup,
0404                                   FillGroup::LevelData &currLevelData,
0405                                   FillGroup::LevelData &prevLevelData,
0406                                   const QPoint &currPt, const QPoint &prevPt,
0407                                   bool sameLevel)
0408 {
0409     if (currGroup.colorIndex != prevGroup.colorIndex || !sameLevel) {
0410         prevLevelData.foreignEdgeSize++;
0411         currLevelData.foreignEdgeSize++;
0412 
0413         if (sameLevel) {
0414             currLevelData.conflictWithGroup[prevGroupId].insert(currPt);
0415             prevLevelData.conflictWithGroup[currGroupId].insert(prevPt);
0416         }
0417 
0418     } else {
0419         prevLevelData.allyEdgeSize++;
0420         currLevelData.allyEdgeSize++;
0421     }
0422 
0423 
0424 }
0425 
0426 ALWAYS_INLINE  void removeForeignAlly(qint32 currGroupId,
0427                                       qint32 prevGroupId,
0428                                       FillGroup &currGroup,
0429                                       FillGroup &prevGroup,
0430                                       FillGroup::LevelData &currLevelData,
0431                                       FillGroup::LevelData &prevLevelData,
0432                                       const QPoint &currPt, const QPoint &prevPt,
0433                                       bool sameLevel)
0434 {
0435     if (currGroup.colorIndex != prevGroup.colorIndex || !sameLevel) {
0436         prevLevelData.foreignEdgeSize--;
0437         currLevelData.foreignEdgeSize--;
0438 
0439         if (sameLevel) {
0440             std::multiset<QPoint, CompareQPoints> &currSet = currLevelData.conflictWithGroup[prevGroupId];
0441             currSet.erase(currSet.find(currPt));
0442 
0443             std::multiset<QPoint, CompareQPoints> &prevSet = prevLevelData.conflictWithGroup[currGroupId];
0444             prevSet.erase(prevSet.find(prevPt));
0445         }
0446 
0447     } else {
0448         prevLevelData.allyEdgeSize--;
0449         currLevelData.allyEdgeSize--;
0450     }
0451 
0452 
0453 }
0454 
0455 ALWAYS_INLINE  void incrementLevelEdge(FillGroup::LevelData &currLevelData,
0456                                        FillGroup::LevelData &prevLevelData,
0457                                        quint8 currLevel,
0458                                        quint8 prevLevel)
0459 {
0460     Q_ASSERT(currLevel != prevLevel);
0461 
0462     if (currLevel > prevLevel) {
0463         currLevelData.negativeEdgeSize++;
0464         prevLevelData.positiveEdgeSize++;
0465     } else {
0466         currLevelData.positiveEdgeSize++;
0467         prevLevelData.negativeEdgeSize++;
0468     }
0469 }
0470 
0471 ALWAYS_INLINE  void decrementLevelEdge(FillGroup::LevelData &currLevelData,
0472                                        FillGroup::LevelData &prevLevelData,
0473                                        quint8 currLevel,
0474                                        quint8 prevLevel)
0475 {
0476     Q_ASSERT(currLevel != prevLevel);
0477 
0478     if (currLevel > prevLevel) {
0479         currLevelData.negativeEdgeSize--;
0480         prevLevelData.positiveEdgeSize--;
0481     } else {
0482         currLevelData.positiveEdgeSize--;
0483         prevLevelData.negativeEdgeSize--;
0484     }
0485 }
0486 
0487 void KisWatershedWorker::Private::visitNeighbour(const QPoint &currPt, const QPoint &prevPt,
0488                                                  quint8 fromDirection, int prevDistance, quint8 prevLevel,
0489                                                  qint32 prevGroupId, FillGroup &prevGroup, FillGroup::LevelData &prevLevelData,
0490                                                  qint32 prevPrevGroupId, FillGroup &prevPrevGroup,
0491                                                  bool statsOnly)
0492 {
0493     if (!boundingRect.contains(currPt)) {
0494         prevLevelData.positiveEdgeSize++;
0495 
0496         if (prevPrevGroupId > 0) {
0497             FillGroup::LevelData &prevPrevLevelData = prevPrevGroup.levels[prevLevel];
0498             prevPrevLevelData.positiveEdgeSize--;
0499         }
0500         return;
0501     }
0502 
0503     KIS_SAFE_ASSERT_RECOVER_RETURN(prevGroupId != backgroundGroupId);
0504 
0505     groupIt->moveTo(currPt.x(), currPt.y());
0506     levelIt->moveTo(currPt.x(), currPt.y());
0507 
0508     const qint32 currGroupId = *reinterpret_cast<const qint32*>(groupIt->rawDataConst());
0509     const quint8 newLevel = *levelIt->rawDataConst();
0510 
0511     FillGroup &currGroup = groups[currGroupId];
0512     FillGroup::LevelData &currLevelData = currGroup.levels[newLevel];
0513 
0514     const bool needsAddTaskPoint =
0515         !currGroupId ||
0516         (recolorMode &&
0517          ((newLevel == prevLevel &&
0518            currGroupId == backgroundGroupId) ||
0519           (newLevel >= prevLevel &&
0520            currGroup.colorIndex == backgroundGroupColor &&
0521            currLevelData.narrowRegion)));
0522 
0523     if (needsAddTaskPoint && !statsOnly) {
0524         TaskPoint pt;
0525         pt.x = currPt.x();
0526         pt.y = currPt.y();
0527         pt.group = prevGroupId;
0528         pt.level = newLevel;
0529         pt.distance = newLevel == prevLevel ? prevDistance + 1 : 0;
0530         pt.prevDirection = fromDirection;
0531 
0532         pointsQueue.push(pt);
0533     }
0534 
0535     // we can never clear the pixel!
0536     KIS_SAFE_ASSERT_RECOVER_RETURN(prevGroupId > 0);
0537     KIS_SAFE_ASSERT_RECOVER_RETURN(prevGroupId != prevPrevGroupId);
0538 
0539     if (currGroupId) {
0540         const bool isSameLevel = prevLevel == newLevel;
0541 
0542 
0543         if ((!prevPrevGroupId ||
0544              prevPrevGroupId == currGroupId) &&
0545             prevGroupId != currGroupId) {
0546 
0547             // we have added a foreign/ally group
0548 
0549             FillGroup::LevelData &currLevelData = currGroup.levels[newLevel];
0550 
0551             addForeignAlly(currGroupId, prevGroupId,
0552                            currGroup, prevGroup,
0553                            currLevelData, prevLevelData,
0554                            currPt, prevPt,
0555                            isSameLevel);
0556 
0557         } else if (prevPrevGroupId &&
0558                    prevPrevGroupId != currGroupId &&
0559                    prevGroupId == currGroupId) {
0560 
0561             // we have removed a foreign/ally group
0562 
0563             FillGroup::LevelData &currLevelData = currGroup.levels[newLevel];
0564             FillGroup::LevelData &prevPrevLevelData = prevPrevGroup.levels[prevLevel];
0565 
0566             removeForeignAlly(currGroupId, prevPrevGroupId,
0567                               currGroup, prevPrevGroup,
0568                               currLevelData, prevPrevLevelData,
0569                               currPt, prevPt,
0570                               isSameLevel);
0571 
0572         } else if (prevPrevGroupId &&
0573                    prevPrevGroupId != currGroupId &&
0574                    prevGroupId != currGroupId) {
0575 
0576             // this pixel has become an foreign/ally pixel of a different group
0577 
0578             FillGroup::LevelData &currLevelData = currGroup.levels[newLevel];
0579 
0580             FillGroup &prevPrevGroup = groups[prevPrevGroupId];
0581             FillGroup::LevelData &prevPrevLevelData = prevPrevGroup.levels[prevLevel];
0582 
0583             removeForeignAlly(currGroupId, prevPrevGroupId,
0584                               currGroup, prevPrevGroup,
0585                               currLevelData, prevPrevLevelData,
0586                               currPt, prevPt,
0587                               isSameLevel);
0588 
0589             addForeignAlly(currGroupId, prevGroupId,
0590                            currGroup, prevGroup,
0591                            currLevelData, prevLevelData,
0592                            currPt, prevPt,
0593                            isSameLevel);
0594         }
0595 
0596         if (!isSameLevel) {
0597 
0598             if (prevGroupId == currGroupId) {
0599                 // we connected with our own disjoint area
0600 
0601                 FillGroup::LevelData &currLevelData = currGroup.levels[newLevel];
0602 
0603                 incrementLevelEdge(currLevelData, prevLevelData,
0604                                    newLevel, prevLevel);
0605             }
0606 
0607             if (prevPrevGroupId == currGroupId) {
0608                 // we removed a pixel for the borderline
0609                 // (now it is registered as foreign/ally pixel)
0610 
0611                 FillGroup::LevelData &currLevelData = currGroup.levels[newLevel];
0612                 FillGroup::LevelData &prevPrevLevelData = currGroup.levels[prevLevel];
0613 
0614                 decrementLevelEdge(currLevelData, prevPrevLevelData,
0615                                    newLevel, prevLevel);
0616             }
0617         }
0618     }
0619 }
0620 
0621 #include <QElapsedTimer>
0622 
0623 void KisWatershedWorker::Private::processQueue(qint32 _backgroundGroupId)
0624 {
0625     QElapsedTimer tt; tt.start();
0626 
0627 
0628     // TODO: lazy initialization of the iterator's position
0629     // TODO: reuse iterators if possible!
0630     groupIt = groupsMap->createRandomAccessorNG();
0631     levelIt = heightMap->createRandomConstAccessorNG();
0632     backgroundGroupId = _backgroundGroupId;
0633     backgroundGroupColor = groups[backgroundGroupId].colorIndex;
0634     recolorMode = backgroundGroupId > 1;
0635 
0636     totalPixelsToFill = qint64(boundingRect.width()) * boundingRect.height();
0637     numFilledPixels = 0;
0638     const int progressReportingMask = (1 << 18) - 1; // report every 512x512 patch
0639 
0640 
0641     if (recolorMode) {
0642         updateNarrowRegionMetrics();
0643     }
0644 
0645     while (!pointsQueue.empty()) {
0646         TaskPoint pt = pointsQueue.top();
0647         pointsQueue.pop();
0648 
0649         groupIt->moveTo(pt.x, pt.y);
0650         qint32 *groupPtr = reinterpret_cast<qint32*>(groupIt->rawData());
0651 
0652         const qint32 prevGroupId = *groupPtr;
0653         FillGroup &prevGroup = groups[prevGroupId];
0654 
0655         if (prevGroupId == backgroundGroupId ||
0656             (recolorMode &&
0657              prevGroup.colorIndex == backgroundGroupColor)) {
0658 
0659             FillGroup &currGroup = groups[pt.group];
0660             FillGroup::LevelData &currLevelData = currGroup.levels[pt.level];
0661             currLevelData.numFilledPixels++;
0662 
0663             if (prevGroupId > 0) {
0664                 FillGroup::LevelData &prevLevelData = prevGroup.levels[pt.level];
0665                 prevLevelData.numFilledPixels--;
0666             } else {
0667                 numFilledPixels++;
0668             }
0669 
0670             const NeighbourStaticOffset *offsets = staticOffsets[pt.prevDirection];
0671             const QPoint currPt(pt.x, pt.y);
0672 
0673             for (int i = 0; i < 4; i++) {
0674                 const NeighbourStaticOffset &offset = offsets[i];
0675 
0676                 const QPoint nextPt = currPt + offset.offset;
0677                 visitNeighbour(nextPt, currPt,
0678                                offset.from, pt.distance, pt.level,
0679                                pt.group, currGroup, currLevelData,
0680                                prevGroupId, prevGroup,
0681                                offset.statsOnly);
0682             }
0683 
0684             *groupPtr = pt.group;
0685 
0686             if (progressUpdater && !(numFilledPixels & progressReportingMask)) {
0687                 const int progressPercent =
0688                     qBound(0, qRound(100.0 * numFilledPixels / totalPixelsToFill), 100);
0689                 progressUpdater->setProgress(progressPercent);
0690                 if (progressUpdater->interrupted()) {
0691                     break;
0692                 }
0693 
0694             }
0695 
0696         } else {
0697             // nothing to do?
0698         }
0699 
0700     }
0701 
0702     // cleanup iterators
0703     groupIt.clear();
0704     levelIt.clear();
0705     backgroundGroupId = 0;
0706     backgroundGroupColor = -1;
0707     recolorMode = false;
0708 
0709 //    ENTER_FUNCTION() << ppVar(tt.elapsed());
0710 }
0711 
0712 void KisWatershedWorker::Private::writeColoring()
0713 {
0714     KisSequentialConstIterator srcIt(groupsMap, boundingRect);
0715     KisSequentialIterator dstIt(dstDevice, boundingRect);
0716 
0717     QVector<KoColor> colors;
0718     for (auto it = keyStrokes.begin(); it != keyStrokes.end(); ++it) {
0719         KoColor color = it->color;
0720         color.convertTo(dstDevice->colorSpace());
0721         colors << color;
0722     }
0723     const int colorPixelSize = dstDevice->pixelSize();
0724 
0725 
0726     while (srcIt.nextPixel() && dstIt.nextPixel()) {
0727         const qint32 *srcPtr = reinterpret_cast<const qint32*>(srcIt.rawDataConst());
0728 
0729         const int colorIndex = groups[*srcPtr].colorIndex;
0730         if (colorIndex >= 0) {
0731             memcpy(dstIt.rawData(), colors[colorIndex].data(), colorPixelSize);
0732         }
0733 
0734     }
0735 }
0736 
0737 QVector<TaskPoint> KisWatershedWorker::Private::tryRemoveConflictingPlane(qint32 group, quint8 level)
0738 {
0739     QVector<TaskPoint> result;
0740 
0741     FillGroup &g = groups[group];
0742     FillGroup::LevelData &l = g.levels[level];
0743 
0744     for (auto conflictIt = l.conflictWithGroup.begin(); conflictIt != l.conflictWithGroup.end(); ++conflictIt) {
0745 
0746         std::vector<QPoint> uniquePoints;
0747         std::unique_copy(conflictIt->begin(), conflictIt->end(), std::back_inserter(uniquePoints));
0748 
0749         for (auto pointIt = uniquePoints.begin(); pointIt != uniquePoints.end(); ++pointIt) {
0750             TaskPoint pt;
0751             pt.x = pointIt->x();
0752             pt.y = pointIt->y();
0753             pt.group = conflictIt.key();
0754             pt.level = level;
0755 
0756             result.append(pt);
0757             // no writing to the group map!
0758         }
0759     }
0760 
0761     return result;
0762 }
0763 
0764 void KisWatershedWorker::Private::updateNarrowRegionMetrics()
0765 {
0766     for (qint32 i = 0; i < groups.size(); i++) {
0767         FillGroup &group = groups[i];
0768 
0769         for (auto levelIt = group.levels.begin(); levelIt != group.levels.end(); ++levelIt) {
0770             FillGroup::LevelData &l = levelIt.value();
0771 
0772             const qreal areaToPerimeterRatio = qreal(l.numFilledPixels) / l.totalEdgeSize();
0773             l.narrowRegion = areaToPerimeterRatio < 2.0;
0774         }
0775     }
0776 }
0777 
0778 QVector<GroupLevelPair> KisWatershedWorker::Private::calculateConflictingPairs()
0779 {
0780     QVector<GroupLevelPair> result;
0781 
0782 
0783     for (qint32 i = 0; i < groups.size(); i++) {
0784         FillGroup &group = groups[i];
0785 
0786         for (auto levelIt = group.levels.begin(); levelIt != group.levels.end(); ++levelIt) {
0787             FillGroup::LevelData &l = levelIt.value();
0788 
0789             for (auto conflictIt = l.conflictWithGroup.begin(); conflictIt != l.conflictWithGroup.end(); ++conflictIt) {
0790                 if (!conflictIt->empty()) {
0791                     result.append(GroupLevelPair(i, levelIt.key()));
0792                     break;
0793                 }
0794             }
0795         }
0796     }
0797 
0798     return result;
0799 }
0800 
0801 #include <boost/accumulators/accumulators.hpp>
0802 #include <boost/accumulators/statistics/stats.hpp>
0803 #include <boost/accumulators/statistics/mean.hpp>
0804 #include <boost/accumulators/statistics/min.hpp>
0805 
0806 void KisWatershedWorker::Private::cleanupForeignEdgeGroups(qreal cleanUpAmount)
0807 {
0808     KIS_SAFE_ASSERT_RECOVER_NOOP(cleanUpAmount > 0.0);
0809 
0810     // convert into the threshold range [0.05...0.5]
0811     const qreal foreignEdgePortionThreshold = 0.05 + 0.45 * (1.0 - qBound(0.0, cleanUpAmount, 1.0));
0812 
0813     QVector<GroupLevelPair> conflicts = calculateConflictingPairs();
0814 
0815     // sort the pairs by the total edge size
0816     QMap<qreal, GroupLevelPair> sortedPairs;
0817     Q_FOREACH (const GroupLevelPair &pair, conflicts) {
0818         const qint32 groupIndex = pair.first;
0819         const quint8 levelIndex = pair.second;
0820         FillGroup::LevelData &level = groups[groupIndex].levels[levelIndex];
0821 
0822         sortedPairs.insert(level.totalEdgeSize(), pair);
0823     }
0824 
0825     // remove sequentially from the smallest to the biggest
0826     for (auto pairIt = sortedPairs.begin(); pairIt != sortedPairs.end(); ++pairIt) {
0827         const qint32 groupIndex = pairIt->first;
0828         const quint8 levelIndex = pairIt->second;
0829         FillGroup::LevelData &level = groups[groupIndex].levels[levelIndex];
0830 
0831         const int thisLength = pairIt.key();
0832         const qreal thisForeignPortion = qreal(level.foreignEdgeSize) / thisLength;
0833 
0834         using namespace boost::accumulators;
0835         accumulator_set<int, stats<tag::count, tag::mean, tag::min>> lengthStats;
0836 
0837         for (auto it = level.conflictWithGroup.begin(); it != level.conflictWithGroup.end(); ++it) {
0838             const FillGroup::LevelData &otherLevel = groups[it.key()].levels[levelIndex];
0839             lengthStats(otherLevel.totalEdgeSize());
0840         }
0841 
0842         KIS_SAFE_ASSERT_RECOVER_BREAK(count(lengthStats));
0843 
0844         const qreal minMetric = min(lengthStats) / qreal(thisLength);
0845         const qreal meanMetric = mean(lengthStats) / qreal(thisLength);
0846 
0847 //        qDebug() << "G" << groupIndex
0848 //                 << "L" << levelIndex
0849 //                 << "con" << level.conflictWithGroup.size()
0850 //                 << "FRP" << thisForeignPortion
0851 //                 << "S" << level.numFilledPixels
0852 //                 << ppVar(thisLength)
0853 //                 << ppVar(min(lengthStats))
0854 //                 << ppVar(mean(lengthStats))
0855 //                 << ppVar(minMetric)
0856 //                 << ppVar(meanMetric);
0857 
0858         if (!(thisForeignPortion > foreignEdgePortionThreshold)) continue;
0859 
0860         if (minMetric > 1.0 && meanMetric > 1.2) {
0861 //            qDebug() << "   * removing...";
0862 
0863             QVector<TaskPoint> taskPoints =
0864                 tryRemoveConflictingPlane(groupIndex, levelIndex);
0865 
0866             if (!taskPoints.isEmpty()) {
0867                 // dump before
0868                 // dumpGroupInfo(groupIndex, levelIndex);
0869 
0870                 Q_FOREACH (const TaskPoint &pt, taskPoints) {
0871                     pointsQueue.push(pt);
0872                 }
0873                 processQueue(groupIndex);
0874 
0875                 // dump after: should become empty!
0876                 // dumpGroupInfo(groupIndex, levelIndex);
0877 
0878                 // the areas might be disjoint, so that removing one "conflicting"
0879                 // part will not remove the whole group+level pair
0880 
0881                 // KIS_SAFE_ASSERT_RECOVER_NOOP(level.totalEdgeSize() == 0);
0882             }
0883 
0884             //dumpGroupMaps();
0885             //calcNumGroupMaps();
0886 
0887         }
0888     }
0889 
0890 }
0891 
0892 void KisWatershedWorker::Private::dumpGroupMaps()
0893 {
0894     KisPaintDeviceSP groupDevice = new KisPaintDevice(KoColorSpaceRegistry::instance()->alpha8());
0895     KisPaintDeviceSP colorDevice = new KisPaintDevice(KoColorSpaceRegistry::instance()->rgb8());
0896     KisPaintDeviceSP pedgeDevice = new KisPaintDevice(KoColorSpaceRegistry::instance()->alpha8());
0897     KisPaintDeviceSP nedgeDevice = new KisPaintDevice(KoColorSpaceRegistry::instance()->alpha8());
0898     KisPaintDeviceSP fedgeDevice = new KisPaintDevice(KoColorSpaceRegistry::instance()->alpha8());
0899 
0900     KisSequentialConstIterator heightIt(heightMap, boundingRect);
0901     KisSequentialConstIterator srcIt(groupsMap, boundingRect);
0902     KisSequentialIterator dstGroupIt(groupDevice, boundingRect);
0903     KisSequentialIterator dstColorIt(colorDevice, boundingRect);
0904     KisSequentialIterator dstPedgeIt(pedgeDevice, boundingRect);
0905     KisSequentialIterator dstNedgeIt(nedgeDevice, boundingRect);
0906     KisSequentialIterator dstFedgeIt(fedgeDevice, boundingRect);
0907 
0908 
0909     QVector<KoColor> colors;
0910     for (auto it = keyStrokes.begin(); it != keyStrokes.end(); ++it) {
0911         KoColor color = it->color;
0912         color.convertTo(colorDevice->colorSpace());
0913         colors << color;
0914     }
0915     const int colorPixelSize = colorDevice->pixelSize();
0916 
0917 
0918 
0919     while (dstGroupIt.nextPixel() &&
0920            heightIt.nextPixel() &&
0921            srcIt.nextPixel() &&
0922            dstColorIt.nextPixel() &&
0923            dstPedgeIt.nextPixel() &&
0924            dstNedgeIt.nextPixel() &&
0925            dstFedgeIt.nextPixel()) {
0926 
0927         const qint32 *srcPtr = reinterpret_cast<const qint32*>(srcIt.rawDataConst());
0928 
0929         *dstGroupIt.rawData() = quint8(*srcPtr);
0930         memcpy(dstColorIt.rawData(), colors[groups[*srcPtr].colorIndex].data(), colorPixelSize);
0931 
0932         quint8 level = *heightIt.rawDataConst();
0933 
0934         if (groups[*srcPtr].levels.contains(level)) {
0935             const FillGroup::LevelData &l = groups[*srcPtr].levels[level];
0936 
0937             const int edgeLength = l.totalEdgeSize();
0938 
0939             *dstPedgeIt.rawData() = 255.0 * qreal(l.positiveEdgeSize) / (edgeLength);
0940             *dstNedgeIt.rawData() = 255.0 * qreal(l.negativeEdgeSize) / (edgeLength);
0941             *dstFedgeIt.rawData() = 255.0 * qreal(l.foreignEdgeSize) / (edgeLength);
0942         } else {
0943             *dstPedgeIt.rawData() = 0;
0944             *dstNedgeIt.rawData() = 0;
0945             *dstFedgeIt.rawData() = 0;
0946         }
0947     }
0948 
0949 
0950     KIS_DUMP_DEVICE_2(groupDevice, boundingRect, "01_groupMap", "dd");
0951     KIS_DUMP_DEVICE_2(colorDevice, boundingRect, "02_colorMap", "dd");
0952     KIS_DUMP_DEVICE_2(pedgeDevice, boundingRect, "03_pedgeMap", "dd");
0953     KIS_DUMP_DEVICE_2(nedgeDevice, boundingRect, "04_nedgeMap", "dd");
0954     KIS_DUMP_DEVICE_2(fedgeDevice, boundingRect, "05_fedgeMap", "dd");
0955 }
0956 
0957 void KisWatershedWorker::Private::calcNumGroupMaps()
0958 {
0959     KisSequentialConstIterator groupIt(groupsMap, boundingRect);
0960     KisSequentialConstIterator levelIt(heightMap, boundingRect);
0961 
0962     QSet<QPair<qint32, quint8>> groups;
0963 
0964     while (groupIt.nextPixel() && levelIt.nextPixel()) {
0965 
0966         const qint32 group = *reinterpret_cast<const qint32*>(groupIt.rawDataConst());
0967         const quint8 level = *reinterpret_cast<const quint8*>(levelIt.rawDataConst());
0968 
0969         groups.insert(qMakePair(group, level));
0970     }
0971 
0972     for (auto it = groups.begin(); it != groups.end(); ++it) {
0973         dumpGroupInfo(it->first, it->second);
0974     }
0975 
0976     ENTER_FUNCTION() << ppVar(groups.size());
0977 }
0978 
0979 void KisWatershedWorker::Private::dumpGroupInfo(qint32 groupIndex, quint8 levelIndex)
0980 {
0981     FillGroup::LevelData &level = groups[groupIndex].levels[levelIndex];
0982 
0983     qDebug() << "G" << groupIndex << "L" << levelIndex << "CI" << this->groups[groupIndex].colorIndex;
0984     qDebug() << "   P" << level.positiveEdgeSize;
0985     qDebug() << "   N" << level.negativeEdgeSize;
0986     qDebug() << "   F" << level.foreignEdgeSize;
0987     qDebug() << "   A" << level.allyEdgeSize;
0988     qDebug() << " (S)" << level.numFilledPixels;
0989 
0990     auto &c = level.conflictWithGroup;
0991 
0992     for (auto cIt = c.begin(); cIt != c.end(); ++cIt) {
0993         qDebug() << "   C" << cIt.key() << cIt.value().size();
0994     }
0995 }