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
penciltool.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 "penciltool.h"
18
19#include <QSettings>
20#include <QPixmap>
21#include "pointerevent.h"
22
23#include "layermanager.h"
24#include "colormanager.h"
25#include "viewmanager.h"
26#include "preferencemanager.h"
27#include "selectionmanager.h"
28#include "undoredomanager.h"
29
30#include "editor.h"
31#include "scribblearea.h"
32#include "layervector.h"
33#include "vectorimage.h"
34
35
36PencilTool::PencilTool(QObject* parent) : StrokeTool(parent)
37{
38}
39
40void PencilTool::loadSettings()
41{
42 StrokeTool::loadSettings();
43
44 mPropertyUsed[StrokeToolProperties::WIDTH_VALUE] = { Layer::BITMAP };
45 mPropertyUsed[StrokeToolProperties::PRESSURE_ENABLED] = { Layer::BITMAP };
46 mPropertyUsed[StrokeToolProperties::FILLCONTOUR_ENABLED] = { Layer::VECTOR };
47 mPropertyUsed[StrokeToolProperties::STABILIZATION_VALUE] = { Layer::BITMAP, Layer::VECTOR };
48
49 QSettings pencilSettings(PENCIL2D, PENCIL2D);
50
51 QHash<int, PropertyInfo> info;
52
53 info[StrokeToolProperties::WIDTH_VALUE] = { WIDTH_MIN, WIDTH_MAX, 4.0 };
54 info[StrokeToolProperties::FEATHER_VALUE] = { FEATHER_MIN, FEATHER_MAX, 50.0 };
55 info[StrokeToolProperties::PRESSURE_ENABLED] = true;
56 info[StrokeToolProperties::FEATHER_ENABLED] = false;
57 info[StrokeToolProperties::STABILIZATION_VALUE] = { StabilizationLevel::NONE, StabilizationLevel::STRONG, StabilizationLevel::STRONG };
58 info[StrokeToolProperties::FILLCONTOUR_ENABLED] = false;
59
60 toolProperties().insertProperties(info);
61 toolProperties().loadFrom(typeName(), pencilSettings);
62
63 if (toolProperties().requireMigration(pencilSettings, ToolProperties::VERSION_1)) {
64 toolProperties().setBaseValue(StrokeToolProperties::WIDTH_VALUE, pencilSettings.value("pencilWidth", 4.0).toReal());
65 toolProperties().setBaseValue(StrokeToolProperties::PRESSURE_ENABLED, pencilSettings.value("pencilPressure", true).toBool());
66 toolProperties().setBaseValue(StrokeToolProperties::STABILIZATION_VALUE, pencilSettings.value("pencilLineStabilization", StabilizationLevel::STRONG).toInt());
67 toolProperties().setBaseValue(StrokeToolProperties::FILLCONTOUR_ENABLED, pencilSettings.value("FillContour", false).toBool());
68
69 pencilSettings.remove("pencilWidth");
70 pencilSettings.remove("pencilPressure");
71 pencilSettings.remove("pencilLineStabilization");
72 pencilSettings.remove("FillContour");
73 }
74
75 toolProperties().setBaseValue(StrokeToolProperties::FEATHER_VALUE, info[StrokeToolProperties::FEATHER_VALUE].defaultReal());
76
77 mQuickSizingProperties.insert(Qt::ShiftModifier, StrokeToolProperties::WIDTH_VALUE);
78}
79
80QCursor PencilTool::cursor()
81{
82 if (mEditor->preference()->isOn(SETTING::TOOL_CURSOR))
83 {
84 return QCursor(QPixmap(":icons/general/cursor-pencil.svg"), 4, 14);
85 }
86 return QCursor(QPixmap(":icons/general/cross.png"), 10, 10);
87}
88
89void PencilTool::pointerPressEvent(PointerEvent *event)
90{
91 mInterpolator.pointerPressEvent(event);
92 if (handleQuickSizing(event)) {
93 return;
94 }
95
96 mMouseDownPoint = getCurrentPoint();
97 mLastBrushPoint = getCurrentPoint();
98
99 startStroke(event->inputType());
100
101 // note: why are we doing this on device press event?
102 if (mEditor->layers()->currentLayer()->type() == Layer::VECTOR && !mEditor->preference()->isOn(SETTING::INVISIBLE_LINES))
103 {
104 mScribbleArea->toggleThinLines();
105 }
106
107 StrokeTool::pointerPressEvent(event);
108}
109
110void PencilTool::pointerMoveEvent(PointerEvent* event)
111{
112 mInterpolator.pointerMoveEvent(event);
113 if (handleQuickSizing(event)) {
114 return;
115 }
116
117 if (event->buttons() & Qt::LeftButton && event->inputType() == mCurrentInputType)
118 {
119 mCurrentPressure = mInterpolator.getPressure();
120 drawStroke();
121 if (mSettings.stabilizerLevel() != mInterpolator.getStabilizerLevel())
122 {
123 mInterpolator.setStabilizerLevel(mSettings.stabilizerLevel());
124 }
125 }
126 StrokeTool::pointerMoveEvent(event);
127}
128
129void PencilTool::pointerReleaseEvent(PointerEvent *event)
130{
131 mInterpolator.pointerReleaseEvent(event);
132 if (handleQuickSizing(event)) {
133 return;
134 }
135
136 if (event->inputType() != mCurrentInputType) return;
137
138 mEditor->backup(typeName());
139 qreal distance = QLineF(getCurrentPoint(), mMouseDownPoint).length();
140 if (distance < 1)
141 {
142 paintAt(mMouseDownPoint);
143 }
144 else
145 {
146 drawStroke();
147 }
148
149 Layer* layer = mEditor->layers()->currentLayer();
150 if (layer->type() == Layer::VECTOR) {
151 paintVectorStroke(layer);
152 }
153 endStroke();
154
155 StrokeTool::pointerReleaseEvent(event);
156}
157
158// draw a single paint dab at the given location
159void PencilTool::paintAt(QPointF point)
160{
161 //qDebug() << "Made a single dab at " << point;
162 Layer* layer = mEditor->layers()->currentLayer();
163 if (layer->type() == Layer::BITMAP)
164 {
165 qreal opacity = (mSettings.pressureEnabled()) ? (mCurrentPressure * 0.5) : 1.0;
166 qreal pressure = (mSettings.pressureEnabled()) ? mCurrentPressure : 1.0;
167 qreal brushWidth = mSettings.width() * pressure;
168 qreal fixedBrushFeather = mSettings.feather();
169
170 mCurrentWidth = brushWidth;
171 mScribbleArea->drawPencil(point,
172 brushWidth,
173 fixedBrushFeather,
174 mEditor->color()->frontColor(),
175 opacity);
176 }
177}
178
179
180void PencilTool::drawStroke()
181{
182 StrokeTool::drawStroke();
183 QList<QPointF> p = mInterpolator.interpolateStroke();
184
185 Layer* layer = mEditor->layers()->currentLayer();
186
187 if (layer->type() == Layer::BITMAP)
188 {
189 qreal pressure = (mSettings.pressureEnabled()) ? mCurrentPressure : 1.0;
190 qreal opacity = (mSettings.pressureEnabled()) ? (mCurrentPressure * 0.5) : 1.0;
191 qreal brushWidth = mSettings.width() * pressure;
192 mCurrentWidth = brushWidth;
193
194 qreal fixedBrushFeather = mSettings.feather();
195 qreal brushStep = qMax(1.0, (0.5 * brushWidth));
196
197 QPointF a = mLastBrushPoint;
198 QPointF b = getCurrentPoint();
199
200 qreal distance = 4 * QLineF(b, a).length();
201 int steps = qRound(distance / brushStep);
202
203 for (int i = 0; i < steps; i++)
204 {
205 QPointF point = mLastBrushPoint + (i + 1) * brushStep * (getCurrentPoint() - mLastBrushPoint) / distance;
206 mScribbleArea->drawPencil(point,
207 brushWidth,
208 fixedBrushFeather,
209 mEditor->color()->frontColor(),
210 opacity);
211
212 if (i == (steps - 1))
213 {
214 mLastBrushPoint = getCurrentPoint();
215 }
216 }
217 }
218 else if (layer->type() == Layer::VECTOR)
219 {
220 mCurrentWidth = 0; // FIXME: WTF?
221 QPen pen(mEditor->color()->frontColor(),
222 1,
223 Qt::DotLine,
224 Qt::RoundCap,
225 Qt::RoundJoin);
226
227 if (p.size() == 4)
228 {
229 QPainterPath path(p[0]);
230 path.cubicTo(p[1],
231 p[2],
232 p[3]);
233 mScribbleArea->drawPath(path, pen, Qt::NoBrush, QPainter::CompositionMode_Source);
234 }
235 }
236}
237
238void PencilTool::paintVectorStroke(Layer* layer)
239{
240 if (mStrokePoints.empty())
241 return;
242
243 // Clear the temporary pixel path
244 mScribbleArea->clearDrawingBuffer();
245 qreal tol = mScribbleArea->getCurveSmoothing() / mEditor->view()->scaling();
246
247 BezierCurve curve(mStrokePoints, mStrokePressures, tol);
248 curve.setWidth(0);
249 curve.setFeather(0);
250 curve.setFilled(false);
251 curve.setInvisibility(true);
252 curve.setVariableWidth(false);
253 curve.setColorNumber(mEditor->color()->frontColorNumber());
254 VectorImage* vectorImage = static_cast<LayerVector*>(layer)->getLastVectorImageAtFrame(mEditor->currentFrame(), 0);
255 if (vectorImage == nullptr) { return; } // Can happen if the first frame is deleted while drawing
256 vectorImage->addCurve(curve, qAbs(mEditor->view()->scaling()), false);
257
258 if (mSettings.fillContourEnabled())
259 {
260 vectorImage->fillContour(mStrokePoints,
261 mEditor->color()->frontColorNumber());
262 }
263
264 if (vectorImage->isAnyCurveSelected() || mEditor->select()->somethingSelected())
265 {
266 mEditor->deselectAll();
267 }
268
269 // select last/newest curve
270 vectorImage->setSelected(vectorImage->getLastCurveNumber(), true);
271
272 // TODO: selection doesn't apply on enter
273
274 mEditor->setModified(mEditor->layers()->currentLayerIndex(), mEditor->currentFrame());
275}
BezierCurve
Definition: beziercurve.h:34
ColorManager::frontColor
QColor frontColor(bool useIndexedColor=true)
frontColor
Definition: colormanager.cpp:61
Layer
Definition: layer.h:33
LayerVector
Definition: layervector.h:26
PointerEvent
Definition: pointerevent.h:8
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::getLastCurveNumber
int getLastCurveNumber()
VectorImage::getLastCurveNumber.
Definition: vectorimage.cpp:2264
VectorImage::addCurve
void addCurve(BezierCurve &newCurve, qreal factor, bool interacts=true)
VectorImage::addCurve.
Definition: vectorimage.cpp:341
VectorImage::fillContour
void fillContour(QList< QPointF > contourPath, int color)
VectorImage::fillContour.
Definition: vectorimage.cpp:1829
VectorImage::isAnyCurveSelected
bool isAnyCurveSelected()
VectorImage::isAnyCurveSelected.
Definition: vectorimage.cpp:826
QCursor
QHash
QHash::insert
QHash::iterator insert(const Key &key, const T &value)
QLineF
QLineF::length
qreal length() const const
QList
QList::empty
bool empty() const const
QList::size
int size() const const
QObject
QObject::event
virtual bool event(QEvent *e)
QPainter::CompositionMode_Source
CompositionMode_Source
QPainterPath
QPen
QPixmap
QPointF
QSettings
QSettings::remove
void remove(const QString &key)
QSettings::value
QVariant value(const QString &key, const QVariant &defaultValue) const const
Qt::NoBrush
NoBrush
Qt::ShiftModifier
ShiftModifier
Qt::LeftButton
LeftButton
Qt::RoundCap
RoundCap
Qt::RoundJoin
RoundJoin
Qt::DotLine
DotLine
QVariant::toBool
bool toBool() const const
QVariant::toInt
int toInt(bool *ok) const const
QVariant::toReal
qreal toReal(bool *ok) const const
Generated on Thu Jan 29 2026 05:10:58 for Pencil2D by doxygen 1.9.6 based on revision 2c971b937d0608b05aa496b0f7e9aebcddf8e7fc