[CpmlSegment] Implemented a decent offseting curve algorithm
[adg.git] / cpml / cpml-segment.c
blobb1b3c7754083575b907132bc6056a8e1e1ad80c7
1 /* CPML - Cairo Path Manipulation Library
2 * Copyright (C) 2008, Nicola Fontana <ntd at entidi.it>
4 * This library is free software; you can redistribute it and/or
5 * modify it under the terms of the GNU Lesser General Public
6 * License as published by the Free Software Foundation; either
7 * version 2 of the License, or (at your option) any later version.
9 * This library is distributed in the hope that it will be useful,
10 * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
12 * Lesser General Public License for more details.
14 * You should have received a copy of the GNU Lesser General Public
15 * License along with this library; if not, write to the
16 * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
17 * Boston, MA 02110-1301, USA.
21 #include "cpml-segment.h"
22 #include "cpml-pair.h"
23 #include "cpml-macros.h"
24 #include "cpml-alloca.h"
26 #include <stdio.h>
27 #include <string.h>
29 static cairo_bool_t segment_normalize (CpmlSegment *segment);
30 static void line_offset (CpmlPair *p,
31 CpmlVector *vector,
32 double offset);
33 static void curve_offset (CpmlPair *p,
34 CpmlVector *vector,
35 double offset);
36 static void join_primitives (cairo_path_data_t *last_data,
37 const CpmlVector *last_vector,
38 const CpmlPair *new_point,
39 const CpmlVector *new_vector);
42 /**
43 * cpml_segment_init:
44 * @segment: a #CpmlSegment
45 * @src: the source cairo_path_t
47 * Builds a CpmlSegment from a cairo_path_t structure. This operation
48 * involves stripping the leading %MOVE_TO primitives and setting the
49 * internal segment structure accordling. A pointer to the source
50 * path segment is kept.
52 * Return value: 1 on success, 0 on errors
53 **/
54 cairo_bool_t
55 cpml_segment_init(CpmlSegment *segment, cairo_path_t *src)
57 /* The cairo path should be defined and in perfect state */
58 if (src == NULL || src->num_data == 0 ||
59 src->status != CAIRO_STATUS_SUCCESS)
60 return 0;
62 segment->original = src;
63 memcpy(&segment->path, src, sizeof(cairo_path_t));
65 return segment_normalize(segment);
68 /**
69 * cpml_segment_copy:
70 * @segment: a #CpmlSegment
71 * @src: the source segment to copy
73 * Makes a shallow copy of @src into @segment.
75 * Return value: @segment or %NULL on errors
76 **/
77 CpmlSegment *
78 cpml_segment_copy(CpmlSegment *segment, const CpmlSegment *src)
80 if (segment == NULL || src == NULL)
81 return NULL;
83 return memcpy(segment, src, sizeof(CpmlSegment));
86 /**
87 * cpml_segment_dump:
88 * @segment: a #CpmlSegment
90 * Dumps the specified @segment to stdout. Useful for debugging purposes.
91 **/
92 void
93 cpml_segment_dump(const CpmlSegment *segment)
95 const cairo_path_t *path;
96 cairo_path_data_t *data;
97 int n_data, n_point;
99 if (segment == NULL) {
100 printf("Trying to dump a NULL segment!\n");
101 return;
104 path = &segment->path;
105 for (n_data = 0; n_data < path->num_data; ++n_data) {
106 data = path->data + n_data;
108 switch (data->header.type) {
109 case CAIRO_PATH_MOVE_TO:
110 printf("Move to ");
111 break;
112 case CAIRO_PATH_LINE_TO:
113 printf("Line to ");
114 break;
115 case CAIRO_PATH_CURVE_TO:
116 printf("Curve to ");
117 break;
118 case CAIRO_PATH_CLOSE_PATH:
119 printf("Path close");
120 break;
121 default:
122 printf("Unknown entity (%d)", data->header.type);
123 break;
126 for (n_point = 1; n_point < data->header.length; ++n_point)
127 printf("(%lf, %lf) ", data[n_point].point.x,
128 data[n_point].point.y);
130 n_data += n_point - 1;
131 printf("\n");
136 * cpml_segment_reset:
137 * @segment: a #CpmlSegment
139 * Modifies @segment to point to the first segment of the original path.
141 void
142 cpml_segment_reset(CpmlSegment *segment)
144 memcpy(&segment->path, segment->original, sizeof(cairo_path_t));
145 segment_normalize(segment);
149 * cpml_segment_next:
150 * @segment: a #CpmlSegment
152 * Modifies @segment to point to the next segment of the original path.
154 * Return value: 1 on success, 0 if no next segment found or errors
156 cairo_bool_t
157 cpml_segment_next(CpmlSegment *segment)
159 int num_data = segment->path.num_data;
160 int offset = segment->path.data - segment->original->data;
162 segment->path.num_data = segment->original->num_data - num_data - offset;
163 segment->path.data += num_data;
165 return segment_normalize(segment);
169 * cpml_segment_reverse:
170 * @segment: a #CpmlSegment
172 * Reverses @segment in-place. The resulting rendering will be the same,
173 * but with the primitives generated in reverse order.
175 void
176 cpml_segment_reverse(CpmlSegment *segment)
178 cairo_path_data_t *data, *dst_data;
179 size_t data_size;
180 double end_x, end_y;
181 int num_data, n_data;
182 int num_points, n_point;
183 const cairo_path_data_t *src_data;
185 num_data = segment->path.num_data;
186 data_size = sizeof(cairo_path_data_t) * num_data;
187 data = cpml_alloca(data_size);
188 end_x = segment->path.data[1].point.x;
189 end_y = segment->path.data[1].point.y;
191 for (n_data = 2; n_data < num_data; ++n_data) {
192 src_data = segment->path.data + n_data;
193 num_points = src_data->header.length;
195 dst_data = data + num_data - n_data - num_points + 2;
196 dst_data->header.type = src_data->header.type;
197 dst_data->header.length = num_points;
199 for (n_point = 1; n_point < num_points; ++n_point) {
200 dst_data[num_points - n_point].point.x = end_x;
201 dst_data[num_points - n_point].point.y = end_y;
202 end_x = src_data[n_point].point.x;
203 end_y = src_data[n_point].point.y;
206 n_data += n_point - 1;
209 data[0].header.type = CAIRO_PATH_MOVE_TO;
210 data[0].header.length = 2;
211 data[1].point.x = end_x;
212 data[1].point.y = end_y;
213 memcpy(segment->path.data, data, data_size);
217 * cpml_segment_transform:
218 * @segment: a #CpmlSegment
219 * @matrix: the matrix to be applied
221 * Applies @matrix on all the points of @segment.
223 void
224 cpml_segment_transform(CpmlSegment *segment, const cairo_matrix_t *matrix)
226 cairo_path_data_t *data;
227 int n_data, num_data;
228 int n_point, num_points;
230 data = segment->path.data;
231 num_data = segment->path.num_data;
233 for (n_data = 0; n_data < num_data; n_data += num_points) {
234 num_points = data->header.length;
235 ++data;
236 for (n_point = 1; n_point < num_points; ++n_point) {
237 cairo_matrix_transform_point(matrix, &data->point.x, &data->point.y);
238 ++data;
244 * cpml_segment_offset:
245 * @segment: a #CpmlSegment
246 * @offset: the offset distance
248 * Offsets a segment of the specified amount, that is builds a "parallel"
249 * segment at the @offset distance from the original one and returns the
250 * result by replacing the original @segment.
252 * Return value: 1 on success, 0 on errors
254 * @todo: closed path are not yet managed; the solution is not so obvious.
256 cairo_bool_t
257 cpml_segment_offset(CpmlSegment *segment, double offset)
259 int num_data, n_data;
260 int num_points, n_point;
261 cairo_path_data_t *data;
262 cairo_path_data_t *last_data;
263 CpmlVector v_old, v_new;
264 CpmlPair p_old;
265 CpmlPair p[4];
267 num_data = segment->path.num_data;
268 last_data = NULL;
269 p_old.x = 0;
270 p_old.y = 0;
272 for (n_data = 0; n_data < num_data; n_data += data->header.length) {
273 data = segment->path.data + n_data;
274 num_points = data->header.length - 1;
276 /* Fill the p[] vector */
277 cpml_pair_copy(&p[0], &p_old);
278 for (n_point = 1; n_point <= num_points; ++ n_point) {
279 p[n_point].x = data[n_point].point.x;
280 p[n_point].y = data[n_point].point.y;
283 /* Save the last direction vector in v_old */
284 cpml_pair_copy(&v_old, &v_new);
286 switch (data->header.type) {
288 case CAIRO_PATH_MOVE_TO:
289 v_new.x = 0;
290 v_new.y = 0;
291 break;
293 case CAIRO_PATH_LINE_TO:
294 line_offset(p, &v_new, offset);
295 break;
297 case CAIRO_PATH_CURVE_TO:
298 curve_offset(p, &v_new, offset);
299 break;
301 case CAIRO_PATH_CLOSE_PATH:
302 return 1;
305 join_primitives(last_data, &v_old, &p[0], &v_new);
307 /* Save the end point of the original primitive in p_old */
308 last_data = data + num_points;
309 p_old.x = last_data->point.x;
310 p_old.y = last_data->point.y;
312 /* Store the results got from the p[] vector in the cairo path */
313 for (n_point = 1; n_point <= num_points; ++ n_point) {
314 data[n_point].point.x = p[n_point].x;
315 data[n_point].point.y = p[n_point].y;
319 return 1;
323 * segment_normalize:
324 * @segment: a #CpmlSegment
326 * Strips the leading CAIRO_PATH_MOVE_TO primitives, updating the CpmlSegment
327 * structure accordling. One, and only once, MOVE_TO primitive is left.
329 * Return value: 1 on success, 0 on no leading MOVE_TOs or on errors
331 static cairo_bool_t
332 segment_normalize(CpmlSegment *segment)
334 cairo_path_data_t *data;
336 if (segment == NULL || segment->path.num_data <= 0)
337 return 0;
339 data = segment->path.data;
340 if (data->header.type != CAIRO_PATH_MOVE_TO) {
341 segment->path.status = CAIRO_STATUS_INVALID_PATH_DATA;
342 return 0;
345 while (segment->path.num_data >= 0) {
346 data += 2;
347 if (data->header.type != CAIRO_PATH_MOVE_TO)
348 return 1;
350 segment->path.data = data;
351 segment->path.num_data -= 2;
354 return 0;
358 * line_offset:
359 * @p: an array of two #CpmlPair structs
360 * @vector: the ending direction vector of the resulting primitive
361 * @offset: distance for the computed parallel line
363 * Given a line segment starting from @p[0] and ending in @p[1],
364 * computes the parallel line distant @offset from the original one
365 * and returns the result in the @p array.
367 * The direction vector of the new line segment is saved in @vector
368 * with a somewhat arbitrary magnitude.
370 static void
371 line_offset(CpmlPair *p, CpmlVector *vector, double offset)
373 CpmlVector normal;
375 cpml_pair_sub(cpml_pair_copy(vector, &p[1]), &p[0]);
377 cpml_vector_from_pair(&normal, vector, offset);
378 cpml_vector_normal(&normal);
380 cpml_pair_add(&p[0], &normal);
381 cpml_pair_add(&p[1], &normal);
385 * curve_offset:
386 * @p: (inout): an array of four #CpmlPair structs
387 * @vector: (out): the ending direction vector of the resulting primitive
388 * @offset: (in): distance for the computed parallel line
390 * Given a cubic Bézier segment starting from @p[0] and ending
391 * in p[3], with control points in @p[1] and @p[2], this function
392 * finds the approximated Bézier curve parallel to the given one
393 * at distance @offset (an offset curve).
395 * The four points needed to build the new curve are returned
396 * in the @p vector. The angular coefficient of the new curve
397 * in @p[3] is returned as @vector with @offset magnitude.
399 * To solve the offset problem, a custom algorithm is used. First, the
400 * resulting curve MUST have the same slope at the start and end point.
401 * These constraints are not sufficient to resolve the system, so I let
402 * the curve pass thought a given point (pm, known and got from the
403 * original curve) at a given time (m, now hardcoded to 0.5).
405 * Firstly, I define some useful variables:
407 * v0 = unitvector(p[1]-p[0]) * offset;
408 * v3 = unitvector(p[3]-p[2]) * offset;
409 * p0 = p[0] + normal v0;
410 * p3 = p[3] + normal v3.
412 * Now I want the curve to have the specified slopes at the start
413 * and end point. Forcing the same slope at the start point means:
415 * p1 = p0 + k1 v0.
417 * where k1 is an arbitrary factor. Decomposing for x and y components and
418 * getting rid of k1:
420 * p1.x - p0.x = k1 v0.x;
421 * p1.y - p0.y = k1 v0.y.
423 * (p1.x - p0.x) v0.y = (p1.y - p0.y) v0.x.
425 * Doing the same for the end point gives:
426 * p2 = p3 + k2 v3.
428 * (p3.x - p2.x) v3.y = (p3.y - p2.y) v3.x.
430 * Now I interpolate the curve by forcing it to pass throught pm
431 * when "time" is m, where 0 < m < 1. The cubic Bézier function is:
433 * C(t) = (1-t)³p0 + 3t(1-t)²p1 + 3t²(1-t)p2 + t³p3.
435 * and forcing t=m and C(t) = pm:
437 * pm = (1-m)³p0 + 3m(1-m)²p1 + 3m²(1-m)p2 + m³p3.
438 * (1-m) p1 + m p2 = (pm - (1-m)³p0 - m³p3) / (3m (1-m)).
440 * Either p0 and p3 of the curve are known, so I can get rid of
441 * the constant part:
443 * pk = (pm - (1-m)³p0 - m³p3) / (3m (1-m));
444 * (1-m) p1 + m p2 = pk.
446 * So the final system is:
448 * (1-m) p1.x + m p2.x = pk->x;
449 * (1-m) p1.y + m p2.y = pk->y;
450 * (p1.x - p0.x) v0.y = (p1.y - p0.y) v0.x.
451 * (p3.x - p2.x) v3.y = (p3.y - p2.y) v3.x.
453 * Given the above system, I get the following pool of equations:
455 * p1.x = (pk.x - m p2.x)/(1-m);
456 * p1.y = (pk.y - m p2.y)/(1-m).
458 * p2.x = (pk.x - (1-m) p1.x)/m;
459 * p2.y = (pk.y - (1-m) p1.y)/m.
461 * p1.x = p0.x + (p1.y - p0.y) v0.x/v0.y;
462 * p1.y = p0.y + (p1.x - p0.x) v0.y/v0.x.
464 * p2.x = p3.x - (p3.y - p2.y) v3.x/v3.y;
465 * p2.y = p3.y - (p3.x - p2.x) v3.y/v3.x.
467 * The algorithm takes from this pool what is needed in any specific case.
469 * Now the hard part: finding the first solution. Solving this system
470 * is a nightmare because of divisions by 0 exceptions.
472 * After a lot of tedious calculations, it came out when the
473 * v3.x*v0.y == v0.x*v3.y condition is met, that is when v0 and v1
474 * are parallel and in the same direction, this algorithm is not valid:
475 * this condition should be checked before anything else.
477 * If v3.x*v0.y == v0.x*v3.y, an alternative approach is provided:
478 * the control points are found by offseting the control poligon
479 * (as done by Tiller and Hanson) but using a 4/3 factor on the p1-p2
480 * line to compensate the distance between the curve and the p1-p2 line
481 * (the top of the curve is at exactly 3/4 of the poligon height
482 * when v1 and v2 are parallel and in the same direction).
484 * So if v3.x*v0.y == v0.x*v3.y and if m is 0.5 (the mid point of the
485 * curve) so that vm is the vector of @offset magnitude at m, I have:
487 * p1 = p0 + (p[1]-p[0]) + 4/3 vm;
488 * p2 = p3 + (p[2]-p[3]) - 4/3 vm.
490 * In the other cases, I get the first coordinate by looking for a
491 * 0 vector component. If no vector component is 0 any division is allowed
492 * so, taking some equation from the pool, the following system is used:
494 * p1.x = (pk.x - m p2.x)/(1-m);
495 * p1.x = p0.x + (p1.y-p0.y) v0.x/v0.y;
496 * p2.x = p3.x - (p3.y - p2.y) v3.x/v3.y;
497 * p2.y = (pk.y - (1-m) p1.y)/m.
499 * And now I resolve to get the p1.y value:
501 * (pk.x - m p2.x)/(1-m) = p0.x + (p1.y-p0.y) v0.x/v0.y;
502 * p2.x = p3.x + ((pk.y - (1-m) p1.y)/m - p3.y) v3.x/v3.y.
504 * p2.x = pk.x/m - (1-m)/m (p0.x - v0.x/v0.y p0.y)
505 * - p1.y (1-m)/m v0.x/v0.y;
506 * p2.x = p3.x + (pk.y/m - p3.y) v3.x/v3.y
507 * - p1.y (1-m)/m v3.x/v3.y.
509 * pk.x/m - (1-m)/m (p0.x - v0.x/v0.y p0.y)
510 * - p1.y (1-m)/m v0.x/v0.y =
511 * p3.x + (pk.y/m - p3.y) v3.x/v3.y
512 * - p1.y (1-m)/m v3.x/v3.y.
514 * pk.x/m - (1-m)/m (p0.x - p0.y v0.x/v0.y)
515 * - p1.y (1-m)/m v0.x/v0.y =
516 * p3.x + (pk.y/m - p3.y) v3.x/v3.y
517 * - p1.y (1-m)/m v3.x/v3.y.
519 * p1.y (v3.x/v3.y - v0.x/v0.y) = (p0.x - p0.y v0.x/v0.y)
520 * + (m (p3.x + (pk.y/m - p3.y) v3.x/v3.y) - pk.x) / (1-m).
522 * And this is the final equation:
524 * p1.y = ((p0.x - p0.y v0.x/v0.y)
525 * + (m (p3.x + (pk.y/m - p3.y) v3.x/v3.y) - pk.x) / (1-m))
526 * / (v3.x/v3.y - v0.x/v0.y).
528 static void
529 curve_offset(CpmlPair *p, CpmlVector *vector, double offset)
531 double m, mm;
532 CpmlVector v0, v3, vm;
533 CpmlPair p0, p1, p2, p3, pm, pk;
535 /* v0 = vector p[1]-p[0] of @offset magnitude */
536 cpml_pair_sub(cpml_pair_copy(&v0, &p[1]), &p[0]);
537 cpml_vector_from_pair(&v0, &v0, offset);
539 /* v3 = vector p[3]-p[2] of @offset magnitude */
540 cpml_pair_sub(cpml_pair_copy(&v3, &p[3]), &p[2]);
541 cpml_vector_from_pair(&v3, &v3, offset);
543 /* p0 = p[0] + normal of v0 (exact final value of p0) */
544 cpml_vector_normal(cpml_pair_copy(&p0, &v0));
545 cpml_pair_add(&p0, &p[0]);
547 /* p3 = p[3] + normal of v3 (exact final value of p3) */
548 cpml_vector_normal(cpml_pair_copy(&p3, &v3));
549 cpml_pair_add(&p3, &p[3]);
551 /* By default, interpolate the new curve by offseting the mid point.
552 * @todo: use a better candidate. */
553 m = 0.5;
554 mm = 1.-m;
556 /* pm = point in C(m) offseted the requested @offset distance */
557 cpml_vector_at_curve(&vm, &p[0], &p[1], &p[2], &p[3], m, offset);
558 cpml_vector_normal(&vm);
559 cpml_pair_at_curve(&pm, &p[0], &p[1], &p[2], &p[3], m);
560 cpml_pair_add(&pm, &vm);
562 /* pk = (pm - (1-m)³p0 - m³p3) / (3m (1-m)) */
563 pk.x = (pm.x - mm*mm*mm*p0.x - m*m*m*p3.x) / (m*3*mm);
564 pk.y = (pm.y - mm*mm*mm*p0.y - m*m*m*p3.y) / (m*3*mm);
566 if (v3.x*v0.y == v0.x*v3.y) {
567 /* Same slope at the start and end point (parallel p0-p1 and p3-p2) */
568 p1.x = p0.x + (p[1].x - p[0].x) + vm.x * 4/3;
569 p1.y = p0.y + (p[1].y - p[0].y) + vm.y * 4/3;
570 p2.x = p3.x + (p[2].x - p[3].x) + vm.x * 4/3;
571 p2.y = p3.y + (p[2].y - p[3].y) + vm.y * 4/3;
573 /* Probably the following can be condensed */
574 } else if (v0.x == 0) {
575 p1.x = p0.x;
576 p2.x = (pk.x - mm*p1.x)/m;
577 if (v3.x == 0)
578 p2.y = p3.y + (pm.y - p3.y) * 4/3;
579 else if (v3.y == 0)
580 p2.y = p3.y;
581 else
582 p2.y = p3.y - (p3.x - p2.x) * v3.y/v3.x;
583 p1.y = (pk.y - m*p2.y)/mm;
584 } else if (v0.y == 0) {
585 p1.y = p0.y;
586 p2.y = (pk.y - mm*p1.y)/m;
587 if (v3.y == 0)
588 p2.x = p3.x + (pm.x - p3.x) * 4/3;
589 else if (v3.x == 0)
590 p2.x = p3.x;
591 else
592 p2.x = p3.x - (p3.y - p2.y) * v3.x/v3.y;
593 p1.x = (pk.x - m*p2.x)/mm;
594 } else if (v3.x == 0) {
595 p2.x = p3.x;
596 p1.x = (pk.x - m*p2.x)/mm;
597 p1.y = p0.y;
598 if (v0.y != 0 && v0.x != 0)
599 p1.y += (p1.x - p0.x) * v0.y/v0.x;
600 p2.y = (pk.y - mm*p1.y)/m;
601 } else if (v3.y == 0) {
602 p2.y = p3.y;
603 p1.y = (pk.y - m*p2.y)/mm;
604 p1.x = p0.x;
605 if (v0.x != 0 && v0.y != 0)
606 p1.x += (p1.y - p0.y) * v0.x/v0.y;
607 p2.x = (pk.x - mm*p1.x)/m;
608 } else if (v3.x*v0.y == v0.x*v3.y) {
609 p1.x = p1.x + (pm.x - p1.x) * 4/3;
610 p1.y = p1.y + (pm.y - p1.y) * 4/3;
611 p2.x = p3.x + (pm.x - p3.x) * 4/3;
612 p2.y = p3.y + (pm.y - p3.y) * 4/3;
613 } else {
614 p1.y = m/mm*v0.y*(v3.y*(p3.x-pk.x/m) + (pk.y/m-p3.y)*v3.x)
615 / (v3.x*v0.y - v0.x*v3.y);
616 p1.x = p0.x + (p1.y - p0.y) * v0.x/v0.y;
617 p2.y = (pk.y - mm*p1.y)/m;
618 p2.x = p3.x - (p3.y - p2.y) * v3.x/v3.y;
621 /* Return the new curve in the original array */
622 cpml_pair_copy(&p[0], &p0);
623 cpml_pair_copy(&p[1], &p1);
624 cpml_pair_copy(&p[2], &p2);
625 cpml_pair_copy(&p[3], &p3);
627 /* Return the ending vector */
628 cpml_pair_copy(vector, &v3);
632 * join_primitives:
633 * @last_data: the previous primitive end data point (if any)
634 * @last_vector: @last_data direction vector
635 * @new_point: the new primitive starting point
636 * @new_vector: @new_point direction vector
638 * Joins two primitive modifying the end point of the first one (stored
639 * as cairo path data in @last_data).
641 * @todo: this approach is quite naive when curves are involved.
643 static void
644 join_primitives(cairo_path_data_t *last_data, const CpmlVector *last_vector,
645 const CpmlPair *new_point, const CpmlVector *new_vector)
647 CpmlPair last_point;
649 if (last_data == NULL)
650 return;
652 last_point.x = last_data->point.x;
653 last_point.y = last_data->point.y;
655 if (cpml_pair_intersection_pv_pv(&last_point,
656 &last_point, last_vector,
657 new_point, new_vector)) {
658 last_data->point.x = last_point.x;
659 last_data->point.y = last_point.y;
660 } else {
661 last_data->point.x = new_point->x;
662 last_data->point.y = new_point->y;