File indexing completed on 2024-05-19 04:26:12

0001 /*
0002  *  SPDX-FileCopyrightText: 2004 Adrian Page <adrian@pagenet.plus.com>
0003  *  SPDX-FileCopyrightText: 2004 Bart Coppens <kde@bartcoppens.be>
0004  *  SPDX-FileCopyrightText: 2010 Lukáš Tvrdý <lukast.dev@gmail.com>
0005  *
0006  *  SPDX-License-Identifier: GPL-2.0-or-later
0007  */
0008 
0009 #include "kis_fill_painter.h"
0010 
0011 #include <stdlib.h>
0012 #include <string.h>
0013 #include <cfloat>
0014 #include <stack>
0015 
0016 #include <QFontInfo>
0017 #include <QFontMetrics>
0018 #include <QPen>
0019 #include <QMatrix>
0020 #include <QImage>
0021 #include <QMap>
0022 #include <QPainter>
0023 #include <QRect>
0024 #include <QString>
0025 
0026 #include <klocalizedstring.h>
0027 
0028 #include <KoUpdater.h>
0029 
0030 #include "generator/kis_generator.h"
0031 #include "filter/kis_filter_configuration.h"
0032 #include "generator/kis_generator_registry.h"
0033 #include "kis_processing_information.h"
0034 #include "kis_debug.h"
0035 #include "kis_image.h"
0036 #include "kis_layer.h"
0037 #include "kis_paint_device.h"
0038 #include <resources/KoPattern.h>
0039 #include "KoColorSpace.h"
0040 #include "kis_transaction.h"
0041 #include "kis_pixel_selection.h"
0042 #include <KoCompositeOpRegistry.h>
0043 #include <floodfill/kis_scanline_fill.h>
0044 #include "kis_selection_filters.h"
0045 #include <kis_perspectivetransform_worker.h>
0046 #include <kis_sequential_iterator.h>
0047 #include <KisColorSelectionPolicies.h>
0048 #include <krita_utils.h>
0049 #include <kis_default_bounds.h>
0050 #include <KisImageResolutionProxy.h>
0051 
0052 
0053 KisFillPainter::KisFillPainter()
0054         : KisPainter()
0055 {
0056     initFillPainter();
0057 }
0058 
0059 KisFillPainter::KisFillPainter(KisPaintDeviceSP device)
0060         : KisPainter(device)
0061 {
0062     initFillPainter();
0063 }
0064 
0065 KisFillPainter::KisFillPainter(KisPaintDeviceSP device, KisSelectionSP selection)
0066         : KisPainter(device, selection)
0067 {
0068     initFillPainter();
0069 }
0070 
0071 void KisFillPainter::initFillPainter()
0072 {
0073     m_width = m_height = -1;
0074     m_careForSelection = false;
0075     m_sizemod = 0;
0076     m_feather = 0;
0077     m_useCompositing = false;
0078     m_threshold = 0;
0079     m_opacitySpread = 0;
0080     m_useSelectionAsBoundary = false;
0081     m_antiAlias = false;
0082     m_regionFillingMode = RegionFillingMode_FloodFill;
0083     m_stopGrowingAtDarkestPixel = false;
0084 }
0085 
0086 void KisFillPainter::fillSelection(const QRect &rc, const KoColor &color)
0087 {
0088     KisPaintDeviceSP fillDevice = new KisPaintDevice(device()->colorSpace());
0089     fillDevice->setDefaultPixel(color);
0090 
0091     bitBlt(rc.topLeft(), fillDevice, rc);
0092 }
0093 
0094 // 'regular' filling
0095 // XXX: This also needs renaming, since filling ought to keep the opacity and the composite op in mind,
0096 //      this is more eraseToColor.
0097 void KisFillPainter::fillRect(qint32 x1, qint32 y1, qint32 w, qint32 h, const KoColor& kc, quint8 opacity)
0098 {
0099     if (w > 0 && h > 0) {
0100         // Make sure we're in the right colorspace
0101 
0102         KoColor kc2(kc); // get rid of const
0103         kc2.convertTo(device()->colorSpace());
0104         quint8 * data = kc2.data();
0105         device()->colorSpace()->setOpacity(data, opacity, 1);
0106 
0107         device()->fill(x1, y1, w, h, data);
0108 
0109         addDirtyRect(QRect(x1, y1, w, h));
0110     }
0111 }
0112 
0113 void KisFillPainter::fillRect(const QRect &rc, const KoPatternSP pattern, const QPoint &offset)
0114 {
0115     fillRect(rc.x(), rc.y(), rc.width(), rc.height(), pattern, offset);
0116 }
0117 
0118 void KisFillPainter::fillRect(qint32 x1, qint32 y1, qint32 w, qint32 h, const KoPatternSP pattern, const QPoint &offset)
0119 {
0120     if (!pattern) return;
0121     if (!pattern->valid()) return;
0122     if (!device()) return;
0123     if (w < 1) return;
0124     if (h < 1) return;
0125 
0126     KisPaintDeviceSP patternLayer = new KisPaintDevice(device()->compositionSourceColorSpace(), pattern->name());
0127     patternLayer->convertFromQImage(pattern->pattern(), 0);
0128 
0129     if (!offset.isNull()) {
0130         patternLayer->moveTo(offset);
0131     }
0132 
0133     fillRect(x1, y1, w, h, patternLayer, QRect(offset.x(), offset.y(), pattern->width(), pattern->height()));
0134 }
0135 
0136 void KisFillPainter::fillRectNoCompose(const QRect &rc, const KoPatternSP pattern, const QTransform transform)
0137 {
0138     if (!pattern) return;
0139     if (!pattern->valid()) return;
0140     if (!device()) return;
0141     if (rc.width() < 1) return;
0142     if (rc.height() < 1) return;
0143 
0144     KisPaintDeviceSP patternLayer = new KisPaintDevice(device()->colorSpace(), pattern->name());
0145     patternLayer->convertFromQImage(pattern->pattern(), 0);
0146 
0147     fillRectNoCompose(rc.x(), rc.y(), rc.width(), rc.height(), patternLayer, QRect(0, 0, pattern->width(), pattern->height()), transform);
0148 }
0149 
0150 void KisFillPainter::fillRectNoCompose(qint32 x1, qint32 y1, qint32 w, qint32 h, const KisPaintDeviceSP device, const QRect& deviceRect, const QTransform transform)
0151 {
0152     /**
0153      * Since this function doesn't do any kind of compositing, so the pixel size
0154      * of the source and destination devices must be exactly the same. The color
0155      * space should ideally be also the same.
0156      */
0157     KIS_SAFE_ASSERT_RECOVER_RETURN(device->pixelSize() == this->device()->pixelSize());
0158     KIS_SAFE_ASSERT_RECOVER_NOOP(*device->colorSpace() == *this->device()->colorSpace());
0159 
0160     KisPaintDeviceSP wrapped = device;
0161     KisDefaultBoundsBaseSP oldBounds = wrapped->defaultBounds();
0162     wrapped->setDefaultBounds(new KisWrapAroundBoundsWrapper(oldBounds, deviceRect));
0163     const bool oldSupportsWrapAroundMode = wrapped->supportsWraproundMode();
0164     wrapped->setSupportsWraparoundMode(true);
0165 
0166 
0167     KisPerspectiveTransformWorker worker(this->device(), transform, false, this->progressUpdater());
0168     worker.runPartialDst(device, this->device(), QRect(x1, y1, w, h));
0169 
0170     addDirtyRect(QRect(x1, y1, w, h));
0171     wrapped->setDefaultBounds(oldBounds);
0172     wrapped->setSupportsWraparoundMode(oldSupportsWrapAroundMode);
0173 }
0174 
0175 void KisFillPainter::fillRect(const QRect &rc, const KisPaintDeviceSP device, const QRect& deviceRect)
0176 {
0177     fillRect(rc.x(), rc.y(), rc.width(), rc.height(), device, deviceRect);
0178 }
0179 
0180 void KisFillPainter::fillRect(qint32 x1, qint32 y1, qint32 w, qint32 h, const KisPaintDeviceSP device, const QRect& deviceRect)
0181 {
0182     const QRect &patternRect = deviceRect;
0183     const QRect fillRect(x1, y1, w, h);
0184 
0185     auto toPatternLocal = [](int value, int offset, int width) {
0186         const int normalizedValue = value - offset;
0187         return offset + (normalizedValue >= 0 ?
0188                          normalizedValue % width :
0189                          width - (-normalizedValue - 1) % width - 1);
0190     };
0191 
0192     int dstY = fillRect.y();
0193     while (dstY <= fillRect.bottom()) {
0194         const int dstRowsRemaining = fillRect.bottom() - dstY + 1;
0195 
0196         const int srcY = toPatternLocal(dstY, patternRect.y(), patternRect.height());
0197         const int height = qMin(patternRect.height() - srcY + patternRect.y(), dstRowsRemaining);
0198 
0199         int dstX = fillRect.x();
0200         while (dstX <= fillRect.right()) {
0201             const int dstColumnsRemaining = fillRect.right() - dstX + 1;
0202 
0203             const int srcX = toPatternLocal(dstX, patternRect.x(), patternRect.width());
0204             const int width = qMin(patternRect.width() - srcX  + patternRect.x(), dstColumnsRemaining);
0205 
0206             bitBlt(dstX, dstY, device, srcX, srcY, width, height);
0207 
0208             dstX += width;
0209         }
0210         dstY += height;
0211     }
0212 
0213     addDirtyRect(QRect(x1, y1, w, h));
0214 }
0215 
0216 void KisFillPainter::fillRect(qint32 x1, qint32 y1, qint32 w, qint32 h, const KisFilterConfigurationSP generator)
0217 {
0218     if (!generator) return;
0219     KisGeneratorSP g = KisGeneratorRegistry::instance()->value(generator->name());
0220     if (!device()) return;
0221     if (w < 1) return;
0222     if (h < 1) return;
0223 
0224     QRect tmpRc(x1, y1, w, h);
0225 
0226     KisProcessingInformation dstCfg(device(), tmpRc.topLeft(), 0);
0227 
0228     g->generate(dstCfg, tmpRc.size(), generator);
0229 
0230     addDirtyRect(tmpRc);
0231 }
0232 
0233 // flood filling
0234 
0235 void KisFillPainter::fillColor(int startX, int startY, KisPaintDeviceSP sourceDevice)
0236 {
0237     if (!m_useCompositing) {
0238         if (m_sizemod || m_feather ||
0239             compositeOpId() != COMPOSITE_OVER ||
0240             opacity() != MAX_SELECTED ||
0241             sourceDevice != device()) {
0242 
0243             warnKrita << "WARNING: Fast Flood Fill (no compositing mode)"
0244                        << "does not support compositeOps, opacity, "
0245                        << "selection enhancements and separate source "
0246                        << "devices";
0247         }
0248 
0249         QRect fillBoundsRect(0, 0, m_width, m_height);
0250         QPoint startPoint(startX, startY);
0251 
0252         if (!fillBoundsRect.contains(startPoint)) return;
0253 
0254         KisScanlineFill gc(device(), startPoint, fillBoundsRect);
0255         gc.setThreshold(m_threshold);
0256         if (m_regionFillingMode == RegionFillingMode_FloodFill) {
0257             gc.fill(paintColor());
0258         } else {
0259             gc.fillUntilColor(paintColor(), m_regionFillingBoundaryColor);
0260         }
0261 
0262     } else {
0263         genericFillStart(startX, startY, sourceDevice);
0264 
0265         // Now create a layer and fill it
0266         KisPaintDeviceSP filled = device()->createCompositionSourceDevice();
0267         Q_CHECK_PTR(filled);
0268         KisFillPainter painter(filled);
0269         painter.fillRect(0, 0, m_width, m_height, paintColor());
0270         painter.end();
0271 
0272         genericFillEnd(filled);
0273     }
0274 }
0275 
0276 void KisFillPainter::fillPattern(int startX, int startY, KisPaintDeviceSP sourceDevice, QTransform patternTransform)
0277 {
0278     genericFillStart(startX, startY, sourceDevice);
0279 
0280     // Now create a layer and fill it
0281     KisPaintDeviceSP filled = device()->createCompositionSourceDevice();
0282     Q_CHECK_PTR(filled);
0283     KisFillPainter painter(filled);
0284     painter.fillRectNoCompose(QRect(0, 0, m_width, m_height), pattern(), patternTransform);
0285     painter.end();
0286 
0287     genericFillEnd(filled);
0288 }
0289 
0290 void KisFillPainter::genericFillStart(int startX, int startY, KisPaintDeviceSP sourceDevice)
0291 {
0292     Q_ASSERT(m_width > 0);
0293     Q_ASSERT(m_height > 0);
0294 
0295     // Create a selection from the surrounding area
0296 
0297     KisPixelSelectionSP pixelSelection = createFloodSelection(startX, startY, sourceDevice,
0298                                                               (selection().isNull() ? 0 : selection()->pixelSelection()));
0299     KisSelectionSP newSelection = new KisSelection(pixelSelection->defaultBounds(),
0300                                                    selection() ? selection()->resolutionProxy() : KisImageResolutionProxy::identity());
0301     newSelection->pixelSelection()->applySelection(pixelSelection, SELECTION_REPLACE);
0302     m_fillSelection = newSelection;
0303 }
0304 
0305 void KisFillPainter::genericFillEnd(KisPaintDeviceSP filled)
0306 {
0307     if (progressUpdater() && progressUpdater()->interrupted()) {
0308         m_width = m_height = -1;
0309         return;
0310     }
0311 
0312 //  TODO: filling using the correct bound of the selection would be better, *but*
0313 //  the selection is limited to the exact bound of a layer, while in reality, we don't
0314 //  want that, since we want a transparent layer to be completely filled
0315 //     QRect rc = m_fillSelection->selectedExactRect();
0316 
0317 
0318     /**
0319      * Apply the real selection to a filled one
0320      */
0321     KisSelectionSP realSelection = selection();
0322     QRect rc;
0323 
0324     if (realSelection) {
0325         rc = m_fillSelection->selectedExactRect().intersected(realSelection->projection()->selectedExactRect());
0326         m_fillSelection->pixelSelection()->applySelection(
0327             realSelection->projection(), SELECTION_INTERSECT);
0328     } else {
0329         rc = m_fillSelection->selectedExactRect();
0330     }
0331 
0332     setSelection(m_fillSelection);
0333     bitBlt(rc.topLeft(), filled, rc);
0334     setSelection(realSelection);
0335 
0336     if (progressUpdater()) progressUpdater()->setProgress(100);
0337 
0338     m_width = m_height = -1;
0339 }
0340 
0341 KisPixelSelectionSP KisFillPainter::createFloodSelection(int startX, int startY, KisPaintDeviceSP sourceDevice,
0342                                                          KisPaintDeviceSP existingSelection)
0343 {
0344     KisPixelSelectionSP newSelection = new KisPixelSelection(new KisSelectionDefaultBounds(device()));
0345     return createFloodSelection(newSelection, startX, startY, sourceDevice, existingSelection);
0346 }
0347 
0348 KisPixelSelectionSP KisFillPainter::createFloodSelection(KisPixelSelectionSP pixelSelection, int startX, int startY,
0349                                                          KisPaintDeviceSP sourceDevice, KisPaintDeviceSP existingSelection)
0350 {
0351 
0352     if (m_width < 0 || m_height < 0) {
0353         if (selection() && m_careForSelection) {
0354             QRect rc = selection()->selectedExactRect();
0355             m_width = rc.width() - (startX - rc.x());
0356             m_height = rc.height() - (startY - rc.y());
0357         }
0358     }
0359     dbgImage << "Width: " << m_width << " Height: " << m_height;
0360     // Otherwise the width and height should have been set
0361     Q_ASSERT(m_width > 0 && m_height > 0);
0362 
0363     QRect fillBoundsRect(0, 0, m_width, m_height);
0364     QPoint startPoint(startX, startY);
0365 
0366     if (!fillBoundsRect.contains(startPoint)) {
0367         return pixelSelection;
0368     }
0369 
0370     KisScanlineFill gc(sourceDevice, startPoint, fillBoundsRect);
0371     gc.setThreshold(m_threshold);
0372     gc.setOpacitySpread(m_useCompositing ? m_opacitySpread : 100);
0373     if (m_regionFillingMode == RegionFillingMode_FloodFill) {
0374         if (m_useSelectionAsBoundary && !pixelSelection.isNull()) {
0375             gc.fillSelection(pixelSelection, existingSelection);
0376         } else {
0377             gc.fillSelection(pixelSelection);
0378         }
0379     } else {
0380         if (m_useSelectionAsBoundary && !pixelSelection.isNull()) {
0381             gc.fillSelectionUntilColor(pixelSelection, m_regionFillingBoundaryColor, existingSelection);
0382         } else {
0383             gc.fillSelectionUntilColor(pixelSelection, m_regionFillingBoundaryColor);
0384         }
0385     }
0386 
0387     if (m_useCompositing) {
0388         if (m_sizemod > 0) {
0389             if (m_stopGrowingAtDarkestPixel) {
0390                 KisGrowUntilDarkestPixelSelectionFilter biggy(m_sizemod, sourceDevice);
0391                 biggy.process(pixelSelection, pixelSelection->selectedRect().adjusted(-m_sizemod, -m_sizemod, m_sizemod, m_sizemod));
0392             } else {
0393                 KisGrowSelectionFilter biggy(m_sizemod, m_sizemod);
0394                 biggy.process(pixelSelection, pixelSelection->selectedRect().adjusted(-m_sizemod, -m_sizemod, m_sizemod, m_sizemod));
0395             }
0396         }
0397         else if (m_sizemod < 0) {
0398             KisShrinkSelectionFilter tiny(-m_sizemod, -m_sizemod, false);
0399             tiny.process(pixelSelection, pixelSelection->selectedRect());
0400         }
0401         // Since the feathering already smooths the selection, the antiAlias
0402         // is not applied if we must feather
0403         if (m_feather > 0) {
0404             KisFeatherSelectionFilter feathery(m_feather);
0405             feathery.process(pixelSelection, pixelSelection->selectedRect().adjusted(-m_feather, -m_feather, m_feather, m_feather));
0406         } else if (m_antiAlias) {
0407             KisAntiAliasSelectionFilter antiAliasFilter;
0408             antiAliasFilter.process(pixelSelection, pixelSelection->selectedRect());
0409         }
0410     }
0411 
0412     return pixelSelection;
0413 }
0414 
0415 template <typename DifferencePolicy, typename SelectionPolicy>
0416 void createSimilarColorsSelectionImpl(KisPixelSelectionSP outSelection,
0417                                       KisPaintDeviceSP referenceDevice,
0418                                       const QRect &rect,
0419                                       KisPixelSelectionSP mask,
0420                                       DifferencePolicy differencePolicy,
0421                                       SelectionPolicy selectionPolicy,
0422                                       KoUpdater *updater = nullptr)
0423 {
0424     KisSequentialConstIterator referenceDeviceIterator(referenceDevice, rect);
0425     KisSequentialIterator outSelectionIterator(outSelection, rect);
0426 
0427     const int totalNumberOfPixels = rect.width() * rect.height();
0428     const int numberOfUpdates = 4;
0429     const int numberOfPixelsPerUpdate = totalNumberOfPixels / numberOfUpdates;
0430     const int progressIncrement = 100 / numberOfUpdates;
0431     int numberOfPixelsProcessed = 0;
0432 
0433     if (mask) {
0434         KisSequentialConstIterator maskIterator(mask, rect);
0435         while (referenceDeviceIterator.nextPixel() &&
0436                outSelectionIterator.nextPixel() &&
0437                maskIterator.nextPixel()) {
0438             if (*maskIterator.rawDataConst() != MIN_SELECTED) {
0439                 *outSelectionIterator.rawData() =
0440                     selectionPolicy.opacityFromDifference(
0441                         differencePolicy.difference(referenceDeviceIterator.rawDataConst())
0442                     );
0443             }
0444             if (updater) {
0445                 ++numberOfPixelsProcessed;
0446                 if (numberOfPixelsProcessed > numberOfPixelsPerUpdate) {
0447                     numberOfPixelsProcessed = 0;
0448                     updater->setProgress(updater->progress() + progressIncrement);
0449                 }
0450             }
0451         }
0452     } else {
0453         while (referenceDeviceIterator.nextPixel() &&
0454                outSelectionIterator.nextPixel()) {
0455             *outSelectionIterator.rawData() =
0456                 selectionPolicy.opacityFromDifference(
0457                     differencePolicy.difference(referenceDeviceIterator.rawDataConst())
0458                 );
0459             if (updater) {
0460                 ++numberOfPixelsProcessed;
0461                 if (numberOfPixelsProcessed > numberOfPixelsPerUpdate) {
0462                     numberOfPixelsProcessed = 0;
0463                     updater->setProgress(updater->progress() + progressIncrement);
0464                 }
0465             }
0466         }
0467     }
0468     if (updater) {
0469         updater->setProgress(100);
0470     }
0471 }
0472 
0473 void KisFillPainter::createSimilarColorsSelection(KisPixelSelectionSP outSelection,
0474                                                   const KoColor &referenceColor,
0475                                                   KisPaintDeviceSP referenceDevice,
0476                                                   const QRect &rect,
0477                                                   KisPixelSelectionSP mask)
0478 {
0479     if (rect.isEmpty()) {
0480         return;
0481     }
0482 
0483     KoColor srcColor(referenceColor);
0484     srcColor.convertTo(referenceDevice->colorSpace());
0485 
0486     const int pixelSize = referenceDevice->pixelSize();
0487     const int softness = 100 - opacitySpread();
0488 
0489     using namespace KisColorSelectionPolicies;
0490 
0491     if (softness == 0) {
0492         HardSelectionPolicy sp(fillThreshold());
0493         if (pixelSize == 1) {
0494             OptimizedDifferencePolicy<quint8> dp(srcColor, fillThreshold());
0495             createSimilarColorsSelectionImpl(outSelection, referenceDevice, rect, mask, dp, sp);
0496         } else if (pixelSize == 2) {
0497             OptimizedDifferencePolicy<quint16> dp(srcColor, fillThreshold());
0498             createSimilarColorsSelectionImpl(outSelection, referenceDevice, rect, mask, dp, sp);
0499         } else if (pixelSize == 4) {
0500             OptimizedDifferencePolicy<quint32> dp(srcColor, fillThreshold());
0501             createSimilarColorsSelectionImpl(outSelection, referenceDevice, rect, mask, dp, sp);
0502         } else if (pixelSize == 8) {
0503             OptimizedDifferencePolicy<quint64> dp(srcColor, fillThreshold());
0504             createSimilarColorsSelectionImpl(outSelection, referenceDevice, rect, mask, dp, sp);
0505         } else {
0506             SlowDifferencePolicy dp(srcColor, fillThreshold());
0507             createSimilarColorsSelectionImpl(outSelection, referenceDevice, rect, mask, dp, sp);
0508         }
0509     } else {
0510         SoftSelectionPolicy sp(fillThreshold(), softness);
0511         if (pixelSize == 1) {
0512             OptimizedDifferencePolicy<quint8> dp(srcColor, fillThreshold());
0513             createSimilarColorsSelectionImpl(outSelection, referenceDevice, rect, mask, dp, sp);
0514         } else if (pixelSize == 2) {
0515             OptimizedDifferencePolicy<quint16> dp(srcColor, fillThreshold());
0516             createSimilarColorsSelectionImpl(outSelection, referenceDevice, rect, mask, dp, sp);
0517         } else if (pixelSize == 4) {
0518             OptimizedDifferencePolicy<quint32> dp(srcColor, fillThreshold());
0519             createSimilarColorsSelectionImpl(outSelection, referenceDevice, rect, mask, dp, sp);
0520         } else if (pixelSize == 8) {
0521             OptimizedDifferencePolicy<quint64> dp(srcColor, fillThreshold());
0522             createSimilarColorsSelectionImpl(outSelection, referenceDevice, rect, mask, dp, sp);
0523         } else {
0524             SlowDifferencePolicy dp(srcColor, fillThreshold());
0525             createSimilarColorsSelectionImpl(outSelection, referenceDevice, rect, mask, dp, sp);
0526         }
0527     }
0528 }
0529 
0530 QVector<KisStrokeJobData*> KisFillPainter::createSimilarColorsSelectionJobs(
0531     KisPixelSelectionSP outSelection,
0532     const QSharedPointer<KoColor> referenceColor,
0533     KisPaintDeviceSP referenceDevice,
0534     const QRect &rect,
0535     KisPixelSelectionSP mask,
0536     QSharedPointer<KisProcessingVisitor::ProgressHelper> progressHelper
0537 )
0538 {
0539     if (rect.isEmpty()) {
0540         return {};
0541     }
0542 
0543     QVector<KisStrokeJobData*> jobsData;
0544     QVector<QRect> fillPatches =
0545         KritaUtils::splitRectIntoPatches(rect, KritaUtils::optimalPatchSize());
0546     const int threshold = fillThreshold();
0547     const int softness = 100 - opacitySpread();
0548     const int sizemod = this->sizemod();
0549     const bool stopGrowingAtDarkestPixel = this->stopGrowingAtDarkestPixel();
0550     const int feather = this->feather();
0551     const bool antiAlias = this->antiAlias();
0552 
0553     KritaUtils::addJobBarrier(jobsData, nullptr);
0554 
0555     for (const QRect &patch : fillPatches) {
0556         KritaUtils::addJobConcurrent(
0557             jobsData,
0558             [referenceDevice, outSelection, mask, referenceColor,
0559              threshold, softness, patch, progressHelper]() mutable
0560             {
0561                 if (patch.isEmpty()) {
0562                     return;
0563                 }
0564 
0565                 KoUpdater *updater = progressHelper ? progressHelper->updater() : nullptr;
0566 
0567                 using namespace KisColorSelectionPolicies;
0568 
0569                 const int pixelSize = referenceDevice->pixelSize();
0570                 KoColor srcColor(*referenceColor);
0571                 srcColor.convertTo(referenceDevice->colorSpace());
0572 
0573                 if (softness == 0) {
0574                     HardSelectionPolicy sp(threshold);
0575                     if (pixelSize == 1) {
0576                         OptimizedDifferencePolicy<quint8> dp(srcColor, threshold);
0577                         createSimilarColorsSelectionImpl(outSelection, referenceDevice, patch, mask, dp, sp, updater);
0578                     } else if (pixelSize == 2) {
0579                         OptimizedDifferencePolicy<quint16> dp(srcColor, threshold);
0580                         createSimilarColorsSelectionImpl(outSelection, referenceDevice, patch, mask, dp, sp, updater);
0581                     } else if (pixelSize == 4) {
0582                         OptimizedDifferencePolicy<quint32> dp(srcColor, threshold);
0583                         createSimilarColorsSelectionImpl(outSelection, referenceDevice, patch, mask, dp, sp, updater);
0584                     } else if (pixelSize == 8) {
0585                         OptimizedDifferencePolicy<quint64> dp(srcColor, threshold);
0586                         createSimilarColorsSelectionImpl(outSelection, referenceDevice, patch, mask, dp, sp, updater);
0587                     } else {
0588                         SlowDifferencePolicy dp(srcColor, threshold);
0589                         createSimilarColorsSelectionImpl(outSelection, referenceDevice, patch, mask, dp, sp, updater);
0590                     }
0591                 } else {
0592                     SoftSelectionPolicy sp(threshold, softness);
0593                     if (pixelSize == 1) {
0594                         OptimizedDifferencePolicy<quint8> dp(srcColor, threshold);
0595                         createSimilarColorsSelectionImpl(outSelection, referenceDevice, patch, mask, dp, sp, updater);
0596                     } else if (pixelSize == 2) {
0597                         OptimizedDifferencePolicy<quint16> dp(srcColor, threshold);
0598                         createSimilarColorsSelectionImpl(outSelection, referenceDevice, patch, mask, dp, sp, updater);
0599                     } else if (pixelSize == 4) {
0600                         OptimizedDifferencePolicy<quint32> dp(srcColor, threshold);
0601                         createSimilarColorsSelectionImpl(outSelection, referenceDevice, patch, mask, dp, sp, updater);
0602                     } else if (pixelSize == 8) {
0603                         OptimizedDifferencePolicy<quint64> dp(srcColor, threshold);
0604                         createSimilarColorsSelectionImpl(outSelection, referenceDevice, patch, mask, dp, sp, updater);
0605                     } else {
0606                         SlowDifferencePolicy dp(srcColor, threshold);
0607                         createSimilarColorsSelectionImpl(outSelection, referenceDevice, patch, mask, dp, sp, updater);
0608                     }
0609                 }
0610             }
0611         );
0612     }
0613 
0614     KritaUtils::addJobSequential(
0615         jobsData,
0616         [outSelection, referenceDevice, mask,
0617          sizemod, stopGrowingAtDarkestPixel, feather, antiAlias, progressHelper]() mutable
0618         {
0619             KoUpdater *updater = progressHelper ? progressHelper->updater() : nullptr;
0620 
0621             if (sizemod > 0) {
0622                 if (stopGrowingAtDarkestPixel) {
0623                     KisGrowUntilDarkestPixelSelectionFilter biggy(sizemod, referenceDevice);
0624                     biggy.process(outSelection, outSelection->selectedRect().adjusted(-sizemod, -sizemod, sizemod, sizemod));
0625                 } else {
0626                     KisGrowSelectionFilter biggy(sizemod, sizemod);
0627                     biggy.process(outSelection, outSelection->selectedRect().adjusted(-sizemod, -sizemod, sizemod, sizemod));
0628                 }
0629             } else if (sizemod < 0) {
0630                 KisShrinkSelectionFilter tiny(-sizemod, -sizemod, false);
0631                 tiny.process(outSelection, outSelection->selectedRect());
0632             }
0633             if (updater) {
0634                 updater->setProgress(33);
0635             }
0636 
0637             // Since the feathering already smooths the selection, the antiAlias
0638             // is not applied if we must feather
0639             if (feather > 0) {
0640                 KisFeatherSelectionFilter feathery(feather);
0641                 feathery.process(outSelection, outSelection->selectedRect().adjusted(-feather, -feather, feather, feather));
0642             } else if (antiAlias) {
0643                 KisAntiAliasSelectionFilter antiAliasFilter;
0644                 antiAliasFilter.process(outSelection, outSelection->selectedRect());
0645             }
0646             if (updater) {
0647                 updater->setProgress(66);
0648             }
0649 
0650             if (mask) {
0651                 outSelection->applySelection(mask, SELECTION_INTERSECT);
0652             }
0653             if (updater) {
0654                 updater->setProgress(100);
0655             }
0656         }
0657     );
0658 
0659     return jobsData;
0660 }