From c020eeac70a76da4776bcdce978909fd48ec6ebc Mon Sep 17 00:00:00 2001 From: Matthias Clasen Date: Thu, 29 Jun 2023 23:09:35 -0400 Subject: [PATCH] curve: Add gsk_curve_offset This method creates an offset curve from an existing curve by just moving the control points laterally. This will be used in stroking. --- gsk/gskcurve.c | 173 ++++++++++++++++++++++++++++++++++++++++++ gsk/gskcurveprivate.h | 3 + 2 files changed, 176 insertions(+) diff --git a/gsk/gskcurve.c b/gsk/gskcurve.c index 8787ef2fc6..08540f7766 100644 --- a/gsk/gskcurve.c +++ b/gsk/gskcurve.c @@ -74,6 +74,9 @@ struct _GskCurveClass GskBoundingBox *bounds); void (* get_tight_bounds) (const GskCurve *curve, GskBoundingBox *bounds); + void (* offset) (const GskCurve *curve, + float distance, + GskCurve *offset_curve); }; /* {{{ Utilities */ @@ -87,6 +90,26 @@ get_tangent (const graphene_point_t *p0, graphene_vec2_normalize (t, t); } +static void +get_normal (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); +} + +/* 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); +} + /* Replace a line by an equivalent quad, * and a quad by an equivalent cubic. */ @@ -129,6 +152,40 @@ gsk_curve_elevate (const GskCurve *curve, /* }}} */ /* {{{ Line */ +/* 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 (fabs (det) < 0.001) + { + 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 gsk_line_curve_init_from_points (GskLineCurve *self, GskPathOperation op, @@ -310,6 +367,23 @@ gsk_line_curve_get_bounds (const GskCurve *curve, gsk_bounding_box_init (bounds, &pts[0], &pts[1]); } +static void +gsk_line_curve_offset (const GskCurve *curve, + float distance, + GskCurve *offset_curve) +{ + const GskLineCurve *self = &curve->line; + const graphene_point_t *pts = self->points; + graphene_vec2_t n; + graphene_point_t p[4]; + + get_normal (&pts[0], &pts[1], &n); + scale_point (&pts[0], &n, distance, &p[0]); + scale_point (&pts[1], &n, distance, &p[1]); + + gsk_curve_init (offset_curve, gsk_pathop_encode (GSK_PATH_LINE, p)); +} + static const GskCurveClass GSK_LINE_CURVE_CLASS = { gsk_line_curve_init, gsk_line_curve_init_foreach, @@ -329,6 +403,7 @@ static const GskCurveClass GSK_LINE_CURVE_CLASS = { gsk_line_curve_decompose_curve, gsk_line_curve_get_bounds, gsk_line_curve_get_bounds, + gsk_line_curve_offset, }; /* }}} */ @@ -677,6 +752,32 @@ gsk_quad_curve_get_tight_bounds (const GskCurve *curve, } } +static void +gsk_quad_curve_offset (const GskCurve *curve, + float distance, + GskCurve *offset) +{ + const GskCubicCurve *self = &curve->cubic; + const graphene_point_t *pts = self->points; + graphene_vec2_t n; + graphene_point_t p[4]; + graphene_point_t m1, m2; + + /* Simply scale control points, a la Tiller and Hanson */ + get_normal (&pts[0], &pts[1], &n); + scale_point (&pts[0], &n, distance, &p[0]); + scale_point (&pts[1], &n, distance, &m1); + + get_normal (&pts[1], &pts[2], &n); + scale_point (&pts[1], &n, distance, &m2); + scale_point (&pts[2], &n, distance, &p[2]); + + if (!line_intersection (&p[0], &m1, &m2, &p[2], &p[1])) + gsk_curve_get_point (curve, 0.5, &p[1]); + + gsk_quad_curve_init_from_points (&offset->quad, p); +} + static const GskCurveClass GSK_QUAD_CURVE_CLASS = { gsk_quad_curve_init, gsk_quad_curve_init_foreach, @@ -696,6 +797,7 @@ static const GskCurveClass GSK_QUAD_CURVE_CLASS = { gsk_quad_curve_decompose_curve, gsk_quad_curve_get_bounds, gsk_quad_curve_get_tight_bounds, + gsk_quad_curve_offset, }; /* }}} */ @@ -1159,6 +1261,39 @@ gsk_cubic_curve_get_tight_bounds (const GskCurve *curve, } } +static void +gsk_cubic_curve_offset (const GskCurve *curve, + float distance, + GskCurve *offset) +{ + const GskCubicCurve *self = &curve->cubic; + const graphene_point_t *pts = self->points; + graphene_vec2_t n; + graphene_point_t p[4]; + graphene_point_t m1, m2, m3, m4; + + /* Simply scale control points, a la Tiller and Hanson */ + get_normal (&pts[0], &pts[1], &n); + scale_point (&pts[0], &n, distance, &p[0]); + scale_point (&pts[1], &n, distance, &m1); + + get_normal (&pts[1], &pts[2], &n); + scale_point (&pts[1], &n, distance, &m2); + scale_point (&pts[2], &n, distance, &m3); + + get_normal (&pts[2], &pts[3], &n); + scale_point (&pts[2], &n, distance, &m4); + scale_point (&pts[3], &n, distance, &p[3]); + + if (!line_intersection (&p[0], &m1, &m2, &m3, &p[1])) + p[1] = m1; + + if (!line_intersection (&m2, &m3, &m4, &p[3], &p[2])) + p[2] = m4; + + gsk_cubic_curve_init_from_points (&offset->cubic, p); +} + static const GskCurveClass GSK_CUBIC_CURVE_CLASS = { gsk_cubic_curve_init, gsk_cubic_curve_init_foreach, @@ -1178,6 +1313,7 @@ static const GskCurveClass GSK_CUBIC_CURVE_CLASS = { gsk_cubic_curve_decompose_curve, gsk_cubic_curve_get_bounds, gsk_cubic_curve_get_tight_bounds, + gsk_cubic_curve_offset, }; /* }}} */ @@ -1818,6 +1954,34 @@ gsk_conic_curve_get_tight_bounds (const GskCurve *curve, } } +static void +gsk_conic_curve_offset (const GskCurve *curve, + float distance, + GskCurve *offset) +{ + const GskConicCurve *self = &curve->conic; + const graphene_point_t *pts = self->points; + graphene_vec2_t n; + graphene_point_t p[4]; + graphene_point_t m1, m2; + + /* Simply scale control points, a la Tiller and Hanson */ + get_normal (&pts[0], &pts[1], &n); + scale_point (&pts[0], &n, distance, &p[0]); + scale_point (&pts[1], &n, distance, &m1); + + get_normal (&pts[1], &pts[3], &n); + scale_point (&pts[1], &n, distance, &m2); + scale_point (&pts[3], &n, distance, &p[3]); + + if (!line_intersection (&p[0], &m1, &m2, &p[3], &p[1])) + p[1] = m1; + + p[2] = pts[2]; + + gsk_conic_curve_init_from_points (&offset->conic, p); +} + static const GskCurveClass GSK_CONIC_CURVE_CLASS = { gsk_conic_curve_init, gsk_conic_curve_init_foreach, @@ -1837,6 +2001,7 @@ static const GskCurveClass GSK_CONIC_CURVE_CLASS = { gsk_conic_curve_decompose_curve, gsk_conic_curve_get_bounds, gsk_conic_curve_get_tight_bounds, + gsk_conic_curve_offset, }; /* }}} */ @@ -2029,6 +2194,14 @@ gsk_curve_get_tight_bounds (const GskCurve *curve, get_class (curve->op)->get_tight_bounds (curve, bounds); } +void +gsk_curve_offset (const GskCurve *curve, + float distance, + GskCurve *offset_curve) +{ + get_class (curve->op)->offset (curve, distance, offset_curve); +} + /* }}} */ /* vim:set foldmethod=marker expandtab: */ diff --git a/gsk/gskcurveprivate.h b/gsk/gskcurveprivate.h index 20b3665de7..78611d0a5c 100644 --- a/gsk/gskcurveprivate.h +++ b/gsk/gskcurveprivate.h @@ -163,6 +163,9 @@ void gsk_curve_get_bounds (const GskCurve GskBoundingBox *bounds); void gsk_curve_get_tight_bounds (const GskCurve *curve, GskBoundingBox *bounds); +void gsk_curve_offset (const GskCurve *curve, + float distance, + GskCurve *offset_curve); int gsk_curve_get_curvature_points (const GskCurve *curve, float t[3]);