Pencil2D  ff90c0872e88be3bf81c548cd60f01983012ec49
Pencil2D is an animation software for both bitmap and vector graphics. It is free, multi-platform, and open source.
 All Classes Functions
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 
22 #include <cmath>
23 #include <limits>
24 #include <QDebug>
25 #include <QLineF>
26 #include <QPainterPath>
27 #include "strokemanager.h"
28 #include "object.h"
29 
30 
31 StrokeManager::StrokeManager()
32 {
33  m_timeshot = 0;
34 
35  mTabletInUse = false;
36  mTabletPressure = 0;
37  mMeanPressure = 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  hasTangent = false;
50  timer.stop();
51  mInpolLevel = -1;
52 }
53 
54 void StrokeManager::setPressure(float pressure)
55 {
56  mTabletPressure = pressure;
57 }
58 
59 QPointF StrokeManager::getEventPosition(QMouseEvent* event)
60 {
61  QPointF pos;
62 
63  if ( mTabletInUse )
64  {
65  // QT BUG (Wacom Tablets): updates are not synchronised in Windows giving different coordinates.
66  // Clue: Not a Microsoft nor Wacom problem because other windows apps are working fine in the same tablet mode.
67  // Solved: Qt bug in Wacom coding -> a lot of patches but no real solutions.
68  // QPointF pos2 = event->pos() + mTabletPosition - event->globalPos();
69  // Patch: next line skips the coordinate problem and it seems safe .
70  pos = event->pos() + mTabletPosition - mTabletPosition.toPoint();
71  //pos = event->pos();
72  //qDebug() << "New pos" << pos << ", Old pos" << pos2;
73  }
74  else
75  {
76  pos = event->localPos();
77  }
78 
79  return pos;
80 }
81 
82 void StrokeManager::mousePressEvent(QMouseEvent* event)
83 {
84  reset();
85  if ( !(event->button() == Qt::NoButton) ) // if the user is pressing the left/right button
86  {
87  mLastPressPixel = getEventPosition(event);
88  }
89  mLastPixel = getEventPosition( event );
90  mCurrentPixel = getEventPosition( event );
91 
92  mStrokeStarted = true;
93 
94 }
95 
96 void StrokeManager::mouseReleaseEvent(QMouseEvent* event)
97 {
98  // flush out stroke
99  if ( mStrokeStarted )
100  {
101  mouseMoveEvent(event);
102  }
103 
104  mStrokeStarted = false;
105 }
106 
107 void StrokeManager::tabletEvent(QTabletEvent* event)
108 {
109  if (event->type() == QEvent::TabletPress) { mTabletInUse = true; }
110  if (event->type() == QEvent::TabletRelease) { mTabletInUse = false; }
111 
112  mTabletPosition = event->posF();
113  setPressure(event->pressure());
114 }
115 
116 void StrokeManager::setInpolLevel(int level)
117 {
118  mInpolLevel = level;
119 }
120 
121 void StrokeManager::mouseMoveEvent(QMouseEvent* event)
122 {
123  QPointF pos = getEventPosition(event);
124 
125  // only applied to drawing tools.
126  if (mInpolLevel != -1){
127  smoothMousePos(pos);
128  } else {
129  // No smoothing
130  mLastPixel = mCurrentPixel;
131  mCurrentPixel = pos;
132  mLastInterpolated = mCurrentPixel;
133 
134  }
135 }
136 
137 void StrokeManager::smoothMousePos(QPointF pos)
138 {
139 
140  // Smooth mouse position before drawing
141  QPointF smoothPos;
142 
143  if (mInpolLevel == 0) {
144 
145  mLastPixel = mCurrentPixel;
146  mCurrentPixel = pos;
147  mLastInterpolated = mCurrentPixel;
148  }
149  else if (mInpolLevel == 1) {
150 
151  // simple interpolation
152  smoothPos = QPointF( ( pos.x() + mCurrentPixel.x() ) / 2.0, ( pos.y() + mCurrentPixel.y() ) / 2.0 );
153  mLastPixel = mCurrentPixel;
154  mCurrentPixel = smoothPos;
155  mLastInterpolated = mCurrentPixel;
156 
157  // shift queue
158  while ( strokeQueue.size() >= STROKE_QUEUE_LENGTH )
159  {
160  strokeQueue.pop_front();
161  }
162 
163  strokeQueue.push_back( smoothPos );
164  } else if (mInpolLevel == 2 ) {
165 
166  smoothPos = QPointF( ( pos.x() + mLastInterpolated.x() ) / 2.0, ( pos.y() + mLastInterpolated.y() ) / 2.0 );
167 
168  mLastInterpolated = mCurrentPixel;
169  mCurrentPixel = smoothPos;
170  mLastPixel = mLastInterpolated;
171  }
172 
173  mousePos = pos;
174 
175  if ( !mStrokeStarted )
176  {
177  return;
178  }
179 
180  if (!mTabletInUse) // a mouse is used instead of a tablet
181  {
182  setPressure(1.0);
183  }
184 }
185 
186 
187 QPointF StrokeManager::interpolateStart(QPointF firstPoint)
188 {
189  if (mInpolLevel == 1) {
190  // Clear queue
191  strokeQueue.clear();
192  pressureQueue.clear();
193 
194  mSingleshotTime.start();
195  previousTime = mSingleshotTime.elapsed();
196 
197  mLastPixel = firstPoint;
198  }
199  else if (mInpolLevel == 2){
200 
201  mSingleshotTime.start();
202  previousTime = mSingleshotTime.elapsed();
203 
204  int sampleSize = 5;
205 
206  // Clear queue
207  strokeQueue.clear();
208  pressureQueue.clear();
209 
210  assert(sampleSize > 0);
211 
212  // fill strokeQueue with firstPoint x times
213  for ( int i = sampleSize; i > 0; i--) {
214  strokeQueue.enqueue(firstPoint);
215  }
216 
217  // last interpolated stroke should always be firstPoint
218  mLastInterpolated = firstPoint;
219 
220  // draw and poll each millisecond
221  timer.setInterval(sampleSize);
222  timer.start();
223  } else if (mInpolLevel == 0) {
224  // Clear queue
225  strokeQueue.clear();
226  pressureQueue.clear();
227 
228  mLastPixel = firstPoint;
229  }
230  return firstPoint;
231 }
232 
233 void StrokeManager::interpolatePoll()
234 {
235  // remove oldest stroke
236  strokeQueue.dequeue();
237 
238  // add new stroke with the last interpolated pixel position
239  strokeQueue.enqueue(mLastInterpolated);
240 }
241 
242 void StrokeManager::interpolatePollAndPaint()
243 {
244  //qDebug() <<"inpol:" << mInpolLevel << "strokes"<< strokeQueue;
245  if (!strokeQueue.isEmpty())
246  {
247  interpolatePoll();
248  interpolateStroke();
249  }
250 }
251 
252 QList<QPointF> StrokeManager::interpolateStroke()
253 {
254  // is nan initially
255  QList<QPointF> result;
256 
257  qreal x = 0,
258  y = 0,
259  pressure = 0;
260 
261  if (mInpolLevel == 1) {
262 
263  result = tangentInpolOp(result);
264 
265  }
266  else if (mInpolLevel == 2){
267 
268  result = meanInpolOp(result, x, y, pressure);
269 
270  } else if (mInpolLevel == 0) {
271 
272  result = noInpolOp(result);
273 
274  }
275  return result;
276 }
277 
278 QList<QPointF> StrokeManager::noInpolOp(QList<QPointF> points)
279 {
280  setPressure(getPressure());
281 
282  points << mLastPixel << mLastPixel << mCurrentPixel << mCurrentPixel;
283 
284  // Set lastPixel to CurrentPixel
285  // new interpolated pixel
286  mLastPixel = mCurrentPixel;
287 
288  return points;
289 }
290 
291 QList<QPointF> StrokeManager::tangentInpolOp(QList<QPointF> points)
292 {
293  int time = mSingleshotTime.elapsed();
294  static const qreal smoothness = 1.f;
295  QLineF line( mLastPixel, mCurrentPixel);
296 
297  qreal scaleFactor = line.length() * 3.f;
298 
299  if ( !hasTangent && scaleFactor > 0.01f)
300  {
301  hasTangent = true;
302  /*
303  qDebug() << "scaleFactor" << scaleFactor
304  << "current pixel " << mCurrentPixel
305  << "last pixel" << mLastPixel;
306  */
307  m_previousTangent = (mCurrentPixel - mLastPixel) * smoothness / (3.0 * scaleFactor);
308  //qDebug() << "previous tangent" << m_previousTangent;
309  QLineF _line(QPointF(0,0), m_previousTangent);
310  // don't bother for small tangents, as they can induce single pixel wobbliness
311  if (_line.length() < 2)
312  {
313  m_previousTangent = QPointF(0,0);
314  }
315  }
316  else
317  {
318  QPointF c1 = mLastPixel + m_previousTangent * scaleFactor;
319  QPointF newTangent = (mCurrentPixel - c1) * smoothness / (3.0 * scaleFactor);
320  //qDebug() << "scalefactor1=" << scaleFactor << m_previousTangent << newTangent;
321  if (scaleFactor == 0)
322  {
323  newTangent = QPointF(0,0);
324  }
325  else
326  {
327  //QLineF _line(QPointF(0,0), newTangent);
328  //if (_line.length() < 2)
329  //{
330  // newTangent = QPointF(0,0);
331  //}
332  }
333  QPointF c2 = mCurrentPixel - newTangent * scaleFactor;
334  //c1 = mLastPixel;
335  //c2 = mCurrentPixel;
336  points << mLastPixel << c1 << c2 << mCurrentPixel;
337  /*
338  qDebug() << mLastPixel
339  << c1
340  << c2
341  << mCurrentPixel;
342  */
343  m_previousTangent = newTangent;
344  }
345 
346  previousTime = time;
347  return points;
348 
349 }
350 
351 // Mean sampling interpolation operation
352 QList<QPointF> StrokeManager::meanInpolOp(QList<QPointF> points, qreal x, qreal y, qreal pressure)
353 {
354  for (int i = 0; i < strokeQueue.size(); i++) {
355  x += strokeQueue[i].x();
356  y += strokeQueue[i].y();
357  pressure += getPressure();
358  }
359 
360  // get arichmic mean of x, y and pressure
361  x /= strokeQueue.size();
362  y /= strokeQueue.size();
363  pressure /= strokeQueue.size();
364 
365  // Use our interpolated points
366  QPointF mNewInterpolated = mLastInterpolated;
367  mNewInterpolated = QPointF(x,y);
368 
369  points << mLastPixel << mLastInterpolated << mNewInterpolated << mCurrentPixel;
370 
371  // Set lastPixel non interpolated pixel to our
372  // new interpolated pixel
373  mLastPixel = mNewInterpolated;
374 
375  return points;
376 }
377 
378 void StrokeManager::interpolateEnd()
379 {
380  // Stop timer
381  timer.stop();
382  if (mInpolLevel == 2) {
383  if (!strokeQueue.isEmpty())
384  {
385 
386  // How many samples should we get point from?
387  // TODO: Qt slider.
388  int sampleSize = 5;
389 
390  assert(sampleSize > 0);
391  for (int i = sampleSize; i > 0; i--)
392  {
393  interpolatePoll();
394  interpolateStroke();
395  }
396  } else {
397  // Do nothing
398  }
399  }
400 }