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