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 false;
125 }
126 
127 bool SmudgeTool::keyReleaseEvent(QKeyEvent*)
128 {
129 
130  toolMode = 0; // default mode
131  mScribbleArea->setCursor(cursor()); // update cursor
132 
133  return true;
134 }
135 
136 void SmudgeTool::pointerPressEvent(PointerEvent* event)
137 {
138  //qDebug() << "smudgetool: mousePressEvent";
139 
140  Layer* layer = mEditor->layers()->currentLayer();
141  auto selectMan = mEditor->select();
142  if (layer == NULL) { return; }
143 
144  if (event->button() == Qt::LeftButton)
145  {
146  if (layer->type() == Layer::BITMAP)
147  {
148  mScribbleArea->setAllDirty();
149  startStroke(event->inputType());
150  mLastBrushPoint = getCurrentPoint();
151  }
152  else if (layer->type() == Layer::VECTOR)
153  {
154  const int currentFrame = mEditor->currentFrame();
155  const float distanceFrom = selectMan->selectionTolerance();
156  VectorImage* vectorImage = static_cast<LayerVector*>(layer)->getLastVectorImageAtFrame(currentFrame, 0);
157  if (vectorImage == nullptr) { return; }
158  selectMan->setCurves(vectorImage->getCurvesCloseTo(getCurrentPoint(), distanceFrom));
159  selectMan->setVertices(vectorImage->getVerticesCloseTo(getCurrentPoint(), distanceFrom));
160 ;
161  if (selectMan->closestCurves().size() > 0 || selectMan->closestCurves().size() > 0) // the user clicks near a vertex or a curve
162  {
163  // Since startStroke() isn't called, handle empty frame behaviour here.
164  // Commented out for now - leads to segfault on mouse-release event.
165 // if(emptyFrameActionEnabled())
166 // {
167 // mScribbleArea->handleDrawingOnEmptyFrame();
168 // }
169 
170  //qDebug() << "closestCurves:" << closestCurves << " | closestVertices" << closestVertices;
171  if (event->modifiers() != Qt::ShiftModifier && !vectorImage->isSelected(selectMan->closestVertices()))
172  {
173  mScribbleArea->paintTransformedSelection();
174  mEditor->deselectAll();
175  }
176 
177  vectorImage->setSelected(selectMan->closestVertices(), true);
178  selectMan->vectorSelection.add(selectMan->closestCurves());
179  selectMan->vectorSelection.add(selectMan->closestVertices());
180 
181  mScribbleArea->update();
182  }
183  else
184  {
185  mEditor->deselectAll();
186  }
187  }
188  }
189 }
190 
191 void SmudgeTool::pointerMoveEvent(PointerEvent* event)
192 {
193  if (event->inputType() != mCurrentInputType) return;
194 
195  Layer* layer = mEditor->layers()->currentLayer();
196  if (layer == NULL) { return; }
197 
198  if (layer->type() != Layer::BITMAP && layer->type() != Layer::VECTOR)
199  {
200  return;
201  }
202 
203  auto selectMan = mEditor->select();
204  if (event->buttons() & Qt::LeftButton) // the user is also pressing the mouse (dragging) {
205  {
206  if (layer->type() == Layer::BITMAP)
207  {
208  drawStroke();
209  }
210  else //if (layer->type() == Layer::VECTOR)
211  {
212  if (event->modifiers() != Qt::ShiftModifier) // (and the user doesn't press shift)
213  {
214  VectorImage* vectorImage = static_cast<LayerVector*>(layer)->getLastVectorImageAtFrame(mEditor->currentFrame(), 0);
215  if (vectorImage == nullptr) { return; }
216  // transforms the selection
217 
218  selectMan->setSelectionTransform(QTransform().translate(offsetFromPressPos().x(), offsetFromPressPos().y()));
219  vectorImage->setSelectionTransformation(selectMan->selectionTransform());
220  }
221  }
222  }
223  else // the user is moving the mouse without pressing it
224  {
225  if (layer->type() == Layer::VECTOR)
226  {
227  VectorImage* vectorImage = static_cast<LayerVector*>(layer)->getLastVectorImageAtFrame(mEditor->currentFrame(), 0);
228  if (vectorImage == nullptr) { return; }
229 
230  selectMan->setVertices(vectorImage->getVerticesCloseTo(getCurrentPoint(), selectMan->selectionTolerance()));
231  }
232  }
233  mScribbleArea->update();
234  mScribbleArea->setAllDirty();
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 == NULL) { 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->setAllDirty();
253  mScribbleArea->clearBitmapBuffer();
254  endStroke();
255  }
256  else if (layer->type() == Layer::VECTOR)
257  {
258  VectorImage *vectorImage = ((LayerVector *)layer)->getLastVectorImageAtFrame(mEditor->currentFrame(), 0);
259  if (vectorImage == nullptr) { return; }
260  vectorImage->applySelectionTransformation();
261 
262  auto selectMan = mEditor->select();
263  selectMan->resetSelectionTransform();
264  for (int k = 0; k < selectMan->vectorSelection.curve.size(); k++)
265  {
266  int curveNumber = selectMan->vectorSelection.curve.at(k);
267  vectorImage->curve(curveNumber).smoothCurve();
268  }
269  mScribbleArea->setModified(mEditor->layers()->currentLayerIndex(), mEditor->currentFrame());
270  }
271  }
272 }
273 
274 void SmudgeTool::drawStroke()
275 {
276  if (!mScribbleArea->isLayerPaintable()) return;
277 
278  Layer* layer = mEditor->layers()->currentLayer();
279  if (layer == nullptr) { return; }
280 
281  BitmapImage *sourceImage = static_cast<LayerBitmap*>(layer)->getLastBitmapImageAtFrame(mEditor->currentFrame(), 0);
282  if (sourceImage == nullptr) { return; } // Can happen if the first frame is deleted while drawing
283  BitmapImage targetImage = sourceImage->copy();
284  StrokeTool::drawStroke();
285  QList<QPointF> p = strokeManager()->interpolateStroke();
286 
287  for (int i = 0; i < p.size(); i++)
288  {
289  p[i] = mEditor->view()->mapScreenToCanvas(p[i]);
290  }
291 
292  qreal opacity = 1.0;
293  mCurrentWidth = properties.width;
294  qreal brushWidth = mCurrentWidth + 0.0 * properties.feather;
295  qreal offset = qMax(0.0, mCurrentWidth - 0.5 * properties.feather) / brushWidth;
296  //opacity = currentPressure; // todo: Probably not interesting?!
297  //brushWidth = brushWidth * opacity;
298 
299  BlitRect rect;
300  QPointF a = mLastBrushPoint;
301  QPointF b = getCurrentPoint();
302 
303 
304  if (toolMode == 1) // liquify hard
305  {
306  qreal brushStep = 2;
307  qreal distance = QLineF(b, a).length() / 2.0;
308  int steps = qRound(distance / brushStep);
309  int rad = qRound(brushWidth / 2.0) + 2;
310 
311  QPointF sourcePoint = mLastBrushPoint;
312  for (int i = 0; i < steps; i++)
313  {
314  targetImage.paste(mScribbleArea->mBufferImg);
315  QPointF targetPoint = mLastBrushPoint + (i + 1) * (brushStep) * (b - mLastBrushPoint) / distance;
316  rect.extend(targetPoint.toPoint());
317  mScribbleArea->liquifyBrush(&targetImage,
318  sourcePoint,
319  targetPoint,
320  brushWidth,
321  offset,
322  opacity);
323 
324  if (i == (steps - 1))
325  {
326  mLastBrushPoint = targetPoint;
327  }
328  sourcePoint = targetPoint;
329  mScribbleArea->paintBitmapBufferRect(rect);
330  mScribbleArea->refreshBitmap(rect, rad);
331  }
332  }
333  else // liquify smooth
334  {
335  qreal brushStep = 2.0;
336  qreal distance = QLineF(b, a).length();
337  int steps = qRound(distance / brushStep);
338  int rad = qRound(brushWidth / 2.0) + 2;
339 
340  QPointF sourcePoint = mLastBrushPoint;
341  for (int i = 0; i < steps; i++)
342  {
343  targetImage.paste(mScribbleArea->mBufferImg);
344  QPointF targetPoint = mLastBrushPoint + (i + 1) * (brushStep) * (b - mLastBrushPoint) / distance;
345  rect.extend(targetPoint.toPoint());
346  mScribbleArea->blurBrush(&targetImage,
347  sourcePoint,
348  targetPoint,
349  brushWidth,
350  offset,
351  opacity);
352 
353  if (i == (steps - 1))
354  {
355  mLastBrushPoint = targetPoint;
356  }
357  sourcePoint = targetPoint;
358  mScribbleArea->paintBitmapBufferRect(rect);
359  mScribbleArea->refreshBitmap(rect, rad);
360  }
361  }
362 }
363 
364 QPointF SmudgeTool::offsetFromPressPos()
365 {
366  return getCurrentPoint() - getCurrentPressPoint();
367 }
368 
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 update()
int size() const const
qreal length() const const
bool isSelected(int curveNumber)
VectorImage::isSelected.
Definition: layer.h:39
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.