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 }