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