1
/*
2
 * Copyright 2008 Chris Wilson
3
 *
4
 * Permission to use, copy, modify, distribute, and sell this software
5
 * and its documentation for any purpose is hereby granted without
6
 * fee, provided that the above copyright notice appear in all copies
7
 * and that both that copyright notice and this permission notice
8
 * appear in supporting documentation, and that the name of
9
 * Chris Wilson not be used in advertising or publicity pertaining to
10
 * distribution of the software without specific, written prior
11
 * permission. Chris Wilson makes no representations about the
12
 * suitability of this software for any purpose.  It is provided "as
13
 * is" without express or implied warranty.
14
 *
15
 * CHRIS WILSON DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS
16
 * SOFTWARE, INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND
17
 * FITNESS, IN NO EVENT SHALL CHRIS WILSON BE LIABLE FOR ANY SPECIAL,
18
 * INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER
19
 * RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION
20
 * OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR
21
 * IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
22
 *
23
 * Author: Chris Wilson <chris@chris-wilson.co.uk>
24
 */
25

            
26
#include "cairo-test.h"
27

            
28
typedef struct _point {
29
    double x,y;
30
} point_t;
31

            
32
typedef struct _knots {
33
    point_t a,b,c,d;
34
} knots_t;
35

            
36
static knots_t knots[5] = {
37
    { {0, 0}, {0, 100}, {100, 100}, {100, 0} },
38
    { {0, 0}, {75, 100}, {25, 100}, {100, 0} },
39
    { {0, 0}, {100, 100}, {0, 100}, {100, 0} },
40
    { {0, 0}, {150, 100}, {-50, 100}, {100, 0} },
41
    { {0, 0}, {100, 200}, {0, -100}, {100, 100} },
42
};
43

            
44
#ifdef REFERENCE
45
static void
46
_lerp_half (const point_t *a, const point_t *b, point_t *result)
47
{
48
    result->x = .5 * (a->x + b->x);
49
    result->y = .5 * (a->y + b->y);
50
}
51

            
52
static void
53
_de_casteljau (knots_t *k1, knots_t *k2)
54
{
55
    point_t ab, bc, cd;
56
    point_t abbc, bccd;
57
    point_t final;
58

            
59
    _lerp_half (&k1->a, &k1->b, &ab);
60
    _lerp_half (&k1->b, &k1->c, &bc);
61
    _lerp_half (&k1->c, &k1->d, &cd);
62
    _lerp_half (&ab, &bc, &abbc);
63
    _lerp_half (&bc, &cd, &bccd);
64
    _lerp_half (&abbc, &bccd, &final);
65

            
66
    k2->a = final;
67
    k2->b = bccd;
68
    k2->c = cd;
69
    k2->d = k1->d;
70

            
71
    k1->b = ab;
72
    k1->c = abbc;
73
    k1->d = final;
74
}
75

            
76
static double
77
_spline_error_squared (const knots_t *knots)
78
{
79
    double bdx, bdy, berr;
80
    double cdx, cdy, cerr;
81
    double dx, dy, v;
82

            
83
    /* Intersection point (px):
84
     *	    px = p1 + u(p2 - p1)
85
     *	    (p - px) ∙ (p2 - p1) = 0
86
     * Thus:
87
     *	    u = ((p - p1) ∙ (p2 - p1)) / ∥p2 - p1∥²;
88
     */
89
    bdx = knots->b.x - knots->a.x;
90
    bdy = knots->b.y - knots->a.y;
91

            
92
    cdx = knots->c.x - knots->a.x;
93
    cdy = knots->c.y - knots->a.y;
94

            
95
    dx = knots->d.x - knots->a.x;
96
    dy = knots->d.y - knots->a.y;
97
    v = dx * dx + dy * dy;
98
    if (v != 0.) {
99
	double u;
100

            
101
	u = bdx * dx + bdy * dy;
102
	if (u <= 0) {
103
	    /* bdx -= 0;
104
	     * bdy -= 0;
105
	     */
106
	} else if (u >= v) {
107
	    bdx -= dx;
108
	    bdy -= dy;
109
	} else {
110
	    bdx -= u/v * dx;
111
	    bdy -= u/v * dy;
112
	}
113

            
114
	u = cdx * dx + cdy * dy;
115
	if (u <= 0) {
116
	    /* cdx -= 0;
117
	     * cdy -= 0;
118
	     */
119
	} else if (u >= v) {
120
	    cdx -= dx;
121
	    cdy -= dy;
122
	} else {
123
	    cdx -= u/v * dx;
124
	    cdy -= u/v * dy;
125
	}
126
    }
127

            
128
    berr = bdx * bdx + bdy * bdy;
129
    cerr = cdx * cdx + cdy * cdy;
130
    if (berr > cerr)
131
	return berr * v;
132
    else
133
	return cerr * v;
134
}
135

            
136
static void
137
_offset_line_to (cairo_t *cr,
138
		 const point_t *p0,
139
		 const point_t *p1,
140
		 const point_t *p2,
141
		 const point_t *p3,
142
		 double offset)
