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 }