[gtk/wip/otte/lottie: 39/43] WIP: path: Add conic curves
- From: Benjamin Otte <otte src gnome org>
- To: commits-list gnome org
- Cc:
- Subject: [gtk/wip/otte/lottie: 39/43] WIP: path: Add conic curves
- Date: Sun, 6 Dec 2020 17:57:16 +0000 (UTC)
commit 43361958635cb6edea2aecfc907277ae0fb19d25
Author: Benjamin Otte <otte redhat com>
Date: Sat Nov 28 07:24:05 2020 +0100
WIP: path: Add conic curves
So far this just adds the API, if you use it, you'll get lots of
g_warnings().
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/gskspline.c | 197 ++++++++++++++++++++++++++++++++---
gsk/gsksplineprivate.h | 13 +++
testsuite/gsk/path.c | 23 ++++
11 files changed, 459 insertions(+), 33 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 bedbe6aa5d..4fe09d9edb 100644
--- a/docs/reference/gsk/gsk4-sections.txt
+++ b/docs/reference/gsk/gsk4-sections.txt
@@ -312,6 +312,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
<SUBSECTION Private>
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 9582525408..405c845eba 100644
--- a/gsk/gskenums.h
+++ b/gsk/gskenums.h
@@ -251,6 +251,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.
*
@@ -261,6 +264,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 1e06910b60..9e9dac1023 100644
--- a/gsk/gskpath.c
+++ b/gsk/gskpath.c
@@ -262,6 +262,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)
@@ -282,6 +283,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;
@@ -312,7 +314,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);
@@ -458,6 +460,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);
}
@@ -465,6 +468,7 @@ static gboolean
gsk_path_foreach_trampoline (GskPathOperation op,
const graphene_point_t *pts,
gsize n_pts,
+ float weight,
gpointer data)
{
GskPathForeachTrampoline *trampoline = data;
@@ -474,11 +478,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,
@@ -486,6 +490,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;
@@ -503,7 +518,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;
@@ -628,7 +643,7 @@ parse_command (const char **p,
if (*cmd == 'X')
allowed = "mM";
else
- allowed = "mMhHvVzZlLcCsStTqQaA";
+ allowed = "mMhHvVzZlLcCsStTqQoOaA";
skip_whitespace (p);
s = strchr (allowed, **p);
@@ -1057,6 +1072,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 18d62ff73a..a7341835d3 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/gskspline.c b/gsk/gskspline.c
index ce4f5fa603..9f367cbc4b 100644
--- a/gsk/gskspline.c
+++ b/gsk/gskspline.c
@@ -25,17 +25,18 @@
#include <math.h>
+#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_cubic_decompose (&decomp, pts, 1.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_into (&decomp, pts, 1.0f);
+ gsk_spline_decompose_conic_subdivide (&d, &pts[0], 0.0f, &pts[3], 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 (&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 33ead68fab..6b49b862c0 100644
--- a/testsuite/gsk/path.c
+++ b/testsuite/gsk/path.c
@@ -324,6 +324,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;
@@ -354,6 +364,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);
}
@@ -363,6 +378,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,
@@ -377,6 +393,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;
@@ -943,6 +960,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;
@@ -969,6 +987,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;
[
Date Prev][
Date Next] [
Thread Prev][
Thread Next]
[
Thread Index]
[
Date Index]
[
Author Index]