143
{
144
    double dx, dy, v;
145

            
146
    dx = p1->x - p0->x;
147
    dy = p1->y - p0->y;
148
     v = hypot (dx, dy);
149
     if (v == 0) {
150
	 dx = p2->x - p0->x;
151
	 dy = p2->y - p0->y;
152
	 v = hypot (dx, dy);
153
	 if (v == 0) {
154
	     dx = p3->x - p0->x;
155
	     dy = p3->y - p0->y;
156
	     v = hypot (dx, dy);
157
	 }
158
     }
159

            
160
     if (v == 0) {
161
	 cairo_line_to (cr, p0->x, p0->y);
162
     } else
163
	 cairo_line_to (cr, p0->x - offset * dy / v, p0->y + offset * dx / v);
164
}
165

            
166
static void
167
_spline_decompose_into (knots_t *k1,
168
			double tolerance_squared,
169
			double offset,
170
			cairo_t *cr)
171
{
172
    knots_t k2;
173

            
174
    if (_spline_error_squared (k1) < tolerance_squared) {
175
	_offset_line_to (cr, &k1->a, &k1->b, &k1->c, &k1->d, offset);
176
	return;
177
    }
178

            
179
    _de_casteljau (k1, &k2);
180

            
181
    _spline_decompose_into (k1, tolerance_squared, offset, cr);
182
    _spline_decompose_into (&k2, tolerance_squared, offset, cr);
183
}
184

            
185
static void
186
_spline_decompose (const knots_t *knots,
187
		   double tolerance, double offset,
188
		   cairo_t *cr)
