All Classes Namespaces Functions Variables Enumerations Properties Pages
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 
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 
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 
54 void StrokeManager::setPressure(float pressure)
55 {
56  mTabletPressure = pressure;
57 }
58 
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 
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 
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 
106 void StrokeManager::setStabilizerLevel(int level)
107 {
108  mStabilizerLevel = level;
109 }
110 
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 
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 
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 
219 void StrokeManager::interpolatePollAndPaint()
220 {
221  //qDebug() <<"inpol:" << mStabilizerLevel << "strokes"<< strokeQueue;
222  if (!strokeQueue.isEmpty())
223  {
224  interpolatePoll();
225  interpolateStroke();
226  }
227 }
228 
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 
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 
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
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 
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 }
void setInterval(int msec)
void enqueue(const T &t)
T dequeue()
NoButton
QPointF posF() const
Returns the QPointF of the device Returns pos() if used on mouse event.
void timeout()
qreal x() const const
qreal y() const const
bool isTabletEvent() const
Returns true if the device was tablet, otherwise false.
qreal pressure() const
Returns a value between 0 and 1 for tablet events, otherwise 1.0.
void stop()
Qt::MouseButton button() const
Returns Qt::MouseButton()
void start(int msec)
QMetaObject::Connection connect(const QObject *sender, const char *signal, const QObject *receiver, const char *method, Qt::ConnectionType type)
qint64 elapsed() const const