1 | /* |
2 | * Copyright © 2019 Benjamin Otte |
3 | * |
4 | * This library is free software; you can redistribute it and/or |
5 | * modify it under the terms of the GNU Lesser General Public |
6 | * License as published by the Free Software Foundation; either |
7 | * version 2.1 of the License, or (at your option) any later version. |
8 | * |
9 | * This library is distributed in the hope that it will be useful, |
10 | * but WITHOUT ANY WARRANTY; without even the implied warranty of |
11 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU |
12 | * Lesser General Public License for more details. |
13 | * |
14 | * You should have received a copy of the GNU Lesser General Public |
15 | * License along with this library. If not, see <http://www.gnu.org/licenses/>. |
16 | * |
17 | * Authors: Benjamin Otte <otte@gnome.org> |
18 | */ |
19 | |
20 | #include "config.h" |
21 | |
22 | #include <gtk/gtk.h> |
23 | |
24 | #define EPSILON (1.f / 1024 / 32) /* 2^-15 */ |
25 | |
26 | /* macros stolen from graphene testsuite, so they get to keep their names */ |
27 | |
28 | #if defined(__GNUC__) && (__GNUC__ > 4 || (__GNUC__ == 4 && __GNUC_MINOR__ >= 9)) |
29 | /* Use typeof on GCC */ |
30 | # define graphene_fuzzy_equals(n1,n2,epsilon) \ |
31 | G_GNUC_EXTENSION({ \ |
32 | __auto_type _n1 = (n1); \ |
33 | __auto_type _n2 = (n2); \ |
34 | __auto_type _epsilon = (epsilon); \ |
35 | ((_n1 > _n2 ? (_n1 - _n2 ) : (_n2 - _n1)) <= _epsilon); \ |
36 | }) |
37 | |
38 | #else |
39 | /* fallback for Visual Studio, typeof not supported */ |
40 | # define graphene_fuzzy_equals(n1,n2,epsilon) \ |
41 | (((n1) > (n2) ? ((n1) - (n2)) : ((n2) - (n1))) <= (epsilon)) |
42 | |
43 | #endif /* __GNUC__ */ |
44 | |
45 | #define graphene_assert_fuzzy_matrix_cell_equal(row,col,n1,n2,epsilon) \ |
46 | G_STMT_START { \ |
47 | if (graphene_fuzzy_equals (n1, n2, epsilon)) ; else { \ |
48 | char *s = g_strdup_printf ("[%d][%d]: " #n1 " == " #n2 " (+/- " #epsilon "): (%.7g == %.7g)", \ |
49 | row, col, n1, n2); \ |
50 | g_assertion_message (G_LOG_DOMAIN, __FILE__, __LINE__, G_STRFUNC, s); \ |
51 | g_free (s); \ |
52 | } \ |
53 | } G_STMT_END |
54 | |
55 | #define graphene_assert_fuzzy_matrix_equal(m1,m2,epsilon) \ |
56 | G_STMT_START { \ |
57 | unsigned int __i, __j; \ |
58 | float __m1[16], __m2[16]; \ |
59 | graphene_matrix_to_float ((m1), __m1); \ |
60 | graphene_matrix_to_float ((m2), __m2); \ |
61 | for (__i = 0; __i < 4; __i++) { \ |
62 | for (__j = 0; __j < 4; __j++) { \ |
63 | unsigned int __idx = __i * 4 + __j; \ |
64 | graphene_assert_fuzzy_matrix_cell_equal (__i, __j, __m1[__idx], __m2[__idx], epsilon); \ |
65 | } \ |
66 | } \ |
67 | } G_STMT_END |
68 | |
69 | #define graphene_assert_fuzzy_transform_equal(t1,t2,epsilon) \ |
70 | G_STMT_START { \ |
71 | graphene_matrix_t __mat1, __mat2; \ |
72 | gsk_transform_to_matrix ((t1), &__mat1); \ |
73 | gsk_transform_to_matrix ((t2), &__mat2); \ |
74 | graphene_assert_fuzzy_matrix_equal (&__mat1, &__mat2, (epsilon)); \ |
75 | } G_STMT_END |
76 | |
77 | static struct { |
78 | GskTransformCategory category; |
79 | } test_transforms[] = { |
80 | { GSK_TRANSFORM_CATEGORY_IDENTITY }, |
81 | { GSK_TRANSFORM_CATEGORY_IDENTITY }, |
82 | { GSK_TRANSFORM_CATEGORY_2D_TRANSLATE }, |
83 | { GSK_TRANSFORM_CATEGORY_3D }, |
84 | { GSK_TRANSFORM_CATEGORY_2D }, |
85 | { GSK_TRANSFORM_CATEGORY_3D }, |
86 | { GSK_TRANSFORM_CATEGORY_2D_AFFINE }, |
87 | { GSK_TRANSFORM_CATEGORY_3D }, |
88 | { GSK_TRANSFORM_CATEGORY_ANY }, |
89 | }; |
90 | |
91 | static GskTransform * |
92 | apply_test_transform (GskTransform *transform, |
93 | guint i) |
94 | { |
95 | switch (i) |
96 | { |
97 | case 0: |
98 | return transform ? transform : gsk_transform_new (); |
99 | |
100 | case 1: |
101 | return gsk_transform_transform (next: transform, NULL); |
102 | |
103 | case 2: |
104 | return gsk_transform_translate (next: transform, point: &GRAPHENE_POINT_INIT (3, 5)); |
105 | |
106 | case 3: |
107 | return gsk_transform_translate_3d (next: transform, point: &GRAPHENE_POINT3D_INIT (3, 5, 7)); |
108 | |
109 | case 4: |
110 | return gsk_transform_rotate (next: transform, angle: 90); |
111 | |
112 | case 5: |
113 | return gsk_transform_rotate_3d (next: transform, angle: 90, axis: graphene_vec3_y_axis ()); |
114 | |
115 | case 6: |
116 | return gsk_transform_scale (next: transform, factor_x: 2, factor_y: 3); |
117 | |
118 | case 7: |
119 | return gsk_transform_scale_3d (next: transform, factor_x: 2, factor_y: 3, factor_z: 5); |
120 | |
121 | case 8: |
122 | return gsk_transform_perspective (next: transform, depth: 5); |
123 | |
124 | default: |
125 | g_assert_not_reached (); |
126 | return NULL; |
127 | } |
128 | } |
129 | |
130 | static GskTransformCategory |
131 | categorize_matrix (const graphene_matrix_t *matrix) |
132 | { |
133 | if (!graphene_fuzzy_equals (graphene_matrix_get_value (matrix, 0, 3), 0, EPSILON) || |
134 | !graphene_fuzzy_equals (graphene_matrix_get_value (matrix, 1, 3), 0, EPSILON) || |
135 | !graphene_fuzzy_equals (graphene_matrix_get_value (matrix, 2, 3), 0, EPSILON) || |
136 | !graphene_fuzzy_equals (graphene_matrix_get_value (matrix, 3, 3), 1, EPSILON)) |
137 | return GSK_TRANSFORM_CATEGORY_ANY; |
138 | |
139 | if (!graphene_fuzzy_equals (graphene_matrix_get_value (matrix, 0, 2), 0, EPSILON) || |
140 | !graphene_fuzzy_equals (graphene_matrix_get_value (matrix, 1, 2), 0, EPSILON) || |
141 | !graphene_fuzzy_equals (graphene_matrix_get_value (matrix, 2, 2), 1, EPSILON) || |
142 | !graphene_fuzzy_equals (graphene_matrix_get_value (matrix, 3, 2), 0, EPSILON) || |
143 | !graphene_fuzzy_equals (graphene_matrix_get_value (matrix, 2, 0), 0, EPSILON) || |
144 | !graphene_fuzzy_equals (graphene_matrix_get_value (matrix, 2, 1), 0, EPSILON)) |
145 | return GSK_TRANSFORM_CATEGORY_3D; |
146 | |
147 | if (!graphene_fuzzy_equals (graphene_matrix_get_value (matrix, 0, 1), 0, EPSILON) || |
148 | !graphene_fuzzy_equals (graphene_matrix_get_value (matrix, 1, 0), 0, EPSILON)) |
149 | return GSK_TRANSFORM_CATEGORY_2D; |
150 | |
151 | if (!graphene_fuzzy_equals (graphene_matrix_get_value (matrix, 0, 0), 1, EPSILON) || |
152 | !graphene_fuzzy_equals (graphene_matrix_get_value (matrix, 1, 1), 1, EPSILON)) |
153 | return GSK_TRANSFORM_CATEGORY_2D_AFFINE; |
154 | |
155 | if (!graphene_fuzzy_equals (graphene_matrix_get_value (matrix, 3, 0), 0, EPSILON) || |
156 | !graphene_fuzzy_equals (graphene_matrix_get_value (matrix, 3, 1), 0, EPSILON)) |
157 | return GSK_TRANSFORM_CATEGORY_2D_TRANSLATE; |
158 | |
159 | return GSK_TRANSFORM_CATEGORY_IDENTITY; |
160 | } |
161 | |
162 | static void |
163 | check_conversions (GskTransform *transform, |
164 | GskTransformCategory expected_category) |
165 | { |
166 | graphene_matrix_t matrix, test; |
167 | float f[16] = { 1, 0, 0, 0, |
168 | 0, 1, 0, 0, |
169 | 0, 0, 1, 0, |
170 | 0, 0, 0, 1 }; |
171 | |
172 | g_assert_cmpint (gsk_transform_get_category (transform), ==, expected_category); |
173 | gsk_transform_to_matrix (self: transform, out_matrix: &matrix); |
174 | /* we don't insist on getting simplifications right. |
175 | * The matrix "scale(2) scale(0.5)" would be categorized as identity, |
176 | * but the transform might not do that. |
177 | */ |
178 | g_assert_cmpint (gsk_transform_get_category (transform), <=, categorize_matrix (&matrix)); |
179 | |
180 | switch (expected_category) |
181 | { |
182 | case GSK_TRANSFORM_CATEGORY_UNKNOWN: |
183 | case GSK_TRANSFORM_CATEGORY_ANY: |
184 | case GSK_TRANSFORM_CATEGORY_3D: |
185 | break; |
186 | |
187 | case GSK_TRANSFORM_CATEGORY_IDENTITY: |
188 | case GSK_TRANSFORM_CATEGORY_2D_TRANSLATE: |
189 | gsk_transform_to_translate (self: transform, |
190 | out_dx: &f[4 * 3 + 0], out_dy: &f[4 * 3 + 1]); |
191 | graphene_matrix_init_from_float (m: &test, v: f); |
192 | graphene_assert_fuzzy_matrix_equal (&matrix, &test, EPSILON); |
193 | |
194 | G_GNUC_FALLTHROUGH; |
195 | case GSK_TRANSFORM_CATEGORY_2D_AFFINE: |
196 | gsk_transform_to_affine (self: transform, |
197 | out_scale_x: &f[4 * 0 + 0], out_scale_y: &f[4 * 1 + 1], |
198 | out_dx: &f[4 * 3 + 0], out_dy: &f[4 * 3 + 1]); |
199 | graphene_matrix_init_from_float (m: &test, v: f); |
200 | graphene_assert_fuzzy_matrix_equal (&matrix, &test, EPSILON); |
201 | |
202 | G_GNUC_FALLTHROUGH; |
203 | case GSK_TRANSFORM_CATEGORY_2D: |
204 | gsk_transform_to_2d (self: transform, |
205 | out_xx: &f[4 * 0 + 0], out_yx: &f[4 * 0 + 1], |
206 | out_xy: &f[4 * 1 + 0], out_yy: &f[4 * 1 + 1], |
207 | out_dx: &f[4 * 3 + 0], out_dy: &f[4 * 3 + 1]); |
208 | graphene_matrix_init_from_float (m: &test, v: f); |
209 | graphene_assert_fuzzy_matrix_equal (&matrix, &test, EPSILON); |
210 | break; |
211 | |
212 | default: |
213 | g_assert_not_reached (); |
214 | } |
215 | } |
216 | |
217 | static void |
218 | test_conversions_simple (void) |
219 | { |
220 | GskTransform *transform; |
221 | guint i; |
222 | |
223 | for (i = 0; i < G_N_ELEMENTS (test_transforms); i++) |
224 | { |
225 | transform = apply_test_transform (NULL, i); |
226 | check_conversions (transform, expected_category: test_transforms[i].category); |
227 | gsk_transform_unref (self: transform); |
228 | } |
229 | } |
230 | |
231 | static void |
232 | test_conversions_transformed (void) |
233 | { |
234 | GskTransform *transform; |
235 | guint i, j, k; |
236 | |
237 | for (i = 0; i < G_N_ELEMENTS (test_transforms); i++) |
238 | { |
239 | for (j = 0; j < G_N_ELEMENTS (test_transforms); j++) |
240 | { |
241 | for (k = 0; k < G_N_ELEMENTS (test_transforms); k++) |
242 | { |
243 | transform = apply_test_transform (NULL, i); |
244 | transform = apply_test_transform (transform, i: j); |
245 | transform = apply_test_transform (transform, i: k); |
246 | check_conversions (transform, MIN (test_transforms[i].category, MIN (test_transforms[j].category, test_transforms[k].category))); |
247 | gsk_transform_unref (self: transform); |
248 | } |
249 | } |
250 | } |
251 | } |
252 | |
253 | static void |
254 | test_invert (void) |
255 | { |
256 | GskTransform *transform, *inverse, *identity; |
257 | guint i, j, k; |
258 | |
259 | for (i = 0; i < G_N_ELEMENTS (test_transforms); i++) |
260 | { |
261 | for (j = 0; j < G_N_ELEMENTS (test_transforms); j++) |
262 | { |
263 | for (k = 0; k < G_N_ELEMENTS (test_transforms); k++) |
264 | { |
265 | transform = apply_test_transform (NULL, i); |
266 | transform = apply_test_transform (transform, i: j); |
267 | transform = apply_test_transform (transform, i: k); |
268 | inverse = gsk_transform_invert (self: gsk_transform_ref (self: transform)); |
269 | g_assert_true (inverse != NULL || transform == NULL); |
270 | |
271 | identity = gsk_transform_transform (next: gsk_transform_ref (self: transform), other: inverse); |
272 | graphene_assert_fuzzy_transform_equal (identity, NULL, EPSILON); |
273 | gsk_transform_unref (self: identity); |
274 | |
275 | inverse = gsk_transform_invert (self: inverse); |
276 | graphene_assert_fuzzy_transform_equal (transform, inverse, EPSILON); |
277 | |
278 | gsk_transform_unref (self: transform); |
279 | gsk_transform_unref (self: inverse); |
280 | } |
281 | } |
282 | } |
283 | } |
284 | |
285 | /* some trivialities around identity transforms */ |
286 | static void |
287 | test_identity (void) |
288 | { |
289 | GskTransform *s, *t, *u, *v, *w, *x; |
290 | char *string; |
291 | float a, b, c, d, tx, ty; |
292 | gboolean res; |
293 | |
294 | s = gsk_transform_new (); |
295 | t = gsk_transform_new (); |
296 | u = gsk_transform_transform (next: gsk_transform_ref (self: s), NULL); |
297 | |
298 | g_assert_cmpint (gsk_transform_get_category (s), ==, GSK_TRANSFORM_CATEGORY_IDENTITY); |
299 | g_assert_cmpint (gsk_transform_get_category (t), ==, GSK_TRANSFORM_CATEGORY_IDENTITY); |
300 | g_assert_cmpint (gsk_transform_get_category (u), ==, GSK_TRANSFORM_CATEGORY_IDENTITY); |
301 | |
302 | g_assert_true (gsk_transform_equal (s, t)); |
303 | g_assert_true (gsk_transform_equal (t, u)); |
304 | g_assert_true (gsk_transform_equal (s, u)); |
305 | |
306 | v = gsk_transform_transform (next: gsk_transform_ref (self: s), other: t); |
307 | |
308 | g_assert_cmpint (gsk_transform_get_category (v), ==, GSK_TRANSFORM_CATEGORY_IDENTITY); |
309 | |
310 | w = gsk_transform_invert (self: gsk_transform_ref (self: v)); |
311 | g_assert_cmpint (gsk_transform_get_category (w), ==, GSK_TRANSFORM_CATEGORY_IDENTITY); |
312 | |
313 | string = gsk_transform_to_string (self: s); |
314 | res = gsk_transform_parse (string, out_transform: &x); |
315 | |
316 | g_assert_true (res); |
317 | g_assert_cmpint (gsk_transform_get_category (x), ==, GSK_TRANSFORM_CATEGORY_IDENTITY); |
318 | |
319 | gsk_transform_to_2d (self: s, out_xx: &a, out_yx: &b, out_xy: &c, out_yy: &d, out_dx: &tx, out_dy: &ty); |
320 | |
321 | g_assert_cmpfloat (a, ==, 1.0f); |
322 | g_assert_cmpfloat (b, ==, 0.0f); |
323 | g_assert_cmpfloat (c, ==, 0.0f); |
324 | g_assert_cmpfloat (d, ==, 1.0f); |
325 | g_assert_cmpfloat (tx, ==, 0.0f); |
326 | g_assert_cmpfloat (ty, ==, 0.f); |
327 | |
328 | gsk_transform_unref (self: s); |
329 | gsk_transform_unref (self: t); |
330 | gsk_transform_unref (self: u); |
331 | gsk_transform_unref (self: v); |
332 | gsk_transform_unref (self: w); |
333 | gsk_transform_unref (self: x); |
334 | |
335 | g_free (mem: string); |
336 | } |
337 | |
338 | static void |
339 | test_identity_equal (void) |
340 | { |
341 | GskTransform *id = gsk_transform_new (); |
342 | GskTransform *t; |
343 | |
344 | g_assert_true (gsk_transform_equal (NULL, NULL)); |
345 | g_assert_true (gsk_transform_equal (id, NULL)); |
346 | g_assert_true (gsk_transform_equal (NULL, id)); |
347 | g_assert_true (gsk_transform_equal (id, id)); |
348 | |
349 | t = gsk_transform_transform (NULL, NULL); |
350 | g_assert_true (gsk_transform_equal (t, NULL)); |
351 | gsk_transform_unref (self: t); |
352 | t = gsk_transform_transform (next: gsk_transform_new (), NULL); |
353 | g_assert_true (gsk_transform_equal (t, NULL)); |
354 | gsk_transform_unref (self: t); |
355 | t = gsk_transform_transform (NULL, other: id); |
356 | g_assert_true (gsk_transform_equal (t, NULL)); |
357 | gsk_transform_unref (self: t); |
358 | t = gsk_transform_transform (next: gsk_transform_new (), other: id); |
359 | g_assert_true (gsk_transform_equal (t, NULL)); |
360 | gsk_transform_unref (self: t); |
361 | t = gsk_transform_new (); |
362 | t = gsk_transform_transform (next: t, other: t); |
363 | g_assert_true (gsk_transform_equal (t, NULL)); |
364 | gsk_transform_unref (self: t); |
365 | |
366 | gsk_transform_unref (self: id); |
367 | } |
368 | |
369 | static void |
370 | test_print_parse (void) |
371 | { |
372 | GskTransform *transform, *parsed; |
373 | guint i, j, k; |
374 | char *str1, *str2; |
375 | gboolean ret; |
376 | |
377 | for (i = 0; i < G_N_ELEMENTS (test_transforms); i++) |
378 | { |
379 | for (j = 0; j < G_N_ELEMENTS (test_transforms); j++) |
380 | { |
381 | for (k = 0; k < G_N_ELEMENTS (test_transforms); k++) |
382 | { |
383 | transform = apply_test_transform (NULL, i); |
384 | transform = apply_test_transform (transform, i: j); |
385 | transform = apply_test_transform (transform, i: k); |
386 | |
387 | str1 = gsk_transform_to_string (self: transform); |
388 | g_assert_nonnull (str1); |
389 | g_assert_true (strlen (str1) > 0); |
390 | |
391 | str2 = gsk_transform_to_string (self: transform); |
392 | g_assert_cmpstr (str1, ==, str2); |
393 | g_free (mem: str2); |
394 | |
395 | ret = gsk_transform_parse (string: str1, out_transform: &parsed); |
396 | g_assert_true (ret); |
397 | graphene_assert_fuzzy_transform_equal (parsed, transform, EPSILON); |
398 | |
399 | str2 = gsk_transform_to_string (self: parsed); |
400 | g_assert_cmpstr (str1, ==, str2); |
401 | g_free (mem: str2); |
402 | |
403 | g_free (mem: str1); |
404 | gsk_transform_unref (self: parsed); |
405 | gsk_transform_unref (self: transform); |
406 | } |
407 | } |
408 | } |
409 | } |
410 | |
411 | static void |
412 | gsk_matrix_transform_rect (const graphene_matrix_t *m, |
413 | const graphene_rect_t *r, |
414 | graphene_quad_t *res) |
415 | { |
416 | graphene_point_t ret[4]; |
417 | graphene_rect_t rr; |
418 | |
419 | graphene_rect_normalize_r (r, res: &rr); |
420 | |
421 | #define TRANSFORM_POINT(matrix, rect, corner, out_p) do {\ |
422 | graphene_vec4_t __s; \ |
423 | graphene_point_t __p; \ |
424 | float w; \ |
425 | graphene_rect_get_ ## corner (rect, &__p); \ |
426 | graphene_vec4_init (&__s, __p.x, __p.y, 0.f, 1.f); \ |
427 | graphene_matrix_transform_vec4 (matrix, &__s, &__s); \ |
428 | w = graphene_vec4_get_w (&__s); \ |
429 | out_p.x = graphene_vec4_get_x (&__s) / w; \ |
430 | out_p.y = graphene_vec4_get_y (&__s) / w; } while (0) |
431 | |
432 | TRANSFORM_POINT (m, &rr, top_left, ret[0]); |
433 | TRANSFORM_POINT (m, &rr, top_right, ret[1]); |
434 | TRANSFORM_POINT (m, &rr, bottom_right, ret[2]); |
435 | TRANSFORM_POINT (m, &rr, bottom_left, ret[3]); |
436 | |
437 | #undef TRANSFORM_POINT |
438 | |
439 | graphene_quad_init (q: res, p1: &ret[0], p2: &ret[1], p3: &ret[2], p4: &ret[3]); |
440 | } |
441 | |
442 | /* This is an auxiliary function used in the GL renderer to |
443 | * determine if transforming an axis-aligned rectangle produces |
444 | * axis-aligned output, to decide whether to use linear |
445 | * interpolation or not. |
446 | */ |
447 | static gboolean |
448 | result_is_axis_aligned (GskTransform *transform, |
449 | const graphene_rect_t *bounds) |
450 | { |
451 | graphene_matrix_t m; |
452 | graphene_quad_t q; |
453 | graphene_rect_t b; |
454 | graphene_point_t b1, b2; |
455 | const graphene_point_t *p; |
456 | int i; |
457 | |
458 | gsk_transform_to_matrix (self: transform, out_matrix: &m); |
459 | gsk_matrix_transform_rect (m: &m, r: bounds, res: &q); |
460 | graphene_quad_bounds (q: &q, r: &b); |
461 | graphene_rect_get_top_left (r: &b, p: &b1); |
462 | graphene_rect_get_bottom_right (r: &b, p: &b2); |
463 | |
464 | for (i = 0; i < 4; i++) |
465 | { |
466 | p = graphene_quad_get_point (q: &q, index_: i); |
467 | if (fabs (x: p->x - b1.x) > FLT_EPSILON && fabs (x: p->x - b2.x) > FLT_EPSILON) |
468 | return FALSE; |
469 | if (fabs (x: p->y - b1.y) > FLT_EPSILON && fabs (x: p->y - b2.y) > FLT_EPSILON) |
470 | return FALSE; |
471 | } |
472 | |
473 | return TRUE; |
474 | } |
475 | |
476 | static void |
477 | test_axis_aligned (void) |
478 | { |
479 | graphene_rect_t r = GRAPHENE_RECT_INIT (0, 0, 10, 10); |
480 | GskTransform *transform = NULL; |
481 | |
482 | transform = gsk_transform_translate (NULL, point: &GRAPHENE_POINT_INIT (10, 10)); |
483 | g_assert_true (result_is_axis_aligned (transform, &r)); |
484 | gsk_transform_unref (self: transform); |
485 | |
486 | transform = gsk_transform_translate_3d (NULL, point: &GRAPHENE_POINT3D_INIT(0, 10, 10)); |
487 | g_assert_true (result_is_axis_aligned (transform, &r)); |
488 | gsk_transform_unref (self: transform); |
489 | |
490 | transform = gsk_transform_rotate (NULL, angle: 90); |
491 | g_assert_true (result_is_axis_aligned (transform, &r)); |
492 | gsk_transform_unref (self: transform); |
493 | |
494 | transform = gsk_transform_scale (NULL, factor_x: 2, factor_y: 3); |
495 | g_assert_true (result_is_axis_aligned (transform, &r)); |
496 | gsk_transform_unref (self: transform); |
497 | |
498 | /* rotating around the y axis does not affect axis alignedness, |
499 | * as long as we don't involve perspective |
500 | */ |
501 | transform = gsk_transform_rotate_3d (NULL, angle: 45, axis: graphene_vec3_y_axis ()); |
502 | g_assert_true (result_is_axis_aligned (transform, &r)); |
503 | gsk_transform_unref (self: transform); |
504 | |
505 | /* rotating by 45 around the z axis, not axis aligned */ |
506 | transform = gsk_transform_rotate (NULL, angle: 45); |
507 | g_assert_false (result_is_axis_aligned (transform, &r)); |
508 | gsk_transform_unref (self: transform); |
509 | |
510 | /* perspective is harmless as long as we stay in the z=0 plane */ |
511 | transform = gsk_transform_perspective (NULL, depth: 100); |
512 | g_assert_true (result_is_axis_aligned (transform, &r)); |
513 | gsk_transform_unref (self: transform); |
514 | |
515 | /* a complex transform that makes things look '3d' */ |
516 | transform = gsk_transform_translate_3d (NULL, point: &GRAPHENE_POINT3D_INIT (0, 0, 50)); |
517 | transform = gsk_transform_perspective (next: transform, depth: 170); |
518 | transform = gsk_transform_translate_3d (next: transform, point: &GRAPHENE_POINT3D_INIT (50, 0, 50)); |
519 | transform = gsk_transform_rotate (next: transform, angle: 20); |
520 | transform = gsk_transform_rotate_3d (next: transform, angle: 20, axis: graphene_vec3_y_axis ()); |
521 | g_assert_false (result_is_axis_aligned (transform, &r)); |
522 | gsk_transform_unref (self: transform); |
523 | } |
524 | |
525 | static void |
526 | test_to_affine (void) |
527 | { |
528 | GskTransform *transform; |
529 | float sx, sy, dx, dy; |
530 | |
531 | transform = gsk_transform_scale (NULL, factor_x: 10.0, factor_y: 5.0); |
532 | gsk_transform_to_affine (self: transform, out_scale_x: &sx, out_scale_y: &sy, out_dx: &dx, out_dy: &dy); |
533 | gsk_transform_unref (self: transform); |
534 | |
535 | g_assert_cmpfloat (sx, ==, 10.0); |
536 | g_assert_cmpfloat (sy, ==, 5.0); |
537 | g_assert_cmpfloat (dx, ==, 0.0); |
538 | g_assert_cmpfloat (dy, ==, 0.0); |
539 | |
540 | transform = gsk_transform_translate (NULL, point: &GRAPHENE_POINT_INIT (10.0, 5.0)); |
541 | gsk_transform_to_affine (self: transform, out_scale_x: &sx, out_scale_y: &sy, out_dx: &dx, out_dy: &dy); |
542 | gsk_transform_unref (self: transform); |
543 | |
544 | g_assert_cmpfloat (sx, ==, 1.0); |
545 | g_assert_cmpfloat (sy, ==, 1.0); |
546 | g_assert_cmpfloat (dx, ==, 10.0); |
547 | g_assert_cmpfloat (dy, ==, 5.0); |
548 | |
549 | transform = gsk_transform_translate (next: gsk_transform_scale (NULL, factor_x: 2.0, factor_y: 3.0), point: &GRAPHENE_POINT_INIT (10.0, 5.0)); |
550 | gsk_transform_to_affine (self: transform, out_scale_x: &sx, out_scale_y: &sy, out_dx: &dx, out_dy: &dy); |
551 | gsk_transform_unref (self: transform); |
552 | |
553 | g_assert_cmpfloat (sx, ==, 2.0); |
554 | g_assert_cmpfloat (sy, ==, 3.0); |
555 | g_assert_cmpfloat (dx, ==, 2.0 * 10.0); |
556 | g_assert_cmpfloat (dy, ==, 3.0 * 5.0); |
557 | |
558 | transform = gsk_transform_scale (next: gsk_transform_translate (NULL, point: &GRAPHENE_POINT_INIT (10.0, 5.0)), factor_x: 2.0, factor_y: 3.0); |
559 | gsk_transform_to_affine (self: transform, out_scale_x: &sx, out_scale_y: &sy, out_dx: &dx, out_dy: &dy); |
560 | gsk_transform_unref (self: transform); |
561 | |
562 | g_assert_cmpfloat (sx, ==, 2.0); |
563 | g_assert_cmpfloat (sy, ==, 3.0); |
564 | g_assert_cmpfloat (dx, ==, 10.0); |
565 | g_assert_cmpfloat (dy, ==, 5.0); |
566 | } |
567 | |
568 | static void |
569 | test_transform_bounds (void) |
570 | { |
571 | GskTransform *t = gsk_transform_translate (NULL, point: &GRAPHENE_POINT_INIT (50, 50)); |
572 | graphene_rect_t bounds = GRAPHENE_RECT_INIT (0, 0, 100, 100); |
573 | graphene_rect_t out; |
574 | |
575 | gsk_transform_transform_bounds (self: t, rect: &bounds, out_rect: &out); |
576 | g_assert_true (graphene_rect_equal (&out, &GRAPHENE_RECT_INIT(50, 50, 100, 100))); |
577 | |
578 | t = gsk_transform_rotate (next: t, angle: 180); |
579 | gsk_transform_transform_bounds (self: t, rect: &bounds, out_rect: &out); |
580 | g_assert_true (graphene_rect_equal (&out, &GRAPHENE_RECT_INIT(-50, -50, 100, 100))); |
581 | |
582 | t = gsk_transform_translate (next: t, point: &GRAPHENE_POINT_INIT (-50, -50)); |
583 | gsk_transform_transform_bounds (self: t, rect: &bounds, out_rect: &out); |
584 | g_assert_true (graphene_rect_equal (&out, &GRAPHENE_RECT_INIT(0, 0, 100, 100))); |
585 | } |
586 | |
587 | #define DEG_TO_RAD(x) ((x) / 180.0 * G_PI) |
588 | |
589 | static void |
590 | test_to_2d (void) |
591 | { |
592 | GskTransform *transform; |
593 | float xx, yx, xy, yy, dx, dy; |
594 | float s, c; |
595 | float tx,ty; |
596 | |
597 | transform = gsk_transform_scale (NULL, factor_x: 10.0, factor_y: 5.0); |
598 | gsk_transform_to_2d (self: transform, out_xx: &xx, out_yx: &yx, out_xy: &xy, out_yy: &yy, out_dx: &dx, out_dy: &dy); |
599 | gsk_transform_unref (self: transform); |
600 | |
601 | g_assert_cmpfloat (xx, ==, 10.0); |
602 | g_assert_cmpfloat (yx, ==, 0.0); |
603 | g_assert_cmpfloat (xy, ==, 0.0); |
604 | g_assert_cmpfloat (yy, ==, 5.0); |
605 | g_assert_cmpfloat (dx, ==, 0.0); |
606 | g_assert_cmpfloat (dy, ==, 0.0); |
607 | |
608 | transform = gsk_transform_translate (NULL, point: &GRAPHENE_POINT_INIT (10.0, 5.0)); |
609 | gsk_transform_to_2d (self: transform, out_xx: &xx, out_yx: &yx, out_xy: &xy, out_yy: &yy, out_dx: &dx, out_dy: &dy); |
610 | gsk_transform_unref (self: transform); |
611 | |
612 | g_assert_cmpfloat (xx, ==, 1.0); |
613 | g_assert_cmpfloat (yx, ==, 0.0); |
614 | g_assert_cmpfloat (xy, ==, 0.0); |
615 | g_assert_cmpfloat (yy, ==, 1.0); |
616 | g_assert_cmpfloat (dx, ==, 10.0); |
617 | g_assert_cmpfloat (dy, ==, 5.0); |
618 | |
619 | transform = gsk_transform_rotate (NULL, angle: 33.0); |
620 | gsk_transform_to_2d (self: transform, out_xx: &xx, out_yx: &yx, out_xy: &xy, out_yy: &yy, out_dx: &dx, out_dy: &dy); |
621 | gsk_transform_unref (self: transform); |
622 | |
623 | c = cosf (DEG_TO_RAD (33.0)); |
624 | s = sinf (DEG_TO_RAD (33.0)); |
625 | |
626 | g_assert_cmpfloat (xx, ==, c); |
627 | g_assert_cmpfloat (yx, ==, s); |
628 | g_assert_cmpfloat (xy, ==, -s); |
629 | g_assert_cmpfloat (yy, ==, c); |
630 | g_assert_cmpfloat (dx, ==, 0.0); |
631 | g_assert_cmpfloat (dy, ==, 0.0); |
632 | |
633 | transform = gsk_transform_skew (NULL, skew_x: 33.0, skew_y: 0); |
634 | gsk_transform_to_2d (self: transform, out_xx: &xx, out_yx: &yx, out_xy: &xy, out_yy: &yy, out_dx: &dx, out_dy: &dy); |
635 | gsk_transform_unref (self: transform); |
636 | |
637 | tx = tanf (DEG_TO_RAD (33.0)); |
638 | |
639 | g_assert_cmpfloat (xx, ==, 1); |
640 | g_assert_cmpfloat (yx, ==, 0); |
641 | g_assert_cmpfloat (xy, ==, tx); |
642 | g_assert_cmpfloat (yy, ==, 1); |
643 | g_assert_cmpfloat (dx, ==, 0.0); |
644 | g_assert_cmpfloat (dy, ==, 0.0); |
645 | |
646 | transform = gsk_transform_skew (NULL, skew_x: 0, skew_y: 66.0); |
647 | gsk_transform_to_2d (self: transform, out_xx: &xx, out_yx: &yx, out_xy: &xy, out_yy: &yy, out_dx: &dx, out_dy: &dy); |
648 | gsk_transform_unref (self: transform); |
649 | |
650 | ty = tanf (DEG_TO_RAD (66.0)); |
651 | |
652 | g_assert_cmpfloat (xx, ==, 1); |
653 | g_assert_cmpfloat (yx, ==, ty); |
654 | g_assert_cmpfloat (xy, ==, 0); |
655 | g_assert_cmpfloat (yy, ==, 1); |
656 | g_assert_cmpfloat (dx, ==, 0.0); |
657 | g_assert_cmpfloat (dy, ==, 0.0); |
658 | } |
659 | |
660 | static void |
661 | test_to_2d_components (void) |
662 | { |
663 | GskTransform *transform, *transform2; |
664 | float skew_x, skew_y, scale_x, scale_y, angle, dx, dy; |
665 | graphene_matrix_t m, m2; |
666 | |
667 | transform = gsk_transform_scale ( |
668 | next: gsk_transform_rotate ( |
669 | next: gsk_transform_translate (NULL, point: &GRAPHENE_POINT_INIT (10, 20)), |
670 | angle: 22), |
671 | factor_x: 3, factor_y: 3); |
672 | gsk_transform_to_2d_components (self: transform, |
673 | out_skew_x: &skew_x, out_skew_y: &skew_y, |
674 | out_scale_x: &scale_x, out_scale_y: &scale_y, |
675 | out_angle: &angle, |
676 | out_dx: &dx, out_dy: &dy); |
677 | g_assert_cmpfloat_with_epsilon (skew_x, 0, 0.0001); |
678 | g_assert_cmpfloat_with_epsilon (skew_y, 0, 0.0001); |
679 | g_assert_cmpfloat_with_epsilon (scale_x, 3, 0.0001); |
680 | g_assert_cmpfloat_with_epsilon (scale_y, 3, 0.0001); |
681 | g_assert_cmpfloat_with_epsilon (angle, 22, 0.0001); |
682 | g_assert_cmpfloat_with_epsilon (dx, 10, 0.0001); |
683 | g_assert_cmpfloat_with_epsilon (dy, 20, 0.0001); |
684 | |
685 | gsk_transform_unref (self: transform); |
686 | |
687 | transform = gsk_transform_skew ( |
688 | next: gsk_transform_scale ( |
689 | next: gsk_transform_rotate ( |
690 | next: gsk_transform_translate (NULL, point: &GRAPHENE_POINT_INIT (10, 20)), |
691 | angle: 22), |
692 | factor_x: 3, factor_y: 6), |
693 | skew_x: 33, skew_y: 0); |
694 | |
695 | g_assert_true (gsk_transform_get_category (transform) >= GSK_TRANSFORM_CATEGORY_2D); |
696 | |
697 | gsk_transform_to_2d_components (self: transform, |
698 | out_skew_x: &skew_x, out_skew_y: &skew_y, |
699 | out_scale_x: &scale_x, out_scale_y: &scale_y, |
700 | out_angle: &angle, |
701 | out_dx: &dx, out_dy: &dy); |
702 | |
703 | transform2 = gsk_transform_skew ( |
704 | next: gsk_transform_scale ( |
705 | next: gsk_transform_rotate ( |
706 | next: gsk_transform_translate (NULL, point: &GRAPHENE_POINT_INIT (dx, dy)), |
707 | angle), |
708 | factor_x: scale_x, factor_y: scale_y), |
709 | skew_x, skew_y); |
710 | |
711 | gsk_transform_to_matrix (self: transform, out_matrix: &m); |
712 | gsk_transform_to_matrix (self: transform2, out_matrix: &m2); |
713 | g_assert_true (graphene_matrix_near (&m, &m2, 0.001)); |
714 | |
715 | gsk_transform_unref (self: transform); |
716 | gsk_transform_unref (self: transform2); |
717 | } |
718 | |
719 | static void |
720 | test_transform_point (void) |
721 | { |
722 | GskTransform *t, *t2; |
723 | graphene_point_t p; |
724 | |
725 | t = gsk_transform_scale (next: gsk_transform_translate (NULL, point: &GRAPHENE_POINT_INIT (1, 2)), factor_x: 2, factor_y: 2); |
726 | t2 = gsk_transform_translate (next: gsk_transform_scale (NULL, factor_x: 2, factor_y: 2), point: &GRAPHENE_POINT_INIT (1, 2)); |
727 | |
728 | gsk_transform_transform_point (self: t, point: &GRAPHENE_POINT_INIT (1,1), out_point: &p); |
729 | g_assert_true (graphene_point_equal (&p, &GRAPHENE_POINT_INIT (3, 4))); |
730 | |
731 | gsk_transform_transform_point (self: t2, point: &GRAPHENE_POINT_INIT (1,1), out_point: &p); |
732 | g_assert_true (graphene_point_equal (&p, &GRAPHENE_POINT_INIT (4, 6))); |
733 | |
734 | gsk_transform_unref (self: t); |
735 | gsk_transform_unref (self: t2); |
736 | } |
737 | |
738 | int |
739 | main (int argc, |
740 | char *argv[]) |
741 | { |
742 | gtk_test_init (argcp: &argc, argvp: &argv, NULL); |
743 | |
744 | g_test_add_func (testpath: "/transform/conversions/simple" , test_func: test_conversions_simple); |
745 | g_test_add_func (testpath: "/transform/conversions/transformed" , test_func: test_conversions_transformed); |
746 | g_test_add_func (testpath: "/transform/identity" , test_func: test_identity); |
747 | g_test_add_func (testpath: "/transform/identity-equal" , test_func: test_identity_equal); |
748 | g_test_add_func (testpath: "/transform/invert" , test_func: test_invert); |
749 | g_test_add_func (testpath: "/transform/print-parse" , test_func: test_print_parse); |
750 | g_test_add_func (testpath: "/transform/check-axis-aligneness" , test_func: test_axis_aligned); |
751 | g_test_add_func (testpath: "/transform/to-affine" , test_func: test_to_affine); |
752 | g_test_add_func (testpath: "/transform/bounds" , test_func: test_transform_bounds); |
753 | g_test_add_func (testpath: "/transform/point" , test_func: test_transform_point); |
754 | g_test_add_func (testpath: "/transform/to-2d" , test_func: test_to_2d); |
755 | g_test_add_func (testpath: "/transform/to-2d-components" , test_func: test_to_2d_components); |
756 | |
757 | return g_test_run (); |
758 | } |
759 | |