1 /* Libart_LGPL - library of basic graphic primitives
2 * Copyright (C) 1998-2000 Raph Levien
4 * This library is free software; you can redistribute it and/or
5 * modify it under the terms of the GNU Library 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 * Library General Public License for more details.
14 * You should have received a copy of the GNU Library General Public
15 * License along with this library; if not, write to the
16 * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
17 * Boston, MA 02111-1307, USA.
22 #include "art_svp_vpath_stroke.h"
29 #include "art_vpath.h"
31 #ifdef ART_USE_NEW_INTERSECTOR
32 #include "art_svp_intersect.h"
34 #include "art_svp_wind.h"
36 #include "art_svp_vpath.h"
39 #define EPSILON_2 1e-12
41 #define yes_OPTIMIZE_INNER
43 /* Render an arc segment starting at (xc + x0, yc + y0) to (xc + x1,
44 yc + y1), centered at (xc, yc), and with given radius. Both x0^2 +
45 y0^2 and x1^2 + y1^2 should be equal to radius^2.
47 A positive value of radius means curve to the left, negative means
51 art_svp_vpath_stroke_arc (ArtVpath **p_vpath, int *pn, int *pn_max,
64 aradius = fabs (radius);
65 theta = 2 * M_SQRT2 * sqrt (flatness / aradius);
66 th_0 = atan2 (y0, x0);
67 th_1 = atan2 (y1, x1);
70 /* curve to the left */
71 if (th_0 < th_1) th_0 += M_PI * 2;
72 n_pts = ceil ((th_0 - th_1) / theta);
76 /* curve to the right */
77 if (th_1 < th_0) th_1 += M_PI * 2;
78 n_pts = ceil ((th_1 - th_0) / theta);
81 printf ("start %f %f; th_0 = %f, th_1 = %f, r = %f, theta = %f\n", x0, y0, th_0, th_1, radius, theta);
83 art_vpath_add_point (p_vpath, pn, pn_max,
84 ART_LINETO, xc + x0, yc + y0);
85 for (i = 1; i < n_pts; i++)
87 theta = th_0 + (th_1 - th_0) * i / n_pts;
88 art_vpath_add_point (p_vpath, pn, pn_max,
89 ART_LINETO, xc + cos (theta) * aradius,
90 yc + sin (theta) * aradius);
92 printf ("mid %f %f\n", cos (theta) * radius, sin (theta) * radius);
95 art_vpath_add_point (p_vpath, pn, pn_max,
96 ART_LINETO, xc + x1, yc + y1);
98 printf ("end %f %f\n", x1, y1);
102 /* Assume that forw and rev are at point i0. Bring them to i1,
103 joining with the vector i1 - i2.
105 This used to be true, but isn't now that the stroke_raw code is
106 filtering out (near)zero length vectors: {It so happens that all
107 invocations of this function maintain the precondition i1 = i0 + 1,
108 so we could decrease the number of arguments by one. We haven't
109 done that here, though.}
111 forw is to the line's right and rev is to its left.
113 Precondition: no zero-length vectors, otherwise a divide by
116 render_seg (ArtVpath **p_forw, int *pn_forw, int *pn_forw_max,
117 ArtVpath **p_rev, int *pn_rev, int *pn_rev_max,
118 ArtVpath *vpath, int i0, int i1, int i2,
119 ArtPathStrokeJoinType join,
120 double line_width, double miter_limit, double flatness)
132 printf ("join style = %d\n", join);
135 /* The vectors of the lines from i0 to i1 and i1 to i2. */
136 dx0 = vpath[i1].x - vpath[i0].x;
137 dy0 = vpath[i1].y - vpath[i0].y;
139 dx1 = vpath[i2].x - vpath[i1].x;
140 dy1 = vpath[i2].y - vpath[i1].y;
142 /* Set dl[xy]0 to the vector from i0 to i1, rotated counterclockwise
143 90 degrees, and scaled to the length of line_width. */
144 scale = line_width / sqrt (dx0 * dx0 + dy0 * dy0);
148 /* Set dl[xy]1 to the vector from i1 to i2, rotated counterclockwise
149 90 degrees, and scaled to the length of line_width. */
150 scale = line_width / sqrt (dx1 * dx1 + dy1 * dy1);
155 printf ("%% render_seg: (%g, %g) - (%g, %g) - (%g, %g)\n",
156 vpath[i0].x, vpath[i0].y,
157 vpath[i1].x, vpath[i1].y,
158 vpath[i2].x, vpath[i2].y);
160 printf ("%% render_seg: d[xy]0 = (%g, %g), dl[xy]0 = (%g, %g)\n",
161 dx0, dy0, dlx0, dly0);
163 printf ("%% render_seg: d[xy]1 = (%g, %g), dl[xy]1 = (%g, %g)\n",
164 dx1, dy1, dlx1, dly1);
167 /* now, forw's last point is expected to be colinear along d[xy]0
168 to point i0 - dl[xy]0, and rev with i0 + dl[xy]0. */
170 /* positive for positive area (i.e. left turn) */
171 cross = dx1 * dy0 - dx0 * dy1;
173 dmx = (dlx0 + dlx1) * 0.5;
174 dmy = (dly0 + dly1) * 0.5;
175 dmr2 = dmx * dmx + dmy * dmy;
177 if (join == ART_PATH_STROKE_JOIN_MITER &&
178 dmr2 * miter_limit * miter_limit < line_width * line_width)
179 join = ART_PATH_STROKE_JOIN_BEVEL;
181 /* the case when dmr2 is zero or very small bothers me
182 (i.e. near a 180 degree angle)
183 ALEX: So, we avoid the optimization when dmr2 is very small. This should
184 be safe since dmx/y is only used in optimization and in MITER case, and MITER
185 should be converted to BEVEL when dmr2 is very small. */
186 if (dmr2 > EPSILON_2)
188 scale = line_width * line_width / dmr2;
193 if (cross * cross < EPSILON_2 && dx0 * dx1 + dy0 * dy1 >= 0)
197 printf ("%% render_seg: straight\n");
199 art_vpath_add_point (p_forw, pn_forw, pn_forw_max,
200 ART_LINETO, vpath[i1].x - dlx0, vpath[i1].y - dly0);
201 art_vpath_add_point (p_rev, pn_rev, pn_rev_max,
202 ART_LINETO, vpath[i1].x + dlx0, vpath[i1].y + dly0);
206 /* left turn, forw is outside and rev is inside */
209 printf ("%% render_seg: left\n");
212 #ifdef NO_OPTIMIZE_INNER
215 (dmr2 > EPSILON_2) &&
216 /* check that i1 + dm[xy] is inside i0-i1 rectangle */
217 (dx0 + dmx) * dx0 + (dy0 + dmy) * dy0 > 0 &&
218 /* and that i1 + dm[xy] is inside i1-i2 rectangle */
219 ((dx1 - dmx) * dx1 + (dy1 - dmy) * dy1 > 0)
220 #ifdef PEDANTIC_INNER
222 /* check that i1 + dl[xy]1 is inside i0-i1 rectangle */
223 (dx0 + dlx1) * dx0 + (dy0 + dly1) * dy0 > 0 &&
224 /* and that i1 + dl[xy]0 is inside i1-i2 rectangle */
225 ((dx1 - dlx0) * dx1 + (dy1 - dly0) * dy1 > 0)
229 /* can safely add single intersection point */
230 art_vpath_add_point (p_rev, pn_rev, pn_rev_max,
231 ART_LINETO, vpath[i1].x + dmx, vpath[i1].y + dmy);
235 /* need to loop-de-loop the inside */
236 art_vpath_add_point (p_rev, pn_rev, pn_rev_max,
237 ART_LINETO, vpath[i1].x + dlx0, vpath[i1].y + dly0);
238 art_vpath_add_point (p_rev, pn_rev, pn_rev_max,
239 ART_LINETO, vpath[i1].x, vpath[i1].y);
240 art_vpath_add_point (p_rev, pn_rev, pn_rev_max,
241 ART_LINETO, vpath[i1].x + dlx1, vpath[i1].y + dly1);
244 if (join == ART_PATH_STROKE_JOIN_BEVEL)
247 art_vpath_add_point (p_forw, pn_forw, pn_forw_max,
248 ART_LINETO, vpath[i1].x - dlx0, vpath[i1].y - dly0);
249 art_vpath_add_point (p_forw, pn_forw, pn_forw_max,
250 ART_LINETO, vpath[i1].x - dlx1, vpath[i1].y - dly1);
252 else if (join == ART_PATH_STROKE_JOIN_MITER)
254 art_vpath_add_point (p_forw, pn_forw, pn_forw_max,
255 ART_LINETO, vpath[i1].x - dmx, vpath[i1].y - dmy);
257 else if (join == ART_PATH_STROKE_JOIN_ROUND) {
258 art_svp_vpath_stroke_arc (p_forw, pn_forw, pn_forw_max,
259 vpath[i1].x, vpath[i1].y,
268 /* right turn, rev is outside and forw is inside */
270 printf ("%% render_seg: right\n");
274 #ifdef NO_OPTIMIZE_INNER
277 (dmr2 > EPSILON_2) &&
278 /* check that i1 - dm[xy] is inside i0-i1 rectangle */
279 (dx0 - dmx) * dx0 + (dy0 - dmy) * dy0 > 0 &&
280 /* and that i1 - dm[xy] is inside i1-i2 rectangle */
281 ((dx1 + dmx) * dx1 + (dy1 + dmy) * dy1 > 0)
282 #ifdef PEDANTIC_INNER
284 /* check that i1 - dl[xy]1 is inside i0-i1 rectangle */
285 (dx0 - dlx1) * dx0 + (dy0 - dly1) * dy0 > 0 &&
286 /* and that i1 - dl[xy]0 is inside i1-i2 rectangle */
287 ((dx1 + dlx0) * dx1 + (dy1 + dly0) * dy1 > 0)
291 /* can safely add single intersection point */
292 art_vpath_add_point (p_forw, pn_forw, pn_forw_max,
293 ART_LINETO, vpath[i1].x - dmx, vpath[i1].y - dmy);
297 /* need to loop-de-loop the inside */
298 art_vpath_add_point (p_forw, pn_forw, pn_forw_max,
299 ART_LINETO, vpath[i1].x - dlx0, vpath[i1].y - dly0);
300 art_vpath_add_point (p_forw, pn_forw, pn_forw_max,
301 ART_LINETO, vpath[i1].x, vpath[i1].y);
302 art_vpath_add_point (p_forw, pn_forw, pn_forw_max,
303 ART_LINETO, vpath[i1].x - dlx1, vpath[i1].y - dly1);
306 if (join == ART_PATH_STROKE_JOIN_BEVEL)
309 art_vpath_add_point (p_rev, pn_rev, pn_rev_max,
310 ART_LINETO, vpath[i1].x + dlx0, vpath[i1].y + dly0);
311 art_vpath_add_point (p_rev, pn_rev, pn_rev_max,
312 ART_LINETO, vpath[i1].x + dlx1, vpath[i1].y + dly1);
314 else if (join == ART_PATH_STROKE_JOIN_MITER)
316 art_vpath_add_point (p_rev, pn_rev, pn_rev_max,
317 ART_LINETO, vpath[i1].x + dmx, vpath[i1].y + dmy);
319 else if (join == ART_PATH_STROKE_JOIN_ROUND) {
320 art_svp_vpath_stroke_arc (p_rev, pn_rev, pn_rev_max,
321 vpath[i1].x, vpath[i1].y,
331 /* caps i1, under the assumption of a vector from i0 */
333 render_cap (ArtVpath **p_result, int *pn_result, int *pn_result_max,
334 ArtVpath *vpath, int i0, int i1,
335 ArtPathStrokeCapType cap, double line_width, double flatness)
343 dx0 = vpath[i1].x - vpath[i0].x;
344 dy0 = vpath[i1].y - vpath[i0].y;
346 /* Set dl[xy]0 to the vector from i0 to i1, rotated counterclockwise
347 90 degrees, and scaled to the length of line_width. */
348 scale = line_width / sqrt (dx0 * dx0 + dy0 * dy0);
353 printf ("cap style = %d\n", cap);
358 case ART_PATH_STROKE_CAP_BUTT:
359 art_vpath_add_point (p_result, pn_result, pn_result_max,
360 ART_LINETO, vpath[i1].x - dlx0, vpath[i1].y - dly0);
361 art_vpath_add_point (p_result, pn_result, pn_result_max,
362 ART_LINETO, vpath[i1].x + dlx0, vpath[i1].y + dly0);
364 case ART_PATH_STROKE_CAP_ROUND:
365 n_pts = ceil (M_PI / (2.0 * M_SQRT2 * sqrt (flatness / line_width)));
366 art_vpath_add_point (p_result, pn_result, pn_result_max,
367 ART_LINETO, vpath[i1].x - dlx0, vpath[i1].y - dly0);
368 for (i = 1; i < n_pts; i++)
370 double theta, c_th, s_th;
372 theta = M_PI * i / n_pts;
375 art_vpath_add_point (p_result, pn_result, pn_result_max,
377 vpath[i1].x - dlx0 * c_th - dly0 * s_th,
378 vpath[i1].y - dly0 * c_th + dlx0 * s_th);
380 art_vpath_add_point (p_result, pn_result, pn_result_max,
381 ART_LINETO, vpath[i1].x + dlx0, vpath[i1].y + dly0);
383 case ART_PATH_STROKE_CAP_SQUARE:
384 art_vpath_add_point (p_result, pn_result, pn_result_max,
386 vpath[i1].x - dlx0 - dly0,
387 vpath[i1].y - dly0 + dlx0);
388 art_vpath_add_point (p_result, pn_result, pn_result_max,
390 vpath[i1].x + dlx0 - dly0,
391 vpath[i1].y + dly0 + dlx0);
397 * art_svp_from_vpath_raw: Stroke a vector path, raw version
398 * @vpath: #ArtVPath to stroke.
401 * @line_width: Width of stroke.
402 * @miter_limit: Miter limit.
403 * @flatness: Flatness.
405 * Exactly the same as art_svp_vpath_stroke(), except that the resulting
406 * stroke outline may self-intersect and have regions of winding number
409 * Return value: Resulting raw stroked outline in svp format.
412 art_svp_vpath_stroke_raw (ArtVpath *vpath,
413 ArtPathStrokeJoinType join,
414 ArtPathStrokeCapType cap,
419 int begin_idx, end_idx;
421 ArtVpath *forw, *rev;
423 int n_forw_max, n_rev_max;
425 int n_result, n_result_max;
426 double half_lw = 0.5 * line_width;
428 int last, xthis, next, second;
432 forw = art_new (ArtVpath, n_forw_max);
435 rev = art_new (ArtVpath, n_rev_max);
439 result = art_new (ArtVpath, n_result_max);
441 for (begin_idx = 0; vpath[begin_idx].code != ART_END; begin_idx = end_idx)
446 closed = (vpath[begin_idx].code == ART_MOVETO);
448 /* we don't know what the first point joins with until we get to the
449 last point and see if it's closed. So we start with the second
452 Note: this is not strictly true (we now know it's closed from
453 the opening pathcode), but why fix code that isn't broken?
457 /* skip over identical points at the beginning of the subpath */
458 for (i = xthis + 1; vpath[i].code == ART_LINETO; i++)
460 dx = vpath[i].x - vpath[xthis].x;
461 dy = vpath[i].y - vpath[xthis].y;
462 if (dx * dx + dy * dy > EPSILON_2)
468 /* invariant: this doesn't coincide with next */
469 while (vpath[next].code == ART_LINETO)
473 /* skip over identical points after the beginning of the subpath */
474 for (i = xthis + 1; vpath[i].code == ART_LINETO; i++)
476 dx = vpath[i].x - vpath[xthis].x;
477 dy = vpath[i].y - vpath[xthis].y;
478 if (dx * dx + dy * dy > EPSILON_2)
482 if (vpath[next].code != ART_LINETO)
484 /* reached end of path */
485 /* make "closed" detection conform to PostScript
486 semantics (i.e. explicit closepath code rather than
487 just the fact that end of the path is the beginning) */
489 vpath[xthis].x == vpath[begin_idx].x &&
490 vpath[xthis].y == vpath[begin_idx].y)
494 /* path is closed, render join to beginning */
495 render_seg (&forw, &n_forw, &n_forw_max,
496 &rev, &n_rev, &n_rev_max,
497 vpath, last, xthis, second,
498 join, half_lw, miter_limit, flatness);
501 printf ("%% forw %d, rev %d\n", n_forw, n_rev);
503 /* do forward path */
504 art_vpath_add_point (&result, &n_result, &n_result_max,
505 ART_MOVETO, forw[n_forw - 1].x,
507 for (j = 0; j < n_forw; j++)
508 art_vpath_add_point (&result, &n_result, &n_result_max,
509 ART_LINETO, forw[j].x,
512 /* do reverse path, reversed */
513 art_vpath_add_point (&result, &n_result, &n_result_max,
514 ART_MOVETO, rev[0].x,
516 for (j = n_rev - 1; j >= 0; j--)
517 art_vpath_add_point (&result, &n_result, &n_result_max,
518 ART_LINETO, rev[j].x,
526 /* add to forw rather than result to ensure that
527 forw has at least one point. */
528 render_cap (&forw, &n_forw, &n_forw_max,
530 cap, half_lw, flatness);
531 art_vpath_add_point (&result, &n_result, &n_result_max,
532 ART_MOVETO, forw[0].x,
534 for (j = 1; j < n_forw; j++)
535 art_vpath_add_point (&result, &n_result, &n_result_max,
536 ART_LINETO, forw[j].x,
538 for (j = n_rev - 1; j >= 0; j--)
539 art_vpath_add_point (&result, &n_result, &n_result_max,
540 ART_LINETO, rev[j].x,
542 render_cap (&result, &n_result, &n_result_max,
543 vpath, second, begin_idx,
544 cap, half_lw, flatness);
545 art_vpath_add_point (&result, &n_result, &n_result_max,
546 ART_LINETO, forw[0].x,
551 render_seg (&forw, &n_forw, &n_forw_max,
552 &rev, &n_rev, &n_rev_max,
553 vpath, last, xthis, next,
554 join, half_lw, miter_limit, flatness);
562 printf ("%% n_result = %d\n", n_result);
564 art_vpath_add_point (&result, &n_result, &n_result_max, ART_END, 0, 0);
576 print_ps_vpath (ArtVpath *vpath)
580 for (i = 0; vpath[i].code != ART_END; i++)
582 switch (vpath[i].code)
585 printf ("%g %g moveto\n", XOFF + vpath[i].x, YOFF - vpath[i].y);
588 printf ("%g %g lineto\n", XOFF + vpath[i].x, YOFF - vpath[i].y);
594 printf ("stroke showpage\n");
598 print_ps_svp (ArtSVP *vpath)
602 printf ("%% begin\n");
603 for (i = 0; i < vpath->n_segs; i++)
605 printf ("%g setgray\n", vpath->segs[i].dir ? 0.7 : 0);
606 for (j = 0; j < vpath->segs[i].n_points; j++)
608 printf ("%g %g %s\n",
609 XOFF + vpath->segs[i].points[j].x,
610 YOFF - vpath->segs[i].points[j].y,
611 j ? "lineto" : "moveto");
616 printf ("showpage\n");
620 /* Render a vector path into a stroked outline.
622 Status of this routine:
624 Basic correctness: Only miter and bevel line joins are implemented,
625 and only butt line caps. Otherwise, seems to be fine.
627 Numerical stability: We cheat (adding random perturbation). Thus,
628 it seems very likely that no numerical stability problems will be
631 Speed: Should be pretty good.
633 Precision: The perturbation fuzzes the coordinates slightly,
634 but not enough to be visible. */
636 * art_svp_vpath_stroke: Stroke a vector path.
637 * @vpath: #ArtVPath to stroke.
640 * @line_width: Width of stroke.
641 * @miter_limit: Miter limit.
642 * @flatness: Flatness.
644 * Computes an svp representing the stroked outline of @vpath. The
645 * width of the stroked line is @line_width.
647 * Lines are joined according to the @join rule. Possible values are
648 * ART_PATH_STROKE_JOIN_MITER (for mitered joins),
649 * ART_PATH_STROKE_JOIN_ROUND (for round joins), and
650 * ART_PATH_STROKE_JOIN_BEVEL (for bevelled joins). The mitered join
651 * is converted to a bevelled join if the miter would extend to a
652 * distance of more than @miter_limit * @line_width from the actual
655 * If there are open subpaths, the ends of these subpaths are capped
656 * according to the @cap rule. Possible values are
657 * ART_PATH_STROKE_CAP_BUTT (squared cap, extends exactly to end
658 * point), ART_PATH_STROKE_CAP_ROUND (rounded half-circle centered at
659 * the end point), and ART_PATH_STROKE_CAP_SQUARE (squared cap,
660 * extending half @line_width past the end point).
662 * The @flatness parameter controls the accuracy of the rendering. It
663 * is most important for determining the number of points to use to
664 * approximate circular arcs for round lines and joins. In general, the
665 * resulting vector path will be within @flatness pixels of the "ideal"
666 * path containing actual circular arcs. I reserve the right to use
667 * the @flatness parameter to convert bevelled joins to miters for very
668 * small turn angles, as this would reduce the number of points in the
669 * resulting outline path.
671 * The resulting path is "clean" with respect to self-intersections, i.e.
672 * the winding number is 0 or 1 at each point.
674 * Return value: Resulting stroked outline in svp format.
677 art_svp_vpath_stroke (ArtVpath *vpath,
678 ArtPathStrokeJoinType join,
679 ArtPathStrokeCapType cap,
684 #ifdef ART_USE_NEW_INTERSECTOR
685 ArtVpath *vpath_stroke;
689 vpath_stroke = art_svp_vpath_stroke_raw (vpath, join, cap,
690 line_width, miter_limit, flatness);
692 //print_ps_vpath (vpath_stroke);
694 svp = art_svp_from_vpath (vpath_stroke);
696 //print_ps_svp (svp);
698 art_free (vpath_stroke);
700 swr = art_svp_writer_rewind_new (ART_WIND_RULE_NONZERO);
701 art_svp_intersector (svp, swr);
703 svp2 = art_svp_writer_rewind_reap (swr);
705 //print_ps_svp (svp2);
710 ArtVpath *vpath_stroke, *vpath2;
711 ArtSVP *svp, *svp2, *svp3;
713 vpath_stroke = art_svp_vpath_stroke_raw (vpath, join, cap,
714 line_width, miter_limit, flatness);
716 //print_ps_vpath (vpath_stroke);
718 vpath2 = art_vpath_perturb (vpath_stroke);
720 //print_ps_vpath (vpath2);
722 art_free (vpath_stroke);
723 svp = art_svp_from_vpath (vpath2);
725 //print_ps_svp (svp);
728 svp2 = art_svp_uncross (svp);
730 //print_ps_svp (svp2);
733 svp3 = art_svp_rewind_uncrossed (svp2, ART_WIND_RULE_NONZERO);
735 //print_ps_svp (svp3);