/* * Copyright © 2019 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 . * * Authors: Benjamin Otte */ #include "config.h" #include #define EPSILON (1.f / 1024 / 32) /* 2^-15 */ /* macros stolen from graphene testsuite, so they get to keep their names */ #if defined(__GNUC__) && (__GNUC__ > 4 || (__GNUC__ == 4 && __GNUC_MINOR__ >= 9)) /* Use typeof on GCC */ # define graphene_fuzzy_equals(n1,n2,epsilon) \ G_GNUC_EXTENSION({ \ __auto_type _n1 = (n1); \ __auto_type _n2 = (n2); \ __auto_type _epsilon = (epsilon); \ ((_n1 > _n2 ? (_n1 - _n2 ) : (_n2 - _n1)) <= _epsilon); \ }) #else /* fallback for Visual Studio, typeof not supported */ # define graphene_fuzzy_equals(n1,n2,epsilon) \ (((n1) > (n2) ? ((n1) - (n2)) : ((n2) - (n1))) <= (epsilon)) #endif /* __GNUC__ */ #define graphene_assert_fuzzy_matrix_cell_equal(row,col,n1,n2,epsilon) \ G_STMT_START { \ if (graphene_fuzzy_equals (n1, n2, epsilon)) ; else { \ char *s = g_strdup_printf ("[%d][%d]: " #n1 " == " #n2 " (+/- " #epsilon "): (%.7g == %.7g)", \ row, col, n1, n2); \ g_assertion_message (G_LOG_DOMAIN, __FILE__, __LINE__, G_STRFUNC, s); \ g_free (s); \ } \ } G_STMT_END #define graphene_assert_fuzzy_matrix_equal(m1,m2,epsilon) \ G_STMT_START { \ unsigned int __i, __j; \ float __m1[16], __m2[16]; \ graphene_matrix_to_float ((m1), __m1); \ graphene_matrix_to_float ((m2), __m2); \ for (__i = 0; __i < 4; __i++) { \ for (__j = 0; __j < 4; __j++) { \ unsigned int __idx = __i * 4 + __j; \ graphene_assert_fuzzy_matrix_cell_equal (__i, __j, __m1[__idx], __m2[__idx], epsilon); \ } \ } \ } G_STMT_END #define graphene_assert_fuzzy_transform_equal(t1,t2,epsilon) \ G_STMT_START { \ graphene_matrix_t __mat1, __mat2; \ gsk_transform_to_matrix ((t1), &__mat1); \ gsk_transform_to_matrix ((t2), &__mat2); \ graphene_assert_fuzzy_matrix_equal (&__mat1, &__mat2, (epsilon)); \ } G_STMT_END static struct { GskTransformCategory category; } test_transforms[] = { { GSK_TRANSFORM_CATEGORY_IDENTITY }, { GSK_TRANSFORM_CATEGORY_IDENTITY }, { GSK_TRANSFORM_CATEGORY_2D_TRANSLATE }, { GSK_TRANSFORM_CATEGORY_3D }, { GSK_TRANSFORM_CATEGORY_2D }, { GSK_TRANSFORM_CATEGORY_3D }, { GSK_TRANSFORM_CATEGORY_2D_AFFINE }, { GSK_TRANSFORM_CATEGORY_3D }, { GSK_TRANSFORM_CATEGORY_ANY }, }; static GskTransform * apply_test_transform (GskTransform *transform, guint i) { switch (i) { case 0: return transform ? transform : gsk_transform_new (); case 1: return gsk_transform_transform (transform, NULL); case 2: return gsk_transform_translate (transform, &GRAPHENE_POINT_INIT (3, 5)); case 3: return gsk_transform_translate_3d (transform, &GRAPHENE_POINT3D_INIT (3, 5, 7)); case 4: return gsk_transform_rotate (transform, 90); case 5: return gsk_transform_rotate_3d (transform, 90, graphene_vec3_y_axis ()); case 6: return gsk_transform_scale (transform, 2, 3); case 7: return gsk_transform_scale_3d (transform, 2, 3, 5); case 8: return gsk_transform_perspective (transform, 5); default: g_assert_not_reached (); return NULL; } } static GskTransformCategory categorize_matrix (const graphene_matrix_t *matrix) { if (!graphene_fuzzy_equals (graphene_matrix_get_value (matrix, 0, 3), 0, EPSILON) || !graphene_fuzzy_equals (graphene_matrix_get_value (matrix, 1, 3), 0, EPSILON) || !graphene_fuzzy_equals (graphene_matrix_get_value (matrix, 2, 3), 0, EPSILON) || !graphene_fuzzy_equals (graphene_matrix_get_value (matrix, 3, 3), 1, EPSILON)) return GSK_TRANSFORM_CATEGORY_ANY; if (!graphene_fuzzy_equals (graphene_matrix_get_value (matrix, 0, 2), 0, EPSILON) || !graphene_fuzzy_equals (graphene_matrix_get_value (matrix, 1, 2), 0, EPSILON) || !graphene_fuzzy_equals (graphene_matrix_get_value (matrix, 2, 2), 1, EPSILON) || !graphene_fuzzy_equals (graphene_matrix_get_value (matrix, 3, 2), 0, EPSILON) || !graphene_fuzzy_equals (graphene_matrix_get_value (matrix, 2, 0), 0, EPSILON) || !graphene_fuzzy_equals (graphene_matrix_get_value (matrix, 2, 1), 0, EPSILON)) return GSK_TRANSFORM_CATEGORY_3D; if (!graphene_fuzzy_equals (graphene_matrix_get_value (matrix, 0, 1), 0, EPSILON) || !graphene_fuzzy_equals (graphene_matrix_get_value (matrix, 1, 0), 0, EPSILON)) return GSK_TRANSFORM_CATEGORY_2D; if (!graphene_fuzzy_equals (graphene_matrix_get_value (matrix, 0, 0), 1, EPSILON) || !graphene_fuzzy_equals (graphene_matrix_get_value (matrix, 1, 1), 1, EPSILON)) return GSK_TRANSFORM_CATEGORY_2D_AFFINE; if (!graphene_fuzzy_equals (graphene_matrix_get_value (matrix, 3, 0), 0, EPSILON) || !graphene_fuzzy_equals (graphene_matrix_get_value (matrix, 3, 1), 0, EPSILON)) return GSK_TRANSFORM_CATEGORY_2D_TRANSLATE; return GSK_TRANSFORM_CATEGORY_IDENTITY; } static void check_conversions (GskTransform *transform, GskTransformCategory expected_category) { graphene_matrix_t matrix, test; float f[16] = { 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1 }; g_assert_cmpint (gsk_transform_get_category (transform), ==, expected_category); gsk_transform_to_matrix (transform, &matrix); /* we don't insist on getting simplifications right. * The matrix "scale(2) scale(0.5)" would be categorized as identity, * but the transform might not do that. */ g_assert_cmpint (gsk_transform_get_category (transform), <=, categorize_matrix (&matrix)); switch (expected_category) { case GSK_TRANSFORM_CATEGORY_UNKNOWN: case GSK_TRANSFORM_CATEGORY_ANY: case GSK_TRANSFORM_CATEGORY_3D: break; case GSK_TRANSFORM_CATEGORY_IDENTITY: case GSK_TRANSFORM_CATEGORY_2D_TRANSLATE: gsk_transform_to_translate (transform, &f[4 * 3 + 0], &f[4 * 3 + 1]); graphene_matrix_init_from_float (&test, f); graphene_assert_fuzzy_matrix_equal (&matrix, &test, EPSILON); G_GNUC_FALLTHROUGH; case GSK_TRANSFORM_CATEGORY_2D_AFFINE: gsk_transform_to_affine (transform, &f[4 * 0 + 0], &f[4 * 1 + 1], &f[4 * 3 + 0], &f[4 * 3 + 1]); graphene_matrix_init_from_float (&test, f); graphene_assert_fuzzy_matrix_equal (&matrix, &test, EPSILON); G_GNUC_FALLTHROUGH; case GSK_TRANSFORM_CATEGORY_2D: gsk_transform_to_2d (transform, &f[4 * 0 + 0], &f[4 * 0 + 1], &f[4 * 1 + 0], &f[4 * 1 + 1], &f[4 * 3 + 0], &f[4 * 3 + 1]); graphene_matrix_init_from_float (&test, f); graphene_assert_fuzzy_matrix_equal (&matrix, &test, EPSILON); break; default: g_assert_not_reached (); } } static void test_conversions_simple (void) { GskTransform *transform; guint i; for (i = 0; i < G_N_ELEMENTS (test_transforms); i++) { transform = apply_test_transform (NULL, i); check_conversions (transform, test_transforms[i].category); gsk_transform_unref (transform); } } static void test_conversions_transformed (void) { GskTransform *transform; guint i, j, k; for (i = 0; i < G_N_ELEMENTS (test_transforms); i++) { for (j = 0; j < G_N_ELEMENTS (test_transforms); j++) { for (k = 0; k < G_N_ELEMENTS (test_transforms); k++) { transform = apply_test_transform (NULL, i); transform = apply_test_transform (transform, j); transform = apply_test_transform (transform, k); check_conversions (transform, MIN (test_transforms[i].category, MIN (test_transforms[j].category, test_transforms[k].category))); gsk_transform_unref (transform); } } } } static void test_invert (void) { GskTransform *transform, *inverse, *identity; guint i, j, k; for (i = 0; i < G_N_ELEMENTS (test_transforms); i++) { for (j = 0; j < G_N_ELEMENTS (test_transforms); j++) { for (k = 0; k < G_N_ELEMENTS (test_transforms); k++) { transform = apply_test_transform (NULL, i); transform = apply_test_transform (transform, j); transform = apply_test_transform (transform, k); inverse = gsk_transform_invert (gsk_transform_ref (transform)); g_assert_true (inverse != NULL || transform == NULL); identity = gsk_transform_transform (gsk_transform_ref (transform), inverse); graphene_assert_fuzzy_transform_equal (identity, NULL, EPSILON); gsk_transform_unref (identity); inverse = gsk_transform_invert (inverse); graphene_assert_fuzzy_transform_equal (transform, inverse, EPSILON); gsk_transform_unref (transform); gsk_transform_unref (inverse); } } } } /* some trivialities around identity transforms */ static void test_identity (void) { GskTransform *s, *t, *u, *v, *w, *x; char *string; float a, b, c, d, tx, ty; gboolean res; s = gsk_transform_new (); t = gsk_transform_new (); u = gsk_transform_transform (gsk_transform_ref (s), NULL); g_assert_cmpint (gsk_transform_get_category (s), ==, GSK_TRANSFORM_CATEGORY_IDENTITY); g_assert_cmpint (gsk_transform_get_category (t), ==, GSK_TRANSFORM_CATEGORY_IDENTITY); g_assert_cmpint (gsk_transform_get_category (u), ==, GSK_TRANSFORM_CATEGORY_IDENTITY); g_assert_true (gsk_transform_equal (s, t)); g_assert_true (gsk_transform_equal (t, u)); g_assert_true (gsk_transform_equal (s, u)); v = gsk_transform_transform (gsk_transform_ref (s), t); g_assert_cmpint (gsk_transform_get_category (v), ==, GSK_TRANSFORM_CATEGORY_IDENTITY); w = gsk_transform_invert (gsk_transform_ref (v)); g_assert_cmpint (gsk_transform_get_category (w), ==, GSK_TRANSFORM_CATEGORY_IDENTITY); string = gsk_transform_to_string (s); res = gsk_transform_parse (string, &x); g_assert_true (res); g_assert_cmpint (gsk_transform_get_category (x), ==, GSK_TRANSFORM_CATEGORY_IDENTITY); gsk_transform_to_2d (s, &a, &b, &c, &d, &tx, &ty); g_assert_cmpfloat (a, ==, 1.0f); g_assert_cmpfloat (b, ==, 0.0f); g_assert_cmpfloat (c, ==, 0.0f); g_assert_cmpfloat (d, ==, 1.0f); g_assert_cmpfloat (tx, ==, 0.0f); g_assert_cmpfloat (ty, ==, 0.f); gsk_transform_unref (s); gsk_transform_unref (t); gsk_transform_unref (u); gsk_transform_unref (v); gsk_transform_unref (w); gsk_transform_unref (x); g_free (string); } static void test_identity_equal (void) { GskTransform *id = gsk_transform_new (); GskTransform *t; g_assert_true (gsk_transform_equal (NULL, NULL)); g_assert_true (gsk_transform_equal (id, NULL)); g_assert_true (gsk_transform_equal (NULL, id)); g_assert_true (gsk_transform_equal (id, id)); t = gsk_transform_transform (NULL, NULL); g_assert_true (gsk_transform_equal (t, NULL)); gsk_transform_unref (t); t = gsk_transform_transform (gsk_transform_new (), NULL); g_assert_true (gsk_transform_equal (t, NULL)); gsk_transform_unref (t); t = gsk_transform_transform (NULL, id); g_assert_true (gsk_transform_equal (t, NULL)); gsk_transform_unref (t); t = gsk_transform_transform (gsk_transform_new (), id); g_assert_true (gsk_transform_equal (t, NULL)); gsk_transform_unref (t); t = gsk_transform_new (); t = gsk_transform_transform (t, t); g_assert_true (gsk_transform_equal (t, NULL)); gsk_transform_unref (t); gsk_transform_unref (id); } static void test_print_parse (void) { GskTransform *transform, *parsed; guint i, j, k; char *str1, *str2; gboolean ret; for (i = 0; i < G_N_ELEMENTS (test_transforms); i++) { for (j = 0; j < G_N_ELEMENTS (test_transforms); j++) { for (k = 0; k < G_N_ELEMENTS (test_transforms); k++) { transform = apply_test_transform (NULL, i); transform = apply_test_transform (transform, j); transform = apply_test_transform (transform, k); str1 = gsk_transform_to_string (transform); g_assert_nonnull (str1); g_assert_true (strlen (str1) > 0); str2 = gsk_transform_to_string (transform); g_assert_cmpstr (str1, ==, str2); g_free (str2); ret = gsk_transform_parse (str1, &parsed); g_assert_true (ret); graphene_assert_fuzzy_transform_equal (parsed, transform, EPSILON); str2 = gsk_transform_to_string (parsed); g_assert_cmpstr (str1, ==, str2); g_free (str2); g_free (str1); gsk_transform_unref (parsed); gsk_transform_unref (transform); } } } ret = gsk_transform_parse ("rotate(45);", &parsed); g_assert_false (ret); g_assert_null (parsed); } static void gsk_matrix_transform_rect (const graphene_matrix_t *m, const graphene_rect_t *r, graphene_quad_t *res) { graphene_point_t ret[4]; graphene_rect_t rr; graphene_rect_normalize_r (r, &rr); #define TRANSFORM_POINT(matrix, rect, corner, out_p) do {\ graphene_vec4_t __s; \ graphene_point_t __p; \ float w; \ graphene_rect_get_ ## corner (rect, &__p); \ graphene_vec4_init (&__s, __p.x, __p.y, 0.f, 1.f); \ graphene_matrix_transform_vec4 (matrix, &__s, &__s); \ w = graphene_vec4_get_w (&__s); \ out_p.x = graphene_vec4_get_x (&__s) / w; \ out_p.y = graphene_vec4_get_y (&__s) / w; } while (0) TRANSFORM_POINT (m, &rr, top_left, ret[0]); TRANSFORM_POINT (m, &rr, top_right, ret[1]); TRANSFORM_POINT (m, &rr, bottom_right, ret[2]); TRANSFORM_POINT (m, &rr, bottom_left, ret[3]); #undef TRANSFORM_POINT graphene_quad_init (res, &ret[0], &ret[1], &ret[2], &ret[3]); } /* This is an auxiliary function used in the GL renderer to * determine if transforming an axis-aligned rectangle produces * axis-aligned output, to decide whether to use linear * interpolation or not. */ static gboolean result_is_axis_aligned (GskTransform *transform, const graphene_rect_t *bounds) { graphene_matrix_t m; graphene_quad_t q; graphene_rect_t b; graphene_point_t b1, b2; const graphene_point_t *p; int i; gsk_transform_to_matrix (transform, &m); gsk_matrix_transform_rect (&m, bounds, &q); graphene_quad_bounds (&q, &b); graphene_rect_get_top_left (&b, &b1); graphene_rect_get_bottom_right (&b, &b2); for (i = 0; i < 4; i++) { p = graphene_quad_get_point (&q, i); if (fabs (p->x - b1.x) > FLT_EPSILON && fabs (p->x - b2.x) > FLT_EPSILON) return FALSE; if (fabs (p->y - b1.y) > FLT_EPSILON && fabs (p->y - b2.y) > FLT_EPSILON) return FALSE; } return TRUE; } static void test_axis_aligned (void) { graphene_rect_t r = GRAPHENE_RECT_INIT (0, 0, 10, 10); GskTransform *transform = NULL; transform = gsk_transform_translate (NULL, &GRAPHENE_POINT_INIT (10, 10)); g_assert_true (result_is_axis_aligned (transform, &r)); gsk_transform_unref (transform); transform = gsk_transform_translate_3d (NULL, &GRAPHENE_POINT3D_INIT(0, 10, 10)); g_assert_true (result_is_axis_aligned (transform, &r)); gsk_transform_unref (transform); transform = gsk_transform_rotate (NULL, 90); g_assert_true (result_is_axis_aligned (transform, &r)); gsk_transform_unref (transform); transform = gsk_transform_scale (NULL, 2, 3); g_assert_true (result_is_axis_aligned (transform, &r)); gsk_transform_unref (transform); /* rotating around the y axis does not affect axis alignedness, * as long as we don't involve perspective */ transform = gsk_transform_rotate_3d (NULL, 45, graphene_vec3_y_axis ()); g_assert_true (result_is_axis_aligned (transform, &r)); gsk_transform_unref (transform); /* rotating by 45 around the z axis, not axis aligned */ transform = gsk_transform_rotate (NULL, 45); g_assert_false (result_is_axis_aligned (transform, &r)); gsk_transform_unref (transform); /* perspective is harmless as long as we stay in the z=0 plane */ transform = gsk_transform_perspective (NULL, 100); g_assert_true (result_is_axis_aligned (transform, &r)); gsk_transform_unref (transform); /* a complex transform that makes things look '3d' */ transform = gsk_transform_translate_3d (NULL, &GRAPHENE_POINT3D_INIT (0, 0, 50)); transform = gsk_transform_perspective (transform, 170); transform = gsk_transform_translate_3d (transform, &GRAPHENE_POINT3D_INIT (50, 0, 50)); transform = gsk_transform_rotate (transform, 20); transform = gsk_transform_rotate_3d (transform, 20, graphene_vec3_y_axis ()); g_assert_false (result_is_axis_aligned (transform, &r)); gsk_transform_unref (transform); } static void test_to_affine (void) { GskTransform *transform; float sx, sy, dx, dy; transform = gsk_transform_scale (NULL, 10.0, 5.0); gsk_transform_to_affine (transform, &sx, &sy, &dx, &dy); gsk_transform_unref (transform); g_assert_cmpfloat (sx, ==, 10.0); g_assert_cmpfloat (sy, ==, 5.0); g_assert_cmpfloat (dx, ==, 0.0); g_assert_cmpfloat (dy, ==, 0.0); transform = gsk_transform_translate (NULL, &GRAPHENE_POINT_INIT (10.0, 5.0)); gsk_transform_to_affine (transform, &sx, &sy, &dx, &dy); gsk_transform_unref (transform); g_assert_cmpfloat (sx, ==, 1.0); g_assert_cmpfloat (sy, ==, 1.0); g_assert_cmpfloat (dx, ==, 10.0); g_assert_cmpfloat (dy, ==, 5.0); transform = gsk_transform_translate (gsk_transform_scale (NULL, 2.0, 3.0), &GRAPHENE_POINT_INIT (10.0, 5.0)); gsk_transform_to_affine (transform, &sx, &sy, &dx, &dy); gsk_transform_unref (transform); g_assert_cmpfloat (sx, ==, 2.0); g_assert_cmpfloat (sy, ==, 3.0); g_assert_cmpfloat (dx, ==, 2.0 * 10.0); g_assert_cmpfloat (dy, ==, 3.0 * 5.0); transform = gsk_transform_scale (gsk_transform_translate (NULL, &GRAPHENE_POINT_INIT (10.0, 5.0)), 2.0, 3.0); gsk_transform_to_affine (transform, &sx, &sy, &dx, &dy); gsk_transform_unref (transform); g_assert_cmpfloat (sx, ==, 2.0); g_assert_cmpfloat (sy, ==, 3.0); g_assert_cmpfloat (dx, ==, 10.0); g_assert_cmpfloat (dy, ==, 5.0); } static void test_transform_bounds (void) { GskTransform *t = gsk_transform_translate (NULL, &GRAPHENE_POINT_INIT (50, 50)); graphene_rect_t bounds = GRAPHENE_RECT_INIT (0, 0, 100, 100); graphene_rect_t out; gsk_transform_transform_bounds (t, &bounds, &out); g_assert_true (graphene_rect_equal (&out, &GRAPHENE_RECT_INIT(50, 50, 100, 100))); t = gsk_transform_rotate (t, 180); gsk_transform_transform_bounds (t, &bounds, &out); g_assert_true (graphene_rect_equal (&out, &GRAPHENE_RECT_INIT(-50, -50, 100, 100))); t = gsk_transform_translate (t, &GRAPHENE_POINT_INIT (-50, -50)); gsk_transform_transform_bounds (t, &bounds, &out); g_assert_true (graphene_rect_equal (&out, &GRAPHENE_RECT_INIT(0, 0, 100, 100))); t = gsk_transform_scale (NULL, -1, 1); gsk_transform_transform_bounds (t, &bounds, &out); g_assert_true (graphene_rect_equal (&out, &GRAPHENE_RECT_INIT(-100, 0, 100, 100))); t = gsk_transform_scale (NULL, -1, -2); gsk_transform_transform_bounds (t, &bounds, &out); g_assert_true (graphene_rect_equal (&out, &GRAPHENE_RECT_INIT(-100, -200, 100, 200))); } #define DEG_TO_RAD(x) ((x) / 180.0 * G_PI) static void test_to_2d (void) { GskTransform *transform; float xx, yx, xy, yy, dx, dy; float s, c; float tx,ty; transform = gsk_transform_scale (NULL, 10.0, 5.0); gsk_transform_to_2d (transform, &xx, &yx, &xy, &yy, &dx, &dy); gsk_transform_unref (transform); g_assert_cmpfloat (xx, ==, 10.0); g_assert_cmpfloat (yx, ==, 0.0); g_assert_cmpfloat (xy, ==, 0.0); g_assert_cmpfloat (yy, ==, 5.0); g_assert_cmpfloat (dx, ==, 0.0); g_assert_cmpfloat (dy, ==, 0.0); transform = gsk_transform_translate (NULL, &GRAPHENE_POINT_INIT (10.0, 5.0)); gsk_transform_to_2d (transform, &xx, &yx, &xy, &yy, &dx, &dy); gsk_transform_unref (transform); g_assert_cmpfloat (xx, ==, 1.0); g_assert_cmpfloat (yx, ==, 0.0); g_assert_cmpfloat (xy, ==, 0.0); g_assert_cmpfloat (yy, ==, 1.0); g_assert_cmpfloat (dx, ==, 10.0); g_assert_cmpfloat (dy, ==, 5.0); transform = gsk_transform_rotate (NULL, 33.0); gsk_transform_to_2d (transform, &xx, &yx, &xy, &yy, &dx, &dy); gsk_transform_unref (transform); c = cosf (DEG_TO_RAD (33.0)); s = sinf (DEG_TO_RAD (33.0)); g_assert_cmpfloat (xx, ==, c); g_assert_cmpfloat (yx, ==, s); g_assert_cmpfloat (xy, ==, -s); g_assert_cmpfloat (yy, ==, c); g_assert_cmpfloat (dx, ==, 0.0); g_assert_cmpfloat (dy, ==, 0.0); transform = gsk_transform_skew (NULL, 33.0, 0); gsk_transform_to_2d (transform, &xx, &yx, &xy, &yy, &dx, &dy); gsk_transform_unref (transform); tx = tanf (DEG_TO_RAD (33.0)); g_assert_cmpfloat (xx, ==, 1); g_assert_cmpfloat (yx, ==, 0); g_assert_cmpfloat (xy, ==, tx); g_assert_cmpfloat (yy, ==, 1); g_assert_cmpfloat (dx, ==, 0.0); g_assert_cmpfloat (dy, ==, 0.0); transform = gsk_transform_skew (NULL, 0, 66.0); gsk_transform_to_2d (transform, &xx, &yx, &xy, &yy, &dx, &dy); gsk_transform_unref (transform); ty = tanf (DEG_TO_RAD (66.0)); g_assert_cmpfloat (xx, ==, 1); g_assert_cmpfloat (yx, ==, ty); g_assert_cmpfloat (xy, ==, 0); g_assert_cmpfloat (yy, ==, 1); g_assert_cmpfloat (dx, ==, 0.0); g_assert_cmpfloat (dy, ==, 0.0); transform = gsk_transform_translate (NULL, &GRAPHENE_POINT_INIT (10.0, 5.0)); transform = gsk_transform_rotate (transform, 33.0); gsk_transform_to_2d (transform, &xx, &yx, &xy, &yy, &dx, &dy); gsk_transform_unref (transform); g_assert_cmpfloat (xx, ==, 0.838670552f); g_assert_cmpfloat (yx, ==, 0.544639051f); g_assert_cmpfloat (xy, ==, -0.544639051f); g_assert_cmpfloat (yy, ==, 0.838670552f); g_assert_cmpfloat (dx, ==, 10.f); g_assert_cmpfloat (dy, ==, 5.f); } static void test_to_2d_components (void) { GskTransform *transform, *transform2; float skew_x, skew_y, scale_x, scale_y, angle, dx, dy; graphene_matrix_t m, m2; transform = gsk_transform_scale ( gsk_transform_rotate ( gsk_transform_translate (NULL, &GRAPHENE_POINT_INIT (10, 20)), 22), 3, 3); gsk_transform_to_2d_components (transform, &skew_x, &skew_y, &scale_x, &scale_y, &angle, &dx, &dy); g_assert_cmpfloat_with_epsilon (skew_x, 0, 0.0001); g_assert_cmpfloat_with_epsilon (skew_y, 0, 0.0001); g_assert_cmpfloat_with_epsilon (scale_x, 3, 0.0001); g_assert_cmpfloat_with_epsilon (scale_y, 3, 0.0001); g_assert_cmpfloat_with_epsilon (angle, 22, 0.0001); g_assert_cmpfloat_with_epsilon (dx, 10, 0.0001); g_assert_cmpfloat_with_epsilon (dy, 20, 0.0001); gsk_transform_unref (transform); transform = gsk_transform_skew ( gsk_transform_scale ( gsk_transform_rotate ( gsk_transform_translate (NULL, &GRAPHENE_POINT_INIT (10, 20)), 22), 3, 6), 33, 0); g_assert_true (gsk_transform_get_category (transform) >= GSK_TRANSFORM_CATEGORY_2D); gsk_transform_to_2d_components (transform, &skew_x, &skew_y, &scale_x, &scale_y, &angle, &dx, &dy); transform2 = gsk_transform_skew ( gsk_transform_scale ( gsk_transform_rotate ( gsk_transform_translate (NULL, &GRAPHENE_POINT_INIT (dx, dy)), angle), scale_x, scale_y), skew_x, skew_y); gsk_transform_to_matrix (transform, &m); gsk_transform_to_matrix (transform2, &m2); g_assert_true (graphene_matrix_near (&m, &m2, 0.001)); gsk_transform_unref (transform); gsk_transform_unref (transform2); transform = gsk_transform_translate (NULL, &GRAPHENE_POINT_INIT (2, 3)); gsk_transform_to_2d_components (transform, &skew_x, &skew_y, &scale_x, &scale_y, &angle, &dx, &dy); g_assert_cmpfloat_with_epsilon (skew_x, 0, 0.0001); g_assert_cmpfloat_with_epsilon (skew_y, 0, 0.0001); g_assert_cmpfloat_with_epsilon (scale_x, 1, 0.0001); g_assert_cmpfloat_with_epsilon (scale_y, 1, 0.0001); g_assert_cmpfloat_with_epsilon (angle, 0, 0.0001); g_assert_cmpfloat_with_epsilon (dx, 2, 0.0001); g_assert_cmpfloat_with_epsilon (dy, 3, 0.0001); gsk_transform_unref (transform); } static void test_transform_point (void) { GskTransform *t, *t2, *t3; graphene_point_t p; t = gsk_transform_scale (gsk_transform_translate (NULL, &GRAPHENE_POINT_INIT (1, 2)), 2, 2); t2 = gsk_transform_translate (gsk_transform_scale (NULL, 2, 2), &GRAPHENE_POINT_INIT (1, 2)); t3 = gsk_transform_rotate (NULL, 90); gsk_transform_transform_point (NULL, &GRAPHENE_POINT_INIT (1,1), &p); g_assert_true (graphene_point_equal (&p, &GRAPHENE_POINT_INIT (1, 1))); gsk_transform_transform_point (t, &GRAPHENE_POINT_INIT (1,1), &p); g_assert_true (graphene_point_equal (&p, &GRAPHENE_POINT_INIT (3, 4))); gsk_transform_transform_point (t2, &GRAPHENE_POINT_INIT (1,1), &p); g_assert_true (graphene_point_equal (&p, &GRAPHENE_POINT_INIT (4, 6))); gsk_transform_transform_point (t3, &GRAPHENE_POINT_INIT (2,1), &p); g_assert_true (graphene_point_equal (&p, &GRAPHENE_POINT_INIT (-1, 2))); gsk_transform_unref (t); gsk_transform_unref (t2); gsk_transform_unref (t3); } static void test_scale_transform (void) { GskTransform *t1, *t2; char *string; gboolean res; GskTransform *x; t1 = gsk_transform_scale (NULL, 4, 9); t2 = gsk_transform_scale (gsk_transform_scale (NULL, 2, 3), 2, 3); g_assert_true (gsk_transform_get_category (t1) == GSK_TRANSFORM_CATEGORY_2D_AFFINE); g_assert_true (gsk_transform_get_category (t2) == GSK_TRANSFORM_CATEGORY_2D_AFFINE); g_assert_true (gsk_transform_equal (t1, t2)); string = gsk_transform_to_string (t1); res = gsk_transform_parse (string, &x); g_assert_true (res); g_assert_true (gsk_transform_equal (t1, x)); gsk_transform_unref (t1); gsk_transform_unref (t2); g_free (string); gsk_transform_unref (x); } static void test_skew_transform (void) { GskTransform *t1, *t2, *t3, *t4; char *string; gboolean res; GskTransform *x; t1 = gsk_transform_skew (NULL, 30, 60); t2 = gsk_transform_skew (NULL, 0, 30); t3 = gsk_transform_skew (NULL, 0, -30); t4 = gsk_transform_skew (NULL, 30, 0); g_assert_true (gsk_transform_get_category (t1) == GSK_TRANSFORM_CATEGORY_2D); g_assert_true (gsk_transform_get_category (t2) == GSK_TRANSFORM_CATEGORY_2D); g_assert_false (gsk_transform_equal (t1, t2)); t2 = gsk_transform_invert (t2); graphene_assert_fuzzy_transform_equal (t2, t3, EPSILON); string = gsk_transform_to_string (t1); res = gsk_transform_parse (string, &x); g_assert_true (res); g_assert_true (gsk_transform_equal (t1, x)); g_free (string); gsk_transform_unref (x); string = gsk_transform_to_string (t3); res = gsk_transform_parse (string, &x); g_assert_true (res); g_assert_true (gsk_transform_equal (t3, x)); g_free (string); gsk_transform_unref (x); string = gsk_transform_to_string (t4); res = gsk_transform_parse (string, &x); g_assert_true (res); g_assert_true (gsk_transform_equal (t4, x)); g_free (string); gsk_transform_unref (x); t3 = gsk_transform_transform (t3, t4); g_assert_true (gsk_transform_get_category (t3) == GSK_TRANSFORM_CATEGORY_2D); gsk_transform_unref (t1); gsk_transform_unref (t2); gsk_transform_unref (t3); gsk_transform_unref (t4); } static void test_perspective_transform (void) { GskTransform *t1, *t2; t1 = gsk_transform_perspective (NULL, 1000); t2 = gsk_transform_perspective (NULL, 300); g_assert_true (gsk_transform_get_category (t1) == GSK_TRANSFORM_CATEGORY_ANY); g_assert_true (gsk_transform_get_category (t2) == GSK_TRANSFORM_CATEGORY_ANY); g_assert_false (gsk_transform_equal (t1, t2)); t2 = gsk_transform_perspective (t2, 700); g_assert_true (gsk_transform_equal (t1, t2)); gsk_transform_unref (t1); gsk_transform_unref (t2); } static void test_rotate_transform (void) { GskTransform *t1, *t2, *t3; graphene_vec3_t vec; t1 = gsk_transform_rotate (NULL, 60); t2 = gsk_transform_rotate (NULL, 20); g_assert_true (gsk_transform_get_category (t1) == GSK_TRANSFORM_CATEGORY_2D); g_assert_true (gsk_transform_get_category (t2) == GSK_TRANSFORM_CATEGORY_2D); g_assert_false (gsk_transform_equal (t1, t2)); t2 = gsk_transform_rotate (t2, 40); g_assert_true (gsk_transform_equal (t1, t2)); t1 = gsk_transform_invert (t1); t3 = gsk_transform_rotate (NULL, -60); g_assert_true (gsk_transform_equal (t1, t3)); gsk_transform_unref (t2); t2 = gsk_transform_rotate_3d (NULL, -60, graphene_vec3_init (&vec, 0, 0, 1)); g_assert_true (gsk_transform_get_category (t2) == GSK_TRANSFORM_CATEGORY_2D); g_assert_true (gsk_transform_equal (t1, t2)); gsk_transform_unref (t1); gsk_transform_unref (t2); gsk_transform_unref (t3); } static void test_rotate3d_transform (void) { GskTransform *t1, *t2; graphene_vec3_t vec; t1 = gsk_transform_rotate_3d (NULL, 60, graphene_vec3_init (&vec, 1, 2, 3)); t2 = gsk_transform_rotate_3d (NULL, -60, graphene_vec3_init (&vec, 1, 2, 3)); g_assert_true (gsk_transform_get_category (t1) == GSK_TRANSFORM_CATEGORY_3D); g_assert_true (gsk_transform_get_category (t2) == GSK_TRANSFORM_CATEGORY_3D); g_assert_false (gsk_transform_equal (t1, t2)); t2 = gsk_transform_invert (t2); g_assert_true (gsk_transform_equal (t1, t2)); gsk_transform_unref (t1); gsk_transform_unref (t2); } static void test_matrix_transform (void) { GskTransform *t1, *t2; float xx, yx, xy, yy, dx, dy; graphene_matrix_t m; if (!gsk_transform_parse ("matrix(2, 0, 0, 3, 1, -1)", &t1)) g_assert_not_reached (); g_assert_true (gsk_transform_get_category (t1) == GSK_TRANSFORM_CATEGORY_2D); gsk_transform_to_2d (t1, &xx, &yx, &xy, &yy, &dx, &dy); g_assert_cmpfloat (xx, ==, 2.f); g_assert_cmpfloat (yx, ==, 0.f); g_assert_cmpfloat (xy, ==, 0.f); g_assert_cmpfloat (yy, ==, 3.f); g_assert_cmpfloat (dx, ==, 1.f); g_assert_cmpfloat (dy, ==, -1.f); graphene_matrix_init_from_float (&m, (const float[]) { 2.f, 0.f, 0.f, 0.f, 0.f, 3.f, 0.f, 0.f, 0.f, 0.f, 1.f, 0.f, 1., -1.f, 0.f, 1.f}); t2 = gsk_transform_matrix (NULL, &m); g_assert_true (gsk_transform_equal (t1, t2)); t1 = gsk_transform_invert (t1); t2 = gsk_transform_transform (t2, t1); graphene_assert_fuzzy_transform_equal (t2, NULL, EPSILON); gsk_transform_unref (t1); gsk_transform_unref (t2); } static void test_matrix_roundtrip (void) { GskTransform *transform; gboolean res; const char *input; char *str; input = "matrix(2, 3, 4, 5, 6, 7)"; res = gsk_transform_parse (input, &transform); g_assert_true (res); g_assert_true (gsk_transform_get_category (transform) >= GSK_TRANSFORM_CATEGORY_2D); str = gsk_transform_to_string (transform); g_assert_cmpstr (str, ==, input); g_free (str); input = "matrix3d(2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17)"; res = gsk_transform_parse (input, &transform); g_assert_true (res); g_assert_true (gsk_transform_get_category (transform) < GSK_TRANSFORM_CATEGORY_2D); str = gsk_transform_to_string (transform); g_assert_cmpstr (str, ==, input); g_free (str); } int main (int argc, char *argv[]) { gtk_test_init (&argc, &argv, NULL); g_test_add_func ("/transform/conversions/simple", test_conversions_simple); g_test_add_func ("/transform/conversions/transformed", test_conversions_transformed); g_test_add_func ("/transform/identity", test_identity); g_test_add_func ("/transform/identity-equal", test_identity_equal); g_test_add_func ("/transform/invert", test_invert); g_test_add_func ("/transform/print-parse", test_print_parse); g_test_add_func ("/transform/check-axis-aligneness", test_axis_aligned); g_test_add_func ("/transform/to-affine", test_to_affine); g_test_add_func ("/transform/bounds", test_transform_bounds); g_test_add_func ("/transform/point", test_transform_point); g_test_add_func ("/transform/to-2d", test_to_2d); g_test_add_func ("/transform/to-2d-components", test_to_2d_components); g_test_add_func ("/transform/scale", test_scale_transform); g_test_add_func ("/transform/skew", test_skew_transform); g_test_add_func ("/transform/perspective", test_perspective_transform); g_test_add_func ("/transform/rotate", test_rotate_transform); g_test_add_func ("/transform/rotate3d", test_rotate3d_transform); g_test_add_func ("/transform/matrix", test_matrix_transform); g_test_add_func ("/transform/matrix/roundtrip", test_matrix_roundtrip); return g_test_run (); }