From 35a655cf1962046969b1ae9b6a2b3b98e98a0a24 Mon Sep 17 00:00:00 2001 From: MrStevns Date: Mon, 28 Aug 2023 19:49:21 +0200 Subject: [PATCH] Rework drag to fill logic --- core_lib/src/graphics/bitmap/bitmapbucket.cpp | 66 +++++++++++-------- core_lib/src/graphics/bitmap/bitmapbucket.h | 11 +++- 2 files changed, 48 insertions(+), 29 deletions(-) diff --git a/core_lib/src/graphics/bitmap/bitmapbucket.cpp b/core_lib/src/graphics/bitmap/bitmapbucket.cpp index 1cf3c18e4..d6114445c 100644 --- a/core_lib/src/graphics/bitmap/bitmapbucket.cpp +++ b/core_lib/src/graphics/bitmap/bitmapbucket.cpp @@ -48,68 +48,82 @@ BitmapBucket::BitmapBucket(Editor* editor, mTargetFillToLayerIndex = initialLayerIndex; mTolerance = mProperties.toleranceEnabled ? static_cast(mProperties.tolerance) : 0; + const QPoint& point = QPoint(qFloor(fillPoint.x()), qFloor(fillPoint.y())); Q_ASSERT(mTargetFillToLayer); - mReferenceImage = *static_cast(initialLayer->getLastKeyFrameAtPosition(frameIndex)); + BitmapImage singleLayerImage = *static_cast(initialLayer->getLastKeyFrameAtPosition(frameIndex)); if (properties.bucketFillReferenceMode == 1) // All layers { mReferenceImage = flattenBitmapLayersToImage(); + } else { + mReferenceImage = singleLayerImage; } - const QPoint point = QPoint(qFloor(fillPoint.x()), qFloor(fillPoint.y())); mStartReferenceColor = mReferenceImage.constScanLine(point.x(), point.y()); + mUseFillToDrag = canUseFillToDrag(point, color, mProperties, singleLayerImage); mPixelCache = new QHash(); } -bool BitmapBucket::allowFill(const QPoint& checkPoint) const +bool BitmapBucket::canUseFillToDrag(const QPoint& fillPoint, const QColor& bucketColor, const Properties& properties, const BitmapImage& referenceImage) { - if (mProperties.fillMode == 0 && qAlpha(mBucketColor) == 0) - { - // Filling in overlay mode with a fully transparent color has no - // effect, so we can skip it in this case + QRgb pressReferenceColorSingleLayer = referenceImage.constScanLine(fillPoint.x(), fillPoint.y()); + QRgb startRef = qUnpremultiply(pressReferenceColorSingleLayer); + + if (properties.fillMode == 0 && ((QColor(qRed(startRef), qGreen(startRef), qBlue(startRef)) == bucketColor.rgb() && qAlpha(startRef) == 255) || bucketColor.alpha() == 0)) { + // In overlay mode: When the reference pixel matches the bucket color and the reference is fully opaque + // Otherwise when the bucket alpha is zero. + return false; + } else if (properties.fillMode == 2 && qAlpha(startRef) == 255) { + // In behind mode: When the reference pixel is already fully opaque, the output will be invisible. return false; } - Q_ASSERT(mTargetFillToLayer); - - BitmapImage targetImage = *static_cast(mTargetFillToLayer)->getLastBitmapImageAtFrame(mEditor->currentFrame(), 0); - if (!targetImage.isLoaded()) { return false; } - - QRgb colorOfReferenceImage = mReferenceImage.constScanLine(checkPoint.x(), checkPoint.y()); - QRgb targetPixelColor = targetImage.constScanLine(checkPoint.x(), checkPoint.y()); + return true; +} +bool BitmapBucket::allowFill(const QPoint& checkPoint, const QRgb& checkColor) const +{ // A normal click to fill should happen unconditionally, because the alternative is utterly confusing. if (!mFilledOnce) { return true; } - if (targetPixelColor == mBucketColor && (mProperties.fillMode == 1 || qAlpha(targetPixelColor) == 255)) + return allowContinousFill(checkPoint, checkColor); +} + +bool BitmapBucket::allowContinousFill(const QPoint& checkPoint, const QRgb& checkColor) const +{ + if (!mUseFillToDrag) { + return false; + } + + const QRgb& colorOfReferenceImage = mReferenceImage.constScanLine(checkPoint.x(), checkPoint.y()); + + if (checkColor == mBucketColor && (mProperties.fillMode == 1 || qAlpha(checkColor) == 255)) { // Avoid filling if target pixel color matches fill color // to avoid creating numerous seemingly useless undo operations return false; - } else if (QColor::fromRgb(targetPixelColor) == QColor::fromRgb(mBucketColor) && mProperties.fillMode == 0) { - // Avoid filling if the target pixel color matches without the alpha component - // this fixes a case where filling a fully opaque pixel with a less opaque version of itself would cause undo - // fills to happen - return false; } return BitmapImage::compareColor(colorOfReferenceImage, mStartReferenceColor, mTolerance, mPixelCache) && - (targetPixelColor == 0 || BitmapImage::compareColor(targetPixelColor, mStartReferenceColor, mTolerance, mPixelCache)); + (checkColor == 0 || BitmapImage::compareColor(checkColor, mStartReferenceColor, mTolerance, mPixelCache)); } -void BitmapBucket::paint(const QPointF updatedPoint, std::function state) +void BitmapBucket::paint(const QPointF& updatedPoint, std::function state) { - const QPoint point = QPoint(qFloor(updatedPoint.x()), qFloor(updatedPoint.y())); + const QPoint& point = QPoint(qFloor(updatedPoint.x()), qFloor(updatedPoint.y())); const int currentFrameIndex = mEditor->currentFrame(); - if (!allowFill(point)) { return; } + BitmapImage* targetImage = static_cast(mTargetFillToLayer)->getLastBitmapImageAtFrame(mEditor->currentFrame(), 0); + if (targetImage == nullptr || !targetImage->isLoaded()) { return; } // Can happen if the first frame is deleted while drawing - BitmapImage* targetImage = static_cast(mTargetFillToLayer->getLastKeyFrameAtPosition(currentFrameIndex)); + const QRgb& targetPixelColor = targetImage->constScanLine(point.x(), point.y()); - if (targetImage == nullptr || !targetImage->isLoaded()) { return; } // Can happen if the first frame is deleted while drawing + if (!allowFill(point, targetPixelColor)) { + return; + } QRgb fillColor = mBucketColor; if (mProperties.fillMode == 1) diff --git a/core_lib/src/graphics/bitmap/bitmapbucket.h b/core_lib/src/graphics/bitmap/bitmapbucket.h index d4062aafa..d5502b965 100644 --- a/core_lib/src/graphics/bitmap/bitmapbucket.h +++ b/core_lib/src/graphics/bitmap/bitmapbucket.h @@ -43,7 +43,7 @@ class BitmapBucket * @param progress - a function that returns the progress of the paint operation, * the layer and frame that was affected at the given point. */ - void paint(const QPointF updatedPoint, std::function progress); + void paint(const QPointF& updatedPoint, std::function progress); private: @@ -57,7 +57,11 @@ class BitmapBucket * @param checkPoint * @return True if you are allowed to fill, otherwise false */ - bool allowFill(const QPoint& checkPoint) const; + bool allowFill(const QPoint& checkPoint, const QRgb& checkColor) const; + bool allowContinousFill(const QPoint& checkPoint, const QRgb& checkColor) const; + + /** Determines whether fill to drag feature can be used */ + bool canUseFillToDrag(const QPoint& fillPoint, const QColor& bucketColor, const Properties& properties, const BitmapImage& referenceImage); BitmapImage flattenBitmapLayersToImage(); @@ -75,7 +79,8 @@ class BitmapBucket int mTolerance = 0; int mTargetFillToLayerIndex = -1; - int mFilledOnce = false; + bool mFilledOnce = false; + bool mUseFillToDrag = false; Properties mProperties; };