diff --git a/app/src/tooloptionwidget.cpp b/app/src/tooloptionwidget.cpp index 11fdba46f7..23e09e66c2 100644 --- a/app/src/tooloptionwidget.cpp +++ b/app/src/tooloptionwidget.cpp @@ -78,6 +78,7 @@ void ToolOptionWidget::updateUI() setPreserveAlpha(p.preserveAlpha); setVectorMergeEnabled(p.vectorMergeEnabled); setAA(p.useAA); + setFillMode(p.fillMode); setStabilizerLevel(p.stabilizerLevel); setTolerance(static_cast(p.tolerance)); setFillContour(p.useFillContour); @@ -109,6 +110,8 @@ void ToolOptionWidget::makeConnectionToEditor(Editor* editor) connect(ui->vectorMergeBox, &QCheckBox::clicked, toolManager, &ToolManager::setVectorMergeEnabled); connect(ui->useAABox, &QCheckBox::clicked, toolManager, &ToolManager::setAA); + connect(ui->fillMode, static_cast(&QComboBox::activated), toolManager, &ToolManager::setFillMode); + connect(ui->inpolLevelsCombo, static_cast(&QComboBox::activated), toolManager, &ToolManager::setStabilizerLevel); connect(ui->toleranceSlider, &SpinSlider::valueChanged, toolManager, &ToolManager::setTolerance); @@ -135,6 +138,7 @@ void ToolOptionWidget::onToolPropertyChanged(ToolType, ToolPropertyType ePropert case PRESERVEALPHA: setPreserveAlpha(p.preserveAlpha); break; case VECTORMERGE: setVectorMergeEnabled(p.vectorMergeEnabled); break; case ANTI_ALIASING: setAA(p.useAA); break; + case FILL_MODE: setFillMode(p.fillMode); break; case STABILIZATION: setStabilizerLevel(p.stabilizerLevel); break; case TOLERANCE: setTolerance(static_cast(p.tolerance)); break; case FILLCONTOUR: setFillContour(p.useFillContour); break; @@ -157,6 +161,7 @@ void ToolOptionWidget::setVisibility(BaseTool* tool) ui->makeInvisibleBox->setVisible(tool->isPropertyEnabled(INVISIBILITY)); ui->preserveAlphaBox->setVisible(tool->isPropertyEnabled(PRESERVEALPHA)); ui->useAABox->setVisible(tool->isPropertyEnabled(ANTI_ALIASING)); + ui->fillModeGroup->setVisible(tool->isPropertyEnabled(FILL_MODE)); ui->stabilizerLabel->setVisible(tool->isPropertyEnabled(STABILIZATION)); ui->inpolLevelsCombo->setVisible(tool->isPropertyEnabled(STABILIZATION)); ui->toleranceSlider->setVisible(tool->isPropertyEnabled(TOLERANCE)); @@ -187,6 +192,7 @@ void ToolOptionWidget::setVisibility(BaseTool* tool) ui->sizeSlider->setLabel(tr("Stroke Thickness")); ui->toleranceSlider->setVisible(false); ui->toleranceSpinBox->setVisible(false); + ui->fillModeGroup->setVisible(false); break; default: ui->sizeSlider->setLabel(tr("Width")); @@ -299,6 +305,11 @@ void ToolOptionWidget::setAA(int x) } } +void ToolOptionWidget::setFillMode(int x) +{ + ui->fillMode->setCurrentIndex(qBound(0, x, ui->fillMode->count() - 1)); +} + void ToolOptionWidget::setStabilizerLevel(int x) { ui->inpolLevelsCombo->setCurrentIndex(qBound(0, x, ui->inpolLevelsCombo->count() - 1)); @@ -341,6 +352,7 @@ void ToolOptionWidget::disableAllOptions() ui->preserveAlphaBox->hide(); ui->vectorMergeBox->hide(); ui->useAABox->hide(); + ui->fillModeGroup->hide(); ui->inpolLevelsCombo->hide(); ui->toleranceSlider->hide(); ui->toleranceSpinBox->hide(); diff --git a/app/src/tooloptionwidget.h b/app/src/tooloptionwidget.h index 7733ac9929..968a1b263d 100644 --- a/app/src/tooloptionwidget.h +++ b/app/src/tooloptionwidget.h @@ -61,6 +61,7 @@ public slots: void setPreserveAlpha(int); void setVectorMergeEnabled(int); void setAA(int); + void setFillMode(int); void setStabilizerLevel(int); void setTolerance(int); void setFillContour(int); diff --git a/app/ui/tooloptions.ui b/app/ui/tooloptions.ui index 0b576fe525..0bcd20d442 100644 --- a/app/ui/tooloptions.ui +++ b/app/ui/tooloptions.ui @@ -47,21 +47,6 @@ - - 6 - - - 0 - - - 0 - - - 0 - - - 2 - @@ -241,6 +226,54 @@ + + + + + 0 + + + 0 + + + 0 + + + 0 + + + + + Transparency + + + + + + + + 0 + 0 + + + + Defines how the fill will behave when the new color is not opaque + + + + Overlay + + + + + Replace + + + + + + + diff --git a/core_lib/src/graphics/bitmap/bitmapimage.cpp b/core_lib/src/graphics/bitmap/bitmapimage.cpp index d5dc50ef8f..cdee543a32 100644 --- a/core_lib/src/graphics/bitmap/bitmapimage.cpp +++ b/core_lib/src/graphics/bitmap/bitmapimage.cpp @@ -790,16 +790,16 @@ bool BitmapImage::compareColor(QRgb newColor, QRgb oldColor, int tolerance, QHas // Flood fill // ----- http://lodev.org/cgtutor/floodfill.html -void BitmapImage::floodFill(BitmapImage* targetImage, +BitmapImage* BitmapImage::floodFill(BitmapImage* targetImage, QRect cameraRect, QPoint point, - QRgb newColor, + QRgb fillColor, int tolerance) { // If the point we are supposed to fill is outside the image and camera bounds, do nothing if(!cameraRect.united(targetImage->bounds()).contains(point)) { - return; + return nullptr; } // Square tolerance for use with compareColor @@ -844,11 +844,11 @@ void BitmapImage::floodFill(BitmapImage* targetImage, spanLeft = spanRight = false; while (xTemp <= targetImage->mBounds.right() && compareColor(targetImage->constScanLine(xTemp, point.y()), oldColor, tolerance, cache.data()) && - newPlacedColor != newColor) + newPlacedColor != fillColor) { // Set pixel color - replaceImage->scanLine(xTemp, point.y(), newColor); + replaceImage->scanLine(xTemp, point.y(), fillColor); if (!spanLeft && (point.y() > targetImage->mBounds.top()) && compareColor(targetImage->constScanLine(xTemp, point.y() - 1), oldColor, tolerance, cache.data())) { @@ -876,7 +876,5 @@ void BitmapImage::floodFill(BitmapImage* targetImage, } } - targetImage->paste(replaceImage); - targetImage->modification(); - delete replaceImage; + return replaceImage; } diff --git a/core_lib/src/graphics/bitmap/bitmapimage.h b/core_lib/src/graphics/bitmap/bitmapimage.h index eb3552a262..02522db41d 100644 --- a/core_lib/src/graphics/bitmap/bitmapimage.h +++ b/core_lib/src/graphics/bitmap/bitmapimage.h @@ -75,7 +75,7 @@ class BitmapImage : public KeyFrame void clear(QRectF rectangle) { clear(rectangle.toRect()); } static inline bool compareColor(QRgb newColor, QRgb oldColor, int tolerance, QHash *cache); - static void floodFill(BitmapImage* targetImage, QRect cameraRect, QPoint point, QRgb newColor, int tolerance); + static BitmapImage* floodFill(BitmapImage* targetImage, QRect cameraRect, QPoint point, QRgb newColor, int tolerance); void drawLine(QPointF P1, QPointF P2, QPen pen, QPainter::CompositionMode cm, bool antialiasing); void drawRect(QRectF rectangle, QPen pen, QBrush brush, QPainter::CompositionMode cm, bool antialiasing); diff --git a/core_lib/src/managers/toolmanager.cpp b/core_lib/src/managers/toolmanager.cpp index a28605a58d..91615def27 100644 --- a/core_lib/src/managers/toolmanager.cpp +++ b/core_lib/src/managers/toolmanager.cpp @@ -194,6 +194,12 @@ void ToolManager::setAA(int usingAA) emit toolPropertyChanged(currentTool()->type(), ANTI_ALIASING); } +void ToolManager::setFillMode(int mode) +{ + currentTool()->setFillMode(mode); + emit toolPropertyChanged(currentTool()->type(), FILL_MODE); +} + void ToolManager::setStabilizerLevel(int level) { currentTool()->setStabilizerLevel(level); diff --git a/core_lib/src/managers/toolmanager.h b/core_lib/src/managers/toolmanager.h index 16666cef47..4a552f315f 100644 --- a/core_lib/src/managers/toolmanager.h +++ b/core_lib/src/managers/toolmanager.h @@ -66,6 +66,7 @@ public slots: void setBezier(bool); void setPressure(bool); void setAA(int); + void setFillMode(int); void setStabilizerLevel(int); void setTolerance(int); void setUseFillContour(bool); diff --git a/core_lib/src/tool/basetool.cpp b/core_lib/src/tool/basetool.cpp index fe7ba6ebe9..54890694a4 100644 --- a/core_lib/src/tool/basetool.cpp +++ b/core_lib/src/tool/basetool.cpp @@ -63,6 +63,7 @@ BaseTool::BaseTool(QObject* parent) : QObject(parent) mPropertyEnabled.insert(PRESERVEALPHA, false); mPropertyEnabled.insert(BEZIER, false); mPropertyEnabled.insert(ANTI_ALIASING, false); + mPropertyEnabled.insert(FILL_MODE, false); mPropertyEnabled.insert(STABILIZATION, false); } @@ -443,6 +444,11 @@ void BaseTool::setAA(const int useAA) properties.useAA = useAA; } +void BaseTool::setFillMode(const int mode) +{ + properties.fillMode = mode; +} + void BaseTool::setStabilizerLevel(const int level) { properties.stabilizerLevel = level; diff --git a/core_lib/src/tool/basetool.h b/core_lib/src/tool/basetool.h index 0334ac9986..2d1a87487e 100644 --- a/core_lib/src/tool/basetool.h +++ b/core_lib/src/tool/basetool.h @@ -47,6 +47,7 @@ class Properties bool bezier_state = false; bool useFeather = true; int useAA = 0; + int fillMode = 0; int stabilizerLevel = 0; qreal tolerance = 0; bool useFillContour = false; @@ -113,6 +114,7 @@ class BaseTool : public QObject virtual void setPreserveAlpha(const bool preserveAlpha); virtual void setVectorMergeEnabled(const bool vectorMergeEnabled); virtual void setAA(const int useAA); + virtual void setFillMode(const int mode); virtual void setStabilizerLevel(const int level); virtual void setTolerance(const int tolerance); virtual void setUseFillContour(const bool useFillContour); diff --git a/core_lib/src/tool/buckettool.cpp b/core_lib/src/tool/buckettool.cpp index 8bd252ad63..ea0b6c58a7 100644 --- a/core_lib/src/tool/buckettool.cpp +++ b/core_lib/src/tool/buckettool.cpp @@ -18,6 +18,7 @@ GNU General Public License for more details. #include #include +#include #include #include #include "pointerevent.h" @@ -47,6 +48,7 @@ void BucketTool::loadSettings() { mPropertyEnabled[TOLERANCE] = true; mPropertyEnabled[WIDTH] = true; + mPropertyEnabled[FILL_MODE] = true; QSettings settings(PENCIL2D, PENCIL2D); @@ -54,6 +56,7 @@ void BucketTool::loadSettings() properties.feather = 10; properties.stabilizerLevel = StabilizationLevel::NONE; properties.useAA = DISABLED; + properties.fillMode = settings.value("fillMode", 0).toInt(); properties.tolerance = settings.value("tolerance", 32.0).toDouble(); } @@ -61,6 +64,7 @@ void BucketTool::resetToDefault() { setWidth(4.0); setTolerance(32.0); + setFillMode(0); } QCursor BucketTool::cursor() @@ -79,6 +83,17 @@ QCursor BucketTool::cursor() } } +void BucketTool::setTolerance(const int tolerance) +{ + // Set current property + properties.tolerance = tolerance; + + // Update settings + QSettings settings(PENCIL2D, PENCIL2D); + settings.setValue("tolerance", tolerance); + settings.sync(); +} + /** * @brief BrushTool::setWidth * @param width @@ -95,14 +110,14 @@ void BucketTool::setWidth(const qreal width) settings.sync(); } -void BucketTool::setTolerance(const int tolerance) +void BucketTool::setFillMode(int mode) { // Set current property - properties.tolerance = tolerance; + properties.fillMode = mode; // Update settings QSettings settings(PENCIL2D, PENCIL2D); - settings.setValue("tolerance", tolerance); + settings.setValue("fillMode", mode); settings.sync(); } @@ -169,11 +184,68 @@ void BucketTool::paintBitmap(Layer* layer) QPoint point = QPoint(qFloor(getLastPoint().x()), qFloor(getLastPoint().y())); QRect cameraRect = mScribbleArea->getCameraRect().toRect(); - BitmapImage::floodFill(targetImage, - cameraRect, - point, - qPremultiply(mEditor->color()->frontColor().rgba()), - properties.tolerance); + + QRgb fillColor = qPremultiply(mEditor->color()->frontColor().rgba()); + QRgb origColor = fillColor; + if (properties.fillMode == 0) + { + if (qAlpha(fillColor) == 0) + { + // Filling in overlay mode with a fully transparent color has no + // effect, so we can skip it in this case + return; + } + } + else if (properties.fillMode == 1) + { + // Pass a fully opaque version of the new color to floodFill + // This is required so we can fully mask out the existing data before + // writing the new color. + QColor tempColor; + tempColor.setRgba(fillColor); + tempColor.setAlphaF(1); + fillColor = tempColor.rgba(); + } + + std::unique_ptr fillImage( + BitmapImage::floodFill(targetImage, + cameraRect, + point, + fillColor, + properties.tolerance)); + + if (fillImage == nullptr) + { + // Nothing was filled for whatever reason + return; + } + + switch(properties.fillMode) + { + default: + case 0: // Overlay mode + // Write fill image on top of target image + targetImage->paste(fillImage.get()); + break; + case 1: // Replace mode + if (qAlpha(origColor) == 0xFF) + { + // When the new color is fully opaque, replace mode + // behaves exactly like overlay mode, and origColor == fillColor + targetImage->paste(fillImage.get()); + } + else + { + // Clearly all pixels in the to-be-filled region from the target image + targetImage->paste(fillImage.get(), QPainter::CompositionMode_DestinationOut); + // Reduce the opacity of the fill to match the new color + BitmapImage properColor(targetImage->bounds(), QColor::fromRgba(origColor)); + properColor.paste(fillImage.get(), QPainter::CompositionMode_DestinationIn); + // Write reduced-opacity fill image on top of target image + targetImage->paste(&properColor); + } + break; + } mScribbleArea->setModified(layerNumber, mEditor->currentFrame()); } diff --git a/core_lib/src/tool/buckettool.h b/core_lib/src/tool/buckettool.h index 900e7e44cb..b8331cff8c 100644 --- a/core_lib/src/tool/buckettool.h +++ b/core_lib/src/tool/buckettool.h @@ -41,6 +41,7 @@ class BucketTool : public StrokeTool void setTolerance(const int tolerance) override; void setWidth(const qreal width) override; + void setFillMode(int mode) override; void paintBitmap(Layer* layer); void paintVector(Layer* layer); diff --git a/core_lib/src/util/pencildef.h b/core_lib/src/util/pencildef.h index 9f01776709..6c75527d65 100644 --- a/core_lib/src/util/pencildef.h +++ b/core_lib/src/util/pencildef.h @@ -55,6 +55,7 @@ enum ToolPropertyType USEFEATHER, VECTORMERGE, ANTI_ALIASING, + FILL_MODE, STABILIZATION, TOLERANCE, FILLCONTOUR