Pencil2D Animation
Download Community News Docs Contribute
  • Overview
  • Articles
  • Code
  •  
  • Class List
  • Class Index
  • Class Hierarchy
  • Class Members
  • File List
Loading...
Searching...
No Matches
  • core_lib
  • src
  • graphics
  • vector
beziercurve.cpp
1/*
2
3Pencil2D - Traditional Animation Software
4Copyright (C) 2005-2007 Patrick Corrieri & Pascal Naidon
5Copyright (C) 2012-2020 Matthew Chiawen Chang
6
7This program is free software; you can redistribute it and/or
8modify it under the terms of the GNU General Public License
9as published by the Free Software Foundation; version 2 of the License.
10
11This program is distributed in the hope that it will be useful,
12but WITHOUT ANY WARRANTY; without even the implied warranty of
13MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14GNU 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
30BezierCurve::BezierCurve()
31{
32}
33
34BezierCurve::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
44BezierCurve::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
87Status 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
147void 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
181void BezierCurve::setOrigin(const QPointF& point)
182{
183 origin = point;
184}
185
186void 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
193void 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
205void 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
217void 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
233void 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
246void BezierCurve::setWidth(qreal desiredWidth)
247{
248 width = desiredWidth;
249}
250
251void BezierCurve::setFeather(qreal desiredFeather)
252{
253 feather = desiredFeather;
254}
255
256void BezierCurve::setVariableWidth(bool YesOrNo)
257{
258 variableWidth = YesOrNo;
259}
260
261void BezierCurve::setInvisibility(bool YesOrNo)
262{
263 invisible = YesOrNo;
264}
265
266void BezierCurve::setSelected(int i, bool YesOrNo)
267{
268 selected[i+1] = YesOrNo;
269}
270
277void BezierCurve::setFilled(bool YesOrNo)
278{
279 mFilled = YesOrNo;
280}
281
282BezierCurve BezierCurve::transformed(QTransform transformation) const
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
315void 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
330void 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
339void 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
365void 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
400void 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
432void BezierCurve::drawPath(QPainter& painter, const 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
517QPainterPath 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
529QPainterPath 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
540QPainterPath BezierCurve::getStrokedPath()
541{
542 return getStrokedPath( width );
543}
544
545QPainterPath BezierCurve::getStrokedPath(qreal width)
546{
547 return getStrokedPath(width, true);
548}
549
550// this function is a mess and outputs buggy results randomly...
551QPainterPath 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();
557 path.setFillRule(Qt::WindingFill);
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
622QRectF BezierCurve::getBoundingRect()
623{
624 qreal radius = getWidth() / 2;
625 return getSimplePath().boundingRect().adjusted(-radius, -radius, radius, radius);
626}
627
628void BezierCurve::createCurve(const QList<QPointF>& pointList, const QList<qreal>& pressureList, bool smooth)
629{
630 int p = 0;
631 int n = pointList.size();
632 // generate the Bezier (cubic) curve from the simplified path and mouse pressure
633 // first, empty everything
634 while (c1.size()>0) c1.removeAt(0);
635 while (c2.size()>0) c2.removeAt(0);
636 while (vertex.size()>0) vertex.removeAt(0);
637 while (selected.size()>0) selected.removeAt(0);
638 while (pressure.size()>0) pressure.removeAt(0);
639
640 setOrigin( pointList.at(0) );
641 selected.append(false);
642 pressure.append(pressureList.at(0));
643
644 for (p=1; p<n; p++)
645 {
646 c1.append(pointList.at(p));
647 c2.append(pointList.at(p));
648 vertex.append(pointList.at(p));
649 pressure.append(pressureList.at(p));
650 selected.append(false);
651
652 }
653 if (smooth)
654 {
655 smoothCurve();
656 }
657 //colorNumber = 0;
658 feather = 0;
659}
660
661
662void BezierCurve::smoothCurve()
663{
664 QPointF c1, c2, c2old, tangentVec, normalVec;
665 int n = vertex.size();
666 c2old = QPointF(-100,-100); // bogus point
667 for(int p=0; p<n-1; p++)
668 {
669 QPointF D = getVertex(p);
670 QPointF Dprev = getVertex(p-1);
671 QPointF Dnext = getVertex(p+1);
672 qreal L1 = mLength(D-Dprev);
673 qreal L2 = mLength(D-Dnext);
674
675 tangentVec = 0.4*(Dnext - Dprev);
676 normalVec = QPointF(-tangentVec.y(), tangentVec.x())/eLength(tangentVec);
677 if ( ((D-Dprev).x()*(D-Dnext).x()+(D-Dprev).y()*(D-Dnext).y())/(1.0*L1*L2) < 0 )
678 {
679 // smooth point
680 c1 = D - tangentVec*(L1+0.0)/(L1+L2);
681 c2 = D + tangentVec*(L2+0.0)/(L1+L2);
682 }
683 else
684 {
685 // sharp point
686 c1 = 0.6*D + 0.4*Dprev;
687 c2 = 0.6*D + 0.4*Dnext;
688 }
689
690 if (p==0)
691 {
692 c2old = 0.5*(vertex.at(0)+c1);
693 }
694
695 this->c1[p] = c2old;
696 this->c2[p] = c1;
697 //appendCubic(c2old, c1, D, pressureList->at(p));
698 c2old = c2;
699 }
700 if (n>2)
701 {
702 this->c1[n-1] = c2old;
703 this->c2[n-1] = 0.5*(c2old+vertex.at(n-1));
704 }
705}
706
707void BezierCurve::simplify(double tol, const QList<QPointF>& inputList, int j, int k, QList<bool>& markList)
708{
709 // -- Douglas-Peucker simplification algorithm
710 // from http://geometryalgorithms.com/Archive/algorithm_0205/
711 if (k <= j + 1) //there is nothing to simplify
712 {
713 // return immediately
714 }
715 else
716 {
717 // test distance of intermediate vertices from segment Vj to Vk
718 double maxd = 0.0; //is the distance of farthest vertex from segment jk
719 int maxi = j; //is the index of the vertex farthest from segement jk
720 for (int i = j + 1; i < k - 1; i++) // each intermediate vertex Vi
721 {
722 QPointF Vij = inputList.at(i) - inputList.at(j);
723 QPointF Vjk = inputList.at(j) - inputList.at(k);
724 double Vijx = Vij.x();
725 double Vijy = Vij.y();
726 double Vjkx = Vjk.x();
727 double Vjky = Vjk.y();
728 double dv = (Vjkx * Vjkx + Vjky * Vjky);
729 if ( dv != 0.0)
730 {
731 dv = sqrt( Vijx*Vijx+Vijy*Vijy - pow(Vijx*Vjkx+Vijy*Vjky,2)/dv );
732 }
733 //qDebug() << "distance = "+QString::number(dv);
734 if (dv < maxd)
735 {
736 //Vi is not farther away, so continue to the next vertex
737 }
738 else //Vi is a new max vertex
739 {
740 maxd = dv;
741 maxi = i; //to remember the farthest vertex
742 }
743 }
744 if (maxd >= tol) //a vertex is farther than tol from Sjk
745 {
746 // split the polyline at the farthest vertex
747 //Mark Vmaxi as part of the simplified polyline
748 markList.replace(maxi, true);
749 //and recursively simplify the two subpolylines
750 simplify(tol, inputList, j, maxi, markList);
751 simplify(tol, inputList, maxi, k, markList);
752 }
753 }
754}
755
756// general useful functions -> to be placed elsewhere
757qreal BezierCurve::eLength(const QPointF point) // calculates the Euclidean Length (of a point seen as a vector)
758{
759 qreal result = sqrt( point.x()*point.x() + point.y()*point.y() ); // could also use QLine.length()... is it any faster?
760 //if (result == 0.0) result = 1.0;
761 return result;
762}
763
764qreal BezierCurve::mLength(const QPointF point) // calculates the Manhattan Length (of a point seen as a vector)
765{
766 qreal result = qAbs(point.x()) + qAbs(point.y());
767 if (result == 0.0) result = 1.0;
768 return result;
769}
770
771void BezierCurve::normalise(QPointF& point)
772{
773 qreal length = eLength(point);
774 if (length > 1.0e-6)
775 {
776 point = point/length;
777 }
778}
779
780qreal BezierCurve::findDistance(BezierCurve curve, int i, QPointF P, QPointF& nearestPoint, qreal& t) //finds the distance between a cubic section and a point
781{
782 //qDebug() << "---- INTER CUBIC SEGMENT";
783 int nSteps = 24;
784 QPointF Q;
785 Q = curve.getVertex(i-1);
786 qreal distMin = eLength(Q-P);
787 nearestPoint = Q;
788 t = 0;
789 for(int k=1; k<=nSteps; k++)
790 {
791 qreal s = (k+0.0)/nSteps;
792 Q = curve.getPointOnCubic(i, s);
793 qreal dist = eLength(Q-P);
794 if (dist <= distMin)
795 {
796 distMin = dist;
797 nearestPoint = Q;
798 t = s;
799 }
800 }
801 //QPointF Q1 = curve.getPointOnCubic(i, t);
802 return distMin;
803}
804
805QPointF BezierCurve::getPointOnCubic(int i, qreal t)
806{
807 return (1.0-t)*(1.0-t)*(1.0-t)*getVertex(i-1)
808 + 3*t*(1.0-t)*(1.0-t)*getC1(i)
809 + 3*t*t*(1.0-t)*getC2(i)
810 + t*t*t*getVertex(i);
811}
812
813
814bool BezierCurve::intersects(QPointF point, qreal distance)
815{
816 bool result = false;
817 if ( getStrokedPath(distance, false).contains(point) )
818 {
819 //if ( getSimplePath().controlPointRect().contains(point)) {
820 result = true;
821 }
822 return result;
823}
824
825bool BezierCurve::intersects(QRectF rectangle)
826{
827 bool result = false;
828 if ( getSimplePath().controlPointRect().intersects(rectangle))
829 {
830 for(int i=0; i<vertex.size(); i++)
831 {
832 if ( rectangle.contains( getVertex(i) ) ) return true;
833 }
834 }
835 return result;
836}
837
838bool BezierCurve::findIntersection(BezierCurve curve1, int i1, BezierCurve curve2, int i2, QList<Intersection>& intersections) //finds the intersection between two cubic sections
839{
840 bool result = false;
841 //qDebug() << "---- INTER CUBIC CUBIC" << i1 << i2;
842 QPointF P1, Q1, P2, Q2;
843 QLineF L1, L2;
844 QRectF R1;
845 QRectF R2;
846
847 P1 = curve1.getVertex(i1-1);
848 Q1 = curve1.getVertex(i1);
849 P2 = curve2.getVertex(i2-1);
850 Q2 = curve2.getVertex(i2);
851 L1 = QLineF(P1, Q1);
852 L2 = QLineF(P2, Q2);
853
854 //qDebug() << "-------------------- ";
855
856 R1.setTopLeft(P1);
857 R1.setBottomRight(Q1);
858 R2.setTopLeft(P2);
859 R2.setBottomRight(Q2);
860
861 QPointF intersectionPoint = QPointF(50.0, 50.0); // bogus point
862 QPointF* cubicIntersection = &intersectionPoint;
863#if QT_VERSION >= QT_VERSION_CHECK(5, 14, 0)
864 if ( R1.intersects(R2) || L2.intersects(L1, cubicIntersection) == QLineF::BoundedIntersection )
865#else
866 if ( R1.intersects(R2) || L2.intersect(L1, cubicIntersection) == QLineF::BoundedIntersection )
867#endif
868 {
869 //if (L2.intersect(L1, intersection) == QLineF::BoundedIntersection) {
870 //qDebug() << " FOUND rectangle intersection ";
871 //if (intersectionPoint != curve1.getVertex(i1-1) && intersectionPoint != curve1.getVertex(i1)) {
872 // qDebug() << " it's not one of the points ";
873 // find the cubic intersection
874 int nSteps = 24;
875 P1 = curve1.getVertex(i1-1);
876 for(int i=1; i<=nSteps; i++)
877 {
878 qreal s = (i+0.0)/nSteps;
879 Q1 = curve1.getPointOnCubic(i1, s);
880 P2 = curve2.getVertex(i2-1);
881 for(int j=1; j<=nSteps; j++)
882 {
883 qreal t = (j+0.0)/nSteps;
884 Q2 = curve2.getPointOnCubic(i2, t);
885 L1 = QLineF(P1, Q1);
886 L2 = QLineF(P2, Q2);
887#if QT_VERSION >= QT_VERSION_CHECK(5, 14, 0)
888 if (L2.intersects(L1, cubicIntersection) == QLineF::BoundedIntersection)
889#else
890 if (L2.intersect(L1, cubicIntersection) == QLineF::BoundedIntersection)
891#endif
892 {
893 QPointF intersectionPoint = *cubicIntersection;
894 if (intersectionPoint != curve1.getVertex(i1-1) && intersectionPoint != curve1.getVertex(i1))
895 {
896 qreal fraction1 = eLength(intersectionPoint-Q1)/(0.0+eLength(Q1-P1));
897 qreal fraction2 = eLength(intersectionPoint-Q2)/(0.0+eLength(Q2-P2));
898 qreal t1 = (i - fraction1)/nSteps;
899 qreal t2 = (j - fraction2)/nSteps;
900 Intersection intersection;
901 intersection.point = intersectionPoint;
902 intersection.t1 = t1;
903 intersection.t2 = t2;
904 intersections.append( intersection );
905 result = true;
906 //qDebug() << "FOUND cubic interesection " << intersectionPoint << i << j;
907 }
908 }
909 P2 = Q2;
910 }
911 P1 = Q1;
912 }
913 }
914 else
915 {
916 //return false; // approximation to speed up the calculation
917 }
918 //qDebug() << "------";
919 return result;
920}
BezierCurve
Definition: beziercurve.h:34
BezierCurve::setFilled
void setFilled(bool yesOrNo)
BezierCurve::setFilled.
Definition: beziercurve.cpp:277
DebugDetails
Definition: pencilerror.h:25
Object
Definition: object.h:42
Status
Definition: pencilerror.h:40
QBrush
QColor
QDomElement
QDomElement::attribute
QString attribute(const QString &name, const QString &defValue) const const
QDomElement::tagName
QString tagName() const const
QDomNode
QDomNode::firstChild
QDomNode firstChild() const const
QDomNode::isNull
bool isNull() const const
QDomNode::nextSibling
QDomNode nextSibling() const const
QDomNode::toElement
QDomElement toElement() const const
QLineF::BoundedIntersection
BoundedIntersection
QLineF::intersect
QLineF::IntersectType intersect(const QLineF &line, QPointF *intersectionPoint) const const
QLineF
QLineF::intersects
QLineF::IntersectionType intersects(const QLineF &line, QPointF *intersectionPoint) const const
QList
QList::append
void append(const T &value)
QList::at
const T & at(int i) const const
QList::insert
void insert(int i, const T &value)
QList::removeAt
void removeAt(int i)
QList::replace
void replace(int i, const T &value)
QList::size
int size() const const
QPainter
QPainter::drawPath
void drawPath(const QPainterPath &path)
QPainter::setBrush
void setBrush(const QBrush &brush)
QPainter::setPen
void setPen(const QColor &color)
QPainter::worldTransform
const QTransform & worldTransform() const const
QPainterPath
QPainterPath::boundingRect
QRectF boundingRect() const const
QPainterPath::closeSubpath
void closeSubpath()
QPainterPath::cubicTo
void cubicTo(const QPointF &c1, const QPointF &c2, const QPointF &endPoint)
QPainterPath::lineTo
void lineTo(const QPointF &endPoint)
QPainterPath::moveTo
void moveTo(const QPointF &point)
QPainterPath::setFillRule
void setFillRule(Qt::FillRule fillRule)
QPen
QPointF
QPointF::x
qreal x() const const
QPointF::y
qreal y() const const
QRectF
QRectF::adjusted
QRectF adjusted(qreal dx1, qreal dy1, qreal dx2, qreal dy2) const const
QRectF::contains
bool contains(const QRectF &rectangle) const const
QRectF::intersects
bool intersects(const QRectF &rectangle) const const
QRectF::setBottomRight
void setBottomRight(const QPointF &position)
QRectF::setTopLeft
void setTopLeft(const QPointF &position)
QString
QString::arg
QString arg(qlonglong a, int fieldWidth, int base, QChar fillChar) const const
QString::number
QString number(int n, int base)
QString::toDouble
double toDouble(bool *ok) const const
QString::toFloat
float toFloat(bool *ok) const const
QString::toInt
int toInt(bool *ok, int base) const const
Qt::NoBrush
NoBrush
Qt::WindingFill
WindingFill
Qt::black
black
Qt::RoundCap
RoundCap
Qt::RoundJoin
RoundJoin
Qt::NoPen
NoPen
QTransform
QTransform::m11
qreal m11() const const
QTransform::map
QPoint map(const QPoint &point) const const
QXmlStreamWriter
QXmlStreamWriter::hasError
bool hasError() const const
QXmlStreamWriter::writeAttribute
void writeAttribute(const QString &qualifiedName, const QString &value)
QXmlStreamWriter::writeEmptyElement
void writeEmptyElement(const QString &qualifiedName)
QXmlStreamWriter::writeEndElement
void writeEndElement()
QXmlStreamWriter::writeStartElement
void writeStartElement(const QString &qualifiedName)
Intersection
Definition: beziercurve.h:27
Generated on Thu May 8 2025 04:47:53 for Pencil2D by doxygen 1.9.6 based on revision 4513250b1d5b1a3676ec0e67b06b7a885ceaae39