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