Inkscape
Vector Graphics Editor
Loading...
Searching...
No Matches
bezier-utils-test.cpp
Go to the documentation of this file.
1#include "utest.h"
2#include <glib.h>
3
4/* MenTaLguY disclaims all responsibility for this evil idea for testing
5 static functions. The main disadvantages are that we retain the
6 #define's and `using' directives of the included file. */
7#include "../bezier-utils.cpp"
8
9using Geom::Point;
10
11static bool range_approx_equal(double const a[], double const b[], unsigned len);
12
13/* (Returns false if NaN encountered.) */
14template<class T>
15static bool range_equal(T const a[], T const b[], unsigned len) {
16 for (unsigned i = 0; i < len; ++i) {
17 if ( a[i] != b[i] ) {
18 return false;
19 }
20 }
21 return true;
22}
23
24inline bool point_approx_equal(Geom::Point const &a, Geom::Point const &b, double const eps)
25{
26 using Geom::X; using Geom::Y;
27 return ( Geom_DF_TEST_CLOSE(a[X], b[X], eps) &&
28 Geom_DF_TEST_CLOSE(a[Y], b[Y], eps) );
29}
30
31static inline double square(double const x) {
32 return x * x;
33}
34
40static void compare_ctlpts(Point const est_b[], Point const exp_est_b[])
41{
42 unsigned diff_mask = 0;
43 for (unsigned i = 0; i < 4; ++i) {
44 for (unsigned d = 0; d < 2; ++d) {
45 if ( fabs( est_b[i][d] - exp_est_b[i][d] ) > 1.1e-5 ) {
46 diff_mask |= 1 << ( i * 2 + d );
47 }
48 }
49 }
50 if ( diff_mask != 0 ) {
51 printf("Warning: got different control points from previously-coded (diffs=0x%x).\n",
52 diff_mask);
53 printf(" Previous:");
54 for (unsigned i = 0; i < 4; ++i) {
55 printf(" (%g, %g)", exp_est_b[i][0], exp_est_b[i][1]); // localizing ok
56 }
57 putchar('\n');
58 printf(" Found: ");
59 for (unsigned i = 0; i < 4; ++i) {
60 printf(" (%g, %g)", est_b[i][0], est_b[i][1]); // localizing ok
61 }
62 putchar('\n');
63 }
64}
65
66static void compare_rms(Point const est_b[], double const t[], Point const d[], unsigned const n,
67 double const exp_rms_error)
68{
69 double sum_errsq = 0.0;
70 for (unsigned i = 0; i < n; ++i) {
71 Point const fit_pt = bezier_pt(3, est_b, t[i]);
72 Point const diff = fit_pt - d[i];
73 sum_errsq += dot(diff, diff);
74 }
75 double const rms_error = sqrt( sum_errsq / n );
76 UTEST_ASSERT( rms_error <= exp_rms_error + 1.1e-6 );
77 if ( rms_error < exp_rms_error - 1.1e-6 ) {
78 /* The fitter code appears to have improved [or the floating point calculations differ
79 on this machine from the machine where exp_rms_error was calculated]. */
80 printf("N.B. rms_error regression requirement can be decreased: have rms_error=%g.\n", rms_error); // localizing ok
81 }
82}
83
84int main(int argc, char *argv[]) {
85 utest_start("bezier-utils.cpp");
86
87 UTEST_TEST("copy_without_nans_or_adjacent_duplicates") {
88 Geom::Point const src[] = {
89 Point(2., 3.),
90 Point(2., 3.),
91 Point(0., 0.),
92 Point(2., 3.),
93 Point(2., 3.),
94 Point(1., 9.),
95 Point(1., 9.)
96 };
97 Point const exp_dest[] = {
98 Point(2., 3.),
99 Point(0., 0.),
100 Point(2., 3.),
101 Point(1., 9.)
102 };
103 g_assert( G_N_ELEMENTS(src) == 7 );
104 Point dest[7];
105 struct tst {
106 unsigned src_ix0;
107 unsigned src_len;
108 unsigned exp_dest_ix0;
109 unsigned exp_dest_len;
110 } const test_data[] = {
111 /* src start ix, src len, exp_dest start ix, exp dest len */
112 {0, 0, 0, 0},
113 {2, 1, 1, 1},
114 {0, 1, 0, 1},
115 {0, 2, 0, 1},
116 {0, 3, 0, 2},
117 {1, 3, 0, 3},
118 {0, 5, 0, 3},
119 {0, 6, 0, 4},
120 {0, 7, 0, 4}
121 };
122 for (unsigned i = 0 ; i < G_N_ELEMENTS(test_data) ; ++i) {
123 tst const &t = test_data[i];
124 UTEST_ASSERT( t.exp_dest_len
125 == copy_without_nans_or_adjacent_duplicates(src + t.src_ix0,
126 t.src_len,
127 dest) );
128 UTEST_ASSERT(range_equal(dest,
129 exp_dest + t.exp_dest_ix0,
130 t.exp_dest_len));
131 }
132 }
133
134 UTEST_TEST("bezier_pt(1)") {
135 Point const a[] = {Point(2.0, 4.0),
136 Point(1.0, 8.0)};
137 UTEST_ASSERT( bezier_pt(1, a, 0.0) == a[0] );
138 UTEST_ASSERT( bezier_pt(1, a, 1.0) == a[1] );
139 UTEST_ASSERT( bezier_pt(1, a, 0.5) == Point(1.5, 6.0) );
140 double const t[] = {0.5, 0.25, 0.3, 0.6};
141 for (unsigned i = 0; i < G_N_ELEMENTS(t); ++i) {
142 double const ti = t[i], si = 1.0 - ti;
143 UTEST_ASSERT( bezier_pt(1, a, ti) == si * a[0] + ti * a[1] );
144 }
145 }
146
147 UTEST_TEST("bezier_pt(2)") {
148 Point const b[] = {Point(1.0, 2.0),
149 Point(8.0, 4.0),
150 Point(3.0, 1.0)};
151 UTEST_ASSERT( bezier_pt(2, b, 0.0) == b[0] );
152 UTEST_ASSERT( bezier_pt(2, b, 1.0) == b[2] );
153 UTEST_ASSERT( bezier_pt(2, b, 0.5) == Point(5.0, 2.75) );
154 double const t[] = {0.5, 0.25, 0.3, 0.6};
155 for (unsigned i = 0; i < G_N_ELEMENTS(t); ++i) {
156 double const ti = t[i], si = 1.0 - ti;
157 Point const exp_pt( si*si * b[0] + 2*si*ti * b[1] + ti*ti * b[2] );
158 Point const pt(bezier_pt(2, b, ti));
159 UTEST_ASSERT(point_approx_equal(pt, exp_pt, 1e-11));
160 }
161 }
162
163 Point const c[] = {Point(1.0, 2.0),
164 Point(8.0, 4.0),
165 Point(3.0, 1.0),
166 Point(-2.0, -4.0)};
167 UTEST_TEST("bezier_pt(3)") {
168 UTEST_ASSERT( bezier_pt(3, c, 0.0) == c[0] );
169 UTEST_ASSERT( bezier_pt(3, c, 1.0) == c[3] );
170 UTEST_ASSERT( bezier_pt(3, c, 0.5) == Point(4.0, 13.0/8.0) );
171 double const t[] = {0.5, 0.25, 0.3, 0.6};
172 for (unsigned i = 0; i < G_N_ELEMENTS(t); ++i) {
173 double const ti = t[i], si = 1.0 - ti;
174 UTEST_ASSERT( LInfty( bezier_pt(3, c, ti)
175 - ( si*si*si * c[0] +
176 3*si*si*ti * c[1] +
177 3*si*ti*ti * c[2] +
178 ti*ti*ti * c[3] ) )
179 < 1e-4 );
180 }
181 }
182
183 struct Err_tst {
184 Point pt;
185 double u;
186 double err;
187 } const err_tst[] = {
188 {c[0], 0.0, 0.0},
189 {Point(4.0, 13.0/8.0), 0.5, 0.0},
190 {Point(4.0, 2.0), 0.5, 9.0/64.0},
191 {Point(3.0, 2.0), 0.5, 1.0 + 9.0/64.0},
192 {Point(6.0, 2.0), 0.5, 4.0 + 9.0/64.0},
193 {c[3], 1.0, 0.0},
194 };
195
196 UTEST_TEST("compute_max_error_ratio") {
197 Point d[G_N_ELEMENTS(err_tst)];
198 double u[G_N_ELEMENTS(err_tst)];
199 for (unsigned i = 0; i < G_N_ELEMENTS(err_tst); ++i) {
200 Err_tst const &t = err_tst[i];
201 d[i] = t.pt;
202 u[i] = t.u;
203 }
204 g_assert( G_N_ELEMENTS(u) == G_N_ELEMENTS(d) );
205 unsigned max_ix = ~0u;
206 double const err_ratio = compute_max_error_ratio(d, u, G_N_ELEMENTS(d), c, 1.0, &max_ix);
207 UTEST_ASSERT( fabs( sqrt(err_tst[4].err) - err_ratio ) < 1e-12 );
208 UTEST_ASSERT( max_ix == 4 );
209 }
210
211 UTEST_TEST("chord_length_parameterize") {
212 /* n == 2 */
213 {
214 Point const d[] = {Point(2.9415, -5.8149),
215 Point(23.021, 4.9814)};
216 double u[G_N_ELEMENTS(d)];
217 double const exp_u[] = {0.0, 1.0};
218 g_assert( G_N_ELEMENTS(u) == G_N_ELEMENTS(exp_u) );
219 chord_length_parameterize(d, u, G_N_ELEMENTS(d));
220 UTEST_ASSERT(range_equal(u, exp_u, G_N_ELEMENTS(exp_u)));
221 }
222
223 /* Straight line. */
224 {
225 double const exp_u[] = {0.0, 0.1829, 0.2105, 0.2105, 0.619, 0.815, 0.999, 1.0};
226 unsigned const n = G_N_ELEMENTS(exp_u);
227 Point d[n];
228 double u[n];
229 Point const a(-23.985, 4.915), b(4.9127, 5.203);
230 for (unsigned i = 0; i < n; ++i) {
231 double bi = exp_u[i], ai = 1.0 - bi;
232 d[i] = ai * a + bi * b;
233 }
234 chord_length_parameterize(d, u, n);
235 UTEST_ASSERT(range_approx_equal(u, exp_u, n));
236 }
237 }
238
239 /* Feed it some points that can be fit exactly with a single bezier segment, and see how
240 well it manages. */
241 Point const src_b[4] = {Point(5., -3.),
242 Point(8., 0.),
243 Point(4., 2.),
244 Point(3., 3.)};
245 double const t[] = {0.0, .001, .03, .05, .09, .13, .18, .25, .29, .33, .39, .44,
246 .51, .57, .62, .69, .75, .81, .91, .93, .97, .98, .999, 1.0};
247 unsigned const n = G_N_ELEMENTS(t);
248 Point d[n];
249 for (unsigned i = 0; i < n; ++i) {
250 d[i] = bezier_pt(3, src_b, t[i]);
251 }
252 Point const tHat1(unit_vector( src_b[1] - src_b[0] ));
253 Point const tHat2(unit_vector( src_b[2] - src_b[3] ));
254
255 UTEST_TEST("generate_bezier") {
256 Point est_b[4];
257 generate_bezier(est_b, d, t, n, tHat1, tHat2, 1.0);
258
259 compare_ctlpts(est_b, src_b);
260
261 /* We're being unfair here in using our t[] rather than best t[] for est_b: we
262 may over-estimate RMS of errors. */
263 compare_rms(est_b, t, d, n, 1e-8);
264 }
265
266 UTEST_TEST("sp_bezier_fit_cubic_full") {
267 Point est_b[4];
268 int splitpoints[2];
269 gint const succ = sp_bezier_fit_cubic_full(est_b, splitpoints, d, n, tHat1, tHat2, square(1.2), 1);
270 UTEST_ASSERT( succ == 1 );
271
272 Point const exp_est_b[4] = {
273 Point(5.000000, -3.000000),
274 Point(7.5753, -0.4247),
275 Point(4.77533, 1.22467),
276 Point(3, 3)
277 };
278 compare_ctlpts(est_b, exp_est_b);
279
280 /* We're being unfair here in using our t[] rather than best t[] for est_b: we
281 may over-estimate RMS of errors. */
282 compare_rms(est_b, t, d, n, .307911);
283 }
284
285 UTEST_TEST("sp_bezier_fit_cubic") {
286 Point est_b[4];
287 gint const succ = sp_bezier_fit_cubic(est_b, d, n, square(1.2));
288 UTEST_ASSERT( succ == 1 );
289
290 Point const exp_est_b[4] = {
291 Point(5.000000, -3.000000),
292 Point(7.57134, -0.423509),
293 Point(4.77929, 1.22426),
294 Point(3, 3)
295 };
296 compare_ctlpts(est_b, exp_est_b);
297
298#if 1 /* A change has been made to right_tangent. I believe that usually this change
299 will result in better fitting, but it won't do as well for this example where
300 we happen to be feeding a t=0.999 point to the fitter. */
301 printf("TODO: Update this test case for revised right_tangent implementation.\n");
302 /* In particular, have a test case to show whether the new implementation
303 really is likely to be better on average. */
304#else
305 /* We're being unfair here in using our t[] rather than best t[] for est_b: we
306 may over-estimate RMS of errors. */
307 compare_rms(est_b, t, d, n, .307983);
308#endif
309 }
310
311 return !utest_end();
312}
313
314/* (Returns false if NaN encountered.) */
315static bool range_approx_equal(double const a[], double const b[], unsigned const len) {
316 for (unsigned i = 0; i < len; ++i) {
317 if (!( fabs( a[i] - b[i] ) < 1e-4 )) {
318 return false;
319 }
320 }
321 return true;
322}
323
324/*
325 Local Variables:
326 mode:c++
327 c-file-style:"stroustrup"
328 c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
329 indent-tabs-mode:nil
330 fill-column:99
331 End:
332*/
333// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:fileencoding=utf-8:textwidth=99 :
int main()
Two-dimensional point that doubles as a vector.
Definition point.h:66
double c[8][4]
@ Y
Definition coord.h:48
@ X
Definition coord.h:48
auto len
Definition safe-printf.h:21
static void compare_rms(Point const est_b[], double const t[], Point const d[], unsigned const n, double const exp_rms_error)
bool point_approx_equal(Geom::Point const &a, Geom::Point const &b, double const eps)
static void compare_ctlpts(Point const est_b[], Point const exp_est_b[])
Determine whether the found control points are the same as previously found on some developer's machi...
static double square(double const x)
static bool range_equal(T const a[], T const b[], unsigned len)
static bool range_approx_equal(double const a[], double const b[], unsigned len)
void dot(Cairo::RefPtr< Cairo::Context > &cr, double x, double y)
int utest_end(void)
Ends a series of tests, reporting test statistics.
Definition utest.h:107
void utest_start(const char *name)
Initializes the framework for running a series of tests.
Definition utest.h:27