Pencil2D Animation
Download Community News Docs Contribute

core_lib/src/tool/strokemanager.cpp Source File

  • Main Page
  • Related Pages
  • Classes
  • Files
  •  
  • File List
Loading...
Searching...
No Matches
  • core_lib
  • src
  • tool
strokemanager.cpp
1/***************************************************************************
2 * This code is heavily influenced by the instrument proxy from QAquarelle *
3 * QAquarelle - Copyright (C) 2009 by Anton R. <commanderkyle@gmail.com> *
4 * *
5 * QAquarelle is free software; you can redistribute it and/or modify *
6 * it under the terms of the GNU General Public License as published by *
7 * the Free Software Foundation; either version 2 of the License, or *
8 * (at your option) any later version. *
9 * *
10 * This program is distributed in the hope that it will be useful, *
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of *
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
13 * GNU General Public License for more details. *
14 * *
15 * You should have received a copy of the GNU General Public License *
16 * along with this program; if not, write to the *
17 * Free Software Foundation, Inc., *
18 * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. *
19 ***************************************************************************/
20
21#include "strokemanager.h"
22
23#include <cmath>
24#include <limits>
25#include <QDebug>
26#include <QLineF>
27#include <QPainterPath>
28#include "object.h"
29#include "pointerevent.h"
30
31
32StrokeManager::StrokeManager()
33{
34 m_timeshot = 0;
35
36 mTabletInUse = false;
37 mTabletPressure = 0;
38
39 reset();
40 connect(&timer, &QTimer::timeout, this, &StrokeManager::interpolatePollAndPaint);
41}
42
43void StrokeManager::reset()
44{
45 mStrokeStarted = false;
46 pressureQueue.clear();
47 strokeQueue.clear();
48 pressure = 0.0f;
49 mHasTangent = false;
50 timer.stop();
51 mStabilizerLevel = -1;
52}
53
54void StrokeManager::setPressure(float pressure)
55{
56 mTabletPressure = pressure;
57}
58
59void StrokeManager::pointerPressEvent(PointerEvent* event)
60{
61 reset();
62 if (!(event->button() == Qt::NoButton)) // if the user is pressing the left/right button
63 {
64 //qDebug() << "press";
65 mLastPressPixel = mCurrentPressPixel;
66 mCurrentPressPixel = event->posF();
67 }
68
69 mLastPixel = mCurrentPixel = event->posF();
70
71 mStrokeStarted = true;
72 setPressure(event->pressure());
73}
74
75void StrokeManager::pointerMoveEvent(PointerEvent* event)
76{
77 // only applied to drawing tools.
78 if (mStabilizerLevel != -1)
79 {
80 smoothMousePos(event->posF());
81 }
82 else
83 {
84 // No smoothing
85 mLastPixel = mCurrentPixel;
86 mCurrentPixel = event->posF();
87 mLastInterpolated = mCurrentPixel;
88 }
89 if(event->isTabletEvent())
90 {
91 setPressure(event->pressure());
92 }
93}
94
95void StrokeManager::pointerReleaseEvent(PointerEvent* event)
96{
97 // flush out stroke
98 if (mStrokeStarted)
99 {
100 pointerMoveEvent(event);
101 }
102
103 mStrokeStarted = false;
104}
105
106void StrokeManager::setStabilizerLevel(int level)
107{
108 mStabilizerLevel = level;
109}
110
111void StrokeManager::smoothMousePos(QPointF pos)
112{
113 // Smooth mouse position before drawing
114 QPointF smoothPos;
115
116 if (mStabilizerLevel == StabilizationLevel::NONE)
117 {
118 mLastPixel = mCurrentPixel;
119 mCurrentPixel = pos;
120 mLastInterpolated = mCurrentPixel;
121 }
122 else if (mStabilizerLevel == StabilizationLevel::SIMPLE)
123 {
124 // simple interpolation
125 smoothPos = QPointF((pos.x() + mCurrentPixel.x()) / 2.0, (pos.y() + mCurrentPixel.y()) / 2.0);
126 mLastPixel = mCurrentPixel;
127 mCurrentPixel = smoothPos;
128 mLastInterpolated = mCurrentPixel;
129
130 // shift queue
131 while (strokeQueue.size() >= STROKE_QUEUE_LENGTH)
132 {
133 strokeQueue.pop_front();
134 }
135
136 strokeQueue.push_back(smoothPos);
137 }
138 else if (mStabilizerLevel == StabilizationLevel::STRONG)
139 {
140 smoothPos = QPointF((pos.x() + mLastInterpolated.x()) / 2.0, (pos.y() + mLastInterpolated.y()) / 2.0);
141
142 mLastInterpolated = mCurrentPixel;
143 mCurrentPixel = smoothPos;
144 mLastPixel = mLastInterpolated;
145 }
146
147 mousePos = pos;
148
149 if (!mStrokeStarted)
150 {
151 return;
152 }
153
154 if (!mTabletInUse) // a mouse is used instead of a tablet
155 {
156 setPressure(1.0);
157 }
158}
159
160
161QPointF StrokeManager::interpolateStart(QPointF firstPoint)
162{
163 if (mStabilizerLevel == StabilizationLevel::SIMPLE)
164 {
165 // Clear queue
166 strokeQueue.clear();
167 pressureQueue.clear();
168
169 mSingleshotTime.start();
170 previousTime = mSingleshotTime.elapsed();
171
172 mLastPixel = firstPoint;
173 }
174 else if (mStabilizerLevel == StabilizationLevel::STRONG)
175 {
176 mSingleshotTime.start();
177 previousTime = mSingleshotTime.elapsed();
178
179 // Clear queue
180 strokeQueue.clear();
181 pressureQueue.clear();
182
183 const int sampleSize = 5;
184 Q_ASSERT(sampleSize > 0);
185
186 // fill strokeQueue with firstPoint x times
187 for (int i = sampleSize; i > 0; i--)
188 {
189 strokeQueue.enqueue(firstPoint);
190 }
191
192 // last interpolated stroke should always be firstPoint
193 mLastInterpolated = firstPoint;
194
195 // draw and poll each millisecond
196 timer.setInterval(sampleSize);
197 timer.start();
198 }
199 else if (mStabilizerLevel == StabilizationLevel::NONE)
200 {
201 // Clear queue
202 strokeQueue.clear();
203 pressureQueue.clear();
204
205 mLastPixel = firstPoint;
206 }
207 return firstPoint;
208}
209
210void StrokeManager::interpolatePoll()
211{
212 // remove oldest stroke
213 strokeQueue.dequeue();
214
215 // add new stroke with the last interpolated pixel position
216 strokeQueue.enqueue(mLastInterpolated);
217}
218
219void StrokeManager::interpolatePollAndPaint()
220{
221 //qDebug() <<"inpol:" << mStabilizerLevel << "strokes"<< strokeQueue;
222 if (!strokeQueue.isEmpty())
223 {
224 interpolatePoll();
225 interpolateStroke();
226 }
227}
228
229QList<QPointF> StrokeManager::interpolateStroke()
230{
231 // is nan initially
232 QList<QPointF> result;
233
234 if (mStabilizerLevel == StabilizationLevel::SIMPLE)
235 {
236 result = tangentInpolOp(result);
237
238 }
239 else if (mStabilizerLevel == StabilizationLevel::STRONG)
240 {
241 qreal x = 0;
242 qreal y = 0;
243 qreal pressure = 0;
244 result = meanInpolOp(result, x, y, pressure);
245
246 }
247 else if (mStabilizerLevel == StabilizationLevel::NONE)
248 {
249 result = noInpolOp(result);
250 }
251 return result;
252}
253
254QList<QPointF> StrokeManager::noInpolOp(QList<QPointF> points)
255{
256 setPressure(getPressure());
257
258 points << mLastPixel << mLastPixel << mCurrentPixel << mCurrentPixel;
259
260 // Set lastPixel to CurrentPixel
261 // new interpolated pixel
262 mLastPixel = mCurrentPixel;
263
264 return points;
265}
266
267QList<QPointF> StrokeManager::tangentInpolOp(QList<QPointF> points)
268{
269 int time = mSingleshotTime.elapsed();
270 static const qreal smoothness = 1.f;
271 QLineF line(mLastPixel, mCurrentPixel);
272
273 qreal scaleFactor = line.length() * 3.f;
274
275 if (!mHasTangent && scaleFactor > 0.01f)
276 {
277 mHasTangent = true;
278 /*
279 qDebug() << "scaleFactor" << scaleFactor
280 << "current pixel " << mCurrentPixel
281 << "last pixel" << mLastPixel;
282 */
283 m_previousTangent = (mCurrentPixel - mLastPixel) * smoothness / (3.0 * scaleFactor);
284 //qDebug() << "previous tangent" << m_previousTangent;
285 QLineF _line(QPointF(0, 0), m_previousTangent);
286 // don't bother for small tangents, as they can induce single pixel wobbliness
287 if (_line.length() < 2)
288 {
289 m_previousTangent = QPointF(0, 0);
290 }
291 }
292 else
293 {
294 QPointF c1 = mLastPixel + m_previousTangent * scaleFactor;
295 QPointF newTangent = (mCurrentPixel - c1) * smoothness / (3.0 * scaleFactor);
296 //qDebug() << "scalefactor1=" << scaleFactor << m_previousTangent << newTangent;
297 if (scaleFactor == 0)
298 {
299 newTangent = QPointF(0, 0);
300 }
301 else
302 {
303 //QLineF _line(QPointF(0,0), newTangent);
304 //if (_line.length() < 2)
305 //{
306 // newTangent = QPointF(0,0);
307 //}
308 }
309 QPointF c2 = mCurrentPixel - newTangent * scaleFactor;
310 //c1 = mLastPixel;
311 //c2 = mCurrentPixel;
312 points << mLastPixel << c1 << c2 << mCurrentPixel;
313 //qDebug() << mLastPixel << c1 << c2 << mCurrentPixel;
314 m_previousTangent = newTangent;
315 }
316
317 previousTime = time;
318 return points;
319}
320
321// Mean sampling interpolation operation
322QList<QPointF> StrokeManager::meanInpolOp(QList<QPointF> points, qreal x, qreal y, qreal pressure)
323{
324 for (int i = 0; i < strokeQueue.size(); i++)
325 {
326 x += strokeQueue[i].x();
327 y += strokeQueue[i].y();
328 pressure += getPressure();
329 }
330
331 // get arithmetic mean of x, y and pressure
332 x /= strokeQueue.size();
333 y /= strokeQueue.size();
334 pressure /= strokeQueue.size();
335
336 // Use our interpolated points
337 QPointF mNewInterpolated = mLastInterpolated;
338 mNewInterpolated = QPointF(x, y);
339
340 points << mLastPixel << mLastInterpolated << mNewInterpolated << mCurrentPixel;
341
342 // Set lastPixel non interpolated pixel to our
343 // new interpolated pixel
344 mLastPixel = mNewInterpolated;
345
346 return points;
347}
348
349void StrokeManager::interpolateEnd()
350{
351 // Stop timer
352 timer.stop();
353 if (mStabilizerLevel == StabilizationLevel::STRONG)
354 {
355 if (!strokeQueue.isEmpty())
356 {
357 // How many samples should we get point from?
358 // TODO: Qt slider.
359 int sampleSize = 5;
360
361 Q_ASSERT(sampleSize > 0);
362 for (int i = sampleSize; i > 0; i--)
363 {
364 interpolatePoll();
365 interpolateStroke();
366 }
367 }
368 }
369}
PointerEvent
Definition: pointerevent.h:8
QElapsedTimer::elapsed
qint64 elapsed() const const
QElapsedTimer::start
void start()
QLineF
QList
QObject::connect
QMetaObject::Connection connect(const QObject *sender, const char *signal, const QObject *receiver, const char *method, Qt::ConnectionType type)
QObject::event
virtual bool event(QEvent *e)
QPointF
QPointF::x
qreal x() const const
QPointF::y
qreal y() const const
QQueue::dequeue
T dequeue()
QQueue::enqueue
void enqueue(const T &t)
Qt::NoButton
NoButton
QTimer::setInterval
void setInterval(int msec)
QTimer::start
void start(int msec)
QTimer::stop
void stop()
QTimer::timeout
void timeout()
Generated on Sun Sep 24 2023 19:39:35 for Pencil2D by doxygen 1.9.6 based on revision 1395c86cb17dafbb32de44cbabe1f4c58636468d