Krita Source Code Documentation
Loading...
Searching...
No Matches
KoPathSegment.cpp
Go to the documentation of this file.
1/* This file is part of the KDE project
2 * SPDX-FileCopyrightText: 2008-2009 Jan Hambrecht <jaham@gmx.net>
3 *
4 * SPDX-License-Identifier: LGPL-2.0-or-later
5 */
6
7#include "KoPathSegment.h"
8#include "KoPathPoint.h"
9#include <FlakeDebug.h>
10#include <QPainterPath>
11#include <QTransform>
12#include <math.h>
13
14#include "kis_global.h"
15
16#include <KisBezierUtils.h>
17
18class Q_DECL_HIDDEN KoPathSegment::Private
19{
20public:
22 : first(p1),
23 second(p2),
24 q(qq)
25 {
26 }
27
29 qreal distanceFromChord(const QPointF &point) const;
30
32 qreal chordLength() const;
33
36
39
42
52 void deCasteljau(qreal t, QPointF *p1, QPointF *p2, QPointF *p3, QPointF *p4, QPointF *p5) const;
53
57};
58
59void KoPathSegment::Private::deCasteljau(qreal t, QPointF *p1, QPointF *p2, QPointF *p3, QPointF *p4, QPointF *p5) const
60{
61 if (!q->isValid())
62 return;
63
64 int deg = q->degree();
65 QPointF q[4];
66
67 q[0] = first->point();
68 if (deg == 2) {
69 q[1] = first->activeControlPoint2() ? first->controlPoint2() : second->controlPoint1();
70 } else if (deg == 3) {
71 q[1] = first->controlPoint2();
72 q[2] = second->controlPoint1();
73 }
74 if (deg >= 0) {
75 q[deg] = second->point();
76 }
77
78 // points of the new segment after the split point
79 QPointF p[3];
80
81 // the De Casteljau algorithm
82 for (unsigned short j = 1; j <= deg; ++j) {
83 for (unsigned short i = 0; i <= deg - j; ++i) {
84 q[i] = (1.0 - t) * q[i] + t * q[i + 1];
85 }
86 p[j - 1] = q[0];
87 }
88
89 if (deg == 2) {
90 if (p2)
91 *p2 = p[0];
92 if (p3)
93 *p3 = p[1];
94 if (p4)
95 *p4 = q[1];
96 } else if (deg == 3) {
97 if (p1)
98 *p1 = p[0];
99 if (p2)
100 *p2 = p[1];
101 if (p3)
102 *p3 = p[2];
103 if (p4)
104 *p4 = q[1];
105 if (p5)
106 *p5 = q[2];
107 }
108}
109
110QList<qreal> KoPathSegment::Private::roots() const
111{
112 QList<qreal> rootParams;
113
114 if (!q->isValid())
115 return rootParams;
116
117 // Calculate how often the control polygon crosses the x-axis
118 // This is the upper limit for the number of roots.
119 int xAxisCrossings = KisBezierUtils::controlPolygonZeros(q->controlPoints());
120
121 if (!xAxisCrossings) {
122 // No solutions.
123 }
124 else if (xAxisCrossings == 1 && q->isFlat(0.01 / chordLength())) {
125 // Exactly one solution.
126 // Calculate intersection of chord with x-axis.
127 QPointF chord = second->point() - first->point();
128 QPointF segStart = first->point();
129 rootParams.append((chord.x() * segStart.y() - chord.y() * segStart.x()) / - chord.y());
130 }
131 else {
132 // Many solutions. Do recursive midpoint subdivision.
133 QPair<KoPathSegment, KoPathSegment> splitSegments = q->splitAt(0.5);
134 rootParams += splitSegments.first.d->roots();
135 rootParams += splitSegments.second.d->roots();
136 }
137
138 return rootParams;
139}
140
141QList<qreal> KoPathSegment::Private::extrema() const
142{
143 int deg = q->degree();
144 if (deg <= 1)
145 return QList<qreal>();
146
147 QList<qreal> params;
148
149 /*
150 * The basic idea for calculating the extrema for bezier segments
151 * was found in comp.graphics.algorithms:
152 *
153 * Both the x coordinate and the y coordinate are polynomial. Newton told
154 * us that at a maximum or minimum the derivative will be zero.
155 *
156 * We have a helpful trick for the derivatives: use the curve r(t) defined by
157 * differences of successive control points.
158 * Setting r(t) to zero and using the x and y coordinates of differences of
159 * successive control points lets us find the parameters t, where the original
160 * bezier curve has a minimum or a maximum.
161 */
162 if (deg == 2) {
163 /*
164 * For quadratic bezier curves r(t) is a linear Bezier curve:
165 *
166 * 1
167 * r(t) = Sum Bi,1(t) *Pi = B0,1(t) * P0 + B1,1(t) * P1
168 * i=0
169 *
170 * r(t) = (1-t) * P0 + t * P1
171 *
172 * r(t) = (P1 - P0) * t + P0
173 */
174
175 // calculating the differences between successive control points
176 QPointF cp = first->activeControlPoint2() ?
177 first->controlPoint2() : second->controlPoint1();
178 QPointF x0 = cp - first->point();
179 QPointF x1 = second->point() - cp;
180
181 // calculating the coefficients
182 QPointF a = x1 - x0;
183 QPointF c = x0;
184
185 if (a.x() != 0.0)
186 params.append(-c.x() / a.x());
187 if (a.y() != 0.0)
188 params.append(-c.y() / a.y());
189 } else if (deg == 3) {
190 /*
191 * For cubic bezier curves r(t) is a quadratic Bezier curve:
192 *
193 * 2
194 * r(t) = Sum Bi,2(t) *Pi = B0,2(t) * P0 + B1,2(t) * P1 + B2,2(t) * P2
195 * i=0
196 *
197 * r(t) = (1-t)^2 * P0 + 2t(1-t) * P1 + t^2 * P2
198 *
199 * r(t) = (P2 - 2*P1 + P0) * t^2 + (2*P1 - 2*P0) * t + P0
200 *
201 */
202 // calculating the differences between successive control points
203 QPointF x0 = first->controlPoint2() - first->point();
204 QPointF x1 = second->controlPoint1() - first->controlPoint2();
205 QPointF x2 = second->point() - second->controlPoint1();
206
207 // calculating the coefficients
208 QPointF a = x2 - 2.0 * x1 + x0;
209 QPointF b = 2.0 * x1 - 2.0 * x0;
210 QPointF c = x0;
211
212 // calculating parameter t at minimum/maximum in x-direction
213 if (a.x() == 0.0) {
214 params.append(- c.x() / b.x());
215 } else {
216 qreal rx = b.x() * b.x() - 4.0 * a.x() * c.x();
217 if (rx < 0.0)
218 rx = 0.0;
219 params.append((-b.x() + sqrt(rx)) / (2.0*a.x()));
220 params.append((-b.x() - sqrt(rx)) / (2.0*a.x()));
221 }
222
223 // calculating parameter t at minimum/maximum in y-direction
224 if (a.y() == 0.0) {
225 params.append(- c.y() / b.y());
226 } else {
227 qreal ry = b.y() * b.y() - 4.0 * a.y() * c.y();
228 if (ry < 0.0)
229 ry = 0.0;
230 params.append((-b.y() + sqrt(ry)) / (2.0*a.y()));
231 params.append((-b.y() - sqrt(ry)) / (2.0*a.y()));
232 }
233 }
234
235 return params;
236}
237
238qreal KoPathSegment::Private::distanceFromChord(const QPointF &point) const
239{
240 // the segments chord
241 QPointF chord = second->point() - first->point();
242 // the point relative to the segment
243 QPointF relPoint = point - first->point();
244 // project point to chord
245 qreal scale = chord.x() * relPoint.x() + chord.y() * relPoint.y();
246 scale /= chord.x() * chord.x() + chord.y() * chord.y();
247
248 // the vector form the point to the projected point on the chord
249 QPointF diffVec = scale * chord - relPoint;
250
251 // the unsigned distance of the point to the chord
252 qreal distance = sqrt(diffVec.x() * diffVec.x() + diffVec.y() * diffVec.y());
253
254 // determine sign of the distance using the cross product
255 if (chord.x()*relPoint.y() - chord.y()*relPoint.x() > 0) {
256 return distance;
257 } else {
258 return -distance;
259 }
260}
261
262qreal KoPathSegment::Private::chordLength() const
263{
264 QPointF chord = second->point() - first->point();
265 return sqrt(chord.x() * chord.x() + chord.y() * chord.y());
266}
267
268QList<QPointF> KoPathSegment::Private::linesIntersection(const KoPathSegment &segment) const
269{
270 //debugFlake << "intersecting two lines";
271 /*
272 we have to line segments:
273
274 s1 = A + r * (B-A), s2 = C + s * (D-C) for r,s in [0,1]
275
276 if s1 and s2 intersect we set s1 = s2 so we get these two equations:
277
278 Ax + r * (Bx-Ax) = Cx + s * (Dx-Cx)
279 Ay + r * (By-Ay) = Cy + s * (Dy-Cy)
280
281 which we can solve to get r and s
282 */
283 QList<QPointF> isects;
284 QPointF A = first->point();
285 QPointF B = second->point();
286 QPointF C = segment.first()->point();
287 QPointF D = segment.second()->point();
288
289 qreal denom = (B.x() - A.x()) * (D.y() - C.y()) - (B.y() - A.y()) * (D.x() - C.x());
290 qreal num_r = (A.y() - C.y()) * (D.x() - C.x()) - (A.x() - C.x()) * (D.y() - C.y());
291 // check if lines are collinear
292 if (denom == 0.0 && num_r == 0.0)
293 return isects;
294
295 qreal num_s = (A.y() - C.y()) * (B.x() - A.x()) - (A.x() - C.x()) * (B.y() - A.y());
296 qreal r = num_r / denom;
297 qreal s = num_s / denom;
298
299 // check if intersection is inside our line segments
300 if (r < 0.0 || r > 1.0)
301 return isects;
302 if (s < 0.0 || s > 1.0)
303 return isects;
304
305 // calculate the actual intersection point
306 isects.append(A + r * (B - A));
307
308 return isects;
309}
310
311
314 : d(new Private(this, first, second))
315{
316}
317
319 : d(new Private(this, 0, 0))
320{
321 if (! segment.first() || segment.first()->parent())
322 setFirst(segment.first());
323 else
324 setFirst(new KoPathPoint(*segment.first()));
325
326 if (! segment.second() || segment.second()->parent())
327 setSecond(segment.second());
328 else
329 setSecond(new KoPathPoint(*segment.second()));
330}
331
332KoPathSegment::KoPathSegment(const QPointF &p0, const QPointF &p1)
333 : d(new Private(this, new KoPathPoint(), new KoPathPoint()))
334{
335 d->first->setPoint(p0);
336 d->second->setPoint(p1);
337}
338
339KoPathSegment::KoPathSegment(const QPointF &p0, const QPointF &p1, const QPointF &p2)
340 : d(new Private(this, new KoPathPoint(), new KoPathPoint()))
341{
342 d->first->setPoint(p0);
343 d->first->setControlPoint2(p1);
344 d->second->setPoint(p2);
345}
346
347KoPathSegment::KoPathSegment(const QPointF &p0, const QPointF &p1, const QPointF &p2, const QPointF &p3)
348 : d(new Private(this, new KoPathPoint(), new KoPathPoint()))
349{
350 d->first->setPoint(p0);
351 d->first->setControlPoint2(p1);
352 d->second->setControlPoint1(p2);
353 d->second->setPoint(p3);
354}
355
357{
358 if (this == &rhs)
359 return (*this);
360
361 if (! rhs.first() || rhs.first()->parent())
362 setFirst(rhs.first());
363 else
364 setFirst(new KoPathPoint(*rhs.first()));
365
366 if (! rhs.second() || rhs.second()->parent())
367 setSecond(rhs.second());
368 else
369 setSecond(new KoPathPoint(*rhs.second()));
370
371 return (*this);
372}
373
375{
376 if (d->first && ! d->first->parent())
377 delete d->first;
378 if (d->second && ! d->second->parent())
379 delete d->second;
380 delete d;
381}
382
384{
385 return d->first;
386}
387
389{
390 if (d->first && !d->first->parent())
391 delete d->first;
392 d->first = first;
393}
394
396{
397 return d->second;
398}
399
401{
402 if (d->second && !d->second->parent())
403 delete d->second;
404 d->second = second;
405}
406
408{
409 return (d->first && d->second);
410}
411
413{
414 if (!isValid() && !rhs.isValid())
415 return true;
416 if (isValid() && !rhs.isValid())
417 return false;
418 if (!isValid() && rhs.isValid())
419 return false;
420
421 return (*first() == *rhs.first() && *second() == *rhs.second());
422}
423
425{
426 if (!d->first || !d->second)
427 return -1;
428
429 bool c1 = d->first->activeControlPoint2();
430 bool c2 = d->second->activeControlPoint1();
431 if (!c1 && !c2)
432 return 1;
433 if (c1 && c2)
434 return 3;
435 return 2;
436}
437
438QPointF KoPathSegment::pointAt(qreal t) const
439{
440 if (!isValid())
441 return QPointF();
442
443 if (degree() == 1) {
444 return d->first->point() + t * (d->second->point() - d->first->point());
445 } else {
446 QPointF splitP;
447
448 d->deCasteljau(t, 0, 0, &splitP, 0, 0);
449
450 return splitP;
451 }
452}
453
455{
456 if (!isValid())
457 return QRectF();
458
459 QList<QPointF> points = controlPoints();
460 QRectF bbox(points.first(), points.first());
461 Q_FOREACH (const QPointF &p, points) {
462 bbox.setLeft(qMin(bbox.left(), p.x()));
463 bbox.setRight(qMax(bbox.right(), p.x()));
464 bbox.setTop(qMin(bbox.top(), p.y()));
465 bbox.setBottom(qMax(bbox.bottom(), p.y()));
466 }
467
468 if (degree() == 1) {
469 // adjust bounding rect of horizontal and vertical lines
470 if (bbox.height() == 0.0)
471 bbox.setHeight(0.1);
472 if (bbox.width() == 0.0)
473 bbox.setWidth(0.1);
474 }
475
476 return bbox;
477}
478
480{
481 if (!isValid())
482 return QRectF();
483
484 QRectF rect = QRectF(d->first->point(), d->second->point()).normalized();
485
486 if (degree() == 1) {
487 // adjust bounding rect of horizontal and vertical lines
488 if (rect.height() == 0.0)
489 rect.setHeight(0.1);
490 if (rect.width() == 0.0)
491 rect.setWidth(0.1);
492 } else {
493 /*
494 * The basic idea for calculating the axis aligned bounding box (AABB) for bezier segments
495 * was found in comp.graphics.algorithms:
496 * Use the points at the extrema of the curve to calculate the AABB.
497 */
498 foreach (qreal t, d->extrema()) {
499 if (t >= 0.0 && t <= 1.0) {
500 QPointF p = pointAt(t);
501 rect.setLeft(qMin(rect.left(), p.x()));
502 rect.setRight(qMax(rect.right(), p.x()));
503 rect.setTop(qMin(rect.top(), p.y()));
504 rect.setBottom(qMax(rect.bottom(), p.y()));
505 }
506 }
507 }
508
509 return rect;
510}
511
513{
514 // this function uses a technique known as bezier clipping to find the
515 // intersections of the two bezier curves
516
517 QList<QPointF> isects;
518
519 if (!isValid() || !segment.isValid())
520 return isects;
521
522 int degree1 = degree();
523 int degree2 = segment.degree();
524
525 QRectF myBound = boundingRect();
526 QRectF otherBound = segment.boundingRect();
527 //debugFlake << "my boundingRect =" << myBound;
528 //debugFlake << "other boundingRect =" << otherBound;
529 if (!myBound.intersects(otherBound)) {
530 //debugFlake << "segments do not intersect";
531 return isects;
532 }
533
534 // short circuit lines intersection
535 if (degree1 == 1 && degree2 == 1) {
536 //debugFlake << "intersecting two lines";
537 isects += d->linesIntersection(segment);
538 return isects;
539 }
540
541 // first calculate the fat line L by using the signed distances
542 // of the control points from the chord
543 qreal dmin, dmax;
544 if (degree1 == 1) {
545 dmin = 0.0;
546 dmax = 0.0;
547 } else if (degree1 == 2) {
548 qreal d1;
549 if (d->first->activeControlPoint2())
550 d1 = d->distanceFromChord(d->first->controlPoint2());
551 else
552 d1 = d->distanceFromChord(d->second->controlPoint1());
553 dmin = qMin(qreal(0.0), qreal(0.5 * d1));
554 dmax = qMax(qreal(0.0), qreal(0.5 * d1));
555 } else {
556 qreal d1 = d->distanceFromChord(d->first->controlPoint2());
557 qreal d2 = d->distanceFromChord(d->second->controlPoint1());
558 if (d1*d2 > 0.0) {
559 dmin = 0.75 * qMin(qreal(0.0), qMin(d1, d2));
560 dmax = 0.75 * qMax(qreal(0.0), qMax(d1, d2));
561 } else {
562 dmin = 4.0 / 9.0 * qMin(qreal(0.0), qMin(d1, d2));
563 dmax = 4.0 / 9.0 * qMax(qreal(0.0), qMax(d1, d2));
564 }
565 }
566
567 //debugFlake << "using fat line: dmax =" << dmax << " dmin =" << dmin;
568
569 /*
570 the other segment is given as a bezier curve of the form:
571 (1) P(t) = sum_i P_i * B_{n,i}(t)
572 our chord line is of the form:
573 (2) ax + by + c = 0
574 we can determine the distance d(t) from any point P(t) to our chord
575 by substituting formula (1) into formula (2):
576 d(t) = sum_i d_i B_{n,i}(t), where d_i = a*x_i + b*y_i + c
577 which forms another explicit bezier curve
578 D(t) = (t,d(t)) = sum_i D_i B_{n,i}(t)
579 now values of t for which P(t) lies outside of our fat line L
580 corresponds to values of t for which D(t) lies above d = dmax or
581 below d = dmin
582 we can determine parameter ranges of t for which P(t) is guaranteed
583 to lie outside of L by identifying ranges of t which the convex hull
584 of D(t) lies above dmax or below dmin
585 */
586 // now calculate the control points of D(t) by using the signed
587 // distances of P_i to our chord
588 KoPathSegment dt;
589 if (degree2 == 1) {
590 QPointF p0(0.0, d->distanceFromChord(segment.first()->point()));
591 QPointF p1(1.0, d->distanceFromChord(segment.second()->point()));
592 dt = KoPathSegment(p0, p1);
593 } else if (degree2 == 2) {
594 QPointF p0(0.0, d->distanceFromChord(segment.first()->point()));
595 QPointF p1 = segment.first()->activeControlPoint2()
596 ? QPointF(0.5, d->distanceFromChord(segment.first()->controlPoint2()))
597 : QPointF(0.5, d->distanceFromChord(segment.second()->controlPoint1()));
598 QPointF p2(1.0, d->distanceFromChord(segment.second()->point()));
599 dt = KoPathSegment(p0, p1, p2);
600 } else if (degree2 == 3) {
601 QPointF p0(0.0, d->distanceFromChord(segment.first()->point()));
602 QPointF p1(1. / 3., d->distanceFromChord(segment.first()->controlPoint2()));
603 QPointF p2(2. / 3., d->distanceFromChord(segment.second()->controlPoint1()));
604 QPointF p3(1.0, d->distanceFromChord(segment.second()->point()));
605 dt = KoPathSegment(p0, p1, p2, p3);
606 } else {
607 //debugFlake << "invalid degree of segment -> exiting";
608 return isects;
609 }
610
611 // get convex hull of the segment D(t)
612 QList<QPointF> hull = dt.convexHull();
613
614 // now calculate intersections with the line y1 = dmin, y2 = dmax
615 // with the convex hull edges
616 int hullCount = hull.count();
617 qreal tmin = 1.0, tmax = 0.0;
618 bool intersectionsFoundMax = false;
619 bool intersectionsFoundMin = false;
620
621 for (int i = 0; i < hullCount; ++i) {
622 QPointF p1 = hull[i];
623 QPointF p2 = hull[(i+1) % hullCount];
624 //debugFlake << "intersecting hull edge (" << p1 << p2 << ")";
625 // hull edge is completely above dmax
626 if (p1.y() > dmax && p2.y() > dmax)
627 continue;
628 // hull edge is completely below dmin
629 if (p1.y() < dmin && p2.y() < dmin)
630 continue;
631 if (p1.x() == p2.x()) {
632 // vertical edge
633 bool dmaxIntersection = (dmax < qMax(p1.y(), p2.y()) && dmax > qMin(p1.y(), p2.y()));
634 bool dminIntersection = (dmin < qMax(p1.y(), p2.y()) && dmin > qMin(p1.y(), p2.y()));
635 if (dmaxIntersection || dminIntersection) {
636 tmin = qMin(tmin, p1.x());
637 tmax = qMax(tmax, p1.x());
638 if (dmaxIntersection) {
639 intersectionsFoundMax = true;
640 //debugFlake << "found intersection with dmax at " << p1.x() << "," << dmax;
641 } else {
642 intersectionsFoundMin = true;
643 //debugFlake << "found intersection with dmin at " << p1.x() << "," << dmin;
644 }
645 }
646 } else if (p1.y() == p2.y()) {
647 // horizontal line
648 if (p1.y() == dmin || p1.y() == dmax) {
649 if (p1.y() == dmin) {
650 intersectionsFoundMin = true;
651 //debugFlake << "found intersection with dmin at " << p1.x() << "," << dmin;
652 //debugFlake << "found intersection with dmin at " << p2.x() << "," << dmin;
653 } else {
654 intersectionsFoundMax = true;
655 //debugFlake << "found intersection with dmax at " << p1.x() << "," << dmax;
656 //debugFlake << "found intersection with dmax at " << p2.x() << "," << dmax;
657 }
658 tmin = qMin(tmin, p1.x());
659 tmin = qMin(tmin, p2.x());
660 tmax = qMax(tmax, p1.x());
661 tmax = qMax(tmax, p2.x());
662 }
663 } else {
664 qreal dx = p2.x() - p1.x();
665 qreal dy = p2.y() - p1.y();
666 qreal m = dy / dx;
667 qreal n = p1.y() - m * p1.x();
668 qreal t1 = (dmax - n) / m;
669 if (t1 >= 0.0 && t1 <= 1.0) {
670 tmin = qMin(tmin, t1);
671 tmax = qMax(tmax, t1);
672 intersectionsFoundMax = true;
673 //debugFlake << "found intersection with dmax at " << t1 << "," << dmax;
674 }
675 qreal t2 = (dmin - n) / m;
676 if (t2 >= 0.0 && t2 < 1.0) {
677 tmin = qMin(tmin, t2);
678 tmax = qMax(tmax, t2);
679 intersectionsFoundMin = true;
680 //debugFlake << "found intersection with dmin at " << t2 << "," << dmin;
681 }
682 }
683 }
684
685 bool intersectionsFound = intersectionsFoundMin && intersectionsFoundMax;
686
687 //if (intersectionsFound)
688 // debugFlake << "clipping segment to interval [" << tmin << "," << tmax << "]";
689
690 if (!intersectionsFound || (1.0 - (tmax - tmin)) <= 0.2) {
691 //debugFlake << "could not clip enough -> split segment";
692 // we could not reduce the interval significantly
693 // so split the curve and calculate intersections
694 // with the remaining parts
695 QPair<KoPathSegment, KoPathSegment> parts = splitAt(0.5);
696 if (d->chordLength() < 1e-5)
697 isects += parts.first.second()->point();
698 else {
699 isects += segment.intersections(parts.first);
700 isects += segment.intersections(parts.second);
701 }
702 } else if (qAbs(tmin - tmax) < 1e-5) {
703 //debugFlake << "Yay, we found an intersection";
704 // the interval is pretty small now, just calculate the intersection at this point
705 isects.append(segment.pointAt(tmin));
706 } else {
707 QPair<KoPathSegment, KoPathSegment> clip1 = segment.splitAt(tmin);
708 //debugFlake << "splitting segment at" << tmin;
709 qreal t = (tmax - tmin) / (1.0 - tmin);
710 QPair<KoPathSegment, KoPathSegment> clip2 = clip1.second.splitAt(t);
711 //debugFlake << "splitting second part at" << t << "("<<tmax<<")";
712 isects += clip2.first.intersections(*this);
713 }
714
715 return isects;
716}
717
718KoPathSegment KoPathSegment::mapped(const QTransform &matrix) const
719{
720 if (!isValid())
721 return *this;
722
723 KoPathPoint * p1 = new KoPathPoint(*d->first);
724 KoPathPoint * p2 = new KoPathPoint(*d->second);
725 p1->map(matrix);
726 p2->map(matrix);
727
728 return KoPathSegment(p1, p2);
729}
730
732{
733 if (! isValid())
734 return KoPathSegment();
735
736 KoPathPoint * p1 = new KoPathPoint(*d->first);
737 KoPathPoint * p2 = new KoPathPoint(*d->second);
738
739 if (degree() == 1) {
740 p1->setControlPoint2(p1->point() + 0.3 * (p2->point() - p1->point()));
741 p2->setControlPoint1(p2->point() + 0.3 * (p1->point() - p2->point()));
742 } else if (degree() == 2) {
743 /* quadric bezier (a0,a1,a2) to cubic bezier (b0,b1,b2,b3):
744 *
745 * b0 = a0
746 * b1 = a0 + 2/3 * (a1-a0)
747 * b2 = a1 + 1/3 * (a2-a1)
748 * b3 = a2
749 */
750 QPointF a1 = p1->activeControlPoint2() ? p1->controlPoint2() : p2->controlPoint1();
751 QPointF b1 = p1->point() + 2.0 / 3.0 * (a1 - p1->point());
752 QPointF b2 = a1 + 1.0 / 3.0 * (p2->point() - a1);
753 p1->setControlPoint2(b1);
754 p2->setControlPoint1(b2);
755 }
756
757 return KoPathSegment(p1, p2);
758}
759
760qreal KoPathSegment::length(qreal error) const
761{
762 /*
763 * This algorithm is implemented based on an idea by Jens Gravesen:
764 * "Adaptive subdivision and the length of Bezier curves" mat-report no. 1992-10, Mathematical Institute,
765 * The Technical University of Denmark.
766 *
767 * By subdividing the curve at parameter value t you only have to find the length of a full Bezier curve.
768 * If you denote the length of the control polygon by L1 i.e.:
769 * L1 = |P0 P1| +|P1 P2| +|P2 P3|
770 *
771 * and the length of the cord by L0 i.e.:
772 * L0 = |P0 P3|
773 *
774 * then
775 * L = 1/2*L0 + 1/2*L1
776 *
777 * is a good approximation to the length of the curve, and the difference
778 * ERR = L1-L0
779 *
780 * is a measure of the error. If the error is to large, then you just subdivide curve at parameter value
781 * 1/2, and find the length of each half.
782 * If m is the number of subdivisions then the error goes to zero as 2^-4m.
783 * If you don't have a cubic curve but a curve of degree n then you put
784 * L = (2*L0 + (n-1)*L1)/(n+1)
785 */
786
787 int deg = degree();
788
789 if (deg == -1)
790 return 0.0;
791
792 QList<QPointF> ctrlPoints = controlPoints();
793
794 // calculate chord length
795 qreal chordLen = d->chordLength();
796
797 if (deg == 1) {
798 return chordLen;
799 }
800
801 // calculate length of control polygon
802 qreal polyLength = 0.0;
803
804 for (int i = 0; i < deg; ++i) {
805 QPointF ctrlSegment = ctrlPoints[i+1] - ctrlPoints[i];
806 polyLength += sqrt(ctrlSegment.x() * ctrlSegment.x() + ctrlSegment.y() * ctrlSegment.y());
807 }
808
809 if ((polyLength - chordLen) > error) {
810 // the error is still bigger than our tolerance -> split segment
811 QPair<KoPathSegment, KoPathSegment> parts = splitAt(0.5);
812 return parts.first.length(error) + parts.second.length(error);
813 } else {
814 // the error is smaller than our tolerance
815 if (deg == 3)
816 return 0.5 * chordLen + 0.5 * polyLength;
817 else
818 return (2.0 * chordLen + polyLength) / 3.0;
819 }
820}
821
822qreal KoPathSegment::lengthAt(qreal t, qreal error) const
823{
824 if (t == 0.0)
825 return 0.0;
826 if (t == 1.0)
827 return length(error);
828
829 QPair<KoPathSegment, KoPathSegment> parts = splitAt(t);
830 return parts.first.length(error);
831}
832
833qreal KoPathSegment::paramAtLength(qreal length, qreal tolerance) const
834{
835 const int deg = degree();
836 // invalid degree or invalid specified length
837 if (deg < 1 || length <= 0.0) {
838 return 0.0;
839 }
840
841 if (deg == 1) {
842 // make sure we return a maximum value of 1.0
843 return qMin(qreal(1.0), length / d->chordLength());
844 }
845
846 // for curves we need to make sure, that the specified length
847 // value does not exceed the actual length of the segment
848 // if that happens, we return 1.0 to avoid infinite looping
849 if (length >= d->chordLength() && length >= this->length(tolerance)) {
850 return 1.0;
851 }
852
853 qreal startT = 0.0; // interval start
854 qreal midT = 0.5; // interval center
855 qreal endT = 1.0; // interval end
856
857 // divide and conquer, split a midpoint and check
858 // on which side of the midpoint to continue
859 qreal midLength = lengthAt(0.5);
860 while (qAbs(midLength - length) / length > tolerance) {
861 if (midLength < length)
862 startT = midT;
863 else
864 endT = midT;
865
866 // new interval center
867 midT = 0.5 * (startT + endT);
868 // length at new interval center
869 midLength = lengthAt(midT);
870 }
871
872 return midT;
873}
874
875bool KoPathSegment::isFlat(qreal tolerance) const
876{
877 /*
878 * Calculate the height of the bezier curve.
879 * This is done by rotating the curve so that then chord
880 * is parallel to the x-axis and the calculating the
881 * parameters t for the extrema of the curve.
882 * The curve points at the extrema are then used to
883 * calculate the height.
884 */
885 if (degree() <= 1)
886 return true;
887
888 QPointF chord = d->second->point() - d->first->point();
889 // calculate angle of chord to the x-axis
890 qreal chordAngle = atan2(chord.y(), chord.x());
891 QTransform m;
892 m.translate(d->first->point().x(), d->first->point().y());
893 m.rotate(chordAngle * M_PI / 180.0);
894 m.translate(-d->first->point().x(), -d->first->point().y());
895
896 KoPathSegment s = mapped(m);
897
898 qreal minDist = 0.0;
899 qreal maxDist = 0.0;
900
901 foreach (qreal t, s.d->extrema()) {
902 if (t >= 0.0 && t <= 1.0) {
903 QPointF p = pointAt(t);
904 qreal dist = s.d->distanceFromChord(p);
905 minDist = qMin(dist, minDist);
906 maxDist = qMax(dist, maxDist);
907 }
908 }
909
910 return (maxDist - minDist <= tolerance);
911}
912
914{
915 QList<QPointF> hull;
916 int deg = degree();
917 if (deg == 1) {
918 // easy just the two end points
919 hull.append(d->first->point());
920 hull.append(d->second->point());
921 } else if (deg == 2) {
922 // we want to have a counter-clockwise oriented triangle
923 // of the three control points
924 QPointF chord = d->second->point() - d->first->point();
925 QPointF cp = d->first->activeControlPoint2() ? d->first->controlPoint2() : d->second->controlPoint1();
926 QPointF relP = cp - d->first->point();
927 // check on which side of the chord the control point is
928 bool pIsRight = (chord.x() * relP.y() - chord.y() * relP.x() > 0);
929 hull.append(d->first->point());
930 if (pIsRight)
931 hull.append(cp);
932 hull.append(d->second->point());
933 if (! pIsRight)
934 hull.append(cp);
935 } else if (deg == 3) {
936 // we want a counter-clockwise oriented polygon
937 QPointF chord = d->second->point() - d->first->point();
938 QPointF relP1 = d->first->controlPoint2() - d->first->point();
939 // check on which side of the chord the control points are
940 bool p1IsRight = (chord.x() * relP1.y() - chord.y() * relP1.x() > 0);
941 hull.append(d->first->point());
942 if (p1IsRight)
943 hull.append(d->first->controlPoint2());
944 hull.append(d->second->point());
945 if (! p1IsRight)
946 hull.append(d->first->controlPoint2());
947
948 // now we have a counter-clockwise triangle with the points i,j,k
949 // we have to check where the last control points lies
950 bool rightOfEdge[3];
951 QPointF lastPoint = d->second->controlPoint1();
952 for (int i = 0; i < 3; ++i) {
953 QPointF relP = lastPoint - hull[i];
954 QPointF edge = hull[(i+1)%3] - hull[i];
955 rightOfEdge[i] = (edge.x() * relP.y() - edge.y() * relP.x() > 0);
956 }
957 for (int i = 0; i < 3; ++i) {
958 int prev = (3 + i - 1) % 3;
959 int next = (i + 1) % 3;
960 // check if point is only right of the n-th edge
961 if (! rightOfEdge[prev] && rightOfEdge[i] && ! rightOfEdge[next]) {
962 // insert by breaking the n-th edge
963 hull.insert(i + 1, lastPoint);
964 break;
965 }
966 // check if it is right of the n-th and right of the (n+1)-th edge
967 if (rightOfEdge[i] && rightOfEdge[next]) {
968 // remove both edge, insert two new edges
969 hull[i+1] = lastPoint;
970 break;
971 }
972 // check if it is right of n-th and right of (n-1)-th edge
973 if (rightOfEdge[i] && rightOfEdge[prev]) {
974 hull[i] = lastPoint;
975 break;
976 }
977 }
978 }
979
980 return hull;
981}
982
983QPair<KoPathSegment, KoPathSegment> KoPathSegment::splitAt(qreal t) const
984{
985 QPair<KoPathSegment, KoPathSegment> results;
986 if (!isValid())
987 return results;
988
989 if (degree() == 1) {
990 QPointF p = d->first->point() + t * (d->second->point() - d->first->point());
991 results.first = KoPathSegment(d->first->point(), p);
992 results.second = KoPathSegment(p, d->second->point());
993 } else {
994 QPointF newCP2, newCP1, splitP, splitCP1, splitCP2;
995
996 d->deCasteljau(t, &newCP2, &splitCP1, &splitP, &splitCP2, &newCP1);
997
998 if (degree() == 2) {
999 if (second()->activeControlPoint1()) {
1000 KoPathPoint *s1p1 = new KoPathPoint(0, d->first->point());
1001 KoPathPoint *s1p2 = new KoPathPoint(0, splitP);
1002 s1p2->setControlPoint1(splitCP1);
1003 KoPathPoint *s2p1 = new KoPathPoint(0, splitP);
1004 KoPathPoint *s2p2 = new KoPathPoint(0, d->second->point());
1005 s2p2->setControlPoint1(splitCP2);
1006 results.first = KoPathSegment(s1p1, s1p2);
1007 results.second = KoPathSegment(s2p1, s2p2);
1008 } else {
1009 results.first = KoPathSegment(d->first->point(), splitCP1, splitP);
1010 results.second = KoPathSegment(splitP, splitCP2, d->second->point());
1011 }
1012 } else {
1013 results.first = KoPathSegment(d->first->point(), newCP2, splitCP1, splitP);
1014 results.second = KoPathSegment(splitP, splitCP2, newCP1, d->second->point());
1015 }
1016 }
1017
1018 return results;
1019}
1020
1022{
1024 controlPoints.append(d->first->point());
1025 if (d->first->activeControlPoint2())
1026 controlPoints.append(d->first->controlPoint2());
1027 if (d->second->activeControlPoint1())
1028 controlPoints.append(d->second->controlPoint1());
1029 controlPoints.append(d->second->point());
1030
1031 return controlPoints;
1032}
1033
1034qreal KoPathSegment::nearestPoint(const QPointF &point) const
1035{
1036 if (!isValid())
1037 return -1.0;
1038
1040}
1041
1042KoPathSegment KoPathSegment::interpolate(const QPointF &p0, const QPointF &p1, const QPointF &p2, qreal t)
1043{
1044 if (t <= 0.0 || t >= 1.0)
1045 return KoPathSegment();
1046
1047 /*
1048 B(t) = [x2 y2] = (1-t)^2*P0 + 2t*(1-t)*P1 + t^2*P2
1049
1050 B(t) - (1-t)^2*P0 - t^2*P2
1051 P1 = --------------------------
1052 2t*(1-t)
1053 */
1054
1055 QPointF c1 = p1 - (1.0-t) * (1.0-t)*p0 - t * t * p2;
1056
1057 qreal denom = 2.0 * t * (1.0-t);
1058
1059 c1.rx() /= denom;
1060 c1.ry() /= denom;
1061
1062 return KoPathSegment(p0, c1, p2);
1063}
1064
1065#if 0
1066void KoPathSegment::printDebug() const
1067{
1068 int deg = degree();
1069 debugFlake << "degree:" << deg;
1070 if (deg < 1)
1071 return;
1072
1073 debugFlake << "P0:" << d->first->point();
1074 if (deg == 1) {
1075 debugFlake << "P2:" << d->second->point();
1076 } else if (deg == 2) {
1077 if (d->first->activeControlPoint2())
1078 debugFlake << "P1:" << d->first->controlPoint2();
1079 else
1080 debugFlake << "P1:" << d->second->controlPoint1();
1081 debugFlake << "P2:" << d->second->point();
1082 } else if (deg == 3) {
1083 debugFlake << "P1:" << d->first->controlPoint2();
1084 debugFlake << "P2:" << d->second->controlPoint1();
1085 debugFlake << "P3:" << d->second->point();
1086 }
1087}
1088#endif
qreal length(const QPointF &vec)
Definition Ellipse.cc:82
#define debugFlake
Definition FlakeDebug.h:15
const Params2D p
QPointF p0
QPointF p2
QPointF p3
QPointF p1
qreal distance(const QPointF &p1, const QPointF &p2)
qreal D(qreal t, const QPointF &P0, const QPointF &P1, const QPointF &P2, const QPointF &P3, const QPointF &p)
#define C(i, j)
A KoPathPoint represents a point in a path.
void setControlPoint1(const QPointF &point)
Set the control point 1.
QPointF point
QPointF controlPoint1
KoPathShape * parent() const
Get the path shape the point belongs to.
bool activeControlPoint2
QPointF controlPoint2
A KoPathSegment consist of two neighboring KoPathPoints.
Private *const d
QList< qreal > roots() const
Returns parameters for curve roots.
QPair< KoPathSegment, KoPathSegment > splitAt(qreal t) const
Splits segment at given position returning the two resulting segments.
void setSecond(KoPathPoint *second)
Sets the second segment point.
KoPathSegment mapped(const QTransform &matrix) const
Returns transformed segment.
qreal length(qreal error=0.005) const
QRectF controlPointRect() const
Returns the control point bounding rect.
qreal paramAtLength(qreal length, qreal tolerance=0.001) const
KoPathPoint * first
qreal chordLength() const
Returns the chord length, i.e. the distance between first and last control point.
qreal lengthAt(qreal t, qreal error=0.005) const
KoPathSegment & operator=(const KoPathSegment &other)
Assigns segment.
Private(KoPathSegment *qq, KoPathPoint *p1, KoPathPoint *p2)
KoPathSegment(KoPathPoint *first=0, KoPathPoint *second=0)
bool isFlat(qreal tolerance=0.01) const
int degree() const
Returns the degree of the segment: 1 = line, 2 = quadratic, 3 = cubic, -1 = invalid.
QPointF pointAt(qreal t) const
Returns point at given t.
static KoPathSegment interpolate(const QPointF &p0, const QPointF &p1, const QPointF &p2, qreal t)
QList< QPointF > convexHull() const
Returns the convex hull polygon of the segment.
QList< QPointF > intersections(const KoPathSegment &segment) const
Returns list of intersections with the given path segment.
qreal nearestPoint(const QPointF &point) const
QList< QPointF > linesIntersection(const KoPathSegment &segment) const
Returns intersection of lines if one exists.
void deCasteljau(qreal t, QPointF *p1, QPointF *p2, QPointF *p3, QPointF *p4, QPointF *p5) const
~KoPathSegment()
Destroys the path segment.
bool operator==(const KoPathSegment &other) const
Compare operator.
KoPathSegment * q
void setFirst(KoPathPoint *first)
Sets the first segment point.
KoPathPoint * second
qreal distanceFromChord(const QPointF &point) const
calculates signed distance of given point from segment chord
bool isValid() const
Returns if segment is valid, e.g. has two valid points.
QRectF boundingRect() const
Returns the axis aligned tight bounding rect.
QList< QPointF > controlPoints() const
Returns ordered list of control points.
QList< qreal > extrema() const
Returns parameters for curve extrema.
KoPathSegment toCubic() const
Returns cubic bezier curve segment of this segment.
#define M_PI
Definition kis_global.h:111
int controlPolygonZeros(const QList< QPointF > &controlPoints)
qreal nearestPoint(const QList< QPointF > controlPoints, const QPointF &point, qreal *resultDistance, QPointF *resultPoint)