All Classes Namespaces Functions Variables Enumerations Properties Pages
smudgetool.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 "smudgetool.h"
18 #include <QPixmap>
19 #include <QSettings>
20 
21 #include "pointerevent.h"
22 #include "vectorimage.h"
23 #include "editor.h"
24 #include "scribblearea.h"
25 
26 #include "layermanager.h"
27 #include "strokemanager.h"
28 #include "viewmanager.h"
29 #include "selectionmanager.h"
30 
31 #include "layerbitmap.h"
32 #include "layervector.h"
33 #include "blitrect.h"
34 
35 SmudgeTool::SmudgeTool(QObject* parent) : StrokeTool(parent)
36 {
37  toolMode = 0; // tool mode
38 }
39 
40 ToolType SmudgeTool::type()
41 {
42  return SMUDGE;
43 }
44 
45 void SmudgeTool::loadSettings()
46 {
47  mPropertyEnabled[WIDTH] = true;
48  mPropertyEnabled[FEATHER] = true;
49 
50  QSettings settings(PENCIL2D, PENCIL2D);
51  properties.width = settings.value("smudgeWidth", 24.0).toDouble();
52  properties.feather = settings.value("smudgeFeather", 48.0).toDouble();
53  properties.pressure = false;
54  properties.stabilizerLevel = -1;
55 
56  mQuickSizingProperties.insert(Qt::ShiftModifier, WIDTH);
57  mQuickSizingProperties.insert(Qt::ControlModifier, FEATHER);
58 }
59 
60 void SmudgeTool::resetToDefault()
61 {
62  setWidth(24.0);
63  setFeather(48.0);
64 }
65 
66 void SmudgeTool::setWidth(const qreal width)
67 {
68  // Set current property
69  properties.width = width;
70 
71  // Update settings
72  QSettings settings(PENCIL2D, PENCIL2D);
73  settings.setValue("smudgeWidth", width);
74  settings.sync();
75 }
76 
77 void SmudgeTool::setFeather(const qreal feather)
78 {
79  // Set current property
80  properties.feather = feather;
81 
82  // Update settings
83  QSettings settings(PENCIL2D, PENCIL2D);
84  settings.setValue("smudgeFeather", feather);
85  settings.sync();
86 }
87 
88 void SmudgeTool::setPressure(const bool pressure)
89 {
90  // Set current property
91  properties.pressure = pressure;
92 
93  // Update settings
94  QSettings settings(PENCIL2D, PENCIL2D);
95  settings.setValue("smudgePressure", pressure);
96  settings.sync();
97 }
98 
100 {
101  // Disabled till we get it working for vector layers...
102  return false;
103 }
104 
105 QCursor SmudgeTool::cursor()
106 {
107  qDebug() << "smudge tool";
108  if (toolMode == 0) { //normal mode
109  return QCursor(QPixmap(":icons/smudge.png"), 0, 16);
110  }
111  else { // blured mode
112  return QCursor(QPixmap(":icons/liquify.png"), -4, 16);
113  }
114 }
115 
116 bool SmudgeTool::keyPressEvent(QKeyEvent *event)
117 {
118  if (event->key() == Qt::Key_Alt)
119  {
120  toolMode = 1; // alternative mode
121  mScribbleArea->setCursor(cursor()); // update cursor
122  return true;
123  }
124  return BaseTool::keyPressEvent(event);
125 }
126 
127 bool SmudgeTool::keyReleaseEvent(QKeyEvent *event)
128 {
129  if (event->key() == Qt::Key_Alt)
130  {
131  toolMode = 0; // default mode
132  mScribbleArea->setCursor(cursor()); // update cursor
133  return true;
134  }
135  return BaseTool::keyReleaseEvent(event);
136 }
137 
138 void SmudgeTool::pointerPressEvent(PointerEvent* event)
139 {
140  //qDebug() << "smudgetool: mousePressEvent";
141 
142  Layer* layer = mEditor->layers()->currentLayer();
143  auto selectMan = mEditor->select();
144  if (layer == nullptr) { return; }
145 
146  if (event->button() == Qt::LeftButton)
147  {
148  startStroke(event->inputType());
149  if (layer->type() == Layer::BITMAP)
150  {
151  mLastBrushPoint = getCurrentPoint();
152  }
153  else if (layer->type() == Layer::VECTOR)
154  {
155  const int currentFrame = mEditor->currentFrame();
156  const float distanceFrom = selectMan->selectionTolerance();
157  VectorImage* vectorImage = static_cast<LayerVector*>(layer)->getLastVectorImageAtFrame(currentFrame, 0);
158  if (vectorImage == nullptr) { return; }
159  selectMan->setCurves(vectorImage->getCurvesCloseTo(getCurrentPoint(), distanceFrom));
160  selectMan->setVertices(vectorImage->getVerticesCloseTo(getCurrentPoint(), distanceFrom));
161 ;
162  if (selectMan->closestCurves().size() > 0 || selectMan->closestCurves().size() > 0) // the user clicks near a vertex or a curve
163  {
164  // Since startStroke() isn't called, handle empty frame behaviour here.
165  // Commented out for now - leads to segfault on mouse-release event.
166 // if(emptyFrameActionEnabled())
167 // {
168 // mScribbleArea->handleDrawingOnEmptyFrame();
169 // }
170 
171  //qDebug() << "closestCurves:" << closestCurves << " | closestVertices" << closestVertices;
172  if (event->modifiers() != Qt::ShiftModifier && !vectorImage->isSelected(selectMan->closestVertices()))
173  {
174  mScribbleArea->paintTransformedSelection();
175  mEditor->deselectAll();
176  }
177 
178  vectorImage->setSelected(selectMan->closestVertices(), true);
179  selectMan->vectorSelection.add(selectMan->closestCurves());
180  selectMan->vectorSelection.add(selectMan->closestVertices());
181 
182  mScribbleArea->update();
183  }
184  else
185  {
186  mEditor->deselectAll();
187  }
188  }
189  }
190 }
191 
192 void SmudgeTool::pointerMoveEvent(PointerEvent* event)
193 {
194  if (event->inputType() != mCurrentInputType) return;
195 
196  Layer* layer = mEditor->layers()->currentLayer();
197  if (layer == nullptr) { return; }
198 
199  if (layer->type() != Layer::BITMAP && layer->type() != Layer::VECTOR)
200  {
201  return;
202  }
203 
204  auto selectMan = mEditor->select();
205  if (event->buttons() & Qt::LeftButton) // the user is also pressing the mouse (dragging) {
206  {
207  if (layer->type() == Layer::BITMAP)
208  {
209  drawStroke();
210  }
211  else //if (layer->type() == Layer::VECTOR)
212  {
213  if (event->modifiers() != Qt::ShiftModifier) // (and the user doesn't press shift)
214  {
215  VectorImage* vectorImage = static_cast<LayerVector*>(layer)->getLastVectorImageAtFrame(mEditor->currentFrame(), 0);
216  if (vectorImage == nullptr) { return; }
217  // transforms the selection
218 
219  selectMan->setSelectionTransform(QTransform().translate(offsetFromPressPos().x(), offsetFromPressPos().y()));
220  vectorImage->setSelectionTransformation(selectMan->selectionTransform());
221  }
222  }
223  }
224  else // the user is moving the mouse without pressing it
225  {
226  if (layer->type() == Layer::VECTOR)
227  {
228  VectorImage* vectorImage = static_cast<LayerVector*>(layer)->getLastVectorImageAtFrame(mEditor->currentFrame(), 0);
229  if (vectorImage == nullptr) { return; }
230 
231  selectMan->setVertices(vectorImage->getVerticesCloseTo(getCurrentPoint(), selectMan->selectionTolerance()));
232  }
233  }
234  mEditor->updateCurrentFrame();
235 }
236 
237 void SmudgeTool::pointerReleaseEvent(PointerEvent* event)
238 {
239  if (event->inputType() != mCurrentInputType) return;
240 
241  Layer* layer = mEditor->layers()->currentLayer();
242  if (layer == nullptr) { return; }
243 
244  if (event->button() == Qt::LeftButton)
245  {
246  mEditor->backup(typeName());
247 
248  if (layer->type() == Layer::BITMAP)
249  {
250  drawStroke();
251  mScribbleArea->paintBitmapBuffer();
252  mScribbleArea->clearBitmapBuffer();
253  endStroke();
254  }
255  else if (layer->type() == Layer::VECTOR)
256  {
257  VectorImage *vectorImage = ((LayerVector *)layer)->getLastVectorImageAtFrame(mEditor->currentFrame(), 0);
258  if (vectorImage == nullptr) { return; }
259  vectorImage->applySelectionTransformation();
260 
261  auto selectMan = mEditor->select();
262  selectMan->resetSelectionTransform();
263  for (int k = 0; k < selectMan->vectorSelection.curve.size(); k++)
264  {
265  int curveNumber = selectMan->vectorSelection.curve.at(k);
266  vectorImage->curve(curveNumber).smoothCurve();
267  }
268  mScribbleArea->setModified(mEditor->layers()->currentLayerIndex(), mEditor->currentFrame());
269  }
270  }
271 }
272 
273 void SmudgeTool::drawStroke()
274 {
275  if (!mScribbleArea->isLayerPaintable()) return;
276 
277  Layer* layer = mEditor->layers()->currentLayer();
278  if (layer == nullptr) { return; }
279 
280  BitmapImage *sourceImage = static_cast<LayerBitmap*>(layer)->getLastBitmapImageAtFrame(mEditor->currentFrame(), 0);
281  if (sourceImage == nullptr) { return; } // Can happen if the first frame is deleted while drawing
282  BitmapImage targetImage = sourceImage->copy();
283  StrokeTool::drawStroke();
284  QList<QPointF> p = strokeManager()->interpolateStroke();
285 
286  for (int i = 0; i < p.size(); i++)
287  {
288  p[i] = mEditor->view()->mapScreenToCanvas(p[i]);
289  }
290 
291  qreal opacity = 1.0;
292  mCurrentWidth = properties.width;
293  qreal brushWidth = mCurrentWidth + 0.0 * properties.feather;
294  qreal offset = qMax(0.0, mCurrentWidth - 0.5 * properties.feather) / brushWidth;
295  //opacity = currentPressure; // todo: Probably not interesting?!
296  //brushWidth = brushWidth * opacity;
297 
298  BlitRect rect;
299  QPointF a = mLastBrushPoint;
300  QPointF b = getCurrentPoint();
301 
302 
303  if (toolMode == 1) // liquify hard
304  {
305  qreal brushStep = 2;
306  qreal distance = QLineF(b, a).length() / 2.0;
307  int steps = qRound(distance / brushStep);
308  int rad = qRound(brushWidth / 2.0) + 2;
309 
310  QPointF sourcePoint = mLastBrushPoint;
311  for (int i = 0; i < steps; i++)
312  {
313  targetImage.paste(mScribbleArea->mBufferImg);
314  QPointF targetPoint = mLastBrushPoint + (i + 1) * (brushStep) * (b - mLastBrushPoint) / distance;
315  rect.extend(targetPoint.toPoint());
316  mScribbleArea->liquifyBrush(&targetImage,
317  sourcePoint,
318  targetPoint,
319  brushWidth,
320  offset,
321  opacity);
322 
323  if (i == (steps - 1))
324  {
325  mLastBrushPoint = targetPoint;
326  }
327  sourcePoint = targetPoint;
328  mScribbleArea->paintBitmapBufferRect(rect);
329  mScribbleArea->refreshBitmap(rect, rad);
330  }
331  }
332  else // liquify smooth
333  {
334  qreal brushStep = 2.0;
335  qreal distance = QLineF(b, a).length();
336  int steps = qRound(distance / brushStep);
337  int rad = qRound(brushWidth / 2.0) + 2;
338 
339  QPointF sourcePoint = mLastBrushPoint;
340  for (int i = 0; i < steps; i++)
341  {
342  targetImage.paste(mScribbleArea->mBufferImg);
343  QPointF targetPoint = mLastBrushPoint + (i + 1) * (brushStep) * (b - mLastBrushPoint) / distance;
344  rect.extend(targetPoint.toPoint());
345  mScribbleArea->blurBrush(&targetImage,
346  sourcePoint,
347  targetPoint,
348  brushWidth,
349  offset,
350  opacity);
351 
352  if (i == (steps - 1))
353  {
354  mLastBrushPoint = targetPoint;
355  }
356  sourcePoint = targetPoint;
357  mScribbleArea->paintBitmapBufferRect(rect);
358  mScribbleArea->refreshBitmap(rect, rad);
359  }
360  }
361 }
362 
363 QPointF SmudgeTool::offsetFromPressPos()
364 {
365  return getCurrentPoint() - getCurrentPressPoint();
366 }
367 
ShiftModifier
Qt::KeyboardModifiers modifiers() const
Returns the modifier created by keyboard while a device was in use.
QHash::iterator insert(const Key &key, const T &value)
void setCursor(const QCursor &)
QList< VertexRef > getVerticesCloseTo(QPointF thisPoint, qreal maxDistance)
VectorImage::getVerticesCloseTo.
QList< int > getCurvesCloseTo(QPointF thisPoint, qreal maxDistance)
VectorImage::getCurvesCloseTo.
bool emptyFrameActionEnabled() override
Whether to enable the "drawing on empty frame" preference.
Definition: smudgetool.cpp:99
LeftButton
void setModified(int layerNumber, int frameNumber)
Set frame on layer to modified and invalidate current frame cache.
void update()
int size() const const
qreal length() const const
bool isSelected(int curveNumber)
VectorImage::isSelected.
Definition: layer.h:38
Qt::MouseButtons buttons() const
Returns Qt::MouseButtons()
int key() const const
void setSelectionTransformation(QTransform transform)
VectorImage::setSelectionTransformation.
QPoint toPoint() const const
Qt::MouseButton button() const
Returns Qt::MouseButton()
void applySelectionTransformation()
VectorImage::applySelectionTransformation.
void setSelected(int curveNumber, bool YesOrNo)
VectorImage::setSelected.
void updateCurrentFrame()
Will call update() and update the canvas Only call this directly If you need the cache to be intact a...
Definition: editor.cpp:1057