All Classes Namespaces Functions Variables Enumerations Properties Pages
penciltool.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 "penciltool.h"
18 
19 #include <QSettings>
20 #include <QPixmap>
21 #include "pointerevent.h"
22 
23 #include "layermanager.h"
24 #include "colormanager.h"
25 #include "strokemanager.h"
26 #include "viewmanager.h"
27 #include "preferencemanager.h"
28 #include "selectionmanager.h"
29 
30 #include "editor.h"
31 #include "scribblearea.h"
32 #include "blitrect.h"
33 #include "layervector.h"
34 #include "vectorimage.h"
35 
36 
37 PencilTool::PencilTool(QObject* parent) : StrokeTool(parent)
38 {
39 }
40 
41 void PencilTool::loadSettings()
42 {
43  mPropertyEnabled[WIDTH] = true;
44  mPropertyEnabled[PRESSURE] = true;
45  mPropertyEnabled[VECTORMERGE] = false;
46  mPropertyEnabled[STABILIZATION] = true;
47  mPropertyEnabled[FILLCONTOUR] = true;
48 
49  QSettings settings(PENCIL2D, PENCIL2D);
50  properties.width = settings.value("pencilWidth", 4).toDouble();
51  properties.feather = 50;
52  properties.pressure = settings.value("pencilPressure", true).toBool();
53  properties.stabilizerLevel = settings.value("pencilLineStabilization", StabilizationLevel::STRONG).toInt();
54  properties.useAA = DISABLED;
55  properties.useFeather = true;
56  properties.useFillContour = false;
57  // properties.invisibility = 1;
58  // properties.preserveAlpha = 0;
59 
60  mQuickSizingProperties.insert(Qt::ShiftModifier, WIDTH);
61 }
62 
63 void PencilTool::resetToDefault()
64 {
65  setWidth(4.0);
66  setFeather(50);
67  setUseFeather(true);
68  setStabilizerLevel(StabilizationLevel::STRONG);
69 }
70 
71 void PencilTool::setWidth(const qreal width)
72 {
73  // Set current property
74  properties.width = width;
75 
76  // Update settings
77  QSettings settings(PENCIL2D, PENCIL2D);
78  settings.setValue("pencilWidth", width);
79  settings.sync();
80 }
81 
82 void PencilTool::setFeather(const qreal feather)
83 {
84  properties.feather = feather;
85 }
86 
87 void PencilTool::setUseFeather(const bool usingFeather)
88 {
89  // Set current property
90  properties.useFeather = usingFeather;
91 
92  // Update settings
93  QSettings settings(PENCIL2D, PENCIL2D);
94  settings.setValue("brushUseFeather", usingFeather);
95  settings.sync();
96 }
97 
98 void PencilTool::setInvisibility(const bool)
99 {
100  // force value
101  properties.invisibility = 1;
102 }
103 
104 void PencilTool::setPressure(const bool pressure)
105 {
106  // Set current property
107  properties.pressure = pressure;
108 
109  // Update settings
110  QSettings settings(PENCIL2D, PENCIL2D);
111  settings.setValue("pencilPressure", pressure);
112  settings.sync();
113 }
114 
115 void PencilTool::setPreserveAlpha(const bool preserveAlpha)
116 {
117  // force value
118  Q_UNUSED( preserveAlpha )
119  properties.preserveAlpha = 0;
120 }
121 
122 void PencilTool::setStabilizerLevel(const int level)
123 {
124  properties.stabilizerLevel = level;
125 
126  QSettings settings(PENCIL2D, PENCIL2D);
127  settings.setValue("pencilLineStabilization", level);
128  settings.sync();
129 }
130 
131 void PencilTool::setUseFillContour(const bool useFillContour)
132 {
133  properties.useFillContour = useFillContour;
134 
135  QSettings settings(PENCIL2D, PENCIL2D);
136  settings.setValue("FillContour", useFillContour);
137  settings.sync();
138 }
139 
140 QCursor PencilTool::cursor()
141 {
142  if (mEditor->preference()->isOn(SETTING::TOOL_CURSOR))
143  {
144  return QCursor(QPixmap(":icons/pencil2.png"), 0, 16);
145  }
146  return QCursor(QPixmap(":icons/cross.png"), 10, 10);
147 }
148 
149 void PencilTool::pointerPressEvent(PointerEvent *event)
150 {
151  mMouseDownPoint = getCurrentPoint();
152  mLastBrushPoint = getCurrentPoint();
153 
154  startStroke(event->inputType());
155 
156  // note: why are we doing this on device press event?
157  if (mEditor->layers()->currentLayer()->type() == Layer::VECTOR && !mEditor->preference()->isOn(SETTING::INVISIBLE_LINES))
158  {
159  mScribbleArea->toggleThinLines();
160  }
161 }
162 
163 void PencilTool::pointerMoveEvent(PointerEvent* event)
164 {
165  if (event->buttons() & Qt::LeftButton && event->inputType() == mCurrentInputType)
166  {
167  mCurrentPressure = strokeManager()->getPressure();
168  drawStroke();
169  if (properties.stabilizerLevel != strokeManager()->getStabilizerLevel())
170  strokeManager()->setStabilizerLevel(properties.stabilizerLevel);
171  }
172 }
173 
174 void PencilTool::pointerReleaseEvent(PointerEvent *event)
175 {
176  if (event->inputType() != mCurrentInputType) return;
177 
178  mEditor->backup(typeName());
179  qreal distance = QLineF(getCurrentPoint(), mMouseDownPoint).length();
180  if (distance < 1)
181  {
182  paintAt(mMouseDownPoint);
183  }
184  else
185  {
186  drawStroke();
187  }
188 
189  Layer* layer = mEditor->layers()->currentLayer();
190  if (layer->type() == Layer::BITMAP)
191  paintBitmapStroke();
192  else if (layer->type() == Layer::VECTOR)
193  paintVectorStroke(layer);
194  endStroke();
195 }
196 
197 // draw a single paint dab at the given location
198 void PencilTool::paintAt(QPointF point)
199 {
200  //qDebug() << "Made a single dab at " << point;
201  Layer* layer = mEditor->layers()->currentLayer();
202  if (layer->type() == Layer::BITMAP)
203  {
204  qreal opacity = (properties.pressure) ? (mCurrentPressure * 0.5) : 1.0;
205  qreal pressure = (properties.pressure) ? mCurrentPressure : 1.0;
206  qreal brushWidth = properties.width * pressure;
207  qreal fixedBrushFeather = properties.feather;
208 
209  mCurrentWidth = brushWidth;
210 
211  BlitRect rect(point.toPoint());
212  mScribbleArea->drawPencil(point,
213  brushWidth,
214  fixedBrushFeather,
215  mEditor->color()->frontColor(),
216  opacity);
217 
218  int rad = qRound(brushWidth) / 2 + 2;
219  mScribbleArea->refreshBitmap(rect, rad);
220  }
221 }
222 
223 
224 void PencilTool::drawStroke()
225 {
226  StrokeTool::drawStroke();
227  QList<QPointF> p = strokeManager()->interpolateStroke();
228 
229  Layer* layer = mEditor->layers()->currentLayer();
230 
231  if (layer->type() == Layer::BITMAP)
232  {
233  qreal pressure = (properties.pressure) ? mCurrentPressure : 1.0;
234  qreal opacity = (properties.pressure) ? (mCurrentPressure * 0.5) : 1.0;
235  qreal brushWidth = properties.width * pressure;
236  mCurrentWidth = brushWidth;
237 
238  qreal fixedBrushFeather = properties.feather;
239  qreal brushStep = qMax(1.0, (0.5 * brushWidth));
240 
241  BlitRect rect;
242 
243  QPointF a = mLastBrushPoint;
244  QPointF b = getCurrentPoint();
245 
246  qreal distance = 4 * QLineF(b, a).length();
247  int steps = qRound(distance / brushStep);
248 
249  for (int i = 0; i < steps; i++)
250  {
251  QPointF point = mLastBrushPoint + (i + 1) * brushStep * (getCurrentPoint() - mLastBrushPoint) / distance;
252  rect.extend(point.toPoint());
253  mScribbleArea->drawPencil(point,
254  brushWidth,
255  fixedBrushFeather,
256  mEditor->color()->frontColor(),
257  opacity);
258 
259  if (i == (steps - 1))
260  {
261  mLastBrushPoint = getCurrentPoint();
262  }
263  }
264 
265  int rad = qRound(brushWidth) / 2 + 2;
266 
267  mScribbleArea->paintBitmapBufferRect(rect);
268  mScribbleArea->refreshBitmap(rect, rad);
269  }
270  else if (layer->type() == Layer::VECTOR)
271  {
272  properties.useFeather = false;
273  mCurrentWidth = 0; // FIXME: WTF?
274  QPen pen(mEditor->color()->frontColor(),
275  1,
276  Qt::DotLine,
277  Qt::RoundCap,
278  Qt::RoundJoin);
279 
280  int rad = qRound((mCurrentWidth / 2 + 2) * mEditor->view()->scaling());
281 
282  if (p.size() == 4)
283  {
284  QPainterPath path(p[0]);
285  path.cubicTo(p[1],
286  p[2],
287  p[3]);
288  mScribbleArea->drawPath(path, pen, Qt::NoBrush, QPainter::CompositionMode_Source);
289  mScribbleArea->refreshVector(path.boundingRect().toRect(), rad);
290  }
291  }
292 }
293 
294 
295 void PencilTool::paintBitmapStroke()
296 {
297  mScribbleArea->paintBitmapBuffer();
298  mScribbleArea->clearBitmapBuffer();
299 }
300 
301 void PencilTool::paintVectorStroke(Layer* layer)
302 {
303  if (mStrokePoints.empty())
304  return;
305 
306  // Clear the temporary pixel path
307  mScribbleArea->clearBitmapBuffer();
308  qreal tol = mScribbleArea->getCurveSmoothing() / mEditor->view()->scaling();
309 
310  BezierCurve curve(mStrokePoints, mStrokePressures, tol);
311  curve.setWidth(0);
312  curve.setFeather(0);
313  curve.setFilled(false);
314  curve.setInvisibility(true);
315  curve.setVariableWidth(false);
316  curve.setColorNumber(mEditor->color()->frontColorNumber());
317  VectorImage* vectorImage = static_cast<LayerVector*>(layer)->getLastVectorImageAtFrame(mEditor->currentFrame(), 0);
318  if (vectorImage == nullptr) { return; } // Can happen if the first frame is deleted while drawing
319  vectorImage->addCurve(curve, qAbs(mEditor->view()->scaling()), properties.vectorMergeEnabled);
320 
321  if (properties.useFillContour)
322  {
323  vectorImage->fillContour(mStrokePoints,
324  mEditor->color()->frontColorNumber());
325  }
326 
327  if (vectorImage->isAnyCurveSelected() || mEditor->select()->somethingSelected())
328  {
329  mEditor->deselectAll();
330  }
331 
332  // select last/newest curve
333  vectorImage->setSelected(vectorImage->getLastCurveNumber(), true);
334 
335  // TODO: selection doesn't apply on enter
336 
337  mScribbleArea->setModified(mEditor->layers()->currentLayerIndex(), mEditor->currentFrame());
338 }
ShiftModifier
QHash::iterator insert(const Key &key, const T &value)
LeftButton
void fillContour(QList< QPointF > contourPath, int color)
VectorImage::fillContour.
void setModified(int layerNumber, int frameNumber)
Set frame on layer to modified and invalidate current frame cache.
RoundCap
int size() const const
virtual bool event(QEvent *e)
qreal length() const const
bool empty() const const
bool isAnyCurveSelected()
VectorImage::isAnyCurveSelected.
Definition: layer.h:38
void addCurve(BezierCurve &newCurve, qreal factor, bool interacts=true)
VectorImage::addCurve.
Qt::MouseButtons buttons() const
Returns Qt::MouseButtons()
CompositionMode_Source
RoundJoin
QColor frontColor(bool useIndexedColor=true)
frontColor
QPoint toPoint() const const
int getLastCurveNumber()
VectorImage::getLastCurveNumber.
void setSelected(int curveNumber, bool YesOrNo)
VectorImage::setSelected.