Add tests for stroking

Add tests to check that any point on a path is
always at most half the line-width away from the
stroke path with that line-width.

Also check that the outlines of random paths look
as expected. We currently have to exclude paths
where points get too close to each other.
This commit is contained in:
Matthias Clasen
2023-06-11 22:55:43 -04:00
parent cd6d88fca5
commit a13929a53c
3 changed files with 303 additions and 0 deletions

View File

@@ -358,6 +358,7 @@ tests = [
['dash'],
['path'],
['path-special-cases'],
['path-stroke'],
['transform'],
['shader'],
]

199
testsuite/gsk/path-stroke.c Normal file
View File

@@ -0,0 +1,199 @@
/*
* Copyright © 2020 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>
/* Test that single-point contours don't crash the stroker */
static void
test_point_to_stroke (void)
{
GskPathBuilder *builder;
GskPath *path;
GskStroke *stroke;
GskPath *path1;
char *string;
builder = gsk_path_builder_new ();
gsk_path_builder_move_to (builder, 100, 100);
gsk_path_builder_cubic_to (builder, 190, 110,
200, 120,
210, 210);
gsk_path_builder_cubic_to (builder, 220, 210,
230, 200,
230, 100);
gsk_path_builder_move_to (builder, 200, 200);
path = gsk_path_builder_free_to_path (builder);
string = gsk_path_to_string (path);
g_assert_cmpstr (string, ==, "M 100 100 C 190 110, 200 120, 210 210 C 220 210, 230 200, 230 100 M 200 200");
g_free (string);
stroke = gsk_stroke_new (20.f);
path1 = gsk_path_stroke (path, stroke);
gsk_stroke_free (stroke);
g_assert_nonnull (path1);
gsk_path_unref (path1);
gsk_path_unref (path);
}
/* Test that the offset curves are generally where they need to be */
static void
check_stroke_at_position (GskPathMeasure *measure,
GskStroke *stroke,
GskPathMeasure *stroke_measure,
float position)
{
graphene_point_t p;
graphene_point_t s;
float w;
float tolerance;
float d;
w = gsk_stroke_get_line_width (stroke);
tolerance = gsk_path_measure_get_tolerance (stroke_measure);
gsk_path_measure_get_point (measure, position, &p, NULL);
gsk_path_measure_get_closest_point (stroke_measure, &p, &s);
d = graphene_point_distance (&p, &s, NULL, NULL);
g_assert_cmpfloat (d, <=, w/2 + tolerance);
}
static void
check_stroke_distance (GskPath *path,
GskPathMeasure *measure,
GskStroke *stroke,
GskPath *stroke_path)
{
GskPathMeasure *stroke_measure;
float length;
float t;
int i;
stroke_measure = gsk_path_measure_new_with_tolerance (stroke_path, 0.1);
length = gsk_path_measure_get_length (measure);
for (i = 0; i < 1000; i++)
{
t = g_test_rand_double_range (0, length);
check_stroke_at_position (measure, stroke, stroke_measure, t);
}
gsk_path_measure_unref (stroke_measure);
}
static void
test_rect_stroke_distance (void)
{
GskPathBuilder *builder;
GskPath *path;
GskPathMeasure *measure;
GskPath *stroke_path;
GskStroke *stroke;
builder = gsk_path_builder_new ();
gsk_path_builder_add_rect (builder, &GRAPHENE_RECT_INIT (0, 0, 100, 100));
path = gsk_path_builder_free_to_path (builder);
stroke = gsk_stroke_new (10);
measure = gsk_path_measure_new (path);
stroke_path = gsk_path_stroke (path, stroke);
check_stroke_distance (path, measure, stroke, stroke_path);
gsk_stroke_free (stroke);
gsk_path_unref (stroke_path);
gsk_path_measure_unref (measure);
gsk_path_unref (path);
}
static void
test_circle_stroke_distance (void)
{
GskPathBuilder *builder;
GskPath *path;
GskPathMeasure *measure;
GskPath *stroke_path;
GskStroke *stroke;
builder = gsk_path_builder_new ();
gsk_path_builder_add_circle (builder, &GRAPHENE_POINT_INIT (100, 100), 50);
path = gsk_path_builder_free_to_path (builder);
stroke = gsk_stroke_new (10);
measure = gsk_path_measure_new (path);
stroke_path = gsk_path_stroke (path, stroke);
check_stroke_distance (path, measure, stroke, stroke_path);
gsk_stroke_free (stroke);
gsk_path_unref (stroke_path);
gsk_path_measure_unref (measure);
gsk_path_unref (path);
}
static void
test_path_stroke_distance (void)
{
GskPath *path;
GskPathMeasure *measure;
GskPath *stroke_path;
GskStroke *stroke;
path = gsk_path_parse ("M 250 150 A 100 100 0 0 0 50 150 A 100 100 0 0 0 250 150 z M 100 100 h 100 v 100 h -100 z M 300 150 C 300 50, 400 50, 400 150 C 400 250, 500 250, 500 150 L 600 150 L 530 190");
stroke = gsk_stroke_new (10);
measure = gsk_path_measure_new (path);
stroke_path = gsk_path_stroke (path, stroke);
check_stroke_distance (path, measure, stroke, stroke_path);
gsk_stroke_free (stroke);
gsk_path_unref (stroke_path);
gsk_path_measure_unref (measure);
gsk_path_unref (path);
}
int
main (int argc,
char *argv[])
{
gtk_test_init (&argc, &argv, NULL);
g_test_add_func ("/stroke/point", test_point_to_stroke);
g_test_add_func ("/stroke/rect/distance", test_rect_stroke_distance);
g_test_add_func ("/stroke/circle/distance", test_circle_stroke_distance);
g_test_add_func ("/stroke/path/distance", test_path_stroke_distance);
return g_test_run ();
}

