All Classes Namespaces Functions Variables Enumerations Properties Pages
bitmapbucket.cpp
1 /*
2 
3 Pencil2D - Traditional Animation Software
4 Copyright (C) 2005-2007 Patrick Corrieri & Pascal Naidon
5 Copyright (C) 2012-2020 Matthew Chiawen Chang
6 
7 This program is free software; you can redistribute it and/or
8 modify it under the terms of the GNU General Public License
9 as published by the Free Software Foundation; version 2 of the License.
10 
11 This program is distributed in the hope that it will be useful,
12 but WITHOUT ANY WARRANTY; without even the implied warranty of
13 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 GNU 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 
27 BitmapBucket::BitmapBucket()
28 {
29 }
30 
31 BitmapBucket::BitmapBucket(Editor* editor,
32  QColor color,
33  QRect maxFillRegion,
34  QPointF fillPoint,
35  Properties properties):
36  mEditor(editor),
37  mBucketStartPoint(fillPoint),
38  mMaxFillRegion(maxFillRegion),
39  mProperties(properties)
40 
41 {
42  Layer* initialLayer = editor->layers()->currentLayer();
43  int initialLayerIndex = mEditor->currentLayerIndex();
44  int frameIndex = mEditor->currentFrame();
45 
46  mBucketColor = qPremultiply(color.rgba());
47 
48  mTargetFillToLayer = initialLayer;
49  mTargetFillToLayerIndex = initialLayerIndex;
50 
51  if (properties.bucketFillToLayerMode == 1)
52  {
53  auto result = findBitmapLayerBelow(initialLayer, initialLayerIndex);
54  mTargetFillToLayer = result.first;
55  mTargetFillToLayerIndex = result.second;
56  }
57  Q_ASSERT(mTargetFillToLayer);
58 
59  mReferenceImage = *static_cast<BitmapImage*>(initialLayer->getLastKeyFrameAtPosition(frameIndex));
60  if (properties.bucketFillReferenceMode == 1) // All layers
61  {
62  mReferenceImage = flattenBitmapLayersToImage();
63  }
64 
65  const QPoint point = QPoint(qFloor(fillPoint.x()), qFloor(fillPoint.y()));
66 
67  BitmapImage* image = static_cast<LayerBitmap*>(mTargetFillToLayer)->getLastBitmapImageAtFrame(frameIndex, 0);
68  mReferenceColor = image->constScanLine(point.x(), point.y());
69 }
70 
71 bool BitmapBucket::shouldFill(QPointF checkPoint) const
72 {
73  const QPoint point = QPoint(qFloor(checkPoint.x()), qFloor(checkPoint.y()));
74 
75  if (mProperties.fillMode == 0 && qAlpha(mBucketColor) == 0)
76  {
77  // Filling in overlay mode with a fully transparent color has no
78  // effect, so we can skip it in this case
79  return false;
80  }
81  Q_ASSERT(mTargetFillToLayer);
82 
83  BitmapImage targetImage = *static_cast<LayerBitmap*>(mTargetFillToLayer)->getLastBitmapImageAtFrame(mEditor->currentFrame(), 0);
84 
85  if (!targetImage.isLoaded()) { return false; }
86 
87  BitmapImage referenceImage = mReferenceImage;
88 
89  QRgb pixelColor = referenceImage.constScanLine(point.x(), point.y());
90  QRgb targetPixelColor = targetImage.constScanLine(point.x(), point.y());
91 
92  if (mProperties.fillMode == 2 && pixelColor != 0)
93  {
94  // don't try to fill because we won't be able to see it anyway...
95  return false;
96  }
97 
98  // First paint is allowed with no rules applied
99  if (mFirstPaint)
100  {
101  return true;
102  }
103 
104  // Ensure that when dragging that we're only filling on either transparent or same color
105  if ((mReferenceColor == targetPixelColor && targetPixelColor == pixelColor) || (pixelColor == 0 && targetPixelColor == 0))
106  {
107  return true;
108  }
109 
110  // When filling with various blending modes we need to verify that the applied color
111  // doesn't match the target color, otherwise it will fill the same color for no reason.
112  // We still expect to only fill on either transparent or same color.
113  if (mAppliedColor != targetPixelColor && pixelColor == 0)
114  {
115  return true;
116  }
117 
118  return false;
119 }
120 
121 void BitmapBucket::paint(const QPointF updatedPoint, std::function<void(BucketState, int, int)> state)
122 {
123  const Layer* targetLayer = mTargetFillToLayer;
124  int targetLayerIndex = mTargetFillToLayerIndex;
125  QRgb fillColor = mBucketColor;
126 
127  const QPoint point = QPoint(qFloor(updatedPoint.x()), qFloor(updatedPoint.y()));
128  const QRect cameraRect = mMaxFillRegion;
129  const int tolerance = mProperties.toleranceEnabled ? static_cast<int>(mProperties.tolerance) : 0;
130  const int currentFrameIndex = mEditor->currentFrame();
131  const QRgb origColor = fillColor;
132 
133  if (!shouldFill(updatedPoint)) { return; }
134 
135  BitmapImage* targetImage = static_cast<BitmapImage*>(targetLayer->getLastKeyFrameAtPosition(currentFrameIndex));
136 
137  if (targetImage == nullptr || !targetImage->isLoaded()) { return; } // Can happen if the first frame is deleted while drawing
138 
139  BitmapImage referenceImage = mReferenceImage;
140 
141  if (mProperties.fillMode == 1)
142  {
143  // Pass a fully opaque version of the new color to floodFill
144  // This is required so we can fully mask out the existing data before
145  // writing the new color.
146  QColor tempColor;
147  tempColor.setRgba(fillColor);
148  tempColor.setAlphaF(1);
149  fillColor = tempColor.rgba();
150  }
151 
152  BitmapImage* replaceImage = nullptr;
153 
154  int expandValue = mProperties.bucketFillExpandEnabled ? mProperties.bucketFillExpand : 0;
155  bool didFloodFill = BitmapImage::floodFill(&replaceImage,
156  &referenceImage,
157  cameraRect,
158  point,
159  fillColor,
160  tolerance,
161  expandValue);
162 
163  if (!didFloodFill) {
164  delete replaceImage;
165  return;
166  }
167  Q_ASSERT(replaceImage != nullptr);
168 
169  state(BucketState::WillFillTarget, targetLayerIndex, currentFrameIndex);
170 
171  if (mProperties.fillMode == 0)
172  {
173  targetImage->paste(replaceImage);
174  }
175  else if (mProperties.fillMode == 2)
176  {
177  targetImage->paste(replaceImage, QPainter::CompositionMode_DestinationOver);
178  }
179  else
180  {
181  // fill mode replace
182  targetImage->paste(replaceImage, QPainter::CompositionMode_DestinationOut);
183  // Reduce the opacity of the fill to match the new color
184  BitmapImage properColor(replaceImage->bounds(), QColor::fromRgba(origColor));
185  properColor.paste(replaceImage, QPainter::CompositionMode_DestinationIn);
186  // Write reduced-opacity fill image on top of target image
187  targetImage->paste(&properColor);
188  }
189 
190  mAppliedColor = targetImage->constScanLine(point.x(), point.y());
191  mFirstPaint = false;
192 
193  targetImage->modification();
194  delete replaceImage;
195 
196  state(BucketState::DidFillTarget, targetLayerIndex, currentFrameIndex);
197 }
198 
199 BitmapImage BitmapBucket::flattenBitmapLayersToImage()
200 {
201  BitmapImage flattenImage = BitmapImage();
202  int currentFrame = mEditor->currentFrame();
203  auto layerMan = mEditor->layers();
204  for (int i = 0; i < layerMan->count(); i++)
205  {
206  Layer* layer = layerMan->getLayer(i);
207  Q_ASSERT(layer);
208  if (layer->type() == Layer::BITMAP && layer->visible())
209  {
210  BitmapImage* image = static_cast<LayerBitmap*>(layer)->getLastBitmapImageAtFrame(currentFrame);
211  if (image) {
212  flattenImage.paste(image);
213  }
214  }
215  }
216  return flattenImage;
217 }
218 
219 std::pair<Layer*, int> BitmapBucket::findBitmapLayerBelow(Layer* targetLayer, int layerIndex) const
220 {
221  bool foundLayerBelow = false;
222  int layerBelowIndex = layerIndex;
223  for (int i = layerIndex - 1; i >= 0; i--)
224  {
225  Layer* searchlayer = mEditor->layers()->getLayer(i);
226  Q_ASSERT(searchlayer);
227 
228  if (searchlayer->type() == Layer::BITMAP && searchlayer->visible())
229  {
230  targetLayer = searchlayer;
231  foundLayerBelow = true;
232  layerBelowIndex = i;
233  break;
234  }
235  }
236 
237  if (foundLayerBelow && !targetLayer->keyExists(mEditor->currentFrame()))
238  {
239  targetLayer->addNewKeyFrameAt(mEditor->currentFrame());
240  emit mEditor->updateTimeLine();
241  }
242  return std::make_pair(targetLayer, layerBelowIndex);
243 }
void setRgba(QRgb rgba)
int x() const const
int y() const const
qreal x() const const
qreal y() const const
Definition: layer.h:38
CompositionMode_DestinationOver
QColor fromRgba(QRgb rgba)
bool shouldFill(QPointF checkPoint) const
Based on the various factors dependant on which tool properties are set, the result will: ...
void paint(const QPointF updatedPoint, std::function< void(BucketState, int, int)> progress)
Will paint at the given point, given that it makes sense.
void setAlphaF(qreal alpha)
Definition: editor.h:55
QRgb rgba() const const