[gtk/wip/otte/lottie: 38/38] WIP: path: Add conic curves




commit 767938fd4fbffe6701e23e3f0d3ed89e69ed131c
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/gskcontour.c                     |  87 ++++++++++++++--
 gsk/gskenums.h                       |   4 +
 gsk/gskpath.c                        |  21 +++-
 gsk/gskpath.h                        |   7 +-
 gsk/gskpathbuilder.c                 |  70 +++++++++++++
 gsk/gskpathbuilder.h                 |  14 +++
 gsk/gskspline.c                      | 197 ++++++++++++++++++++++++++++++++---
 gsk/gsksplineprivate.h               |  13 +++
 10 files changed, 397 insertions(+), 28 deletions(-)
---
diff --git a/demos/gtk-demo/path_text.c b/demos/gtk-demo/path_text.c
index 8a3b69432b..bc49c681f7 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 9e9984d9d1..f7257991c1 100644
--- a/docs/reference/gsk/gsk4-sections.txt
+++ b/docs/reference/gsk/gsk4-sections.txt
@@ -306,6 +306,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..b7fc59833f 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,20 @@ 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[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;
@@ -959,6 +971,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;
@@ -1111,6 +1133,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 +1201,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 +1438,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 +1486,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 +1522,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 aae7587251..05f356c5a7 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.
  *
@@ -259,6 +262,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..4285fbdc1b 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;
@@ -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);
+        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_CONICS)
+        return trampoline->func (op, pts, n_pts, weight, trampoline->user_data);
+
+      /* XXX: decompose into curves if allowed */
+      gsk_spline_decompose_conic (pts,
+                                  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_CURVES | GSK_PATH_FOREACH_ALLOW_CONICS))
     {
       trampoline = (GskPathForeachTrampoline) { flags, tolerance, func, user_data, TRUE };
       func = gsk_path_foreach_trampoline;
diff --git a/gsk/gskpath.h b/gsk/gskpath.h
index 328da7dfbb..009b8fe5e8 100644
--- a/gsk/gskpath.h
+++ b/gsk/gskpath.h
@@ -33,6 +33,8 @@ G_BEGIN_DECLS
  * GskPathForeachFlags:
  * @GSK_PATH_FOREACH_ALLOW_CURVES: Allow emission of %GSK_PATH_CURVE
  *     operations.
+ * @GSK_PATH_FOREACH_ALLOW_CONICS: Allow emission of %GSK_PATH_CONIC
+ *     operations.
  *
  * Flags that can be passed to gsk_path_foreach() to enable additional
  * features.
@@ -43,7 +45,8 @@ G_BEGIN_DECLS
  */
 typedef enum
 {
-  GSK_PATH_FOREACH_ALLOW_CURVES = (1 << 0)
+  GSK_PATH_FOREACH_ALLOW_CURVES = (1 << 0),
+  GSK_PATH_FOREACH_ALLOW_CONICS = (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 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 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..a94a447148 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 < 1 / 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 (-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);
+}
+
+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[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");
+}
+
+/* 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 (start_progress - end_progress < MIN_PROGRESS ||
+      !gsk_spline_conic_too_curvy (start, &mid, end, d->tolerance))
+    {
+      gsk_spline_decompose_add_point (&d->decomp, end, end_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,


[Date Prev][Date Next]   [Thread Prev][Thread Next]   [Thread Index] [Date Index] [Author Index]