189
{
190
    knots_t k;
191

            
192
    k = *knots;
193
    _spline_decompose_into (&k, tolerance * tolerance, offset, cr);
194

            
195
    _offset_line_to (cr, &knots->d, &knots->c, &knots->b, &knots->a, -offset);
196
}
197

            
198
static void
199
_knots_reverse (knots_t *knots)
200
{
201
    point_t tmp;
202

            
203
    tmp = knots->a;
204
    knots->a = knots->d;
205
    knots->d = tmp;
206

            
207
    tmp = knots->b;
208
    knots->b = knots->c;
209
    knots->c = tmp;
210
}
211

            
212
static void
213
thick_splines (cairo_t *cr, double offset)
214
{
215
    knots_t k;
216

            
217
    cairo_save (cr);
218
    cairo_translate (cr, 15, 15);
219

            
220
    k = knots[0];
221

            
222
    cairo_new_path (cr);
223
    _spline_decompose (&k, .1, offset, cr);
224
    _knots_reverse (&k);
225
    _spline_decompose (&k, .1, offset, cr);
226
    cairo_close_path (cr);
227
    cairo_fill (cr);
228

            
229
    cairo_translate (cr, 130, 0);
230

            
231
    k = knots[1];
232

            
233
    cairo_new_path (cr);
234
    _spline_decompose (&k, .1, offset, cr);
235
    _knots_reverse (&k);
236
    _spline_decompose (&k, .1, offset, cr);
237
    cairo_close_path (cr);
238
    cairo_fill (cr);
239

            
240
    cairo_translate (cr, 130, 0);
241

            
242
    k = knots[2];
243

            
244
    cairo_new_path (cr);
245
    _spline_decompose (&k, .1, offset, cr);
246
    _knots_reverse (&k);
247
    _spline_decompose (&k, .1, offset, cr);
248
    cairo_close_path (cr);
249
    cairo_fill (cr);
250

            
251
    cairo_translate (cr, -130 - 65, 130);
252

            
253
    k = knots[3];
254

            
255
    cairo_new_path (cr);
256
    _spline_decompose (&k, .1, offset, cr);
257
    _knots_reverse (&k);
258
    _spline_decompose (&k, .1, offset, cr);
259
    cairo_close_path (cr);
260
    cairo_fill (cr);
261

            
262
    cairo_translate (cr, 130, 0);
263

            
264
    k = knots[4];
265

            
266
    cairo_new_path (cr);
267
    _spline_decompose (&k, .1, offset, cr);
268
    _knots_reverse (&k);
269
    _spline_decompose (&k, .1, offset, cr);
270
    cairo_close_path (cr);
271
    cairo_fill (cr);
272
    cairo_restore (cr);
273
}
274

            
275
static void
276
thin_splines (cairo_t *cr)
277
{
278
    cairo_save (cr);
279
    cairo_translate (cr, 15, 15);
280

            
281
    cairo_new_path (cr);
282
    _spline_decompose (&knots[0], .1, 0, cr);
283
    cairo_stroke (cr);
284

            
285
    cairo_translate (cr, 130, 0);
286

            
287
    cairo_new_path (cr);
288
    _spline_decompose (&knots[1], .1, 0, cr);
289
    cairo_stroke (cr);
290

            
291
    cairo_translate (cr, 130, 0);
292

            
293
    cairo_new_path (cr);
294
    _spline_decompose (&knots[2], .1, 0, cr);
295
    cairo_stroke (cr);
296

            
297
    cairo_translate (cr, -130 - 65, 130);
298

            
299
    cairo_new_path (cr);
300
    _spline_decompose (&knots[3], .1, 0, cr);
301
    cairo_stroke (cr);
302

            
303
    cairo_translate (cr, 130, 0);
304

            
305
    cairo_new_path (cr);
306
    _spline_decompose (&knots[4], .1, 0, cr);
307
    cairo_stroke (cr);
308
    cairo_restore (cr);
309
}
310
#endif
311

            
312
static void
313
60
draw_bbox (cairo_t *cr, double x0, double y0, double x1, double y1)
314
{
315
60
    cairo_rectangle (cr,
316
60
		     floor (x0) + .5, floor (y0) + .5,
317
60
		     ceil (x1) - floor (x0), ceil (y1) - floor (y0));
318
60
    cairo_stroke (cr);
319
60
}
320

            
321
static void
322
6
stroke_splines (cairo_t *cr)
323
{
324
    double stroke_x0, stroke_x1, stroke_y0, stroke_y1;
325
    double path_x0, path_x1, path_y0, path_y1;
326

            
327
6
    cairo_save (cr);
328
6
    cairo_translate (cr, 15, 15);
329

            
330
6
    cairo_new_path (cr);
331
6
    cairo_move_to (cr,
332
		   knots[0].a.x, knots[0].a.y);
333
6
    cairo_curve_to (cr,
334
		    knots[0].b.x, knots[0].b.y,
335
		    knots[0].c.x, knots[0].c.y,
336
		    knots[0].d.x, knots[0].d.y);
337
6
    cairo_stroke_extents (cr, &stroke_x0, &stroke_y0, &stroke_x1, &stroke_y1);
338
6
    cairo_path_extents (cr, &path_x0, &path_y0, &path_x1, &path_y1);
339
6
    cairo_stroke (cr);
340

            
341
6
    cairo_save (cr); {
342
6
	cairo_set_line_width (cr, 1);
343
6
	cairo_set_source_rgb (cr, 1, 0, 0);
344
6
	draw_bbox (cr, stroke_x0, stroke_y0, stroke_x1, stroke_y1);
345
6
	cairo_set_source_rgb (cr, 0, 0, 1);
346
6
	draw_bbox (cr, path_x0, path_y0, path_x1, path_y1);
347
6
    } cairo_restore (cr);
348

            
349
6
    cairo_translate (cr, 130, 0);
350

            
351
6
    cairo_new_path (cr);
352
6
    cairo_move_to (cr,
353
		   knots[1].a.x, knots[1].a.y);
354
6
    cairo_curve_to (cr,
355
		    knots[1].b.x, knots[1].b.y,
356
		    knots[1].c.x, knots[1].c.y,
357
		    knots[1].d.x, knots[1].d.y);
358
6
    cairo_stroke_extents (cr, &stroke_x0, &stroke_y0, &stroke_x1, &stroke_y1);
359
6
    cairo_path_extents (cr, &path_x0, &path_y0, &path_x1, &path_y1);
360
6
    cairo_stroke (cr);
361

            
362
6
    cairo_save (cr); {
363
6
	cairo_set_line_width (cr, 1);
364
6
	cairo_set_source_rgb (cr, 1, 0, 0);
365
6
	draw_bbox (cr, stroke_x0, stroke_y0, stroke_x1, stroke_y1);
366
6
	cairo_set_source_rgb (cr, 0, 0, 1);
367
6
	draw_bbox (cr, path_x0, path_y0, path_x1, path_y1);
368
6
    } cairo_restore (cr);
369

            
370
6
    cairo_translate (cr, 130, 0);
371

            
372
6
    cairo_new_path (cr);
373
6
    cairo_move_to (cr,
374
		   knots[2].a.x, knots[2].a.y);
375
6
    cairo_curve_to (cr,
376
		    knots[2].b.x, knots[2].b.y,
377
		    knots[2].c.x, knots[2].c.y,
378
		    knots[2].d.x, knots[2].d.y);
379
6
    cairo_stroke_extents (cr, &stroke_x0, &stroke_y0, &stroke_x1, &stroke_y1);
380
6
    cairo_path_extents (cr, &path_x0, &path_y0, &path_x1, &path_y1);
381
6
    cairo_stroke (cr);
382

            
383
6
    cairo_save (cr); {
384
6
	cairo_set_line_width (cr, 1);
385
6
	cairo_set_source_rgb (cr, 1, 0, 0);
386
6
	draw_bbox (cr, stroke_x0, stroke_y0, stroke_x1, stroke_y1);
387
6
	cairo_set_source_rgb (cr, 0, 0, 1);
388
6
	draw_bbox (cr, path_x0, path_y0, path_x1, path_y1);
389
6
    } cairo_restore (cr);
390

            
391
6
    cairo_translate (cr, -130 - 65, 130);
392

            
393
6
    cairo_new_path (cr);
394
6
    cairo_move_to (cr,
395
		   knots[3].a.x, knots[3].a.y);
396
6
    cairo_curve_to (cr,
397
		    knots[3].b.x, knots[3].b.y,
398
		    knots[3].c.x, knots[3].c.y,
399
		    knots[3].d.x, knots[3].d.y);
400
6
    cairo_stroke_extents (cr, &stroke_x0, &stroke_y0, &stroke_x1, &stroke_y1);
401
6
    cairo_path_extents (cr, &path_x0, &path_y0, &path_x1, &path_y1);
402
6
    cairo_stroke (cr);
403

            
404
6
    cairo_save (cr); {
405
6
	cairo_set_line_width (cr, 1);
406
6
	cairo_set_source_rgb (cr, 1, 0, 0);
407
6
	draw_bbox (cr, stroke_x0, stroke_y0, stroke_x1, stroke_y1);
408
6
	cairo_set_source_rgb (cr, 0, 0, 1);
409
6
	draw_bbox (cr, path_x0, path_y0, path_x1, path_y1);
410
6
    } cairo_restore (cr);
411

            
412
6
    cairo_translate (cr, 130, 0);
413

            
414
6
    cairo_new_path (cr);
415
6
    cairo_move_to (cr,
416
		   knots[4].a.x, knots[4].a.y);
417
6
    cairo_curve_to (cr,
418
		    knots[4].b.x, knots[4].b.y,
419
		    knots[4].c.x, knots[4].c.y,
420
		    knots[4].d.x, knots[4].d.y);
421
6
    cairo_stroke_extents (cr, &stroke_x0, &stroke_y0, &stroke_x1, &stroke_y1);
422
6
    cairo_path_extents (cr, &path_x0, &path_y0, &path_x1, &path_y1);
423
6
    cairo_stroke (cr);
424

            
425
6
    cairo_save (cr); {
426
6
	cairo_set_line_width (cr, 1);
427
6
	cairo_set_source_rgb (cr, 1, 0, 0);
428
6
	draw_bbox (cr, stroke_x0, stroke_y0, stroke_x1, stroke_y1);
429
6
	cairo_set_source_rgb (cr, 0, 0, 1);
430
6
	draw_bbox (cr, path_x0, path_y0, path_x1, path_y1);
431
6
    } cairo_restore (cr);
432

            
433
6
    cairo_restore (cr);
434
6
}
435

            
436
static cairo_test_status_t
437
3
draw (cairo_t *cr, int width, int height)
438
{
439
3
    cairo_set_source_rgb (cr, 1, 1, 1);
440
3
    cairo_paint (cr);
441

            
442
#ifdef REFERENCE
443
    cairo_set_source_rgb (cr, 0, 0, 0);
444
    thick_splines (cr, 5);
445

            
446
    cairo_set_source_rgb (cr, 1, 1, 1);
447
    thin_splines (cr);
448
#endif
449

            
450
    /*
451
     * Use a high tolerance to reduce dependence upon algorithm used for
452
     * spline decomposition.
453
     */
454
3
    cairo_set_tolerance (cr, 0.001);
455

            
456
3
    cairo_set_line_width (cr, 10);
457
3
    cairo_set_source_rgb (cr, 0, 0, 0);
458
3
    stroke_splines (cr);
459
3
    cairo_set_line_width (cr, 2);
460
3
    cairo_set_source_rgb (cr, 1, 1, 1);
461
3
    stroke_splines (cr);
462

            
463
3
    return CAIRO_TEST_SUCCESS;
464
}
465

            
466
1
CAIRO_TEST (spline_decomposition,
467
	    "Tests splines with various inflection points",
468
	    "stroke, spline", /* keywords */
469
	    NULL, /* requirements */
470
	    390, 260,
471
	    NULL, draw)