[gtk/wip/matthiasc/lottie-stroke: 19/24] Add gsk_path_to_stroke
- From: Matthias Clasen <matthiasc src gnome org>
- To: commits-list gnome org
- Cc:
- Subject: [gtk/wip/matthiasc/lottie-stroke: 19/24] Add gsk_path_to_stroke
- Date: Sun, 29 Nov 2020 18:57:12 +0000 (UTC)
commit 16c82c6876fe954e76dbf7d16bb4890bd29a5570
Author: Matthias Clasen <mclasen redhat com>
Date: Fri Nov 27 14:05:25 2020 -0500
Add gsk_path_to_stroke
Implement gsk_path_to_stroke, which takes a path and stroke
parameters, and returns a new path for the outline the would
be produced by stroking the path with these parameters.
There are still some stability issues with sharp joins, and
subdividing is not done yet.
gsk/gskpath.c | 1123 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++
gsk/gskpath.h | 9 +
2 files changed, 1132 insertions(+)
---
diff --git a/gsk/gskpath.c b/gsk/gskpath.c
index c2f476ce22..0169c81589 100644
--- a/gsk/gskpath.c
+++ b/gsk/gskpath.c
@@ -24,6 +24,8 @@
#include "gskpathbuilder.h"
#include "gsksplineprivate.h"
+#include "gskstrokeprivate.h"
+
/**
* SECTION:gskpath
* @Title: Path
@@ -2853,3 +2855,1124 @@ error:
return NULL;
}
+
+/* path-to-stroke */
+
+/* Set n to the normal of the line through p0 and p1 */
+static void
+normal_vector (const graphene_point_t *p0,
+ const graphene_point_t *p1,
+ graphene_vec2_t *n)
+{
+ graphene_vec2_init (n, p0->y - p1->y, p1->x - p0->x);
+ graphene_vec2_normalize (n, n);
+}
+
+static void
+direction_vector (const graphene_point_t *p0,
+ const graphene_point_t *p1,
+ graphene_vec2_t *t)
+{
+ graphene_vec2_init (t, p1->x - p0->x, p1->y - p0->y);
+ graphene_vec2_normalize (t, t);
+}
+
+static void
+get_bezier (const graphene_point_t *points,
+ int length,
+ float t,
+ graphene_point_t *p)
+{
+ if (length == 1)
+ *p = points[0];
+ else
+ {
+ graphene_point_t *newpoints;
+ int i;
+
+ newpoints = g_alloca (sizeof (graphene_point_t) * (length - 1));
+ for (i = 0; i < length - 1; i++)
+ graphene_point_interpolate (&points[i], &points[i + 1], t, &newpoints[i]);
+ get_bezier (newpoints, length - 1, t, p);
+ }
+}
+
+static void
+get_cubic (const graphene_point_t points[4],
+ float t,
+ graphene_point_t *p)
+{
+ get_bezier (points, 4, t, p);
+}
+
+static void
+split_bezier_recurse (const graphene_point_t *points,
+ int length,
+ float t,
+ graphene_point_t *left,
+ graphene_point_t *right,
+ int *lpos,
+ int *rpos)
+{
+ if (length == 1)
+ {
+ left[*lpos] = points[0];
+ (*lpos)++;
+ right[*rpos] = points[length - 1];
+ (*rpos)--;
+ }
+ else
+ {
+ graphene_point_t *newpoints;
+ int i;
+
+ newpoints = g_alloca (sizeof (graphene_point_t) * (length - 1));
+ for (i = 0; i < length - 1; i++)
+ {
+ if (i == 0)
+ {
+ left[*lpos] = points[i];
+ (*lpos)++;
+ }
+ if (i + 1 == length - 1)
+ {
+ right[*rpos] = points[i + 1];
+ (*rpos)--;
+ }
+ graphene_point_interpolate (&points[i], &points[i + 1], t, &newpoints[i]);
+ }
+ split_bezier_recurse (newpoints, length - 1, t, left, right, lpos, rpos);
+ }
+}
+
+/* Given Bezier control points and a t value between 0 and 1,
+ * return new Bezier control points for two segments in left
+ * and right that are obtained by splitting the curve at the
+ * point for t.
+ */
+static void
+split_bezier (const graphene_point_t *points,
+ int length,
+ float t,
+ graphene_point_t *left,
+ graphene_point_t *right)
+{
+ int lpos = 0;
+ int rpos = length - 1;
+
+ split_bezier_recurse (points, length, t, left, right, &lpos, &rpos);
+}
+
+/* compute the angle between a, b and c in the range of [0, 360] */
+static float
+three_point_angle (const graphene_point_t *a,
+ const graphene_point_t *b,
+ const graphene_point_t *c)
+{
+ float angle = atan2 (c->y - b->y, c->x - b->x) - atan2 (a->y - b->y, a->x - b->x);
+
+ if (angle < 0)
+ angle += 2 * M_PI;
+
+ return RAD_TO_DEG (angle);
+}
+
+static gboolean
+cubic_is_simple (const graphene_point_t *pts)
+{
+ float a1, a2, s;
+ graphene_vec2_t n1, n2;
+
+ a1 = three_point_angle (&pts[0], &pts[1], &pts[2]);
+ a2 = three_point_angle (&pts[1], &pts[2], &pts[3]);
+
+ if ((a1 < 180.f && a2 > 180.f) || (a1 > 180.f && a2 < 180.f))
+ return FALSE;
+
+ normal_vector (&pts[0], &pts[1], &n1);
+ normal_vector (&pts[2], &pts[3], &n2);
+
+ s = graphene_vec2_dot (&n1, &n2);
+
+ if (fabs (acos (s)) >= M_PI / 3.f)
+ return FALSE;
+
+ return TRUE;
+}
+
+static gboolean
+acceptable (float t)
+{
+ return 0 <= t && t <= 1;
+}
+
+static float
+cuberoot (float v)
+{
+ if (v < 0)
+ return -pow (-v, 1.f / 3);
+ return pow (v, 1.f / 3);
+}
+
+static int
+get_cubic_roots (float pa,
+ float pb,
+ float pc,
+ float pd,
+ float roots[3])
+{
+ float a, b, c, d;
+ float q, q2;
+ float p, p3;
+ float discriminant;
+ float u1, v1, sd;
+ int n_roots = 0;
+
+ d = -pa + 3*pb - 3*pc + pd;
+ a = 3*pa - 6*pb + 3*pc;
+ b = -3*pa + 3*pb;
+ c = pa;
+
+ if (fabs (d) < 0.0001)
+ {
+ if (fabs (a) < 0.0001)
+ {
+ if (fabs (b) < 0.0001)
+ return 0;
+
+ if (acceptable (-c / b))
+ {
+ roots[0] = -c / b;
+ return 1;
+ }
+
+ return 0;
+ }
+ q = sqrt (b*b - 4*a*c);
+ roots[n_roots] = (-b + q) / (2 * a);
+ if (acceptable (roots[n_roots]))
+ n_roots++;
+ roots[n_roots] = (-b - q) / (2 * a);
+ if (acceptable (roots[n_roots]))
+ n_roots++;
+ return n_roots;
+ }
+
+ a /= d;
+ b /= d;
+ c /= d;
+
+ p = (3*b - a*a)/3;
+ p3 = p/3;
+ q = (2*a*a*a - 9*a*b + 27*c)/27;
+ q2 = q/2;
+ discriminant = q2*q2 + p3*p3*p3;
+
+ if (discriminant < 0)
+ {
+ float mp3 = -p/3;
+ float mp33 = mp3*mp3*mp3;
+ float r = sqrt (mp33);
+ float t = -q / (2*r);
+ float cosphi = t < -1 ? -1 : (t > 1 ? 1 : t);
+ float phi = acos (cosphi);
+ float crtr = cuberoot (r);
+ float t1 = 2*crtr;
+
+ roots[n_roots] = t1 * cos (phi/3) - a/3;
+ if (acceptable (roots[n_roots]))
+ n_roots++;
+ roots[n_roots] = t1 * cos ((phi + 2*M_PI) / 3) - a/3;
+ if (acceptable (roots[n_roots]))
+ n_roots++;
+ roots[n_roots] = t1 * cos ((phi + 4*M_PI) / 3) - a/3;
+ if (acceptable (roots[n_roots]))
+ n_roots++;
+ return n_roots;
+ }
+
+ if (discriminant == 0)
+ {
+ u1 = q2 < 0 ? cuberoot (-q2) : -cuberoot (q2);
+ roots[n_roots] = 2*u1 - a/3;
+ if (acceptable (roots[n_roots]))
+ n_roots++;
+ roots[n_roots] = -u1 - a/3;
+ if (acceptable (roots[n_roots]))
+ n_roots++;
+ return n_roots;
+ }
+
+ sd = sqrt (discriminant);
+ u1 = cuberoot (sd - q2);
+ v1 = cuberoot (sd + q2);
+ roots[n_roots] = u1 - v1 - a/3;
+ if (acceptable (roots[n_roots]))
+ n_roots++;
+ return n_roots;
+}
+
+static int
+get_cubic_extrema (float pa,
+ float pb,
+ float pc,
+ float pd,
+ float roots[2])
+{
+ float a, b, c;
+ float d, t;
+ int n_roots = 0;
+
+ a = 3 * (pd - 3*pc + 3*pb - pa);
+ b = 6 * (pc - 2*pb + pa);
+ c = 3 * (pb - pa);
+
+ if (fabs (a) > 0.0001)
+ {
+ if (b*b > 4*a*c)
+ {
+ d = sqrt (b*b - 4*a*c);
+ t = (-b + d)/(2*a);
+ if (acceptable (t))
+ roots[n_roots++] = t;
+ t = (-b - d)/(2*a);
+ if (acceptable (t))
+ roots[n_roots++] = t;
+ }
+ else
+ {
+ t = -b / (2*a);
+ if (acceptable (t))
+ roots[n_roots++] = t;
+ }
+ }
+ else if (fabs (b) > 0.0001)
+ {
+ t = -c / b;
+ if (acceptable (t))
+ roots[n_roots++] = t;
+ }
+
+ return n_roots;
+}
+
+static int
+get_cubic_inflections (float pa,
+ float pb,
+ float pc,
+ float pd,
+ float roots[1])
+{
+ float a, b;
+ float t;
+ int n_roots = 0;
+
+ a = 3 * (pd - 3*pc + 3*pb - pa);
+ b = 6 * (pc - 2*pb + pa);
+
+ if (fabs (a) > 0.0001)
+ {
+ t = -b / (2 * a);
+ if (acceptable (t))
+ roots[n_roots++] = t;
+ }
+
+ return n_roots;
+}
+
+/* Compute q = p + d * n */
+static void
+scale_point (const graphene_point_t *p,
+ const graphene_vec2_t *n,
+ float d,
+ graphene_point_t *q)
+{
+ q->x = p->x + d * graphene_vec2_get_x (n);
+ q->y = p->y + d * graphene_vec2_get_y (n);
+}
+
+static void
+midpoint (const graphene_point_t *a,
+ const graphene_point_t *b,
+ graphene_point_t *q)
+{
+ q->x = (a->x + b->x) / 2;
+ q->y = (a->y + b->y) / 2;
+}
+
+/* Set p to the intersection of the lines through a, b and c, d.
+ * Return the number of intersections found (0 or 1) */
+static int
+line_intersection (const graphene_point_t *a,
+ const graphene_point_t *b,
+ const graphene_point_t *c,
+ const graphene_point_t *d,
+ graphene_point_t *p)
+{
+ float a1 = b->y - a->y;
+ float b1 = a->x - b->x;
+ float c1 = a1*a->x + b1*a->y;
+
+ float a2 = d->y - c->y;
+ float b2 = c->x - d->x;
+ float c2 = a2*c->x+ b2*c->y;
+
+ float det = a1*b2 - a2*b1;
+
+ if (det == 0)
+ {
+ p->x = NAN;
+ p->y = NAN;
+ return 0;
+ }
+ else
+ {
+ p->x = (b2*c1 - b1*c2) / det;
+ p->y = (a1*c2 - a2*c1) / det;
+ return 1;
+ }
+}
+
+static void
+align_point (const graphene_point_t *p0,
+ const graphene_point_t *a,
+ const graphene_point_t *b,
+ graphene_point_t *q)
+{
+ graphene_vec2_t n;
+ float angle;
+
+ direction_vector (a, b, &n);
+ angle = -atan2 (graphene_vec2_get_y (&n), graphene_vec2_get_x (&n));
+ q->x = (p0->x - a->x) * cos (angle) - (p0->y - a->y) * sin (angle);
+ q->y = (p0->x - a->x) * sin (angle) + (p0->y - a->y) * cos (angle);
+}
+
+/* Place intersections between the line and the curve in q.
+ * Return the number of intersections found (0 to 3).
+ */
+static int
+line_curve_intersection (const graphene_point_t *a,
+ const graphene_point_t *b,
+ const graphene_point_t pts[4],
+ graphene_point_t q[3])
+{
+ graphene_point_t p[4];
+ float t[3];
+ int n, i;
+
+ align_point (&pts[0], a, b, &p[0]);
+ align_point (&pts[1], a, b, &p[1]);
+ align_point (&pts[2], a, b, &p[2]);
+ align_point (&pts[3], a, b, &p[3]);
+
+ n = get_cubic_roots (p[0].y, p[1].y, p[2].y, p[3].y, t);
+ for (i = 0; i < n; i++)
+ get_cubic (pts, t[i], &q[i]);
+
+ return n;
+}
+
+static void
+get_curve_bounds (const graphene_point_t pts[4],
+ graphene_rect_t *bounds)
+{
+ graphene_point_t p;
+ float t[2];
+ int n, i;
+
+ get_cubic (pts, 0, &p);
+ graphene_rect_init (bounds, p.x, p.y, 0, 0);
+
+ get_cubic (pts, 1, &p);
+ graphene_rect_expand (bounds, &p, bounds);
+
+ n = get_cubic_extrema (pts[0].x, pts[1].x, pts[2].x, pts[3].x, t);
+ for (i = 0; i < n; i++)
+ {
+ get_cubic (pts, t[i], &p);
+ graphene_rect_expand (bounds, &p, bounds);
+ }
+
+ n = get_cubic_extrema (pts[0].y, pts[1].y, pts[2].y, pts[3].y, t);
+ for (i = 0; i < n; i++)
+ {
+ get_cubic (pts, t[i], &p);
+ graphene_rect_expand (bounds, &p, bounds);
+ }
+}
+
+static void
+curve_intersection_recurse (const graphene_point_t pts1[4],
+ const graphene_point_t pts2[4],
+ graphene_point_t q[9],
+ int *pos)
+{
+ graphene_rect_t b1, b2;
+
+ graphene_point_t p11[4];
+ graphene_point_t p12[4];
+ graphene_point_t p21[4];
+ graphene_point_t p22[4];
+
+ if (*pos == 9)
+ return;
+
+ get_curve_bounds (pts1, &b1);
+ get_curve_bounds (pts2, &b2);
+
+ if (!graphene_rect_intersection (&b1, &b2, NULL))
+ return;
+
+ if (b1.size.width < 0.1 && b1.size.height < 0.1 &&
+ b2.size.width < 0.1 && b2.size.height < 0.1)
+ {
+ q[*pos] = pts1[0];
+ (*pos)++;
+ return;
+ }
+
+ split_bezier (pts1, 4, 0.5, p11, p12);
+ split_bezier (pts2, 4, 0.5, p21, p22);
+
+ curve_intersection_recurse (p11, p21, q, pos);
+ curve_intersection_recurse (p11, p22, q, pos);
+ curve_intersection_recurse (p12, p21, q, pos);
+ curve_intersection_recurse (p12, p22, q, pos);
+}
+
+/* Place intersections between the curves in q.
+ * Return the number of intersections found (0 to 9).
+ */
+static int
+curve_intersection (const graphene_point_t pts1[4],
+ const graphene_point_t pts2[4],
+ graphene_point_t q[9])
+{
+ int pos = 0;
+
+ curve_intersection_recurse (pts1, pts2, q, &pos);
+
+ return pos;
+}
+
+
+typedef struct
+{
+ GskPathOperation op;
+ int n_pts;
+ graphene_point_t pts[4]; /* points of the curve */
+
+ graphene_point_t r[4]; /* offset to the right */
+ graphene_point_t l[4]; /* offset to the left */
+ graphene_point_t re[2]; /* intersection of adjacent r lines of this and next op */
+ graphene_point_t le[2]; /* intersection of adjacent l lines of this and next op */
+ float angle[2]; /* angles between tangents at the both ends */
+} PathOpData;
+
+
+#if 0
+static const char *
+op_to_string (GskPathOperation op)
+{
+ const char *names[] = { "MOVE", "CLOSE", "LINE", "CURVE" };
+ return names[op];
+}
+
+static void
+assert_right_angle (const graphene_point_t *a,
+ const graphene_point_t *b,
+ const graphene_point_t *c)
+{
+ graphene_vec2_t u, v;
+
+ graphene_vec2_init (&u, a->x - b->x, a->y - b->y);
+ graphene_vec2_init (&v, a->x - c->x, a->y - c->y);
+
+ g_assert_cmpfloat_with_epsilon (graphene_vec2_dot (&u, &v), 0, 0.002);
+}
+
+static void
+assert_same_distance (const graphene_point_t *a,
+ const graphene_point_t *b,
+ const graphene_point_t *c)
+{
+ g_assert_cmpfloat_with_epsilon (graphene_point_distance (a, b, NULL, NULL),
+ graphene_point_distance (b, c, NULL, NULL),
+ 0.001);
+}
+#endif
+
+static void
+compute_offsets (PathOpData *op,
+ float d)
+{
+ graphene_vec2_t n1, n2, n3;
+
+ if (op->op != GSK_PATH_MOVE)
+ {
+ normal_vector (&op->pts[0], &op->pts[1], &n1);
+ scale_point (&op->pts[0], &n1, d, &op->r[0]);
+ scale_point (&op->pts[0], &n1, -d, &op->l[0]);
+
+ normal_vector (&op->pts[op->n_pts - 1], &op->pts[op->n_pts - 2], &n3);
+ scale_point (&op->pts[op->n_pts - 1], &n3, -d, &op->r[op->n_pts - 1]);
+ scale_point (&op->pts[op->n_pts - 1], &n3, d, &op->l[op->n_pts - 1]);
+ }
+
+ if (op->op == GSK_PATH_CURVE)
+ {
+ graphene_point_t m1, m2, m3, m4;
+
+ /* Simply scale control points, a la Tiller and Hanson */
+
+ normal_vector (&op->pts[1], &op->pts[2], &n2);
+
+ scale_point (&op->pts[1], &n1, d, &m1);
+ scale_point (&op->pts[2], &n3, -d, &m4);
+
+ scale_point (&op->pts[1], &n2, d, &m2);
+ scale_point (&op->pts[2], &n2, d, &m3);
+
+ /* FIXME: handle the parallel case */
+ line_intersection (&op->r[0], &m1, &m2, &m3, &op->r[1]);
+ line_intersection (&m2, &m3, &m4, &op->r[3], &op->r[2]);
+
+ scale_point (&op->pts[1], &n1, -d, &m1);
+ scale_point (&op->pts[2], &n3, d, &m4);
+
+ scale_point (&op->pts[1], &n2, -d, &m2);
+ scale_point (&op->pts[2], &n2, -d, &m3);
+
+ line_intersection (&op->l[0], &m1, &m2, &m3, &op->l[1]);
+ line_intersection (&m2, &m3, &m4, &op->l[3], &op->l[2]);
+ }
+
+ op->re[0] = op->r[0];
+ op->le[0] = op->l[0];
+ op->re[1] = op->r[op->n_pts - 1];
+ op->le[1] = op->l[op->n_pts - 1];
+
+#if 0
+ assert_right_angle (&op->pts[0], &op->pts[1], &op->r[0]);
+ assert_right_angle (&op->pts[0], &op->pts[1], &op->l[0]);
+ assert_right_angle (&op->pts[op->n_pts - 1], &op->pts[op->n_pts - 2], &op->r[op->n_pts - 1]);
+ assert_right_angle (&op->pts[op->n_pts - 1], &op->pts[op->n_pts - 2], &op->l[op->n_pts - 1]);
+#endif
+}
+
+static void
+compute_intersections (PathOpData *op1,
+ PathOpData *op2)
+{
+ op1->angle[1] = three_point_angle (&op1->pts[op1->n_pts - 2],
+ &op1->pts[op1->n_pts - 1],
+ &op2->pts[1]);
+
+ op2->angle[0] = op1->angle[1];
+
+ if (fabs (op1->angle[1] - 180.f) >= 1)
+ {
+ line_intersection (&op1->r[op1->n_pts - 2], &op1->r[op1->n_pts - 1],
+ &op2->r[0], &op2->r[1],
+ &op1->re[1]);
+ line_intersection (&op1->l[op1->n_pts - 2], &op1->l[op1->n_pts - 1],
+ &op2->l[0], &op2->l[1],
+ &op1->le[1]);
+
+ if (op1->n_pts == 2 && op2->n_pts == 4)
+ {
+ graphene_point_t p[3];
+ int n;
+
+ if (op1->angle[1] > 180.f)
+ {
+ n = line_curve_intersection (&op1->r[0], &op1->r[1], op2->r, p);
+ if (n > 0)
+ op1->re[1] = p[0];
+ }
+ else
+ {
+ n = line_curve_intersection (&op1->l[0], &op1->l[1], op2->l, p);
+ if (n > 0)
+ op1->le[1] = p[0];
+ }
+ }
+ else if (op1->n_pts == 4 && op2->n_pts == 2)
+ {
+ graphene_point_t p[3];
+ int n;
+
+ if (op1->angle[1] > 180.f)
+ {
+ n = line_curve_intersection (&op2->r[0], &op2->r[1], op1->r, p);
+ if (n > 0)
+ op1->re[1] = p[0];
+ }
+ else
+ {
+ n = line_curve_intersection (&op2->l[0], &op2->l[1], op1->l, p);
+ if (n > 0)
+ op1->le[1] = p[0];
+ }
+ }
+ else if (op1->n_pts == 4 && op2->n_pts == 4)
+ {
+ graphene_point_t p[9];
+ int n;
+
+ if (op1->angle[1] > 180.f)
+ {
+ n = curve_intersection (op1->r, op2->r, p);
+ if (n > 0)
+ op1->re[1] = p[0];
+ }
+ else
+ {
+ n = curve_intersection (op1->l, op2->l, p);
+ if (n > 0)
+ op1->le[1] = p[0];
+ }
+ }
+ }
+ else
+ {
+ midpoint (&op1->r[op1->n_pts - 1], &op2->r[0], &op1->re[1]);
+ midpoint (&op1->l[op1->n_pts - 1], &op2->l[0], &op1->le[1]);
+ }
+
+ op2->re[0] = op1->re[1];
+ op2->le[0] = op1->le[1];
+
+#if 0
+ g_assert_true (graphene_point_near (&op1->re[1], &op2->re[0], 0.001));
+ g_assert_true (graphene_point_near (&op1->le[1], &op2->le[0], 0.001));
+ assert_same_distance (&op1->r[op1->n_pts - 1], &op1->re[1], &op2->r[0]);
+ assert_same_distance (&op1->l[op1->n_pts - 1], &op1->le[1], &op2->l[0]);
+#endif
+}
+
+static PathOpData *
+path_op_data_new (GskPathOperation op,
+ const graphene_point_t *pts,
+ gsize n_pts)
+{
+ PathOpData *d;
+ int i;
+
+ d = g_new0 (PathOpData, 1);
+ d->op = op;
+ for (i = 0; i < n_pts; i++)
+ d->pts[i] = pts[i];
+ d->n_pts = n_pts;
+
+ return d;
+}
+
+typedef struct
+{
+ GList *ops;
+ GskStroke *stroke;
+} AddOpData;
+
+static gboolean
+add_op_to_list (GskPathOperation op,
+ const graphene_point_t *pts,
+ gsize n_pts,
+ float weight,
+ gpointer user_data)
+{
+ AddOpData *data = user_data;
+
+ switch (op)
+ {
+ case GSK_PATH_MOVE:
+ /* We have no use for the initial move, so toss it */
+ break;
+
+ case GSK_PATH_CLOSE:
+ case GSK_PATH_LINE:
+ data->ops = g_list_prepend (data->ops, path_op_data_new (op, pts, n_pts));
+ break;
+
+ case GSK_PATH_CURVE:
+ data->ops = g_list_prepend (data->ops, path_op_data_new (GSK_PATH_CURVE, pts, 4));
+ break;
+
+ case GSK_PATH_CONIC:
+ g_warning ("FIXME: support conics in the stroker");
+ g_assert_not_reached ();
+ break;
+
+ default:
+ g_assert_not_reached ();
+ }
+
+ return TRUE;
+}
+
+static GList *
+contour_to_ops (GskContour *contour,
+ GskStroke *stroke)
+{
+ AddOpData data;
+
+ data.ops = NULL;
+ data.stroke = stroke;
+
+ gsk_contour_foreach (contour, GSK_PATH_TOLERANCE_DEFAULT, add_op_to_list, &data);
+
+ return g_list_reverse (data.ops);
+}
+
+static void
+gsk_contour_to_stroke (GskContour *contour,
+ GskStroke *stroke,
+ GskPathBuilder *builder)
+{
+ GList *ops, *l;
+ GList *last = NULL;
+ PathOpData *op;
+ PathOpData *op1;
+
+ ops = contour_to_ops (contour, stroke);
+
+ /* Compute offset start and end points */
+ for (l = ops; l; l = l->next)
+ {
+ op = l->data;
+ compute_offsets (op, stroke->line_width / 2);
+ }
+
+ /* Compute intersections */
+ for (l = ops; l; l = l->next)
+ {
+ op = l->data;
+ if (l->next != NULL)
+ {
+ op1 = l->next->data;
+ compute_intersections (op, op1);
+ }
+ else
+ last = l;
+ }
+
+ if (contour->klass->get_flags (contour) & GSK_PATH_CLOSED && last)
+ {
+ op = last->data;
+ op1 = ops->data;
+ compute_intersections (op, op1);
+ }
+
+ /* Walk the ops for the right edge */
+ last = NULL;
+ for (l = ops; l; l = l->next)
+ {
+ op = l->data;
+
+ if (l->prev == NULL)
+ gsk_path_builder_move_to (builder, op->re[0].x, op->re[0].y);
+
+ switch (op->op)
+ {
+ case GSK_PATH_MOVE:
+ g_assert_not_reached ();
+ break;
+
+ case GSK_PATH_CLOSE:
+ last = l;
+ break;
+
+ case GSK_PATH_LINE:
+ if (op->angle[1] >= 180)
+ gsk_path_builder_line_to (builder, op->re[1].x, op->re[1].y);
+ else
+ gsk_path_builder_line_to (builder, op->r[1].x, op->r[1].y);
+ break;
+
+ case GSK_PATH_CURVE:
+ if (op->angle[1] >= 180)
+ gsk_path_builder_curve_to (builder, op->r[1].x, op->r[1].y,
+ op->r[2].x, op->r[2].y,
+ op->re[1].x, op->re[1].y);
+ else
+ gsk_path_builder_curve_to (builder, op->r[1].x, op->r[1].y,
+ op->r[2].x, op->r[2].y,
+ op->r[3].x, op->r[3].y);
+ break;
+
+ case GSK_PATH_CONIC:
+ g_warning ("FIXME: support conics in the stroker");
+ g_assert_not_reached ();
+ break;
+
+ default:
+ g_assert_not_reached ();
+ }
+
+ if (l->next || (contour->klass->get_flags (contour) & GSK_PATH_CLOSED))
+ {
+ op1 = l->next ? l->next->data : ops->data;
+
+ if (op->angle[1] < 180)
+ {
+ /* Deal with joins */
+ switch (stroke->line_join)
+ {
+ case GSK_LINE_JOIN_MITER:
+ case GSK_LINE_JOIN_MITER_CLIP:
+ if (op->angle[1] != 0 && fabs (1 / sin (DEG_TO_RAD (op->angle[1]) / 2) <=
stroke->miter_limit))
+ {
+ gsk_path_builder_line_to (builder, op->re[1].x, op->re[1].y);
+ gsk_path_builder_line_to (builder, op1->r[0].x, op1->r[0].y);
+ }
+ else if (stroke->line_join == GSK_LINE_JOIN_MITER_CLIP)
+ {
+ graphene_point_t p, q, p1, p2;
+ graphene_vec2_t t, n;
+
+ direction_vector (&op->pts[op->n_pts - 1], &op->re[1], &t);
+ scale_point (&op->pts[op->n_pts - 1], &t, stroke->line_width * stroke->miter_limit /
2, &p);
+
+ normal_vector (&op->pts[op->n_pts - 1], &op->re[1], &n);
+ scale_point (&p, &n, 1, &q);
+
+ line_intersection (&p, &q, &op->r[1], &op->re[1], &p1);
+ line_intersection (&p, &q, &op1->r[0], &op->re[1], &p2);
+
+ gsk_path_builder_line_to (builder, p1.x, p1.y);
+ gsk_path_builder_line_to (builder, p2.x, p2.y);
+ gsk_path_builder_line_to (builder, op1->r[0].x, op1->r[0].y);
+ }
+ else
+ {
+ gsk_path_builder_line_to (builder, op1->r[0].x, op1->r[0].y);
+ }
+ break;
+
+ case GSK_LINE_JOIN_BEVEL:
+ gsk_path_builder_line_to (builder, op1->r[0].x, op1->r[0].y);
+ break;
+
+ case GSK_LINE_JOIN_ROUND:
+ gsk_path_builder_svg_arc_to (builder,
+ stroke->line_width / 2, stroke->line_width / 2,
+ 0, 0, 0,
+ op1->r[0].x, op1->r[0].y);
+ break;
+
+ default:
+ g_assert_not_reached ();
+ }
+ }
+ }
+
+ if (l->next == NULL)
+ last = l;
+ }
+
+ if (! (contour->klass->get_flags (contour) & GSK_PATH_CLOSED))
+ {
+ /* Deal with the cap at the end */
+ op = last->data;
+ switch (stroke->line_cap)
+ {
+ case GSK_LINE_CAP_BUTT:
+ gsk_path_builder_line_to (builder, op->le[1].x, op->le[1].y);
+ break;
+
+ case GSK_LINE_CAP_ROUND:
+ gsk_path_builder_svg_arc_to (builder,
+ stroke->line_width / 2, stroke->line_width / 2,
+ 0, 1, 0,
+ op->le[1].x, op->le[1].y);
+ break;
+
+ case GSK_LINE_CAP_SQUARE:
+ {
+ float dx = op->re[1].y - op->pts[op->n_pts - 1].y;
+ float dy = - op->re[1].x + op->pts[op->n_pts - 1].x;
+
+ gsk_path_builder_line_to (builder, op->re[1].x + dx, op->re[1].y + dy);
+ gsk_path_builder_line_to (builder, op->le[1].x + dx, op->le[1].y + dy);
+ gsk_path_builder_line_to (builder, op->le[1].x, op->le[1].y);
+ }
+ break;
+
+ default:
+ g_assert_not_reached ();
+ }
+ }
+ else
+ {
+ gsk_path_builder_close (builder);
+
+ /* Start the left contour */
+ op = last->data;
+ if (op->angle[0] <= 180)
+ gsk_path_builder_move_to (builder, op->le[1].x, op->le[1].y);
+ else
+ gsk_path_builder_move_to (builder, op->l[1].x, op->l[1].y);
+ }
+
+ /* Walk the ops backwards for the left edge */
+ for (l = last; l; l = l->prev)
+ {
+ op = l->data;
+
+ switch (op->op)
+ {
+ case GSK_PATH_MOVE:
+ g_assert_not_reached ();
+ break;
+
+ case GSK_PATH_CLOSE:
+ case GSK_PATH_LINE:
+ if (op->angle[0] <= 180)
+ gsk_path_builder_line_to (builder, op->le[0].x, op->le[0].y);
+ else
+ gsk_path_builder_line_to (builder, op->l[0].x, op->l[0].y);
+ break;
+
+ case GSK_PATH_CURVE:
+ if (op->angle[0] <= 180)
+ gsk_path_builder_curve_to (builder, op->l[2].x, op->l[2].y,
+ op->l[1].x, op->l[1].y,
+ op->le[0].x, op->le[0].y);
+ else
+ gsk_path_builder_curve_to (builder, op->l[2].x, op->l[2].y,
+ op->l[1].x, op->l[1].y,
+ op->l[0].x, op->l[0].y);
+ break;
+
+ case GSK_PATH_CONIC:
+ g_warning ("FIXME: support conics in the stroker");
+ g_assert_not_reached ();
+ break;
+
+ default:
+ g_assert_not_reached ();
+ }
+
+ if (l->prev || (contour->klass->get_flags (contour) & GSK_PATH_CLOSED))
+ {
+ op1 = l->prev ? l->prev->data : last->data;
+
+ if (op->angle[0] > 180)
+ {
+ /* Deal with joins */
+ switch (stroke->line_join)
+ {
+ case GSK_LINE_JOIN_MITER:
+ case GSK_LINE_JOIN_MITER_CLIP:
+ if (op->angle[0] != 0 && fabs (1 / sin (DEG_TO_RAD (op->angle[0]) / 2) <=
stroke->miter_limit))
+ {
+ gsk_path_builder_line_to (builder, op->le[0].x, op->le[0].y);
+ gsk_path_builder_line_to (builder, op1->l[op1->n_pts - 1].x, op1->l[op1->n_pts - 1].y);
+ }
+ else if (stroke->line_join == GSK_LINE_JOIN_MITER_CLIP)
+ {
+ graphene_point_t p, q, p1, p2;
+ graphene_vec2_t t, n;
+
+ direction_vector (&op->pts[0], &op->le[0], &t);
+ scale_point (&op->pts[0], &t, stroke->line_width * stroke->miter_limit / 2, &p);
+
+ normal_vector (&op->pts[0], &op->le[0], &n);
+ scale_point (&p, &n, 1, &q);
+
+ line_intersection (&p, &q, &op->l[0], &op->le[0], &p1);
+ line_intersection (&p, &q, &op1->l[1], &op->le[0], &p2);
+
+ gsk_path_builder_line_to (builder, p1.x, p1.y);
+ gsk_path_builder_line_to (builder, p2.x, p2.y);
+ gsk_path_builder_line_to (builder, op1->l[op1->n_pts - 1].x, op1->l[op1->n_pts - 1].y);
+ }
+ else
+ {
+ gsk_path_builder_line_to (builder, op1->l[op1->n_pts - 1].x, op1->l[op1->n_pts - 1].y);
+ }
+ break;
+
+ case GSK_LINE_JOIN_BEVEL:
+ gsk_path_builder_line_to (builder, op1->l[op1->n_pts - 1].x, op1->l[op1->n_pts - 1].y);
+ break;
+
+ case GSK_LINE_JOIN_ROUND:
+ gsk_path_builder_svg_arc_to (builder,
+ stroke->line_width / 2, stroke->line_width / 2,
+ 0, 0, 0,
+ op1->l[op1->n_pts - 1].x, op1->l[op1->n_pts - 1].y);
+ break;
+
+ default:
+ g_assert_not_reached ();
+ }
+ }
+ }
+ }
+
+ if (! (contour->klass->get_flags (contour) & GSK_PATH_CLOSED))
+ {
+ /* Deal with the cap at the beginning */
+ op = ops->data;
+ switch (stroke->line_cap)
+ {
+ case GSK_LINE_CAP_BUTT:
+ gsk_path_builder_line_to (builder, op->re[0].x, op->re[0].y);
+ break;
+
+ case GSK_LINE_CAP_ROUND:
+ gsk_path_builder_svg_arc_to (builder,
+ stroke->line_width / 2, stroke->line_width / 2,
+ 0, 1, 0,
+ op->re[0].x, op->re[0].y);
+ break;
+
+ case GSK_LINE_CAP_SQUARE:
+ {
+ float dx = op->le[0].y - op->pts[0].y;
+ float dy = - op->le[0].x + op->pts[0].x;
+
+ gsk_path_builder_line_to (builder, op->le[0].x + dx, op->le[0].y + dy);
+ gsk_path_builder_line_to (builder, op->re[0].x + dx, op->re[0].y + dy);
+ gsk_path_builder_line_to (builder, op->re[0].x, op->re[0].y);
+ }
+ break;
+
+ default:
+ g_assert_not_reached ();
+ break;
+ }
+ }
+
+ gsk_path_builder_close (builder);
+
+ g_list_free_full (ops, g_free);
+}
+
+/**
+ * gsk_path_to_stroke:
+ * @path: a #GskPath
+ * @stroke: stroke parameters
+ *
+ * Create a new path that follows the outline of the area
+ * that would be affected by stroking along @path with
+ * the given stroke parameters.
+ *
+ * Returns: a new #GskPath
+ */
+GskPath *
+gsk_path_to_stroke (GskPath *path,
+ GskStroke *stroke)
+{
+ GskPathBuilder *builder;
+ int i;
+
+ builder = gsk_path_builder_new ();
+
+ for (i = 0; i < path->n_contours; i++)
+ gsk_contour_to_stroke (path->contours[i], stroke, builder);
+
+ return gsk_path_builder_free_to_path (builder);
+}
diff --git a/gsk/gskpath.h b/gsk/gskpath.h
index 27b01d4a44..033ec77bd1 100644
--- a/gsk/gskpath.h
+++ b/gsk/gskpath.h
@@ -85,6 +85,15 @@ gboolean gsk_path_foreach (GskPath
GskPathForeachFunc func,
gpointer user_data);
+GDK_AVAILABLE_IN_ALL
+gboolean gsk_path_in_fill (GskPath *path,
+ graphene_point_t *point,
+ GskFillRule fill_rule);
+
+GDK_AVAILABLE_IN_ALL
+GskPath * gsk_path_to_stroke (GskPath *path,
+ GskStroke *stroke);
+
G_END_DECLS
#endif /* __GSK_PATH_H__ */
[
Date Prev][
Date Next] [
Thread Prev][
Thread Next]
[
Thread Index]
[
Date Index]
[
Author Index]