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
StrokeManager()32 StrokeManager::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
reset()43 void 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
setPressure(float pressure)54 void StrokeManager::setPressure(float pressure)
55 {
56 mTabletPressure = pressure;
57 }
58
pointerPressEvent(PointerEvent * event)59 void 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
pointerMoveEvent(PointerEvent * event)75 void 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
pointerReleaseEvent(PointerEvent * event)95 void StrokeManager::pointerReleaseEvent(PointerEvent* event)
96 {
97 // flush out stroke
98 if (mStrokeStarted)
99 {
100 pointerMoveEvent(event);
101 }
102
103 mStrokeStarted = false;
104 }
105
setStabilizerLevel(int level)106 void StrokeManager::setStabilizerLevel(int level)
107 {
108 mStabilizerLevel = level;
109 }
110
smoothMousePos(QPointF pos)111 void 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
interpolateStart(QPointF firstPoint)161 QPointF 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
interpolatePoll()210 void 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
interpolatePollAndPaint()219 void StrokeManager::interpolatePollAndPaint()
220 {
221 //qDebug() <<"inpol:" << mStabilizerLevel << "strokes"<< strokeQueue;
222 if (!strokeQueue.isEmpty())
223 {
224 interpolatePoll();
225 interpolateStroke();
226 }
227 }
228
interpolateStroke()229 QList<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
noInpolOp(QList<QPointF> points)254 QList<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
tangentInpolOp(QList<QPointF> points)267 QList<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
meanInpolOp(QList<QPointF> points,qreal x,qreal y,qreal pressure)322 QList<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
interpolateEnd()349 void 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 }
370