From 28d583334875a0ec12744b8319583e29b7271b25 Mon Sep 17 00:00:00 2001 From: Benjamin Otte Date: Sat, 28 Nov 2020 07:24:05 +0100 Subject: [PATCH] path: Add conic curves So far this just adds the API, if you use it, you'll get lots of g_warnings(). This will be fixed in future commits. --- demos/gtk-demo/path_text.c | 12 +- docs/reference/gsk/gsk4-sections.txt | 2 + gsk/gskcontour.c | 88 ++++++++++-- gsk/gskenums.h | 4 + gsk/gskpath.c | 58 +++++++- gsk/gskpath.h | 9 +- gsk/gskpathbuilder.c | 72 ++++++++++ gsk/gskpathbuilder.h | 14 ++ gsk/gskrendernodeparser.c | 2 +- gsk/gskspline.c | 197 +++++++++++++++++++++++++-- gsk/gsksplineprivate.h | 13 ++ testsuite/gsk/path.c | 23 ++++ 12 files changed, 460 insertions(+), 34 deletions(-) diff --git a/demos/gtk-demo/path_text.c b/demos/gtk-demo/path_text.c index cb006eb34d..905fd148a4 100644 --- a/demos/gtk-demo/path_text.c +++ b/demos/gtk-demo/path_text.c @@ -103,6 +103,7 @@ static gboolean gtk_path_transform_op (GskPathOperation op, const graphene_point_t *pts, gsize n_pts, + float weight, gpointer data) { GtkPathTransform *transform = data; @@ -135,6 +136,15 @@ gtk_path_transform_op (GskPathOperation op, } break; + case GSK_PATH_CONIC: + { + graphene_point_t res[2]; + gtk_path_transform_point (transform->measure, &pts[1], transform->scale, &res[0]); + gtk_path_transform_point (transform->measure, &pts[2], transform->scale, &res[1]); + gsk_path_builder_conic_to (transform->builder, res[0].x, res[0].y, res[1].x, res[1].y, weight); + } + break; + case GSK_PATH_CLOSE: gsk_path_builder_close (transform->builder); break; @@ -160,7 +170,7 @@ gtk_path_transform (GskPathMeasure *measure, else transform.scale = 1.0f; - gsk_path_foreach (path, GSK_PATH_FOREACH_ALLOW_CURVES, gtk_path_transform_op, &transform); + gsk_path_foreach (path, GSK_PATH_FOREACH_ALLOW_CURVE, gtk_path_transform_op, &transform); return gsk_path_builder_free_to_path (transform.builder); } diff --git a/docs/reference/gsk/gsk4-sections.txt b/docs/reference/gsk/gsk4-sections.txt index 098f2fe82b..947bee04af 100644 --- a/docs/reference/gsk/gsk4-sections.txt +++ b/docs/reference/gsk/gsk4-sections.txt @@ -362,6 +362,8 @@ gsk_path_builder_line_to gsk_path_builder_rel_line_to gsk_path_builder_curve_to gsk_path_builder_rel_curve_to +gsk_path_builder_conic_to +gsk_path_builder_rel_conic_to gsk_path_builder_close GSK_TYPE_PATH_BUILDER diff --git a/gsk/gskcontour.c b/gsk/gskcontour.c index 6c4f2228ff..76595e1145 100644 --- a/gsk/gskcontour.c +++ b/gsk/gskcontour.c @@ -217,11 +217,11 @@ gsk_rect_contour_foreach (const GskContour *contour, GRAPHENE_POINT_INIT (self->x, self->y) }; - return func (GSK_PATH_MOVE, &pts[0], 1, user_data) - && func (GSK_PATH_LINE, &pts[0], 2, user_data) - && func (GSK_PATH_LINE, &pts[1], 2, user_data) - && func (GSK_PATH_LINE, &pts[2], 2, user_data) - && func (GSK_PATH_CLOSE, &pts[3], 2, user_data); + return func (GSK_PATH_MOVE, &pts[0], 1, 0, user_data) + && func (GSK_PATH_LINE, &pts[0], 2, 0, user_data) + && func (GSK_PATH_LINE, &pts[1], 2, 0, user_data) + && func (GSK_PATH_LINE, &pts[2], 2, 0, user_data) + && func (GSK_PATH_CLOSE, &pts[3], 2, 0, user_data); } static gpointer @@ -601,7 +601,7 @@ gsk_circle_contour_curve (const graphene_point_t curve[4], { ForeachWrapper *wrapper = data; - return wrapper->func (GSK_PATH_CURVE, curve, 4, wrapper->user_data); + return wrapper->func (GSK_PATH_CURVE, curve, 4, 0, wrapper->user_data); } static gboolean @@ -613,7 +613,7 @@ gsk_circle_contour_foreach (const GskContour *contour, const GskCircleContour *self = (const GskCircleContour *) contour; graphene_point_t start = GSK_CIRCLE_POINT_INIT (self, self->start_angle); - if (!func (GSK_PATH_MOVE, &start, 1, user_data)) + if (!func (GSK_PATH_MOVE, &start, 1, 0, user_data)) return FALSE; if (!gsk_spline_decompose_arc (&self->center, @@ -627,7 +627,7 @@ gsk_circle_contour_foreach (const GskContour *contour, if (fabs (self->start_angle - self->end_angle) >= 360) { - if (!func (GSK_PATH_CLOSE, (graphene_point_t[2]) { start, start }, 2, user_data)) + if (!func (GSK_PATH_CLOSE, (graphene_point_t[2]) { start, start }, 2, 0, user_data)) return FALSE; } @@ -908,8 +908,21 @@ gsk_standard_contour_foreach (const GskContour *contour, for (i = 0; i < self->n_ops; i ++) { - if (!func (self->ops[i].op, &self->points[self->ops[i].point], n_points[self->ops[i].op], user_data)) - return FALSE; + if (self->ops[i].op == GSK_PATH_CONIC) + { + graphene_point_t pts[3] = { self->points[self->ops[i].point], + self->points[self->ops[i].point + 1], + self->points[self->ops[i].point + 3] }; + float weight = self->points[self->ops[i].point + 2].x; + + if (!func (GSK_PATH_CONIC, pts, 3, weight, user_data)) + return FALSE; + } + else + { + if (!func (self->ops[i].op, &self->points[self->ops[i].point], n_points[self->ops[i].op], 0, user_data)) + return FALSE; + } } return TRUE; @@ -959,6 +972,16 @@ gsk_standard_contour_print (const GskContour *contour, _g_string_append_point (string, &pt[3]); break; + case GSK_PATH_CONIC: + /* This is not valid SVG */ + g_string_append (string, " O "); + _g_string_append_point (string, &pt[1]); + g_string_append (string, ", "); + _g_string_append_point (string, &pt[3]); + g_string_append (string, ", "); + _g_string_append_double (string, pt[2].x); + break; + default: g_assert_not_reached(); return; @@ -1111,6 +1134,14 @@ gsk_standard_contour_init_measure (const GskContour *contour, } break; + case GSK_PATH_CONIC: + { + LengthDecompose decomp = { array, { length, length, 0, 0, pt[0], i } }; + gsk_spline_decompose_conic (pt, tolerance, gsk_standard_contour_measure_add_point, &decomp); + length = decomp.measure.start; + } + break; + default: g_assert_not_reached(); return NULL; @@ -1171,6 +1202,10 @@ gsk_standard_contour_measure_get_point (GskStandardContour *self, gsk_spline_get_point_cubic (pts, progress, pos, tangent); break; + case GSK_PATH_CONIC: + gsk_spline_get_point_conic (pts, progress, pos, tangent); + break; + case GSK_PATH_MOVE: default: g_assert_not_reached (); @@ -1404,6 +1439,25 @@ gsk_standard_contour_add_segment (const GskContour *contour, } break; + case GSK_PATH_CONIC: + { + graphene_point_t *pts = &self->points[self->ops[start_measure->op].point]; + graphene_point_t curve[4], discard[4]; + + gsk_spline_split_conic (pts, discard, curve, start_progress); + if (end_measure && end_measure->op == start_measure->op) + { + graphene_point_t tiny[4]; + gsk_spline_split_conic (curve, tiny, discard, (end_progress - start_progress) / (1 - start_progress)); + gsk_path_builder_move_to (builder, tiny[0].x, tiny[0].y); + gsk_path_builder_conic_to (builder, tiny[1].x, tiny[1].y, tiny[3].x, tiny[3].y, tiny[2].x); + return; + } + gsk_path_builder_move_to (builder, curve[0].x, curve[0].y); + gsk_path_builder_conic_to (builder, curve[1].x, curve[1].y, curve[3].x, curve[3].y, curve[2].x); + } + break; + case GSK_PATH_MOVE: default: g_assert_not_reached(); @@ -1433,6 +1487,10 @@ gsk_standard_contour_add_segment (const GskContour *contour, gsk_path_builder_curve_to (builder, pt[1].x, pt[1].y, pt[2].x, pt[2].y, pt[3].x, pt[3].y); break; + case GSK_PATH_CONIC: + gsk_path_builder_conic_to (builder, pt[1].x, pt[1].y, pt[3].x, pt[3].y, pt[2].x); + break; + default: g_assert_not_reached(); return; @@ -1465,6 +1523,16 @@ gsk_standard_contour_add_segment (const GskContour *contour, } break; + case GSK_PATH_CONIC: + { + graphene_point_t *pts = &self->points[self->ops[end_measure->op].point]; + graphene_point_t curve[4], discard[4]; + + gsk_spline_split_conic (pts, curve, discard, end_progress); + gsk_path_builder_conic_to (builder, curve[1].x, curve[1].y, curve[3].x, curve[3].y, curve[2].x); + } + break; + case GSK_PATH_MOVE: default: g_assert_not_reached(); diff --git a/gsk/gskenums.h b/gsk/gskenums.h index 1255840027..452cc1f7b9 100644 --- a/gsk/gskenums.h +++ b/gsk/gskenums.h @@ -253,6 +253,9 @@ typedef enum { * @GSK_PATH_CURVE: A curve-to operation describing a cubic Bézier curve * with 4 points describing the start point, the two control points * and the end point of the curve. + * @GSK_PATH_CONIC: A weighted quadratic bezier curve with 3 points + * describing the start point, control point and end point of the + * curve. A weight for the curve will be passed, too. * * Path operations can be used to approximate a #GskPath. * @@ -263,6 +266,7 @@ typedef enum { GSK_PATH_CLOSE, GSK_PATH_LINE, GSK_PATH_CURVE, + GSK_PATH_CONIC, } GskPathOperation; /** diff --git a/gsk/gskpath.c b/gsk/gskpath.c index 8808f17742..1642481ab8 100644 --- a/gsk/gskpath.c +++ b/gsk/gskpath.c @@ -266,6 +266,7 @@ static gboolean gsk_path_to_cairo_add_op (GskPathOperation op, const graphene_point_t *pts, gsize n_pts, + float weight, gpointer cr) { switch (op) @@ -286,6 +287,7 @@ gsk_path_to_cairo_add_op (GskPathOperation op, cairo_curve_to (cr, pts[1].x, pts[1].y, pts[2].x, pts[2].y, pts[3].x, pts[3].y); break; + case GSK_PATH_CONIC: default: g_assert_not_reached (); return FALSE; @@ -316,7 +318,7 @@ gsk_path_to_cairo (GskPath *self, g_return_if_fail (cr != NULL); gsk_path_foreach_with_tolerance (self, - GSK_PATH_FOREACH_ALLOW_CURVES, + GSK_PATH_FOREACH_ALLOW_CURVE, cairo_get_tolerance (cr), gsk_path_to_cairo_add_op, cr); @@ -461,6 +463,7 @@ gsk_path_foreach_trampoline_add_point (const graphene_point_t *from, trampoline->retval = trampoline->func (GSK_PATH_LINE, (graphene_point_t[2]) { *from, *to }, 2, + 0.0f, trampoline->user_data); } @@ -468,6 +471,7 @@ static gboolean gsk_path_foreach_trampoline (GskPathOperation op, const graphene_point_t *pts, gsize n_pts, + float weight, gpointer data) { GskPathForeachTrampoline *trampoline = data; @@ -477,11 +481,11 @@ gsk_path_foreach_trampoline (GskPathOperation op, case GSK_PATH_MOVE: case GSK_PATH_CLOSE: case GSK_PATH_LINE: - return trampoline->func (op, pts, n_pts, trampoline->user_data); + return trampoline->func (op, pts, n_pts, weight, trampoline->user_data); case GSK_PATH_CURVE: - if (trampoline->flags & GSK_PATH_FOREACH_ALLOW_CURVES) - return trampoline->func (op, pts, n_pts, trampoline->user_data); + if (trampoline->flags & GSK_PATH_FOREACH_ALLOW_CURVE) + return trampoline->func (op, pts, n_pts, weight, trampoline->user_data); gsk_spline_decompose_cubic (pts, trampoline->tolerance, @@ -489,6 +493,17 @@ gsk_path_foreach_trampoline (GskPathOperation op, trampoline); return trampoline->retval; + case GSK_PATH_CONIC: + if (trampoline->flags & GSK_PATH_FOREACH_ALLOW_CONIC) + return trampoline->func (op, pts, n_pts, weight, trampoline->user_data); + + /* XXX: decompose into curves if allowed */ + gsk_spline_decompose_conic ((graphene_point_t[4]) { pts[0], pts[1], { weight, }, pts[2] }, + trampoline->tolerance, + gsk_path_foreach_trampoline_add_point, + trampoline); + return trampoline->retval; + default: g_assert_not_reached (); return FALSE; @@ -506,7 +521,7 @@ gsk_path_foreach_with_tolerance (GskPath *self, gsize i; /* If we need to massage the data, set up a trampoline here */ - if (flags != GSK_PATH_FOREACH_ALLOW_CURVES) + if (flags != (GSK_PATH_FOREACH_ALLOW_CURVE | GSK_PATH_FOREACH_ALLOW_CONIC)) { trampoline = (GskPathForeachTrampoline) { flags, tolerance, func, user_data, TRUE }; func = gsk_path_foreach_trampoline; @@ -631,7 +646,7 @@ parse_command (const char **p, if (*cmd == 'X') allowed = "mM"; else - allowed = "mMhHvVzZlLcCsStTqQaA"; + allowed = "mMhHvVzZlLcCsStTqQoOaA"; skip_whitespace (p); s = strchr (allowed, **p); @@ -1060,6 +1075,37 @@ gsk_path_parse (const char *string) } break; + case 'O': + case 'o': + { + double x1, y1, x2, y2, weight; + + if (parse_coordinate_pair (&p, &x1, &y1) && + parse_coordinate_pair (&p, &x2, &y2) && + parse_nonnegative_number (&p, &weight)) + { + if (cmd == 'c') + { + x1 += x; + y1 += y; + x2 += x; + y2 += y; + } + if (strchr ("zZ", prev_cmd)) + { + gsk_path_builder_move_to (builder, x, y); + path_x = x; + path_y = y; + } + gsk_path_builder_conic_to (builder, x1, y1, x2, y2, weight); + x = x2; + y = y2; + } + else + goto error; + } + break; + case 'A': case 'a': { diff --git a/gsk/gskpath.h b/gsk/gskpath.h index 328da7dfbb..31f4cd10e9 100644 --- a/gsk/gskpath.h +++ b/gsk/gskpath.h @@ -31,7 +31,9 @@ G_BEGIN_DECLS /** * GskPathForeachFlags: - * @GSK_PATH_FOREACH_ALLOW_CURVES: Allow emission of %GSK_PATH_CURVE + * @GSK_PATH_FOREACH_ALLOW_CURVE: Allow emission of %GSK_PATH_CURVE + * operations. + * @GSK_PATH_FOREACH_ALLOW_CONIC: Allow emission of %GSK_PATH_CONIC * operations. * * Flags that can be passed to gsk_path_foreach() to enable additional @@ -43,7 +45,8 @@ G_BEGIN_DECLS */ typedef enum { - GSK_PATH_FOREACH_ALLOW_CURVES = (1 << 0) + GSK_PATH_FOREACH_ALLOW_CURVE = (1 << 0), + GSK_PATH_FOREACH_ALLOW_CONIC = (1 << 1) } GskPathForeachFlags; /** @@ -51,6 +54,7 @@ typedef enum * @op: The operation to perform * @pts: The points of the operation * @n_pts: The number of points + * @weight: The weight for conic curves, or unused if not a conic curve. * @user_data: The user data provided with the function * * Prototype of the callback to iterate throught the operations of @@ -62,6 +66,7 @@ typedef enum typedef gboolean (* GskPathForeachFunc) (GskPathOperation op, const graphene_point_t *pts, gsize n_pts, + float weight, gpointer user_data); #define GSK_TYPE_PATH (gsk_path_get_type ()) diff --git a/gsk/gskpathbuilder.c b/gsk/gskpathbuilder.c index 7dee9016a1..d7d53c8441 100644 --- a/gsk/gskpathbuilder.c +++ b/gsk/gskpathbuilder.c @@ -549,6 +549,78 @@ gsk_path_builder_rel_curve_to (GskPathBuilder *builder, builder->current_point.y + y3); } +/** + * gsk_path_builder_conic_to: + * @builder: a #GskPathBuilder + * @x1: x coordinate of control point + * @y1: y coordinate of control point + * @x2: x coordinate of the end of the curve + * @y2: y coordinate of the end of the curve + * @weight: weight of the curve + * + * Adds a [conic curve](https://en.wikipedia.org/wiki/Non-uniform_rational_B-spline) + * from the current point to @x2, @y2 with the given + * @weight and @x1, @y1 as the single control point. + * + * Conic curves can be used to draw ellipses and circles. + * + * After this, @x2, @y2 will be the new current point. + **/ +void +gsk_path_builder_conic_to (GskPathBuilder *builder, + float x1, + float y1, + float x2, + float y2, + float weight) +{ + g_return_if_fail (builder != NULL); + g_return_if_fail (weight >= 0); + + builder->flags &= ~GSK_PATH_FLAT; + gsk_path_builder_append_current (builder, + GSK_PATH_CONIC, + 3, (graphene_point_t[3]) { + GRAPHENE_POINT_INIT (x1, y1), + GRAPHENE_POINT_INIT (weight, 0), + GRAPHENE_POINT_INIT (x2, y2) + }); +} + +/** + * gsk_path_builder_rel_conic_to: + * @builder: a #GskPathBuilder + * @x1: x offset of control point + * @y1: y offset of control point + * @x2: x offset of the end of the curve + * @y2: y offset of the end of the curve + * @weight: weight of the curve + * + * Adds a [conic curve](https://en.wikipedia.org/wiki/Non-uniform_rational_B-spline) + * from the current point to @x2, @y2 with the given + * @weight and @x1, @y1 as the single control point. + * + * This is the relative version of gsk_path_builder_conic_to(). + **/ +void +gsk_path_builder_rel_conic_to (GskPathBuilder *builder, + float x1, + float y1, + float x2, + float y2, + float weight) +{ + g_return_if_fail (builder != NULL); + g_return_if_fail (weight >= 0); + + gsk_path_builder_conic_to (builder, + builder->current_point.x + x1, + builder->current_point.y + y1, + builder->current_point.x + x2, + builder->current_point.y + y2, + weight); +} + /** * gsk_path_builder_close: * @builder: a #GskPathBuilder diff --git a/gsk/gskpathbuilder.h b/gsk/gskpathbuilder.h index c2ee8157ba..fbcac022fa 100644 --- a/gsk/gskpathbuilder.h +++ b/gsk/gskpathbuilder.h @@ -99,6 +99,20 @@ void gsk_path_builder_rel_curve_to (GskPathBuilder float x3, float y3); GDK_AVAILABLE_IN_ALL +void gsk_path_builder_conic_to (GskPathBuilder *builder, + float x1, + float y1, + float x2, + float y2, + float weight); +GDK_AVAILABLE_IN_ALL +void gsk_path_builder_rel_conic_to (GskPathBuilder *builder, + float x1, + float y1, + float x2, + float y2, + float weight); +GDK_AVAILABLE_IN_ALL void gsk_path_builder_close (GskPathBuilder *builder); G_END_DECLS diff --git a/gsk/gskrendernodeparser.c b/gsk/gskrendernodeparser.c index 3f2e9c659a..0a7e05a3f9 100644 --- a/gsk/gskrendernodeparser.c +++ b/gsk/gskrendernodeparser.c @@ -2663,7 +2663,7 @@ append_path_param (Printer *p, for (s = str; *s; s++) { if (*s == ' ' && - (s[1] == 'M' || s[1] == 'C' || s[1] == 'Z' || s[1] == 'L')) + (s[1] == 'M' || s[1] == 'C' || s[1] == 'Z' || s[1] == 'L' || s[1] == 'O')) *s = '\n'; } append_escaping_newlines (p->str, str); diff --git a/gsk/gskspline.c b/gsk/gskspline.c index d429e9d04f..711226229c 100644 --- a/gsk/gskspline.c +++ b/gsk/gskspline.c @@ -25,17 +25,18 @@ #include +#define MIN_PROGRESS (1/1024.f) + typedef struct { graphene_point_t last_point; float last_progress; - float tolerance; GskSplineAddPointFunc func; gpointer user_data; -} GskCubicDecomposition; +} GskSplineDecompose; static void -gsk_spline_decompose_add_point (GskCubicDecomposition *decomp, +gsk_spline_decompose_add_point (GskSplineDecompose *decomp, const graphene_point_t *pt, float progress) { @@ -47,6 +48,20 @@ gsk_spline_decompose_add_point (GskCubicDecomposition *decomp, decomp->last_progress += progress; } +static void +gsk_spline_decompose_finish (GskSplineDecompose *decomp, + const graphene_point_t *end_point) +{ + g_assert (graphene_point_equal (&decomp->last_point, end_point)); + g_assert (decomp->last_progress == 1.0f || decomp->last_progress == 0.0f); +} + +typedef struct +{ + GskSplineDecompose decomp; + float tolerance; +} GskCubicDecomposition; + static void gsk_spline_cubic_get_coefficients (graphene_point_t coeffs[4], const graphene_point_t pts[4]) @@ -202,22 +217,22 @@ gsk_spline_cubic_too_curvy (const graphene_point_t pts[4], } static void -gsk_spline_decompose_into (GskCubicDecomposition *decomp, - const graphene_point_t pts[4], - float progress) +gsk_spline_cubic_decompose (GskCubicDecomposition *d, + const graphene_point_t pts[4], + float progress) { graphene_point_t left[4], right[4]; - if (!gsk_spline_cubic_too_curvy (pts, decomp->tolerance) || progress < 1 / 1024.f) + if (!gsk_spline_cubic_too_curvy (pts, d->tolerance) || progress < MIN_PROGRESS) { - gsk_spline_decompose_add_point (decomp, &pts[3], progress); + gsk_spline_decompose_add_point (&d->decomp, &pts[3], progress); return; } gsk_spline_split_cubic (pts, left, right, 0.5); - gsk_spline_decompose_into (decomp, left, progress / 2); - gsk_spline_decompose_into (decomp, right, progress / 2); + gsk_spline_cubic_decompose (d, left, progress / 2); + gsk_spline_cubic_decompose (d, right, progress / 2); } void @@ -226,12 +241,166 @@ gsk_spline_decompose_cubic (const graphene_point_t pts[4], GskSplineAddPointFunc add_point_func, gpointer user_data) { - GskCubicDecomposition decomp = { pts[0], 0.0f, tolerance, add_point_func, user_data }; + GskCubicDecomposition decomp = { { pts[0], 0.0f, add_point_func, user_data }, tolerance }; - gsk_spline_decompose_into (&decomp, pts, 1.0f); + gsk_spline_cubic_decompose (&decomp, pts, 1.0f); - g_assert (graphene_point_equal (&decomp.last_point, &pts[3])); - g_assert (decomp.last_progress == 1.0f || decomp.last_progress == 0.0f); + gsk_spline_decompose_finish (&decomp.decomp, &pts[3]); +} + +/* CONIC */ + +typedef struct { + graphene_point_t num[3]; + graphene_point_t denom[3]; +} ConicCoefficients; + +typedef struct +{ + GskSplineDecompose decomp; + float tolerance; + ConicCoefficients c; +} GskConicDecomposition; + + +static void +gsk_spline_conic_get_coefficents (ConicCoefficients *c, + const graphene_point_t pts[4]) +{ + float w = pts[2].x; + graphene_point_t pw = GRAPHENE_POINT_INIT (w * pts[1].x, w * pts[1].y); + + c->num[2] = pts[0]; + c->num[1] = GRAPHENE_POINT_INIT (2 * (pw.x - pts[0].x), + 2 * (pw.y - pts[0].y)); + c->num[0] = GRAPHENE_POINT_INIT (pts[3].x - 2 * pw.x + pts[0].x, + pts[3].y - 2 * pw.y + pts[0].y); + + c->denom[2] = GRAPHENE_POINT_INIT (1, 1); + c->denom[1] = GRAPHENE_POINT_INIT (2 * (w - 1), 2 * (w - 1)); + c->denom[0] = GRAPHENE_POINT_INIT (-c->denom[1].x, -c->denom[1].y); +} + +static inline void +gsk_spline_eval_quad (const graphene_point_t quad[3], + float progress, + graphene_point_t *result) +{ + *result = GRAPHENE_POINT_INIT ((quad[0].x * progress + quad[1].x) * progress + quad[2].x, + (quad[0].y * progress + quad[1].y) * progress + quad[2].y); +} + +static inline void +gsk_spline_eval_conic (const ConicCoefficients *c, + float progress, + graphene_point_t *result) +{ + graphene_point_t num, denom; + + gsk_spline_eval_quad (c->num, progress, &num); + gsk_spline_eval_quad (c->denom, progress, &denom); + *result = GRAPHENE_POINT_INIT (num.x / denom.x, num.y / denom.y); +} + +void +gsk_spline_get_point_conic (const graphene_point_t pts[4], + float progress, + graphene_point_t *pos, + graphene_vec2_t *tangent) +{ + ConicCoefficients c; + + gsk_spline_conic_get_coefficents (&c, pts); + + if (pos) + gsk_spline_eval_conic (&c, progress, pos); + + if (tangent) + { + graphene_point_t tmp; + float w = pts[2].x; + + /* The tangent will be 0 in these corner cases, just + * treat it like a line here. */ + if ((progress <= 0.f && graphene_point_equal (&pts[0], &pts[1])) || + (progress >= 1.f && graphene_point_equal (&pts[1], &pts[3]))) + { + graphene_vec2_init (tangent, pts[3].x - pts[0].x, pts[3].y - pts[0].y); + return; + } + + gsk_spline_eval_quad ((graphene_point_t[3]) { + GRAPHENE_POINT_INIT ((w - 1) * (pts[3].x - pts[0].x), + (w - 1) * (pts[3].y - pts[0].y)), + GRAPHENE_POINT_INIT (pts[3].x - pts[0].x - 2 * w * (pts[1].x - pts[0].x), + pts[3].y - pts[0].y - 2 * w * (pts[1].y - pts[0].y)), + GRAPHENE_POINT_INIT (w * (pts[1].x - pts[0].x), + w * (pts[1].y - pts[0].y)) + }, + progress, + &tmp); + graphene_vec2_init (tangent, tmp.x, tmp.y); + graphene_vec2_normalize (tangent, tangent); + } +} + +void +gsk_spline_split_conic (const graphene_point_t pts[4], + graphene_point_t result1[4], + graphene_point_t result2[4], + float progress) +{ + g_warning ("FIXME: Stop treating conics as lines"); +} + +/* taken from Skia, including the very descriptive name */ +static gboolean +gsk_spline_conic_too_curvy (const graphene_point_t *start, + const graphene_point_t *mid, + const graphene_point_t *end, + float tolerance) +{ + return fabs ((start->x + end->x) * 0.5 - mid->x) > tolerance + || fabs ((start->y + end->y) * 0.5 - mid->y) > tolerance; +} + +static void +gsk_spline_decompose_conic_subdivide (GskConicDecomposition *d, + const graphene_point_t *start, + float start_progress, + const graphene_point_t *end, + float end_progress) +{ + graphene_point_t mid; + float mid_progress; + + mid_progress = (start_progress + end_progress) / 2; + gsk_spline_eval_conic (&d->c, mid_progress, &mid); + + if (end_progress - start_progress < MIN_PROGRESS || + !gsk_spline_conic_too_curvy (start, &mid, end, d->tolerance)) + { + gsk_spline_decompose_add_point (&d->decomp, end, end_progress - start_progress); + return; + } + + gsk_spline_decompose_conic_subdivide (d, start, start_progress, &mid, mid_progress); + gsk_spline_decompose_conic_subdivide (d, &mid, mid_progress, end, end_progress); +} + +void +gsk_spline_decompose_conic (const graphene_point_t pts[4], + float tolerance, + GskSplineAddPointFunc add_point_func, + gpointer user_data) +{ + GskConicDecomposition d = { { pts[0], 0.0f, add_point_func, user_data }, tolerance, }; + + gsk_spline_conic_get_coefficents (&d.c, pts); + + gsk_spline_decompose_conic_subdivide (&d, &pts[0], 0.0f, &pts[3], 1.0f); + + gsk_spline_decompose_finish (&d.decomp, &pts[3]); } /* Spline deviation from the circle in radius would be given by: diff --git a/gsk/gsksplineprivate.h b/gsk/gsksplineprivate.h index 7bd6282402..02556937ce 100644 --- a/gsk/gsksplineprivate.h +++ b/gsk/gsksplineprivate.h @@ -44,6 +44,19 @@ void gsk_spline_decompose_cubic (const graphene_ GskSplineAddPointFunc add_point_func, gpointer user_data); +void gsk_spline_get_point_conic (const graphene_point_t pts[4], + float progress, + graphene_point_t *pos, + graphene_vec2_t *tangent); +void gsk_spline_split_conic (const graphene_point_t pts[4], + graphene_point_t result1[4], + graphene_point_t result2[4], + float progress); +void gsk_spline_decompose_conic (const graphene_point_t pts[4], + float tolerance, + GskSplineAddPointFunc add_point_func, + gpointer user_data); + typedef gboolean (* GskSplineAddCurveFunc) (const graphene_point_t curve[4], gpointer user_data); gboolean gsk_spline_decompose_arc (const graphene_point_t *center, diff --git a/testsuite/gsk/path.c b/testsuite/gsk/path.c index 193c2e045c..20eda06ff1 100644 --- a/testsuite/gsk/path.c +++ b/testsuite/gsk/path.c @@ -349,6 +349,16 @@ path_operation_print (const PathOperation *p, _g_string_append_point (string, &p->pts[3]); break; + case GSK_PATH_CONIC: + /* This is not valid SVG */ + g_string_append (string, " O "); + _g_string_append_point (string, &p->pts[1]); + g_string_append (string, ", "); + _g_string_append_point (string, &p->pts[2]); + g_string_append (string, ", "); + _g_string_append_double (string, p->weight); + break; + default: g_assert_not_reached(); return; @@ -379,6 +389,11 @@ path_operation_equal (const PathOperation *p1, && graphene_point_near (&p1->pts[2], &p2->pts[2], epsilon) && graphene_point_near (&p1->pts[3], &p2->pts[3], epsilon); + case GSK_PATH_CONIC: + return graphene_point_near (&p1->pts[1], &p2->pts[1], epsilon) + && graphene_point_near (&p1->pts[2], &p2->pts[2], epsilon) + && G_APPROX_VALUE (p1->weight, p2->weight, epsilon); + default: g_return_val_if_reached (FALSE); } @@ -388,6 +403,7 @@ static gboolean collect_path_operation_cb (GskPathOperation op, const graphene_point_t *pts, gsize n_pts, + float weight, gpointer user_data) { g_array_append_vals (user_data, @@ -402,6 +418,7 @@ collect_path_operation_cb (GskPathOperation op, GRAPHENE_POINT_INIT(n_pts > 3 ? pts[3].x : 0, n_pts > 3 ? pts[3].y : 0) }, + weight }, 1); return TRUE; @@ -968,6 +985,7 @@ static gboolean rotate_path_cb (GskPathOperation op, const graphene_point_t *pts, gsize n_pts, + float weight, gpointer user_data) { GskPathBuilder **builders = user_data; @@ -994,6 +1012,11 @@ rotate_path_cb (GskPathOperation op, gsk_path_builder_curve_to (builders[1], pts[1].y, -pts[1].x, pts[2].y, -pts[2].x, pts[3].y, -pts[3].x); break; + case GSK_PATH_CONIC: + gsk_path_builder_conic_to (builders[0], pts[2].x, pts[2].y, pts[3].x, pts[3].y, weight); + gsk_path_builder_conic_to (builders[1], pts[2].y, -pts[2].x, pts[3].y, -pts[3].x, weight); + break; + default: g_assert_not_reached (); return FALSE;