Krita Source Code Documentation
Loading...
Searching...
No Matches
KoCurveFit.cpp
Go to the documentation of this file.
1/* This file is part of the KDE project
2 SPDX-FileCopyrightText: 2001-2003 Rob Buis <buis@kde.org>
3 SPDX-FileCopyrightText: 2007, 2009 Jan Hambrecht <jaham@gmx.net>
4
5 SPDX-License-Identifier: LGPL-2.0-or-later
6*/
7
8#include "KoCurveFit.h"
9#include <KoPathShape.h>
10#include <QVector>
11#include <math.h>
12
14const qreal Zero = 10e-12;
15
16/*
17 An Algorithm for Automatically Fitting Digitized Curves
18 by Philip J. Schneider
19 from "Graphics Gems", Academic Press, 1990
20
21 http://tog.acm.org/resources/GraphicsGems/gems/FitCurves.c
22 http://tog.acm.org/resources/GraphicsGems/gems/README
23*/
24
25class FitVector {
26public:
27 FitVector(const QPointF &p)
28 : m_X(p.x())
29 , m_Y(p.y())
30 {
31 }
32
34 : m_X(0)
35 , m_Y(0)
36 {
37 }
38
39 FitVector(const QPointF &a, const QPointF &b)
40 : m_X(a.x() - b.x())
41 , m_Y(a.y() - b.y())
42 {
43 }
44
45 void normalize() {
46 qreal len = length();
47 if (qFuzzyCompare(len, qreal(0.0))) {
48 return;
49 }
50 m_X /= len; m_Y /= len;
51 }
52
53 void negate() {
54 m_X = -m_X;
55 m_Y = -m_Y;
56 }
57
58 void scale(qreal s) {
59 qreal len = length();
60 if (qFuzzyCompare(len, qreal(0.0))) {
61 return;
62 }
63 m_X *= s / len;
64 m_Y *= s / len;
65 }
66
67 qreal dot(const FitVector &v) const {
68 return ((m_X*v.m_X) + (m_Y*v.m_Y));
69 }
70
71 qreal length() const {
72 return (qreal) sqrt(m_X*m_X + m_Y*m_Y);
73 }
74
75 QPointF operator+(const QPointF &p) {
76 QPointF b(p.x() + m_X, p.y() + m_Y);
77 return b;
78 }
79
80public:
81 qreal m_X, m_Y;
82};
83
84qreal distance(const QPointF &p1, const QPointF &p2)
85{
86 qreal dx = (p1.x() - p2.x());
87 qreal dy = (p1.y() - p2.y());
88 return sqrt(dx*dx + dy*dy);
89}
90
91
93{
94 FitVector tHat1(points.at(end + 1), points.at(end));
95
96 tHat1.normalize();
97
98 return tHat1;
99}
100
102{
103 FitVector tHat1(points.at(end - 1), points.at(end));
104
105 tHat1.normalize();
106
107 return tHat1;
108}
109
110/*
111 * ChordLengthParameterize :
112 * Assign parameter values to digitized points
113 * using relative distances between points.
114 */
115static qreal *ChordLengthParameterize(const QList<QPointF> &points, int first, int last)
116{
117 int i;
118 qreal *u; /* Parameterization */
119
120 u = new qreal[(last-first+1)];
121
122 u[0] = 0.0;
123 for (i = first + 1; i <= last; ++i) {
124 u[i-first] = u[i-first-1] +
125 distance(points.at(i), points.at(i - 1));
126 }
127
128 qreal denominator = u[last-first];
129 if (qFuzzyCompare(denominator, qreal(0.0))) {
130 denominator = Zero;
131 }
132
133 for (i = first + 1; i <= last; ++i) {
134 u[i-first] = u[i-first] / denominator;
135 }
136
137 return(u);
138}
139
141{
142 FitVector c;
143 c.m_X = a.m_X + b.m_X; c.m_Y = a.m_Y + b.m_Y;
144 return (c);
145}
147{
148 FitVector result;
149 result.m_X = v.m_X * s; result.m_Y = v.m_Y * s;
150 return (result);
151}
152
154{
155 FitVector c;
156 c.m_X = a.m_X - b.m_X; c.m_Y = a.m_Y - b.m_Y;
157 return (c);
158}
159
160static FitVector ComputeCenterTangent(const QList<QPointF> &points, int center)
161{
162 FitVector V1, V2, tHatCenter;
163
164 FitVector cpointb(points.at(center - 1));
165 FitVector cpoint(points.at(center));
166 FitVector cpointa(points.at(center + 1));
167
168 V1 = VectorSub(cpointb, cpoint);
169 V2 = VectorSub(cpoint, cpointa);
170 tHatCenter.m_X = ((V1.m_X + V2.m_X) / 2.0);
171 tHatCenter.m_Y = ((V1.m_Y + V2.m_Y) / 2.0);
172 tHatCenter.normalize();
173 return tHatCenter;
174}
175
176/*
177 * B0, B1, B2, B3 :
178 * Bezier multipliers
179 */
180static qreal B0(qreal u)
181{
182 qreal tmp = 1.0 - u;
183 return (tmp * tmp * tmp);
184}
185
186
187static qreal B1(qreal u)
188{
189 qreal tmp = 1.0 - u;
190 return (3 * u *(tmp * tmp));
191}
192
193static qreal B2(qreal u)
194{
195 qreal tmp = 1.0 - u;
196 return (3 * u * u * tmp);
197}
198
199static qreal B3(qreal u)
200{
201 return (u * u * u);
202}
203
204/*
205 * GenerateBezier :
206 * Use least-squares method to find Bezier control points for region.
207 *
208 */
209QPointF* GenerateBezier(const QList<QPointF> &points, int first, int last, qreal *uPrime, FitVector tHat1, FitVector tHat2)
210{
211 int i;
212 int nPts; /* Number of pts in sub-curve */
213 qreal C[2][2]; /* Matrix C */
214 qreal X[2]; /* Matrix X */
215 qreal det_C0_C1, /* Determinants of matrices */
216 det_C0_X,
217 det_X_C1;
218 qreal alpha_l, /* Alpha values, left and right */
219 alpha_r;
220 FitVector tmp; /* Utility variable */
221 QPointF *curve;
222
223 curve = new QPointF[4];
224 nPts = last - first + 1;
225
226 /* Precomputed rhs for eqn */
227 // FitVector A[nPts][2]
229
230 /* Compute the A's */
231 for (i = 0; i < nPts; ++i) {
232 FitVector v1, v2;
233 v1 = tHat1;
234 v2 = tHat2;
235 v1.scale(B1(uPrime[i]));
236 v2.scale(B2(uPrime[i]));
237 A[i][0] = v1;
238 A[i][1] = v2;
239 }
240
241 /* Create the C and X matrices */
242 C[0][0] = 0.0;
243 C[0][1] = 0.0;
244 C[1][0] = 0.0;
245 C[1][1] = 0.0;
246 X[0] = 0.0;
247 X[1] = 0.0;
248
249 for (i = 0; i < nPts; ++i) {
250 C[0][0] += (A[i][0]).dot(A[i][0]);
251 C[0][1] += A[i][0].dot(A[i][1]);
252 /* C[1][0] += V2Dot(&A[i][0], &A[i][1]);*/
253 C[1][0] = C[0][1];
254 C[1][1] += A[i][1].dot(A[i][1]);
255
256 FitVector vfirstp1(points.at(first + i));
257 FitVector vfirst(points.at(first));
258 FitVector vlast(points.at(last));
259
260 tmp = VectorSub(vfirstp1,
261 VectorAdd(
262 VectorScale(vfirst, B0(uPrime[i])),
263 VectorAdd(
264 VectorScale(vfirst, B1(uPrime[i])),
265 VectorAdd(
266 VectorScale(vlast, B2(uPrime[i])),
267 VectorScale(vlast, B3(uPrime[i]))))));
268
269
270 X[0] += A[i][0].dot(tmp);
271 X[1] += A[i][1].dot(tmp);
272 }
273
274 /* Compute the determinants of C and X */
275 det_C0_C1 = C[0][0] * C[1][1] - C[1][0] * C[0][1];
276 det_C0_X = C[0][0] * X[1] - C[0][1] * X[0];
277 det_X_C1 = X[0] * C[1][1] - X[1] * C[0][1];
278
279 /* Finally, derive alpha values */
280 if (qFuzzyCompare(det_C0_C1, qreal(0.0))) {
281 det_C0_C1 = (C[0][0] * C[1][1]) * 10e-12;
282 if (qFuzzyCompare(det_C0_C1, qreal(0.0))) {
283 det_C0_C1 = Zero;
284 }
285 }
286 alpha_l = det_X_C1 / det_C0_C1;
287 alpha_r = det_C0_X / det_C0_C1;
288
289
290 /* If alpha negative, use the Wu/Barsky heuristic (see text) */
291 /* (if alpha is 0, you get coincident control points that lead to
292 * divide by zero in any subsequent NewtonRaphsonRootFind() call. */
293 if (alpha_l < 1.0e-6 || alpha_r < 1.0e-6) {
294 qreal dist = distance(points.at(last), points.at(first)) / 3.0;
295
296 curve[0] = points.at(first);
297 curve[3] = points.at(last);
298
299 tHat1.scale(dist);
300 tHat2.scale(dist);
301
302 curve[1] = tHat1 + curve[0];
303 curve[2] = tHat2 + curve[3];
304 return curve;
305 }
306
307 /* First and last control points of the Bezier curve are */
308 /* positioned exactly at the first and last data points */
309 /* Control points 1 and 2 are positioned an alpha distance out */
310 /* on the tangent vectors, left and right, respectively */
311 curve[0] = points.at(first);
312 curve[3] = points.at(last);
313
314 tHat1.scale(alpha_l);
315 tHat2.scale(alpha_r);
316
317 curve[1] = tHat1 + curve[0];
318 curve[2] = tHat2 + curve[3];
319
320 return (curve);
321}
322
323/*
324 * Bezier :
325 * Evaluate a Bezier curve at a particular parameter value
326 *
327 */
328static QPointF BezierII(int degree, QPointF *V, qreal t)
329{
330 int i, j;
331 QPointF Q; /* Point on curve at parameter t */
332 QPointF *Vtemp; /* Local copy of control points */
333
334 Vtemp = new QPointF[degree+1];
335
336 for (i = 0; i <= degree; ++i) {
337 Vtemp[i] = V[i];
338 }
339
340 /* Triangle computation */
341 for (i = 1; i <= degree; ++i) {
342 for (j = 0; j <= degree - i; ++j) {
343 Vtemp[j].setX((1.0 - t) * Vtemp[j].x() + t * Vtemp[j+1].x());
344 Vtemp[j].setY((1.0 - t) * Vtemp[j].y() + t * Vtemp[j+1].y());
345 }
346 }
347
348 Q = Vtemp[0];
349 delete[] Vtemp;
350 return Q;
351}
352
353/*
354 * ComputeMaxError :
355 * Find the maximum squared distance of digitized points
356 * to fitted curve.
357*/
358static qreal ComputeMaxError(const QList<QPointF> &points, int first, int last, QPointF *curve, qreal *u, int *splitPoint)
359{
360 int i;
361 qreal maxDist; /* Maximum error */
362 qreal dist; /* Current error */
363 QPointF P; /* Point on curve */
364 FitVector v; /* Vector from point to curve */
365
366 *splitPoint = (last - first + 1) / 2;
367 maxDist = 0.0;
368 for (i = first + 1; i < last; ++i) {
369 P = BezierII(3, curve, u[i-first]);
370 v = VectorSub(P, points.at(i));
371 dist = v.length();
372 if (dist >= maxDist) {
373 maxDist = dist;
374 *splitPoint = i;
375 }
376 }
377 return (maxDist);
378}
379
380
381/*
382 * NewtonRaphsonRootFind :
383 * Use Newton-Raphson iteration to find better root.
384 */
385static qreal NewtonRaphsonRootFind(QPointF *Q, QPointF P, qreal u)
386{
387 qreal numerator, denominator;
388 QPointF Q1[3], Q2[2]; /* Q' and Q'' */
389 QPointF Q_u, Q1_u, Q2_u; /*u evaluated at Q, Q', & Q'' */
390 qreal uPrime; /* Improved u */
391 int i;
392
393 /* Compute Q(u) */
394 Q_u = BezierII(3, Q, u);
395
396 /* Generate control vertices for Q' */
397 for (i = 0; i <= 2; ++i) {
398 Q1[i].setX((Q[i+1].x() - Q[i].x()) * 3.0);
399 Q1[i].setY((Q[i+1].y() - Q[i].y()) * 3.0);
400 }
401
402 /* Generate control vertices for Q'' */
403 for (i = 0; i <= 1; ++i) {
404 Q2[i].setX((Q1[i+1].x() - Q1[i].x()) * 2.0);
405 Q2[i].setY((Q1[i+1].y() - Q1[i].y()) * 2.0);
406 }
407
408 /* Compute Q'(u) and Q''(u) */
409 Q1_u = BezierII(2, Q1, u);
410 Q2_u = BezierII(1, Q2, u);
411
412 /* Compute f(u)/f'(u) */
413 numerator = (Q_u.x() - P.x()) * (Q1_u.x()) + (Q_u.y() - P.y()) * (Q1_u.y());
414 denominator = (Q1_u.x()) * (Q1_u.x()) + (Q1_u.y()) * (Q1_u.y()) +
415 (Q_u.x() - P.x()) * (Q2_u.x()) + (Q_u.y() - P.y()) * (Q2_u.y());
416
417 if (qFuzzyCompare(denominator, qreal(0.0))) {
418 denominator = Zero;
419 }
420
421 /* u = u - f(u)/f'(u) */
422 uPrime = u - (numerator / denominator);
423 return (uPrime);
424}
425
426/*
427 * Reparameterize:
428 * Given set of points and their parameterization, try to find
429 * a better parameterization.
430 *
431 */
432static qreal *Reparameterize(const QList<QPointF> &points, int first, int last, qreal *u, QPointF *curve)
433{
434 int nPts = last - first + 1;
435 int i;
436 qreal *uPrime; /* New parameter values */
437
438 uPrime = new qreal[nPts];
439 for (i = first; i <= last; ++i) {
440 uPrime[i-first] = NewtonRaphsonRootFind(curve, points.at(i), u[i-first]);
441 }
442 return (uPrime);
443}
444
445QPointF *FitCubic(const QList<QPointF> &points, int first, int last, FitVector tHat1, FitVector tHat2, float error, int &width)
446{
447 qreal *u;
448 qreal *uPrime;
449 qreal maxError;
450 int splitPoint;
451 int nPts;
452 qreal iterationError;
453 int maxIterations = 4;
454 FitVector tHatCenter;
455 QPointF *curve;
456 int i;
457
458 width = 0;
459
460
461 iterationError = error * error;
462 nPts = last - first + 1;
463
464 if (nPts == 2) {
465 qreal dist = distance(points.at(last), points.at(first)) / 3.0;
466
467 curve = new QPointF[4];
468
469 curve[0] = points.at(first);
470 curve[3] = points.at(last);
471
472 tHat1.scale(dist);
473 tHat2.scale(dist);
474
475 curve[1] = tHat1 + curve[0];
476 curve[2] = tHat2 + curve[3];
477
478 width = 4;
479 return curve;
480 }
481
482 /* Parameterize points, and attempt to fit curve */
483 u = ChordLengthParameterize(points, first, last);
484 curve = GenerateBezier(points, first, last, u, tHat1, tHat2);
485
486
487 /* Find max deviation of points to fitted curve */
488 maxError = ComputeMaxError(points, first, last, curve, u, &splitPoint);
489 if (maxError < error) {
490 delete[] u;
491 width = 4;
492 return curve;
493 }
494
495
496 /* If error not too large, try some reparameterization */
497 /* and iteration */
498 if (maxError < iterationError) {
499 for (i = 0; i < maxIterations; ++i) {
500 uPrime = Reparameterize(points, first, last, u, curve);
501 delete[] curve;
502 curve = GenerateBezier(points, first, last, uPrime, tHat1, tHat2);
503 maxError = ComputeMaxError(points, first, last,
504 curve, uPrime, &splitPoint);
505 if (maxError < error) {
506 delete[] u;
507 delete[] uPrime;
508 width = 4;
509 return curve;
510 }
511 delete[] u;
512 u = uPrime;
513 }
514 }
515
516 /* Fitting failed -- split at max error point and fit recursively */
517 delete[] u;
518 delete[] curve;
519 tHatCenter = ComputeCenterTangent(points, splitPoint);
520
521 int w1, w2;
522 QPointF *cu1 = 0, *cu2 = 0;
523 cu1 = FitCubic(points, first, splitPoint, tHat1, tHatCenter, error, w1);
524
525 tHatCenter.negate();
526 cu2 = FitCubic(points, splitPoint, last, tHatCenter, tHat2, error, w2);
527
528 QPointF *newcurve = new QPointF[w1+w2];
529 for (int i = 0; i < w1; ++i) {
530 newcurve[i] = cu1[i];
531 }
532 for (int i = 0; i < w2; ++i) {
533 newcurve[i+w1] = cu2[i];
534 }
535
536 delete[] cu1;
537 delete[] cu2;
538 width = w1 + w2;
539 return newcurve;
540}
541
542
543KoPathShape * bezierFit(const QList<QPointF> &points, float error)
544{
545 FitVector tHat1, tHat2;
546
547 tHat1 = ComputeLeftTangent(points, 0);
548 tHat2 = ComputeRightTangent(points, points.count() - 1);
549
550 int width = 0;
551 QPointF *curve;
552 curve = FitCubic(points, 0, points.count() - 1, tHat1, tHat2, error, width);
553
554 KoPathShape * path = new KoPathShape();
555
556 if (width > 3) {
557 path->moveTo(curve[0]);
558 path->curveTo(curve[1], curve[2], curve[3]);
559 for (int i = 4; i < width; i += 4) {
560 path->curveTo(curve[i+1], curve[i+2], curve[i+3]);
561 }
562 }
563
564 delete[] curve;
565 return path;
566}
567
Eigen::Matrix< double, 4, 2 > Q
const Params2D p
qreal v
qreal u
QPointF p2
QPointF p1
KoPathShape * bezierFit(const QList< QPointF > &points, float error)
FitVector ComputeLeftTangent(const QList< QPointF > &points, int end)
static FitVector VectorSub(FitVector a, FitVector b)
static FitVector VectorScale(FitVector v, qreal s)
static qreal B2(qreal u)
static qreal * Reparameterize(const QList< QPointF > &points, int first, int last, qreal *u, QPointF *curve)
static qreal B0(qreal u)
QPointF * GenerateBezier(const QList< QPointF > &points, int first, int last, qreal *uPrime, FitVector tHat1, FitVector tHat2)
FitVector ComputeRightTangent(const QList< QPointF > &points, int end)
static qreal B1(qreal u)
const qreal Zero
our equivalent to zero
static FitVector ComputeCenterTangent(const QList< QPointF > &points, int center)
static QPointF BezierII(int degree, QPointF *V, qreal t)
static qreal ComputeMaxError(const QList< QPointF > &points, int first, int last, QPointF *curve, qreal *u, int *splitPoint)
QPointF * FitCubic(const QList< QPointF > &points, int first, int last, FitVector tHat1, FitVector tHat2, float error, int &width)
static qreal * ChordLengthParameterize(const QList< QPointF > &points, int first, int last)
qreal distance(const QPointF &p1, const QPointF &p2)
static qreal B3(qreal u)
static FitVector VectorAdd(FitVector a, FitVector b)
static qreal NewtonRaphsonRootFind(QPointF *Q, QPointF P, qreal u)
#define C(i, j)
#define P(i, j, k)
void normalize()
void scale(qreal s)
void negate()
FitVector(const QPointF &a, const QPointF &b)
FitVector(const QPointF &p)
QPointF operator+(const QPointF &p)
qreal dot(const FitVector &v) const
qreal length() const
The position of a path point within a path shape.
Definition KoPathShape.h:63
static bool qFuzzyCompare(half p1, half p2)