File indexing completed on 2024-05-12 15:58:15

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 
0047 KisFillPainter::KisFillPainter()
0048         : KisPainter()
0049 {
0050     initFillPainter();
0051 }
0052 
0053 KisFillPainter::KisFillPainter(KisPaintDeviceSP device)
0054         : KisPainter(device)
0055 {
0056     initFillPainter();
0057 }
0058 
0059 KisFillPainter::KisFillPainter(KisPaintDeviceSP device, KisSelectionSP selection)
0060         : KisPainter(device, selection)
0061 {
0062     initFillPainter();
0063 }
0064 
0065 void KisFillPainter::initFillPainter()
0066 {
0067     m_width = m_height = -1;
0068     m_careForSelection = false;
0069     m_sizemod = 0;
0070     m_feather = 0;
0071     m_useCompositioning = false;
0072     m_threshold = 0;
0073     m_opacitySpread = 0;
0074     m_useSelectionAsBoundary = false;
0075     m_antiAlias = false;
0076 }
0077 
0078 void KisFillPainter::fillSelection(const QRect &rc, const KoColor &color)
0079 {
0080     KisPaintDeviceSP fillDevice = new KisPaintDevice(device()->colorSpace());
0081     fillDevice->setDefaultPixel(color);
0082 
0083     bitBlt(rc.topLeft(), fillDevice, rc);
0084 }
0085 
0086 // 'regular' filling
0087 // XXX: This also needs renaming, since filling ought to keep the opacity and the composite op in mind,
0088 //      this is more eraseToColor.
0089 void KisFillPainter::fillRect(qint32 x1, qint32 y1, qint32 w, qint32 h, const KoColor& kc, quint8 opacity)
0090 {
0091     if (w > 0 && h > 0) {
0092         // Make sure we're in the right colorspace
0093 
0094         KoColor kc2(kc); // get rid of const
0095         kc2.convertTo(device()->colorSpace());
0096         quint8 * data = kc2.data();
0097         device()->colorSpace()->setOpacity(data, opacity, 1);
0098 
0099         device()->fill(x1, y1, w, h, data);
0100 
0101         addDirtyRect(QRect(x1, y1, w, h));
0102     }
0103 }
0104 
0105 void KisFillPainter::fillRect(const QRect &rc, const KoPatternSP pattern, const QPoint &offset)
0106 {
0107     fillRect(rc.x(), rc.y(), rc.width(), rc.height(), pattern, offset);
0108 }
0109 
0110 void KisFillPainter::fillRect(qint32 x1, qint32 y1, qint32 w, qint32 h, const KoPatternSP pattern, const QPoint &offset)
0111 {
0112     if (!pattern) return;
0113     if (!pattern->valid()) return;
0114     if (!device()) return;
0115     if (w < 1) return;
0116     if (h < 1) return;
0117 
0118     KisPaintDeviceSP patternLayer = new KisPaintDevice(device()->compositionSourceColorSpace(), pattern->name());
0119     patternLayer->convertFromQImage(pattern->pattern(), 0);
0120 
0121     if (!offset.isNull()) {
0122         patternLayer->moveTo(offset);
0123     }
0124 
0125     fillRect(x1, y1, w, h, patternLayer, QRect(offset.x(), offset.y(), pattern->width(), pattern->height()));
0126 }
0127 
0128 void KisFillPainter::fillRectNoCompose(const QRect &rc, const KoPatternSP pattern, const QTransform transform)
0129 {
0130     if (!pattern) return;
0131     if (!pattern->valid()) return;
0132     if (!device()) return;
0133     if (rc.width() < 1) return;
0134     if (rc.height() < 1) return;
0135 
0136     KisPaintDeviceSP patternLayer = new KisPaintDevice(device()->colorSpace(), pattern->name());
0137     patternLayer->convertFromQImage(pattern->pattern(), 0);
0138 
0139     fillRectNoCompose(rc.x(), rc.y(), rc.width(), rc.height(), patternLayer, QRect(0, 0, pattern->width(), pattern->height()), transform);
0140 }
0141 
0142 void KisFillPainter::fillRectNoCompose(qint32 x1, qint32 y1, qint32 w, qint32 h, const KisPaintDeviceSP device, const QRect& deviceRect, const QTransform transform)
0143 {
0144     /**
0145      * Since this function doesn't do any kind of compostiting, so the pixel size
0146      * of the source and destination devices must be exactly the same. The color
0147      * space should ideally be also the same.
0148      */
0149     KIS_SAFE_ASSERT_RECOVER_RETURN(device->pixelSize() == this->device()->pixelSize());
0150     KIS_SAFE_ASSERT_RECOVER_NOOP(*device->colorSpace() == *this->device()->colorSpace());
0151 
0152     KisPaintDeviceSP wrapped = device;
0153     KisDefaultBoundsBaseSP oldBounds = wrapped->defaultBounds();
0154     wrapped->setDefaultBounds(new KisWrapAroundBoundsWrapper(oldBounds, deviceRect));
0155 
0156     KisPerspectiveTransformWorker worker(this->device(), transform, false, this->progressUpdater());
0157     worker.runPartialDst(device, this->device(), QRect(x1, y1, w, h));
0158 
0159     addDirtyRect(QRect(x1, y1, w, h));
0160     wrapped->setDefaultBounds(oldBounds);
0161 }
0162 
0163 void KisFillPainter::fillRect(const QRect &rc, const KisPaintDeviceSP device, const QRect& deviceRect)
0164 {
0165     fillRect(rc.x(), rc.y(), rc.width(), rc.height(), device, deviceRect);
0166 }
0167 
0168 void KisFillPainter::fillRect(qint32 x1, qint32 y1, qint32 w, qint32 h, const KisPaintDeviceSP device, const QRect& deviceRect)
0169 {
0170     const QRect &patternRect = deviceRect;
0171     const QRect fillRect(x1, y1, w, h);
0172 
0173     auto toPatternLocal = [](int value, int offset, int width) {
0174         const int normalizedValue = value - offset;
0175         return offset + (normalizedValue >= 0 ?
0176                          normalizedValue % width :
0177                          width - (-normalizedValue - 1) % width - 1);
0178     };
0179 
0180     int dstY = fillRect.y();
0181     while (dstY <= fillRect.bottom()) {
0182         const int dstRowsRemaining = fillRect.bottom() - dstY + 1;
0183 
0184         const int srcY = toPatternLocal(dstY, patternRect.y(), patternRect.height());
0185         const int height = qMin(patternRect.height() - srcY + patternRect.y(), dstRowsRemaining);
0186 
0187         int dstX = fillRect.x();
0188         while (dstX <= fillRect.right()) {
0189             const int dstColumnsRemaining = fillRect.right() - dstX + 1;
0190 
0191             const int srcX = toPatternLocal(dstX, patternRect.x(), patternRect.width());
0192             const int width = qMin(patternRect.width() - srcX  + patternRect.x(), dstColumnsRemaining);
0193 
0194             bitBlt(dstX, dstY, device, srcX, srcY, width, height);
0195 
0196             dstX += width;
0197         }
0198         dstY += height;
0199     }
0200 
0201     addDirtyRect(QRect(x1, y1, w, h));
0202 }
0203 
0204 void KisFillPainter::fillRect(qint32 x1, qint32 y1, qint32 w, qint32 h, const KisFilterConfigurationSP generator)
0205 {
0206     if (!generator) return;
0207     KisGeneratorSP g = KisGeneratorRegistry::instance()->value(generator->name());
0208     if (!device()) return;
0209     if (w < 1) return;
0210     if (h < 1) return;
0211 
0212     QRect tmpRc(x1, y1, w, h);
0213 
0214     KisProcessingInformation dstCfg(device(), tmpRc.topLeft(), 0);
0215 
0216     g->generate(dstCfg, tmpRc.size(), generator);
0217 
0218     addDirtyRect(tmpRc);
0219 }
0220 
0221 // flood filling
0222 
0223 void KisFillPainter::fillColor(int startX, int startY, KisPaintDeviceSP sourceDevice)
0224 {
0225     if (!m_useCompositioning) {
0226         if (m_sizemod || m_feather ||
0227             compositeOpId() != COMPOSITE_OVER ||
0228             opacity() != MAX_SELECTED ||
0229             sourceDevice != device()) {
0230 
0231             warnKrita << "WARNING: Fast Flood Fill (no compositioning mode)"
0232                        << "does not support compositeOps, opacity, "
0233                        << "selection enhancements and separate source "
0234                        << "devices";
0235         }
0236 
0237         QRect fillBoundsRect(0, 0, m_width, m_height);
0238         QPoint startPoint(startX, startY);
0239 
0240         if (!fillBoundsRect.contains(startPoint)) return;
0241 
0242         KisScanlineFill gc(device(), startPoint, fillBoundsRect);
0243         gc.setThreshold(m_threshold);
0244         gc.fillColor(paintColor());
0245 
0246     } else {
0247         genericFillStart(startX, startY, sourceDevice);
0248 
0249         // Now create a layer and fill it
0250         KisPaintDeviceSP filled = device()->createCompositionSourceDevice();
0251         Q_CHECK_PTR(filled);
0252         KisFillPainter painter(filled);
0253         painter.fillRect(0, 0, m_width, m_height, paintColor());
0254         painter.end();
0255 
0256         genericFillEnd(filled);
0257     }
0258 }
0259 
0260 void KisFillPainter::fillPattern(int startX, int startY, KisPaintDeviceSP sourceDevice, QTransform patternTransform)
0261 {
0262     genericFillStart(startX, startY, sourceDevice);
0263 
0264     // Now create a layer and fill it
0265     KisPaintDeviceSP filled = device()->createCompositionSourceDevice();
0266     Q_CHECK_PTR(filled);
0267     KisFillPainter painter(filled);
0268     painter.fillRectNoCompose(QRect(0, 0, m_width, m_height), pattern(), patternTransform);
0269     painter.end();
0270 
0271     genericFillEnd(filled);
0272 }
0273 
0274 void KisFillPainter::genericFillStart(int startX, int startY, KisPaintDeviceSP sourceDevice)
0275 {
0276     Q_ASSERT(m_width > 0);
0277     Q_ASSERT(m_height > 0);
0278 
0279     // Create a selection from the surrounding area
0280 
0281     KisPixelSelectionSP pixelSelection = createFloodSelection(startX, startY, sourceDevice,
0282                                                               (selection().isNull() ? 0 : selection()->pixelSelection()));
0283     KisSelectionSP newSelection = new KisSelection(pixelSelection->defaultBounds());
0284     newSelection->pixelSelection()->applySelection(pixelSelection, SELECTION_REPLACE);
0285     m_fillSelection = newSelection;
0286 }
0287 
0288 void KisFillPainter::genericFillEnd(KisPaintDeviceSP filled)
0289 {
0290     if (progressUpdater() && progressUpdater()->interrupted()) {
0291         m_width = m_height = -1;
0292         return;
0293     }
0294 
0295 //  TODO: filling using the correct bound of the selection would be better, *but*
0296 //  the selection is limited to the exact bound of a layer, while in reality, we don't
0297 //  want that, since we want a transparent layer to be completely filled
0298 //     QRect rc = m_fillSelection->selectedExactRect();
0299 
0300 
0301     /**
0302      * Apply the real selection to a filled one
0303      */
0304     KisSelectionSP realSelection = selection();
0305     QRect rc;
0306 
0307     if (realSelection) {
0308         rc = m_fillSelection->selectedExactRect().intersected(realSelection->projection()->selectedExactRect());
0309         m_fillSelection->pixelSelection()->applySelection(
0310             realSelection->projection(), SELECTION_INTERSECT);
0311     } else {
0312         rc = m_fillSelection->selectedExactRect();
0313     }
0314 
0315     setSelection(m_fillSelection);
0316     bitBlt(rc.topLeft(), filled, rc);
0317     setSelection(realSelection);
0318 
0319     if (progressUpdater()) progressUpdater()->setProgress(100);
0320 
0321     m_width = m_height = -1;
0322 }
0323 
0324 KisPixelSelectionSP KisFillPainter::createFloodSelection(int startX, int startY, KisPaintDeviceSP sourceDevice,
0325                                                          KisPaintDeviceSP existingSelection)
0326 {
0327     KisPixelSelectionSP newSelection = new KisPixelSelection(new KisSelectionDefaultBounds(device()));
0328     return createFloodSelection(newSelection, startX, startY, sourceDevice, existingSelection);
0329 }
0330 
0331 KisPixelSelectionSP KisFillPainter::createFloodSelection(KisPixelSelectionSP pixelSelection, int startX, int startY,
0332                                                          KisPaintDeviceSP sourceDevice, KisPaintDeviceSP existingSelection)
0333 {
0334 
0335     if (m_width < 0 || m_height < 0) {
0336         if (selection() && m_careForSelection) {
0337             QRect rc = selection()->selectedExactRect();
0338             m_width = rc.width() - (startX - rc.x());
0339             m_height = rc.height() - (startY - rc.y());
0340         }
0341     }
0342     dbgImage << "Width: " << m_width << " Height: " << m_height;
0343     // Otherwise the width and height should have been set
0344     Q_ASSERT(m_width > 0 && m_height > 0);
0345 
0346     QRect fillBoundsRect(0, 0, m_width, m_height);
0347     QPoint startPoint(startX, startY);
0348 
0349     if (!fillBoundsRect.contains(startPoint)) {
0350         return pixelSelection;
0351     }
0352 
0353     KisScanlineFill gc(sourceDevice, startPoint, fillBoundsRect);
0354     gc.setThreshold(m_threshold);
0355     gc.setOpacitySpread(m_useCompositioning ? m_opacitySpread : 100);
0356     if (m_useSelectionAsBoundary && !pixelSelection.isNull()) {
0357         gc.fillSelectionWithBoundary(pixelSelection, existingSelection);
0358     } else {
0359         gc.fillSelection(pixelSelection);
0360     }
0361 
0362     if (m_useCompositioning) {
0363         if (m_sizemod > 0) {
0364             KisGrowSelectionFilter biggy(m_sizemod, m_sizemod);
0365             biggy.process(pixelSelection, pixelSelection->selectedRect().adjusted(-m_sizemod, -m_sizemod, m_sizemod, m_sizemod));
0366         }
0367         else if (m_sizemod < 0) {
0368             KisShrinkSelectionFilter tiny(-m_sizemod, -m_sizemod, false);
0369             tiny.process(pixelSelection, pixelSelection->selectedRect());
0370         }
0371         // Since the feathering already smooths the selection, the antiAlias
0372         // is not applied if we must feather
0373         if (m_feather > 0) {
0374             KisFeatherSelectionFilter feathery(m_feather);
0375             feathery.process(pixelSelection, pixelSelection->selectedRect().adjusted(-m_feather, -m_feather, m_feather, m_feather));
0376         } else if (m_antiAlias) {
0377             KisAntiAliasSelectionFilter antiAliasFilter;
0378             antiAliasFilter.process(pixelSelection, pixelSelection->selectedRect());
0379         }
0380     }
0381 
0382     return pixelSelection;
0383 }