Compare commits

...

14 Commits

Author SHA1 Message Date
Matthias Clasen
08b786a984 tools: Add more commands to gtk4-path-tool
Add pathops.
2023-09-23 20:15:33 -04:00
Matthias Clasen
54bf63259c gtk-demo: Add a demo for path ops
This demo is called Glyphs, since that is what
it works with.
2023-09-23 20:15:33 -04:00
Matthias Clasen
53b5441e39 Add some tests for pathops 2023-09-23 20:15:33 -04:00
Matthias Clasen
3b2ab7ad1e Add API for boolean operations on paths
The new APIs here are:
gsk_path_union
gsk_path_intersection
gsk_path_difference
gsk_path_symmetric_difference
gsk_path_simplify
2023-09-23 20:15:33 -04:00
Matthias Clasen
42eaacbab4 Implement boolean operations on paths
Implement union, intersection, difference and
symmetric difference of two paths, as well as
simplification of a single path.
2023-09-23 20:15:33 -04:00
Matthias Clasen
6a1660b78e path-tool: Show intersections 2023-09-23 20:14:45 -04:00
Matthias Clasen
821fc35941 path-tool: Allow showing two paths 2023-09-23 20:14:45 -04:00
Matthias Clasen
af0492ed0c Add a path intersection demo 2023-09-23 20:14:45 -04:00
Matthias Clasen
dcdbb73702 Add some path intersection tests 2023-09-23 20:14:45 -04:00
Matthias Clasen
60b2cf5d4a Add gsk_path_foreach_intersection
This function makes it possible to iterate
through the intersections of two paths.
2023-09-23 20:14:45 -04:00
Matthias Clasen
2fcb20cc4a contour: Add private api to circle contours
Add api to retrieve the parameters of a circle
contour. This will be used in the following
commits.
2023-09-23 15:29:12 -04:00
Matthias Clasen
89567767c5 Add tests for gsk_curve_intersect 2023-09-23 15:29:12 -04:00
Matthias Clasen
6577f386b0 curve: Add gsk_curve_intersect
Add a way to find the intersections of two curves.
We can handle some curve-line intersections directly,
the general case is handled via bisection.
2023-09-23 15:29:12 -04:00
Matthias Clasen
6ce8bd6581 curve: Some refactoring
Move cusp-related code to gskcurveintersect.c.
Add functions to find cusps and inflection points of cubics.
These will be used for intersections and in the stroker.
2023-09-23 15:29:12 -04:00
26 changed files with 7375 additions and 204 deletions

View File

@@ -295,6 +295,7 @@
<file>gears.c</file> <file>gears.c</file>
<file>gestures.c</file> <file>gestures.c</file>
<file>glarea.c</file> <file>glarea.c</file>
<file>glyphs.c</file>
<file>gltransition.c</file> <file>gltransition.c</file>
<file>headerbar.c</file> <file>headerbar.c</file>
<file>hypertext.c</file> <file>hypertext.c</file>
@@ -339,6 +340,7 @@
<file>path_fill.c</file> <file>path_fill.c</file>
<file>path_maze.c</file> <file>path_maze.c</file>
<file>path_spinner.c</file> <file>path_spinner.c</file>
<file>path_sweep.c</file>
<file>path_walk.c</file> <file>path_walk.c</file>
<file>path_text.c</file> <file>path_text.c</file>
<file>peg_solitaire.c</file> <file>peg_solitaire.c</file>
@@ -426,6 +428,10 @@
<gresource prefix="/fontrendering"> <gresource prefix="/fontrendering">
<file>fontrendering.ui</file> <file>fontrendering.ui</file>
</gresource> </gresource>
<gresource prefix="/path_sweep">
<file>path_sweep.ui</file>
<file compressed="true">path_world.txt</file>
</gresource>
<gresource prefix="/path_walk"> <gresource prefix="/path_walk">
<file>path_walk.ui</file> <file>path_walk.ui</file>
<file compressed="true">path_world.txt</file> <file compressed="true">path_world.txt</file>

1186
demos/gtk-demo/glyphs.c Normal file

File diff suppressed because it is too large Load Diff

View File

