Compare commits

...

9 Commits

Author SHA1 Message Date
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
17 changed files with 3805 additions and 204 deletions

View File

@@ -339,6 +339,7 @@
<file>path_fill.c</file>
<file>path_maze.c</file>
<file>path_spinner.c</file>
<file>path_sweep.c</file>
<file>path_walk.c</file>
<file>path_text.c</file>
<file>peg_solitaire.c</file>
@@ -426,6 +427,10 @@
<gresource prefix="/fontrendering">
<file>fontrendering.ui</file>
</gresource>
<gresource prefix="/path_sweep">
<file>path_sweep.ui</file>
<file compressed="true">path_world.txt</file>
</gresource>
<gresource prefix="/path_walk">
<file>path_walk.ui</file>
<file compressed="true">path_world.txt</file>

View File

@@ -75,6 +75,7 @@ demos = files([
'path_fill.c',
'path_maze.c',
'path_spinner.c',
'path_sweep.c',
'path_walk.c',
'path_text.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

@@ -1749,6 +1749,19 @@ gsk_circle_contour_new (const graphene_point_t *center,
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 */

View File

@@ -36,6 +36,10 @@ GskContour * gsk_standard_contour_new (GskPathFlags
GskContour * gsk_circle_contour_new (const graphene_point_t *center,
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_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);
}
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
project_point_onto_line (const GskCurve *curve,
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);
}
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: */

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,
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

View File

@@ -144,6 +144,55 @@ gboolean gsk_path_foreach (GskPath
GskPathForeachFunc func,
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);
G_DEFINE_AUTOPTR_CLEANUP_FUNC(GskPath, gsk_path_unref)
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: */

View File

@@ -28,6 +28,7 @@ gsk_public_sources = files([
'gskglshader.c',
'gskpath.c',
'gskpathbuilder.c',
'gskpathintersect.c',
'gskpathmeasure.c',
'gskpathparser.c',
'gskpathpoint.c',
@@ -45,6 +46,7 @@ gsk_private_sources = files([
'gskcairoblur.c',
'gskcontour.c',
'gskcurve.c',
'gskcurveintersect.c',
'gskdebug.c',
'gskprivate.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,7 @@ tests = [
['shader'],
['path', [ 'path-utils.c' ] ],
['path-special-cases'],
['path-intersect'],
]
test_cargs = []
@@ -406,6 +407,7 @@ endforeach
internal_tests = [
[ 'curve' ],
[ 'curve-special-cases' ],
[ 'curve-intersect' ],
[ 'path-private' ],
[ 'diff' ],
[ '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 ();
}

View File

@@ -32,12 +32,14 @@
#include "path-view.h"
static void
show_path_fill (GskPath *path,
show_path_fill (GskPath *path1,
GskPath *path2,
GskFillRule fill_rule,
const GdkRGBA *fg_color,
const GdkRGBA *bg_color,
gboolean show_points,
gboolean show_controls,
gboolean show_intersections,
const GdkRGBA *point_color)
{
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_window_set_child (GTK_WINDOW (window), sw);
child = path_view_new (path);
child = g_object_new (PATH_TYPE_VIEW, NULL);
g_object_set (child,
"path1", path1,
"path2", path2,
"do-fill", TRUE,
"fill-rule", fill_rule,
"fg-color", fg_color,
"bg-color", bg_color,
"show-points", show_points,
"show-controls", show_controls,
"show-intersections", show_intersections,
"point-color", point_color,
NULL);
@@ -74,12 +79,14 @@ show_path_fill (GskPath *path,
}
static void
show_path_stroke (GskPath *path,
show_path_stroke (GskPath *path1,
GskPath *path2,
GskStroke *stroke,
const GdkRGBA *fg_color,
const GdkRGBA *bg_color,
gboolean show_points,
gboolean show_controls,
gboolean show_intersections,
const GdkRGBA *point_color)
{
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_window_set_child (GTK_WINDOW (window), sw);
child = path_view_new (path);
child = g_object_new (PATH_TYPE_VIEW, NULL);
g_object_set (child,
"path1", path1,
"path2", path2,
"do-fill", FALSE,
"stroke", stroke,
"fg-color", fg_color,
"bg-color", bg_color,
"show-points", show_points,
"show-controls", show_controls,
"show-intersections", show_intersections,
"point-color", point_color,
NULL);
@@ -123,6 +133,7 @@ do_show (int *argc,
gboolean do_stroke = FALSE;
gboolean show_points = FALSE;
gboolean show_controls = FALSE;
gboolean show_intersections = FALSE;
const char *fill = "winding";
const char *fg_color = "black";
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 },
{ "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 },
{ "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") },
{ "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") },
@@ -160,7 +172,8 @@ do_show (int *argc,
{ "dash-offset", 0, 0, G_OPTION_ARG_DOUBLE, &dash_offset, N_("Dash offset (number)"), N_("VALUE") },
{ NULL, }
};
GskPath *path;
GskPath *path1;
GskPath *path2;
GskFillRule fill_rule;
GdkRGBA fg, bg, pc;
GskLineCap line_cap;
@@ -210,13 +223,17 @@ do_show (int *argc,
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);
}
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);
get_color (&fg, fg_color);
@@ -234,11 +251,12 @@ do_show (int *argc,
_gsk_stroke_set_dashes (stroke, dashes);
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
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);
}

View File

@@ -25,6 +25,8 @@ struct _PathView
{
GtkWidget parent_instance;
GskPath *path1;
GskPath *path2;
GskPath *path;
GskStroke *stroke;
graphene_rect_t bounds;
@@ -35,13 +37,17 @@ struct _PathView
gboolean do_fill;
gboolean show_points;
gboolean show_controls;
gboolean show_intersections;
GskPath *line_path;
GskPath *point_path;
GdkRGBA point_color;
GskPath *intersection_line_path;
GskPath *intersection_point_path;
};
enum {
PROP_PATH = 1,
PROP_PATH1 = 1,
PROP_PATH2,
PROP_DO_FILL,
PROP_STROKE,
PROP_FILL_RULE,
@@ -50,6 +56,7 @@ enum {
PROP_POINT_COLOR,
PROP_SHOW_POINTS,
PROP_SHOW_CONTROLS,
PROP_SHOW_INTERSECTIONS,
N_PROPERTIES
};
@@ -79,10 +86,14 @@ path_view_dispose (GObject *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->stroke, gsk_stroke_free);
g_clear_pointer (&self->line_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);
}
@@ -97,8 +108,12 @@ path_view_get_property (GObject *object,
switch (prop_id)
{
case PROP_PATH:
g_value_set_boxed (value, self->path);
case PROP_PATH1:
g_value_set_boxed (value, self->path1);
break;
case PROP_PATH2:
g_value_set_boxed (value, self->path2);
break;
case PROP_DO_FILL:
@@ -129,6 +144,10 @@ path_view_get_property (GObject *object,
g_value_set_boolean (value, self->show_controls);
break;
case PROP_SHOW_INTERSECTIONS:
g_value_set_boolean (value, self->show_intersections);
break;
case PROP_POINT_COLOR:
g_value_set_boxed (value, &self->point_color);
break;
@@ -268,6 +287,93 @@ update_controls (PathView *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
path_view_set_property (GObject *object,
guint prop_id,
@@ -278,11 +384,16 @@ path_view_set_property (GObject *object,
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:
g_clear_pointer (&self->path, gsk_path_unref);
self->path = g_value_dup_boxed (value);
update_controls (self);
case PROP_PATH2:
g_clear_pointer (&self->path2, gsk_path_unref);
self->path2 = g_value_dup_boxed (value);
update_path (self);
break;
case PROP_DO_FILL:
@@ -321,6 +432,11 @@ path_view_set_property (GObject *object,
update_controls (self);
break;
case PROP_SHOW_INTERSECTIONS:
self->show_intersections = g_value_get_boolean (value);
update_intersections (self);
break;
case PROP_POINT_COLOR:
self->point_color = *(GdkRGBA *) g_value_get_boxed (value);
gtk_widget_queue_draw (GTK_WIDGET (self));
@@ -344,9 +460,9 @@ path_view_measure (GtkWidget *widget,
PathView *self = PATH_VIEW (widget);
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
*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
@@ -383,6 +499,18 @@ path_view_snapshot (GtkWidget *widget,
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);
}
@@ -399,8 +527,13 @@ path_view_class_init (PathViewClass *class)
widget_class->measure = path_view_measure;
widget_class->snapshot = path_view_snapshot;
properties[PROP_PATH]
= g_param_spec_boxed ("path", NULL, NULL,
properties[PROP_PATH1]
= 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,
G_PARAM_READWRITE|G_PARAM_EXPLICIT_NOTIFY);
@@ -440,6 +573,11 @@ path_view_class_init (PathViewClass *class)
FALSE,
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]
= g_param_spec_boxed ("point-color", NULL, NULL,
GDK_TYPE_RGBA,
@@ -452,6 +590,6 @@ GtkWidget *
path_view_new (GskPath *path)
{
return g_object_new (PATH_TYPE_VIEW,
"path", path,
"path1", path,
NULL);
}