Inkscape
Vector Graphics Editor
Loading...
Searching...
No Matches
pencil-2.cpp
Go to the documentation of this file.
1/*
2 * pencil-2 Toy - point fitting.
3 *
4 * 2009 njh
5 *
6 * This library is free software; you can redistribute it and/or
7 * modify it either under the terms of the GNU Lesser General Public
8 * License version 2.1 as published by the Free Software Foundation
9 * (the "LGPL") or, at your option, under the terms of the Mozilla
10 * Public License Version 1.1 (the "MPL"). If you do not alter this
11 * notice, a recipient may use your version of this file under either
12 * the MPL or the LGPL.
13 *
14 * You should have received a copy of the LGPL along with this library
15 * in the file COPYING-LGPL-2.1; if not, write to the Free Software
16 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
17 * You should have received a copy of the MPL along with this library
18 * in the file COPYING-MPL-1.1
19 *
20 * The contents of this file are subject to the Mozilla Public License
21 * Version 1.1 (the "License"); you may not use this file except in
22 * compliance with the License. You may obtain a copy of the License at
23 * http://www.mozilla.org/MPL/
24 *
25 * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY
26 * OF ANY KIND, either express or implied. See the LGPL or the MPL for
27 * the specific language governing rights and limitations.
28 *
29 */
30
31#include <2geom/d2.h>
32#include <2geom/sbasis.h>
35#include <2geom/math-utils.h>
36
37#include <toys/path-cairo.h>
39
40#define SP_HUGE 1e5
41#define noBEZIER_DEBUG
42
43#ifdef HAVE_IEEEFP_H
44# include <ieeefp.h>
45#endif
46
47namespace Geom{
48
49namespace BezierFitter{
50
52
53/* Forward declarations */
54static void generate_bezier(Point b[], Point const d[], double const u[], unsigned len,
55 Point const &tHat1, Point const &tHat2, double tolerance_sq);
56static void estimate_lengths(Point bezier[],
57 Point const data[], double const u[], unsigned len,
58 Point const &tHat1, Point const &tHat2);
59static void estimate_bi(Point b[4], unsigned ei,
60 Point const data[], double const u[], unsigned len);
61static void reparameterize(Point const d[], unsigned len, double u[], CubicBezier const & bezCurve);
62static double NewtonRaphsonRootFind(CubicBezier const & Q, Point const &P, double u);
63static Point darray_center_tangent(Point const d[], unsigned center, unsigned length);
64static Point darray_right_tangent(Point const d[], unsigned const len);
65static unsigned copy_without_nans_or_adjacent_duplicates(Point const src[], unsigned src_len, Point dest[]);
66static void chord_length_parameterize(Point const d[], double u[], unsigned len);
67static double compute_max_error_ratio(Point const d[], double const u[], unsigned len,
68 BezierCurve const bezCurve, double tolerance,
69 unsigned *splitPoint);
70static double compute_hook(Point const &a, Point const &b, double const u,
71 CubicBezier const & bezCurve,
72 double const tolerance);
73static double compute_hook(Point const &a, Point const &b, double const u,
74 BezierCurve const bezCurve,
75 double const tolerance) {
76 CubicBezier cb(bezCurve[0], bezCurve[1], bezCurve[2], bezCurve[3]);
77 return compute_hook(a, b, u, cb, tolerance);
78
79}
80
81
82static void reparameterize_pts(Point const d[], unsigned len, double u[], BezierCurve const bezCurve) {
83 CubicBezier cb(bezCurve[0], bezCurve[1], bezCurve[2], bezCurve[3]);
84 reparameterize(d, len, u, cb);
85}
86
87
88
89static Point const unconstrained_tangent(0, 0);
90
91
92/*
93 * B0, B1, B2, B3 : Bezier multipliers
94 */
95
96#define B0(u) ( ( 1.0 - u ) * ( 1.0 - u ) * ( 1.0 - u ) )
97#define B1(u) ( 3 * u * ( 1.0 - u ) * ( 1.0 - u ) )
98#define B2(u) ( 3 * u * u * ( 1.0 - u ) )
99#define B3(u) ( u * u * u )
100
101#ifdef BEZIER_DEBUG
102# define DOUBLE_ASSERT(x) assert( ( (x) > -SP_HUGE ) && ( (x) < SP_HUGE ) )
103# define BEZIER_ASSERT(b) do { \
104 DOUBLE_ASSERT((b)[0][X]); DOUBLE_ASSERT((b)[0][Y]); \
105 DOUBLE_ASSERT((b)[1][X]); DOUBLE_ASSERT((b)[1][Y]); \
106 DOUBLE_ASSERT((b)[2][X]); DOUBLE_ASSERT((b)[2][Y]); \
107 DOUBLE_ASSERT((b)[3][X]); DOUBLE_ASSERT((b)[3][Y]); \
108 } while(0)
109#else
110# define DOUBLE_ASSERT(x) do { } while(0)
111# define BEZIER_ASSERT(b) do { } while(0)
112#endif
113
114
115Point
116bezier_pt(unsigned const degree, Point const V[], double const t)
117{
118 return bernstein_value_at(t, V, degree);
119
120}
121
122/*
123 * ComputeLeftTangent, ComputeRightTangent, ComputeCenterTangent :
124 * Approximate unit tangents at endpoints and "center" of digitized curve
125 */
126
134Point
135darray_left_tangent(Point const d[], unsigned const len)
136{
137 assert( len >= 2 );
138 assert( d[0] != d[1] );
139 return unit_vector( d[1] - d[0] );
140}
141
152static Point
153darray_right_tangent(Point const d[], unsigned const len)
154{
155 assert( 2 <= len );
156 unsigned const last = len - 1;
157 unsigned const prev = last - 1;
158 assert( d[last] != d[prev] );
159 return unit_vector( d[prev] - d[last] );
160}
161
173Point
174darray_left_tangent(Point const d[], unsigned const len, double const tolerance_sq)
175{
176 assert( 2 <= len );
177 assert( 0 <= tolerance_sq );
178 for (unsigned i = 1;;) {
179 Point const pi(d[i]);
180 Point const t(pi - d[0]);
181 double const distsq = dot(t, t);
182 if ( tolerance_sq < distsq ) {
183 return unit_vector(t);
184 }
185 ++i;
186 if (i == len) {
187 return ( distsq == 0
189 : unit_vector(t) );
190 }
191 }
192}
193
204Point
205darray_right_tangent(Point const d[], unsigned const len, double const tolerance_sq)
206{
207 assert( 2 <= len );
208 assert( 0 <= tolerance_sq );
209 unsigned const last = len - 1;
210 for (unsigned i = last - 1;; i--) {
211 Point const pi(d[i]);
212 Point const t(pi - d[last]);
213 double const distsq = dot(t, t);
214 if ( tolerance_sq < distsq ) {
215 return unit_vector(t);
216 }
217 if (i == 0) {
218 return ( distsq == 0
220 : unit_vector(t) );
221 }
222 }
223}
224
235static Point
237 unsigned const center,
238 unsigned const len)
239{
240 assert( center != 0 );
241 assert( center < len - 1 );
242
243 Point ret;
244 if ( d[center + 1] == d[center - 1] ) {
245 /* Rotate 90 degrees in an arbitrary direction. */
246 Point const diff = d[center] - d[center - 1];
247 ret = rot90(diff);
248 } else {
249 ret = d[center - 1] - d[center + 1];
250 }
251 ret.normalize();
252 return ret;
253}
254
255
256int
257bezier_fit_cubic_r(Point bezier[], Point const data[], int const len, double const error, unsigned const max_beziers);
258
259int
260bezier_fit_cubic_full(Point bezier[], int split_points[],
261 Point const data[], int const len,
262 Point const &tHat1, Point const &tHat2,
263 double const error, unsigned const max_beziers);
264
265
271int
272bezier_fit_cubic(Point *bezier, Point const *data, int len, double error)
273{
274 return bezier_fit_cubic_r(bezier, data, len, error, 1);
275}
276
286int
287bezier_fit_cubic_r(Point bezier[], Point const data[], int const len, double const error, unsigned const max_beziers)
288{
289 if(bezier == NULL ||
290 data == NULL ||
291 len <= 0 ||
292 max_beziers >= (1ul << (31 - 2 - 1 - 3)))
293 return -1;
294
295 Point *uniqued_data = new Point[len];
296 unsigned uniqued_len = copy_without_nans_or_adjacent_duplicates(data, len, uniqued_data);
297
298 if ( uniqued_len < 2 ) {
299 delete[] uniqued_data;
300 return 0;
301 }
302
303 /* Call fit-cubic function with recursion. */
304 int const ret = bezier_fit_cubic_full(bezier, NULL, uniqued_data, uniqued_len,
306 error, max_beziers);
307 delete[] uniqued_data;
308 return ret;
309}
310
311
312
318static unsigned
319copy_without_nans_or_adjacent_duplicates(Point const src[], unsigned src_len, Point dest[])
320{
321 unsigned si = 0;
322 for (;;) {
323 if ( si == src_len ) {
324 return 0;
325 }
326 if (!std::isnan(src[si][X]) &&
327 !std::isnan(src[si][Y])) {
328 dest[0] = Point(src[si]);
329 ++si;
330 break;
331 }
332 si++;
333 }
334 unsigned di = 0;
335 for (; si < src_len; ++si) {
336 Point const src_pt = Point(src[si]);
337 if ( src_pt != dest[di]
338 && !std::isnan(src_pt[X])
339 && !std::isnan(src_pt[Y])) {
340 dest[++di] = src_pt;
341 }
342 }
343 unsigned dest_len = di + 1;
344 assert( dest_len <= src_len );
345 return dest_len;
346}
347
356int
357bezier_fit_cubic_full(Point bezier[], int split_points[],
358 Point const data[], int const len,
359 Point const &tHat1, Point const &tHat2,
360 double const error, unsigned const max_beziers)
361{
362 int const maxIterations = 4; /* std::max times to try iterating */
363
364 if(!(bezier != NULL) ||
365 !(data != NULL) ||
366 !(len > 0) ||
367 !(max_beziers >= 1) ||
368 !(error >= 0.0))
369 return -1;
370
371 if ( len < 2 ) return 0;
372
373 if ( len == 2 ) {
374 /* We have 2 points, which can be fitted trivially. */
375 bezier[0] = data[0];
376 bezier[3] = data[len - 1];
377 double const dist = distance(bezier[0], bezier[3]) / 3.0;
378 if (std::isnan(dist)) {
379 /* Numerical problem, fall back to straight line segment. */
380 bezier[1] = bezier[0];
381 bezier[2] = bezier[3];
382 } else {
383 bezier[1] = ( is_zero(tHat1)
384 ? ( 2 * bezier[0] + bezier[3] ) / 3.
385 : bezier[0] + dist * tHat1 );
386 bezier[2] = ( is_zero(tHat2)
387 ? ( bezier[0] + 2 * bezier[3] ) / 3.
388 : bezier[3] + dist * tHat2 );
389 }
390 BEZIER_ASSERT(bezier);
391 return 1;
392 }
393
394 /* Parameterize points, and attempt to fit curve */
395 unsigned splitPoint; /* Point to split point set at. */
396 bool is_corner;
397 {
398 double *u = new double[len];
400 if ( u[len - 1] == 0.0 ) {
401 /* Zero-length path: every point in data[] is the same.
402 *
403 * (Clients aren't allowed to pass such data; handling the case is defensive
404 * programming.)
405 */
406 delete[] u;
407 return 0;
408 }
409
410 generate_bezier(bezier, data, u, len, tHat1, tHat2, error);
411 reparameterize_pts(data, len, u, bezier);
412
413 /* Find max deviation of points to fitted curve. */
414 double const tolerance = std::sqrt(error + 1e-9);
415 double maxErrorRatio = compute_max_error_ratio(data, u, len, bezier, tolerance, &splitPoint);
416
417 if ( fabs(maxErrorRatio) <= 1.0 ) {
418 BEZIER_ASSERT(bezier);
419 delete[] u;
420 return 1;
421 }
422
423 /* If error not too large, then try some reparameterization and iteration. */
424 if ( 0.0 <= maxErrorRatio && maxErrorRatio <= 3.0 ) {
425 for (int i = 0; i < maxIterations; i++) {
426 generate_bezier(bezier, data, u, len, tHat1, tHat2, error);
427 reparameterize_pts(data, len, u, bezier);
428 maxErrorRatio = compute_max_error_ratio(data, u, len, bezier, tolerance, &splitPoint);
429 if ( fabs(maxErrorRatio) <= 1.0 ) {
430 BEZIER_ASSERT(bezier);
431 delete[] u;
432 return 1;
433 }
434 }
435 }
436 delete[] u;
437 is_corner = (maxErrorRatio < 0);
438 }
439
440 if (is_corner) {
441 assert(splitPoint < unsigned(len));
442 if (splitPoint == 0) {
443 if (is_zero(tHat1)) {
444 /* Got spike even with unconstrained initial tangent. */
445 ++splitPoint;
446 } else {
447 return bezier_fit_cubic_full(bezier, split_points, data, len, unconstrained_tangent, tHat2,
448 error, max_beziers);
449 }
450 } else if (splitPoint == unsigned(len - 1)) {
451 if (is_zero(tHat2)) {
452 /* Got spike even with unconstrained final tangent. */
453 --splitPoint;
454 } else {
455 return bezier_fit_cubic_full(bezier, split_points, data, len, tHat1, unconstrained_tangent,
456 error, max_beziers);
457 }
458 }
459 }
460
461 if ( 1 < max_beziers ) {
462 /*
463 * Fitting failed -- split at max error point and fit recursively
464 */
465 unsigned const rec_max_beziers1 = max_beziers - 1;
466
467 Point recTHat2, recTHat1;
468 if (is_corner) {
469 if(!(0 < splitPoint && splitPoint < unsigned(len - 1)))
470 return -1;
471 recTHat1 = recTHat2 = unconstrained_tangent;
472 } else {
473 /* Unit tangent vector at splitPoint. */
474 recTHat2 = darray_center_tangent(data, splitPoint, len);
475 recTHat1 = -recTHat2;
476 }
477 int const nsegs1 = bezier_fit_cubic_full(bezier, split_points, data, splitPoint + 1,
478 tHat1, recTHat2, error, rec_max_beziers1);
479 if ( nsegs1 < 0 ) {
480#ifdef BEZIER_DEBUG
481 g_print("fit_cubic[1]: recursive call failed\n");
482#endif
483 return -1;
484 }
485 assert( nsegs1 != 0 );
486 if (split_points != NULL) {
487 split_points[nsegs1 - 1] = splitPoint;
488 }
489 unsigned const rec_max_beziers2 = max_beziers - nsegs1;
490 int const nsegs2 = bezier_fit_cubic_full(bezier + nsegs1*4,
491 ( split_points == NULL
492 ? NULL
493 : split_points + nsegs1 ),
494 data + splitPoint, len - splitPoint,
495 recTHat1, tHat2, error, rec_max_beziers2);
496 if ( nsegs2 < 0 ) {
497#ifdef BEZIER_DEBUG
498 g_print("fit_cubic[2]: recursive call failed\n");
499#endif
500 return -1;
501 }
502
503#ifdef BEZIER_DEBUG
504 g_print("fit_cubic: success[nsegs: %d+%d=%d] on max_beziers:%u\n",
505 nsegs1, nsegs2, nsegs1 + nsegs2, max_beziers);
506#endif
507 return nsegs1 + nsegs2;
508 } else {
509 return -1;
510 }
511}
512
513
525static void
527 Point const data[], double const u[], unsigned const len,
528 Point const &tHat1, Point const &tHat2,
529 double const tolerance_sq)
530{
531 bool const est1 = is_zero(tHat1);
532 bool const est2 = is_zero(tHat2);
533 Point est_tHat1( est1
534 ? darray_left_tangent(data, len, tolerance_sq)
535 : tHat1 );
536 Point est_tHat2( est2
537 ? darray_right_tangent(data, len, tolerance_sq)
538 : tHat2 );
539 estimate_lengths(bezier, data, u, len, est_tHat1, est_tHat2);
540 /* We find that darray_right_tangent tends to produce better results
541 for our current freehand tool than full estimation. */
542 if (est1) {
543 estimate_bi(bezier, 1, data, u, len);
544 if (bezier[1] != bezier[0]) {
545 est_tHat1 = unit_vector(bezier[1] - bezier[0]);
546 }
547 estimate_lengths(bezier, data, u, len, est_tHat1, est_tHat2);
548 }
549}
550
551
552static void
554 Point const data[], double const uPrime[], unsigned const len,
555 Point const &tHat1, Point const &tHat2)
556{
557 double C[2][2]; /* Matrix C. */
558 double X[2]; /* Matrix X. */
559
560 /* Create the C and X matrices. */
561 C[0][0] = 0.0;
562 C[0][1] = 0.0;
563 C[1][0] = 0.0;
564 C[1][1] = 0.0;
565 X[0] = 0.0;
566 X[1] = 0.0;
567
568 /* First and last control points of the Bezier curve are positioned exactly at the first and
569 last data points. */
570 bezier[0] = data[0];
571 bezier[3] = data[len - 1];
572
573 for (unsigned i = 0; i < len; i++) {
574 /* Bezier control point coefficients. */
575 double const b0 = B0(uPrime[i]);
576 double const b1 = B1(uPrime[i]);
577 double const b2 = B2(uPrime[i]);
578 double const b3 = B3(uPrime[i]);
579
580 /* rhs for eqn */
581 Point const a1 = b1 * tHat1;
582 Point const a2 = b2 * tHat2;
583
584 C[0][0] += dot(a1, a1);
585 C[0][1] += dot(a1, a2);
586 C[1][0] = C[0][1];
587 C[1][1] += dot(a2, a2);
588
589 /* Additional offset to the data point from the predicted point if we were to set bezier[1]
590 to bezier[0] and bezier[2] to bezier[3]. */
591 Point const shortfall
592 = ( data[i]
593 - ( ( b0 + b1 ) * bezier[0] )
594 - ( ( b2 + b3 ) * bezier[3] ) );
595 X[0] += dot(a1, shortfall);
596 X[1] += dot(a2, shortfall);
597 }
598
599 /* We've constructed a pair of equations in the form of a matrix product C * alpha = X.
600 Now solve for alpha. */
601 double alpha_l, alpha_r;
602
603 /* Compute the determinants of C and X. */
604 double const det_C0_C1 = C[0][0] * C[1][1] - C[1][0] * C[0][1];
605 if ( det_C0_C1 != 0 ) {
606 /* Apparently Kramer's rule. */
607 double const det_C0_X = C[0][0] * X[1] - C[0][1] * X[0];
608 double const det_X_C1 = X[0] * C[1][1] - X[1] * C[0][1];
609 alpha_l = det_X_C1 / det_C0_C1;
610 alpha_r = det_C0_X / det_C0_C1;
611 } else {
612 /* The matrix is under-determined. Try requiring alpha_l == alpha_r.
613 *
614 * One way of implementing the constraint alpha_l == alpha_r is to treat them as the same
615 * variable in the equations. We can do this by adding the columns of C to form a single
616 * column, to be multiplied by alpha to give the column vector X.
617 *
618 * We try each row in turn.
619 */
620 double const c0 = C[0][0] + C[0][1];
621 if (c0 != 0) {
622 alpha_l = alpha_r = X[0] / c0;
623 } else {
624 double const c1 = C[1][0] + C[1][1];
625 if (c1 != 0) {
626 alpha_l = alpha_r = X[1] / c1;
627 } else {
628 /* Let the below code handle this. */
629 alpha_l = alpha_r = 0.;
630 }
631 }
632 }
633
634 /* If alpha negative, use the Wu/Barsky heuristic (see text). (If alpha is 0, you get
635 coincident control points that lead to divide by zero in any subsequent
636 NewtonRaphsonRootFind() call.) */
639 if ( alpha_l < 1.0e-6 ||
640 alpha_r < 1.0e-6 )
641 {
642 alpha_l = alpha_r = distance(data[0], data[len-1]) / 3.0;
643 }
644
645 /* Control points 1 and 2 are positioned an alpha distance out on the tangent vectors, left and
646 right, respectively. */
647 bezier[1] = alpha_l * tHat1 + bezier[0];
648 bezier[2] = alpha_r * tHat2 + bezier[3];
649
650 return;
651}
652
653static double lensq(Point const p) {
654 return dot(p, p);
655}
656
657static void
658estimate_bi(Point bezier[4], unsigned const ei,
659 Point const data[], double const u[], unsigned const len)
660{
661 if(!(1 <= ei && ei <= 2))
662 return;
663 unsigned const oi = 3 - ei;
664 double num[2] = {0., 0.};
665 double den = 0.;
666 for (unsigned i = 0; i < len; ++i) {
667 double const ui = u[i];
668 double const b[4] = {
669 B0(ui),
670 B1(ui),
671 B2(ui),
672 B3(ui)
673 };
674
675 for (unsigned d = 0; d < 2; ++d) {
676 num[d] += b[ei] * (b[0] * bezier[0][d] +
677 b[oi] * bezier[oi][d] +
678 b[3] * bezier[3][d] +
679 - data[i][d]);
680 }
681 den -= b[ei] * b[ei];
682 }
683
684 if (den != 0.) {
685 for (unsigned d = 0; d < 2; ++d) {
686 bezier[ei][d] = num[d] / den;
687 }
688 } else {
689 bezier[ei] = ( oi * bezier[0] + ei * bezier[3] ) / 3.;
690 }
691}
692
703static void
705 unsigned const len,
706 double u[],
707 CubicBezier const &bezCurve)
708{
709 assert( 2 <= len );
710
711 unsigned const last = len - 1;
712 assert( bezCurve[0] == d[0] );
713 assert( bezCurve[3] == d[last] );
714 assert( u[0] == 0.0 );
715 assert( u[last] == 1.0 );
716 /* Otherwise, consider including 0 and last in the below loop. */
717
718 for (unsigned i = 1; i < last; i++) {
719 u[i] = NewtonRaphsonRootFind(bezCurve, d[i], u[i]);
720 }
721}
722
732static double
733NewtonRaphsonRootFind(CubicBezier const &Q, Point const &P, double const u)
734{
735 assert( 0.0 <= u );
736 assert( u <= 1.0 );
737
738 std::vector<Point> Q_u = Q.pointAndDerivatives(u, 2);
739
740 /* Compute f(u)/f'(u), where f is the derivative wrt u of distsq(u) = 0.5 * the square of the
741 distance from P to Q(u). Here we're using Newton-Raphson to find a stationary point in the
742 distsq(u), hopefully corresponding to a local minimum in distsq (and hence a local minimum
743 distance from P to Q(u)). */
744 Point const diff = Q_u[0] - P;
745 double numerator = dot(diff, Q_u[1]);
746 double denominator = dot(Q_u[1], Q_u[1]) + dot(diff, Q_u[2]);
747
748 double improved_u;
749 if ( denominator > 0. ) {
750 /* One iteration of Newton-Raphson:
751 improved_u = u - f(u)/f'(u) */
752 improved_u = u - ( numerator / denominator );
753 } else {
754 /* Using Newton-Raphson would move in the wrong direction (towards a local maximum rather
755 than local minimum), so we move an arbitrary amount in the right direction. */
756 if ( numerator > 0. ) {
757 improved_u = u * .98 - .01;
758 } else if ( numerator < 0. ) {
759 /* Deliberately asymmetrical, to reduce the chance of cycling. */
760 improved_u = .031 + u * .98;
761 } else {
762 improved_u = u;
763 }
764 }
765
766 if (!std::isfinite(improved_u)) {
767 improved_u = u;
768 } else if ( improved_u < 0.0 ) {
769 improved_u = 0.0;
770 } else if ( improved_u > 1.0 ) {
771 improved_u = 1.0;
772 }
773
774 /* Ensure that improved_u isn't actually worse. */
775 {
776 double const diff_lensq = lensq(diff);
777 for (double proportion = .125; ; proportion += .125) {
778 if ( lensq( Q.pointAt(improved_u) - P ) > diff_lensq ) {
779 if ( proportion > 1.0 ) {
780 //g_warning("found proportion %g", proportion);
781 improved_u = u;
782 break;
783 }
784 improved_u = ( ( 1 - proportion ) * improved_u +
785 proportion * u );
786 } else {
787 break;
788 }
789 }
790 }
791
792 DOUBLE_ASSERT(improved_u);
793 return improved_u;
794}
795
796
802static void
803chord_length_parameterize(Point const d[], double u[], unsigned const len)
804{
805 if(!( 2 <= len ))
806 return;
807
808 /* First let u[i] equal the distance travelled along the path from d[0] to d[i]. */
809 u[0] = 0.0;
810 for (unsigned i = 1; i < len; i++) {
811 double const dist = distance(d[i], d[i-1]);
812 u[i] = u[i-1] + dist;
813 }
814
815 /* Then scale to [0.0 .. 1.0]. */
816 double tot_len = u[len - 1];
817 if(!( tot_len != 0 ))
818 return;
819 if (std::isfinite(tot_len)) {
820 for (unsigned i = 1; i < len; ++i) {
821 u[i] /= tot_len;
822 }
823 } else {
824 /* We could do better, but this probably never happens anyway. */
825 for (unsigned i = 1; i < len; ++i) {
826 u[i] = i / (double) ( len - 1 );
827 }
828 }
829
835 if (u[len - 1] != 1) {
836 double const diff = u[len - 1] - 1;
837 if (fabs(diff) > 1e-13) {
838 assert(0); // No warnings in 2geom
839 //g_warning("u[len - 1] = %19g (= 1 + %19g), expecting exactly 1",
840 // u[len - 1], diff);
841 }
842 u[len - 1] = 1;
843 }
844
845#ifdef BEZIER_DEBUG
846 assert( u[0] == 0.0 && u[len - 1] == 1.0 );
847 for (unsigned i = 1; i < len; i++) {
848 assert( u[i] >= u[i-1] );
849 }
850#endif
851}
852
853
854
855
867static double
868compute_max_error_ratio(Point const d[], double const u[], unsigned const len,
869 BezierCurve const bezCurve, double const tolerance,
870 unsigned *const splitPoint)
871{
872 assert( 2 <= len );
873 unsigned const last = len - 1;
874 assert( bezCurve[0] == d[0] );
875 assert( bezCurve[3] == d[last] );
876 assert( u[0] == 0.0 );
877 assert( u[last] == 1.0 );
878 /* I.e. assert that the error for the first & last points is zero.
879 * Otherwise we should include those points in the below loop.
880 * The assertion is also necessary to ensure 0 < splitPoint < last.
881 */
882
883 double maxDistsq = 0.0; /* Maximum error */
884 double max_hook_ratio = 0.0;
885 unsigned snap_end = 0;
886 Point prev = bezCurve[0];
887 for (unsigned i = 1; i <= last; i++) {
888 Point const curr = bezier_pt(3, bezCurve, u[i]);
889 double const distsq = lensq( curr - d[i] );
890 if ( distsq > maxDistsq ) {
891 maxDistsq = distsq;
892 *splitPoint = i;
893 }
894 double const hook_ratio = compute_hook(prev, curr, .5 * (u[i - 1] + u[i]), bezCurve, tolerance);
895 if (max_hook_ratio < hook_ratio) {
896 max_hook_ratio = hook_ratio;
897 snap_end = i;
898 }
899 prev = curr;
900 }
901
902 double const dist_ratio = std::sqrt(maxDistsq) / tolerance;
903 double ret;
904 if (max_hook_ratio <= dist_ratio) {
905 ret = dist_ratio;
906 } else {
907 assert(0 < snap_end);
908 ret = -max_hook_ratio;
909 *splitPoint = snap_end - 1;
910 }
911 assert( ret == 0.0
912 || ( ( *splitPoint < last )
913 && ( *splitPoint != 0 || ret < 0. ) ) );
914 return ret;
915}
916
938static double
939compute_hook(Point const &a, Point const &b, double const u, CubicBezier const & bezCurve,
940 double const tolerance)
941{
942 Point const P = bezCurve.pointAt(u);
943 double const dist = distance((a+b)*.5, P);
944 if (dist < tolerance) {
945 return 0;
946 }
947 double const allowed = distance(a, b) + tolerance;
948 return dist / allowed;
954}
955
956}
957
958}
959
960#include <2geom/bezier-utils.h>
961
962
963using std::vector;
964using namespace Geom;
965using namespace std;
966
967class PointToBezierTester: public Toy {
968 //std::vector<Slider> sliders;
969 PointHandle adjuster, adjuster2, adjuster3;
970 std::vector<Toggle> toggles;
972
973 void draw(cairo_t *cr, std::ostringstream *notify, int width, int height, bool save, std::ostringstream *timer_stream) override {
974 cairo_save(cr);
975
976 cairo_set_source_rgba (cr, 0., 0., 0., 1);
977 cairo_set_line_width (cr, 0.5);
978 adjuster2.pos[0]=150;
979 adjuster2.pos[1]=std::min(std::max(adjuster2.pos[1],150.),450.);
980 cairo_move_to(cr, 150, 150);
981 cairo_line_to(cr, 150, 450);
982 cairo_stroke(cr);
983 ostringstream val_s;
984 double scale0=(450-adjuster2.pos[1])/300;
985 double curve_precision = pow(10, scale0*5-2);
986 val_s << curve_precision;
987 draw_text(cr, adjuster2.pos, val_s.str().c_str());
988 cairo_restore(cr);
989
990 cairo_save(cr);
991
992 cairo_set_source_rgba (cr, 0., 0., 0., 1);
993 cairo_set_line_width (cr, 0.5);
994
995 if(!mouses.empty()) {
996 cairo_move_to(cr, mouses[0]);
997 for(auto & mouse : mouses) {
998 cairo_line_to(cr, mouse);
999 }
1000 cairo_stroke(cr);
1001 }
1002
1003 if(!mouses.empty()) {
1004 Point bezier[1000];
1005 int segsgenerated;
1006 {
1007 Timer tm;
1008
1009 tm.ask_for_timeslice();
1010 tm.start();
1011 segsgenerated = bezier_fit_cubic_r(bezier, &mouses[0],
1012 mouses.size(), curve_precision, 240);
1013
1014 Timer::Time als_time = tm.lap();
1015 *notify << "original time = " << als_time << std::endl;
1016 }
1017
1018 if(1) {
1019 cairo_save(cr);
1020 cairo_set_source_rgba (cr, 0., 1., 0., 1);
1021 cairo_move_to(cr, bezier[0]);
1022 int bezi=1;
1023 for(int i = 0; i < segsgenerated; i ++) {
1024 cairo_curve_to(cr, bezier[bezi], bezier[bezi+1], bezier[bezi+2]);
1025 bezi += 4;
1026 }
1027 cairo_stroke(cr);
1028 cairo_restore(cr);
1029
1030 }
1031 {
1032 Timer tm;
1033
1034 tm.ask_for_timeslice();
1035 tm.start();
1036 segsgenerated = Geom::BezierFitter::bezier_fit_cubic_r(bezier, &mouses[0],
1037 mouses.size(), curve_precision, 240);
1038
1039 Timer::Time als_time = tm.lap();
1040 *notify << "experimental version time = " << als_time << std::endl;
1041 }
1042
1043 if (1) {
1044 cairo_save(cr);
1045 cairo_set_source_rgba (cr, 0., 0., 0., 1);
1046 cairo_move_to(cr, bezier[0]);
1047 int bezi=1;
1048 for(int i = 0; i < segsgenerated; i ++) {
1049 cairo_curve_to(cr, bezier[bezi], bezier[bezi+1], bezier[bezi+2]);
1050 bezi += 4;
1051 }
1052 cairo_stroke(cr);
1053 cairo_restore(cr);
1054 }
1055 *notify << "segments : "<< segsgenerated <<"\n";
1056 }
1057 cairo_restore(cr);
1058
1059 /*
1060 Point p(25, height - 50), d(50,25);
1061 toggles[0].bounds = Rect(p, p + d);
1062 p+= Point(75, 0);
1063 toggles[1].bounds = Rect(p, p + d);
1064 draw_toggles(cr, toggles);
1065 */
1066 Toy::draw(cr, notify, width, height, save,timer_stream);
1067 }
1068
1069public:
1070 void key_hit(unsigned keyval, unsigned modifiers) override {
1071 if(keyval == 's') toggles[0].toggle();
1072 redraw();
1073 }
1074 vector<Point> mouses;
1075 int mouse_drag;
1076
1077 void mouse_pressed(Geom::Point const &pos, unsigned button, unsigned modifiers) override {
1078 toggle_events(toggles, pos, button);
1079 Toy::mouse_pressed(pos, button, modifiers);
1080 if(!selected) {
1081 mouse_drag = 1;
1082 mouses.clear();
1083 }
1084 }
1085
1086
1087 void mouse_moved(Geom::Point const &pos, unsigned modifiers) override {
1088 if(mouse_drag) {
1089 mouses.emplace_back(pos);
1090 redraw();
1091 } else {
1092 Toy::mouse_moved(pos, modifiers);
1093 }
1094 }
1095
1096 void mouse_released(Geom::Point const &pos, unsigned button, unsigned modifiers) override {
1097 mouse_drag = 0;
1098 stroke.clear();
1099 stroke.push_cut(0);
1100 Path pth;
1101 for(unsigned i = 2; i < mouses.size(); i+=2) {
1102 pth.append(QuadraticBezier(mouses[i-2], mouses[i-1], mouses[i]));
1103 }
1104 stroke = pth.toPwSb();
1105 Toy::mouse_released(pos, button, modifiers);
1106 }
1107
1108 PointToBezierTester() {
1109 adjuster2.pos = Geom::Point(150,300);
1110 handles.push_back(&adjuster2);
1111 toggles.emplace_back("Seq", false);
1112 toggles.emplace_back("Linfty", true);
1113 //}
1114 //sliders.push_back(Slider(0.0, 1.0, 0.0, 0.0, "t"));
1115 //handles.push_back(&(sliders[0]));
1116 }
1117};
1118
1119int main(int argc, char **argv) {
1120 init(argc, argv, new PointToBezierTester);
1121 return 0;
1122}
1123
1124/*
1125 Local Variables:
1126 mode:c++
1127 c-file-style:"stroustrup"
1128 c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
1129 indent-tabs-mode:nil
1130 fill-column:99
1131 End:
1132*/
1133// vim: filetype = cpp:expandtab:shiftwidth = 4:tabstop = 8:softtabstop = 4:encoding = utf-8:textwidth = 99 :
Basic intersection routines.
int main()
Bezier fitting algorithms.
Bezier curve with compile-time specified order.
Two-dimensional Bezier curve of arbitrary order.
Point pointAt(Coord t) const override
Evaluate the curve at a specified time value.
std::vector< Point > pointAndDerivatives(Coord t, unsigned n) const override
Evaluate the curve and its derivatives.
Sequence of contiguous curves, aka spline.
Definition path.h:353
Piecewise< D2< SBasis > > toPwSb() const
Definition path.cpp:388
void append(Curve *curve)
Add a new curve to the end of the path.
Definition path.h:750
Function defined as discrete pieces.
Definition piecewise.h:71
Two-dimensional point that doubles as a vector.
Definition point.h:66
void normalize()
Normalize the vector representing the point.
Definition point.cpp:96
Geom::Point pos
void ask_for_timeslice()
Ask the OS nicely for a big time slice.
void start()
void lap(long long &ns)
virtual void mouse_pressed(Geom::Point const &pos, unsigned button, unsigned modifiers)
virtual void mouse_moved(Geom::Point const &pos, unsigned modifiers)
vector< Handle * > handles
Handle * selected
virtual void mouse_released(Geom::Point const &pos, unsigned button, unsigned modifiers)
virtual void save(FILE *f)
virtual void key_hit(unsigned keyval, unsigned modifiers)
virtual void draw(cairo_t *cr, std::ostringstream *notify, int w, int h, bool save, std::ostringstream *timing_stream)
Colors::Color stroke
Lifts one dimensional objects into 2D.
BezierCurveN< 2 > QuadraticBezier
Quadratic (order 2) Bezier curve.
@ Y
Definition coord.h:48
@ X
Definition coord.h:48
T pow(T const &t, int n)
Integer exponentiation for transforms.
Definition transforms.h:98
Low level math functions and compatibility wrappers.
static void reparameterize(Point const d[], unsigned len, double u[], CubicBezier const &bezCurve)
Given set of points and their parameterization, try to find a better assignment of parameter values f...
Definition pencil-2.cpp:704
static Point const unconstrained_tangent(0, 0)
int bezier_fit_cubic_full(Point bezier[], int split_points[], Point const data[], int const len, Point const &tHat1, Point const &tHat2, double const error, unsigned const max_beziers)
Fit a multi-segment Bezier curve to a set of digitized points, without possible weedout of identical ...
Definition pencil-2.cpp:357
static void generate_bezier(Point b[], Point const d[], double const u[], unsigned len, Point const &tHat1, Point const &tHat2, double tolerance_sq)
Fill in bezier[] based on the given data and tangent requirements, using a least-squares fit.
Definition pencil-2.cpp:526
Point bezier_pt(unsigned const degree, Point const V[], double const t)
Definition pencil-2.cpp:116
int bezier_fit_cubic(Point *bezier, Point const *data, int len, double error)
Fit a single-segment Bezier curve to a set of digitized points.
Definition pencil-2.cpp:272
int bezier_fit_cubic_r(Point bezier[], Point const data[], int const len, double const error, unsigned const max_beziers)
Fit a multi-segment Bezier curve to a set of digitized points, with possible weedout of identical poi...
Definition pencil-2.cpp:287
static Point darray_right_tangent(Point const d[], unsigned const len)
Estimates the (backward) tangent at d[last - 0.5].
Definition pencil-2.cpp:153
static void estimate_lengths(Point bezier[], Point const data[], double const u[], unsigned len, Point const &tHat1, Point const &tHat2)
Definition pencil-2.cpp:553
static void estimate_bi(Point b[4], unsigned ei, Point const data[], double const u[], unsigned len)
Definition pencil-2.cpp:658
static void reparameterize_pts(Point const d[], unsigned len, double u[], BezierCurve const bezCurve)
Definition pencil-2.cpp:82
static double lensq(Point const p)
Definition pencil-2.cpp:653
static double NewtonRaphsonRootFind(CubicBezier const &Q, Point const &P, double u)
Use Newton-Raphson iteration to find better root.
Definition pencil-2.cpp:733
static double compute_hook(Point const &a, Point const &b, double const u, CubicBezier const &bezCurve, double const tolerance)
Whereas compute_max_error_ratio() checks for itself that each data point is near some point on the cu...
Definition pencil-2.cpp:939
static void chord_length_parameterize(Point const d[], double u[], unsigned len)
Assign parameter values to digitized points using relative distances between points.
Definition pencil-2.cpp:803
static double compute_max_error_ratio(Point const d[], double const u[], unsigned len, BezierCurve const bezCurve, double tolerance, unsigned *splitPoint)
Find the maximum squared distance of digitized points to fitted curve, and (if this maximum error is ...
Definition pencil-2.cpp:868
Point darray_left_tangent(Point const d[], unsigned const len)
Estimate the (forward) tangent at point d[first + 0.5].
Definition pencil-2.cpp:135
static Point darray_center_tangent(Point const d[], unsigned center, unsigned length)
Estimates the (backward) tangent at d[center], by averaging the two segments connected to d[center] (...
Definition pencil-2.cpp:236
static unsigned copy_without_nans_or_adjacent_duplicates(Point const src[], unsigned src_len, Point dest[])
Copy points from src to dest, filter out points containing NaN and adjacent points with equal x and y...
Definition pencil-2.cpp:319
Various utility functions.
Definition affine.h:22
Coord length(LineSegment const &seg)
Angle distance(Angle const &a, Angle const &b)
Definition angle.h:163
bool is_zero(Point const &p)
int bezier_fit_cubic_r(Point bezier[], Point const data[], int len, double error, unsigned max_beziers)
Fit a multi-segment Bezier curve to a set of digitized points, with possible weedout of identical poi...
T bernstein_value_at(double t, T const *c_, unsigned n)
Compute the value of a Bernstein-Bezier polynomial.
Definition bezier.h:55
T dot(D2< T > const &a, D2< T > const &b)
Definition d2.h:355
Point unit_vector(Point const &a)
D2< T > rot90(D2< T > const &a)
Definition d2.h:397
STL namespace.
void cairo_line_to(cairo_t *cr, Geom::Point p1)
struct _cairo cairo_t
Definition path-cairo.h:16
void cairo_move_to(cairo_t *cr, Geom::Point p1)
void cairo_curve_to(cairo_t *cr, Geom::Point p1, Geom::Point p2, Geom::Point p3)
auto len
Definition safe-printf.h:21
two-dimensional geometric operators.
Polynomial in symmetric power basis (S-basis)
int num
Definition scribble.cpp:47
size_t degree
static const Point data[]
double height
double width
void draw_text(cairo_t *cr, Geom::Point pos, const char *txt, bool bottom=false, const char *fontdesc="Sans")
void toggle_events(std::vector< Toggle > &ts, Geom::Point const &pos, unsigned button)
void cairo_set_source_rgba(cairo_t *cr, colour c)
void redraw()
void init(int argc, char **argv, Toy *t, int width=600, int height=600)