All Classes Namespaces Functions Variables Enumerations Properties Pages
beziercurve.cpp
1 /*
2 
3 Pencil2D - Traditional Animation Software
4 Copyright (C) 2005-2007 Patrick Corrieri & Pascal Naidon
5 Copyright (C) 2012-2020 Matthew Chiawen Chang
6 
7 This program is free software; you can redistribute it and/or
8 modify it under the terms of the GNU General Public License
9 as published by the Free Software Foundation; version 2 of the License.
10 
11 This program is distributed in the hope that it will be useful,
12 but WITHOUT ANY WARRANTY; without even the implied warranty of
13 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 GNU General Public License for more details.
15 
16 */
17 
18 #include "beziercurve.h"
19 
20 #include <cmath>
21 #include <QList>
22 #include <QXmlStreamWriter>
23 #include <QDomElement>
24 #include <QDebug>
25 #include <QPainterPath>
26 #include "object.h"
27 #include "pencilerror.h"
28 
29 
30 BezierCurve::BezierCurve()
31 {
32 }
33 
34 BezierCurve::BezierCurve(const QList<QPointF>& pointList, bool smooth)
35 {
36  QList<qreal> pressureList;
37  for (int i = 0; i < pointList.size(); i++)
38  {
39  pressureList << 0.5; // default pressure
40  }
41  createCurve(pointList, pressureList, smooth);
42 }
43 
44 BezierCurve::BezierCurve(const QList<QPointF>& pointList, const QList<qreal>& pressureList, double tol, bool smooth)
45 {
46  // FIXME: crashes if n == 0
47  int n = pointList.size();
48 
49  Q_ASSERT(n > 0);
50 
51  // Simplify path
52  QList<bool> markList;
53  for (int i=0; i<n; i++) { markList.append(false); }
54  markList.replace(0, true);
55  markList.replace(n-1, true);
56  BezierCurve::simplify(tol, pointList, 0, n-1, markList);
57 
58  QList<QPointF> simplifiedPointList;
59  QList<qreal> simplifiedPressureList;
60  for(int i=0; i<n; i++)
61  {
62  if (markList.at(i) == true)
63  {
64  simplifiedPointList.append(pointList.at(i));
65  if (pressureList.size() > i)
66  {
67  // Make sure that the stroke point always has a pressure (and a width)
68  //
69  qreal currentPressure = pressureList.at(i);
70  if (currentPressure < 0.1) {
71  currentPressure = 0.1;
72  }
73  simplifiedPressureList.append(currentPressure);
74  }
75  else
76  {
77  simplifiedPressureList.append(0.5); // default pressure
78  }
79  }
80  }
81 
82  // Create curve from the simplified path
83  createCurve(simplifiedPointList, simplifiedPressureList, smooth);
84 }
85 
86 
87 Status BezierCurve::createDomElement( QXmlStreamWriter& xmlStream )
88 {
89  xmlStream.writeStartElement( "curve" );
90  xmlStream.writeAttribute( "width", QString::number( width ) );
91  xmlStream.writeAttribute( "variableWidth", variableWidth ? "true" : "false" );
92  if (feather>0) xmlStream.writeAttribute( "feather", QString::number( feather ) );
93  xmlStream.writeAttribute( "invisible", invisible ? "true" : "false" );
94  xmlStream.writeAttribute( "filled", mFilled ? "true" : "false" );
95  xmlStream.writeAttribute( "colourNumber", QString::number( colorNumber ) );
96  xmlStream.writeAttribute( "originX", QString::number( origin.x() ) );
97  xmlStream.writeAttribute( "originY", QString::number( origin.y() ) );
98  xmlStream.writeAttribute( "originPressure", QString::number( pressure.at(0) ) );
99 
100  int errorLocation = -1;
101  for ( int i = 0; i < c1.size() ; i++ )
102  {
103  xmlStream.writeEmptyElement( "segment" );
104  xmlStream.writeAttribute( "c1x", QString::number( c1.at( i ).x() ) );
105  xmlStream.writeAttribute( "c1y", QString::number( c1.at( i ).y() ) );
106  xmlStream.writeAttribute( "c2x", QString::number( c2.at( i ).x() ) );
107  xmlStream.writeAttribute( "c2y", QString::number( c2.at( i ).y() ) );
108  xmlStream.writeAttribute( "vx", QString::number( vertex.at( i ).x() ) );
109  xmlStream.writeAttribute( "vy", QString::number( vertex.at( i ).y() ) );
110  xmlStream.writeAttribute( "pressure", QString::number( pressure.at( i + 1 ) ) );
111  if ( errorLocation < 0 && xmlStream.hasError() )
112  {
113  errorLocation = i;
114  }
115  }
116 
117  xmlStream.writeEndElement(); // Close curve element
118 
119  if ( xmlStream.hasError() && errorLocation >= 0 )
120  {
121  DebugDetails debugInfo;
122  debugInfo << "BezierCurve::createDomElement";
123  debugInfo << QString("width = %1").arg(width);
124  debugInfo << QString("variableWidth = %1").arg("variableWidth");
125  debugInfo << QString("feather = %1").arg(feather);
126  debugInfo << QString("invisible = %1").arg(invisible);
127  debugInfo << QString("filled = %1").arg(mFilled);
128  debugInfo << QString("colorNumber = %1").arg(colorNumber);
129  debugInfo << QString("originX = %1").arg(origin.x());
130  debugInfo << QString("originY = %1").arg(origin.y());
131  debugInfo << QString("originPressure = %1").arg(pressure.at(0));
132  debugInfo << QString("- segmentTag[%1] has failed to write").arg(errorLocation);
133  debugInfo << QString("&nbsp;&nbsp;c1x = %1").arg(c1.at(errorLocation).x());
134  debugInfo << QString("&nbsp;&nbsp;c1y = %1").arg(c1.at(errorLocation).y());
135  debugInfo << QString("&nbsp;&nbsp;c2x = %1").arg(c2.at(errorLocation).x());
136  debugInfo << QString("&nbsp;&nbsp;c2y = %1").arg(c2.at(errorLocation).y());
137  debugInfo << QString("&nbsp;&nbsp;vx = %1").arg(vertex.at(errorLocation).x());
138  debugInfo << QString("&nbsp;&nbsp;vy = %1").arg(vertex.at(errorLocation).y());
139  debugInfo << QString("&nbsp;&nbsp;pressure = %1").arg(pressure.at(errorLocation + 1));
140 
141  return Status(Status::FAIL, debugInfo);
142  }
143 
144  return Status::OK;
145 }
146 
147 void BezierCurve::loadDomElement(const QDomElement& element)
148 {
149  width = element.attribute("width").toDouble();
150  variableWidth = (element.attribute("variableWidth") == "1") || (element.attribute("variableWidth") == "true");
151  feather = element.attribute("feather").toDouble();
152  invisible = (element.attribute("invisible") == "1") || (element.attribute("invisible") == "true");
153  mFilled = (element.attribute("filled") == "1") || (element.attribute("filled") == "true");
154  if (width == 0) invisible = true;
155 
156  colorNumber = element.attribute("colourNumber").toInt();
157  origin = QPointF( element.attribute("originX").toFloat(), element.attribute("originY").toFloat() );
158  pressure.append( element.attribute("originPressure").toFloat() );
159  selected.append(false);
160 
161  QDomNode segmentTag = element.firstChild();
162  while (!segmentTag.isNull())
163  {
164  QDomElement segmentElement = segmentTag.toElement();
165  if (!segmentElement.isNull())
166  {
167  if (segmentElement.tagName() == "segment")
168  {
169  QPointF c1Point = QPointF(segmentElement.attribute("c1x").toFloat(), segmentElement.attribute("c1y").toFloat());
170  QPointF c2Point = QPointF(segmentElement.attribute("c2x").toFloat(), segmentElement.attribute("c2y").toFloat());
171  QPointF vertexPoint = QPointF(segmentElement.attribute("vx").toFloat(), segmentElement.attribute("vy").toFloat());
172  qreal pressureValue = segmentElement.attribute("pressure").toFloat();
173  appendCubic(c1Point, c2Point, vertexPoint, pressureValue);
174  }
175  }
176  segmentTag = segmentTag.nextSibling();
177  }
178 }
179 
180 
181 void BezierCurve::setOrigin(const QPointF& point)
182 {
183  origin = point;
184 }
185 
186 void BezierCurve::setOrigin(const QPointF& point, const qreal& pressureValue, const bool& trueOrFalse)
187 {
188  origin = point;
189  pressure[0] = pressureValue;
190  selected[0] = trueOrFalse;
191 }
192 
193 void BezierCurve::setC1(int i, const QPointF& point)
194 {
195  if ( i >= 0 || i < c1.size() )
196  {
197  c1[i] = point;
198  }
199  else
200  {
201  qDebug() << "BezierCurve::setC1! index out of bounds:" << i;
202  }
203 }
204 
205 void BezierCurve::setC2(int i, const QPointF& point)
206 {
207  if ( i >= 0 || i < c2.size() )
208  {
209  c2[i] = point;
210  }
211  else
212  {
213  qDebug() << "BezierCurve::setC2! index out of bounds:" << i;
214  }
215 }
216 
217 void BezierCurve::setVertex(int i, const QPointF& point)
218 {
219  if (i == -1)
220  {
221  origin = point;
222  }
223  else if (i >= 0 && i < vertex.size())
224  {
225  vertex[i] = point;
226  }
227  else
228  {
229  qDebug() << "BezierCurve::setVertex! index out of bounds:" << i;
230  }
231 }
232 
233 void BezierCurve::setLastVertex(const QPointF& point)
234 {
235  if (vertex.size() > 0)
236  {
237  vertex[vertex.size()-1] = point;
238  }
239  else
240  {
241  qDebug() << "BezierCurve::setLastVertex! curve has less than 2 vertices";
242  }
243 }
244 
245 
246 void BezierCurve::setWidth(qreal desiredWidth)
247 {
248  width = desiredWidth;
249 }
250 
251 void BezierCurve::setFeather(qreal desiredFeather)
252 {
253  feather = desiredFeather;
254 }
255 
256 void BezierCurve::setVariableWidth(bool YesOrNo)
257 {
258  variableWidth = YesOrNo;
259 }
260 
261 void BezierCurve::setInvisibility(bool YesOrNo)
262 {
263  invisible = YesOrNo;
264 }
265 
266 void BezierCurve::setSelected(int i, bool YesOrNo)
267 {
268  selected[i+1] = YesOrNo;
269 }
270 
277 void BezierCurve::setFilled(bool YesOrNo)
278 {
279  mFilled = YesOrNo;
280 }
281 
282 BezierCurve BezierCurve::transformed(QTransform transformation)
283 {
284  BezierCurve transformedCurve = *this; // copy the curve
285  if (isSelected(-1)) { transformedCurve.setOrigin(transformation.map(origin)); }
286  for(int i=0; i< vertex.size(); i++)
287  {
288  if (isSelected(i-1)) { transformedCurve.setC1(i, transformation.map(c1.at(i))); }
289  if (isSelected(i))
290  {
291  transformedCurve.setC2(i, transformation.map(c2.at(i)));
292  transformedCurve.setVertex(i, transformation.map(vertex.at(i)));
293  }
294  }
295  //transformedCurve.smoothCurve();
296  /*QPointF newOrigin = origin;
297  if (isSelected(-1)) { newOrigin = transformation.map(newOrigin); }
298  transformedCurve.setOrigin( newOrigin );
299  for(int i=0; i< vertex.size(); i++) {
300  QPointF newC1 = c1.at(i);
301  QPointF newC2 = c2.at(i);
302  QPointF newVertex = vertex.at(i);
303  if (isSelected(i-1)) { newC1 = transformation.map(newC1); }
304  if (isSelected(i)) { newC2 = transformation.map(newC2); newVertex = transformation.map(newVertex); }
305  transformedCurve.appendCubic( newC1, newC2, newVertex, pressure.at(i) );
306  if (isSelected(i)) { transformedCurve.setSelected(i, true); }
307  }
308  transformedCurve.setWidth( width);
309  transformedCurve.setVariableWidth( variableWidth );
310  //transformedCurve.setSelected(true); // or select only the selected elements of the orginal curve?
311  */
312  return transformedCurve;
313 }
314 
315 void BezierCurve::transform(QTransform transformation)
316 {
317  if (isSelected(-1)) setOrigin( transformation.map(origin) );
318  for(int i=0; i< vertex.size(); i++)
319  {
320  if (isSelected(i-1)) c1[i] = transformation.map(c1.at(i));
321  if (isSelected(i))
322  {
323  c2[i] = transformation.map(c2.at(i));
324  vertex[i] = transformation.map(vertex.at(i));
325  }
326  }
327  //smoothCurve();
328 }
329 
330 void BezierCurve::appendCubic(const QPointF& c1Point, const QPointF& c2Point, const QPointF& vertexPoint, qreal pressureValue)
331 {
332  c1.append(c1Point);
333  c2.append(c2Point);
334  vertex.append(vertexPoint);
335  pressure.append(pressureValue);
336  selected.append(false);
337 }
338 
339 void BezierCurve::addPoint(int position, const QPointF point)
340 {
341  if ( position > -1 && position < getVertexSize() )
342  {
343  QPointF v1 = getVertex(position-1);
344  QPointF v2 = getVertex(position);
345  QPointF c1o = getC1(position);
346  QPointF c2o = getC2(position);
347 
348  c1[position] = point + 0.2*(v2-v1);
349  c2[position] = v2 + (c2o-v2)*(0.5);
350 
351  c1.insert(position, v1 + (c1o-v1)*(0.5) );
352  c2.insert(position, point - 0.2*(v2-v1));
353  vertex.insert(position, point);
354  pressure.insert(position, getPressure(position));
355  selected.insert(position, isSelected(position) && isSelected(position-1));
356 
357  //smoothCurve();
358  }
359  else
360  {
361  qDebug() << "Error BezierCurve::addPoint(int, QPointF)";
362  }
363 }
364 
365 void BezierCurve::addPoint(int position, const qreal fraction) // fraction is where to split the bezier curve (ex: fraction=0.5)
366 {
367  // de Casteljau's method is used
368  // http://en.wikipedia.org/wiki/De_Casteljau%27s_algorithm
369  // http://www.damtp.cam.ac.uk/user/na/PartIII/cagd2002/halve.ps
370  if ( position > -1 && position < getVertexSize() )
371  {
372  QPointF vA = getVertex(position-1);
373  QPointF vB = getVertex(position);
374  QPointF c1o = getC1(position);
375  QPointF c2o = getC2(position);
376  QPointF c12 = (1-fraction)*c1o + fraction*c2o;
377  QPointF cA1 = (1-fraction)*vA + fraction*c1o;
378  QPointF cB2 = (1-fraction)*c2o + fraction*vB;
379  QPointF cA2 = (1-fraction)*cA1 + fraction*c12;
380  QPointF cB1 = (1-fraction)*c12 + fraction*cB2;
381  QPointF vM = (1-fraction)*cA2 + fraction*cB1;
382 
383  setC1(position, cB1);
384  setC2(position, cB2);
385 
386  c1.insert(position, cA1);
387  c2.insert(position, cA2);
388  vertex.insert(position, vM);
389  pressure.insert(position, getPressure(position));
390  selected.insert(position, isSelected(position) && isSelected(position-1));
391 
392  //smoothCurve();
393  }
394  else
395  {
396  qDebug() << "Error BezierCurve::addPoint(int, qreal)";
397  }
398 }
399 
400 void BezierCurve::removeVertex(int i)
401 {
402  int n = vertex.size();
403  if (i>-2 && i< n)
404  {
405  if (i== -1)
406  {
407  origin = vertex.at(0);
408  vertex.removeAt(0);
409  c1.removeAt(0);
410  c2.removeAt(0);
411  pressure.removeAt(0);
412  selected.removeAt(0);
413  }
414  else
415  {
416  vertex.removeAt(i);
417  c2.removeAt(i);
418  pressure.removeAt(i+1);
419  selected.removeAt(i+1);
420  if ( i != n-1 )
421  {
422  c1.removeAt(i+1);
423  }
424  else
425  {
426  c1.removeAt(i);
427  }
428  }
429  }
430 }
431 
432 void BezierCurve::drawPath(QPainter& painter, Object* object, QTransform transformation, bool simplified, bool showThinLines )
433 {
434  QColor color = object->getColor(colorNumber).color;
435 
436  BezierCurve myCurve;
437  if (isPartlySelected()) { myCurve = (transformed(transformation)); }
438  else { myCurve = *this; }
439 
440  if ( variableWidth && !simplified && !invisible)
441  {
442  painter.setPen(QPen(QBrush(color), 1, Qt::NoPen, Qt::RoundCap,Qt::RoundJoin));
443  painter.setBrush(color);
444  painter.drawPath(myCurve.getStrokedPath());
445  }
446  else
447  {
448  qreal renderedWidth = width;
449  if (simplified)
450  {
451  renderedWidth = 1.0/painter.worldTransform().m11();
452 
453  // Make sure the line width is positive.
454  renderedWidth = fabs(renderedWidth);
455  }
456  painter.setBrush(Qt::NoBrush);
457  if ( invisible )
458  {
459  if (showThinLines)
460  {
461  if (simplified)
462  {
463  // Set simplified lines to black for the fill function to define contours.
464  painter.setPen(QPen(QBrush(Qt::black), renderedWidth, Qt::SolidLine, Qt::RoundCap,Qt::RoundJoin));
465  }
466  else
467  {
468  painter.setPen(QPen(QBrush(color), 0, Qt::DotLine, Qt::RoundCap,Qt::RoundJoin));
469  }
470  }
471  else
472  {
473  painter.setPen(Qt::NoPen);
474  }
475  }
476  else
477  {
478  painter.setPen( QPen( QBrush( color ), renderedWidth, Qt::SolidLine, Qt::RoundCap, Qt::RoundJoin ) );
479  //painter.setPen( QPen( Qt::darkYellow , 5, Qt::SolidLine, Qt::RoundCap, Qt::RoundJoin ) );
480  }
481  QPainterPath path = myCurve.getSimplePath();
482  painter.drawPath( path );
483  }
484 
485  if (!simplified)
486  {
487  // highlight the selected elements
488  color = QColor(100,150,255); // highlight color
489  painter.setBrush(Qt::NoBrush);
490  qreal lineWidth = 1.5/painter.worldTransform().m11();
491  lineWidth = fabs(lineWidth); // make sure line width is positive, otherwise nothing is drawn
492  painter.setPen(QPen(QBrush(color), lineWidth, Qt::SolidLine, Qt::RoundCap,Qt::RoundJoin));
493  if (isSelected()) painter.drawPath(myCurve.getSimplePath());
494 
495 
496  for(int i=-1; i< vertex.size(); i++)
497  {
498  if (isSelected(i))
499  {
500 // painter.fillRect(myCurve.getVertex(i).x()-0.5*squareWidth, myCurve.getVertex(i).y()-0.5*squareWidth, squareWidth, squareWidth, color);
501 
502  //painter.fillRect(QRectF(myCurve.getVertex(i).x()-0.5*squareWidth, myCurve.getVertex(i).y()-0.5*squareWidth, squareWidth, squareWidth), color);
503 
504  /*painter.drawText(myCurve.getVertex(i)+QPointF(4.0,0.0), QString::number(i)+"-"+QString::number(myCurve.getVertex(i).x())+","+QString::number(myCurve.getVertex(i).y()));
505  QPointF normale = QPointF(4.0, 0.0);
506  if (i>-1) { normale = (myCurve.getVertex(i)-myCurve.getC2(i)); } else { normale = (myCurve.getC1(i+1)-myCurve.getVertex(i)); }
507  normale = QPointF(-normale.y(), normale.x());
508  normale = 8.0*normale/eLength(normale)/painter.matrix().m11();
509  painter.drawLine(myCurve.getVertex(i), myCurve.getVertex(i)+normale);
510  painter.drawText(myCurve.getVertex(i)+2*normale, QString::number(i));*/
511  }
512  }
513  }
514 }
515 
516 // Without curve fitting
517 QPainterPath BezierCurve::getStraightPath()
518 {
519  QPainterPath path;
520  path.moveTo(origin);
521  for(int i=0; i<vertex.size(); i++)
522  {
523  path.lineTo(vertex[i]);
524  }
525  return path;
526 }
527 
528 // With bezier curve fitting
529 QPainterPath BezierCurve::getSimplePath()
530 {
531  QPainterPath path;
532  path.moveTo(origin);
533  for(int i=0; i<vertex.size(); i++)
534  {
535  path.cubicTo(c1.at(i), c2.at(i), vertex.at(i));
536  }
537  return path;
538 }
539 
540 QPainterPath BezierCurve::getStrokedPath()
541 {
542  return getStrokedPath( width );
543 }
544 
545 QPainterPath BezierCurve::getStrokedPath(qreal width)
546 {
547  return getStrokedPath(width, true);
548 }
549 
550 // this function is a mess and outputs buggy results randomly...
551 QPainterPath BezierCurve::getStrokedPath(qreal width, bool usePressure)
552 {
553  QPainterPath path;
554  QPointF tangentVec, normalVec, normalVec2, normalVec2_1, normalVec2_2;
555  qreal width2 = width;
556  int n = vertex.size();
558 
559  normalVec = QPointF(-(c1.at(0) - origin).y(), (c1.at(0) - origin).x());
560  normalise(normalVec);
561  if (usePressure) width2 = width * 0.5 * pressure.at(0);
562  if (n==1 && width2 == 0.0) width2 = 0.15 * width;
563  path.moveTo(origin + width2*normalVec);
564  for(int i=0; i<n; i++)
565  {
566  if (i==n-1)
567  {
568  normalVec2 = QPointF(-(vertex.at(i) - c2.at(i)).y(), (vertex.at(i) - c2.at(i)).x());
569  }
570  else
571  {
572  normalVec2_1 = QPointF(-(vertex.at(i) - c2.at(i)).y(), (vertex.at(i) - c2.at(i)).x());
573  normalise(normalVec2_1);
574  normalVec2_2 = QPointF(-(c1.at(i+1) - vertex.at(i)).y(), (c1.at(i+1) - vertex.at(i)).x());
575  normalise(normalVec2_2);
576  normalVec2 = normalVec2_1 + normalVec2_2;
577  }
578  normalise(normalVec2);
579  if (usePressure) width2 = width * 0.5 * pressure.at(i);
580  if (n==1 && width2 == 0.0) width2 = 0.15 * width;
581  //if (i==n-1) width2 = 0.0;
582  path.cubicTo(c1.at(i) + width2*normalVec, c2.at(i) + width2*normalVec2, vertex.at(i) + width2*normalVec2);
583  //path.moveTo(vertex.at(i) + width*normalVec2);
584  //path.lineTo(vertex.at(i) - width*normalVec2);
585  normalVec = normalVec2;
586  }
587  if (usePressure) width2 = width * 0.5 * pressure.at(n-1);
588  if (n==1 && width2 == 0.0) width2 = 0.15 * width;
589 
590  //path.lineTo(vertex.at(n-1) - width2*normalVec);
591  tangentVec = (vertex.at(n-1)-c2.at(n-1));
592  normalise(tangentVec);
593  path.cubicTo(vertex.at(n-1) + width2*(normalVec+1.8*tangentVec), vertex.at(n-1) + width2*(-normalVec+1.8*tangentVec), vertex.at(n-1) - width2*normalVec);
594 
595  for(int i=n-2; i>=0; i--)
596  {
597  normalVec2_1 = QPointF((vertex.at(i) - c1.at(i+1)).y(), -(vertex.at(i) - c1.at(i+1)).x());
598  normalise(normalVec2_1);
599  normalVec2_2 = QPointF((c2.at(i) - vertex.at(i)).y(), -(c2.at(i) - vertex.at(i)).x());
600  normalise(normalVec2_2);
601  normalVec2 = normalVec2_1 + normalVec2_2;
602  normalise(normalVec2);
603  if (usePressure) width2 = width * 0.5 * pressure.at(i);
604  if (n==1 && width2 == 0.0) width2 = 0.15 * width;
605  path.cubicTo(c2.at(i+1) - width2*normalVec, c1.at(i+1) - width2*normalVec2, vertex.at(i) - width2*normalVec2);
606  normalVec = normalVec2;
607  }
608  normalVec2 = QPointF((origin - c1.at(0)).y(), -(origin - c1.at(0)).x());
609  normalise(normalVec2);
610  if (usePressure) width2 = width * 0.5 * pressure.at(0);
611  if (n==1 && width2 == 0.0) width2 = 0.15 * width;
612  path.cubicTo(c2.at(0) - width2*normalVec, c1.at(0) - width2*normalVec2, origin - width2*normalVec2);
613 
614  tangentVec = (origin-c1.at(0));
615  normalise(tangentVec);
616  path.cubicTo(origin + width2*(-normalVec+1.8*tangentVec), origin + width2*(normalVec+1.8*tangentVec), origin + width2*normalVec);
617 
618  path.closeSubpath();
619  return path;
620 }
621 
622 QRectF BezierCurve::getBoundingRect()
623 {
624  return getSimplePath().boundingRect();
625 }
626 
627 void BezierCurve::createCurve(const QList<QPointF>& pointList, const QList<qreal>& pressureList, bool smooth)
628 {
629  int p = 0;
630  int n = pointList.size();
631  // generate the Bezier (cubic) curve from the simplified path and mouse pressure
632  // first, empty everything
633  while (c1.size()>0) c1.removeAt(0);
634  while (c2.size()>0) c2.removeAt(0);
635  while (vertex.size()>0) vertex.removeAt(0);
636  while (selected.size()>0) selected.removeAt(0);
637  while (pressure.size()>0) pressure.removeAt(0);
638 
639  setOrigin( pointList.at(0) );
640  selected.append(false);
641  pressure.append(pressureList.at(0));
642 
643  for (p=1; p<n; p++)
644  {
645  c1.append(pointList.at(p));
646  c2.append(pointList.at(p));
647  vertex.append(pointList.at(p));
648  pressure.append(pressureList.at(p));
649  selected.append(false);
650 
651  }
652  if (smooth)
653  {
654  smoothCurve();
655  }
656  //colorNumber = 0;
657  feather = 0;
658 }
659 
660 
661 void BezierCurve::smoothCurve()
662 {
663  QPointF c1, c2, c2old, tangentVec, normalVec;
664  int n = vertex.size();
665  c2old = QPointF(-100,-100); // bogus point
666  for(int p=0; p<n-1; p++)
667  {
668  QPointF D = getVertex(p);
669  QPointF Dprev = getVertex(p-1);
670  QPointF Dnext = getVertex(p+1);
671  qreal L1 = mLength(D-Dprev);
672  qreal L2 = mLength(D-Dnext);
673 
674  tangentVec = 0.4*(Dnext - Dprev);
675  normalVec = QPointF(-tangentVec.y(), tangentVec.x())/eLength(tangentVec);
676  if ( ((D-Dprev).x()*(D-Dnext).x()+(D-Dprev).y()*(D-Dnext).y())/(1.0*L1*L2) < 0 )
677  {
678  // smooth point
679  c1 = D - tangentVec*(L1+0.0)/(L1+L2);
680  c2 = D + tangentVec*(L2+0.0)/(L1+L2);
681  }
682  else
683  {
684  // sharp point
685  c1 = 0.6*D + 0.4*Dprev;
686  c2 = 0.6*D + 0.4*Dnext;
687  }
688 
689  if (p==0)
690  {
691  c2old = 0.5*(vertex.at(0)+c1);
692  }
693 
694  this->c1[p] = c2old;
695  this->c2[p] = c1;
696  //appendCubic(c2old, c1, D, pressureList->at(p));
697  c2old = c2;
698  }
699  if (n>2)
700  {
701  this->c1[n-1] = c2old;
702  this->c2[n-1] = 0.5*(c2old+vertex.at(n-1));
703  }
704 }
705 
706 void BezierCurve::simplify(double tol, const QList<QPointF>& inputList, int j, int k, QList<bool>& markList)
707 {
708  // -- Douglas-Peucker simplification algorithm
709  // from http://geometryalgorithms.com/Archive/algorithm_0205/
710  if (k <= j + 1) //there is nothing to simplify
711  {
712  // return immediately
713  }
714  else
715  {
716  // test distance of intermediate vertices from segment Vj to Vk
717  double maxd = 0.0; //is the distance of farthest vertex from segment jk
718  int maxi = j; //is the index of the vertex farthest from segement jk
719  for (int i = j + 1; i < k - 1; i++) // each intermediate vertex Vi
720  {
721  QPointF Vij = inputList.at(i) - inputList.at(j);
722  QPointF Vjk = inputList.at(j) - inputList.at(k);
723  double Vijx = Vij.x();
724  double Vijy = Vij.y();
725  double Vjkx = Vjk.x();
726  double Vjky = Vjk.y();
727  double dv = (Vjkx * Vjkx + Vjky * Vjky);
728  if ( dv != 0.0)
729  {
730  dv = sqrt( Vijx*Vijx+Vijy*Vijy - pow(Vijx*Vjkx+Vijy*Vjky,2)/dv );
731  }
732  //qDebug() << "distance = "+QString::number(dv);
733  if (dv < maxd)
734  {
735  //Vi is not farther away, so continue to the next vertex
736  }
737  else //Vi is a new max vertex
738  {
739  maxd = dv;
740  maxi = i; //to remember the farthest vertex
741  }
742  }
743  if (maxd >= tol) //a vertex is farther than tol from Sjk
744  {
745  // split the polyline at the farthest vertex
746  //Mark Vmaxi as part of the simplified polyline
747  markList.replace(maxi, true);
748  //and recursively simplify the two subpolylines
749  simplify(tol, inputList, j, maxi, markList);
750  simplify(tol, inputList, maxi, k, markList);
751  }
752  }
753 }
754 
755 // general useful functions -> to be placed elsewhere
756 qreal BezierCurve::eLength(const QPointF point) // calculates the Euclidean Length (of a point seen as a vector)
757 {
758  qreal result = sqrt( point.x()*point.x() + point.y()*point.y() ); // could also use QLine.length()... is it any faster?
759  //if (result == 0.0) result = 1.0;
760  return result;
761 }
762 
763 qreal BezierCurve::mLength(const QPointF point) // calculates the Manhattan Length (of a point seen as a vector)
764 {
765  qreal result = qAbs(point.x()) + qAbs(point.y());
766  if (result == 0.0) result = 1.0;
767  return result;
768 }
769 
770 void BezierCurve::normalise(QPointF& point)
771 {
772  qreal length = eLength(point);
773  if (length > 1.0e-6)
774  {
775  point = point/length;
776  }
777 }
778 
779 qreal BezierCurve::findDistance(BezierCurve curve, int i, QPointF P, QPointF& nearestPoint, qreal& t) //finds the distance between a cubic section and a point
780 {
781  //qDebug() << "---- INTER CUBIC SEGMENT";
782  int nSteps = 24;
783  QPointF Q;
784  Q = curve.getVertex(i-1);
785  qreal distMin = eLength(Q-P);
786  nearestPoint = Q;
787  t = 0;
788  for(int k=1; k<=nSteps; k++)
789  {
790  qreal s = (k+0.0)/nSteps;
791  Q = curve.getPointOnCubic(i, s);
792  qreal dist = eLength(Q-P);
793  if (dist <= distMin)
794  {
795  distMin = dist;
796  nearestPoint = Q;
797  t = s;
798  }
799  }
800  //QPointF Q1 = curve.getPointOnCubic(i, t);
801  return distMin;
802 }
803 
804 QPointF BezierCurve::getPointOnCubic(int i, qreal t)
805 {
806  return (1.0-t)*(1.0-t)*(1.0-t)*getVertex(i-1)
807  + 3*t*(1.0-t)*(1.0-t)*getC1(i)
808  + 3*t*t*(1.0-t)*getC2(i)
809  + t*t*t*getVertex(i);
810 }
811 
812 
813 bool BezierCurve::intersects(QPointF point, qreal distance)
814 {
815  bool result = false;
816  if ( getStrokedPath(distance, false).contains(point) )
817  {
818  //if ( getSimplePath().controlPointRect().contains(point)) {
819  result = true;
820  }
821  return result;
822 }
823 
824 bool BezierCurve::intersects(QRectF rectangle)
825 {
826  bool result = false;
827  if ( getSimplePath().controlPointRect().intersects(rectangle))
828  {
829  for(int i=0; i<vertex.size(); i++)
830  {
831  if ( rectangle.contains( getVertex(i) ) ) return true;
832  }
833  }
834  return result;
835 }
836 
837 bool BezierCurve::findIntersection(BezierCurve curve1, int i1, BezierCurve curve2, int i2, QList<Intersection>& intersections) //finds the intersection between two cubic sections
838 {
839  bool result = false;
840  //qDebug() << "---- INTER CUBIC CUBIC" << i1 << i2;
841  QPointF P1, Q1, P2, Q2;
842  QLineF L1, L2;
843  QRectF R1;
844  QRectF R2;
845 
846  P1 = curve1.getVertex(i1-1);
847  Q1 = curve1.getVertex(i1);
848  P2 = curve2.getVertex(i2-1);
849  Q2 = curve2.getVertex(i2);
850  L1 = QLineF(P1, Q1);
851  L2 = QLineF(P2, Q2);
852 
853  //qDebug() << "-------------------- ";
854 
855  R1.setTopLeft(P1);
856  R1.setBottomRight(Q1);
857  R2.setTopLeft(P2);
858  R2.setBottomRight(Q2);
859 
860  QPointF intersectionPoint = QPointF(50.0, 50.0); // bogus point
861  QPointF* cubicIntersection = &intersectionPoint;
862  if ( R1.intersects(R2) || L2.intersect(L1, cubicIntersection) == QLineF::BoundedIntersection )
863  {
864  //if (L2.intersect(L1, intersection) == QLineF::BoundedIntersection) {
865  //qDebug() << " FOUND rectangle intersection ";
866  //if (intersectionPoint != curve1.getVertex(i1-1) && intersectionPoint != curve1.getVertex(i1)) {
867  // qDebug() << " it's not one of the points ";
868  // find the cubic intersection
869  int nSteps = 24;
870  P1 = curve1.getVertex(i1-1);
871  for(int i=1; i<=nSteps; i++)
872  {
873  qreal s = (i+0.0)/nSteps;
874  Q1 = curve1.getPointOnCubic(i1, s);
875  P2 = curve2.getVertex(i2-1);
876  for(int j=1; j<=nSteps; j++)
877  {
878  qreal t = (j+0.0)/nSteps;
879  Q2 = curve2.getPointOnCubic(i2, t);
880  L1 = QLineF(P1, Q1);
881  L2 = QLineF(P2, Q2);
882  if (L2.intersect(L1, cubicIntersection) == QLineF::BoundedIntersection)
883  {
884  QPointF intersectionPoint = *cubicIntersection;
885  if (intersectionPoint != curve1.getVertex(i1-1) && intersectionPoint != curve1.getVertex(i1))
886  {
887  qreal fraction1 = eLength(intersectionPoint-Q1)/(0.0+eLength(Q1-P1));
888  qreal fraction2 = eLength(intersectionPoint-Q2)/(0.0+eLength(Q2-P2));
889  qreal t1 = (i - fraction1)/nSteps;
890  qreal t2 = (j - fraction2)/nSteps;
891  Intersection intersection;
892  intersection.point = intersectionPoint;
893  intersection.t1 = t1;
894  intersection.t2 = t2;
895  intersections.append( intersection );
896  result = true;
897  //qDebug() << "FOUND cubic interesection " << intersectionPoint << i << j;
898  }
899  }
900  P2 = Q2;
901  }
902  P1 = Q1;
903  }
904  }
905  else
906  {
907  //return false; // approximation to speed up the calculation
908  }
909  //qDebug() << "------";
910  return result;
911 }
QRectF boundingRect() const const
BoundedIntersection
QString attribute(const QString &name, const QString &defValue) const const
void closeSubpath()
QPoint map(const QPoint &point) const const
WindingFill
bool hasError() const const
const T & at(int i) const const
void removeAt(int i)
bool contains(const QRectF &rectangle) const const
void cubicTo(const QPointF &c1, const QPointF &c2, const QPointF &endPoint)
void moveTo(const QPointF &point)
bool intersects(const QRectF &rectangle) const const
double toDouble(bool *ok) const const
RoundCap
int size() const const
QDomNode nextSibling() const const
QDomElement toElement() const const
QString number(int n, int base)
qreal x() const const
qreal y() const const
void append(const T &value)
QLineF::IntersectType intersect(const QLineF &line, QPointF *intersectionPoint) const const
void setFillRule(Qt::FillRule fillRule)
void setPen(const QColor &color)
void lineTo(const QPointF &endPoint)
qreal m11() const const
int toInt(bool *ok, int base) const const
void setBrush(const QBrush &brush)
const QTransform & worldTransform() const const
RoundJoin
void setTopLeft(const QPointF &position)
bool isNull() const const
void writeAttribute(const QString &qualifiedName, const QString &value)
QDomNode firstChild() const const
void drawPath(const QPainterPath &path)
void insert(int i, const T &value)
float toFloat(bool *ok) const const
QString arg(qlonglong a, int fieldWidth, int base, QChar fillChar) const const
void setFilled(bool yesOrNo)
BezierCurve::setFilled.
Definition: object.h:41
QString tagName() const const
void setBottomRight(const QPointF &position)
void writeEmptyElement(const QString &qualifiedName)
void writeEndElement()
void writeStartElement(const QString &qualifiedName)
void replace(int i, const T &value)