[gtk/wip/otte/lottie: 41/43] WIP: path: Add conic curves
- From: Benjamin Otte <otte src gnome org>
- To: commits-list gnome org
- Cc:
- Subject: [gtk/wip/otte/lottie: 41/43] WIP: path: Add conic curves
- Date: Sun, 29 Nov 2020 17:37:01 +0000 (UTC)
commit f3311f7ae3c2b77237e2e742ef70a905c6abef2b
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 | 10 ++++
docs/reference/gsk/gsk4-sections.txt | 2 +
gsk/gskenums.h | 4 ++
gsk/gskpath.c | 94 ++++++++++++++++++++++++++++++----
gsk/gskpath.h | 2 +
gsk/gskpathbuilder.c | 70 +++++++++++++++++++++++++
gsk/gskpathbuilder.h | 14 +++++
gsk/gskspline.c | 99 ++++++++++++++++++++++++++++++++++++
gsk/gsksplineprivate.h | 13 +++++
9 files changed, 297 insertions(+), 11 deletions(-)
---
diff --git a/demos/gtk-demo/path_text.c b/demos/gtk-demo/path_text.c
index 22e57f3eed..144e13a58c 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;
diff --git a/docs/reference/gsk/gsk4-sections.txt b/docs/reference/gsk/gsk4-sections.txt
index 208f7b23f0..8a0838ef3c 100644
--- a/docs/reference/gsk/gsk4-sections.txt
+++ b/docs/reference/gsk/gsk4-sections.txt
@@ -303,6 +303,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/gskenums.h b/gsk/gskenums.h
index 2bfe689ebe..ba8c6ff54f 100644
--- a/gsk/gskenums.h
+++ b/gsk/gskenums.h
@@ -249,6 +249,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.
**/
@@ -257,6 +260,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 582ff2aaf2..43000b759a 100644
--- a/gsk/gskpath.c
+++ b/gsk/gskpath.c
@@ -247,11 +247,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
@@ -613,7 +613,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
@@ -625,7 +625,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,
@@ -639,7 +639,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;
}
@@ -870,13 +870,25 @@ gsk_standard_contour_foreach (const GskContour *contour,
[GSK_PATH_MOVE] = 1,
[GSK_PATH_CLOSE] = 2,
[GSK_PATH_LINE] = 2,
- [GSK_PATH_CURVE] = 4
+ [GSK_PATH_CURVE] = 4,
};
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[2] = { self->points[self->ops[i].point],
+ self->points[self->ops[i].point + 2] };
+ float weight = self->points[self->ops[i].point + 1].x;
+
+ if (!func (GSK_PATH_CONIC, pts, 2, 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;
@@ -926,6 +938,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_double (string, pt[2].x);
+ g_string_append (string, ", ");
+ _g_string_append_point (string, &pt[3]);
+ break;
+
default:
g_assert_not_reached();
return;
@@ -1078,6 +1100,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;
@@ -1138,6 +1168,9 @@ 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 ();
@@ -1371,6 +1404,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();
@@ -1400,6 +1452,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;
@@ -1432,6 +1488,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();
@@ -1812,6 +1878,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)
@@ -1832,6 +1899,11 @@ 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:
+ g_warning ("FIXME: Stop treating conics as lines");
+ cairo_line_to (cr, pts[3].x, pts[3].y);
+ break;
+
default:
g_assert_not_reached ();
return FALSE;
diff --git a/gsk/gskpath.h b/gsk/gskpath.h
index bb8ae9875f..27b01d4a44 100644
--- a/gsk/gskpath.h
+++ b/gsk/gskpath.h
@@ -34,6 +34,7 @@ G_BEGIN_DECLS
* @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
@@ -45,6 +46,7 @@ G_BEGIN_DECLS
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 8ab256ed20..fb5d27bc4b 100644
--- a/gsk/gskpathbuilder.c
+++ b/gsk/gskpathbuilder.c
@@ -549,6 +549,76 @@ 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);
+
+ 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);
+
+ 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 4868ccf25e..dd7379deb8 100644
--- a/gsk/gskpathbuilder.h
+++ b/gsk/gskpathbuilder.h
@@ -92,6 +92,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..3b5b001cf6 100644
--- a/gsk/gskspline.c
+++ b/gsk/gskspline.c
@@ -234,6 +234,105 @@ gsk_spline_decompose_cubic (const graphene_point_t pts[4],
g_assert (decomp.last_progress == 1.0f || decomp.last_progress == 0.0f);
}
+/* CONIC */
+
+typedef struct {
+ graphene_point_t num[3];
+ graphene_point_t denom[3];
+} ConicCoefficients;
+
+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 (-pts[1].x, -pts[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);
+}
+
+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)
+ {
+ graphene_point_t num, denom;
+ gsk_spline_eval_quad (c.num, progress, &num);
+ gsk_spline_eval_quad (c.denom, progress, &denom);
+ *pos = GRAPHENE_POINT_INIT (num.x / denom.x, num.y / denom.y);
+ }
+
+ 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[2])))
+ {
+ graphene_vec2_init (tangent, pts[2].x - pts[0].x, pts[2].y - pts[0].y);
+ return;
+ }
+
+ gsk_spline_eval_quad ((graphene_point_t[3]) {
+ GRAPHENE_POINT_INIT (w * (pts[1].x - pts[0].x),
+ w * (pts[1].y - pts[0].y)),
+ GRAPHENE_POINT_INIT (pts[2].x - pts[0].x - 2 * w * (pts[1].x - pts[0].x),
+ pts[2].y - pts[0].y - 2 * w * (pts[1].y - pts[0].y)),
+ GRAPHENE_POINT_INIT ((w - 1) * (pts[2].x - pts[0].x),
+ (w - 1) * (pts[2].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");
+}
+
+void
+gsk_spline_decompose_conic (const graphene_point_t pts[4],
+ float tolerance,
+ GskSplineAddPointFunc add_point_func,
+ gpointer user_data)
+{
+ g_warning ("FIXME: Stop treating conics as lines");
+}
+
/* Spline deviation from the circle in radius would be given by:
error = sqrt (x**2 + y**2) - 1
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,
[
Date Prev][
Date Next] [
Thread Prev][
Thread Next]
[
Thread Index]
[
Date Index]
[
Author Index]