[gtk/path-ops: 17/19] path: Implement boolean ops
- From: Matthias Clasen <matthiasc src gnome org>
- To: commits-list gnome org
- Cc:
- Subject: [gtk/path-ops: 17/19] path: Implement boolean ops
- Date: Fri, 25 Mar 2022 13:56:53 +0000 (UTC)
commit a19e2596d1ab1b28dd2b32079bbcb0a6d1cd785c
Author: Matthias Clasen <mclasen redhat com>
Date: Thu Mar 24 01:41:02 2022 -0400
path: Implement boolean ops
This adds apis for union, intersection, difference
and symmetric difference of two paths.
The implementation is not super-robust yet.
gsk/gskpath.h | 16 ++
gsk/gskpathsimplify.c | 765 ++++++++++++++++++++++++++++++++++++++------------
2 files changed, 605 insertions(+), 176 deletions(-)
---
diff --git a/gsk/gskpath.h b/gsk/gskpath.h
index 60188009aa..feb7a3e9a5 100644
--- a/gsk/gskpath.h
+++ b/gsk/gskpath.h
@@ -124,6 +124,22 @@ GskPath * gsk_path_offset (GskPath
GskLineJoin line_join,
float miter_limit);
+GDK_AVAILABLE_IN_ALL
+GskPath * gsk_path_union (GskPath *first,
+ GskPath *second);
+
+GDK_AVAILABLE_IN_ALL
+GskPath * gsk_path_intersection (GskPath *first,
+ GskPath *second);
+
+GDK_AVAILABLE_IN_ALL
+GskPath * gsk_path_difference (GskPath *first,
+ GskPath *second);
+
+GDK_AVAILABLE_IN_ALL
+GskPath * gsk_path_symmetric_difference (GskPath *first,
+ GskPath *second);
+
GDK_AVAILABLE_IN_ALL
GskPath * gsk_path_simplify (GskPath *self);
diff --git a/gsk/gskpathsimplify.c b/gsk/gskpathsimplify.c
index 835eca5220..b79af4cca7 100644
--- a/gsk/gskpathsimplify.c
+++ b/gsk/gskpathsimplify.c
@@ -48,8 +48,13 @@ typedef struct
GskCurve curve;
Connection *start;
Connection *end;
- gboolean internal;
+ gboolean interior;
+ gboolean area_to_the_left;
+ gboolean area_to_the_right;
+ gboolean classified;
gboolean collected;
+ float start_angle;
+ float end_angle;
char *name;
} Curve;
@@ -60,15 +65,6 @@ curve_free (Curve *c)
g_free (c);
}
-typedef struct
-{
- GList *curves;
- GList *connections;
- Curve *start;
-} SimplifyData;
-
-static int collect_num = 0;
-
static gboolean
is_stationary_close (GskCurve *curve)
{
@@ -76,12 +72,23 @@ is_stationary_close (GskCurve *curve)
graphene_point_near (&curve->line.points[0], &curve->line.points[1], 0.01);
}
+static gboolean
+is_tiny (GskCurve *curve)
+{
+ GskBoundingBox bounds;
+
+ gsk_curve_get_bounds (curve, &bounds);
+
+ return bounds.max.x - bounds.min.x < 0.01 &&
+ bounds.max.y - bounds.min.y < 0.01;
+}
+
static void
merge_connections (GList **connections,
Connection *c1,
Connection *c2)
{
- g_assert (graphene_point_near (&c1->p, &c2->p, 0.1));
+ //g_assert (graphene_point_near (&c1->p, &c2->p, 0.1));
if (c1 == c2)
return;
@@ -101,17 +108,24 @@ merge_connections (GList **connections,
connection_free (c2);
}
+typedef struct
+{
+ GList *curves;
+ GList *connections;
+ Curve *start;
+ int num;
+} CollectData;
+
static gboolean
-simplify_collect_cb (GskPathOperation op,
- const graphene_point_t *pts,
- gsize n_pts,
- float weight,
- gpointer user_data)
+collect_cb (GskPathOperation op,
+ const graphene_point_t *pts,
+ gsize n_pts,
+ float weight,
+ gpointer user_data)
{
- SimplifyData *data = user_data;
+ CollectData *data = user_data;
Curve *curve;
Connection *connection;
- const char *opname[] = { "Move", "Close", "Line", "Curve", "Conic" };
g_assert (op != GSK_PATH_CONIC);
@@ -139,6 +153,7 @@ simplify_collect_cb (GskPathOperation op,
curve->curve.op = GSK_PATH_MOVE;
curve->curve.line.points[0] = pts[0];
curve->end = connection;
+ g_ptr_array_add (curve->end->edges, curve);
data->curves = g_list_prepend (data->curves, curve);
data->start = curve;
@@ -182,7 +197,10 @@ simplify_collect_cb (GskPathOperation op,
}
#ifdef G_ENABLE_DEBUG
- curve->name = g_strdup_printf ("%s %d", opname[op], collect_num++);
+ {
+ const char *opname[] = { "Move", "Close", "Line", "Curve", "Conic" };
+ curve->name = g_strdup_printf ("%s %d", opname[op], data->num++);
+ }
#endif
return TRUE;
@@ -192,61 +210,82 @@ static void
gsk_path_builder_append_curve (GskPathBuilder *builder,
GskCurve *curve)
{
- switch (curve->op)
- {
- case GSK_PATH_CLOSE:
- gsk_path_builder_close (builder);
- break;
-
- case GSK_PATH_MOVE:
- gsk_path_builder_move_to (builder, curve->line.points[0].x, curve->line.points[0].y);
- break;
-
- case GSK_PATH_LINE:
- gsk_path_builder_line_to (builder, curve->line.points[1].x, curve->line.points[1].y);
- break;
-
- case GSK_PATH_CURVE:
- gsk_path_builder_curve_to (builder,
- curve->curve.points[1].x, curve->curve.points[1].y,
- curve->curve.points[2].x, curve->curve.points[2].y,
- curve->curve.points[3].x, curve->curve.points[3].y);
- break;
-
- case GSK_PATH_CONIC:
- gsk_path_builder_conic_to (builder,
- curve->conic.points[1].x, curve->conic.points[1].y,
- curve->conic.points[2].x, curve->conic.points[2].y,
- curve->conic.points[3].x);
- break;
-
- default:
- g_assert_not_reached ();
- }
+ if (curve->op == GSK_PATH_MOVE)
+ gsk_path_builder_move_to (builder, curve->line.points[0].x, curve->line.points[0].y);
+ else
+ gsk_curve_builder_to (curve, builder);
}
-#define NEAR(f1, f2) (fabs ((f2) - (f1)) < 1e-3)
+typedef enum
+{
+ PATH_OP_SIMPLIFY,
+ PATH_OP_UNION,
+ PATH_OP_INTERSECTION,
+ PATH_OP_DIFFERENCE,
+ PATH_OP_XOR
+} PathOp;
+
+typedef struct
+{
+ PathOp operation;
+ GskPathMeasure *first;
+ GskPathMeasure *second;
+} PathOpData;
static gboolean
-curve_is_external (GskCurve *curve,
- GskPathMeasure *measure)
+point_is_inside (graphene_point_t *p,
+ PathOpData *opdata)
+{
+ gboolean in1, in2;
+
+ in1 = gsk_path_measure_in_fill (opdata->first, p, GSK_FILL_RULE_WINDING);
+ if (opdata->second)
+ in2 = gsk_path_measure_in_fill (opdata->second, p, GSK_FILL_RULE_WINDING);
+ else
+ in2 = FALSE;
+ switch (opdata->operation)
+ {
+ case PATH_OP_SIMPLIFY: return in1;
+ case PATH_OP_UNION: return in1 || in2;
+ case PATH_OP_INTERSECTION: return in1 && in2;
+ case PATH_OP_DIFFERENCE: return in1 && !in2;
+ case PATH_OP_XOR: return in1 != in2;
+ default: g_assert_not_reached ();
+ }
+}
+
+static void
+classify_boundary (Curve *curve,
+ PathOpData *opdata)
{
graphene_point_t pos, pos1, pos2;
graphene_vec2_t normal;
- if (curve->op == GSK_PATH_MOVE || is_stationary_close (curve))
- return TRUE;
+ gsk_curve_get_point (&curve->curve, 0.5, &pos);
+ gsk_curve_get_normal (&curve->curve, 0.5, &normal);
- gsk_curve_get_point (curve, 0.5, &pos);
- gsk_curve_get_normal (curve, 0.5, &normal);
+ pos1.x = pos.x + 0.5 * graphene_vec2_get_x (&normal);
+ pos1.y = pos.y + 0.5 * graphene_vec2_get_y (&normal);
+ pos2.x = pos.x - 0.5 * graphene_vec2_get_x (&normal);
+ pos2.y = pos.y - 0.5 * graphene_vec2_get_y (&normal);
- pos1.x = pos.x + 5 * graphene_vec2_get_x (&normal);
- pos1.y = pos.y + 5 * graphene_vec2_get_y (&normal);
- pos2.x = pos.x - 5 * graphene_vec2_get_x (&normal);
- pos2.y = pos.y - 5 * graphene_vec2_get_y (&normal);
+ curve->area_to_the_left = point_is_inside (&pos1, opdata);
+ curve->area_to_the_right = point_is_inside (&pos2, opdata);
+ curve->interior = curve->area_to_the_left == curve->area_to_the_right;
+ curve->classified = TRUE;
+}
- return gsk_path_measure_in_fill (measure, &pos1, GSK_FILL_RULE_WINDING) !=
- gsk_path_measure_in_fill (measure, &pos2, GSK_FILL_RULE_WINDING);
+static void
+compute_angles (Curve *curve)
+{
+ graphene_vec2_t tangent;
+
+ gsk_curve_get_start_tangent (&curve->curve, &tangent);
+ curve->start_angle = atan2 (- graphene_vec2_get_y (&tangent),
+ - graphene_vec2_get_x (&tangent));
+ gsk_curve_get_end_tangent (&curve->curve, &tangent);
+ curve->end_angle = atan2 (graphene_vec2_get_y (&tangent),
+ graphene_vec2_get_x (&tangent));
}
static Curve *
@@ -254,33 +293,118 @@ find_next (Curve *curve)
{
Connection *c = curve->end;
Curve *next = NULL;
+ guint idx;
+ int dir;
- g_print ("at %s:\n", curve->name);
- for (int i = 0; i < c->edges->len; i++)
+ g_assert (c != NULL);
+ g_assert (c->edges != NULL);
+
+ GSK_NOTE (PATHS,
+ g_print ("at %s:\n", curve->name);
+ for (int i = 0; i < c->edges->len; i++)
+ {
+ Curve *n = g_ptr_array_index (c->edges, i);
+ const char *ind = " ";
+ float angle;
+
+ if (n->collected) ind = "()";
+ else if (n->interior) ind = "[]";
+
+ angle = n->start == c ? n->start_angle : n->end_angle;
+
+ g_print ("\t%c%s%c %g\n", ind[0], n->name, ind[1], angle);
+ }
+ );
+
+ if (curve->curve.op == GSK_PATH_MOVE)
{
- Curve *n = g_ptr_array_index (c->edges, i);
- g_print ("\t%s\n", n->name);
- }
+ /* If we start a new contour, we just need to pick an
+ * eligible outgoing curve.
+ */
+ for (int d = 0; d < c->edges->len; d++)
+ {
+ Curve *n = g_ptr_array_index (c->edges, d);
+
+ if (n->collected || n->interior)
+ continue;
+
+ if (n->curve.op == GSK_PATH_MOVE)
+ continue;
- for (int i = 0; i < c->edges->len; i++)
+ if (n->end == c)
+ continue;
+
+ next = n;
+ break;
+ }
+ }
+ else
{
- Curve *n = g_ptr_array_index (c->edges, i);
+ /* Edges are sorted counterclockwise by their tangent.
+ * We pick the next eligible edge to the left
+ * or to the right of curve, depending on whether
+ * the left or right is inside.
+ */
+ g_ptr_array_find (c->edges, curve, &idx);
+ dir = curve->area_to_the_left ? -1 : 1;
+ for (int d = 0; d < c->edges->len; d++)
+ {
+ guint pos = (idx + dir * (d + 1)) % c->edges->len;
+ Curve *n = g_ptr_array_index (c->edges, pos);
+ float angle;
- if (n->collected || n->internal)
- continue;
+ if (n->collected || n->interior)
+ continue;
- if (next != NULL && n->curve.op == GSK_PATH_CLOSE)
- continue;
+ if (n->curve.op == GSK_PATH_MOVE)
+ continue;
- if (next != NULL && next->start == c)
- continue;
+ angle = n->end == c ? n->end_angle : n->start_angle;
+ if (fabs (angle - curve->end_angle) < 0.0001)
+ continue;
- if (next != NULL)
- g_print ("Two candidates. Do the sorting dance!\n");
+ next = n;
+ break;
+ }
+ }
- next = n;
+ if (next && next->end == c)
+ {
+ GskCurve r;
+ Connection *s;
+ gboolean a;
+
+ GSK_NOTE (PATHS, g_print ("reversing %s\n", next->name));
+ gsk_curve_reverse (&next->curve, &r);
+ next->curve = r;
+ s = next->start;
+ next->start = next->end;
+ next->end = s;
+ a = next->area_to_the_left;
+ next->area_to_the_left = next->area_to_the_right;
+ next->area_to_the_right = a;
}
+ GSK_NOTE (PATHS,
+ if (next == NULL)
+ {
+ g_print ("at %s: next is NULL\n", curve->name);
+ for (int i = 0; i < c->edges->len; i++)
+ {
+ Curve *n = g_ptr_array_index (c->edges, i);
+ const char *ind = " ";
+ float angle;
+
+ if (n->collected) ind = "()";
+ else if (n->interior) ind = "[]";
+
+ angle = n->start == c ? n->start_angle : n->end_angle;
+
+ g_print ("\t%c%s%c %g\n", ind[0], n->name, ind[1], angle);
+ }
+ }
+ );
+
return next;
}
@@ -289,21 +413,22 @@ validate_curves (GList *curves,
gboolean split)
{
#ifdef G_ENABLE_DEBUG
- Curve *start;
for (GList *l = curves; l; l = l->next)
{
Curve *c = l->data;
GSK_NOTE (PATHS,
- g_print ("%s%s: ", c->internal ? "(" : "", c->name);
+ g_print ("%s%s: ", c->interior ? "(" : "", c->name);
if (c->curve.op != GSK_PATH_MOVE)
gsk_curve_print (&c->curve);
else
g_print ("%g %g", c->curve.line.points[0].x, c->curve.line.points[0].y);
- g_print ("%s\n", c->internal ? ")" : "");
+ g_print ("%s\n", c->interior ? ")" : "");
);
+#if 0
+ Curve *start;
if (c->curve.op == GSK_PATH_MOVE)
{
start = NULL;
@@ -317,9 +442,11 @@ validate_curves (GList *curves,
g_assert (c->start != NULL);
g_assert (c->end != NULL);
- g_assert (graphene_point_near (&c->start->p, gsk_curve_get_start_point (&c->curve), 0.001));
- g_assert (graphene_point_near (&c->end->p, gsk_curve_get_end_point (&c->curve), 0.001));
+ g_assert (graphene_point_near (&c->start->p, gsk_curve_get_start_point (&c->curve), 0.1));
+ g_assert (graphene_point_near (&c->end->p, gsk_curve_get_end_point (&c->curve), 0.1));
+#if 0
+ /* This isn't true with MOVE */
if (split)
{
g_assert (c->start->edges->len % 2 == 0);
@@ -332,6 +459,7 @@ validate_curves (GList *curves,
g_assert (c->start->edges->len == 2);
g_assert (c->end->edges->len == 2);
}
+#endif
g_assert (g_ptr_array_find (c->start->edges, c, &pos));
g_assert (g_ptr_array_find (c->end->edges, c, &pos));
@@ -342,6 +470,7 @@ validate_curves (GList *curves,
g_assert (g_ptr_array_find (start->start->edges, c, &pos));
}
}
+#endif
}
#endif
}
@@ -351,57 +480,122 @@ g_list_insert_after (GList *l,
gpointer data)
{
GList *res G_GNUC_UNUSED;
-
+ /* Silence warn-if-unused */
res = g_list_insert_before (l, l->next, data);
}
-/**
- * gsk_path_simplify:
- * @self: a `GskPath`
- *
- * Create a new path that describes the area as @self,
- * without overlapping contours.
- *
- * Returns: a new `GskPath`
- */
-GskPath *
-gsk_path_simplify (GskPath *self)
+static GskPath *
+gsk_path_remove_unclosed (GskPath *path)
{
- SimplifyData data;
+ GSList *contours;
+ GskPath *path2;
+
+ contours = NULL;
+ for (int i = 0; i < gsk_path_get_n_contours (path); i++)
+ {
+ const GskContour *contour = gsk_path_get_contour (path, i);
+ if (gsk_contour_get_flags (contour) & GSK_PATH_CLOSED)
+ contours = g_slist_prepend (contours, (gpointer)contour);
+ }
+
+ GSK_NOTE (PATHS, g_print ("%ld contours, %d closed\n", gsk_path_get_n_contours (path), g_slist_length
(contours)));
+
+ contours = g_slist_reverse (contours);
+ path2 = gsk_path_new_from_contours (contours);
+ g_slist_free (contours);
+
+ gsk_path_unref (path);
+
+ return path2;
+}
+
+#define NEAR(f1, f2) (fabs ((f2) - (f1)) < 0.0001)
+
+typedef struct
+{
+ float t1;
+ float t2;
+ graphene_point_t p;
+ Connection *connection;
+} SplitPoint;
+
+static int
+compare_t1 (const void *p1,
+ const void *p2)
+{
+ const SplitPoint *sp1 = p1;
+ const SplitPoint *sp2 = p2;
+ return sp1->t1 < sp2->t1 ? -1 : (sp1->t1 > sp2->t1 ? 1 : 0);
+}
+
+static int
+compare_t2 (const void *p1,
+ const void *p2)
+{
+ const SplitPoint *sp1 = p1;
+ const SplitPoint *sp2 = p2;
+ return sp1->t2 < sp2->t2 ? -1 : (sp1->t2 > sp2->t2 ? 1 : 0);
+}
+
+static int
+compare_angle (gconstpointer p1,
+ gconstpointer p2,
+ gpointer user_data)
+{
+ Connection *c = user_data;
+ Curve *c1 = *(Curve **)p1;
+ Curve *c2 = *(Curve **)p2;
+ float f1 = c1->start == c ? c1->start_angle : c1->end_angle;
+ float f2 = c2->start == c ? c2->start_angle : c2->end_angle;
+
+ return f1 < f2 ? -1 : (f1 > f2 ? 1 : 0);
+}
+
+static GskPath *
+gsk_path_op (PathOp operation,
+ GskPath *self,
+ GskPath *other)
+{
+ GList *curves;
+ GList *connections;
+ CollectData data;
GskPathBuilder *builder;
GList *l, *ll;
- GskPathMeasure *measure;
- gboolean closed;
+ PathOpData opdata;
/* Special-case convex paths */
- if (gsk_path_compute_convexity (self) == GSK_CONVEXITY_CONVEX)
+ if (operation == PATH_OP_SIMPLIFY &&
+ gsk_path_compute_convexity (self) == GSK_CONVEXITY_CONVEX)
return gsk_path_ref (self);
GSK_NOTE (PATHS, g_print ("collecting\n"));
data.curves = NULL;
data.connections = NULL;
+ data.num = 0;
- collect_num = 0;
- gsk_path_foreach (self, GSK_PATH_FOREACH_ALLOW_CURVE, simplify_collect_cb, &data);
+ gsk_path_foreach (self, GSK_PATH_FOREACH_ALLOW_CURVE, collect_cb, &data);
+ if (other)
+ gsk_path_foreach (other, GSK_PATH_FOREACH_ALLOW_CURVE, collect_cb, &data);
- data.curves = g_list_reverse (data.curves);
- data.connections = g_list_reverse (data.connections);
+ curves = g_list_reverse (data.curves);
+ connections = g_list_reverse (data.connections);
- validate_curves (data.curves, FALSE);
+ validate_curves (curves, FALSE);
- GSK_NOTE (PATHS, g_print ("intersecting\n"));
- l = data.curves;
+ GSK_NOTE (PATHS, g_print ("splitting\n"));
+ l = curves;
while (l)
{
Curve *cd1 = l->data;
while (l && (cd1->curve.op == GSK_PATH_MOVE ||
- is_stationary_close (&cd1->curve)))
+ is_tiny (&cd1->curve)))
{
l = l->next;
- if (l)
- cd1 = l->data;
+ if (!l)
+ break;
+ cd1 = l->data;
}
if (!l)
@@ -410,44 +604,61 @@ gsk_path_simplify (GskPath *self)
ll = l;
while (ll)
{
- Curve *cd2 = ll->data;
+ Curve *cd2;
float t1[9], t2[9];
- graphene_point_t p [9];
+ graphene_point_t p[9];
+ SplitPoint sp[9];
int n;
- GList *next;
- Connection *connection;
GskCurve cs, ce;
Curve *cd;
+ GList *before;
+ char *name;
+
+ cd1 = l->data;
+ cd2 = ll->data;
while (ll && (cd2->curve.op == GSK_PATH_MOVE ||
- is_stationary_close (&cd2->curve)))
+ is_tiny (&cd2->curve)))
{
ll = ll->next;
- if (ll)
- cd2 = ll->data;
+ if (!ll)
+ break;
+ cd2 = ll->data;
}
if (!ll)
break;
- next = ll->next;
-
n = gsk_curve_intersect (&cd1->curve, &cd2->curve, t1, t2, p, sizeof (p));
+
+ GSK_NOTE (PATHS,
+ if (n > 1 || (n == 1 && (!NEAR (t1[0], 0) && !NEAR (t1[0], 1))))
+ g_print ("%d intersections between %s and %s\n",
+ n, cd1->name, cd2->name);
+ );
+
+ for (int i = 0; i < n; i++)
+ sp[i] = (SplitPoint) { t1[i], t2[i], p[i], NULL};
+
+ qsort (sp, n, sizeof (SplitPoint), compare_t1);
+
+ name = g_strdup (cd1->name);
+ before = l;
for (int i = 0; i < n; i++)
{
- if (NEAR (t1[i], 0))
- connection = cd1->start;
- else if (NEAR (t1[i], 1))
- connection = cd1->end;
+ if (NEAR (sp[i].t1, 0))
+ sp[i].connection = cd1->start;
+ else if (NEAR (sp[i].t1, 1))
+ sp[i].connection = cd1->end;
else
{
- connection = g_new0 (Connection, 1);
- connection->p = p[i];
- connection->edges = g_ptr_array_new ();
+ sp[i].connection = g_new0 (Connection, 1);
+ sp[i].connection->p = sp[i].p;
+ sp[i].connection->edges = g_ptr_array_new ();
- data.connections = g_list_prepend (data.connections, connection);
+ connections = g_list_prepend (connections, sp[i].connection);
- gsk_curve_split (&cd1->curve, t1[i], &cs, &ce);
+ gsk_curve_split (&cd1->curve, sp[i].t1, &cs, &ce);
cd1->curve = cs;
cd = g_new0 (Curve, 1);
@@ -455,26 +666,61 @@ gsk_path_simplify (GskPath *self)
cd->end = cd1->end;
g_ptr_array_remove (cd->end->edges, cd1);
g_ptr_array_add (cd->end->edges, cd);
+
#ifdef G_ENABLE_DEBUG
- cd->name = g_strdup_printf ("%s.%d", cd1->name, i);
+ if (i == 0)
+ {
+ char *nn = g_strdup_printf ("%s.0", name);
+ g_free (cd1->name);
+ cd1->name = nn;
+ }
+ cd->name = g_strdup_printf ("%s.%d", name, i + 1);
#endif
- cd1->end = connection;
- cd->start = connection;
-
- g_list_insert_after (l, cd);
-
- g_ptr_array_add (connection->edges, cd1);
- g_ptr_array_add (connection->edges, cd);
+ GSK_NOTE (PATHS,
+ if (i == 0)
+ {
+ g_print ("split %s from %s at %g\n", cd1->name, name, sp[i].t1);
+ g_print ("%s%s: ", cd1->interior ? "(" : "", cd1->name);
+ gsk_curve_print (&cd1->curve);
+ g_print ("%s\n", cd1->interior ? ")" : "");
+ }
+ );
+ GSK_NOTE (PATHS,
+ g_print ("split %s from %s at %g\n", cd->name, name, sp[i].t1);
+ g_print ("%s%s: ", cd->interior ? "(" : "", cd->name);
+ gsk_curve_print (&cd->curve);
+ g_print ("%s\n", cd->interior ? ")" : "");
+ );
+
+ cd1->end = sp[i].connection;
+ cd->start = sp[i].connection;
+
+ g_list_insert_after (before, cd);
+ before = before->next;
+
+ g_ptr_array_add (sp[i].connection->edges, cd1);
+ g_ptr_array_add (sp[i].connection->edges, cd);
+
+ cd1 = cd;
+ for (int j = i + 1; j < n; j++)
+ sp[j].t1 = (sp[j].t1 - sp[i].t1) / (1 - sp[i].t1);
}
+ }
+ g_free (name);
+
+ qsort (sp, n, sizeof (SplitPoint), compare_t2);
- if (NEAR (t2[i], 0))
- merge_connections (&data.connections, connection, cd2->start);
- else if (NEAR (t2[i], 1))
- merge_connections (&data.connections, connection, cd2->end);
+ name = g_strdup (cd2->name);
+ for (int i = 0; i < n; i++)
+ {
+ if (NEAR (sp[i].t2, 0))
+ merge_connections (&connections, sp[i].connection, cd2->start);
+ else if (NEAR (sp[i].t2, 1))
+ merge_connections (&connections, sp[i].connection, cd2->end);
else
{
- gsk_curve_split (&cd2->curve, t2[i], &cs, &ce);
+ gsk_curve_split (&cd2->curve, sp[i].t2, &cs, &ce);
cd2->curve = cs;
cd = g_new0 (Curve, 1);
@@ -482,21 +728,50 @@ gsk_path_simplify (GskPath *self)
cd->end = cd2->end;
g_ptr_array_remove (cd->end->edges, cd2);
g_ptr_array_add (cd->end->edges, cd);
+
#ifdef G_ENABLE_DEBUG
- cd->name = g_strdup_printf ("%s.%d", cd2->name, i);
+ if (i == 0)
+ {
+ char *nn = g_strdup_printf ("%s.0", name);
+ g_free (cd2->name);
+ cd2->name = nn;
+ }
+ cd->name = g_strdup_printf ("%s.%d", name, i + 1);
#endif
- cd2->end = connection;
- cd->start = connection;
+ GSK_NOTE (PATHS,
+ if (i == 0)
+ {
+ g_print ("split %s from %s at %g\n", cd2->name, name, sp[i].t2);
+ g_print ("%s%s: ", cd2->interior ? "(" : "", cd2->name);
+ gsk_curve_print (&cd2->curve);
+ g_print ("%s\n", cd2->interior ? ")" : "");
+ }
+ );
+ GSK_NOTE (PATHS,
+ g_print ("split %s from %s at %g\n", cd->name, name, sp[i].t2);
+ g_print ("%s%s: ", cd->interior ? "(" : "", cd->name);
+ gsk_curve_print (&cd->curve);
+ g_print ("%s\n", cd->interior ? ")" : "");
+ );
+
+ cd2->end = sp[i].connection;
+ cd->start = sp[i].connection;
g_list_insert_after (ll, cd);
+ ll = ll->next;
+
+ g_ptr_array_add (sp[i].connection->edges, cd2);
+ g_ptr_array_add (sp[i].connection->edges, cd);
- g_ptr_array_add (connection->edges, cd2);
- g_ptr_array_add (connection->edges, cd);
+ cd2 = cd;
+ for (int j = i + 1; j < n; j++)
+ sp[j].t2 = (sp[j].t2 - sp[i].t2) / (1 - sp[i].t2);
}
}
+ g_free (name);
- ll = next;
+ ll = ll->next;
}
l = l->next;
@@ -504,78 +779,216 @@ gsk_path_simplify (GskPath *self)
GSK_NOTE (PATHS, g_print ("classifying\n"));
- measure = gsk_path_measure_new (self);
+ opdata.operation = operation;
+ opdata.first = gsk_path_measure_new (self);
+ opdata.second = other ? gsk_path_measure_new (other) : NULL;
- for (l = data.curves; l; l = l->next)
+ for (l = curves; l; l = l->next)
{
Curve *curve = l->data;
- curve->internal = !curve_is_external (&curve->curve, measure);
+ if (curve->curve.op == GSK_PATH_MOVE || is_stationary_close (&curve->curve))
+ continue;
+
+ compute_angles (curve);
+
+#if 0
+ if (curve->start->edges->len == 2)
+ {
+ Curve *other_curve;
+
+ if (curve == g_ptr_array_index (curve->start->edges, 0))
+ other_curve = g_ptr_array_index (curve->start->edges, 1);
+ else
+ other_curve = g_ptr_array_index (curve->start->edges, 0);
+ if (other_curve->classified)
+ {
+ /* propagate classification */
+ curve->area_to_the_left = other_curve->area_to_the_left;
+ curve->area_to_the_right = other_curve->area_to_the_right;
+ curve->interior = other_curve->interior;
+ curve->classified = TRUE;
+ continue;
+ }
+ }
+#endif
+
+ classify_boundary (curve, &opdata);
}
- gsk_path_measure_unref (measure);
+ gsk_path_measure_unref (opdata.first);
+ if (opdata.second)
+ gsk_path_measure_unref (opdata.second);
+
+ validate_curves (curves, TRUE);
- validate_curves (data.curves, TRUE);
+ GSK_NOTE (PATHS, g_print ("sorting\n"));
+
+ for (l = connections; l; l = l->next)
+ {
+ Connection *c = l->data;
+
+ g_ptr_array_sort_with_data (c->edges, compare_angle, c);
+ }
GSK_NOTE (PATHS, g_print ("reassembling\n"));
builder = gsk_path_builder_new ();
- closed = TRUE;
- for (l = data.curves; l; l = l->next)
+ for (l = curves; l; l = l->next)
{
Curve *curve = l->data;
- const graphene_point_t *p;
+ Connection *start;
- if (curve->collected || curve->internal)
+ if (curve->collected || curve->interior)
continue;
- /* start a new contour */
- if (!closed)
- {
- gsk_path_builder_close (builder);
- closed = TRUE;
- }
-
g_assert (curve->curve.op != GSK_PATH_CLOSE);
- g_assert (!curve->internal);
GSK_NOTE (PATHS, g_print ("start new contour %s\n", curve->name));
if (curve->curve.op == GSK_PATH_MOVE)
- p = &curve->curve.line.points[0];
+ start = curve->end;
else
- p = gsk_curve_get_start_point (&curve->curve);
- gsk_path_builder_move_to (builder, p->x, p->y);
+ start = curve->start;
+
+ gsk_path_builder_move_to (builder, start->p.x, start->p.y);
if (curve->curve.op != GSK_PATH_MOVE)
gsk_path_builder_append_curve (builder, &curve->curve);
curve->collected = TRUE;
- closed = FALSE;
/* collect segments, following through connections */
for (curve = find_next (curve); curve; curve = find_next (curve))
{
- g_assert (!curve->internal);
+ g_assert (!curve->interior);
if (curve->collected)
- break;
+ {
+ g_warning ("find_next returned collected %s, falling off\n", curve->name);
+ break;
+ }
GSK_NOTE (PATHS, g_print ("append %s\n", curve->name));
gsk_path_builder_append_curve (builder, &curve->curve);
curve->collected = TRUE;
- if (curve->curve.op == GSK_PATH_CLOSE ||
- curve->curve.op == GSK_PATH_MOVE)
+ if (curve->curve.op == GSK_PATH_CLOSE)
+ {
+ GSK_NOTE (PATHS, g_print ("explicitly closed\n"));
+ break;
+ }
+
+ if (curve->end == start)
{
- closed = TRUE;
+ GSK_NOTE (PATHS, g_print ("implicitly closed\n"));
+ gsk_path_builder_close (builder);
break;
}
}
+
+ gsk_path_builder_close (builder);
+ }
+
+#ifdef G_ENABLE_DEBUG
+ /* Check that we've collected all */
+ for (l = curves; l; l = l->next)
+ {
+ Curve *curve = l->data;
+ g_assert (curve->interior == !curve->collected);
}
+#endif
+
+ g_list_free_full (curves, (GDestroyNotify) curve_free);
+ g_list_free_full (connections, (GDestroyNotify) connection_free);
+
+ return gsk_path_remove_unclosed (gsk_path_builder_free_to_path (builder));
+}
+
+/**
+ * gsk_path_simplify:
+ * @self: a `GskPath`
+ *
+ * Create a new path that describes the same area as @self,
+ * without overlapping contours.
+ *
+ * Returns: a new `GskPath`
+ */
+GskPath *
+gsk_path_simplify (GskPath *self)
+{
+ GSK_NOTE (PATHS, g_print ("\n***simplify***\n"));
+ return gsk_path_op (PATH_OP_SIMPLIFY, self, NULL);
+}
+
+/**
+ * gsk_path_union:
+ * @first: a `GskPath`
+ * @second: a `GskPath`
+ *
+ * Create a new path that describes the union of the areas
+ * of the given paths.
+ *
+ * Returns: a new `GskPath`
+ */
+GskPath *
+gsk_path_union (GskPath *first,
+ GskPath *second)
+{
+ GSK_NOTE (PATHS, g_print ("\n***union***\n"));
+ return gsk_path_op (PATH_OP_UNION, first, second);
+}
- g_list_free_full (data.curves, (GDestroyNotify) curve_free);
- g_list_free_full (data.connections, (GDestroyNotify) connection_free);
+/**
+ * gsk_path_intersection:
+ * @first: a `GskPath`
+ * @second: a `GskPath`
+ *
+ * Create a new path that describes the intersection of the areas
+ * of the given paths.
+ *
+ * Returns: a new `GskPath`
+ */
+GskPath *
+gsk_path_intersection (GskPath *first,
+ GskPath *second)
+{
+ GSK_NOTE (PATHS, g_print ("\n***intersection***\n"));
+ return gsk_path_op (PATH_OP_INTERSECTION, first, second);
+}
- return gsk_path_builder_free_to_path (builder);
+/**
+ * gsk_path_difference:
+ * @first: a `GskPath`
+ * @second: a `GskPath`
+ *
+ * Create a new path that describes the difference of the areas
+ * of the given paths.
+ *
+ * Returns: a new `GskPath`
+ */
+GskPath *
+gsk_path_difference (GskPath *first,
+ GskPath *second)
+{
+ GSK_NOTE (PATHS, g_print ("\n***difference***\n"));
+ return gsk_path_op (PATH_OP_DIFFERENCE, first, second);
+}
+
+/**
+ * gsk_path_symmetric_difference:
+ * @first: a `GskPath`
+ * @second: a `GskPath`
+ *
+ * Create a new path that describes the symmetric difference
+ * of the areas of the given paths.
+ *
+ * Returns: a new `GskPath`
+ */
+GskPath *
+gsk_path_symmetric_difference (GskPath *first,
+ GskPath *second)
+{
+ GSK_NOTE (PATHS, g_print ("\n***symmetric difference***\n"));
+ return gsk_path_op (PATH_OP_XOR, first, second);
}
[
Date Prev][
Date Next] [
Thread Prev][
Thread Next]
[
Thread Index]
[
Date Index]
[
Author Index]