#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:
{
GskAlignedPoint p[2 ];
init_random_point (&p[0 ].pt);
init_random_point (&p[1 ].pt);
gsk_curve_init (curve, gsk_pathop_encode (GSK_PATH_LINE, p));
}
break ;
case GSK_PATH_QUAD:
{
GskAlignedPoint p[3 ];
init_random_point (&p[0 ].pt);
init_random_point (&p[1 ].pt);
init_random_point (&p[2 ].pt);
gsk_curve_init (curve, gsk_pathop_encode (GSK_PATH_QUAD, p));
}
break ;
case GSK_PATH_CUBIC:
{
GskAlignedPoint p[4 ];
init_random_point (&p[0 ].pt);
init_random_point (&p[1 ].pt);
init_random_point (&p[2 ].pt);
init_random_point (&p[3 ].pt);
gsk_curve_init (curve, gsk_pathop_encode (GSK_PATH_CUBIC, p));
}
break ;
case GSK_PATH_CONIC:
{
GskAlignedPoint p[4 ];
init_random_point (&p[0 ].pt);
init_random_point (&p[1 ].pt);
p[2 ].pt.x = g_test_rand_double_range (0 .2 , 20 );
p[2 ].pt.y = 0 .f;
init_random_point (&p[3 ].pt);
gsk_curve_init (curve, gsk_pathop_encode (GSK_PATH_CONIC, 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_CONIC);
}
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 .0 f, 0 .00001 );
gsk_curve_get_start_tangent (&c, &exact);
g_assert_cmpfloat_with_epsilon (graphene_vec2_length (&exact), 1 .0 f, 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 .0 f, 0 .00001 );
gsk_curve_get_end_tangent (&c, &exact);
g_assert_cmpfloat_with_epsilon (graphene_vec2_length (&exact), 1 .0 f, 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 could assert equality here because evaluating the polynomials with 0
* has no effect on accuracy , but for arcs , we use trigonometric functions ,
* so allow a small error .
*/
gsk_curve_get_point (&c, 0 , &p);
g_assert_true (graphene_point_near (gsk_curve_get_start_point (&c), &p, 0 .001 ));
/* 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 .0 f);
g_assert_cmpfloat (from_progress, <, to_progress);
g_assert_cmpfloat (to_progress, <=, 1 .0 f);
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 + 0 .0002 );
g_assert_cmpfloat (fabs (mid.y - p.y), <=, tolerance + 0 .0002 );
}
}
}
g_array_unref (array);
}
}
static gboolean
add_curve_to_array (GskPathOperation op,
const graphene_point_t *pts,
gsize n_pts,
float weight,
gpointer user_data)
{
GArray *array = user_data;
GskCurve c;
gsk_curve_init_foreach (&c, op, pts, n_pts, weight);
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 ;
case GSK_PATH_CONIC:
g_assert_true (flags & GSK_PATH_FOREACH_ALLOW_CONIC);
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 < 20 ; i++)
{
GskCurve c;
init_random_curve (&c);
for (int j = 0 ; j < 20 ; j++)
{
GskCurve c1, c2;
graphene_point_t p;
graphene_vec2_t t, t1, t2;
float split;
split = g_test_rand_double_range (0 .1 , 0 .9 );
gsk_curve_split (&c, split, &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, split, &p);
gsk_curve_get_tangent (&c, split, &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 ));
#if 0
/* hard to guarantee this for totally random random curves */
g_assert_cmpfloat_with_epsilon (gsk_curve_get_length (&c),
gsk_curve_get_length (&c1) + gsk_curve_get_length (&c2),
1 );
#endif
}
}
}
static void
test_curve_derivative (void )
{
GskCurve c;
float t;
graphene_vec2_t t1, t2;
graphene_point_t p;
for (int i = 0 ; i < 100 ; i++)
{
init_random_curve (&c);
for (int j = 0 ; j < 100 ; j++)
{
t = g_test_rand_double_range (0 , 1 );
gsk_curve_get_derivative_at (&c, t, &p);
gsk_curve_get_tangent (&c, t, &t1);
graphene_vec2_init (&t2, p.x, p.y);
graphene_vec2_normalize (&t2, &t2);
g_assert_true (graphene_vec2_near (&t1, &t2, 0 .1 ));
}
}
}
static void
test_curve_length (void )
{
GskCurve c;
float l, l0;
for (int i = 0 ; i < 1000 ; i++)
{
init_random_curve (&c);
l = gsk_curve_get_length (&c);
l0 = graphene_point_distance (gsk_curve_get_start_point (&c),
gsk_curve_get_end_point (&c),
NULL, NULL);
g_assert_true (l >= l0 - 0 .001 );
if (c.op == GSK_PATH_LINE)
g_assert_cmpfloat_with_epsilon (l, l0, 0 .001 );
}
}
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-line" , test_curve_decompose_into_line);
g_test_add_func ("/curve/decompose-quad" , test_curve_decompose_into_quad);
g_test_add_func ("/curve/decompose-cubic" , test_curve_decompose_into_cubic);
g_test_add_func ("/curve/split" , test_curve_split);
g_test_add_func ("/curve/derivative" , test_curve_derivative);
g_test_add_func ("/curve/length" , test_curve_length);
return g_test_run ();
}
Messung V0.5 in Prozent C=98 H=91 G=94
¤ Dauer der Verarbeitung: 0.14 Sekunden
(vorverarbeitet am 2026-07-03)
¤
*© Formatika GbR, Deutschland