mirror of
https://gitlab.gnome.org/GNOME/gtk.git
synced 2024-11-14 20:51:07 +00:00
gsk: Add tests for GskCurve
This commit is contained in:
parent
1b5dfcba7e
commit
a4cbabb80f
159
testsuite/gsk/curve-special-cases.c
Normal file
159
testsuite/gsk/curve-special-cases.c
Normal file
@ -0,0 +1,159 @@
|
||||
/*
|
||||
* 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 <gtk/gtk.h>
|
||||
|
||||
#include "gsk/gskcurveprivate.h"
|
||||
|
||||
static void
|
||||
test_curve_tangents (void)
|
||||
{
|
||||
GskCurve c;
|
||||
graphene_point_t p[4];
|
||||
graphene_vec2_t t;
|
||||
|
||||
graphene_point_init (&p[0], 0, 0);
|
||||
graphene_point_init (&p[1], 100, 0);
|
||||
gsk_curve_init (&c, gsk_pathop_encode (GSK_PATH_LINE, p));
|
||||
|
||||
gsk_curve_get_start_tangent (&c, &t);
|
||||
g_assert_true (graphene_vec2_near (&t, graphene_vec2_x_axis (), 0.0001));
|
||||
gsk_curve_get_end_tangent (&c, &t);
|
||||
g_assert_true (graphene_vec2_near (&t, graphene_vec2_x_axis (), 0.0001));
|
||||
|
||||
graphene_point_init (&p[0], 0, 0);
|
||||
graphene_point_init (&p[1], 0, 100);
|
||||
gsk_curve_init (&c, gsk_pathop_encode (GSK_PATH_LINE, p));
|
||||
|
||||
gsk_curve_get_start_tangent (&c, &t);
|
||||
g_assert_true (graphene_vec2_near (&t, graphene_vec2_y_axis (), 0.0001));
|
||||
gsk_curve_get_end_tangent (&c, &t);
|
||||
g_assert_true (graphene_vec2_near (&t, graphene_vec2_y_axis (), 0.0001));
|
||||
|
||||
graphene_point_init (&p[0], 0, 0);
|
||||
graphene_point_init (&p[1], 50, 0);
|
||||
graphene_point_init (&p[2], 100, 50);
|
||||
graphene_point_init (&p[3], 100, 100);
|
||||
gsk_curve_init (&c, gsk_pathop_encode (GSK_PATH_CUBIC, p));
|
||||
|
||||
gsk_curve_get_start_tangent (&c, &t);
|
||||
g_assert_true (graphene_vec2_near (&t, graphene_vec2_x_axis (), 0.0001));
|
||||
gsk_curve_get_end_tangent (&c, &t);
|
||||
g_assert_true (graphene_vec2_near (&t, graphene_vec2_y_axis (), 0.0001));
|
||||
}
|
||||
|
||||
static void
|
||||
test_curve_degenerate_tangents (void)
|
||||
{
|
||||
GskCurve c;
|
||||
graphene_point_t p[4];
|
||||
graphene_vec2_t t;
|
||||
|
||||
graphene_point_init (&p[0], 0, 0);
|
||||
graphene_point_init (&p[1], 0, 0);
|
||||
graphene_point_init (&p[2], 100, 0);
|
||||
graphene_point_init (&p[3], 100, 0);
|
||||
gsk_curve_init (&c, gsk_pathop_encode (GSK_PATH_CUBIC, p));
|
||||
|
||||
gsk_curve_get_start_tangent (&c, &t);
|
||||
g_assert_true (graphene_vec2_near (&t, graphene_vec2_x_axis (), 0.0001));
|
||||
gsk_curve_get_end_tangent (&c, &t);
|
||||
g_assert_true (graphene_vec2_near (&t, graphene_vec2_x_axis (), 0.0001));
|
||||
|
||||
graphene_point_init (&p[0], 0, 0);
|
||||
graphene_point_init (&p[1], 50, 0);
|
||||
graphene_point_init (&p[2], 50, 0);
|
||||
graphene_point_init (&p[3], 100, 0);
|
||||
gsk_curve_init (&c, gsk_pathop_encode (GSK_PATH_CUBIC, p));
|
||||
|
||||
gsk_curve_get_start_tangent (&c, &t);
|
||||
g_assert_true (graphene_vec2_near (&t, graphene_vec2_x_axis (), 0.0001));
|
||||
gsk_curve_get_end_tangent (&c, &t);
|
||||
g_assert_true (graphene_vec2_near (&t, graphene_vec2_x_axis (), 0.0001));
|
||||
}
|
||||
|
||||
static gboolean
|
||||
pathop_cb (GskPathOperation op,
|
||||
const graphene_point_t *pts,
|
||||
gsize n_pts,
|
||||
gpointer user_data)
|
||||
{
|
||||
GskCurve *curve = user_data;
|
||||
|
||||
g_assert (op != GSK_PATH_CLOSE);
|
||||
|
||||
if (op == GSK_PATH_MOVE)
|
||||
return TRUE;
|
||||
|
||||
gsk_curve_init_foreach (curve, op, pts, n_pts);
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
static void
|
||||
parse_curve (GskCurve *c,
|
||||
const char *str)
|
||||
{
|
||||
GskPath *path = gsk_path_parse (str);
|
||||
|
||||
gsk_path_foreach (path, -1, pathop_cb, c);
|
||||
|
||||
gsk_path_unref (path);
|
||||
}
|
||||
|
||||
static void
|
||||
test_curve_crossing (void)
|
||||
{
|
||||
struct {
|
||||
const char *c;
|
||||
const graphene_point_t p;
|
||||
int crossing;
|
||||
} tests[] = {
|
||||
{ "M 0 0 L 200 200", { 200, 100 }, 0 },
|
||||
{ "M 0 0 L 200 200", { 0, 100 }, 1 },
|
||||
{ "M 0 200 L 200 0", { 0, 100 }, -1 },
|
||||
{ "M 0 0 C 100 100 200 200 300 300", { 200, 100 }, 0 },
|
||||
{ "M 0 0 C 100 100 200 200 300 300", { 0, 100 }, 1 },
|
||||
{ "M 0 300 C 100 200 200 100 300 0", { 0, 100 }, -1 },
|
||||
{ "M 0 0 C 100 600 200 -300 300 300", { 0, 150 }, 1 },
|
||||
{ "M 0 0 C 100 600 200 -300 300 300", { 100, 150 }, 0 },
|
||||
{ "M 0 0 C 100 600 200 -300 300 300", { 200, 150 }, 1 },
|
||||
};
|
||||
|
||||
for (unsigned int i = 0; i < G_N_ELEMENTS (tests); i++)
|
||||
{
|
||||
GskCurve c;
|
||||
|
||||
parse_curve (&c, tests[i].c);
|
||||
|
||||
g_assert_true (gsk_curve_get_crossing (&c, &tests[i].p) == tests[i].crossing);
|
||||
}
|
||||
}
|
||||
|
||||
int
|
||||
main (int argc,
|
||||
char *argv[])
|
||||
{
|
||||
gtk_test_init (&argc, &argv, NULL);
|
||||
|
||||
g_test_add_func ("/curve/special/tangents", test_curve_tangents);
|
||||
g_test_add_func ("/curve/special/degenerate-tangents", test_curve_degenerate_tangents);
|
||||
g_test_add_func ("/curve/special/crossing", test_curve_crossing);
|
||||
|
||||
return g_test_run ();
|
||||
}
|
337
testsuite/gsk/curve.c
Normal file
337
testsuite/gsk/curve.c
Normal file
@ -0,0 +1,337 @@
|
||||
#include <gtk/gtk.h>
|
||||
#include "gsk/gskcurveprivate.h"
|
||||
|
||||
static void
|
||||
init_random_point (graphene_point_t *p)
|
||||
{
|
||||
p->x = g_test_rand_double_range (0, 1000);
|
||||
p->y = g_test_rand_double_range (0, 1000);
|
||||
}
|
||||
|
||||
static void
|
||||
init_random_curve_with_op (GskCurve *curve,
|
||||
GskPathOperation min_op,
|
||||
GskPathOperation max_op)
|
||||
{
|
||||
switch (g_test_rand_int_range (min_op, max_op + 1))
|
||||
{
|
||||
case GSK_PATH_LINE:
|
||||
{
|
||||
graphene_point_t p[2];
|
||||
|
||||
init_random_point (&p[0]);
|
||||
init_random_point (&p[1]);
|
||||
gsk_curve_init (curve, gsk_pathop_encode (GSK_PATH_LINE, p));
|
||||
}
|
||||
break;
|
||||
|
||||
case GSK_PATH_QUAD:
|
||||
{
|
||||
graphene_point_t p[3];
|
||||
|
||||
init_random_point (&p[0]);
|
||||
init_random_point (&p[1]);
|
||||
init_random_point (&p[2]);
|
||||
gsk_curve_init (curve, gsk_pathop_encode (GSK_PATH_QUAD, p));
|
||||
}
|
||||
break;
|
||||
|
||||
case GSK_PATH_CUBIC:
|
||||
{
|
||||
graphene_point_t p[4];
|
||||
|
||||
init_random_point (&p[0]);
|
||||
init_random_point (&p[1]);
|
||||
init_random_point (&p[2]);
|
||||
init_random_point (&p[3]);
|
||||
gsk_curve_init (curve, gsk_pathop_encode (GSK_PATH_CUBIC, p));
|
||||
}
|
||||
break;
|
||||
|
||||
default:
|
||||
g_assert_not_reached ();
|
||||
}
|
||||
}
|
||||
|
||||
static void
|
||||
init_random_curve (GskCurve *curve)
|
||||
{
|
||||
init_random_curve_with_op (curve, GSK_PATH_LINE, GSK_PATH_CUBIC);
|
||||
}
|
||||
|
||||
static void
|
||||
test_curve_tangents (void)
|
||||
{
|
||||
for (int i = 0; i < 100; i++)
|
||||
{
|
||||
GskCurve c;
|
||||
graphene_vec2_t vec, exact;
|
||||
|
||||
init_random_curve (&c);
|
||||
|
||||
gsk_curve_get_tangent (&c, 0, &vec);
|
||||
g_assert_cmpfloat_with_epsilon (graphene_vec2_length (&vec), 1.0f, 0.00001);
|
||||
gsk_curve_get_start_tangent (&c, &exact);
|
||||
g_assert_cmpfloat_with_epsilon (graphene_vec2_length (&exact), 1.0f, 0.00001);
|
||||
g_assert_true (graphene_vec2_near (&vec, &exact, 0.05));
|
||||
|
||||
gsk_curve_get_tangent (&c, 1, &vec);
|
||||
g_assert_cmpfloat_with_epsilon (graphene_vec2_length (&vec), 1.0f, 0.00001);
|
||||
gsk_curve_get_end_tangent (&c, &exact);
|
||||
g_assert_cmpfloat_with_epsilon (graphene_vec2_length (&exact), 1.0f, 0.00001);
|
||||
g_assert_true (graphene_vec2_near (&vec, &exact, 0.05));
|
||||
}
|
||||
}
|
||||
|
||||
static void
|
||||
test_curve_points (void)
|
||||
{
|
||||
for (int i = 0; i < 100; i++)
|
||||
{
|
||||
GskCurve c;
|
||||
graphene_point_t p;
|
||||
|
||||
init_random_curve (&c);
|
||||
|
||||
/* We can assert equality here because evaluating the polynomials with 0
|
||||
* has no effect on accuracy.
|
||||
*/
|
||||
gsk_curve_get_point (&c, 0, &p);
|
||||
g_assert_true (graphene_point_equal (gsk_curve_get_start_point (&c), &p));
|
||||
/* But here we evaluate the polynomials with 1 which gives the highest possible
|
||||
* accuracy error. So we'll just be generous here.
|
||||
*/
|
||||
gsk_curve_get_point (&c, 1, &p);
|
||||
g_assert_true (graphene_point_near (gsk_curve_get_end_point (&c), &p, 0.05));
|
||||
}
|
||||
}
|
||||
|
||||
/* at this point the subdivision stops and the decomposer
|
||||
* violates tolerance rules
|
||||
*/
|
||||
#define MIN_PROGRESS (1/1024.f)
|
||||
|
||||
typedef struct
|
||||
{
|
||||
graphene_point_t p;
|
||||
float t;
|
||||
} PointOnLine;
|
||||
|
||||
static gboolean
|
||||
add_line_to_array (const graphene_point_t *from,
|
||||
const graphene_point_t *to,
|
||||
float from_progress,
|
||||
float to_progress,
|
||||
GskCurveLineReason reason,
|
||||
gpointer user_data)
|
||||
{
|
||||
GArray *array = user_data;
|
||||
PointOnLine *last = &g_array_index (array, PointOnLine, array->len - 1);
|
||||
|
||||
g_assert_true (array->len > 0);
|
||||
g_assert_cmpfloat (from_progress, >=, 0.0f);
|
||||
g_assert_cmpfloat (from_progress, <, to_progress);
|
||||
g_assert_cmpfloat (to_progress, <=, 1.0f);
|
||||
|
||||
g_assert_true (graphene_point_equal (&last->p, from));
|
||||
g_assert_cmpfloat (last->t, ==, from_progress);
|
||||
|
||||
g_array_append_vals (array, (PointOnLine[1]) { { *to, to_progress } }, 1);
|
||||
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
static void
|
||||
test_curve_decompose (void)
|
||||
{
|
||||
static const float tolerance = 0.5;
|
||||
|
||||
for (int i = 0; i < 100; i++)
|
||||
{
|
||||
GArray *array;
|
||||
GskCurve c;
|
||||
|
||||
init_random_curve (&c);
|
||||
|
||||
array = g_array_new (FALSE, FALSE, sizeof (PointOnLine));
|
||||
g_array_append_vals (array, (PointOnLine[1]) { { *gsk_curve_get_start_point (&c), 0.f } }, 1);
|
||||
|
||||
g_assert_true (gsk_curve_decompose (&c, tolerance, add_line_to_array, array));
|
||||
|
||||
g_assert_cmpint (array->len, >=, 2); /* We at least got a line to the end */
|
||||
g_assert_cmpfloat (g_array_index (array, PointOnLine, array->len - 1).t, ==, 1.0);
|
||||
|
||||
for (int j = 0; j < array->len; j++)
|
||||
{
|
||||
PointOnLine *pol = &g_array_index (array, PointOnLine, j);
|
||||
graphene_point_t p;
|
||||
|
||||
/* Check that the points we got are actually on the line */
|
||||
gsk_curve_get_point (&c, pol->t, &p);
|
||||
g_assert_true (graphene_point_near (&pol->p, &p, 0.05));
|
||||
|
||||
/* Check that the mid point is not further than the tolerance */
|
||||
if (j > 0)
|
||||
{
|
||||
PointOnLine *last = &g_array_index (array, PointOnLine, j - 1);
|
||||
graphene_point_t mid;
|
||||
|
||||
if (pol->t - last->t > MIN_PROGRESS)
|
||||
{
|
||||
graphene_point_interpolate (&last->p, &pol->p, 0.5, &mid);
|
||||
gsk_curve_get_point (&c, (pol->t + last->t) / 2, &p);
|
||||
/* The decomposer does this cheaper Manhattan distance test,
|
||||
* so graphene_point_near() does not work */
|
||||
g_assert_cmpfloat (fabs (mid.x - p.x), <=, tolerance);
|
||||
g_assert_cmpfloat (fabs (mid.y - p.y), <=, tolerance);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
g_array_unref (array);
|
||||
}
|
||||
}
|
||||
|
||||
static gboolean
|
||||
add_curve_to_array (GskPathOperation op,
|
||||
const graphene_point_t *pts,
|
||||
gsize n_pts,
|
||||
gpointer user_data)
|
||||
{
|
||||
GArray *array = user_data;
|
||||
GskCurve c;
|
||||
|
||||
gsk_curve_init_foreach (&c, op, pts, n_pts);
|
||||
g_array_append_val (array, c);
|
||||
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
static void
|
||||
test_curve_decompose_into (GskPathForeachFlags flags)
|
||||
{
|
||||
for (int i = 0; i < 100; i++)
|
||||
{
|
||||
GskCurve c;
|
||||
GskPathBuilder *builder;
|
||||
const graphene_point_t *s;
|
||||
GskPath *path;
|
||||
GArray *array;
|
||||
|
||||
init_random_curve (&c);
|
||||
|
||||
builder = gsk_path_builder_new ();
|
||||
|
||||
s = gsk_curve_get_start_point (&c);
|
||||
gsk_path_builder_move_to (builder, s->x, s->y);
|
||||
gsk_curve_builder_to (&c, builder);
|
||||
path = gsk_path_builder_free_to_path (builder);
|
||||
|
||||
array = g_array_new (FALSE, FALSE, sizeof (GskCurve));
|
||||
|
||||
g_assert_true (gsk_curve_decompose_curve (&c, flags, 0.1, add_curve_to_array, array));
|
||||
|
||||
g_assert_cmpint (array->len, >=, 1);
|
||||
|
||||
for (int j = 0; j < array->len; j++)
|
||||
{
|
||||
GskCurve *c2 = &g_array_index (array, GskCurve, j);
|
||||
|
||||
switch (c2->op)
|
||||
{
|
||||
case GSK_PATH_MOVE:
|
||||
case GSK_PATH_CLOSE:
|
||||
case GSK_PATH_LINE:
|
||||
break;
|
||||
case GSK_PATH_QUAD:
|
||||
g_assert_true (flags & GSK_PATH_FOREACH_ALLOW_QUAD);
|
||||
break;
|
||||
case GSK_PATH_CUBIC:
|
||||
g_assert_true (flags & GSK_PATH_FOREACH_ALLOW_CUBIC);
|
||||
break;
|
||||
default:
|
||||
g_assert_not_reached ();
|
||||
}
|
||||
}
|
||||
|
||||
g_array_unref (array);
|
||||
gsk_path_unref (path);
|
||||
}
|
||||
}
|
||||
|
||||
static void
|
||||
test_curve_decompose_into_line (void)
|
||||
{
|
||||
test_curve_decompose_into (0);
|
||||
}
|
||||
|
||||
static void
|
||||
test_curve_decompose_into_quad (void)
|
||||
{
|
||||
test_curve_decompose_into (GSK_PATH_FOREACH_ALLOW_QUAD);
|
||||
}
|
||||
|
||||
static void
|
||||
test_curve_decompose_into_cubic (void)
|
||||
{
|
||||
test_curve_decompose_into (GSK_PATH_FOREACH_ALLOW_CUBIC);
|
||||
}
|
||||
|
||||
/* Some sanity checks for splitting curves. */
|
||||
static void
|
||||
test_curve_split (void)
|
||||
{
|
||||
for (int i = 0; i < 100; i++)
|
||||
{
|
||||
GskCurve c;
|
||||
GskCurve c1, c2;
|
||||
graphene_point_t p;
|
||||
graphene_vec2_t t, t1, t2;
|
||||
|
||||
init_random_curve (&c);
|
||||
|
||||
gsk_curve_split (&c, 0.5, &c1, &c2);
|
||||
|
||||
g_assert_true (c1.op == c.op);
|
||||
g_assert_true (c2.op == c.op);
|
||||
|
||||
g_assert_true (graphene_point_near (gsk_curve_get_start_point (&c),
|
||||
gsk_curve_get_start_point (&c1), 0.005));
|
||||
g_assert_true (graphene_point_near (gsk_curve_get_end_point (&c1),
|
||||
gsk_curve_get_start_point (&c2), 0.005));
|
||||
g_assert_true (graphene_point_near (gsk_curve_get_end_point (&c),
|
||||
gsk_curve_get_end_point (&c2), 0.005));
|
||||
gsk_curve_get_point (&c, 0.5, &p);
|
||||
gsk_curve_get_tangent (&c, 0.5, &t);
|
||||
g_assert_true (graphene_point_near (gsk_curve_get_end_point (&c1), &p, 0.005));
|
||||
g_assert_true (graphene_point_near (gsk_curve_get_start_point (&c2), &p, 0.005));
|
||||
|
||||
gsk_curve_get_start_tangent (&c, &t1);
|
||||
gsk_curve_get_start_tangent (&c1, &t2);
|
||||
g_assert_true (graphene_vec2_near (&t1, &t2, 0.005));
|
||||
gsk_curve_get_end_tangent (&c1, &t1);
|
||||
gsk_curve_get_start_tangent (&c2, &t2);
|
||||
g_assert_true (graphene_vec2_near (&t1, &t2, 0.005));
|
||||
g_assert_true (graphene_vec2_near (&t, &t1, 0.005));
|
||||
g_assert_true (graphene_vec2_near (&t, &t2, 0.005));
|
||||
gsk_curve_get_end_tangent (&c, &t1);
|
||||
gsk_curve_get_end_tangent (&c2, &t2);
|
||||
g_assert_true (graphene_vec2_near (&t1, &t2, 0.005));
|
||||
}
|
||||
}
|
||||
|
||||
int
|
||||
main (int argc, char *argv[])
|
||||
{
|
||||
(g_test_init) (&argc, &argv, NULL);
|
||||
|
||||
g_test_add_func ("/curve/points", test_curve_points);
|
||||
g_test_add_func ("/curve/tangents", test_curve_tangents);
|
||||
g_test_add_func ("/curve/decompose", test_curve_decompose);
|
||||
g_test_add_func ("/curve/decompose/into/line", test_curve_decompose_into_line);
|
||||
g_test_add_func ("/curve/decompose/into/quad", test_curve_decompose_into_quad);
|
||||
g_test_add_func ("/curve/decompose/into/cubic", test_curve_decompose_into_cubic);
|
||||
g_test_add_func ("/curve/split", test_curve_split);
|
||||
|
||||
return g_test_run ();
|
||||
}
|
@ -389,6 +389,8 @@ foreach t : tests
|
||||
endforeach
|
||||
|
||||
internal_tests = [
|
||||
[ 'curve' ],
|
||||
[ 'curve-special-cases' ],
|
||||
[ 'diff' ],
|
||||
[ 'half-float' ],
|
||||
['rounded-rect'],
|
||||
|
Loading…
Reference in New Issue
Block a user