View File

@@ -1162,6 +1162,108 @@ test_in_fill_rotated (void)
#undef N_FILL_RULES
}
static void
check_distance_at_position (GskPathMeasure *measure,
GskPathMeasure *measure2,
float distance,
float position)
{
graphene_point_t p;
graphene_point_t s;
float d;
gsk_path_measure_get_point (measure, position, &p, NULL);
gsk_path_measure_get_closest_point (measure2, &p, &s);
d = graphene_point_distance (&p, &s, NULL, NULL);
g_assert_true (d <= distance + gsk_path_measure_get_tolerance (measure2));
}
static void
check_path_distance (GskPath *path,
GskPath *path2,
float distance)
{
GskPathMeasure *measure;
GskPathMeasure *measure2;
float length;
float t;
int i;
measure = gsk_path_measure_new_with_tolerance (path, 0.1);
measure2 = gsk_path_measure_new_with_tolerance (path2, 0.1);
length = gsk_path_measure_get_length (measure);
for (i = 0; i < 1000; i++)
{
t = g_test_rand_double_range (0, length);
check_distance_at_position (measure, measure2, distance, t);
}
gsk_path_measure_unref (measure);
gsk_path_measure_unref (measure2);
}
static gboolean
check_spaced_out (GskPathOperation op,
const graphene_point_t *pts,
gsize n_pts,
float weight,
gpointer user_data)
{
float *distance = user_data;
if (n_pts == 1)
return TRUE;
return graphene_point_distance (&pts[0], &pts[n_pts - 1], NULL, NULL) >= *distance;
}
static gboolean
path_is_spaced_out (GskPath *path,
float distance)
{
return gsk_path_foreach (path, -1, check_spaced_out, &distance);
}
static void
test_path_stroke_distance (void)
{
GskPath *path;
GskPath *stroke_path;
GskStroke *stroke;
int i;
float width = 10;
stroke = gsk_stroke_new (width);
gsk_stroke_set_line_cap (stroke, GSK_LINE_CAP_ROUND);
gsk_stroke_set_line_join (stroke, GSK_LINE_JOIN_ROUND);
i = 0;
while (i < 1000)
{
path = create_random_path (1);
/* We know the stroker can't robustly handle paths with
* too close neighbouring points
*/
if (!path_is_spaced_out (path, width / 2))
continue;
stroke_path = gsk_path_stroke (path, stroke);
check_path_distance (path, stroke_path, width / 2);
/* This relies on round joins */
check_path_distance (stroke_path, path, width / 2);
gsk_path_unref (stroke_path);
gsk_path_unref (path);
i++;
}
gsk_stroke_free (stroke);
}
int
main (int argc,
char *argv[])
@@ -1179,6 +1281,7 @@ main (int argc,
g_test_add_func ("/path/parse", test_parse);
g_test_add_func ("/path/in-fill-union", test_in_fill_union);
g_test_add_func ("/path/in-fill-rotated", test_in_fill_rotated);
g_test_add_func ("/path/stroke/distance", test_path_stroke_distance);
return g_test_run ();
}