@@ -34,6 +34,7 @@ demos = files([
'gestures.c', 'gestures.c',
'glarea.c', 'glarea.c',
'gltransition.c', 'gltransition.c',
'glyphs.c',
'headerbar.c', 'headerbar.c',
'hypertext.c', 'hypertext.c',
'iconscroll.c', 'iconscroll.c',
@@ -75,6 +76,7 @@ demos = files([
'path_fill.c', 'path_fill.c',
'path_maze.c', 'path_maze.c',
'path_spinner.c', 'path_spinner.c',
'path_sweep.c',
'path_walk.c', 'path_walk.c',
'path_text.c', 'path_text.c',
'peg_solitaire.c', 'peg_solitaire.c',

319
demos/gtk-demo/path_sweep.c Normal file
View File

@@ -0,0 +1,319 @@
/* Path/Sweep
*
* This demo shows how path intersections can be used.
*
* The world map that is used here is a path with 211 lines and 1569 cubic
* Bėzier segments in 121 contours.
*/
#include <glib/gi18n.h>
#include <gtk/gtk.h>
#define GTK_TYPE_PATH_SWEEP (gtk_path_sweep_get_type ())
G_DECLARE_FINAL_TYPE (GtkPathSweep, gtk_path_sweep, GTK, PATH_SWEEP, GtkWidget)
#define POINT_SIZE 8
enum {
PROP_0,
PROP_PATH,
N_PROPS
};
struct _GtkPathSweep
{
GtkWidget parent_instance;
GskPath *path;
graphene_rect_t bounds;
float y_pos;
gboolean in;
};
struct _GtkPathSweepClass
{
GtkWidgetClass parent_class;
};
static GParamSpec *properties[N_PROPS] = { NULL, };
G_DEFINE_TYPE (GtkPathSweep, gtk_path_sweep, GTK_TYPE_WIDGET)
static gboolean
intersection_cb (GskPath *path1,
const GskPathPoint *point1,
GskPath *path2,
const GskPathPoint *point2,
GskPathIntersection kind,
gpointer data)
{
GskPathBuilder *builder = data;
graphene_point_t p;
gsk_path_point_get_position (point1, path1, &p);
gsk_path_builder_add_circle (builder, &p, 4);
return TRUE;
}
static GskPath *
get_intersection_path (GskPath *path1,
GskPath *path2)
{
GskPathBuilder *builder = gsk_path_builder_new ();
gsk_path_foreach_intersection (path1, path2, intersection_cb, builder);
return gsk_path_builder_to_path (builder);
}
static void
gtk_path_sweep_snapshot (GtkWidget *widget,
GtkSnapshot *snapshot)
{
GtkPathSweep *self = GTK_PATH_SWEEP (widget);
GskStroke *stroke;
if (self->path == NULL)
return;
gtk_snapshot_save (snapshot);
stroke = gsk_stroke_new (2.0);
gtk_snapshot_append_stroke (snapshot, self->path, stroke, &(GdkRGBA) { 0, 0, 0, 1 });
if (self->in)
{
graphene_rect_t bounds;
GskPathBuilder *builder;
GskPath *line, *isecs;
gsk_path_get_stroke_bounds (self->path, stroke, &bounds);
builder = gsk_path_builder_new ();
gsk_path_builder_move_to (builder, bounds.origin.x, bounds.origin.y + self->y_pos);
gsk_path_builder_line_to (builder, bounds.origin.x + bounds.size.width, bounds.origin.y + self->y_pos);
line = gsk_path_builder_free_to_path (builder);
gtk_snapshot_append_stroke (snapshot, line, stroke, &(GdkRGBA) { 0, 0, 0, 1 });
isecs = get_intersection_path (self->path, line);
gtk_snapshot_append_fill (snapshot, isecs, GSK_FILL_RULE_WINDING, &(GdkRGBA) { 1, 0, 0, 1 });
gtk_snapshot_append_stroke (snapshot, isecs, stroke, &(GdkRGBA) { 0, 0, 0, 1 });
gsk_path_unref (isecs);
gsk_path_unref (line);
}
gsk_stroke_free (stroke);
gtk_snapshot_restore (snapshot);
}
static void
gtk_path_sweep_measure (GtkWidget *widget,
GtkOrientation orientation,
int for_size,
int *minimum,
int *natural,
int *minimum_baseline,
int *natural_baseline)
{
GtkPathSweep *self = GTK_PATH_SWEEP (widget);
if (orientation == GTK_ORIENTATION_HORIZONTAL)
*minimum = *natural = (int) ceilf (self->bounds.size.width);
else
*minimum = *natural = (int) ceilf (self->bounds.size.height);
}
static void
gtk_path_sweep_set_path (GtkPathSweep *self,
GskPath *path)
{
if (self->path == path)
return;
g_clear_pointer (&self->path, gsk_path_unref);
graphene_rect_init (&self->bounds, 0, 0, 0, 0);
if (path)
{
GskStroke *stroke;
self->path = gsk_path_ref (path);
stroke = gsk_stroke_new (2.0);
gsk_path_get_stroke_bounds (path, stroke, &self->bounds);
gsk_stroke_free (stroke);
}
gtk_widget_queue_resize (GTK_WIDGET (self));
g_object_notify_by_pspec (G_OBJECT (self), properties[PROP_PATH]);
}
static void
gtk_path_sweep_set_property (GObject *object,
guint prop_id,
const GValue *value,
GParamSpec *pspec)
{
GtkPathSweep *self = GTK_PATH_SWEEP (object);
switch (prop_id)
{
case PROP_PATH:
gtk_path_sweep_set_path (self, g_value_get_boxed (value));
break;
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
break;
}
}
static void
gtk_path_sweep_get_property (GObject *object,
guint prop_id,
GValue *value,
GParamSpec *pspec)
{
GtkPathSweep *self = GTK_PATH_SWEEP (object);
switch (prop_id)
{
case PROP_PATH:
g_value_set_boxed (value, self->path);
break;
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
break;
}
}
static void
gtk_path_sweep_dispose (GObject *object)
{
GtkPathSweep *self = GTK_PATH_SWEEP (object);
g_clear_pointer (&self->path, gsk_path_unref);
G_OBJECT_CLASS (gtk_path_sweep_parent_class)->dispose (object);
}
static void
gtk_path_sweep_class_init (GtkPathSweepClass *klass)
{
GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass);
GObjectClass *object_class = G_OBJECT_CLASS (klass);
object_class->dispose = gtk_path_sweep_dispose;
object_class->set_property = gtk_path_sweep_set_property;
object_class->get_property = gtk_path_sweep_get_property;
widget_class->snapshot = gtk_path_sweep_snapshot;
widget_class->measure = gtk_path_sweep_measure;
properties[PROP_PATH] =
g_param_spec_boxed ("path", NULL, NULL,
GSK_TYPE_PATH,
G_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY | G_PARAM_STATIC_STRINGS);
g_object_class_install_properties (object_class, N_PROPS, properties);
}
static void
motion_cb (GtkEventControllerMotion *controller,
double x,
double y,
gpointer data)
{
GtkPathSweep *self = data;
self->y_pos = y;
gtk_widget_queue_draw (GTK_WIDGET (self));
}
static void
enter_cb (GtkEventControllerMotion *controller,
double x,
double y,
gpointer data)
{
GtkPathSweep *self = data;
self->in = TRUE;
self->y_pos = y;
gtk_widget_queue_draw (GTK_WIDGET (self));
}
static void
leave_cb (GtkEventControllerMotion *controller,
gpointer data)
{
GtkPathSweep *self = data;
self->in = FALSE;
gtk_widget_queue_draw (GTK_WIDGET (self));
}
static void
gtk_path_sweep_init (GtkPathSweep *self)
{
GtkEventController *controller;
/* Data taken from
* https://commons.wikimedia.org/wiki/Maps_of_the_world#/media/File:Simplified_blank_world_map_without_Antartica_(no_borders).svg
*/
GBytes *data = g_resources_lookup_data ("/path_sweep/path_world.txt", 0, NULL);
GskPath *path = gsk_path_parse (g_bytes_get_data (data, NULL));
g_bytes_unref (data);
gtk_path_sweep_set_path (self, path);
gsk_path_unref (path);
controller = gtk_event_controller_motion_new ();
g_signal_connect (controller, "motion", G_CALLBACK (motion_cb), self);
g_signal_connect (controller, "enter", G_CALLBACK (enter_cb), self);
g_signal_connect (controller, "leave", G_CALLBACK (leave_cb), self);
gtk_widget_add_controller (GTK_WIDGET (self), controller);
}
GtkWidget *
gtk_path_sweep_new (void)
{
GtkPathSweep *self;
self = g_object_new (GTK_TYPE_PATH_SWEEP, NULL);
return GTK_WIDGET (self);
}
GtkWidget *
do_path_sweep (GtkWidget *do_widget)
{
static GtkWidget *window = NULL;
if (!window)
{
GtkBuilder *builder;
g_type_ensure (GTK_TYPE_PATH_SWEEP);
builder = gtk_builder_new_from_resource ("/path_sweep/path_sweep.ui");
window = GTK_WIDGET (gtk_builder_get_object (builder, "window"));
gtk_window_set_display (GTK_WINDOW (window),
gtk_widget_get_display (do_widget));
g_object_add_weak_pointer (G_OBJECT (window), (gpointer *) &window);
g_object_unref (builder);
}
if (!gtk_widget_get_visible (window))
gtk_window_present (GTK_WINDOW (window));
else
gtk_window_destroy (GTK_WINDOW (window));
return window;
}

View File

@@ -0,0 +1,17 @@
<?xml version="1.0" encoding="UTF-8"?>
<interface>
<object class="GtkWindow" id="window">
<property name="title" translatable="yes">World Map</property>
<property name="child">
<object class="GtkBox">
<property name="orientation">vertical</property>
<child>
<object class="GtkPathSweep" id="view">
<property name="hexpand">true</property>
<property name="vexpand">true</property>
</object>
</child>
</object>
</property>
</object>
</interface>

View File

@@ -12,6 +12,11 @@ SYNOPSIS
-------- --------
| **gtk4-path-tool** <COMMAND> [OPTIONS...] <PATH> | **gtk4-path-tool** <COMMAND> [OPTIONS...] <PATH>
| |
| **gtk4-path-tool** simplify [OPTIONS...] <PATH>
| **gtk4-path-tool** intersection [OPTIONS...] <PATH> <PATH>
| **gtk4-path-tool** union [OPTIONS...] <PATH> <PATH>
| **gtk4-path-tool** difference [OPTIONS...] <PATH> <PATH>
| **gtk4-path-tool** symmetric-difference [OPTIONS...] <PATH> <PATH>
| **gtk4-path-tool** decompose [OPTIONS...] <PATH> | **gtk4-path-tool** decompose [OPTIONS...] <PATH>
| **gtk4-path-tool** show [OPTIONS...] <PATH> | **gtk4-path-tool** show [OPTIONS...] <PATH>
| **gtk4-path-tool** render [OPTIONS...] <PATH> | **gtk4-path-tool** render [OPTIONS...] <PATH>
@@ -195,6 +200,7 @@ The interior of the path is filled.
The limit at which to clip miters at line joins. The default value is 4. The limit at which to clip miters at line joins. The default value is 4.
<<<<<<< HEAD
``--dashes=VALUE`` ``--dashes=VALUE``
The dash pattern to use for this stroke. A dash pattern is specified by The dash pattern to use for this stroke. A dash pattern is specified by
@@ -208,6 +214,21 @@ The interior of the path is filled.
The offset into the dash pattern where dashing should begin. The offset into the dash pattern where dashing should begin.
The default value is 0. The default value is 0.
Boolean Operations
^^^^^^^^^^^^^^^^^^
The ``intersection``, ``union``, ``difference`` and ``symmetric-difference`` commands
perform boolean operations on paths. Given two paths, they create a new path which
encircles the area that is the intersection, union, difference or symmetric difference
of the areas encircled by the paths.
Simplification
^^^^^^^^^^^^^^
The ``simplify`` command removes areas of overlap from a path such that the resulting
path encircles the same area, but every edge in the resulting path is a boundary between
the inside and the outside.
Reversing Reversing
^^^^^^^^^ ^^^^^^^^^

View File

@@ -1749,6 +1749,19 @@ gsk_circle_contour_new (const graphene_point_t *center,
return (GskContour *) self; return (GskContour *) self;
} }
void
gsk_circle_contour_get_params (const GskContour *contour,
graphene_point_t *center,
float *radius,
gboolean *ccw)
{
const GskCircleContour *self = (const GskCircleContour *) contour;
*center = self->center;
*radius = self->radius;
*ccw = self->ccw;
}
/* }}} */ /* }}} */
/* {{{ Rectangle */ /* {{{ Rectangle */

View File

@@ -36,6 +36,10 @@ GskContour * gsk_standard_contour_new (GskPathFlags
GskContour * gsk_circle_contour_new (const graphene_point_t *center, GskContour * gsk_circle_contour_new (const graphene_point_t *center,
float radius); float radius);
void gsk_circle_contour_get_params (const GskContour *contour,
graphene_point_t *center,
float *radius,
gboolean *ccw);
GskContour * gsk_rect_contour_new (const graphene_rect_t *rect); GskContour * gsk_rect_contour_new (const graphene_rect_t *rect);
GskContour * gsk_rounded_rect_contour_new (const GskRoundedRect *rounded_rect); GskContour * gsk_rounded_rect_contour_new (const GskRoundedRect *rounded_rect);

View File

@@ -2330,6 +2330,30 @@ gsk_curve_get_crossing (const GskCurve *curve,
return get_class (curve->op)->get_crossing (curve, point); return get_class (curve->op)->get_crossing (curve, point);
} }
float
gsk_curve_get_length_to (const GskCurve *curve,
float t)
{
return get_class (curve->op)->get_length_to (curve, t);
}
float
gsk_curve_get_length (const GskCurve *curve)
{
return gsk_curve_get_length_to (curve, 1);
}
float
gsk_curve_at_length (const GskCurve *curve,
float length,
float epsilon)
{
return get_class (curve->op)->get_at_length (curve, length, epsilon);
}
/* }}} */
/* {{{ Closest point */
static gboolean static gboolean
project_point_onto_line (const GskCurve *curve, project_point_onto_line (const GskCurve *curve,
const graphene_point_t *point, const graphene_point_t *point,
@@ -2451,187 +2475,6 @@ gsk_curve_get_closest_point (const GskCurve *curve,
return find_closest_point (curve, point, threshold, 0, 1, out_dist, out_t); return find_closest_point (curve, point, threshold, 0, 1, out_dist, out_t);
} }
float
gsk_curve_get_length_to (const GskCurve *curve,
float t)
{
return get_class (curve->op)->get_length_to (curve, t);
}
float
gsk_curve_get_length (const GskCurve *curve)
{
return gsk_curve_get_length_to (curve, 1);
}
/* Compute the inverse of the arclength using bisection,
* to a given precision
*/
float
gsk_curve_at_length (const GskCurve *curve,
float length,
float epsilon)
{
return get_class (curve->op)->get_at_length (curve, length, epsilon);
}
static inline void
_sincosf (float angle,
float *out_s,
float *out_c)
{
#ifdef HAVE_SINCOSF
sincosf (angle, out_s, out_c);
#else
*out_s = sinf (angle);
*out_c = cosf (angle);
#endif
}
static void
align_points (const graphene_point_t *p,
const graphene_point_t *a,
const graphene_point_t *b,
graphene_point_t *q,
int n)
{
graphene_vec2_t n1;
float angle;
float s, c;
get_tangent (a, b, &n1);
angle = - atan2f (graphene_vec2_get_y (&n1), graphene_vec2_get_x (&n1));
_sincosf (angle, &s, &c);
for (int i = 0; i < n; i++)
{
q[i].x = (p[i].x - a->x) * c - (p[i].y - a->y) * s;
q[i].y = (p[i].x - a->x) * s + (p[i].y - a->y) * c;
}
}
static int
filter_allowable (float t[3],
int n)
{
float g[3];
int j = 0;
for (int i = 0; i < n; i++)
if (0 < t[i] && t[i] < 1)
g[j++] = t[i];
for (int i = 0; i < j; i++)
t[i] = g[i];
return j;
}
/* find solutions for at^2 + bt + c = 0 */
static int
solve_quadratic (float a, float b, float c, float t[2])
{
float d;
int n = 0;
if (fabsf (a) > 0.0001)
{
if (b*b > 4*a*c)
{
d = sqrtf (b*b - 4*a*c);
t[n++] = (-b + d)/(2*a);
t[n++] = (-b - d)/(2*a);
}
else
{
t[n++] = -b / (2*a);
}
}
else if (fabsf (b) > 0.0001)
{
t[n++] = -c / b;
}
return n;
}
int
gsk_curve_get_curvature_points (const GskCurve *curve,
float t[3])
{
const graphene_point_t *pts = curve->cubic.points;
graphene_point_t p[4];
float a, b, c, d;
float x, y, z;
int n;
if (curve->op != GSK_PATH_CUBIC)
return 0; /* FIXME */
align_points (pts, &pts[0], &pts[3], p, 4);
a = p[2].x * p[1].y;
b = p[3].x * p[1].y;
c = p[1].x * p[2].y;
d = p[3].x * p[2].y;
x = - 3*a + 2*b + 3*c - d;
y = 3*a - b - 3*c;
z = c - a;
n = solve_quadratic (x, y, z, t);
return filter_allowable (t, n);
}
/* Find cusps inside the open interval from 0 to 1.
*
* According to Stone & deRose, A Geometric Characterization
* of Parametric Cubic curves, a necessary and sufficient
* condition is that the first derivative vanishes.
*/
int
gsk_curve_get_cusps (const GskCurve *curve,
float t[2])
{
const graphene_point_t *pts = curve->cubic.points;
graphene_point_t p[3];
float ax, bx, cx;
float ay, by, cy;
float tx[3];
int nx;
int n = 0;
if (curve->op != GSK_PATH_CUBIC)
return 0;
p[0].x = 3 * (pts[1].x - pts[0].x);
p[0].y = 3 * (pts[1].y - pts[0].y);
p[1].x = 3 * (pts[2].x - pts[1].x);
p[1].y = 3 * (pts[2].y - pts[1].y);
p[2].x = 3 * (pts[3].x - pts[2].x);
p[2].y = 3 * (pts[3].y - pts[2].y);
ax = p[0].x - 2 * p[1].x + p[2].x;
bx = - 2 * p[0].x + 2 * p[1].x;
cx = p[0].x;
nx = solve_quadratic (ax, bx, cx, tx);
nx = filter_allowable (tx, nx);
ay = p[0].y - 2 * p[1].y + p[2].y;
by = - 2 * p[0].y + 2 * p[1].y;
cy = p[0].y;
for (int i = 0; i < nx; i++)
{
float ti = tx[i];
if (0 < ti && ti < 1 &&
fabsf (ay * ti * ti + by * ti + cy) < 0.001)
t[n++] = ti;
}
return n;
}
/* }}} */ /* }}} */
/* vim:set foldmethod=marker expandtab: */ /* vim:set foldmethod=marker expandtab: */

1115
gsk/gskcurveintersect.c Normal file

File diff suppressed because it is too large Load Diff

View File

@@ -187,6 +187,19 @@ int gsk_curve_get_curvature_points (const GskCurve
int gsk_curve_get_cusps (const GskCurve *curve, int gsk_curve_get_cusps (const GskCurve *curve,
float t[2]); float t[2]);
int gsk_curve_intersect (const GskCurve *curve1,
const GskCurve *curve2,
float *t1,
float *t2,
graphene_point_t *p,
GskPathIntersection *kind,
int n);
int gsk_curve_self_intersect (const GskCurve *curve,
float *t1,
graphene_point_t *p,
int n);
G_END_DECLS G_END_DECLS

View File

@@ -144,6 +144,79 @@ gboolean gsk_path_foreach (GskPath
GskPathForeachFunc func, GskPathForeachFunc func,
gpointer user_data); gpointer user_data);
/**
* GskPathIntersection:
* @GSK_PATH_INTERSECTION_NONE: No intersection
* @GSK_PATH_INTERSECTION_NORMAL: A normal intersection, where the two paths
* cross each other
* @GSK_PATH_INTERSECTION_START: The start of a segment where the two paths coincide
* @GSK_PATH_INTERSECTION_END: The end of a segment where the two paths coincide
*
* The values of this enumeration classify intersections
* between paths.
*/
typedef enum
{
GSK_PATH_INTERSECTION_NONE,
GSK_PATH_INTERSECTION_NORMAL,
GSK_PATH_INTERSECTION_START,
GSK_PATH_INTERSECTION_END,
} GskPathIntersection;
/**
* GskPathIntersectionFunc:
* @path1: the first path
* @point1: the intersection as point on @path1
* @path2: the second path
* @point2: the intersection as point on @path2
* @kind: specify the nature of the intersection
* @user_data: user data
*
* Prototype of the callback to iterate through the
* intersections of two paths.
*
* Returns: %TRUE to continue iterating, %FALSE to
* immediately abort and not call the function again
*
* Since: 4.14
*/
typedef gboolean (* GskPathIntersectionFunc) (GskPath *path1,
const GskPathPoint *point1,
GskPath *path2,
const GskPathPoint *point2,
GskPathIntersection kind,
gpointer user_data);
GDK_AVAILABLE_IN_4_14
gboolean gsk_path_foreach_intersection (GskPath *path1,
GskPath *path2,
GskPathIntersectionFunc func,
gpointer user_data);
GDK_AVAILABLE_IN_4_14
GskPath * gsk_path_union (GskPath *first,
GskPath *second,
GskFillRule fill_rule);
GDK_AVAILABLE_IN_4_14
GskPath * gsk_path_intersection (GskPath *first,
GskPath *second,
GskFillRule fill_rule);
GDK_AVAILABLE_IN_4_14
GskPath * gsk_path_difference (GskPath *first,
GskPath *second,
GskFillRule fill_rule);
GDK_AVAILABLE_IN_4_14
GskPath * gsk_path_symmetric_difference (GskPath *first,
GskPath *second,
GskFillRule fill_rule);
GDK_AVAILABLE_IN_4_14
GskPath * gsk_path_simplify (GskPath *self,
GskFillRule fill_rule);
G_DEFINE_AUTOPTR_CLEANUP_FUNC(GskPath, gsk_path_unref) G_DEFINE_AUTOPTR_CLEANUP_FUNC(GskPath, gsk_path_unref)
G_END_DECLS G_END_DECLS

705
gsk/gskpathintersect.c Normal file
View File

@@ -0,0 +1,705 @@
/*
* Copyright © 2020 Benjamin Otte
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library. If not, see <http://www.gnu.org/licenses/>.
*
* Authors: Benjamin Otte <otte@gnome.org>
*/
#include "config.h"
#include "gskpathprivate.h"
#include "gskcurveprivate.h"
#include "gskpathbuilder.h"
#include "gskpathpoint.h"
#include "gskcontourprivate.h"
typedef struct
{
gsize count;
gboolean closed;
gboolean z_is_empty;
} CountCurveData;
static gboolean
count_cb (GskPathOperation op,
const graphene_point_t *pts,
gsize n_pts,
float weight,
gpointer data)
{
CountCurveData *ccd = data;
(ccd->count)++;
if (op ==GSK_PATH_CLOSE)
{
ccd->closed = TRUE;
ccd->z_is_empty = graphene_point_equal (&pts[0], &pts[1]);
}
return TRUE;
}
static gsize
count_curves (const GskContour *contour,
gboolean *closed,
gboolean *z_is_empty)
{
CountCurveData data;
data.count = 0;
data.closed = FALSE;
data.z_is_empty = FALSE;
gsk_contour_foreach (contour, count_cb, &data);
*closed = data.closed;
*z_is_empty = data.z_is_empty;
return data.count;
}
typedef struct
{
GskPathPoint point1;
GskPathPoint point2;
GskPathIntersection kind;
} Intersection;
typedef struct
{
GskPath *path1;
GskPath *path2;
GskPathIntersectionFunc func;
gpointer data;
gsize contour1;
gsize contour2;
gsize idx1;
gsize idx2;
const GskContour *c1;
const GskContour *c2;
GskCurve curve1;
GskCurve curve2;
gboolean c1_closed;
gboolean c2_closed;
gboolean c1_z_is_empty;
gboolean c2_z_is_empty;
gsize c1_count;
gsize c2_count;
GArray *points;
GArray *all_points;
} PathIntersectData;
static gboolean
intersect_curve2 (GskPathOperation op,
const graphene_point_t *pts,
gsize n_pts,
float weight,
gpointer data)
{
PathIntersectData *pd = data;
float t1[10], t2[10];
graphene_point_t p[10];
GskPathIntersection kind[10];
int n;
if (op == GSK_PATH_MOVE)
{
if (gsk_contour_get_n_ops (pd->c2) == 1)
{
float dist, tt;
if (gsk_curve_get_closest_point (&pd->curve1, &pts[0], 1, &dist, &tt) && dist == 0)
{
Intersection is;
is.kind = GSK_PATH_INTERSECTION_NORMAL;
is.point1.contour = pd->contour1;
is.point1.idx = pd->idx1;
is.point1.t = tt;
is.point2.contour = pd->contour2;
is.point2.idx = 0;
is.point2.t = 1;
g_array_append_val (pd->points, is);
}
}
return TRUE;
}
if (op == GSK_PATH_CLOSE)
{
if (graphene_point_equal (&pts[0], &pts[1]))
return TRUE;
}
pd->idx2++;
gsk_curve_init_foreach (&pd->curve2, op, pts, n_pts, weight);
n = gsk_curve_intersect (&pd->curve1, &pd->curve2, t1, t2, p, kind, 19);
for (int i = 0; i < n; i++)
{
Intersection is;
is.point1.contour = pd->contour1;
is.point2.contour = pd->contour2;
is.point1.idx = pd->idx1;
is.point2.idx = pd->idx2;
is.point1.t = t1[i];
is.point2.t = t2[i];
is.kind = kind[i];
#if 0
g_print ("append p1 { %lu %lu %f } p2 { %lu %lu %f } %s\n",
is.point1.contour, is.point1.idx, is.point1.t,
is.point2.contour, is.point2.idx, is.point2.t,
kn[is.kind]);
#endif
g_array_append_val (pd->points, is);
}
return TRUE;
}
static gboolean
intersect_curve (GskPathOperation op,
const graphene_point_t *pts,
gsize n_pts,
float weight,
gpointer data)
{
PathIntersectData *pd = data;
GskBoundingBox b1, b2;
if (op == GSK_PATH_MOVE)
{
if (gsk_contour_get_n_ops (pd->c1) == 1)
{
GskPathPoint point;
float dist;
if (gsk_contour_get_closest_point (pd->c2, &pts[0], 1, &point, &dist) && dist == 0)
{
Intersection is;
is.kind = GSK_PATH_INTERSECTION_NORMAL;
is.point1.contour = pd->contour1;
is.point1.idx = 0;
is.point1.t = 1;
is.point2.contour = pd->contour2;
is.point2.idx = point.idx;
is.point2.t = point.t;
g_array_append_val (pd->points, is);
}
}
return TRUE;
}
if (op == GSK_PATH_CLOSE)
{
if (graphene_point_equal (&pts[0], &pts[1]))
return TRUE;
}
pd->idx1++;
gsk_curve_init_foreach (&pd->curve1, op, pts, n_pts, weight);
gsk_curve_get_bounds (&pd->curve1, &b1);
gsk_contour_get_bounds (pd->c2, &b2);
if (gsk_bounding_box_intersection (&b1, &b2, NULL))
{
pd->idx2 = 0;
if (!gsk_contour_foreach (pd->c2, intersect_curve2, pd))
return FALSE;
}
return TRUE;
}
static gboolean
gsk_path_point_near (const GskPathPoint *p1,
const GskPathPoint *p2,
gboolean closed,
gsize count,
gboolean z_is_empty,
float epsilon)
{
if (p1->idx == p2->idx && fabsf (p1->t - p2->t) < epsilon)
return TRUE;
if (p1->idx + 1 == p2->idx && (1 - p1->t + p2->t < epsilon))
return TRUE;
if (p2->idx + 1 == p1->idx && (1 - p2->t + p1->t < epsilon))
return TRUE;
if (closed)
{
if (p1->idx == 1 && p2->idx == count - 1 && (1 - p2->t + p1->t < epsilon))
return TRUE;
if (p2->idx == 1 && p1->idx == count - 1 && (1 - p1->t + p2->t < epsilon))
return TRUE;
}
if (closed && z_is_empty)
{
if (p1->idx == 1 && p2->idx == count - 2 && (1 - p2->t + p1->t < epsilon))
return TRUE;
if (p2->idx == 1 && p1->idx == count - 2 && (1 - p1->t + p2->t < epsilon))
return TRUE;
}
return FALSE;
}
static int cmp_path1 (gconstpointer p1, gconstpointer p2);
static void
default_contour_collect_intersections (const GskContour *contour1,
const GskContour *contour2,
PathIntersectData *pd)
{
pd->idx1 = 0;
g_array_set_size (pd->points, 0);
gsk_contour_foreach (contour1, intersect_curve, pd);
g_array_sort (pd->points, cmp_path1);
#if 0
g_print ("after sorting\n");
for (gsize i = 0; i < pd->points->len; i++)
{
Intersection *is = &g_array_index (pd->points, Intersection, i);
const char *kn[] = { "none", "normal", "start", "end" };
g_print ("p1 { %lu %lu %f } p2 { %lu %lu %f } %s\n",
is->point1.contour, is->point1.idx, is->point1.t,
is->point2.contour, is->point2.idx, is->point2.t,
kn[is->kind]);
}
#endif
for (gsize i = 0; i < pd->points->len; i++)
{
Intersection *is1 = &g_array_index (pd->points, Intersection, i);
for (gsize j = i + 1; j < pd->points->len; j++)
{
Intersection *is2 = &g_array_index (pd->points, Intersection, j);
if (!gsk_path_point_near (&is1->point1, &is2->point1,
pd->c1_closed, pd->c1_count, pd->c1_z_is_empty,
0.001))
continue;
if (!gsk_path_point_near (&is1->point2, &is2->point2,
pd->c2_closed, pd->c2_count, pd->c2_z_is_empty,
0.001))
continue;
if (is1->kind == GSK_PATH_INTERSECTION_NORMAL && is2->kind != GSK_PATH_INTERSECTION_NONE)
is1->kind = GSK_PATH_INTERSECTION_NONE;
else if (is2->kind == GSK_PATH_INTERSECTION_NORMAL && is1->kind != GSK_PATH_INTERSECTION_NONE)
is2->kind = GSK_PATH_INTERSECTION_NONE;
}
}
#if 0
g_print ("after collapsing\n");
for (gsize i = 0; i < pd->points->len; i++)
{
Intersection *is = &g_array_index (pd->points, Intersection, i);
const char *kn[] = { "none", "normal", "start", "end" };
g_print ("p1 { %lu %lu %f } p2 { %lu %lu %f } %s\n",
is->point1.contour, is->point1.idx, is->point1.t,
is->point2.contour, is->point2.idx, is->point2.t,
kn[is->kind]);
}
#endif
for (gsize i = 0; i < pd->points->len; i++)
{
Intersection *is1 = &g_array_index (pd->points, Intersection, i);
for (gsize j = i + 1; j < pd->points->len; j++)
{
Intersection *is2 = &g_array_index (pd->points, Intersection, j);
if (!gsk_path_point_near (&is1->point1, &is2->point1, FALSE, 0, FALSE, 0.001))
break;
if (!gsk_path_point_near (&is1->point2, &is2->point2,
pd->c2_closed, pd->c2_count, pd->c2_z_is_empty,
0.001))
break;
if ((is1->kind == GSK_PATH_INTERSECTION_END &&
is2->kind == GSK_PATH_INTERSECTION_START) ||
(is1->kind == GSK_PATH_INTERSECTION_START &&
is2->kind == GSK_PATH_INTERSECTION_END))
{
is1->kind = GSK_PATH_INTERSECTION_NONE;
is2->kind = GSK_PATH_INTERSECTION_NONE;
}
}
}
#if 0
g_print ("after merging segments\n");
for (gsize i = 0; i < pd->points->len; i++)
{
Intersection *is = &g_array_index (pd->points, Intersection, i);
const char *kn[] = { "none", "normal", "start", "end" };
g_print ("p1 { %lu %lu %f } p2 { %lu %lu %f } %s\n",
is->point1.contour, is->point1.idx, is->point1.t,
is->point2.contour, is->point2.idx, is->point2.t,
kn[is->kind]);
}
#endif
for (gsize j = 0; j < pd->points->len; j++)
{
Intersection *is = &g_array_index (pd->points, Intersection, j);
if (is->kind != GSK_PATH_INTERSECTION_NONE)
g_array_append_val (pd->all_points, *is);
}
}
static int
circle_intersect (const graphene_point_t *center1,
float radius1,
const graphene_point_t *center2,
float radius2,
graphene_point_t points[2])
{
float d;
float a, h;
graphene_point_t m;
graphene_vec2_t n;
g_assert (radius1 >= 0);
g_assert (radius2 >= 0);
d = graphene_point_distance (center1, center2, NULL, NULL);
if (d < fabsf (radius1 - radius2))
return 0;
if (d > radius1 + radius2)
return 0;
if (d == radius1 + radius2)
{
graphene_point_interpolate (center1, center2, radius1 / (radius1 + radius2), &points[0]);
return 1;
}
a = (radius1*radius1 - radius2*radius2 + d*d)/(2*d);
h = sqrtf (radius1*radius1 - a*a);
graphene_point_interpolate (center1, center2, a/d, &m);
graphene_vec2_init (&n, center2->y - center1->y, center1->x - center2->x);
graphene_vec2_normalize (&n, &n);
graphene_point_init (&points[0], m.x + graphene_vec2_get_x (&n) * h,
m.y + graphene_vec2_get_y (&n) * h);
graphene_point_init (&points[1], m.x - graphene_vec2_get_x (&n) * h,
m.y - graphene_vec2_get_y (&n) * h);
return 2;
}
static void
circle_contour_collect_intersections (const GskContour *contour1,
const GskContour *contour2,
PathIntersectData *pd)
{
graphene_point_t center1, center2;
float radius1, radius2;
gboolean ccw1, ccw2;
graphene_point_t p[2];
int n;
Intersection is[2];
gsk_circle_contour_get_params (contour1, &center1, &radius1, &ccw1);
gsk_circle_contour_get_params (contour2, &center2, &radius2, &ccw2);
if (graphene_point_equal (&center1, &center2) && radius1 == radius2)
{
is[0].kind = GSK_PATH_INTERSECTION_START;
is[0].point1.contour = pd->contour1;
is[0].point1.idx = 1;
is[0].point1.t = 0;
is[0].point2.contour = pd->contour2;
is[0].point2.idx = 1;
is[0].point2.t = 0;
is[1].kind = GSK_PATH_INTERSECTION_END;
is[1].point1.contour = pd->contour1;
is[1].point1.idx = 1;
is[1].point1.t = 1;
is[1].point2.contour = pd->contour2;
is[1].point2.idx = 1;
is[1].point2.t = 1;
if (ccw1 != ccw2)
{
is[0].point2.t = 1;
is[1].point2.t = 0;
}
g_array_append_val (pd->all_points, is[0]);
g_array_append_val (pd->all_points, is[1]);
return;
}
n = circle_intersect (&center1, radius1, &center2, radius2, p);
for (int i = 0; i < n; i++)
{
float d;
is[i].kind = GSK_PATH_INTERSECTION_NORMAL;
is[i].point1.contour = pd->contour1;
is[i].point2.contour = pd->contour2;
gsk_contour_get_closest_point (contour1, &p[i], 1, &is[i].point1, &d);
gsk_contour_get_closest_point (contour2, &p[i], 1, &is[i].point2, &d);
}
if (n == 1)
{
g_array_append_val (pd->all_points, is[0]);
}
else if (n == 2)
{
if (gsk_path_point_compare (&is[0].point1, &is[1].point1) < 0)
{
g_array_append_val (pd->all_points, is[0]);
g_array_append_val (pd->all_points, is[1]);
}
else
{
g_array_append_val (pd->all_points, is[1]);
g_array_append_val (pd->all_points, is[0]);
}
}
}
static void
contour_collect_intersections (const GskContour *contour1,
const GskContour *contour2,
PathIntersectData *pd)
{
if (strcmp (gsk_contour_get_type_name (contour1), "GskCircleContour") == 0 &&
strcmp (gsk_contour_get_type_name (contour2), "GskCircleContour") == 0)
circle_contour_collect_intersections (contour1, contour2, pd);
else
default_contour_collect_intersections (contour1, contour2, pd);
}
static int
cmp_path1 (gconstpointer p1,
gconstpointer p2)
{
const Intersection *i1 = p1;
const Intersection *i2 = p2;
int i;
i = gsk_path_point_compare (&i1->point1, &i2->point1);
if (i != 0)
return i;
return gsk_path_point_compare (&i1->point2, &i2->point2);
}
static gboolean
contour_foreach_intersection (const GskContour *contour1,
PathIntersectData *pd)
{
GskBoundingBox b1, b2;
gsk_contour_get_bounds (contour1, &b1);
g_array_set_size (pd->all_points, 0);
for (gsize i = 0; i < gsk_path_get_n_contours (pd->path2); i++)
{
const GskContour *contour2 = gsk_path_get_contour (pd->path2, i);
gsk_contour_get_bounds (contour1, &b2);
if (gsk_bounding_box_intersection (&b1, &b2, NULL))
{
pd->contour2 = i;
pd->c2 = contour2;
pd->c2_count = count_curves (contour2, &pd->c2_closed, &pd->c2_z_is_empty);
contour_collect_intersections (contour1, contour2, pd);
}
}
g_array_sort (pd->all_points, cmp_path1);
#if 0
g_print ("after sorting\n");
for (gsize i = 0; i < pd->all_points->len; i++)
{
Intersection *is = &g_array_index (pd->all_points, Intersection, i);
const char *kn[] = { "none", "normal", "start", "end" };
g_print ("p1 { %lu %lu %f } p2 { %lu %lu %f } %s\n",
is->point1.contour, is->point1.idx, is->point1.t,
is->point2.contour, is->point2.idx, is->point2.t,
kn[is->kind]);
}
for (gsize i = 0; i + 1 < pd->all_points->len; i++)
{
Intersection *is1 = &g_array_index (pd->all_points, Intersection, i);
Intersection *is2 = &g_array_index (pd->all_points, Intersection, i + 1);
if (gsk_path_point_equal (&is1->point1, &is2->point1) &&
gsk_path_point_equal (&is1->point2, &is2->point2))
{
if (is1->kind == GSK_PATH_INTERSECTION_END &&
is2->kind == GSK_PATH_INTERSECTION_START)
{
is1->kind = GSK_PATH_INTERSECTION_NONE;
is2->kind = GSK_PATH_INTERSECTION_NONE;
}
else
{
is2->kind = MAX (is1->kind, is2->kind);
is1->kind = GSK_PATH_INTERSECTION_NONE;
}
}
}
g_print ("emitting\n");
for (gsize i = 0; i < pd->all_points->len; i++)
{
Intersection *is = &g_array_index (pd->all_points, Intersection, i);
const char *kn[] = { "none", "normal", "start", "end" };
g_print ("p1 { %lu %lu %f } p2 { %lu %lu %f } %s\n",
is->point1.contour, is->point1.idx, is->point1.t,
is->point2.contour, is->point2.idx, is->point2.t,
kn[is->kind]);
}
#endif
for (gsize i = 0; i < pd->all_points->len; i++)
{
Intersection *is = &g_array_index (pd->all_points, Intersection, i);
if (is->kind != GSK_PATH_INTERSECTION_NONE)
{
if (!pd->func (pd->path1, &is->point1, pd->path2, &is->point2, is->kind, pd->data))
return FALSE;
}
}
return TRUE;
}
/**
* gsk_path_foreach_intersection:
* @path1: the first path
* @path2: the second path
* @func: (scope call) (closure user_data): the function to call for intersections
* @user_data: (nullable): user data passed to @func
*
* Finds intersections between two paths.
*
* This function finds intersections between @path1 and @path2,
* and calls @func for each of them, in increasing order for @path1.
*
* When segments of the paths coincide, the callback is called once
* for the start of the segment, with @GSK_PATH_INTERSECTION_START, and
* once for the end of the segment, with @GSK_PATH_INTERSECTION_END.
* Note that other intersections may occur between the start and end
* of such a segment.
*
* If @func returns `FALSE`, the iteration is stopped.
*
* Returns: `FALSE` if @func returned FALSE`, `TRUE` otherwise.
*
* Since: 4.14
*/
gboolean
gsk_path_foreach_intersection (GskPath *path1,
GskPath *path2,
GskPathIntersectionFunc func,
gpointer data)
{
PathIntersectData pd = {
.path1 = path1,
.path2 = path2,
.func = func,
.data = data,
};
gboolean ret;
pd.points = g_array_new (FALSE, FALSE, sizeof (Intersection));
pd.all_points = g_array_new (FALSE, FALSE, sizeof (Intersection));
ret = TRUE;
for (gsize i = 0; i < gsk_path_get_n_contours (path1); i++)
{
const GskContour *contour1 = gsk_path_get_contour (path1, i);
pd.contour1 = i;
pd.c1 = contour1;
pd.c1_count = count_curves (contour1, &pd.c1_closed, &pd.c1_z_is_empty);
pd.idx1 = 0;
if (!contour_foreach_intersection (contour1, &pd))
{
ret = FALSE;
break;
}
}
g_array_unref (pd.points);
g_array_unref (pd.all_points);
return ret;
}
/* vim:set foldmethod=marker expandtab: */

1818
gsk/gskpathops.c Normal file

File diff suppressed because it is too large Load Diff

View File

@@ -56,5 +56,19 @@ gboolean gsk_path_foreach_with_tolerance (GskPath
void gsk_path_builder_add_contour (GskPathBuilder *builder, void gsk_path_builder_add_contour (GskPathBuilder *builder,
GskContour *contour); GskContour *contour);
typedef enum
{
GSK_PATH_OP_SIMPLIFY,
GSK_PATH_OP_UNION,
GSK_PATH_OP_INTERSECTION,
GSK_PATH_OP_DIFFERENCE,
GSK_PATH_OP_XOR
} GskPathOp;
GskPath * gsk_path_op (GskPathOp operation,
GskFillRule fill_rule,
GskPath *first,
GskPath *second);
G_END_DECLS G_END_DECLS

View File

@@ -28,7 +28,9 @@ gsk_public_sources = files([
'gskglshader.c', 'gskglshader.c',
'gskpath.c', 'gskpath.c',
'gskpathbuilder.c', 'gskpathbuilder.c',
'gskpathintersect.c',
'gskpathmeasure.c', 'gskpathmeasure.c',
'gskpathops.c',
'gskpathparser.c', 'gskpathparser.c',
'gskpathpoint.c', 'gskpathpoint.c',
'gskrenderer.c', 'gskrenderer.c',
@@ -45,6 +47,7 @@ gsk_private_sources = files([
'gskcairoblur.c', 'gskcairoblur.c',
'gskcontour.c', 'gskcontour.c',
'gskcurve.c', 'gskcurve.c',
'gskcurveintersect.c',
'gskdebug.c', 'gskdebug.c',
'gskprivate.c', 'gskprivate.c',
'gskprofiler.c', 'gskprofiler.c',

View File

@@ -0,0 +1,551 @@
#include <gtk/gtk.h>
#include "gsk/gskcurveprivate.h"
static void
test_line_line_intersection (void)
{
GskCurve c1, c2;
graphene_point_t p1[2], p2[2];
float t1, t2;
graphene_point_t p;
GskPathIntersection kind;
int n;
graphene_point_init (&p1[0], 10, 0);
graphene_point_init (&p1[1], 10, 100);
graphene_point_init (&p2[0], 0, 10);
graphene_point_init (&p2[1], 100, 10);
gsk_curve_init (&c1, gsk_pathop_encode (GSK_PATH_LINE, p1));
gsk_curve_init (&c2, gsk_pathop_encode (GSK_PATH_LINE, p2));
n = gsk_curve_intersect (&c1, &c2, &t1, &t2, &p, &kind, 1);
g_assert_cmpint (n, ==, 1);
g_assert_cmpfloat_with_epsilon (t1, 0.1, 0.0001);
g_assert_cmpfloat_with_epsilon (t2, 0.1, 0.0001);
g_assert_true (graphene_point_near (&p, &GRAPHENE_POINT_INIT (10, 10), 0.0001));
}
static void
test_line_line_end_intersection (void)
{
GskCurve c1, c2;
graphene_point_t p1[2], p2[2];
float t1, t2;
graphene_point_t p;
GskPathIntersection kind;
int n;
graphene_point_init (&p1[0], 10, 0);
graphene_point_init (&p1[1], 10, 100);
graphene_point_init (&p2[0], 10, 100);
graphene_point_init (&p2[1], 100, 10);
gsk_curve_init (&c1, gsk_pathop_encode (GSK_PATH_LINE, p1));
gsk_curve_init (&c2, gsk_pathop_encode (GSK_PATH_LINE, p2));
n = gsk_curve_intersect (&c1, &c2, &t1, &t2, &p, &kind, 1);
g_assert_cmpint (n, ==, 1);
g_assert_cmpfloat_with_epsilon (t1, 1, 0.0001);
g_assert_cmpfloat_with_epsilon (t2, 0, 0.0001);
g_assert_true (graphene_point_near (&p, &GRAPHENE_POINT_INIT (10, 100), 0.0001));
}
static void
test_line_line_none_intersection (void)
{
GskCurve c1, c2;
graphene_point_t p1[2], p2[2];
float t1, t2;
graphene_point_t p;
GskPathIntersection kind;
int n;
graphene_point_init (&p1[0], 0, 0);
graphene_point_init (&p1[1], 10, 0);
graphene_point_init (&p2[0], 20, 0);
graphene_point_init (&p2[1], 30, 0);
gsk_curve_init (&c1, gsk_pathop_encode (GSK_PATH_LINE, p1));
gsk_curve_init (&c2, gsk_pathop_encode (GSK_PATH_LINE, p2));
n = gsk_curve_intersect (&c1, &c2, &t1, &t2, &p, &kind, 1);
g_assert_cmpint (n, ==, 0);
graphene_point_init (&p1[0], 247.103424, 95.7965317);
graphene_point_init (&p1[1], 205.463974, 266.758484);
graphene_point_init (&p2[0], 183.735962, 355.968689);
graphene_point_init (&p2[1], 121.553253, 611.27655);
gsk_curve_init (&c1, gsk_pathop_encode (GSK_PATH_LINE, p1));
gsk_curve_init (&c2, gsk_pathop_encode (GSK_PATH_LINE, p2));
n = gsk_curve_intersect (&c1, &c2, &t1, &t2, &p, &kind, 1);
g_assert_cmpint (n, ==, 0);
}
static void
test_line_line_parallel (void)
{
GskCurve c1, c2;
graphene_point_t p1[2], p2[2];
float t1[2], t2[2];
graphene_point_t p[2];
GskPathIntersection kind[2];
int n;
graphene_point_init (&p1[0], 10, 10);
graphene_point_init (&p1[1], 110, 10);
graphene_point_init (&p2[0], 20, 10);
graphene_point_init (&p2[1], 120, 10);
gsk_curve_init (&c1, gsk_pathop_encode (GSK_PATH_LINE, p1));
gsk_curve_init (&c2, gsk_pathop_encode (GSK_PATH_LINE, p2));
n = gsk_curve_intersect (&c1, &c2, t1, t2, p, kind, 2);
g_assert_cmpint (n, ==, 2);
g_assert_cmpfloat_with_epsilon (t1[0], 0.1f, 0.01);
g_assert_cmpfloat_with_epsilon (t1[1], 1.f, 0.01);
g_assert_cmpfloat_with_epsilon (t2[0], 0.f, 0.01);
g_assert_cmpfloat_with_epsilon (t2[1], 0.9f, 0.01);
}
static void
test_line_line_same (void)
{
GskCurve c1, c2;
graphene_point_t p1[2], p2[2];
float t1[2], t2[2];
graphene_point_t p[2];
GskPathIntersection kind[2];
int n;
graphene_point_init (&p1[0], 10, 10);
graphene_point_init (&p1[1], 100, 10);
graphene_point_init (&p2[0], 10, 10);
graphene_point_init (&p2[1], 100, 10);
gsk_curve_init (&c1, gsk_pathop_encode (GSK_PATH_LINE, p1));
gsk_curve_init (&c2, gsk_pathop_encode (GSK_PATH_LINE, p2));
n = gsk_curve_intersect (&c1, &c2, t1, t2, p, kind, 2);
g_assert_cmpint (n, ==, 2);
g_assert_cmpfloat_with_epsilon (t1[0], 0.f, 0.01);
g_assert_cmpfloat_with_epsilon (t1[1], 1.f, 0.01);
g_assert_cmpfloat_with_epsilon (t2[0], 0.f, 0.01);
g_assert_cmpfloat_with_epsilon (t2[1], 1.f, 0.01);
}
static void
test_line_curve_intersection (void)
{
GskCurve c1, c2;
graphene_point_t p1[4], p2[2];
float t1[9], t2[9];
graphene_point_t p[9];
GskPathIntersection kind[9];
int n;
GskBoundingBox b;
graphene_point_init (&p1[0], 0, 100);
graphene_point_init (&p1[1], 50, 100);
graphene_point_init (&p1[2], 50, 0);
graphene_point_init (&p1[3], 100, 0);
graphene_point_init (&p2[0], 0, 0);
graphene_point_init (&p2[1], 100, 100);
gsk_curve_init (&c1, gsk_pathop_encode (GSK_PATH_CUBIC, p1));
gsk_curve_init (&c2, gsk_pathop_encode (GSK_PATH_LINE, p2));
n = gsk_curve_intersect (&c1, &c2, t1, t2, p, kind, 1);
g_assert_cmpint (n, ==, 1);
g_assert_cmpfloat_with_epsilon (t1[0], 0.5, 0.0001);
g_assert_cmpfloat_with_epsilon (t2[0], 0.5, 0.0001);
g_assert_true (graphene_point_near (&p[0], &GRAPHENE_POINT_INIT (50, 50), 0.0001));
gsk_curve_get_tight_bounds (&c1, &b);
gsk_bounding_box_contains_point (&b, &p[0]);
gsk_curve_get_tight_bounds (&c2, &b);
gsk_bounding_box_contains_point (&b, &p[0]);
}
static void
test_line_curve_multiple_intersection (void)
{
GskCurve c1, c2;
graphene_point_t p1[4], p2[2];
float t1[9], t2[9];
graphene_point_t p[9];
GskPathIntersection kind[9];
graphene_point_t pp;
int n;
GskBoundingBox b1, b2;
graphene_point_init (&p1[0], 100, 200);
graphene_point_init (&p1[1], 350, 100);
graphene_point_init (&p1[2], 100, 350);
graphene_point_init (&p1[3], 400, 300);
graphene_point_init (&p2[0], 0, 0);
graphene_point_init (&p2[1], 100, 100);
gsk_curve_init (&c1, gsk_pathop_encode (GSK_PATH_CUBIC, p1));
gsk_curve_init (&c2, gsk_pathop_encode (GSK_PATH_LINE, p2));
n = gsk_curve_intersect (&c1, &c2, t1, t2, p, kind, 3);
g_assert_cmpint (n, ==, 0);
graphene_point_init (&p2[0], 0, 0);
graphene_point_init (&p2[1], 200, 200);
gsk_curve_init (&c1, gsk_pathop_encode (GSK_PATH_CUBIC, p1));
gsk_curve_init (&c2, gsk_pathop_encode (GSK_PATH_LINE, p2));
n = gsk_curve_intersect (&c1, &c2, t1, t2, p, kind, 3);
g_assert_cmpint (n, ==, 1);
g_assert_cmpfloat_with_epsilon (t1[0], 0.136196628, 0.0001);
g_assert_cmpfloat_with_epsilon (t2[0], 0.88487947, 0.0001);
g_assert_true (graphene_point_near (&p[0], &GRAPHENE_POINT_INIT (176.975891, 176.975891), 0.001));
gsk_curve_get_point (&c1, t1[0], &pp);
g_assert_true (graphene_point_near (&p[0], &pp, 0.001));
gsk_curve_get_point (&c2, t2[0], &pp);
g_assert_true (graphene_point_near (&p[0], &pp, 0.001));
gsk_curve_get_tight_bounds (&c1, &b1);
gsk_curve_get_tight_bounds (&c2, &b2);
gsk_bounding_box_contains_point (&b1, &p[0]);
gsk_bounding_box_contains_point (&b2, &p[0]);
graphene_point_init (&p2[0], 0, 0);
graphene_point_init (&p2[1], 280, 280);
gsk_curve_init (&c1, gsk_pathop_encode (GSK_PATH_CUBIC, p1));
gsk_curve_init (&c2, gsk_pathop_encode (GSK_PATH_LINE, p2));
n = gsk_curve_intersect (&c1, &c2, t1, t2, p, kind, 3);
g_assert_cmpint (n, ==, 2);
g_assert_cmpfloat_with_epsilon (t1[0], 0.136196628, 0.0001);
g_assert_cmpfloat_with_epsilon (t2[0], 0.632056773, 0.0001);
g_assert_true (graphene_point_near (&p[0], &GRAPHENE_POINT_INIT (176.975891, 176.975891), 0.001));
gsk_curve_get_point (&c1, t1[0], &pp);
g_assert_true (graphene_point_near (&p[0], &pp, 0.001));
gsk_curve_get_point (&c2, t2[0], &pp);
g_assert_true (graphene_point_near (&p[0], &pp, 0.001));
g_assert_cmpfloat_with_epsilon (t1[1], 0.499999911, 0.0001);
g_assert_cmpfloat_with_epsilon (t2[1], 0.825892806, 0.0001);
g_assert_true (graphene_point_near (&p[1], &GRAPHENE_POINT_INIT (231.25, 231.25), 0.001));
gsk_curve_get_point (&c1, t1[1], &pp);
g_assert_true (graphene_point_near (&p[1], &pp, 0.001));
gsk_curve_get_point (&c2, t2[1], &pp);
g_assert_true (graphene_point_near (&p[1], &pp, 0.001));
gsk_curve_get_tight_bounds (&c1, &b1);
gsk_curve_get_tight_bounds (&c2, &b2);
gsk_bounding_box_contains_point (&b1, &p[0]);
gsk_bounding_box_contains_point (&b1, &p[1]);
gsk_bounding_box_contains_point (&b2, &p[0]);
gsk_bounding_box_contains_point (&b2, &p[1]);
graphene_point_init (&p2[0], 0, 0);
graphene_point_init (&p2[1], 1000, 1000);
gsk_curve_init (&c1, gsk_pathop_encode (GSK_PATH_CUBIC, p1));
gsk_curve_init (&c2, gsk_pathop_encode (GSK_PATH_LINE, p2));
n = gsk_curve_intersect (&c1, &c2, t1, t2, p, kind, 3);
g_assert_cmpint (n, ==, 3);
g_assert_cmpfloat_with_epsilon (t1[0], 0.863803446, 0.0001);
g_assert_cmpfloat_with_epsilon (t2[0], 0.305377066, 0.0001);
g_assert_true (graphene_point_near (&p[0], &GRAPHENE_POINT_INIT (305.377075, 305.377075), 0.001));
gsk_curve_get_point (&c1, t1[0], &pp);
g_assert_true (graphene_point_near (&p[0], &pp, 0.001));
gsk_curve_get_point (&c2, t2[0], &pp);
g_assert_true (graphene_point_near (&p[0], &pp, 0.001));
g_assert_cmpfloat_with_epsilon (t1[1], 0.136196628, 0.0001);
g_assert_cmpfloat_with_epsilon (t2[1], 0.176975891, 0.0001);
g_assert_true (graphene_point_near (&p[1], &GRAPHENE_POINT_INIT (176.975891, 176.975891), 0.001));
gsk_curve_get_point (&c1, t1[1], &pp);
g_assert_true (graphene_point_near (&p[1], &pp, 0.001));
gsk_curve_get_point (&c2, t2[1], &pp);
g_assert_true (graphene_point_near (&p[1], &pp, 0.001));
g_assert_cmpfloat_with_epsilon (t1[2], 0.5, 0.0001);
g_assert_cmpfloat_with_epsilon (t2[2], 0.231249988, 0.0001);
g_assert_true (graphene_point_near (&p[2], &GRAPHENE_POINT_INIT (231.249985, 231.249985), 0.001));
gsk_curve_get_point (&c1, t1[2], &pp);
g_assert_true (graphene_point_near (&p[2], &pp, 0.001));
gsk_curve_get_point (&c2, t2[2], &pp);
g_assert_true (graphene_point_near (&p[2], &pp, 0.001));
gsk_curve_get_tight_bounds (&c1, &b1);
gsk_curve_get_tight_bounds (&c2, &b2);
gsk_bounding_box_contains_point (&b1, &p[0]);
gsk_bounding_box_contains_point (&b1, &p[1]);
gsk_bounding_box_contains_point (&b1, &p[2]);
gsk_bounding_box_contains_point (&b2, &p[0]);
gsk_bounding_box_contains_point (&b2, &p[1]);
gsk_bounding_box_contains_point (&b2, &p[2]);
}
static void
test_line_curve_end_intersection (void)
{
GskCurve c1, c2;
graphene_point_t p1[4], p2[2];
float t1[9], t2[9];
graphene_point_t p[9];
GskPathIntersection kind[9];
int n;
graphene_point_init (&p1[0], 0, 100);
graphene_point_init (&p1[1], 50, 100);
graphene_point_init (&p1[2], 50, 0);
graphene_point_init (&p1[3], 100, 0);
graphene_point_init (&p2[0], 100, 0);
graphene_point_init (&p2[1], 100, 100);
gsk_curve_init (&c1, gsk_pathop_encode (GSK_PATH_CUBIC, p1));
gsk_curve_init (&c2, gsk_pathop_encode (GSK_PATH_LINE, p2));
n = gsk_curve_intersect (&c1, &c2, t1, t2, p, kind, 1);
g_assert_cmpint (n, ==, 1);
g_assert_cmpfloat_with_epsilon (t1[0], 1, 0.0001);
g_assert_cmpfloat_with_epsilon (t2[0], 0, 0.0001);
g_assert_true (graphene_point_near (&p[0], &GRAPHENE_POINT_INIT (100, 0), 0.0001));
}
static void
test_line_curve_none_intersection (void)
{
GskCurve c1, c2;
graphene_point_t p1[4], p2[2];
float t1[9], t2[9];
graphene_point_t p[9];
GskPathIntersection kind[9];
int n;
graphene_point_init (&p1[0], 333, 78);
graphene_point_init (&p1[1], 415, 78);
graphene_point_init (&p1[2], 463, 131);
graphene_point_init (&p1[3], 463, 223);
graphene_point_init (&p2[0], 520, 476);
graphene_point_init (&p2[1], 502, 418);
gsk_curve_init (&c1, gsk_pathop_encode (GSK_PATH_CUBIC, p1));
gsk_curve_init (&c2, gsk_pathop_encode (GSK_PATH_LINE, p2));
n = gsk_curve_intersect (&c1, &c2, t1, t2, p, kind, 1);
g_assert_cmpint (n, ==, 0);
}
static void
test_curve_curve_intersection (void)
{
GskCurve c1, c2;
graphene_point_t p1[4], p2[4];
float t1[9], t2[9];
graphene_point_t p[9];
GskPathIntersection kind[9];
int n;
GskBoundingBox b;
graphene_point_init (&p1[0], 0, 0);
graphene_point_init (&p1[1], 33.333, 100);
graphene_point_init (&p1[2], 66.667, 0);
graphene_point_init (&p1[3], 100, 100);
graphene_point_init (&p2[0], 0, 50);
graphene_point_init (&p2[1], 100, 0);
graphene_point_init (&p2[2], 20, 0); // weight 20
graphene_point_init (&p2[3], 50, 100);
gsk_curve_init (&c1, gsk_pathop_encode (GSK_PATH_CUBIC, p1));
gsk_curve_init (&c2, gsk_pathop_encode (GSK_PATH_CONIC, p2));
n = gsk_curve_intersect (&c1, &c2, t1, t2, p, kind, 9);
g_assert_cmpint (n, ==, 2);
g_assert_cmpfloat (t1[0], <, 0.5);
g_assert_cmpfloat (t1[1], >, 0.5);
g_assert_cmpfloat (t2[0], <, 0.5);
g_assert_cmpfloat (t2[1], >, 0.5);
gsk_curve_get_tight_bounds (&c1, &b);
gsk_bounding_box_contains_point (&b, &p[0]);
gsk_curve_get_tight_bounds (&c2, &b);
gsk_bounding_box_contains_point (&b, &p[0]);
}
static void
test_curve_curve_end_intersection (void)
{
GskCurve c1, c2;
graphene_point_t p1[4], p2[4];
float t1[9], t2[9];
graphene_point_t p[9];
GskPathIntersection kind[9];
int n;
graphene_point_init (&p1[0], 0, 0);
graphene_point_init (&p1[1], 33.333, 100);
graphene_point_init (&p1[2], 66.667, 0);
graphene_point_init (&p1[3], 100, 100);
graphene_point_init (&p2[0], 100, 100);
graphene_point_init (&p2[1], 100, 0);
graphene_point_init (&p2[2], 20, 0);
graphene_point_init (&p2[3], 10, 0);
gsk_curve_init (&c1, gsk_pathop_encode (GSK_PATH_CUBIC, p1));
gsk_curve_init (&c2, gsk_pathop_encode (GSK_PATH_CONIC, p2));
n = gsk_curve_intersect (&c1, &c2, t1, t2, p, kind, 9);
g_assert_cmpint (n, ==, 1);
g_assert_cmpfloat_with_epsilon (t1[0], 1, 0.0001);
g_assert_cmpfloat_with_epsilon (t2[0], 0, 0.0001);
}
static void
test_curve_curve_end_intersection2 (void)
{
GskCurve c, c1, c2;
graphene_point_t p1[4];
float t1[9], t2[9];
graphene_point_t p[9];
GskPathIntersection kind[9];
int n;
graphene_point_init (&p1[0], 200, 100);
graphene_point_init (&p1[1], 300, 300);
graphene_point_init (&p1[2], 100, 300);
graphene_point_init (&p1[3], 300, 100);
gsk_curve_init (&c, gsk_pathop_encode (GSK_PATH_CUBIC, p1));
gsk_curve_split (&c, 0.5, &c1, &c2);
n = gsk_curve_intersect (&c1, &c2, t1, t2, p, kind, 9);
g_assert_cmpint (n, ==, 2);
}
static void
test_curve_curve_max_intersection (void)
{
GskCurve c1, c2;
graphene_point_t p1[4], p2[4];
float t1[9], t2[9];
graphene_point_t p[9];
GskPathIntersection kind[9];
int n;
graphene_point_init (&p1[0], 106, 100);
graphene_point_init (&p1[1], 118, 264);
graphene_point_init (&p1[2], 129, 4);
graphene_point_init (&p1[3], 128, 182);
graphene_point_init (&p2[0], 54, 135);
graphene_point_init (&p2[1], 263, 136);
graphene_point_init (&p2[2], 2, 143);
graphene_point_init (&p2[3], 141, 150);
gsk_curve_init (&c1, gsk_pathop_encode (GSK_PATH_CUBIC, p1));
gsk_curve_init (&c2, gsk_pathop_encode (GSK_PATH_CUBIC, p2));
n = gsk_curve_intersect (&c1, &c2, t1, t2, p, kind, 9);
g_assert_cmpint (n, ==, 9);
}
/* This showed up as artifacts in the stroker when our
* intersection code failed to find intersections with
* horizontal lines
*/
static void
test_curve_intersection_horizontal_line (void)
{
GskCurve c1, c2;
float t1, t2;
graphene_point_t p;
GskPathIntersection kind;
int n;
gsk_curve_init (&c1,
gsk_pathop_encode (GSK_PATH_CONIC,
(const graphene_point_t[4]) {
GRAPHENE_POINT_INIT (200.000, 165.000),
GRAPHENE_POINT_INIT (220.858, 165.000),
GRAPHENE_POINT_INIT (1.4142, 0),
GRAPHENE_POINT_INIT (292.929, 92.929),
}));
gsk_curve_init_foreach (&c2,
GSK_PATH_LINE,
(const graphene_point_t[2]) {
GRAPHENE_POINT_INIT (300, 110),
GRAPHENE_POINT_INIT (100, 110),
},
2,
0);
n = gsk_curve_intersect (&c1, &c2, &t1, &t2, &p, &kind, 1);
g_assert_true (n == 1);
}
int
main (int argc, char *argv[])
{
(g_test_init) (&argc, &argv, NULL);
g_test_add_func ("/curve/intersection/line-line", test_line_line_intersection);
g_test_add_func ("/curve/intersection/line-line-none", test_line_line_none_intersection);
g_test_add_func ("/curve/intersection/line-line-end", test_line_line_end_intersection);
g_test_add_func ("/curve/intersection/line-line-parallel", test_line_line_parallel);
g_test_add_func ("/curve/intersection/line-line-same", test_line_line_same);
g_test_add_func ("/curve/intersection/line-curve", test_line_curve_intersection);
g_test_add_func ("/curve/intersection/line-curve-end", test_line_curve_end_intersection);
g_test_add_func ("/curve/intersection/line-curve-none", test_line_curve_none_intersection);
g_test_add_func ("/curve/intersection/line-curve-multiple", test_line_curve_multiple_intersection);
g_test_add_func ("/curve/intersection/curve-curve", test_curve_curve_intersection);
g_test_add_func ("/curve/intersection/curve-curve-end", test_curve_curve_end_intersection);
g_test_add_func ("/curve/intersection/curve-curve-end2", test_curve_curve_end_intersection2);
g_test_add_func ("/curve/intersection/curve-curve-max", test_curve_curve_max_intersection);
g_test_add_func ("/curve/intersection/horizontal-line", test_curve_intersection_horizontal_line);
return g_test_run ();
}

View File

@@ -374,6 +374,8 @@ tests = [
['shader'], ['shader'],
['path', [ 'path-utils.c' ] ], ['path', [ 'path-utils.c' ] ],
['path-special-cases'], ['path-special-cases'],
['path-intersect'],
['path-ops', [ 'path-utils.c' ] ],
] ]
test_cargs = [] test_cargs = []
@@ -406,6 +408,7 @@ endforeach
internal_tests = [ internal_tests = [
[ 'curve' ], [ 'curve' ],
[ 'curve-special-cases' ], [ 'curve-special-cases' ],
[ 'curve-intersect' ],
[ 'path-private' ], [ 'path-private' ],
[ 'diff' ], [ 'diff' ],
[ 'half-float' ], [ 'half-float' ],

View File

@@ -0,0 +1,806 @@
#include <gtk/gtk.h>
#define assert_path_point_equal(point,_contour,_idx,_t) \
g_assert_cmpint ((point)->contour, ==, (_contour)); \
g_assert_cmpint ((point)->idx, ==, (_idx)); \
g_assert_cmpfloat_with_epsilon ((point)->t, (_t), 0.0001);
typedef struct
{
GskPathPoint point1[20];
GskPathPoint point2[20];
GskPathIntersection kind[20];
int found;
} CollectData;
static gboolean
collect_cb (GskPath *path1,
const GskPathPoint *point1,
GskPath *path2,
const GskPathPoint *point2,
GskPathIntersection kind,
gpointer data)
{
CollectData *res = data;
#if 0
const char *kn[] = { "none", "normal", "start", "end" };
g_print ("%s idx1 %lu t1 %f idx2 %lu t2 %f\n",
kn[kind],
point1->idx, point1->t, point2->idx, point2->t);
#endif
g_assert_true (res->found < 20);
res->point1[res->found] = *point1;
res->point2[res->found] = *point2;
res->kind[res->found] = kind;
res->found++;
return TRUE;
}
static void
test_intersect_simple (void)
{
GskPath *path1, *path2;
graphene_point_t p1, p2;
CollectData res;
path1 = gsk_path_parse ("M 100 100 h 200 v 100 h -200 z");
path2 = gsk_path_parse ("M 150 150 h 200 v 100 h -200 z");
res.found = 0;
gsk_path_foreach_intersection (path1, path2, collect_cb, &res);
g_assert_true (res.found == 2);
assert_path_point_equal (&res.point1[0], 0, 2, 0.5);
assert_path_point_equal (&res.point1[1], 0, 3, 0.75);
assert_path_point_equal (&res.point2[0], 0, 1, 0.75);
assert_path_point_equal (&res.point2[1], 0, 4, 0.5);
g_assert_true (res.kind[0] == GSK_PATH_INTERSECTION_NORMAL);
g_assert_true (res.kind[1] == GSK_PATH_INTERSECTION_NORMAL);
gsk_path_point_get_position (&res.point1[0], path1, &p1);
gsk_path_point_get_position (&res.point2[0], path2, &p2);
g_assert_true (graphene_point_equal (&p1, &p2));
gsk_path_point_get_position (&res.point1[1], path1, &p1);
gsk_path_point_get_position (&res.point2[1], path2, &p2);
g_assert_true (graphene_point_equal (&p1, &p2));
gsk_path_unref (path1);
gsk_path_unref (path2);
}
static void
test_intersect_simple2 (void)
{
GskPath *path1, *path2;
CollectData res;
path1 = gsk_path_parse ("M 100 100 h 200 v 100 h -200 z");
path2 = gsk_path_parse ("M 100 100 h 200 v 100 h -200 z");
res.found = 0;
gsk_path_foreach_intersection (path1, path2, collect_cb, &res);
g_assert_true (res.found == 2);
g_assert_true (res.kind[0] == GSK_PATH_INTERSECTION_START);
g_assert_true (res.kind[1] == GSK_PATH_INTERSECTION_END);
gsk_path_unref (path1);
gsk_path_unref (path2);
}
static void
test_intersect_simple3 (void)
{
GskPath *path1, *path2;
CollectData res;
path1 = gsk_path_parse ("M 100 100 h 200 v 100 h -200 z");
path2 = gsk_path_parse ("M 300 100 h -200 v 100 h 200 z");
res.found = 0;
gsk_path_foreach_intersection (path1, path2, collect_cb, &res);
g_assert_true (res.found == 2);
g_assert_true (res.kind[0] == GSK_PATH_INTERSECTION_START);
g_assert_true (res.kind[1] == GSK_PATH_INTERSECTION_END);
gsk_path_unref (path1);
gsk_path_unref (path2);
}
static void
test_intersect_reverse (void)
{
GskPath *path1, *path2;
graphene_point_t p1, p2;
CollectData res;
path1 = gsk_path_parse ("M 100 100 h 200 v 100 h -200 z");
path2 = gsk_path_parse ("M 150 150 v 100 h 200 v -100 z");
res.found = 0;
gsk_path_foreach_intersection (path1, path2, collect_cb, &res);
g_assert_true (res.found == 2);
assert_path_point_equal (&res.point1[0], 0, 2, 0.5);
assert_path_point_equal (&res.point1[1], 0, 3, 0.75);
assert_path_point_equal (&res.point2[0], 0, 4, 0.25);
assert_path_point_equal (&res.point2[1], 0, 1, 0.5);
g_assert_true (res.kind[0] == GSK_PATH_INTERSECTION_NORMAL);
g_assert_true (res.kind[1] == GSK_PATH_INTERSECTION_NORMAL);
gsk_path_point_get_position (&res.point1[0], path1, &p1);
gsk_path_point_get_position (&res.point2[0], path2, &p2);
g_assert_true (graphene_point_equal (&p1, &p2));
gsk_path_point_get_position (&res.point1[1], path1, &p1);
gsk_path_point_get_position (&res.point2[1], path2, &p2);
g_assert_true (graphene_point_equal (&p1, &p2));
gsk_path_unref (path1);
gsk_path_unref (path2);
}
static void
test_intersect_line_box (void)
{
GskPath *path1, *path2;
graphene_point_t p1, p2;
CollectData res;
path1 = gsk_path_parse ("M 50 150 l 300 0");
path2 = gsk_path_parse ("M 100 100 h 200 v 100 h -200 z");
res.found = 0;
gsk_path_foreach_intersection (path1, path2, collect_cb, &res);
g_assert_true (res.found == 2);
assert_path_point_equal (&res.point1[0], 0, 1, 50.f/300.f);
assert_path_point_equal (&res.point1[1], 0, 1, 250.f/300.f);
assert_path_point_equal (&res.point2[0], 0, 4, 0.5);
assert_path_point_equal (&res.point2[1], 0, 2, 0.5);
g_assert_true (res.kind[0] == GSK_PATH_INTERSECTION_NORMAL);
g_assert_true (res.kind[1] == GSK_PATH_INTERSECTION_NORMAL);
gsk_path_point_get_position (&res.point1[0], path1, &p1);
gsk_path_point_get_position (&res.point2[0], path2, &p2);
g_assert_true (graphene_point_equal (&p1, &p2));
gsk_path_point_get_position (&res.point1[1], path1, &p1);
gsk_path_point_get_position (&res.point2[1], path2, &p2);
g_assert_true (graphene_point_equal (&p1, &p2));
gsk_path_unref (path1);
gsk_path_unref (path2);
}
static void
test_intersect_xplus (void)
{
GskPath *path1, *path2;
CollectData res;
path1 = gsk_path_parse ("M 0 0 L 100 100 M 0 100 L 100 0");
path2 = gsk_path_parse ("M 0 50 L 100 50 M 50 0 L 50 100");
res.found = 0;
gsk_path_foreach_intersection (path1, path2, collect_cb, &res);
g_assert_true (res.found == 4);
assert_path_point_equal (&res.point1[0], 0, 1, 0.5);
assert_path_point_equal (&res.point1[1], 0, 1, 0.5);
assert_path_point_equal (&res.point1[2], 1, 1, 0.5);
assert_path_point_equal (&res.point1[3], 1, 1, 0.5);
assert_path_point_equal (&res.point2[0], 0, 1, 0.5);
assert_path_point_equal (&res.point2[1], 1, 1, 0.5);
assert_path_point_equal (&res.point2[2], 0, 1, 0.5);
assert_path_point_equal (&res.point2[3], 1, 1, 0.5);
gsk_path_unref (path1);
gsk_path_unref (path2);
}
static void
test_intersect_point (void)
{
GskPath *path1, *path2;
CollectData res;
path1 = gsk_path_parse ("M 0 50");
path2 = gsk_path_parse ("M 0 0 L 0 100");
res.found = 0;
gsk_path_foreach_intersection (path1, path2, collect_cb, &res);
g_assert_true (res.found == 1);
assert_path_point_equal (&res.point1[0], 0, 0, 1);
assert_path_point_equal (&res.point2[0], 0, 1, 0.5);
res.found = 0;
gsk_path_foreach_intersection (path2, path1, collect_cb, &res);
g_assert_true (res.found == 1);
assert_path_point_equal (&res.point1[0], 0, 1, 0.5);
assert_path_point_equal (&res.point2[0], 0, 0, 1);
res.found = 0;
gsk_path_foreach_intersection (path1, path1, collect_cb, &res);
g_assert_true (res.found == 1);
assert_path_point_equal (&res.point1[0], 0, 0, 1);
assert_path_point_equal (&res.point2[0], 0, 0, 1);
gsk_path_unref (path1);
gsk_path_unref (path2);
}
static void
test_intersect_contours (void)
{
GskPath *path1, *path2;
CollectData res;
path1 = gsk_path_parse ("M 0 100 L 200 100");
path2 = gsk_path_parse ("M 150 0 150 200 M 50 0 50 200");
res.found = 0;
gsk_path_foreach_intersection (path1, path2, collect_cb, &res);
g_assert_true (res.found == 2);
assert_path_point_equal (&res.point1[0], 0, 1, 0.25f);
assert_path_point_equal (&res.point1[1], 0, 1, 0.75f);
assert_path_point_equal (&res.point2[0], 1, 1, 0.5f);
assert_path_point_equal (&res.point2[1], 0, 1, 0.5f);
g_assert_true (res.kind[0] == GSK_PATH_INTERSECTION_NORMAL);
g_assert_true (res.kind[1] == GSK_PATH_INTERSECTION_NORMAL);
gsk_path_unref (path1);
gsk_path_unref (path2);
}
static void
test_intersect_contours2 (void)
{
GskPath *path1, *path2;
CollectData res;
path1 = gsk_path_parse ("M 0 100 L 200 100");
path2 = gsk_path_parse ("M 150 0 L 150 200 M 50 0 L 50 200 M 60 100 L 140 100");
res.found = 0;
gsk_path_foreach_intersection (path1, path2, collect_cb, &res);
g_assert_true (res.found == 4);
assert_path_point_equal (&res.point1[0], 0, 1, 0.25f);
assert_path_point_equal (&res.point1[1], 0, 1, 0.3f);
assert_path_point_equal (&res.point2[0], 1, 1, 0.5f);
assert_path_point_equal (&res.point2[1], 2, 1, 0.f);
g_assert_true (res.kind[0] == GSK_PATH_INTERSECTION_NORMAL);
g_assert_true (res.kind[1] == GSK_PATH_INTERSECTION_START);
g_assert_true (res.kind[2] == GSK_PATH_INTERSECTION_END);
g_assert_true (res.kind[3] == GSK_PATH_INTERSECTION_NORMAL);
gsk_path_unref (path1);
gsk_path_unref (path2);
}
static void
test_intersect_contours3 (void)
{
GskPath *path1, *path2;
CollectData res;
path1 = gsk_path_parse ("M 150 0 L 150 200 M 50 0 L 50 200 M 60 100 L 140 100");
path2 = gsk_path_parse ("M 0 100 L 200 100");
res.found = 0;
gsk_path_foreach_intersection (path1, path2, collect_cb, &res);
g_assert_true (res.found == 4);
assert_path_point_equal (&res.point1[0], 0, 1, 0.5f);
assert_path_point_equal (&res.point1[1], 1, 1, 0.5f);
assert_path_point_equal (&res.point2[0], 0, 1, 0.75f);
assert_path_point_equal (&res.point2[1], 0, 1, 0.25f);
g_assert_true (res.kind[0] == GSK_PATH_INTERSECTION_NORMAL);
g_assert_true (res.kind[1] == GSK_PATH_INTERSECTION_NORMAL);
g_assert_true (res.kind[2] == GSK_PATH_INTERSECTION_START);
g_assert_true (res.kind[3] == GSK_PATH_INTERSECTION_END);
gsk_path_unref (path1);
gsk_path_unref (path2);
}
static void
test_intersect_coincide (void)
{
GskPath *path1, *path2;
CollectData res;
path1 = gsk_path_parse ("M 100 100 h 200 v 100 h -200 z");
path2 = gsk_path_parse ("M 150 100 h 100 v 50 h -100 z");
res.found = 0;
gsk_path_foreach_intersection (path1, path2, collect_cb, &res);
g_assert_true (res.found == 2);
assert_path_point_equal (&res.point1[0], 0, 1, 0.25);
assert_path_point_equal (&res.point1[1], 0, 1, 0.75);
assert_path_point_equal (&res.point2[0], 0, 1, 0);
assert_path_point_equal (&res.point2[1], 0, 1, 1);
g_assert_true (res.kind[0] == GSK_PATH_INTERSECTION_START);
g_assert_true (res.kind[1] == GSK_PATH_INTERSECTION_END);
gsk_path_unref (path1);
gsk_path_unref (path2);
}
static void
test_intersect_coincide2 (void)
{
GskPath *path1, *path2;
CollectData res;
path1 = gsk_path_parse ("M 150 100 h 100 v 50 h -100 z");
path2 = gsk_path_parse ("M 100 100 h 200 v 100 h -200 z");
res.found = 0;
gsk_path_foreach_intersection (path1, path2, collect_cb, &res);
g_assert_true (res.found == 2);
assert_path_point_equal (&res.point1[0], 0, 1, 0);
assert_path_point_equal (&res.point1[1], 0, 1, 1);
assert_path_point_equal (&res.point2[0], 0, 1, 0.25);
assert_path_point_equal (&res.point2[1], 0, 1, 0.75);
g_assert_true (res.kind[0] == GSK_PATH_INTERSECTION_START);
g_assert_true (res.kind[1] == GSK_PATH_INTERSECTION_END);
gsk_path_unref (path1);
gsk_path_unref (path2);
}
static void
test_intersect_coincide3 (void)
{
GskPath *path1, *path2;
CollectData res;
path1 = gsk_path_parse ("M 100 100 h 200 v 100 h -200 z");
path2 = gsk_path_parse ("M 150 100 h 100 v 50 h -25 v -50 h -50 v 50 h -25 z");
res.found = 0;
gsk_path_foreach_intersection (path1, path2, collect_cb, &res);
g_assert_true (res.found == 4);
g_assert_true (res.kind[0] == GSK_PATH_INTERSECTION_START);
g_assert_true (res.kind[1] == GSK_PATH_INTERSECTION_START);
g_assert_true (res.kind[2] == GSK_PATH_INTERSECTION_END);
g_assert_true (res.kind[3] == GSK_PATH_INTERSECTION_END);
assert_path_point_equal (&res.point1[0], 0, 1, 0.25);
assert_path_point_equal (&res.point1[1], 0, 1, 0.375);
assert_path_point_equal (&res.point1[2], 0, 1, 0.625);
assert_path_point_equal (&res.point1[3], 0, 1, 0.75);
assert_path_point_equal (&res.point2[0], 0, 1, 0);
assert_path_point_equal (&res.point2[1], 0, 5, 1);
assert_path_point_equal (&res.point2[2], 0, 5, 0);
assert_path_point_equal (&res.point2[3], 0, 1, 1);
gsk_path_unref (path1);
gsk_path_unref (path2);
}
static void
test_intersect_coincide4 (void)
{
GskPath *path1, *path2;
CollectData res;
path1 = gsk_path_parse ("M 100 100 h 200 v 100 h -200 z");
path2 = gsk_path_parse ("M 150 100 h 100 v 50 h -25 v -100 h -50 v 100 h -25 z");
res.found = 0;
gsk_path_foreach_intersection (path1, path2, collect_cb, &res);
g_assert_true (res.found == 4);
g_assert_true (res.kind[0] == GSK_PATH_INTERSECTION_START);
g_assert_true (res.kind[1] == GSK_PATH_INTERSECTION_NORMAL);
g_assert_true (res.kind[2] == GSK_PATH_INTERSECTION_NORMAL);
g_assert_true (res.kind[3] == GSK_PATH_INTERSECTION_END);
assert_path_point_equal (&res.point1[0], 0, 1, 0.25);
assert_path_point_equal (&res.point1[1], 0, 1, 0.375);
assert_path_point_equal (&res.point1[2], 0, 1, 0.625);
assert_path_point_equal (&res.point1[3], 0, 1, 0.75);
assert_path_point_equal (&res.point2[0], 0, 1, 0);
assert_path_point_equal (&res.point2[1], 0, 6, 0.5);
assert_path_point_equal (&res.point2[2], 0, 4, 0.5);
assert_path_point_equal (&res.point2[3], 0, 1, 1);
gsk_path_unref (path1);
gsk_path_unref (path2);
}
/* the next few tests explore overlapping segments */
static void
test_intersect_coincide5 (void)
{
GskPath *path1, *path2;
CollectData res;
path1 = gsk_path_parse ("M 150 100 h 100 v 100 h -100 z");
path2 = gsk_path_parse ("M 100 100 h 200 v 50 h -100 v -50 h 25 v -50 h -100 z");
res.found = 0;
gsk_path_foreach_intersection (path1, path2, collect_cb, &res);
g_assert_true (res.found == 5);
g_assert_true (res.kind[0] == GSK_PATH_INTERSECTION_START);
g_assert_true (res.kind[1] == GSK_PATH_INTERSECTION_START);
g_assert_true (res.kind[2] == GSK_PATH_INTERSECTION_END);
g_assert_true (res.kind[3] == GSK_PATH_INTERSECTION_END);
g_assert_true (res.kind[4] == GSK_PATH_INTERSECTION_NORMAL);
assert_path_point_equal (&res.point1[0], 0, 1, 0);
assert_path_point_equal (&res.point1[1], 0, 1, 0.5);
assert_path_point_equal (&res.point1[2], 0, 1, 0.75);
assert_path_point_equal (&res.point1[3], 0, 1, 1);
assert_path_point_equal (&res.point1[4], 0, 2, 0.5);
assert_path_point_equal (&res.point2[0], 0, 1, 0.25);
assert_path_point_equal (&res.point2[1], 0, 5, 0);
assert_path_point_equal (&res.point2[2], 0, 5, 1);
assert_path_point_equal (&res.point2[3], 0, 1, 0.75);
assert_path_point_equal (&res.point2[4], 0, 3, 0.5);
gsk_path_unref (path1);
gsk_path_unref (path2);
}
static void
test_intersect_coincide6 (void)
{
GskPath *path1, *path2;
CollectData res;
path1 = gsk_path_parse ("M 150 100 h 75 l 25 50 v 50 h -100 z");
path2 = gsk_path_parse ("M 100 100 h 200 v 50 h -100 v -50 h 50 v -50 h -125 z");
res.found = 0;
gsk_path_foreach_intersection (path1, path2, collect_cb, &res);
g_assert_true (res.found == 5);
g_assert_true (res.kind[0] == GSK_PATH_INTERSECTION_START);
g_assert_true (res.kind[1] == GSK_PATH_INTERSECTION_START);
g_assert_true (res.kind[2] == GSK_PATH_INTERSECTION_END);
g_assert_true (res.kind[3] == GSK_PATH_INTERSECTION_END);
g_assert_true (res.kind[4] == GSK_PATH_INTERSECTION_NORMAL);
assert_path_point_equal (&res.point1[0], 0, 1, 0);
assert_path_point_equal (&res.point1[1], 0, 1, 2./3.);
assert_path_point_equal (&res.point1[2], 0, 1, 1);
assert_path_point_equal (&res.point1[3], 0, 1, 1);
assert_path_point_equal (&res.point1[4], 0, 3, 0);
assert_path_point_equal (&res.point2[0], 0, 1, 0.25);
assert_path_point_equal (&res.point2[1], 0, 5, 0);
assert_path_point_equal (&res.point2[2], 0, 1, 0.625);
assert_path_point_equal (&res.point2[3], 0, 5, 0.5);
assert_path_point_equal (&res.point2[4], 0, 3, 0.5);
gsk_path_unref (path1);
gsk_path_unref (path2);
}
static int
circle_intersect (const graphene_point_t *center1,
float radius1,
const graphene_point_t *center2,
float radius2,
graphene_point_t points[2])
{
float d;
float a, h;
graphene_point_t m;
graphene_vec2_t n;
g_assert (radius1 >= 0);
g_assert (radius2 >= 0);
d = graphene_point_distance (center1, center2, NULL, NULL);
if (d < fabsf (radius1 - radius2))
return 0;
if (d > radius1 + radius2)
return 0;
if (d == radius1 + radius2)
{
graphene_point_interpolate (center1, center2, radius1 / (radius1 + radius2), &points[0]);
return 1;
}
/*
a + b = d;
a^2 + h^2 = r1^2
b^2 + h^2 = r2^2
a^2 - (d - a)^2 = r1^2 - r2^2
a^2 - (d^2 - 2ad + a2) = r1^2 - r2^2
2ad -d^2 = r1^2 - r2^2
2ad = r1^2 - r2^2 + d^2
a = (r1^2 - r2^2 + d^2) / (2d)
p1/2 = c1 + a/d * (c2 - c1) +/- h * n(c1,c2);
*/
a = (radius1*radius1 - radius2*radius2 + d*d)/(2*d);
h = sqrtf (radius1*radius1 - a*a);
graphene_point_interpolate (center1, center2, a/d, &m);
graphene_vec2_init (&n, center2->y - center1->y, center1->x - center2->x);
graphene_vec2_normalize (&n, &n);
graphene_point_init (&points[0], m.x + graphene_vec2_get_x (&n) * h,
m.y + graphene_vec2_get_y (&n) * h);
graphene_point_init (&points[1], m.x - graphene_vec2_get_x (&n) * h,
m.y - graphene_vec2_get_y (&n) * h);
return 2;
}
static void
test_intersect_circle (void)
{
GskPathBuilder *builder;
GskPath *path1, *path2;
CollectData res;
builder = gsk_path_builder_new ();
gsk_path_builder_add_circle (builder, &GRAPHENE_POINT_INIT (0, 0), 12);
path1 = gsk_path_builder_free_to_path (builder);
builder = gsk_path_builder_new ();
gsk_path_builder_add_circle (builder, &GRAPHENE_POINT_INIT (1, 1), 10);
path2 = gsk_path_builder_free_to_path (builder);
res.found = 0;
gsk_path_foreach_intersection (path1, path2, collect_cb, &res);
g_assert_true (res.found == 0);
gsk_path_unref (path1);
gsk_path_unref (path2);
}
static void
test_intersect_circle2 (void)
{
GskPathBuilder *builder;
GskPath *path1, *path2;
CollectData res;
builder = gsk_path_builder_new ();
gsk_path_builder_add_circle (builder, &GRAPHENE_POINT_INIT (0, 0), 12);
path1 = gsk_path_builder_free_to_path (builder);
builder = gsk_path_builder_new ();
gsk_path_builder_add_circle (builder, &GRAPHENE_POINT_INIT (0, 25), 10);
path2 = gsk_path_builder_free_to_path (builder);
res.found = 0;
gsk_path_foreach_intersection (path1, path2, collect_cb, &res);
g_assert_true (res.found == 0);
gsk_path_unref (path1);
gsk_path_unref (path2);
}
static void
test_intersect_circle3 (void)
{
GskPathBuilder *builder;
GskPath *path1, *path2;
CollectData res;
builder = gsk_path_builder_new ();
gsk_path_builder_add_circle (builder, &GRAPHENE_POINT_INIT (0, 0), 12);
path1 = gsk_path_builder_free_to_path (builder);
builder = gsk_path_builder_new ();
gsk_path_builder_add_circle (builder, &GRAPHENE_POINT_INIT (0, 22), 10);
path2 = gsk_path_builder_free_to_path (builder);
res.found = 0;
gsk_path_foreach_intersection (path1, path2, collect_cb, &res);
g_assert_true (res.found == 1);
gsk_path_unref (path1);
gsk_path_unref (path2);
}
static void
test_intersect_circle4 (void)
{
GskPathBuilder *builder;
GskPath *path1, *path2;
CollectData res;
graphene_point_t p[2], pos;
int n;
builder = gsk_path_builder_new ();
gsk_path_builder_add_circle (builder, &GRAPHENE_POINT_INIT (0, 0), 12);
path1 = gsk_path_builder_free_to_path (builder);
builder = gsk_path_builder_new ();
gsk_path_builder_add_circle (builder, &GRAPHENE_POINT_INIT (0, 18), 10);
path2 = gsk_path_builder_free_to_path (builder);
res.found = 0;
gsk_path_foreach_intersection (path1, path2, collect_cb, &res);
g_assert_true (res.found == 2);
n = circle_intersect (&GRAPHENE_POINT_INIT (0, 0), 12,
&GRAPHENE_POINT_INIT (0, 18), 10,
p);
g_assert_true (n == 2);
gsk_path_point_get_position (&res.point1[0], path1, &pos);
g_assert_true (graphene_point_near (&p[0], &pos, 0.01));
gsk_path_point_get_position (&res.point1[1], path1, &pos);
g_assert_true (graphene_point_near (&p[1], &pos, 0.01));
gsk_path_unref (path1);
gsk_path_unref (path2);
}
static void
test_intersect_circle5 (void)
{
GskPathBuilder *builder;
GskPath *path1, *path2;
CollectData res;
graphene_point_t p[2], pos;
int n;
builder = gsk_path_builder_new ();
gsk_path_builder_add_circle (builder, &GRAPHENE_POINT_INIT (0, 0), 12);
path1 = gsk_path_builder_free_to_path (builder);
builder = gsk_path_builder_new ();
gsk_path_builder_add_circle (builder, &GRAPHENE_POINT_INIT (10, 10), 10);
path2 = gsk_path_builder_free_to_path (builder);
res.found = 0;
gsk_path_foreach_intersection (path1, path2, collect_cb, &res);
g_assert_true (res.found == 2);
n = circle_intersect (&GRAPHENE_POINT_INIT (0, 0), 12,
&GRAPHENE_POINT_INIT (10, 10), 10,
p);
g_assert_true (n == 2);
gsk_path_point_get_position (&res.point1[0], path1, &pos);
g_assert_true (graphene_point_near (&p[0], &pos, 0.01));
gsk_path_point_get_position (&res.point1[1], path1, &pos);
g_assert_true (graphene_point_near (&p[1], &pos, 0.01));
gsk_path_unref (path1);
gsk_path_unref (path2);
}
static void
test_intersect_circle6 (void)
{
GskPathBuilder *builder;
GskPath *path1;
CollectData res;
builder = gsk_path_builder_new ();
gsk_path_builder_add_circle (builder, &GRAPHENE_POINT_INIT (100, 100), 12);
path1 = gsk_path_builder_free_to_path (builder);
res.found = 0;
gsk_path_foreach_intersection (path1, path1, collect_cb, &res);
g_assert_true (res.found == 2);
g_assert_true (res.kind[0] == GSK_PATH_INTERSECTION_START);
g_assert_true (res.kind[1] == GSK_PATH_INTERSECTION_END);
gsk_path_unref (path1);
}
static void
test_intersect_circle7 (void)
{
GskPathBuilder *builder;
GskPath *path1, *path2;
CollectData res;
builder = gsk_path_builder_new ();
gsk_path_builder_add_circle (builder, &GRAPHENE_POINT_INIT (100, 100), 12);
path1 = gsk_path_builder_free_to_path (builder);
builder = gsk_path_builder_new ();
gsk_path_builder_add_reverse_path (builder, path1);
path2 = gsk_path_builder_free_to_path (builder);
res.found = 0;
gsk_path_foreach_intersection (path1, path2, collect_cb, &res);
g_assert_true (res.found == 2);
g_assert_true (res.kind[0] == GSK_PATH_INTERSECTION_START);
g_assert_true (res.kind[1] == GSK_PATH_INTERSECTION_END);
gsk_path_unref (path1);
gsk_path_unref (path2);
}
static void
test_intersect_circle_rounded_rect (void)
{
GskRoundedRect rr;
GskPathBuilder *builder;
GskPath *path1, *path2;
CollectData res;
rr.bounds = GRAPHENE_RECT_INIT (10, 10, 100, 100);
rr.corner[GSK_CORNER_TOP_LEFT] = GRAPHENE_SIZE_INIT (20, 20);
rr.corner[GSK_CORNER_TOP_RIGHT] = GRAPHENE_SIZE_INIT (20, 20);
rr.corner[GSK_CORNER_BOTTOM_RIGHT] = GRAPHENE_SIZE_INIT (20, 20);
rr.corner[GSK_CORNER_BOTTOM_LEFT] = GRAPHENE_SIZE_INIT (20, 20);
builder = gsk_path_builder_new ();
gsk_path_builder_add_rounded_rect (builder, &rr);
path1 = gsk_path_builder_free_to_path (builder);
builder = gsk_path_builder_new ();
gsk_path_builder_add_circle (builder, &GRAPHENE_POINT_INIT (30, 30), 20);
gsk_path_builder_add_circle (builder, &GRAPHENE_POINT_INIT (90, 30), 20);
gsk_path_builder_add_circle (builder, &GRAPHENE_POINT_INIT (90, 90), 20);
gsk_path_builder_add_circle (builder, &GRAPHENE_POINT_INIT (30, 90), 20);
path2 = gsk_path_builder_free_to_path (builder);
res.found = 0;
gsk_path_foreach_intersection (path1, path2, collect_cb, &res);
g_assert_true (res.found == 8);
g_assert_true (res.kind[0] == GSK_PATH_INTERSECTION_START);
g_assert_true (res.kind[1] == GSK_PATH_INTERSECTION_END);
g_assert_true (res.kind[2] == GSK_PATH_INTERSECTION_START);
g_assert_true (res.kind[3] == GSK_PATH_INTERSECTION_END);
g_assert_true (res.kind[4] == GSK_PATH_INTERSECTION_START);
g_assert_true (res.kind[5] == GSK_PATH_INTERSECTION_END);
g_assert_true (res.kind[6] == GSK_PATH_INTERSECTION_START);
g_assert_true (res.kind[7] == GSK_PATH_INTERSECTION_END);
gsk_path_unref (path1);
gsk_path_unref (path2);
}
int
main (int argc, char *argv[])
{
(g_test_init) (&argc, &argv, NULL);
g_test_add_func ("/path/intersect/simple", test_intersect_simple);
g_test_add_func ("/path/intersect/simple2", test_intersect_simple2);
g_test_add_func ("/path/intersect/simple3", test_intersect_simple3);
g_test_add_func ("/path/intersect/reverse", test_intersect_reverse);
g_test_add_func ("/path/intersect/line-box", test_intersect_line_box);
g_test_add_func ("/path/intersect/xplus", test_intersect_xplus);
g_test_add_func ("/path/intersect/point", test_intersect_point);
g_test_add_func ("/path/intersect/contours", test_intersect_contours);
g_test_add_func ("/path/intersect/contours2", test_intersect_contours2);
g_test_add_func ("/path/intersect/contours3", test_intersect_contours3);
g_test_add_func ("/path/intersect/coincide", test_intersect_coincide);
g_test_add_func ("/path/intersect/coincide2", test_intersect_coincide2);
g_test_add_func ("/path/intersect/coincide3", test_intersect_coincide3);
g_test_add_func ("/path/intersect/coincide4", test_intersect_coincide4);
g_test_add_func ("/path/intersect/coincide5", test_intersect_coincide5);
g_test_add_func ("/path/intersect/coincide6", test_intersect_coincide6);
g_test_add_func ("/path/intersect/circle", test_intersect_circle);
g_test_add_func ("/path/intersect/circle2", test_intersect_circle2);
g_test_add_func ("/path/intersect/circle3", test_intersect_circle3);
g_test_add_func ("/path/intersect/circle4", test_intersect_circle4);
g_test_add_func ("/path/intersect/circle5", test_intersect_circle5);
g_test_add_func ("/path/intersect/circle6", test_intersect_circle6);
g_test_add_func ("/path/intersect/circle7", test_intersect_circle7);
g_test_add_func ("/path/intersect/circle-rounded-rect", test_intersect_circle_rounded_rect);
return g_test_run ();
}

381
testsuite/gsk/path-ops.c Normal file
View File

@@ -0,0 +1,381 @@
/*
* Copyright © 2022 Red Hat, Inc.
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library. If not, see <http://www.gnu.org/licenses/>.
*
* Authors: Matthias Clasen <mclasen@redhat.com>
*/
#include <gtk/gtk.h>
#include "path-utils.h"
typedef enum
{
OP_UNION,
OP_INTERSECTION,
OP_DIFFERENCE,
OP_SYMMETRIC_DIFFERENCE,
} Op;
static void
test_ops_simple (void)
{
struct {
const char *in1;
const char *in2;
Op op;
const char *out;
} tests[] = {
/* partially overlapping edge */
{ "M 100 100 L 100 200 L 200 200 Z",
"M 150 150 L 150 250 L 250 250 Z",
OP_UNION,
"M 100 100 L 100 200 L 150 200 L 150 250 L 250 250 L 200 200 L 150 150 L 100 100 Z" },
{ "M 100 100 L 100 200 L 200 200 Z",
"M 150 150 L 150 250 L 250 250 Z",
OP_INTERSECTION,
"M 150 200 L 200 200 L 150 150 L 150 200 Z" },
{ "M 100 100 L 100 200 L 200 200 Z",
"M 150 150 L 150 250 L 250 250 Z",
OP_DIFFERENCE,
"M 100 100 L 100 200 L 150 200 L 150 150 L 100 100 Z" },
{ "M 100 100 L 100 200 L 200 200 Z",
"M 150 150 L 150 250 L 250 250 Z",
OP_SYMMETRIC_DIFFERENCE,
"M 100 100 L 100 200 L 150 200 L 150 150 L 100 100 Z M 200 200 L 150 200 L 150 250 "
"L 250 250 L 200 200 Z" },
/* two triangles in general position */
{ "M 100 100 L 100 200 L 200 200 Z",
"M 170 120 L 100 240 L 170 240 Z",
OP_UNION,
"M 100 100 L 100 200 L 123.33333587646484 200 L 100 240 L 170 240 L 170 200 L 200 200 "
"L 170 170 L 170 120 L 151.57894897460938 151.57894897460938 L 100 100 Z" },
{ "M 100 100 L 100 200 L 200 200 Z",
"M 170 120 L 100 240 L 170 240 Z",
OP_INTERSECTION,
"M 123.33333587646484 200 L 170 200 L 170 170 L 151.57894897460938 151.57894897460938 "
"L 123.33332824707031 200 Z" },
{ "M 100 100 L 100 200 L 200 200 Z",
"M 170 120 L 100 240 L 170 240 Z",
OP_DIFFERENCE,
"M 100 100 L 100 200 L 123.33333587646484 200 L 151.57894897460938 151.57894897460938 "
"L 100 100 Z M 170 200 L 200 200 L 170 170 L 170 200 Z" },
{ "M 100 100 L 100 200 L 200 200 Z",
"M 170 120 L 100 240 L 170 240 Z",
OP_SYMMETRIC_DIFFERENCE,
"M 100 100 L 100 200 L 123.33333587646484 200 L 151.57894897460938 151.57894897460938 "
"L 100 100 Z M 170 200 L 123.33333587646484 200 L 100 240 L 170 240 L 170 200 Z "
"M 170 200 L 200 200 L 170 170 L 170 200 Z M 151.57894897460938 151.57894897460938 "
"L 170 170 L 170 120 L 151.57894897460938 151.57894897460938 Z" },
/* nested contours, oriented in opposite direction */
{ "M 100 100 L 100 200 L 200 200 Z",
"M 120 140 L 170 190 L 120 190 Z",
OP_UNION,
"M 100 100 L 100 200 L 200 200 L 100 100 Z" },
{ "M 100 100 L 100 200 L 200 200 Z",
"M 120 140 L 170 190 L 120 190 Z",
OP_INTERSECTION,
"M 170 190 L 120 140 L 120 190 L 170 190 Z" },
{ "M 100 100 L 100 200 L 200 200 Z",
"M 120 140 L 170 190 L 120 190 Z",
OP_DIFFERENCE,
"M 100 100 L 100 200 L 200 200 L 100 100 Z M 120 140 L 170 190 L 120 190 L 120 140 Z" },
{ "M 100 100 L 100 200 L 200 200 Z",
"M 120 140 L 170 190 L 120 190 Z",
OP_SYMMETRIC_DIFFERENCE,
"M 100 100 L 100 200 L 200 200 L 100 100 Z M 120 140 L 170 190 L 120 190 L 120 140 Z" },
/* nested contours, oriented in opposite direction, other way around */
{ "M 100 100 L 200 200 L 100 200 Z",
"M 120 140 L 120 190 L 170 190 Z",
OP_UNION,
"M 200 200 L 100 100 L 100 200 L 200 200 Z" },
{ "M 100 100 L 200 200 L 100 200 Z",
"M 120 140 L 120 190 L 170 190 Z",
OP_INTERSECTION,
"M 120 140 L 120 190 L 170 190 L 120 140 Z" },
{ "M 100 100 L 200 200 L 100 200 Z",
"M 120 140 L 120 190 L 170 190 Z",
OP_DIFFERENCE,
"M 200 200 L 100 100 L 100 200 L 200 200 Z M 120 190 L 120 140 L 170 190 L 120 190 Z" },
{ "M 100 100 L 200 200 L 100 200 Z",
"M 120 140 L 120 190 L 170 190 Z",
OP_SYMMETRIC_DIFFERENCE,
"M 200 200 L 100 100 L 100 200 L 200 200 Z M 120 190 L 120 140 L 170 190 L 120 190 Z" },
/* nested contours, oriented in the same direction */
{ "M 100 100 L 100 200 L 200 200 Z",
"M 120 140 L 120 190 L 170 190 Z",
OP_UNION,
"M 100 100 L 100 200 L 200 200 L 100 100 Z" },
{ "M 100 100 L 100 200 L 200 200 Z",
"M 120 140 L 120 190 L 170 190 Z",
OP_INTERSECTION,
"M 120 140 L 120 190 L 170 190 L 120 140 Z" },
{ "M 100 100 L 100 200 L 200 200 Z",
"M 120 140 L 120 190 L 170 190 Z",
OP_DIFFERENCE,
"M 100 100 L 100 200 L 200 200 L 100 100 Z M 120 190 L 120 140 L 170 190 L 120 190 Z" },
{ "M 100 100 L 100 200 L 200 200 Z",
"M 120 140 L 120 190 L 170 190 Z",
OP_SYMMETRIC_DIFFERENCE,
"M 100 100 L 100 200 L 200 200 L 100 100 Z M 120 190 L 120 140 L 170 190 L 120 190 Z" },
/* a 3-way intersection */
{ "M 100 200 L 150 104 L 145 104 L 200 200 Z",
"M 100 108.571 L 200 108.571 L 200 50 L 100 50 Z",
OP_UNION,
"M 147.61904907226562 108.57142639160156 L 100 200 L 200 200 "
"L 147.61904907226562 108.57142639160156 Z M 100 108.57099914550781 "
"L 147.61927795410156 108.57099914550781 L 200 108.57099914550781 L 200 50 "
"L 100 50 L 100 108.57099914550781 Z" },
{ "M 100 200 L 150 104 L 145 104 L 200 200 Z",
"M 100 108.571 L 200 108.571 L 200 50 L 100 50 Z",
OP_INTERSECTION,
"M 147.61904907226562 108.57142639160156 L 150 104 L 145 104 "
"L 147.61904907226562 108.57142639160156 Z" },
{ "M 100 200 L 150 104 L 145 104 L 200 200 Z",
"M 100 108.571 L 200 108.571 L 200 50 L 100 50 Z",
OP_DIFFERENCE,
"M 147.61904907226562 108.57142639160156 L 100 200 L 200 200 "
"L 147.61904907226562 108.57142639160156 Z" },
{ "M 100 200 L 150 104 L 145 104 L 200 200 Z",
"M 100 108.571 L 200 108.571 L 200 50 L 100 50 Z",
OP_SYMMETRIC_DIFFERENCE,
"M 147.61904907226562 108.57142639160156 L 100 200 L 200 200 "
"L 147.61904907226562 108.57142639160156 Z M 150 104 "
"L 147.61904907226562 108.57142639160156 L 200 108.57099914550781 "
"L 200 50 L 100 50 L 100 108.57099914550781 L 147.61927795410156 108.57099914550781 "
"L 145 104 L 150 104 Z" },
/* touching quadratics */
{ "M 100 100 Q 150 200 200 100 Z",
"M 100 200 Q 150 100 200 200 Z",
OP_UNION,
"M 100 100 "
"Q 124.987984 149.975967, 149.975967 149.999985 "
"Q 174.987976 150.024033, 200 100 "
"L 100 100 "
"Z "
"M 149.975967 150 "
"Q 124.987984 150.024033, 100 200 "
"L 200 200 "
"Q 174.987976 149.975967, 149.975967 150.000015 "
"Z" },
/* overlapping quadratics, two intersections, different orientations */
{ "M 100 100 Q 150 200 200 100 Z",
"M 100 180 Q 150 80 200 180 Z",
OP_UNION,
"M 100 100 "
"Q 113.819313 127.638626, 127.638626 139.999374 "
"Q 113.819695 152.360611, 100 180 "
"L 200 180 "
"Q 186.180313 152.360611, 172.360611 139.999939 "
"Q 186.180298 127.639389, 200 100 "
"L 100 100 "
"Z" },
{ "M 100 100 Q 150 200 200 100 Z",
"M 100 180 Q 150 80 200 180 Z",
OP_INTERSECTION,
"M 127.638626 139.99939 "
"Q 149.999619 160.000275, 172.360611 140.000061 "
"Q 150 120.000061, 127.639389 139.999939 "
"Z" },
{ "M 100 100 Q 150 200 200 100 Z",
"M 100 180 Q 150 80 200 180 Z",
OP_DIFFERENCE,
"M 100 100 "
"Q 113.819313 127.638626, 127.638626 139.999374 "
"Q 150 120.000061, 172.360611 139.999939 "
"Q 186.180298 127.639389, 200 100 "
"L 100 100 "
"Z" },
{ "M 100 100 Q 150 200 200 100 Z",
"M 100 180 Q 150 80 200 180 Z",
OP_SYMMETRIC_DIFFERENCE,
"M 100 100 "
"Q 113.819313 127.638626, 127.638626 139.999374 "
"Q 150 120.000061, 172.360611 139.999939 "
"Q 186.180298 127.639389, 200 100 "
"L 100 100 "
"Z "
"M 172.360611 140.000061 "
"Q 149.999619 160.000275, 127.638626 139.999374 "
"Q 113.819695 152.360611, 100 180 "
"L 200 180 "
"Q 186.180313 152.360611, 172.360611 139.999939 "
"Z" },
/* overlapping quadratics, two intersections, same orientation */
{ "M 100 100 Q 150 200 200 100 Z",
"M 100 180 L 200 180 Q 150 80 100 180 Z",
OP_UNION,
"M 100 100 "
"Q 113.819313 127.638626, 127.638626 139.999374 "
"Q 113.819695 152.360611, 100 180 "
"L 200 180 "
"Q 186.180695 152.361374, 172.361389 140.000626 "
"Q 186.180298 127.639389, 200 100 "
"L 100 100 "
"Z" },
{ "M 100 100 Q 150 200 200 100 Z",
"M 100 180 L 200 180 Q 150 80 100 180 Z",
OP_INTERSECTION,
"M 127.638626 139.99939 "
"Q 149.999619 160.000275, 172.360611 140.000061 "
"Q 150.000397 119.999725, 127.639397 139.999939 "
"Z" },
{ "M 100 100 Q 150 200 200 100 Z",
"M 100 180 L 200 180 Q 150 80 100 180 Z",
OP_DIFFERENCE,
"M 100 100 "
"Q 113.819313 127.638626, 127.638626 139.999374 "
"Q 150.000397 119.999725, 172.361389 140.000626 "
"Q 186.180298 127.639389, 200 100 "
"L 100 100 "
"Z" },
{ "M 100 100 Q 150 200 200 100 Z",
"M 100 180 L 200 180 Q 150 80 100 180 Z",
OP_SYMMETRIC_DIFFERENCE,
"M 100 100 "
"Q 113.819313 127.638626, 127.638626 139.999374 "
"Q 150.000397 119.999725, 172.361389 140.000626 "
"Q 186.180298 127.639389, 200 100 "
"L 100 100 "
"Z "
"M 172.360611 140.000061 "
"Q 149.999619 160.000275, 127.638626 139.999374 "
"Q 113.819695 152.360611, 100 180 "
"L 200 180 "
"Q 186.180695 152.361374, 172.361389 140.000626 "
"Z" },
/* two polygons with near edges */
{ "M 100 100 L 100 200 L 400 200 L 400 100 Z",
"M 150 103 L 250 100 L 300 103 L 250 180 Z",
OP_UNION,
"M 100 100 L 100 200 L 400 200 L 400 100 L 250 100 L 100 100 Z" },
{ "M 100 100 L 100 200 L 400 200 L 400 100 Z",
"M 150 103 L 250 100 L 300 103 L 250 180 Z",
OP_INTERSECTION,
"M 250 100 L 150 103 L 250 180 L 300 103 L 250 100 Z" },
{ "M 100 100 L 100 200 L 400 200 L 400 100 Z",
"M 150 103 L 250 100 L 300 103 L 250 180 Z",
OP_DIFFERENCE,
"M 100 100 L 100 200 L 400 200 L 400 100 L 250 100 L 300 103 L 250 180 L 150 103 L 250 100 L 100 100 Z" },
{ "M 100 100 L 100 200 L 400 200 L 400 100 Z",
"M 150 103 L 250 100 L 300 103 L 250 180 Z",
OP_SYMMETRIC_DIFFERENCE,
"M 100 100 L 100 200 L 400 200 L 400 100 L 250 100 L 300 103 L 250 180 L 150 103 L 250 100 L 100 100 Z" },
/* Collinear line segments */
{ "M 100 100 L 200 100 L 250 100 L 100 200 Z",
"M 150 100 L 300 100 L 300 200 Z",
OP_UNION,
"M 150 100 "
"L 100 100 "
"L 100 200 "
"L 200 133.333328 "
"L 300 200 "
"L 300 100 "
"L 250 100 "
"L 200 100 "
"L 150 100 "
"Z" },
{ "M 100 100 L 200 100 L 250 100 L 100 200 Z",
"M 150 100 L 300 100 L 300 200 Z",
OP_INTERSECTION,
"M 200 100 "
"L 150 100 "
"L 200 133.333328 "
"L 250 100 "
"L 200 100 "
"Z" },
{ "M 100 100 L 200 100 L 250 100 L 100 200 Z",
"M 150 100 L 300 100 L 300 200 Z",
OP_DIFFERENCE,
"M 150 100 L 100 100 L 100 200 L 200 133.33332824707031 L 150 100 Z" },
{ "M 100 100 L 200 100 L 250 100 L 100 200 Z",
"M 150 100 L 300 100 L 300 200 Z",
OP_SYMMETRIC_DIFFERENCE,
"M 150 100 L 100 100 L 100 200 L 200 133.33332824707031 L 150 100 Z "
"M 250 100 L 200 133.33332824707031 L 300 200 L 300 100 L 250 100 Z" },
/* a complicated union */
{ "M 175 100 L 175 400 L 300 400 L 300 100 z",
"M 100 100 C 200 200 200 300 100 400 L 0 400 C 233.3333334 300 233.3333334 200 0 100 Z",
OP_UNION,
"M 175 100 "
"L 175 250 "
"L 175 400 "
"L 300 400 "
"L 300 100 "
"L 175 100 "
"Z "
"M 175 250 "
"Q 175 175, 100 100 "
"L 0 100 "
"Q 174.955811 174.981064, 174.999985 249.962112 "
"Z "
"M 100 400 "
"Q 175 325, 175 250 "
"Q 175.044189 324.981049, 0 400 "
"L 100 400 "
"Z" },
};
for (int i = 0; i < G_N_ELEMENTS (tests); i++)
{
GskPath *p1, *p2, *p3, *p;
if (g_test_verbose ())
{
const char *opname[] = { "union", "intersection", "difference", "symmetric-difference" };
g_test_message ("testcase %d op %s \"%s\" \"%s\"", i, opname[tests[i].op], tests[i].in1, tests[i].in2);
}
p1 = gsk_path_parse (tests[i].in1);
p2 = gsk_path_parse (tests[i].in2);
switch (tests[i].op)
{
case OP_UNION:
p = gsk_path_union (p1, p2, GSK_FILL_RULE_WINDING);
break;
case OP_INTERSECTION:
p = gsk_path_intersection (p1, p2, GSK_FILL_RULE_WINDING);
break;
case OP_DIFFERENCE:
p = gsk_path_difference (p1, p2, GSK_FILL_RULE_WINDING);
break;
case OP_SYMMETRIC_DIFFERENCE:
p = gsk_path_symmetric_difference (p1, p2, GSK_FILL_RULE_WINDING);
break;
default:
g_assert_not_reached ();
}
g_assert_nonnull (p);
p3 = gsk_path_parse (tests[i].out);
assert_path_equal_with_epsilon (p, p3, 0.0001);
gsk_path_unref (p);
gsk_path_unref (p1);
gsk_path_unref (p2);
gsk_path_unref (p3);
}
}
int
main (int argc,
char *argv[])
{
gtk_test_init (&argc, &argv, NULL);
g_test_add_func ("/ops/simple", test_ops_simple);
return g_test_run ();
}

View File

@@ -0,0 +1,104 @@
/* Copyright 2023 Red Hat, Inc.
*
* GTK+ is free software; you can redistribute it and/or modify it
* under the terms of the GNU Lesser General Public License as
* published by the Free Software Foundation; either version 2 of the
* License, or (at your option) any later version.
*
* GLib is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with GTK+; see the file COPYING. If not,
* see <http://www.gnu.org/licenses/>.
*
* Author: Matthias Clasen
*/
#include "config.h"
#include <gtk/gtk.h>
#include "gtk-path-tool.h"
#include <glib/gi18n-lib.h>
void
do_pathop (const char *op, int *argc, const char ***argv)
{
GError *error = NULL;
const char *fill = "winding";
char **args = NULL;
GOptionContext *context;
GOptionEntry entries[] = {
{ "fill-rule", 0, 0, G_OPTION_ARG_STRING, &fill, N_("Fill rule"), N_("RULE") },
{ G_OPTION_REMAINING, 0, 0, G_OPTION_ARG_STRING_ARRAY, &args, NULL, N_("PATH…") },
{ NULL, },
};
GskPath *path1, *path2, *result;
GskFillRule fill_rule;
char *prgname;
char *summary;
prgname = g_strconcat ("gtk4-path-tool ", op, NULL);
summary = g_strdup_printf (_("Apply the %s path operation."), op);
g_set_prgname (prgname);
context = g_option_context_new (NULL);
g_option_context_set_translation_domain (context, GETTEXT_PACKAGE);
g_option_context_add_main_entries (context, entries, NULL);
g_option_context_set_summary (context, summary);
if (!g_option_context_parse (context, argc, (char ***)argv, &error))
{
g_printerr ("%s\n", error->message);
g_error_free (error);
exit (1);
}
g_option_context_free (context);
if (args == NULL)
{
g_printerr ("%s\n", _("No paths given."));
exit (1);
}
path1 = get_path (args[0]);
if (args[1] != NULL)
path2 = get_path (args[1]);
else
path2 = NULL;
fill_rule = get_enum_value (GSK_TYPE_FILL_RULE, _("fill rule"), fill);
if (strcmp (op, "simplify") == 0)
result = gsk_path_simplify (path1, fill_rule);
else if (strcmp (op, "union") == 0)
result = gsk_path_union (path1, path2, fill_rule);
else if (strcmp (op, "intersection") == 0)
result = gsk_path_intersection (path1, path2, fill_rule);
else if (strcmp (op, "difference") == 0)
result = gsk_path_difference (path1, path2, fill_rule);
else if (strcmp (op, "symmetric-difference") == 0)
result = gsk_path_symmetric_difference (path1, path2, fill_rule);
else
{
char *msg = g_strdup_printf (_("'%s' is not a supported operation."), op);
g_printerr ("%s\n", msg);
exit (1);
}
if (result)
{
char *str = gsk_path_to_string (result);
g_print ("%s\n", str);
g_free (str);
}
else
{
g_printerr ("%s\n", _("That didn't work out."));
exit (1);
}
}

View File

@@ -32,12 +32,14 @@
#include "path-view.h" #include "path-view.h"
static void static void
show_path_fill (GskPath *path, show_path_fill (GskPath *path1,
GskPath *path2,
GskFillRule fill_rule, GskFillRule fill_rule,
const GdkRGBA *fg_color, const GdkRGBA *fg_color,
const GdkRGBA *bg_color, const GdkRGBA *bg_color,
gboolean show_points, gboolean show_points,
gboolean show_controls, gboolean show_controls,
gboolean show_intersections,
const GdkRGBA *point_color) const GdkRGBA *point_color)
{ {
GtkWidget *window, *sw, *child; GtkWidget *window, *sw, *child;
@@ -52,14 +54,17 @@ show_path_fill (GskPath *path,
gtk_scrolled_window_set_propagate_natural_height (GTK_SCROLLED_WINDOW (sw), TRUE); gtk_scrolled_window_set_propagate_natural_height (GTK_SCROLLED_WINDOW (sw), TRUE);
gtk_window_set_child (GTK_WINDOW (window), sw); gtk_window_set_child (GTK_WINDOW (window), sw);
child = path_view_new (path); child = g_object_new (PATH_TYPE_VIEW, NULL);
g_object_set (child, g_object_set (child,
"path1", path1,
"path2", path2,
"do-fill", TRUE, "do-fill", TRUE,
"fill-rule", fill_rule, "fill-rule", fill_rule,
"fg-color", fg_color, "fg-color", fg_color,
"bg-color", bg_color, "bg-color", bg_color,
"show-points", show_points, "show-points", show_points,
"show-controls", show_controls, "show-controls", show_controls,
"show-intersections", show_intersections,
"point-color", point_color, "point-color", point_color,
NULL); NULL);
@@ -74,12 +79,14 @@ show_path_fill (GskPath *path,
} }
static void static void
show_path_stroke (GskPath *path, show_path_stroke (GskPath *path1,
GskPath *path2,
GskStroke *stroke, GskStroke *stroke,
const GdkRGBA *fg_color, const GdkRGBA *fg_color,
const GdkRGBA *bg_color, const GdkRGBA *bg_color,
gboolean show_points, gboolean show_points,
gboolean show_controls, gboolean show_controls,
gboolean show_intersections,
const GdkRGBA *point_color) const GdkRGBA *point_color)
{ {
GtkWidget *window, *sw, *child; GtkWidget *window, *sw, *child;
@@ -94,14 +101,17 @@ show_path_stroke (GskPath *path,
gtk_scrolled_window_set_propagate_natural_height (GTK_SCROLLED_WINDOW (sw), TRUE); gtk_scrolled_window_set_propagate_natural_height (GTK_SCROLLED_WINDOW (sw), TRUE);
gtk_window_set_child (GTK_WINDOW (window), sw); gtk_window_set_child (GTK_WINDOW (window), sw);
child = path_view_new (path); child = g_object_new (PATH_TYPE_VIEW, NULL);
g_object_set (child, g_object_set (child,
"path1", path1,
"path2", path2,
"do-fill", FALSE, "do-fill", FALSE,
"stroke", stroke, "stroke", stroke,
"fg-color", fg_color, "fg-color", fg_color,
"bg-color", bg_color, "bg-color", bg_color,
"show-points", show_points, "show-points", show_points,
"show-controls", show_controls, "show-controls", show_controls,
"show-intersections", show_intersections,
"point-color", point_color, "point-color", point_color,
NULL); NULL);
@@ -123,6 +133,7 @@ do_show (int *argc,
gboolean do_stroke = FALSE; gboolean do_stroke = FALSE;
gboolean show_points = FALSE; gboolean show_points = FALSE;
gboolean show_controls = FALSE; gboolean show_controls = FALSE;
gboolean show_intersections = FALSE;
const char *fill = "winding"; const char *fill = "winding";
const char *fg_color = "black"; const char *fg_color = "black";
const char *bg_color = "white"; const char *bg_color = "white";
@@ -141,6 +152,7 @@ do_show (int *argc,
{ "stroke", 0, 0, G_OPTION_ARG_NONE, &do_stroke, N_("Stroke the path"), NULL }, { "stroke", 0, 0, G_OPTION_ARG_NONE, &do_stroke, N_("Stroke the path"), NULL },
{ "points", 0, 0, G_OPTION_ARG_NONE, &show_points, N_("Show path points"), NULL }, { "points", 0, 0, G_OPTION_ARG_NONE, &show_points, N_("Show path points"), NULL },
{ "controls", 0, 0, G_OPTION_ARG_NONE, &show_controls, N_("Show control points"), NULL }, { "controls", 0, 0, G_OPTION_ARG_NONE, &show_controls, N_("Show control points"), NULL },
{ "intersections", 0, 0, G_OPTION_ARG_NONE, &show_intersections, N_("Show intersections"), NULL },
{ "fg-color", 0, 0, G_OPTION_ARG_STRING, &fg_color, N_("Foreground color"), N_("COLOR") }, { "fg-color", 0, 0, G_OPTION_ARG_STRING, &fg_color, N_("Foreground color"), N_("COLOR") },
{ "bg-color", 0, 0, G_OPTION_ARG_STRING, &bg_color, N_("Background color"), N_("COLOR") }, { "bg-color", 0, 0, G_OPTION_ARG_STRING, &bg_color, N_("Background color"), N_("COLOR") },
{ "point-color", 0, 0, G_OPTION_ARG_STRING, &point_color, N_("Point color"), N_("COLOR") }, { "point-color", 0, 0, G_OPTION_ARG_STRING, &point_color, N_("Point color"), N_("COLOR") },
@@ -160,7 +172,8 @@ do_show (int *argc,
{ "dash-offset", 0, 0, G_OPTION_ARG_DOUBLE, &dash_offset, N_("Dash offset (number)"), N_("VALUE") }, { "dash-offset", 0, 0, G_OPTION_ARG_DOUBLE, &dash_offset, N_("Dash offset (number)"), N_("VALUE") },
{ NULL, } { NULL, }
}; };
GskPath *path; GskPath *path1;
GskPath *path2;
GskFillRule fill_rule; GskFillRule fill_rule;
GdkRGBA fg, bg, pc; GdkRGBA fg, bg, pc;
GskLineCap line_cap; GskLineCap line_cap;
@@ -210,13 +223,17 @@ do_show (int *argc,
exit (1); exit (1);
} }
if (g_strv_length (args) > 1) if (g_strv_length (args) > 2)
{ {
g_printerr ("%s\n", _("Can only show a single path")); g_printerr ("%s\n", _("Can only show one or two paths"));
exit (1); exit (1);
} }
path = get_path (args[0]); path1 = get_path (args[0]);
if (g_strv_length (args) > 1)
path2 = get_path (args[1]);
else
path2 = NULL;
fill_rule = get_enum_value (GSK_TYPE_FILL_RULE, _("fill rule"), fill); fill_rule = get_enum_value (GSK_TYPE_FILL_RULE, _("fill rule"), fill);
get_color (&fg, fg_color); get_color (&fg, fg_color);
@@ -234,11 +251,12 @@ do_show (int *argc,
_gsk_stroke_set_dashes (stroke, dashes); _gsk_stroke_set_dashes (stroke, dashes);
if (do_stroke) if (do_stroke)
show_path_stroke (path, stroke, &fg, &bg, show_points, show_controls, &pc); show_path_stroke (path1, path2, stroke, &fg, &bg, show_points, show_controls, show_intersections, &pc);
else else
show_path_fill (path, fill_rule, &fg, &bg, show_points, show_controls, &pc); show_path_fill (path1, path2, fill_rule, &fg, &bg, show_points, show_controls, show_intersections, &pc);
gsk_path_unref (path); g_clear_pointer (&path1, gsk_path_unref);
g_clear_pointer (&path2, gsk_path_unref);
g_strfreev (args); g_strfreev (args);
} }

View File

@@ -38,6 +38,12 @@ usage (void)
"Perform various tasks on paths.\n" "Perform various tasks on paths.\n"
"\n" "\n"
"Commands:\n" "Commands:\n"
" simplify Simplify the path\n"
" intersection Intersect two paths\n"
" union Create the union of two paths\n"
" difference Create the difference of two paths\n"
" symmetric-difference\n"
" Create the symmetric difference of two paths\n"
" decompose Decompose the path\n" " decompose Decompose the path\n"
" reverse Reverse the path\n" " reverse Reverse the path\n"
" restrict Restrict the path to a segment\n" " restrict Restrict the path to a segment\n"
@@ -128,8 +134,12 @@ main (int argc, const char *argv[])
if (strcmp (argv[0], "decompose") == 0) if (strcmp (argv[0], "decompose") == 0)
do_decompose (&argc, &argv); do_decompose (&argc, &argv);
else if (strcmp (argv[0], "difference") == 0)
do_pathop (argv[0], &argc, &argv);
else if (strcmp (argv[0], "info") == 0) else if (strcmp (argv[0], "info") == 0)
do_info (&argc, &argv); do_info (&argc, &argv);
else if (strcmp (argv[0], "intersection") == 0)
do_pathop (argv[0], &argc, &argv);
else if (strcmp (argv[0], "render") == 0) else if (strcmp (argv[0], "render") == 0)
do_render (&argc, &argv); do_render (&argc, &argv);
else if (strcmp (argv[0], "restrict") == 0) else if (strcmp (argv[0], "restrict") == 0)
@@ -138,6 +148,12 @@ main (int argc, const char *argv[])
do_reverse (&argc, &argv); do_reverse (&argc, &argv);
else if (strcmp (argv[0], "show") == 0) else if (strcmp (argv[0], "show") == 0)
do_show (&argc, &argv); do_show (&argc, &argv);
else if (strcmp (argv[0], "simplify") == 0)
do_pathop (argv[0], &argc, &argv);
else if (strcmp (argv[0], "symmetric-difference") == 0)
do_pathop (argv[0], &argc, &argv);
else if (strcmp (argv[0], "union") == 0)
do_pathop (argv[0], &argc, &argv);
else else
usage (); usage ();

View File

@@ -6,6 +6,7 @@ void do_restrict (int *argc, const char ***argv);
void do_reverse (int *argc, const char ***argv); void do_reverse (int *argc, const char ***argv);
void do_render (int *argc, const char ***argv); void do_render (int *argc, const char ***argv);
void do_show (int *argc, const char ***argv); void do_show (int *argc, const char ***argv);
void do_pathop (const char *op, int *argc, const char ***argv);
GskPath *get_path (const char *arg); GskPath *get_path (const char *arg);
int get_enum_value (GType type, int get_enum_value (GType type,

View File

@@ -26,6 +26,7 @@ gtk_tools = [
['gtk4-path-tool', ['gtk-path-tool.c', ['gtk4-path-tool', ['gtk-path-tool.c',
'gtk-path-tool-decompose.c', 'gtk-path-tool-decompose.c',
'gtk-path-tool-info.c', 'gtk-path-tool-info.c',
'gtk-path-tool-pathops.c',
'gtk-path-tool-render.c', 'gtk-path-tool-render.c',
'gtk-path-tool-restrict.c', 'gtk-path-tool-restrict.c',
'gtk-path-tool-reverse.c', 'gtk-path-tool-reverse.c',

View File

@@ -25,6 +25,8 @@ struct _PathView
{ {
GtkWidget parent_instance; GtkWidget parent_instance;
GskPath *path1;
GskPath *path2;
GskPath *path; GskPath *path;
GskStroke *stroke; GskStroke *stroke;
graphene_rect_t bounds; graphene_rect_t bounds;
@@ -35,13 +37,17 @@ struct _PathView
gboolean do_fill; gboolean do_fill;
gboolean show_points; gboolean show_points;
gboolean show_controls; gboolean show_controls;
gboolean show_intersections;
GskPath *line_path; GskPath *line_path;
GskPath *point_path; GskPath *point_path;
GdkRGBA point_color; GdkRGBA point_color;
GskPath *intersection_line_path;
GskPath *intersection_point_path;
}; };
enum { enum {
PROP_PATH = 1, PROP_PATH1 = 1,
PROP_PATH2,
PROP_DO_FILL, PROP_DO_FILL,
PROP_STROKE, PROP_STROKE,
PROP_FILL_RULE, PROP_FILL_RULE,
@@ -50,6 +56,7 @@ enum {
PROP_POINT_COLOR, PROP_POINT_COLOR,
PROP_SHOW_POINTS, PROP_SHOW_POINTS,
PROP_SHOW_CONTROLS, PROP_SHOW_CONTROLS,
PROP_SHOW_INTERSECTIONS,
N_PROPERTIES N_PROPERTIES
}; };
@@ -79,10 +86,14 @@ path_view_dispose (GObject *object)
{ {
PathView *self = PATH_VIEW (object); PathView *self = PATH_VIEW (object);
g_clear_pointer (&self->path1, gsk_path_unref);
g_clear_pointer (&self->path2, gsk_path_unref);
g_clear_pointer (&self->path, gsk_path_unref); g_clear_pointer (&self->path, gsk_path_unref);
g_clear_pointer (&self->stroke, gsk_stroke_free); g_clear_pointer (&self->stroke, gsk_stroke_free);
g_clear_pointer (&self->line_path, gsk_path_unref); g_clear_pointer (&self->line_path, gsk_path_unref);
g_clear_pointer (&self->point_path, gsk_path_unref); g_clear_pointer (&self->point_path, gsk_path_unref);
g_clear_pointer (&self->intersection_line_path, gsk_path_unref);
g_clear_pointer (&self->intersection_point_path, gsk_path_unref);
G_OBJECT_CLASS (path_view_parent_class)->dispose (object); G_OBJECT_CLASS (path_view_parent_class)->dispose (object);
} }
@@ -97,8 +108,12 @@ path_view_get_property (GObject *object,
switch (prop_id) switch (prop_id)
{ {
case PROP_PATH: case PROP_PATH1:
g_value_set_boxed (value, self->path); g_value_set_boxed (value, self->path1);
break;
case PROP_PATH2:
g_value_set_boxed (value, self->path2);
break; break;
case PROP_DO_FILL: case PROP_DO_FILL:
@@ -129,6 +144,10 @@ path_view_get_property (GObject *object,
g_value_set_boolean (value, self->show_controls); g_value_set_boolean (value, self->show_controls);
break; break;
case PROP_SHOW_INTERSECTIONS:
g_value_set_boolean (value, self->show_intersections);
break;
case PROP_POINT_COLOR: case PROP_POINT_COLOR:
g_value_set_boxed (value, &self->point_color); g_value_set_boxed (value, &self->point_color);
break; break;
@@ -268,6 +287,93 @@ update_controls (PathView *self)
update_bounds (self); update_bounds (self);
} }
typedef struct {
GskPathBuilder *line_builder;
GskPathBuilder *point_builder;
GskPathPoint start;
int segment;
} IntersectionData;
static gboolean
intersection_cb (GskPath *path1,
const GskPathPoint *point1,
GskPath *path2,
const GskPathPoint *point2,
GskPathIntersection kind,
gpointer data)
{
IntersectionData *id = data;
graphene_point_t pos;
switch (kind)
{
case GSK_PATH_INTERSECTION_NORMAL:
gsk_path_point_get_position (point1, path1, &pos);
gsk_path_builder_add_circle (id->point_builder, &pos, 3);
break;
case GSK_PATH_INTERSECTION_START:
if (id->segment == 0)
id->start = *point1;
id->segment++;
break;
case GSK_PATH_INTERSECTION_END:
id->segment--;
if (id->segment == 0)
gsk_path_builder_add_segment (id->line_builder, path1, &id->start, point1);
break;
case GSK_PATH_INTERSECTION_NONE:
default:
g_assert_not_reached ();
}
return TRUE;
}
static void
update_intersections (PathView *self)
{
IntersectionData id;
g_clear_pointer (&self->intersection_line_path, gsk_path_unref);
g_clear_pointer (&self->intersection_point_path, gsk_path_unref);
if (!self->show_intersections || !self->path1 || !self->path2)
return;
id.line_builder = gsk_path_builder_new ();
id.point_builder = gsk_path_builder_new ();
id.segment = 0;
gsk_path_foreach_intersection (self->path1, self->path2, intersection_cb, &id);
self->intersection_line_path = gsk_path_builder_free_to_path (id.line_builder);
self->intersection_point_path = gsk_path_builder_free_to_path (id.point_builder);
gtk_widget_queue_draw (GTK_WIDGET (self));
}
static void
update_path (PathView *self)
{
GskPathBuilder *builder;
g_clear_pointer (&self->path, gsk_path_unref);
builder = gsk_path_builder_new ();
if (self->path1)
gsk_path_builder_add_path (builder, self->path1);
if (self->path2)
gsk_path_builder_add_path (builder, self->path2);
self->path = gsk_path_builder_free_to_path (builder);
update_intersections (self);
update_controls (self);
}
static void static void
path_view_set_property (GObject *object, path_view_set_property (GObject *object,
guint prop_id, guint prop_id,
@@ -278,11 +384,16 @@ path_view_set_property (GObject *object,
switch (prop_id) switch (prop_id)
{ {
case PROP_PATH1:
g_clear_pointer (&self->path1, gsk_path_unref);
self->path1 = g_value_dup_boxed (value);
update_path (self);
break;
case PROP_PATH: case PROP_PATH2:
g_clear_pointer (&self->path, gsk_path_unref); g_clear_pointer (&self->path2, gsk_path_unref);
self->path = g_value_dup_boxed (value); self->path2 = g_value_dup_boxed (value);
update_controls (self); update_path (self);
break; break;
case PROP_DO_FILL: case PROP_DO_FILL:
@@ -321,6 +432,11 @@ path_view_set_property (GObject *object,
update_controls (self); update_controls (self);
break; break;
case PROP_SHOW_INTERSECTIONS:
self->show_intersections = g_value_get_boolean (value);
update_intersections (self);
break;
case PROP_POINT_COLOR: case PROP_POINT_COLOR:
self->point_color = *(GdkRGBA *) g_value_get_boxed (value); self->point_color = *(GdkRGBA *) g_value_get_boxed (value);
gtk_widget_queue_draw (GTK_WIDGET (self)); gtk_widget_queue_draw (GTK_WIDGET (self));
@@ -344,9 +460,9 @@ path_view_measure (GtkWidget *widget,
PathView *self = PATH_VIEW (widget); PathView *self = PATH_VIEW (widget);
if (orientation == GTK_ORIENTATION_HORIZONTAL) if (orientation == GTK_ORIENTATION_HORIZONTAL)
*minimum = *natural = (int) ceilf (self->bounds.size.width) + 2 * self->padding; *minimum = *natural = (int) ceilf (self->bounds.origin.x + self->bounds.size.width) + 2 * self->padding;
else else
*minimum = *natural = (int) ceilf (self->bounds.size.height) + 2 * self->padding; *minimum = *natural = (int) ceilf (self->bounds.origin.y + self->bounds.size.height) + 2 * self->padding;
} }
static void static void
@@ -383,6 +499,18 @@ path_view_snapshot (GtkWidget *widget,
gtk_snapshot_append_stroke (snapshot, self->point_path, stroke, &self->fg); gtk_snapshot_append_stroke (snapshot, self->point_path, stroke, &self->fg);
} }
if (self->intersection_line_path)
{
GskStroke *stroke = gsk_stroke_new (gsk_stroke_get_line_width (self->stroke));
gtk_snapshot_append_stroke (snapshot, self->intersection_line_path, stroke, &self->point_color);
}
if (self->intersection_point_path)
{
gtk_snapshot_append_fill (snapshot, self->intersection_point_path, GSK_FILL_RULE_WINDING, &self->point_color);
}
gtk_snapshot_restore (snapshot); gtk_snapshot_restore (snapshot);
} }
@@ -399,8 +527,13 @@ path_view_class_init (PathViewClass *class)
widget_class->measure = path_view_measure; widget_class->measure = path_view_measure;
widget_class->snapshot = path_view_snapshot; widget_class->snapshot = path_view_snapshot;
properties[PROP_PATH] properties[PROP_PATH1]
= g_param_spec_boxed ("path", NULL, NULL, = g_param_spec_boxed ("path1", NULL, NULL,
GSK_TYPE_PATH,
G_PARAM_READWRITE|G_PARAM_EXPLICIT_NOTIFY);
properties[PROP_PATH2]
= g_param_spec_boxed ("path2", NULL, NULL,
GSK_TYPE_PATH, GSK_TYPE_PATH,
G_PARAM_READWRITE|G_PARAM_EXPLICIT_NOTIFY); G_PARAM_READWRITE|G_PARAM_EXPLICIT_NOTIFY);
@@ -440,6 +573,11 @@ path_view_class_init (PathViewClass *class)
FALSE, FALSE,
G_PARAM_READWRITE|G_PARAM_EXPLICIT_NOTIFY); G_PARAM_READWRITE|G_PARAM_EXPLICIT_NOTIFY);
properties[PROP_SHOW_INTERSECTIONS]
= g_param_spec_boolean ("show-intersections", NULL, NULL,
FALSE,
G_PARAM_READWRITE|G_PARAM_EXPLICIT_NOTIFY);
properties[PROP_POINT_COLOR] properties[PROP_POINT_COLOR]
= g_param_spec_boxed ("point-color", NULL, NULL, = g_param_spec_boxed ("point-color", NULL, NULL,
GDK_TYPE_RGBA, GDK_TYPE_RGBA,
@@ -452,6 +590,6 @@ GtkWidget *
path_view_new (GskPath *path) path_view_new (GskPath *path)
{ {
return g_object_new (PATH_TYPE_VIEW, return g_object_new (PATH_TYPE_VIEW,
"path", path, "path1", path,
NULL); NULL);
} }