Pencil2D Animation
Download Community News Docs Contribute
  • Overview
  • Articles
  • Code
  •  
  • Class List
  • Class Index
  • Class Hierarchy
  • Class Members
  • File List
Loading...
Searching...
No Matches
  • core_lib
  • src
  • graphics
  • bitmap
bitmapbucket.cpp
1/*
2
3Pencil2D - Traditional Animation Software
4Copyright (C) 2005-2007 Patrick Corrieri & Pascal Naidon
5Copyright (C) 2012-2020 Matthew Chiawen Chang
6
7This program is free software; you can redistribute it and/or
8modify it under the terms of the GNU General Public License
9as published by the Free Software Foundation; version 2 of the License.
10
11This program is distributed in the hope that it will be useful,
12but WITHOUT ANY WARRANTY; without even the implied warranty of
13MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14GNU General Public License for more details.
15
16*/
17#include "bitmapbucket.h"
18
19#include <QtMath>
20#include <QDebug>
21
22#include "editor.h"
23#include "layermanager.h"
24
25#include "layerbitmap.h"
26
27BitmapBucket::BitmapBucket()
28{
29}
30
31BitmapBucket::BitmapBucket(Editor* editor,
32 QColor color,
33 QRect maxFillRegion,
34 QPointF fillPoint,
35 Properties properties):
36 mEditor(editor),
37 mMaxFillRegion(maxFillRegion),
38 mProperties(properties)
39
40{
41 Layer* initialLayer = editor->layers()->currentLayer();
42 int initialLayerIndex = mEditor->currentLayerIndex();
43 int frameIndex = mEditor->currentFrame();
44
45 mBucketColor = qPremultiply(color.rgba());
46
47 mTargetFillToLayer = initialLayer;
48 mTargetFillToLayerIndex = initialLayerIndex;
49
50 mTolerance = mProperties.toleranceEnabled ? static_cast<int>(mProperties.tolerance) : 0;
51 const QPoint& point = QPoint(qFloor(fillPoint.x()), qFloor(fillPoint.y()));
52
53 Q_ASSERT(mTargetFillToLayer);
54
55 BitmapImage singleLayerImage = *static_cast<BitmapImage*>(initialLayer->getLastKeyFrameAtPosition(frameIndex));
56 if (properties.bucketFillReferenceMode == 1) // All layers
57 {
58 mReferenceImage = flattenBitmapLayersToImage();
59 } else {
60 mReferenceImage = singleLayerImage;
61 }
62 mStartReferenceColor = mReferenceImage.constScanLine(point.x(), point.y());
63 mUseDragToFill = canUseDragToFill(point, color, singleLayerImage);
64
65 mPixelCache = new QHash<QRgb, bool>();
66}
67
68bool BitmapBucket::canUseDragToFill(const QPoint& fillPoint, const QColor& bucketColor, const BitmapImage& referenceImage)
69{
70 QRgb pressReferenceColorSingleLayer = referenceImage.constScanLine(fillPoint.x(), fillPoint.y());
71 QRgb startRef = qUnpremultiply(pressReferenceColorSingleLayer);
72
73 if (mProperties.fillMode == 0 && ((QColor(qRed(startRef), qGreen(startRef), qBlue(startRef)) == bucketColor.rgb() && qAlpha(startRef) == 255) || bucketColor.alpha() == 0)) {
74 // In overlay mode: When the reference pixel matches the bucket color and the reference is fully opaque
75 // Otherwise when the bucket alpha is zero.
76 return false;
77 } else if (mProperties.fillMode == 2 && qAlpha(startRef) == 255) {
78 // In behind mode: When the reference pixel is already fully opaque, the output will be invisible.
79 return false;
80 }
81
82 return true;
83}
84
85bool BitmapBucket::allowFill(const QPoint& checkPoint, const QRgb& checkColor) const
86{
87 // A normal click to fill should happen unconditionally, because the alternative is utterly confusing.
88 if (!mFilledOnce) {
89 return true;
90 }
91
92 return allowContinuousFill(checkPoint, checkColor);
93}
94
95bool BitmapBucket::allowContinuousFill(const QPoint& checkPoint, const QRgb& checkColor) const
96{
97 if (!mUseDragToFill) {
98 return false;
99 }
100
101 const QRgb& colorOfReferenceImage = mReferenceImage.constScanLine(checkPoint.x(), checkPoint.y());
102
103 if (checkColor == mBucketColor && (mProperties.fillMode == 1 || qAlpha(checkColor) == 255))
104 {
105 // Avoid filling if target pixel color matches fill color
106 // to avoid creating numerous seemingly useless undo operations
107 return false;
108 }
109
110 return BitmapImage::compareColor(colorOfReferenceImage, mStartReferenceColor, mTolerance, mPixelCache) &&
111 (checkColor == 0 || BitmapImage::compareColor(checkColor, mStartReferenceColor, mTolerance, mPixelCache));
112}
113
114void BitmapBucket::paint(const QPointF& updatedPoint, std::function<void(BucketState, int, int)> state)
115{
116 const int currentFrameIndex = mEditor->currentFrame();
117
118 BitmapImage* targetImage = static_cast<LayerBitmap*>(mTargetFillToLayer)->getLastBitmapImageAtFrame(currentFrameIndex, 0);
119 if (targetImage == nullptr || !targetImage->isLoaded()) { return; } // Can happen if the first frame is deleted while drawing
120
121 QPoint point = QPoint(qFloor(updatedPoint.x()), qFloor(updatedPoint.y()));
122 if (!mReferenceImage.contains(point))
123 {
124 // If point is outside the our max known fill area, move the fill point anywhere within the bounds
125 point = mReferenceImage.topLeft();
126 }
127
128 const QRgb& targetPixelColor = targetImage->constScanLine(point.x(), point.y());
129
130 if (!allowFill(point, targetPixelColor)) {
131 return;
132 }
133
134 QRgb fillColor = mBucketColor;
135 if (mProperties.fillMode == 1)
136 {
137 // Pass a fully opaque version of the new color to floodFill
138 // This is required so we can fully mask out the existing data before
139 // writing the new color.
140 QColor tempColor;
141 tempColor.setRgba(fillColor);
142 tempColor.setAlphaF(1);
143 fillColor = tempColor.rgba();
144 }
145
146 BitmapImage* replaceImage = nullptr;
147
148 int expandValue = mProperties.bucketFillExpandEnabled ? mProperties.bucketFillExpand : 0;
149 bool didFloodFill = BitmapImage::floodFill(&replaceImage,
150 &mReferenceImage,
151 mMaxFillRegion,
152 point,
153 fillColor,
154 mTolerance,
155 expandValue);
156
157 if (!didFloodFill) {
158 delete replaceImage;
159 return;
160 }
161 Q_ASSERT(replaceImage != nullptr);
162
163 state(BucketState::WillFillTarget, mTargetFillToLayerIndex, currentFrameIndex);
164
165 if (mProperties.fillMode == 0)
166 {
167 targetImage->paste(replaceImage);
168 }
169 else if (mProperties.fillMode == 2)
170 {
171 targetImage->paste(replaceImage, QPainter::CompositionMode_DestinationOver);
172 }
173 else
174 {
175 // fill mode replace
176 targetImage->paste(replaceImage, QPainter::CompositionMode_DestinationOut);
177 // Reduce the opacity of the fill to match the new color
178 BitmapImage properColor(replaceImage->bounds(), QColor::fromRgba(mBucketColor));
179 properColor.paste(replaceImage, QPainter::CompositionMode_DestinationIn);
180 // Write reduced-opacity fill image on top of target image
181 targetImage->paste(&properColor);
182 }
183
184 targetImage->modification();
185 delete replaceImage;
186
187 state(BucketState::DidFillTarget, mTargetFillToLayerIndex, currentFrameIndex);
188 mFilledOnce = true;
189}
190
191BitmapImage BitmapBucket::flattenBitmapLayersToImage()
192{
193 BitmapImage flattenImage = BitmapImage();
194 int currentFrame = mEditor->currentFrame();
195 auto layerMan = mEditor->layers();
196 for (int i = 0; i < layerMan->count(); i++)
197 {
198 Layer* layer = layerMan->getLayer(i);
199 Q_ASSERT(layer);
200 if (layer->type() == Layer::BITMAP && layer->visible())
201 {
202 BitmapImage* image = static_cast<LayerBitmap*>(layer)->getLastBitmapImageAtFrame(currentFrame);
203 if (image) {
204 flattenImage.paste(image);
205 }
206 }
207 }
208 return flattenImage;
209}
BitmapBucket::canUseDragToFill
bool canUseDragToFill(const QPoint &fillPoint, const QColor &bucketColor, const BitmapImage &referenceImage)
Determines whether fill to drag feature can be used.
Definition: bitmapbucket.cpp:68
BitmapBucket::allowFill
bool allowFill(const QPoint &checkPoint, const QRgb &checkColor) const
Based on the various factors dependant on which tool properties are set, the result will:
Definition: bitmapbucket.cpp:85
BitmapBucket::paint
void paint(const QPointF &updatedPoint, std::function< void(BucketState, int, int)> progress)
Will paint at the given point, given that it makes sense.
Definition: bitmapbucket.cpp:114
BitmapImage
Definition: bitmapimage.h:28
BitmapImage::compareColor
static bool compareColor(QRgb newColor, QRgb oldColor, int tolerance, QHash< QRgb, bool > *cache)
Compare colors for the purposes of flood filling.
Definition: bitmapimage.h:146
Editor
Definition: editor.h:71
LayerBitmap
Definition: layerbitmap.h:26
Layer
Definition: layer.h:33
Properties
Definition: basetool.h:39
QColor
QColor::alpha
int alpha() const const
QColor::fromRgba
QColor fromRgba(QRgb rgba)
QColor::rgb
QRgb rgb() const const
QColor::rgba
QRgb rgba() const const
QColor::setAlphaF
void setAlphaF(qreal alpha)
QColor::setRgba
void setRgba(QRgb rgba)
QHash
QPainter::CompositionMode_DestinationOver
CompositionMode_DestinationOver
QPoint
QPoint::x
int x() const const
QPoint::y
int y() const const
QPointF
QPointF::x
qreal x() const const
QPointF::y
qreal y() const const
QRect
Generated on Thu May 8 2025 04:47:53 for Pencil2D by doxygen 1.9.6 based on revision 4513250b1d5b1a3676ec0e67b06b7a885ceaae39