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 | |
21 | /** |
22 | * GskTransform: (ref-func gsk_transform_ref) (unref-func gsk_transform_unref) |
23 | * |
24 | * `GskTransform` is an object to describe transform matrices. |
25 | * |
26 | * Unlike `graphene_matrix_t`, `GskTransform` retains the steps in how |
27 | * a transform was constructed, and allows inspecting them. It is modeled |
28 | * after the way CSS describes transforms. |
29 | * |
30 | * `GskTransform` objects are immutable and cannot be changed after creation. |
31 | * This means code can safely expose them as properties of objects without |
32 | * having to worry about others changing them. |
33 | */ |
34 | |
35 | #include "config.h" |
36 | |
37 | #include "gsktransformprivate.h" |
38 | |
39 | /* {{{ Boilerplate */ |
40 | |
41 | struct _GskTransformClass |
42 | { |
43 | gsize struct_size; |
44 | const char *type_name; |
45 | |
46 | void (* finalize) (GskTransform *transform); |
47 | void (* to_matrix) (GskTransform *transform, |
48 | graphene_matrix_t *out_matrix); |
49 | void (* apply_2d) (GskTransform *transform, |
50 | float *out_xx, |
51 | float *out_yx, |
52 | float *out_xy, |
53 | float *out_yy, |
54 | float *out_dx, |
55 | float *out_dy); |
56 | void (* apply_affine) (GskTransform *transform, |
57 | float *out_scale_x, |
58 | float *out_scale_y, |
59 | float *out_dx, |
60 | float *out_dy); |
61 | void (* apply_translate) (GskTransform *transform, |
62 | float *out_dx, |
63 | float *out_dy); |
64 | void (* print) (GskTransform *transform, |
65 | GString *string); |
66 | GskTransform * (* apply) (GskTransform *transform, |
67 | GskTransform *apply_to); |
68 | GskTransform * (* invert) (GskTransform *transform, |
69 | GskTransform *next); |
70 | /* both matrices have the same type */ |
71 | gboolean (* equal) (GskTransform *first_transform, |
72 | GskTransform *second_transform); |
73 | }; |
74 | |
75 | |
76 | G_DEFINE_BOXED_TYPE (GskTransform, gsk_transform, |
77 | gsk_transform_ref, |
78 | gsk_transform_unref) |
79 | |
80 | static gboolean |
81 | gsk_transform_is_identity (GskTransform *self); |
82 | static GskTransform * |
83 | gsk_transform_matrix_with_category (GskTransform *next, |
84 | const graphene_matrix_t*matrix, |
85 | GskTransformCategory category); |
86 | |
87 | static inline gboolean |
88 | gsk_transform_has_class (GskTransform *self, |
89 | const GskTransformClass *transform_class) |
90 | { |
91 | return self != NULL && self->transform_class == transform_class; |
92 | } |
93 | |
94 | /*< private > |
95 | * gsk_transform_alloc: |
96 | * @transform_class: class structure for this self |
97 | * @category: The category of this transform. Will be used to initialize |
98 | * the result's category together with &next's category |
99 | * @next: (transfer full) (nullable): Next transform to multiply with |
100 | * |
101 | * Returns: (transfer full): the newly created `GskTransform` |
102 | */ |
103 | static gpointer |
104 | gsk_transform_alloc (const GskTransformClass *transform_class, |
105 | GskTransformCategory category, |
106 | GskTransform *next) |
107 | { |
108 | GskTransform *self; |
109 | |
110 | g_return_val_if_fail (transform_class != NULL, NULL); |
111 | |
112 | self = g_atomic_rc_box_alloc0 (block_size: transform_class->struct_size); |
113 | |
114 | self->transform_class = transform_class; |
115 | self->category = next ? MIN (category, next->category) : category; |
116 | if (gsk_transform_is_identity (self: next)) |
117 | gsk_transform_unref (self: next); |
118 | else |
119 | self->next = next; |
120 | |
121 | return self; |
122 | } |
123 | |
124 | static void |
125 | gsk_transform_finalize (GskTransform *self) |
126 | { |
127 | self->transform_class->finalize (self); |
128 | |
129 | gsk_transform_unref (self: self->next); |
130 | } |
131 | |
132 | /* }}} */ |
133 | /* {{{ IDENTITY */ |
134 | |
135 | static void |
136 | gsk_identity_transform_finalize (GskTransform *transform) |
137 | { |
138 | } |
139 | |
140 | static void |
141 | gsk_identity_transform_to_matrix (GskTransform *transform, |
142 | graphene_matrix_t *out_matrix) |
143 | { |
144 | graphene_matrix_init_identity (m: out_matrix); |
145 | } |
146 | |
147 | static void |
148 | gsk_identity_transform_apply_2d (GskTransform *transform, |
149 | float *out_xx, |
150 | float *out_yx, |
151 | float *out_xy, |
152 | float *out_yy, |
153 | float *out_dx, |
154 | float *out_dy) |
155 | { |
156 | } |
157 | |
158 | static void |
159 | gsk_identity_transform_apply_affine (GskTransform *transform, |
160 | float *out_scale_x, |
161 | float *out_scale_y, |
162 | float *out_dx, |
163 | float *out_dy) |
164 | { |
165 | } |
166 | |
167 | static void |
168 | gsk_identity_transform_apply_translate (GskTransform *transform, |
169 | float *out_dx, |
170 | float *out_dy) |
171 | { |
172 | } |
173 | |
174 | static void |
175 | gsk_identity_transform_print (GskTransform *transform, |
176 | GString *string) |
177 | { |
178 | g_string_append (string, val: "none" ); |
179 | } |
180 | |
181 | static GskTransform * |
182 | gsk_identity_transform_apply (GskTransform *transform, |
183 | GskTransform *apply_to) |
184 | { |
185 | /* We do the following to make sure inverting a non-NULL transform |
186 | * will return a non-NULL transform. |
187 | */ |
188 | if (apply_to) |
189 | return apply_to; |
190 | else |
191 | return gsk_transform_new (); |
192 | } |
193 | |
194 | static GskTransform * |
195 | gsk_identity_transform_invert (GskTransform *transform, |
196 | GskTransform *next) |
197 | { |
198 | /* We do the following to make sure inverting a non-NULL transform |
199 | * will return a non-NULL transform. |
200 | */ |
201 | if (next) |
202 | return next; |
203 | else |
204 | return gsk_transform_new (); |
205 | } |
206 | |
207 | static gboolean |
208 | gsk_identity_transform_equal (GskTransform *first_transform, |
209 | GskTransform *second_transform) |
210 | { |
211 | return TRUE; |
212 | } |
213 | |
214 | static const GskTransformClass GSK_IDENTITY_TRANSFORM_CLASS = |
215 | { |
216 | sizeof (GskTransform), |
217 | "GskIdentityTransform" , |
218 | gsk_identity_transform_finalize, |
219 | gsk_identity_transform_to_matrix, |
220 | gsk_identity_transform_apply_2d, |
221 | gsk_identity_transform_apply_affine, |
222 | gsk_identity_transform_apply_translate, |
223 | gsk_identity_transform_print, |
224 | gsk_identity_transform_apply, |
225 | gsk_identity_transform_invert, |
226 | gsk_identity_transform_equal, |
227 | }; |
228 | |
229 | /*<private> |
230 | * gsk_transform_is_identity: |
231 | * @transform: (nullable): A transform |
232 | * |
233 | * Checks if the transform is a representation of the identity |
234 | * transform. |
235 | * |
236 | * This is different from a transform like `scale(2) scale(0.5)` |
237 | * which just results in an identity transform when simplified. |
238 | * |
239 | * Returns: %TRUE if this transform is a representation of |
240 | * the identity transform |
241 | **/ |
242 | static gboolean |
243 | gsk_transform_is_identity (GskTransform *self) |
244 | { |
245 | return self == NULL || |
246 | (self->transform_class == &GSK_IDENTITY_TRANSFORM_CLASS && gsk_transform_is_identity (self: self->next)); |
247 | } |
248 | |
249 | /* }}} */ |
250 | /* {{{ MATRIX */ |
251 | |
252 | typedef struct _GskMatrixTransform GskMatrixTransform; |
253 | |
254 | struct _GskMatrixTransform |
255 | { |
256 | GskTransform parent; |
257 | |
258 | graphene_matrix_t matrix; |
259 | }; |
260 | |
261 | static void |
262 | gsk_matrix_transform_finalize (GskTransform *self) |
263 | { |
264 | } |
265 | |
266 | static void |
267 | gsk_matrix_transform_to_matrix (GskTransform *transform, |
268 | graphene_matrix_t *out_matrix) |
269 | { |
270 | GskMatrixTransform *self = (GskMatrixTransform *) transform; |
271 | |
272 | graphene_matrix_init_from_matrix (m: out_matrix, src: &self->matrix); |
273 | } |
274 | |
275 | static void |
276 | gsk_matrix_transform_apply_2d (GskTransform *transform, |
277 | float *out_xx, |
278 | float *out_yx, |
279 | float *out_xy, |
280 | float *out_yy, |
281 | float *out_dx, |
282 | float *out_dy) |
283 | { |
284 | GskMatrixTransform *self = (GskMatrixTransform *) transform; |
285 | graphene_matrix_t mat; |
286 | |
287 | graphene_matrix_init_from_2d (m: &mat, |
288 | xx: *out_xx, yx: *out_yx, |
289 | xy: *out_xy, yy: *out_yy, |
290 | x_0: *out_dx, y_0: *out_dy); |
291 | graphene_matrix_multiply (a: &self->matrix, b: &mat, res: &mat); |
292 | |
293 | /* not using graphene_matrix_to_2d() because it may |
294 | * fail the is_2d() check due to improper rounding */ |
295 | *out_xx = graphene_matrix_get_value (m: &mat, row: 0, col: 0); |
296 | *out_yx = graphene_matrix_get_value (m: &mat, row: 0, col: 1); |
297 | *out_xy = graphene_matrix_get_value (m: &mat, row: 1, col: 0); |
298 | *out_yy = graphene_matrix_get_value (m: &mat, row: 1, col: 1); |
299 | *out_dx = graphene_matrix_get_value (m: &mat, row: 3, col: 0); |
300 | *out_dy = graphene_matrix_get_value (m: &mat, row: 3, col: 1); |
301 | } |
302 | |
303 | static void |
304 | gsk_matrix_transform_apply_affine (GskTransform *transform, |
305 | float *out_scale_x, |
306 | float *out_scale_y, |
307 | float *out_dx, |
308 | float *out_dy) |
309 | { |
310 | GskMatrixTransform *self = (GskMatrixTransform *) transform; |
311 | |
312 | switch (transform->category) |
313 | { |
314 | case GSK_TRANSFORM_CATEGORY_UNKNOWN: |
315 | case GSK_TRANSFORM_CATEGORY_ANY: |
316 | case GSK_TRANSFORM_CATEGORY_3D: |
317 | case GSK_TRANSFORM_CATEGORY_2D: |
318 | default: |
319 | g_assert_not_reached (); |
320 | break; |
321 | |
322 | case GSK_TRANSFORM_CATEGORY_2D_AFFINE: |
323 | *out_dx += *out_scale_x * graphene_matrix_get_x_translation (m: &self->matrix); |
324 | *out_dy += *out_scale_y * graphene_matrix_get_y_translation (m: &self->matrix); |
325 | *out_scale_x *= graphene_matrix_get_x_scale (m: &self->matrix); |
326 | *out_scale_y *= graphene_matrix_get_y_scale (m: &self->matrix); |
327 | break; |
328 | |
329 | case GSK_TRANSFORM_CATEGORY_2D_TRANSLATE: |
330 | *out_dx += *out_scale_x * graphene_matrix_get_x_translation (m: &self->matrix); |
331 | *out_dy += *out_scale_y * graphene_matrix_get_y_translation (m: &self->matrix); |
332 | break; |
333 | |
334 | case GSK_TRANSFORM_CATEGORY_IDENTITY: |
335 | break; |
336 | } |
337 | } |
338 | |
339 | static void |
340 | gsk_matrix_transform_apply_translate (GskTransform *transform, |
341 | float *out_dx, |
342 | float *out_dy) |
343 | { |
344 | GskMatrixTransform *self = (GskMatrixTransform *) transform; |
345 | |
346 | switch (transform->category) |
347 | { |
348 | case GSK_TRANSFORM_CATEGORY_UNKNOWN: |
349 | case GSK_TRANSFORM_CATEGORY_ANY: |
350 | case GSK_TRANSFORM_CATEGORY_3D: |
351 | case GSK_TRANSFORM_CATEGORY_2D: |
352 | case GSK_TRANSFORM_CATEGORY_2D_AFFINE: |
353 | default: |
354 | g_assert_not_reached (); |
355 | break; |
356 | |
357 | case GSK_TRANSFORM_CATEGORY_2D_TRANSLATE: |
358 | *out_dx += graphene_matrix_get_x_translation (m: &self->matrix); |
359 | *out_dy += graphene_matrix_get_y_translation (m: &self->matrix); |
360 | break; |
361 | |
362 | case GSK_TRANSFORM_CATEGORY_IDENTITY: |
363 | break; |
364 | } |
365 | } |
366 | |
367 | static void |
368 | string_append_double (GString *string, |
369 | double d) |
370 | { |
371 | char buf[G_ASCII_DTOSTR_BUF_SIZE]; |
372 | |
373 | g_ascii_formatd (buffer: buf, G_ASCII_DTOSTR_BUF_SIZE, format: "%g" , d); |
374 | g_string_append (string, val: buf); |
375 | } |
376 | |
377 | static void |
378 | gsk_matrix_transform_print (GskTransform *transform, |
379 | GString *string) |
380 | { |
381 | GskMatrixTransform *self = (GskMatrixTransform *) transform; |
382 | guint i; |
383 | float f[16]; |
384 | |
385 | g_string_append (string, val: "matrix3d(" ); |
386 | graphene_matrix_to_float (m: &self->matrix, v: f); |
387 | for (i = 0; i < 16; i++) |
388 | { |
389 | if (i > 0) |
390 | g_string_append (string, val: ", " ); |
391 | string_append_double (string, d: f[i]); |
392 | } |
393 | g_string_append (string, val: ")" ); |
394 | } |
395 | |
396 | static GskTransform * |
397 | gsk_matrix_transform_apply (GskTransform *transform, |
398 | GskTransform *apply_to) |
399 | { |
400 | GskMatrixTransform *self = (GskMatrixTransform *) transform; |
401 | |
402 | return gsk_transform_matrix_with_category (next: apply_to, |
403 | matrix: &self->matrix, |
404 | category: transform->category); |
405 | } |
406 | |
407 | static GskTransform * |
408 | gsk_matrix_transform_invert (GskTransform *transform, |
409 | GskTransform *next) |
410 | { |
411 | GskMatrixTransform *self = (GskMatrixTransform *) transform; |
412 | graphene_matrix_t inverse; |
413 | |
414 | if (!graphene_matrix_inverse (m: &self->matrix, res: &inverse)) |
415 | { |
416 | gsk_transform_unref (self: next); |
417 | return NULL; |
418 | } |
419 | |
420 | return gsk_transform_matrix_with_category (next, |
421 | matrix: &inverse, |
422 | category: transform->category); |
423 | } |
424 | |
425 | static gboolean |
426 | gsk_matrix_transform_equal (GskTransform *first_transform, |
427 | GskTransform *second_transform) |
428 | { |
429 | GskMatrixTransform *first = (GskMatrixTransform *) first_transform; |
430 | GskMatrixTransform *second = (GskMatrixTransform *) second_transform; |
431 | |
432 | if (graphene_matrix_equal_fast (a: &first->matrix, b: &second->matrix)) |
433 | return TRUE; |
434 | |
435 | return graphene_matrix_equal (a: &first->matrix, b: &second->matrix); |
436 | } |
437 | |
438 | static const GskTransformClass GSK_TRANSFORM_TRANSFORM_CLASS = |
439 | { |
440 | sizeof (GskMatrixTransform), |
441 | "GskMatrixTransform" , |
442 | gsk_matrix_transform_finalize, |
443 | gsk_matrix_transform_to_matrix, |
444 | gsk_matrix_transform_apply_2d, |
445 | gsk_matrix_transform_apply_affine, |
446 | gsk_matrix_transform_apply_translate, |
447 | gsk_matrix_transform_print, |
448 | gsk_matrix_transform_apply, |
449 | gsk_matrix_transform_invert, |
450 | gsk_matrix_transform_equal, |
451 | }; |
452 | |
453 | static GskTransform * |
454 | gsk_transform_matrix_with_category (GskTransform *next, |
455 | const graphene_matrix_t *matrix, |
456 | GskTransformCategory category) |
457 | { |
458 | GskMatrixTransform *result = gsk_transform_alloc (transform_class: &GSK_TRANSFORM_TRANSFORM_CLASS, category, next); |
459 | |
460 | graphene_matrix_init_from_matrix (m: &result->matrix, src: matrix); |
461 | |
462 | return &result->parent; |
463 | } |
464 | |
465 | /** |
466 | * gsk_transform_matrix: |
467 | * @next: (nullable) (transfer full): the next transform |
468 | * @matrix: the matrix to multiply @next with |
469 | * |
470 | * Multiplies @next with the given @matrix. |
471 | * |
472 | * Returns: The new transform |
473 | **/ |
474 | GskTransform * |
475 | gsk_transform_matrix (GskTransform *next, |
476 | const graphene_matrix_t *matrix) |
477 | { |
478 | return gsk_transform_matrix_with_category (next, matrix, category: GSK_TRANSFORM_CATEGORY_UNKNOWN); |
479 | } |
480 | |
481 | /* }}} */ |
482 | /* {{{ TRANSLATE */ |
483 | |
484 | typedef struct _GskTranslateTransform GskTranslateTransform; |
485 | |
486 | struct _GskTranslateTransform |
487 | { |
488 | GskTransform parent; |
489 | |
490 | graphene_point3d_t point; |
491 | }; |
492 | |
493 | static void |
494 | gsk_translate_transform_finalize (GskTransform *self) |
495 | { |
496 | } |
497 | |
498 | static void |
499 | gsk_translate_transform_to_matrix (GskTransform *transform, |
500 | graphene_matrix_t *out_matrix) |
501 | { |
502 | GskTranslateTransform *self = (GskTranslateTransform *) transform; |
503 | |
504 | graphene_matrix_init_translate (m: out_matrix, p: &self->point); |
505 | } |
506 | |
507 | static void |
508 | gsk_translate_transform_apply_2d (GskTransform *transform, |
509 | float *out_xx, |
510 | float *out_yx, |
511 | float *out_xy, |
512 | float *out_yy, |
513 | float *out_dx, |
514 | float *out_dy) |
515 | { |
516 | GskTranslateTransform *self = (GskTranslateTransform *) transform; |
517 | |
518 | g_assert (self->point.z == 0.0); |
519 | |
520 | *out_dx += *out_xx * self->point.x + *out_xy * self->point.y; |
521 | *out_dy += *out_yx * self->point.x + *out_yy * self->point.y; |
522 | } |
523 | |
524 | static void |
525 | gsk_translate_transform_apply_affine (GskTransform *transform, |
526 | float *out_scale_x, |
527 | float *out_scale_y, |
528 | float *out_dx, |
529 | float *out_dy) |
530 | { |
531 | GskTranslateTransform *self = (GskTranslateTransform *) transform; |
532 | |
533 | g_assert (self->point.z == 0.0); |
534 | |
535 | *out_dx += *out_scale_x * self->point.x; |
536 | *out_dy += *out_scale_y * self->point.y; |
537 | } |
538 | |
539 | static void |
540 | gsk_translate_transform_apply_translate (GskTransform *transform, |
541 | float *out_dx, |
542 | float *out_dy) |
543 | { |
544 | GskTranslateTransform *self = (GskTranslateTransform *) transform; |
545 | |
546 | g_assert (self->point.z == 0.0); |
547 | |
548 | *out_dx += self->point.x; |
549 | *out_dy += self->point.y; |
550 | } |
551 | |
552 | static GskTransform * |
553 | gsk_translate_transform_apply (GskTransform *transform, |
554 | GskTransform *apply_to) |
555 | { |
556 | GskTranslateTransform *self = (GskTranslateTransform *) transform; |
557 | |
558 | return gsk_transform_translate_3d (next: apply_to, point: &self->point); |
559 | } |
560 | |
561 | static GskTransform * |
562 | gsk_translate_transform_invert (GskTransform *transform, |
563 | GskTransform *next) |
564 | { |
565 | GskTranslateTransform *self = (GskTranslateTransform *) transform; |
566 | |
567 | return gsk_transform_translate_3d (next, point: &GRAPHENE_POINT3D_INIT (-self->point.x, -self->point.y, -self->point.z)); |
568 | } |
569 | |
570 | static gboolean |
571 | gsk_translate_transform_equal (GskTransform *first_transform, |
572 | GskTransform *second_transform) |
573 | { |
574 | GskTranslateTransform *first = (GskTranslateTransform *) first_transform; |
575 | GskTranslateTransform *second = (GskTranslateTransform *) second_transform; |
576 | |
577 | return G_APPROX_VALUE (first->point.x, second->point.x, FLT_EPSILON) && |
578 | G_APPROX_VALUE (first->point.y, second->point.y, FLT_EPSILON) && |
579 | G_APPROX_VALUE (first->point.z, second->point.z, FLT_EPSILON); |
580 | } |
581 | |
582 | static void |
583 | gsk_translate_transform_print (GskTransform *transform, |
584 | GString *string) |
585 | { |
586 | GskTranslateTransform *self = (GskTranslateTransform *) transform; |
587 | |
588 | if (self->point.z == 0) |
589 | g_string_append (string, val: "translate(" ); |
590 | else |
591 | g_string_append (string, val: "translate3d(" ); |
592 | |
593 | string_append_double (string, d: self->point.x); |
594 | g_string_append (string, val: ", " ); |
595 | string_append_double (string, d: self->point.y); |
596 | if (self->point.z != 0) |
597 | { |
598 | g_string_append (string, val: ", " ); |
599 | string_append_double (string, d: self->point.z); |
600 | } |
601 | g_string_append (string, val: ")" ); |
602 | } |
603 | |
604 | static const GskTransformClass GSK_TRANSLATE_TRANSFORM_CLASS = |
605 | { |
606 | sizeof (GskTranslateTransform), |
607 | "GskTranslateTransform" , |
608 | gsk_translate_transform_finalize, |
609 | gsk_translate_transform_to_matrix, |
610 | gsk_translate_transform_apply_2d, |
611 | gsk_translate_transform_apply_affine, |
612 | gsk_translate_transform_apply_translate, |
613 | gsk_translate_transform_print, |
614 | gsk_translate_transform_apply, |
615 | gsk_translate_transform_invert, |
616 | gsk_translate_transform_equal, |
617 | }; |
618 | |
619 | /** |
620 | * gsk_transform_translate: |
621 | * @next: (nullable) (transfer full): the next transform |
622 | * @point: the point to translate the transform by |
623 | * |
624 | * Translates @next in 2-dimensional space by @point. |
625 | * |
626 | * Returns: (nullable): The new transform |
627 | **/ |
628 | GskTransform * |
629 | gsk_transform_translate (GskTransform *next, |
630 | const graphene_point_t *point) |
631 | { |
632 | graphene_point3d_t point3d; |
633 | |
634 | graphene_point3d_init (p: &point3d, x: point->x, y: point->y, z: 0); |
635 | |
636 | return gsk_transform_translate_3d (next, point: &point3d); |
637 | } |
638 | |
639 | /** |
640 | * gsk_transform_translate_3d: |
641 | * @next: (nullable) (transfer full): the next transform |
642 | * @point: the point to translate the transform by |
643 | * |
644 | * Translates @next by @point. |
645 | * |
646 | * Returns: (nullable): The new transform |
647 | **/ |
648 | GskTransform * |
649 | gsk_transform_translate_3d (GskTransform *next, |
650 | const graphene_point3d_t *point) |
651 | { |
652 | GskTranslateTransform *result; |
653 | |
654 | if (graphene_point3d_equal (a: point, b: graphene_point3d_zero ())) |
655 | return next; |
656 | |
657 | if (gsk_transform_has_class (self: next, transform_class: &GSK_TRANSLATE_TRANSFORM_CLASS)) |
658 | { |
659 | GskTranslateTransform *t = (GskTranslateTransform *) next; |
660 | GskTransform *r = gsk_transform_translate_3d (next: gsk_transform_ref (self: next->next), |
661 | point: &GRAPHENE_POINT3D_INIT(t->point.x + point->x, |
662 | t->point.y + point->y, |
663 | t->point.z + point->z)); |
664 | gsk_transform_unref (self: next); |
665 | return r; |
666 | } |
667 | |
668 | result = gsk_transform_alloc (transform_class: &GSK_TRANSLATE_TRANSFORM_CLASS, |
669 | category: point->z == 0.0 ? GSK_TRANSFORM_CATEGORY_2D_TRANSLATE |
670 | : GSK_TRANSFORM_CATEGORY_3D, |
671 | next); |
672 | |
673 | graphene_point3d_init_from_point (p: &result->point, src: point); |
674 | |
675 | return &result->parent; |
676 | } |
677 | |
678 | /* }}} */ |
679 | /* {{{ ROTATE */ |
680 | |
681 | typedef struct _GskRotateTransform GskRotateTransform; |
682 | |
683 | struct _GskRotateTransform |
684 | { |
685 | GskTransform parent; |
686 | |
687 | float angle; |
688 | }; |
689 | |
690 | static void |
691 | gsk_rotate_transform_finalize (GskTransform *self) |
692 | { |
693 | } |
694 | |
695 | static inline void |
696 | _sincos (float deg, |
697 | float *out_s, |
698 | float *out_c) |
699 | { |
700 | if (deg == 90.0) |
701 | { |
702 | *out_c = 0.0; |
703 | *out_s = 1.0; |
704 | } |
705 | else if (deg == 180.0) |
706 | { |
707 | *out_c = -1.0; |
708 | *out_s = 0.0; |
709 | } |
710 | else if (deg == 270.0) |
711 | { |
712 | *out_c = 0.0; |
713 | *out_s = -1.0; |
714 | } |
715 | else if (deg == 0.0) |
716 | { |
717 | *out_c = 1.0; |
718 | *out_s = 0.0; |
719 | } |
720 | else |
721 | { |
722 | float angle = deg * M_PI / 180.0; |
723 | |
724 | #ifdef HAVE_SINCOSF |
725 | sincosf (x: angle, sinx: out_s, cosx: out_c); |
726 | #else |
727 | *out_s = sinf (angle); |
728 | *out_c = cosf (angle); |
729 | #endif |
730 | |
731 | } |
732 | } |
733 | |
734 | static void |
735 | gsk_rotate_transform_to_matrix (GskTransform *transform, |
736 | graphene_matrix_t *out_matrix) |
737 | { |
738 | GskRotateTransform *self = (GskRotateTransform *) transform; |
739 | float c, s; |
740 | |
741 | _sincos (deg: self->angle, out_s: &s, out_c: &c); |
742 | |
743 | graphene_matrix_init_from_2d (m: out_matrix, |
744 | xx: c, yx: s, |
745 | xy: -s, yy: c, |
746 | x_0: 0, y_0: 0); |
747 | } |
748 | |
749 | static void |
750 | gsk_rotate_transform_apply_2d (GskTransform *transform, |
751 | float *out_xx, |
752 | float *out_yx, |
753 | float *out_xy, |
754 | float *out_yy, |
755 | float *out_dx, |
756 | float *out_dy) |
757 | { |
758 | GskRotateTransform *self = (GskRotateTransform *) transform; |
759 | float s, c, xx, xy, yx, yy; |
760 | |
761 | _sincos (deg: self->angle, out_s: &s, out_c: &c); |
762 | |
763 | xx = c * *out_xx + s * *out_xy; |
764 | yx = c * *out_yx + s * *out_yy; |
765 | xy = -s * *out_xx + c * *out_xy; |
766 | yy = -s * *out_yx + c * *out_yy; |
767 | |
768 | *out_xx = xx; |
769 | *out_yx = yx; |
770 | *out_xy = xy; |
771 | *out_yy = yy; |
772 | } |
773 | |
774 | static GskTransform * |
775 | gsk_rotate_transform_apply (GskTransform *transform, |
776 | GskTransform *apply_to) |
777 | { |
778 | GskRotateTransform *self = (GskRotateTransform *) transform; |
779 | |
780 | return gsk_transform_rotate (next: apply_to, angle: self->angle); |
781 | } |
782 | |
783 | static GskTransform * |
784 | gsk_rotate_transform_invert (GskTransform *transform, |
785 | GskTransform *next) |
786 | { |
787 | GskRotateTransform *self = (GskRotateTransform *) transform; |
788 | |
789 | return gsk_transform_rotate (next, angle: - self->angle); |
790 | } |
791 | |
792 | static gboolean |
793 | gsk_rotate_transform_equal (GskTransform *first_transform, |
794 | GskTransform *second_transform) |
795 | { |
796 | GskRotateTransform *first = (GskRotateTransform *) first_transform; |
797 | GskRotateTransform *second = (GskRotateTransform *) second_transform; |
798 | |
799 | return G_APPROX_VALUE (first->angle, second->angle, 0.01f); |
800 | } |
801 | |
802 | static void |
803 | gsk_rotate_transform_print (GskTransform *transform, |
804 | GString *string) |
805 | { |
806 | GskRotateTransform *self = (GskRotateTransform *) transform; |
807 | |
808 | g_string_append (string, val: "rotate(" ); |
809 | string_append_double (string, d: self->angle); |
810 | g_string_append (string, val: ")" ); |
811 | } |
812 | |
813 | static const GskTransformClass GSK_ROTATE_TRANSFORM_CLASS = |
814 | { |
815 | sizeof (GskRotateTransform), |
816 | "GskRotateTransform" , |
817 | gsk_rotate_transform_finalize, |
818 | gsk_rotate_transform_to_matrix, |
819 | gsk_rotate_transform_apply_2d, |
820 | NULL, |
821 | NULL, |
822 | gsk_rotate_transform_print, |
823 | gsk_rotate_transform_apply, |
824 | gsk_rotate_transform_invert, |
825 | gsk_rotate_transform_equal, |
826 | }; |
827 | |
828 | static inline float |
829 | normalize_angle (float angle) |
830 | { |
831 | |
832 | if (angle >= 0 && angle < 360) |
833 | return angle; |
834 | |
835 | while (angle >= 360) |
836 | angle -= 360; |
837 | while (angle < 0) |
838 | angle += 360; |
839 | |
840 | /* Due to precision issues we may end up with a result that is just |
841 | * past the allowed range when rounded. For example, something like |
842 | * -epsilon + 360 when rounded to a float may end up with 360. |
843 | * So, we handle these cases by returning the exact value 0. |
844 | */ |
845 | |
846 | if (angle >= 360) |
847 | angle = 0; |
848 | |
849 | g_assert (angle < 360.0); |
850 | g_assert (angle >= 0.0); |
851 | |
852 | return angle; |
853 | } |
854 | |
855 | /** |
856 | * gsk_transform_rotate: |
857 | * @next: (nullable) (transfer full): the next transform |
858 | * @angle: the rotation angle, in degrees (clockwise) |
859 | * |
860 | * Rotates @next @angle degrees in 2D - or in 3D-speak, around the z axis. |
861 | * |
862 | * Returns: (nullable): The new transform |
863 | */ |
864 | GskTransform * |
865 | gsk_transform_rotate (GskTransform *next, |
866 | float angle) |
867 | { |
868 | GskRotateTransform *result; |
869 | |
870 | if (angle == 0.0f) |
871 | return next; |
872 | |
873 | if (gsk_transform_has_class (self: next, transform_class: &GSK_ROTATE_TRANSFORM_CLASS)) |
874 | { |
875 | GskTransform *r = gsk_transform_rotate (next: gsk_transform_ref (self: next->next), |
876 | angle: ((GskRotateTransform *) next)->angle + angle); |
877 | gsk_transform_unref (self: next); |
878 | return r; |
879 | } |
880 | |
881 | result = gsk_transform_alloc (transform_class: &GSK_ROTATE_TRANSFORM_CLASS, |
882 | category: GSK_TRANSFORM_CATEGORY_2D, |
883 | next); |
884 | |
885 | result->angle = normalize_angle (angle); |
886 | |
887 | return &result->parent; |
888 | } |
889 | |
890 | /* }}} */ |
891 | /* {{{ ROTATE 3D */ |
892 | |
893 | typedef struct _GskRotate3dTransform GskRotate3dTransform; |
894 | |
895 | struct _GskRotate3dTransform |
896 | { |
897 | GskTransform parent; |
898 | |
899 | float angle; |
900 | graphene_vec3_t axis; |
901 | }; |
902 | |
903 | static void |
904 | gsk_rotate3d_transform_finalize (GskTransform *self) |
905 | { |
906 | } |
907 | |
908 | static void |
909 | gsk_rotate3d_transform_to_matrix (GskTransform *transform, |
910 | graphene_matrix_t *out_matrix) |
911 | { |
912 | GskRotate3dTransform *self = (GskRotate3dTransform *) transform; |
913 | |
914 | graphene_matrix_init_rotate (m: out_matrix, angle: self->angle, axis: &self->axis); |
915 | } |
916 | |
917 | static GskTransform * |
918 | gsk_rotate3d_transform_apply (GskTransform *transform, |
919 | GskTransform *apply_to) |
920 | { |
921 | GskRotate3dTransform *self = (GskRotate3dTransform *) transform; |
922 | |
923 | return gsk_transform_rotate_3d (next: apply_to, angle: self->angle, axis: &self->axis); |
924 | } |
925 | |
926 | static GskTransform * |
927 | gsk_rotate3d_transform_invert (GskTransform *transform, |
928 | GskTransform *next) |
929 | { |
930 | GskRotate3dTransform *self = (GskRotate3dTransform *) transform; |
931 | |
932 | return gsk_transform_rotate_3d (next, angle: - self->angle, axis: &self->axis); |
933 | } |
934 | |
935 | static gboolean |
936 | gsk_rotate3d_transform_equal (GskTransform *first_transform, |
937 | GskTransform *second_transform) |
938 | { |
939 | GskRotate3dTransform *first = (GskRotate3dTransform *) first_transform; |
940 | GskRotate3dTransform *second = (GskRotate3dTransform *) second_transform; |
941 | |
942 | return G_APPROX_VALUE (first->angle, second->angle, 0.01f) && |
943 | graphene_vec3_equal (v1: &first->axis, v2: &second->axis); |
944 | } |
945 | |
946 | static void |
947 | gsk_rotate3d_transform_print (GskTransform *transform, |
948 | GString *string) |
949 | { |
950 | GskRotate3dTransform *self = (GskRotate3dTransform *) transform; |
951 | float f[3]; |
952 | guint i; |
953 | |
954 | g_string_append (string, val: "rotate3d(" ); |
955 | graphene_vec3_to_float (v: &self->axis, dest: f); |
956 | for (i = 0; i < 3; i++) |
957 | { |
958 | string_append_double (string, d: f[i]); |
959 | g_string_append (string, val: ", " ); |
960 | } |
961 | string_append_double (string, d: self->angle); |
962 | g_string_append (string, val: ")" ); |
963 | } |
964 | |
965 | static const GskTransformClass GSK_ROTATE3D_TRANSFORM_CLASS = |
966 | { |
967 | sizeof (GskRotate3dTransform), |
968 | "GskRotate3dTransform" , |
969 | gsk_rotate3d_transform_finalize, |
970 | gsk_rotate3d_transform_to_matrix, |
971 | NULL, |
972 | NULL, |
973 | NULL, |
974 | gsk_rotate3d_transform_print, |
975 | gsk_rotate3d_transform_apply, |
976 | gsk_rotate3d_transform_invert, |
977 | gsk_rotate3d_transform_equal, |
978 | }; |
979 | |
980 | /** |
981 | * gsk_transform_rotate_3d: |
982 | * @next: (nullable) (transfer full): the next transform |
983 | * @angle: the rotation angle, in degrees (clockwise) |
984 | * @axis: The rotation axis |
985 | * |
986 | * Rotates @next @angle degrees around @axis. |
987 | * |
988 | * For a rotation in 2D space, use [method@Gsk.Transform.rotate] |
989 | * |
990 | * Returns: (nullable): The new transform |
991 | */ |
992 | GskTransform * |
993 | gsk_transform_rotate_3d (GskTransform *next, |
994 | float angle, |
995 | const graphene_vec3_t *axis) |
996 | { |
997 | GskRotate3dTransform *result; |
998 | |
999 | if (graphene_vec3_get_x (v: axis) == 0.0 && graphene_vec3_get_y (v: axis) == 0.0) |
1000 | return gsk_transform_rotate (next, angle); |
1001 | |
1002 | if (angle == 0.0f) |
1003 | return next; |
1004 | |
1005 | result = gsk_transform_alloc (transform_class: &GSK_ROTATE3D_TRANSFORM_CLASS, |
1006 | category: GSK_TRANSFORM_CATEGORY_3D, |
1007 | next); |
1008 | |
1009 | result->angle = normalize_angle (angle); |
1010 | graphene_vec3_init_from_vec3 (v: &result->axis, src: axis); |
1011 | |
1012 | return &result->parent; |
1013 | } |
1014 | |
1015 | /* }}} */ |
1016 | /* {{{ SKEW */ |
1017 | |
1018 | typedef struct _GskSkewTransform GskSkewTransform; |
1019 | |
1020 | struct _GskSkewTransform |
1021 | { |
1022 | GskTransform parent; |
1023 | |
1024 | float skew_x; |
1025 | float skew_y; |
1026 | }; |
1027 | |
1028 | static void |
1029 | gsk_skew_transform_finalize (GskTransform *self) |
1030 | { |
1031 | } |
1032 | |
1033 | #define DEG_TO_RAD(x) ((x) / 180.f * G_PI) |
1034 | #define RAD_TO_DEG(x) ((x) * 180.f / G_PI) |
1035 | |
1036 | static void |
1037 | gsk_skew_transform_to_matrix (GskTransform *transform, |
1038 | graphene_matrix_t *out_matrix) |
1039 | { |
1040 | GskSkewTransform *self = (GskSkewTransform *) transform; |
1041 | |
1042 | graphene_matrix_init_skew (m: out_matrix, |
1043 | DEG_TO_RAD (self->skew_x), |
1044 | DEG_TO_RAD (self->skew_y)); |
1045 | } |
1046 | |
1047 | static void |
1048 | gsk_skew_transform_apply_2d (GskTransform *transform, |
1049 | float *out_xx, |
1050 | float *out_yx, |
1051 | float *out_xy, |
1052 | float *out_yy, |
1053 | float *out_dx, |
1054 | float *out_dy) |
1055 | { |
1056 | graphene_matrix_t sm, mat; |
1057 | |
1058 | gsk_skew_transform_to_matrix (transform, out_matrix: &sm); |
1059 | graphene_matrix_init_from_2d (m: &mat, xx: *out_xx, yx: *out_yx, |
1060 | xy: *out_xy, yy: *out_yy, |
1061 | x_0: *out_dx, y_0: *out_dy); |
1062 | |
1063 | graphene_matrix_multiply (a: &sm, b: &mat, res: &mat); |
1064 | |
1065 | *out_xx = graphene_matrix_get_value (m: &mat, row: 0, col: 0); |
1066 | *out_yx = graphene_matrix_get_value (m: &mat, row: 0, col: 1); |
1067 | *out_xy = graphene_matrix_get_value (m: &mat, row: 1, col: 0); |
1068 | *out_yy = graphene_matrix_get_value (m: &mat, row: 1, col: 1); |
1069 | *out_dx = graphene_matrix_get_value (m: &mat, row: 3, col: 0); |
1070 | *out_dy = graphene_matrix_get_value (m: &mat, row: 3, col: 1); |
1071 | } |
1072 | |
1073 | static GskTransform * |
1074 | gsk_skew_transform_apply (GskTransform *transform, |
1075 | GskTransform *apply_to) |
1076 | { |
1077 | GskSkewTransform *self = (GskSkewTransform *) transform; |
1078 | |
1079 | return gsk_transform_skew (next: apply_to, skew_x: self->skew_x, skew_y: self->skew_y); |
1080 | } |
1081 | |
1082 | static void |
1083 | gsk_skew_transform_print (GskTransform *transform, |
1084 | GString *string) |
1085 | { |
1086 | GskSkewTransform *self = (GskSkewTransform *) transform; |
1087 | |
1088 | if (self->skew_y == 0) |
1089 | { |
1090 | g_string_append (string, val: "skewX(" ); |
1091 | string_append_double (string, d: self->skew_x); |
1092 | g_string_append (string, val: ")" ); |
1093 | } |
1094 | else if (self->skew_x == 0) |
1095 | { |
1096 | g_string_append (string, val: "skewY(" ); |
1097 | string_append_double (string, d: self->skew_y); |
1098 | g_string_append (string, val: ")" ); |
1099 | } |
1100 | else |
1101 | { |
1102 | g_string_append (string, val: "skew(" ); |
1103 | string_append_double (string, d: self->skew_x); |
1104 | g_string_append (string, val: ", " ); |
1105 | string_append_double (string, d: self->skew_y); |
1106 | g_string_append (string, val: ")" ); |
1107 | } |
1108 | } |
1109 | |
1110 | static GskTransform * |
1111 | gsk_skew_transform_invert (GskTransform *transform, |
1112 | GskTransform *next) |
1113 | { |
1114 | GskSkewTransform *self = (GskSkewTransform *) transform; |
1115 | float tx, ty; |
1116 | graphene_matrix_t matrix; |
1117 | |
1118 | tx = tanf (DEG_TO_RAD (self->skew_x)); |
1119 | ty = tanf (DEG_TO_RAD (self->skew_y)); |
1120 | |
1121 | graphene_matrix_init_from_2d (m: &matrix, |
1122 | xx: 1 / (1 - tx * ty), |
1123 | yx: - ty / (1 - tx * ty), |
1124 | xy: - tx / (1 - tx * ty), |
1125 | yy: 1 / (1 - tx * ty), |
1126 | x_0: 0, y_0: 0); |
1127 | return gsk_transform_matrix_with_category (next, |
1128 | matrix: &matrix, |
1129 | category: GSK_TRANSFORM_CATEGORY_2D); |
1130 | } |
1131 | |
1132 | static gboolean |
1133 | gsk_skew_transform_equal (GskTransform *first_transform, |
1134 | GskTransform *second_transform) |
1135 | { |
1136 | GskSkewTransform *first = (GskSkewTransform *) first_transform; |
1137 | GskSkewTransform *second = (GskSkewTransform *) second_transform; |
1138 | |
1139 | return G_APPROX_VALUE (first->skew_x, second->skew_x, FLT_EPSILON) && |
1140 | G_APPROX_VALUE (first->skew_y, second->skew_y, FLT_EPSILON); |
1141 | } |
1142 | |
1143 | static const GskTransformClass GSK_SKEW_TRANSFORM_CLASS = |
1144 | { |
1145 | sizeof (GskSkewTransform), |
1146 | "GskSkewTransform" , |
1147 | gsk_skew_transform_finalize, |
1148 | gsk_skew_transform_to_matrix, |
1149 | gsk_skew_transform_apply_2d, |
1150 | NULL, |
1151 | NULL, |
1152 | gsk_skew_transform_print, |
1153 | gsk_skew_transform_apply, |
1154 | gsk_skew_transform_invert, |
1155 | gsk_skew_transform_equal, |
1156 | }; |
1157 | |
1158 | /** |
1159 | * gsk_transform_skew: |
1160 | * @next: (nullable) (transfer full): the next transform |
1161 | * @skew_x: skew factor, in degrees, on the X axis |
1162 | * @skew_y: skew factor, in degrees, on the Y axis |
1163 | * |
1164 | * Applies a skew transform. |
1165 | * |
1166 | * Returns: (nullable): The new transform |
1167 | * |
1168 | * Since: 4.6 |
1169 | */ |
1170 | GskTransform * |
1171 | gsk_transform_skew (GskTransform *next, |
1172 | float skew_x, |
1173 | float skew_y) |
1174 | { |
1175 | GskSkewTransform *result; |
1176 | |
1177 | if (skew_x == 0 && skew_y == 0) |
1178 | return next; |
1179 | |
1180 | result = gsk_transform_alloc (transform_class: &GSK_SKEW_TRANSFORM_CLASS, |
1181 | category: GSK_TRANSFORM_CATEGORY_2D, |
1182 | next); |
1183 | |
1184 | result->skew_x = skew_x; |
1185 | result->skew_y = skew_y; |
1186 | |
1187 | return &result->parent; |
1188 | } |
1189 | /* }}} */ |
1190 | /* {{{ SCALE */ |
1191 | |
1192 | typedef struct _GskScaleTransform GskScaleTransform; |
1193 | |
1194 | struct _GskScaleTransform |
1195 | { |
1196 | GskTransform parent; |
1197 | |
1198 | float factor_x; |
1199 | float factor_y; |
1200 | float factor_z; |
1201 | }; |
1202 | |
1203 | static void |
1204 | gsk_scale_transform_finalize (GskTransform *self) |
1205 | { |
1206 | } |
1207 | |
1208 | static void |
1209 | gsk_scale_transform_to_matrix (GskTransform *transform, |
1210 | graphene_matrix_t *out_matrix) |
1211 | { |
1212 | GskScaleTransform *self = (GskScaleTransform *) transform; |
1213 | |
1214 | graphene_matrix_init_scale (m: out_matrix, x: self->factor_x, y: self->factor_y, z: self->factor_z); |
1215 | } |
1216 | |
1217 | static void |
1218 | gsk_scale_transform_apply_2d (GskTransform *transform, |
1219 | float *out_xx, |
1220 | float *out_yx, |
1221 | float *out_xy, |
1222 | float *out_yy, |
1223 | float *out_dx, |
1224 | float *out_dy) |
1225 | { |
1226 | GskScaleTransform *self = (GskScaleTransform *) transform; |
1227 | |
1228 | g_assert (self->factor_z == 1.0); |
1229 | |
1230 | *out_xx *= self->factor_x; |
1231 | *out_yx *= self->factor_x; |
1232 | *out_xy *= self->factor_y; |
1233 | *out_yy *= self->factor_y; |
1234 | } |
1235 | |
1236 | static void |
1237 | gsk_scale_transform_apply_affine (GskTransform *transform, |
1238 | float *out_scale_x, |
1239 | float *out_scale_y, |
1240 | float *out_dx, |
1241 | float *out_dy) |
1242 | { |
1243 | GskScaleTransform *self = (GskScaleTransform *) transform; |
1244 | |
1245 | g_assert (self->factor_z == 1.0); |
1246 | |
1247 | *out_scale_x *= self->factor_x; |
1248 | *out_scale_y *= self->factor_y; |
1249 | } |
1250 | |
1251 | static GskTransform * |
1252 | gsk_scale_transform_apply (GskTransform *transform, |
1253 | GskTransform *apply_to) |
1254 | { |
1255 | GskScaleTransform *self = (GskScaleTransform *) transform; |
1256 | |
1257 | return gsk_transform_scale_3d (next: apply_to, factor_x: self->factor_x, factor_y: self->factor_y, factor_z: self->factor_z); |
1258 | } |
1259 | |
1260 | static GskTransform * |
1261 | gsk_scale_transform_invert (GskTransform *transform, |
1262 | GskTransform *next) |
1263 | { |
1264 | GskScaleTransform *self = (GskScaleTransform *) transform; |
1265 | |
1266 | return gsk_transform_scale_3d (next, |
1267 | factor_x: 1.f / self->factor_x, |
1268 | factor_y: 1.f / self->factor_y, |
1269 | factor_z: 1.f / self->factor_z); |
1270 | } |
1271 | |
1272 | static gboolean |
1273 | gsk_scale_transform_equal (GskTransform *first_transform, |
1274 | GskTransform *second_transform) |
1275 | { |
1276 | GskScaleTransform *first = (GskScaleTransform *) first_transform; |
1277 | GskScaleTransform *second = (GskScaleTransform *) second_transform; |
1278 | |
1279 | return G_APPROX_VALUE (first->factor_x, second->factor_x, FLT_EPSILON) && |
1280 | G_APPROX_VALUE (first->factor_y, second->factor_y, FLT_EPSILON) && |
1281 | G_APPROX_VALUE (first->factor_z, second->factor_z, FLT_EPSILON); |
1282 | } |
1283 | |
1284 | static void |
1285 | gsk_scale_transform_print (GskTransform *transform, |
1286 | GString *string) |
1287 | { |
1288 | GskScaleTransform *self = (GskScaleTransform *) transform; |
1289 | |
1290 | if (self->factor_z == 1.0) |
1291 | { |
1292 | g_string_append (string, val: "scale(" ); |
1293 | string_append_double (string, d: self->factor_x); |
1294 | if (self->factor_x != self->factor_y) |
1295 | { |
1296 | g_string_append (string, val: ", " ); |
1297 | string_append_double (string, d: self->factor_y); |
1298 | } |
1299 | g_string_append (string, val: ")" ); |
1300 | } |
1301 | else |
1302 | { |
1303 | g_string_append (string, val: "scale3d(" ); |
1304 | string_append_double (string, d: self->factor_x); |
1305 | g_string_append (string, val: ", " ); |
1306 | string_append_double (string, d: self->factor_y); |
1307 | g_string_append (string, val: ", " ); |
1308 | string_append_double (string, d: self->factor_z); |
1309 | g_string_append (string, val: ")" ); |
1310 | } |
1311 | } |
1312 | |
1313 | static const GskTransformClass GSK_SCALE_TRANSFORM_CLASS = |
1314 | { |
1315 | sizeof (GskScaleTransform), |
1316 | "GskScaleTransform" , |
1317 | gsk_scale_transform_finalize, |
1318 | gsk_scale_transform_to_matrix, |
1319 | gsk_scale_transform_apply_2d, |
1320 | gsk_scale_transform_apply_affine, |
1321 | NULL, |
1322 | gsk_scale_transform_print, |
1323 | gsk_scale_transform_apply, |
1324 | gsk_scale_transform_invert, |
1325 | gsk_scale_transform_equal, |
1326 | }; |
1327 | |
1328 | /** |
1329 | * gsk_transform_scale: |
1330 | * @next: (nullable) (transfer full): the next transform |
1331 | * @factor_x: scaling factor on the X axis |
1332 | * @factor_y: scaling factor on the Y axis |
1333 | * |
1334 | * Scales @next in 2-dimensional space by the given factors. |
1335 | * |
1336 | * Use [method@Gsk.Transform.scale_3d] to scale in all 3 dimensions. |
1337 | * |
1338 | * Returns: (nullable): The new transform |
1339 | **/ |
1340 | GskTransform * |
1341 | gsk_transform_scale (GskTransform *next, |
1342 | float factor_x, |
1343 | float factor_y) |
1344 | { |
1345 | return gsk_transform_scale_3d (next, factor_x, factor_y, factor_z: 1.0); |
1346 | } |
1347 | |
1348 | /** |
1349 | * gsk_transform_scale_3d: |
1350 | * @next: (nullable) (transfer full): the next transform |
1351 | * @factor_x: scaling factor on the X axis |
1352 | * @factor_y: scaling factor on the Y axis |
1353 | * @factor_z: scaling factor on the Z axis |
1354 | * |
1355 | * Scales @next by the given factors. |
1356 | * |
1357 | * Returns: (nullable): The new transform |
1358 | **/ |
1359 | GskTransform * |
1360 | gsk_transform_scale_3d (GskTransform *next, |
1361 | float factor_x, |
1362 | float factor_y, |
1363 | float factor_z) |
1364 | { |
1365 | GskScaleTransform *result; |
1366 | |
1367 | if (factor_x == 1 && factor_y == 1 && factor_z == 1) |
1368 | return next; |
1369 | |
1370 | if (gsk_transform_has_class (self: next, transform_class: &GSK_SCALE_TRANSFORM_CLASS)) |
1371 | { |
1372 | GskScaleTransform *scale = (GskScaleTransform *) next; |
1373 | GskTransform *r = gsk_transform_scale_3d (next: gsk_transform_ref (self: next->next), |
1374 | factor_x: scale->factor_x * factor_x, |
1375 | factor_y: scale->factor_y * factor_y, |
1376 | factor_z: scale->factor_z * factor_z); |
1377 | gsk_transform_unref (self: next); |
1378 | return r; |
1379 | } |
1380 | |
1381 | result = gsk_transform_alloc (transform_class: &GSK_SCALE_TRANSFORM_CLASS, |
1382 | category: factor_z != 1.0 ? GSK_TRANSFORM_CATEGORY_3D |
1383 | : GSK_TRANSFORM_CATEGORY_2D_AFFINE, |
1384 | next); |
1385 | |
1386 | result->factor_x = factor_x; |
1387 | result->factor_y = factor_y; |
1388 | result->factor_z = factor_z; |
1389 | |
1390 | return &result->parent; |
1391 | } |
1392 | |
1393 | /* }}} */ |
1394 | /* {{{ PERSPECTIVE */ |
1395 | |
1396 | typedef struct _GskPerspectiveTransform GskPerspectiveTransform; |
1397 | |
1398 | struct _GskPerspectiveTransform |
1399 | { |
1400 | GskTransform parent; |
1401 | |
1402 | float depth; |
1403 | }; |
1404 | |
1405 | static void |
1406 | gsk_perspective_transform_finalize (GskTransform *self) |
1407 | { |
1408 | } |
1409 | |
1410 | static void |
1411 | gsk_perspective_transform_to_matrix (GskTransform *transform, |
1412 | graphene_matrix_t *out_matrix) |
1413 | { |
1414 | GskPerspectiveTransform *self = (GskPerspectiveTransform *) transform; |
1415 | float f[16] = { 1.f, 0.f, 0.f, 0.f, |
1416 | 0.f, 1.f, 0.f, 0.f, |
1417 | 0.f, 0.f, 1.f, self->depth ? -1.f / self->depth : 0.f, |
1418 | 0.f, 0.f, 0.f, 1.f }; |
1419 | |
1420 | graphene_matrix_init_from_float (m: out_matrix, v: f); |
1421 | } |
1422 | |
1423 | |
1424 | static GskTransform * |
1425 | gsk_perspective_transform_apply (GskTransform *transform, |
1426 | GskTransform *apply_to) |
1427 | { |
1428 | GskPerspectiveTransform *self = (GskPerspectiveTransform *) transform; |
1429 | |
1430 | return gsk_transform_perspective (next: apply_to, depth: self->depth); |
1431 | } |
1432 | |
1433 | static GskTransform * |
1434 | gsk_perspective_transform_invert (GskTransform *transform, |
1435 | GskTransform *next) |
1436 | { |
1437 | GskPerspectiveTransform *self = (GskPerspectiveTransform *) transform; |
1438 | |
1439 | return gsk_transform_perspective (next, depth: - self->depth); |
1440 | } |
1441 | |
1442 | static gboolean |
1443 | gsk_perspective_transform_equal (GskTransform *first_transform, |
1444 | GskTransform *second_transform) |
1445 | { |
1446 | GskPerspectiveTransform *first = (GskPerspectiveTransform *) first_transform; |
1447 | GskPerspectiveTransform *second = (GskPerspectiveTransform *) second_transform; |
1448 | |
1449 | return G_APPROX_VALUE (first->depth, second->depth, 0.001f); |
1450 | } |
1451 | |
1452 | static void |
1453 | gsk_perspective_transform_print (GskTransform *transform, |
1454 | GString *string) |
1455 | { |
1456 | GskPerspectiveTransform *self = (GskPerspectiveTransform *) transform; |
1457 | |
1458 | g_string_append (string, val: "perspective(" ); |
1459 | string_append_double (string, d: self->depth); |
1460 | g_string_append (string, val: ")" ); |
1461 | } |
1462 | |
1463 | static const GskTransformClass GSK_PERSPECTIVE_TRANSFORM_CLASS = |
1464 | { |
1465 | sizeof (GskPerspectiveTransform), |
1466 | "GskPerspectiveTransform" , |
1467 | gsk_perspective_transform_finalize, |
1468 | gsk_perspective_transform_to_matrix, |
1469 | NULL, |
1470 | NULL, |
1471 | NULL, |
1472 | gsk_perspective_transform_print, |
1473 | gsk_perspective_transform_apply, |
1474 | gsk_perspective_transform_invert, |
1475 | gsk_perspective_transform_equal, |
1476 | }; |
1477 | |
1478 | /** |
1479 | * gsk_transform_perspective: |
1480 | * @next: (nullable) (transfer full): the next transform |
1481 | * @depth: distance of the z=0 plane. Lower values give a more |
1482 | * flattened pyramid and therefore a more pronounced |
1483 | * perspective effect. |
1484 | * |
1485 | * Applies a perspective projection transform. |
1486 | * |
1487 | * This transform scales points in X and Y based on their Z value, |
1488 | * scaling points with positive Z values away from the origin, and |
1489 | * those with negative Z values towards the origin. Points |
1490 | * on the z=0 plane are unchanged. |
1491 | * |
1492 | * Returns: The new transform |
1493 | */ |
1494 | GskTransform * |
1495 | gsk_transform_perspective (GskTransform *next, |
1496 | float depth) |
1497 | { |
1498 | GskPerspectiveTransform *result; |
1499 | |
1500 | if (gsk_transform_has_class (self: next, transform_class: &GSK_PERSPECTIVE_TRANSFORM_CLASS)) |
1501 | { |
1502 | GskTransform *r = gsk_transform_perspective (next: gsk_transform_ref (self: next->next), |
1503 | depth: ((GskPerspectiveTransform *) next)->depth + depth); |
1504 | gsk_transform_unref (self: next); |
1505 | return r; |
1506 | } |
1507 | |
1508 | result = gsk_transform_alloc (transform_class: &GSK_PERSPECTIVE_TRANSFORM_CLASS, |
1509 | category: GSK_TRANSFORM_CATEGORY_ANY, |
1510 | next); |
1511 | |
1512 | result->depth = depth; |
1513 | |
1514 | return &result->parent; |
1515 | } |
1516 | |
1517 | /* }}} */ |
1518 | /* {{{ PUBLIC API */ |
1519 | |
1520 | /** |
1521 | * gsk_transform_ref: |
1522 | * @self: (nullable): a `GskTransform` |
1523 | * |
1524 | * Acquires a reference on the given `GskTransform`. |
1525 | * |
1526 | * Returns: (nullable) (transfer none): the `GskTransform` with an additional reference |
1527 | */ |
1528 | GskTransform * |
1529 | gsk_transform_ref (GskTransform *self) |
1530 | { |
1531 | if (self == NULL) |
1532 | return NULL; |
1533 | |
1534 | return g_atomic_rc_box_acquire (self); |
1535 | } |
1536 | |
1537 | /** |
1538 | * gsk_transform_unref: |
1539 | * @self: (nullable): a `GskTransform` |
1540 | * |
1541 | * Releases a reference on the given `GskTransform`. |
1542 | * |
1543 | * If the reference was the last, the resources associated to the @self are |
1544 | * freed. |
1545 | */ |
1546 | void |
1547 | gsk_transform_unref (GskTransform *self) |
1548 | { |
1549 | if (self == NULL) |
1550 | return; |
1551 | |
1552 | g_atomic_rc_box_release_full (mem_block: self, clear_func: (GDestroyNotify) gsk_transform_finalize); |
1553 | } |
1554 | |
1555 | /** |
1556 | * gsk_transform_print: |
1557 | * @self: (nullable): a `GskTransform` |
1558 | * @string: The string to print into |
1559 | * |
1560 | * Converts @self into a human-readable string representation suitable |
1561 | * for printing. |
1562 | * |
1563 | * The result of this function can later be parsed with |
1564 | * [func@Gsk.Transform.parse]. |
1565 | */ |
1566 | void |
1567 | gsk_transform_print (GskTransform *self, |
1568 | GString *string) |
1569 | { |
1570 | g_return_if_fail (string != NULL); |
1571 | |
1572 | if (self == NULL) |
1573 | { |
1574 | g_string_append (string, val: "none" ); |
1575 | return; |
1576 | } |
1577 | |
1578 | if (self->next != NULL) |
1579 | { |
1580 | gsk_transform_print (self: self->next, string); |
1581 | g_string_append (string, val: " " ); |
1582 | } |
1583 | |
1584 | self->transform_class->print (self, string); |
1585 | } |
1586 | |
1587 | /** |
1588 | * gsk_transform_to_string: |
1589 | * @self: (nullable): a `GskTransform` |
1590 | * |
1591 | * Converts a matrix into a string that is suitable for printing. |
1592 | * |
1593 | * The resulting string can be parsed with [func@Gsk.Transform.parse]. |
1594 | * |
1595 | * This is a wrapper around [method@Gsk.Transform.print]. |
1596 | * |
1597 | * Returns: A new string for @self |
1598 | */ |
1599 | char * |
1600 | gsk_transform_to_string (GskTransform *self) |
1601 | { |
1602 | GString *string; |
1603 | |
1604 | string = g_string_new (init: "" ); |
1605 | |
1606 | gsk_transform_print (self, string); |
1607 | |
1608 | return g_string_free (string, FALSE); |
1609 | } |
1610 | |
1611 | /** |
1612 | * gsk_transform_to_matrix: |
1613 | * @self: (nullable): a `GskTransform` |
1614 | * @out_matrix: (out caller-allocates): The matrix to set |
1615 | * |
1616 | * Computes the actual value of @self and stores it in @out_matrix. |
1617 | * |
1618 | * The previous value of @out_matrix will be ignored. |
1619 | */ |
1620 | void |
1621 | gsk_transform_to_matrix (GskTransform *self, |
1622 | graphene_matrix_t *out_matrix) |
1623 | { |
1624 | graphene_matrix_t m; |
1625 | |
1626 | if (self == NULL) |
1627 | { |
1628 | graphene_matrix_init_identity (m: out_matrix); |
1629 | return; |
1630 | } |
1631 | |
1632 | gsk_transform_to_matrix (self: self->next, out_matrix); |
1633 | self->transform_class->to_matrix (self, &m); |
1634 | graphene_matrix_multiply (a: &m, b: out_matrix, res: out_matrix); |
1635 | } |
1636 | |
1637 | /** |
1638 | * gsk_transform_to_2d: |
1639 | * @self: a 2D `GskTransform` |
1640 | * @out_xx: (out): return location for the xx member |
1641 | * @out_yx: (out): return location for the yx member |
1642 | * @out_xy: (out): return location for the xy member |
1643 | * @out_yy: (out): return location for the yy member |
1644 | * @out_dx: (out): return location for the x0 member |
1645 | * @out_dy: (out): return location for the y0 member |
1646 | * |
1647 | * Converts a `GskTransform` to a 2D transformation matrix. |
1648 | * |
1649 | * @self must be a 2D transformation. If you are not |
1650 | * sure, use gsk_transform_get_category() >= |
1651 | * %GSK_TRANSFORM_CATEGORY_2D to check. |
1652 | * |
1653 | * The returned values have the following layout: |
1654 | * |
1655 | * ``` |
1656 | * | xx yx | | a b 0 | |
1657 | * | xy yy | = | c d 0 | |
1658 | * | dx dy | | tx ty 1 | |
1659 | * ``` |
1660 | * |
1661 | * This function can be used to convert between a `GskTransform` |
1662 | * and a matrix type from other 2D drawing libraries, in particular |
1663 | * Cairo. |
1664 | */ |
1665 | void |
1666 | gsk_transform_to_2d (GskTransform *self, |
1667 | float *out_xx, |
1668 | float *out_yx, |
1669 | float *out_xy, |
1670 | float *out_yy, |
1671 | float *out_dx, |
1672 | float *out_dy) |
1673 | { |
1674 | *out_xx = 1.0f; |
1675 | *out_yx = 0.0f; |
1676 | *out_xy = 0.0f; |
1677 | *out_yy = 1.0f; |
1678 | *out_dx = 0.0f; |
1679 | *out_dy = 0.0f; |
1680 | |
1681 | if (self == NULL) |
1682 | return; |
1683 | |
1684 | if (G_UNLIKELY (self->category < GSK_TRANSFORM_CATEGORY_2D)) |
1685 | { |
1686 | char *s = gsk_transform_to_string (self); |
1687 | g_warning ("Given transform \"%s\" is not a 2D transform." , s); |
1688 | g_free (mem: s); |
1689 | return; |
1690 | } |
1691 | |
1692 | gsk_transform_to_2d (self: self->next, |
1693 | out_xx, out_yx, |
1694 | out_xy, out_yy, |
1695 | out_dx, out_dy); |
1696 | |
1697 | self->transform_class->apply_2d (self, |
1698 | out_xx, out_yx, |
1699 | out_xy, out_yy, |
1700 | out_dx, out_dy); |
1701 | } |
1702 | |
1703 | /** |
1704 | * gsk_transform_to_2d_components: |
1705 | * @self: a `GskTransform` |
1706 | * @out_skew_x: (out): return location for the skew factor |
1707 | * in the x direction |
1708 | * @out_skew_y: (out): return location for the skew factor |
1709 | * in the y direction |
1710 | * @out_scale_x: (out): return location for the scale |
1711 | * factor in the x direction |
1712 | * @out_scale_y: (out): return location for the scale |
1713 | * factor in the y direction |
1714 | * @out_angle: (out): return location for the rotation angle |
1715 | * @out_dx: (out): return location for the translation |
1716 | * in the x direction |
1717 | * @out_dy: (out): return location for the translation |
1718 | * in the y direction |
1719 | * |
1720 | * Converts a `GskTransform` to 2D transformation factors. |
1721 | * |
1722 | * To recreate an equivalent transform from the factors returned |
1723 | * by this function, use |
1724 | * |
1725 | * gsk_transform_skew ( |
1726 | * gsk_transform_scale ( |
1727 | * gsk_transform_rotate ( |
1728 | * gsk_transform_translate (NULL, &GRAPHENE_POINT_T (dx, dy)), |
1729 | * angle), |
1730 | * scale_x, scale_y), |
1731 | * skew_x, skew_y) |
1732 | * |
1733 | * @self must be a 2D transformation. If you are not sure, use |
1734 | * |
1735 | * gsk_transform_get_category() >= %GSK_TRANSFORM_CATEGORY_2D |
1736 | * |
1737 | * to check. |
1738 | * |
1739 | * Since: 4.6 |
1740 | */ |
1741 | void |
1742 | gsk_transform_to_2d_components (GskTransform *self, |
1743 | float *out_skew_x, |
1744 | float *out_skew_y, |
1745 | float *out_scale_x, |
1746 | float *out_scale_y, |
1747 | float *out_angle, |
1748 | float *out_dx, |
1749 | float *out_dy) |
1750 | { |
1751 | float a, b, c, d, e, f; |
1752 | |
1753 | gsk_transform_to_2d (self, out_xx: &a, out_yx: &b, out_xy: &c, out_yy: &d, out_dx: &e, out_dy: &f); |
1754 | |
1755 | *out_dx = e; |
1756 | *out_dy = f; |
1757 | |
1758 | #define sign(f) ((f) < 0 ? -1 : 1) |
1759 | |
1760 | if (a != 0 || b != 0) |
1761 | { |
1762 | float det = a * d - b * c; |
1763 | float r = sqrtf (x: a*a + b*b); |
1764 | |
1765 | *out_angle = RAD_TO_DEG (sign (b) * acosf (a / r)); |
1766 | *out_scale_x = r; |
1767 | *out_scale_y = det / r; |
1768 | *out_skew_x = RAD_TO_DEG (atanf ((a*c + b*d) / (r*r))); |
1769 | *out_skew_y = 0; |
1770 | } |
1771 | else if (c != 0 || d != 0) |
1772 | { |
1773 | float det = a * d - b * c; |
1774 | float s = sqrtf (x: c*c + d*d); |
1775 | |
1776 | *out_angle = RAD_TO_DEG (G_PI/2 - sign (d) * acosf (-c / s)); |
1777 | *out_scale_x = det / s; |
1778 | *out_scale_y = s; |
1779 | *out_skew_x = 0; |
1780 | *out_skew_y = RAD_TO_DEG (atanf ((a*c + b*d) / (s*s))); |
1781 | } |
1782 | else |
1783 | { |
1784 | *out_angle = 0; |
1785 | *out_scale_x = 0; |
1786 | *out_scale_y = 0; |
1787 | *out_skew_x = 0; |
1788 | *out_skew_y = 0; |
1789 | } |
1790 | } |
1791 | |
1792 | /** |
1793 | * gsk_transform_to_affine: |
1794 | * @self: a `GskTransform` |
1795 | * @out_scale_x: (out): return location for the scale |
1796 | * factor in the x direction |
1797 | * @out_scale_y: (out): return location for the scale |
1798 | * factor in the y direction |
1799 | * @out_dx: (out): return location for the translation |
1800 | * in the x direction |
1801 | * @out_dy: (out): return location for the translation |
1802 | * in the y direction |
1803 | * |
1804 | * Converts a `GskTransform` to 2D affine transformation factors. |
1805 | * |
1806 | * To recreate an equivalent transform from the factors returned |
1807 | * by this function, use |
1808 | * |
1809 | * gsk_transform_scale (gsk_transform_translate (NULL, |
1810 | * &GRAPHENE_POINT_T (dx, dy)), |
1811 | * sx, sy) |
1812 | * |
1813 | * @self must be a 2D affine transformation. If you are not |
1814 | * sure, use |
1815 | * |
1816 | * gsk_transform_get_category() >= %GSK_TRANSFORM_CATEGORY_2D_AFFINE |
1817 | * |
1818 | * to check. |
1819 | */ |
1820 | void |
1821 | gsk_transform_to_affine (GskTransform *self, |
1822 | float *out_scale_x, |
1823 | float *out_scale_y, |
1824 | float *out_dx, |
1825 | float *out_dy) |
1826 | { |
1827 | *out_scale_x = 1.0f; |
1828 | *out_scale_y = 1.0f; |
1829 | *out_dx = 0.0f; |
1830 | *out_dy = 0.0f; |
1831 | |
1832 | if (self == NULL) |
1833 | return; |
1834 | |
1835 | if (G_UNLIKELY (self->category < GSK_TRANSFORM_CATEGORY_2D_AFFINE)) |
1836 | { |
1837 | char *s = gsk_transform_to_string (self); |
1838 | g_warning ("Given transform \"%s\" is not an affine 2D transform." , s); |
1839 | g_free (mem: s); |
1840 | return; |
1841 | } |
1842 | |
1843 | gsk_transform_to_affine (self: self->next, |
1844 | out_scale_x, out_scale_y, |
1845 | out_dx, out_dy); |
1846 | |
1847 | self->transform_class->apply_affine (self, |
1848 | out_scale_x, out_scale_y, |
1849 | out_dx, out_dy); |
1850 | } |
1851 | |
1852 | /** |
1853 | * gsk_transform_to_translate: |
1854 | * @self: a `GskTransform` |
1855 | * @out_dx: (out): return location for the translation |
1856 | * in the x direction |
1857 | * @out_dy: (out): return location for the translation |
1858 | * in the y direction |
1859 | * |
1860 | * Converts a `GskTransform` to a translation operation. |
1861 | * |
1862 | * @self must be a 2D transformation. If you are not |
1863 | * sure, use |
1864 | * |
1865 | * gsk_transform_get_category() >= %GSK_TRANSFORM_CATEGORY_2D_TRANSLATE |
1866 | * |
1867 | * to check. |
1868 | */ |
1869 | void |
1870 | gsk_transform_to_translate (GskTransform *self, |
1871 | float *out_dx, |
1872 | float *out_dy) |
1873 | { |
1874 | *out_dx = 0.0f; |
1875 | *out_dy = 0.0f; |
1876 | |
1877 | if (self == NULL) |
1878 | return; |
1879 | |
1880 | if (G_UNLIKELY (self->category < GSK_TRANSFORM_CATEGORY_2D_TRANSLATE)) |
1881 | { |
1882 | char *s = gsk_transform_to_string (self); |
1883 | g_warning ("Given transform \"%s\" is not an affine 2D translation." , s); |
1884 | g_free (mem: s); |
1885 | |
1886 | return; |
1887 | } |
1888 | |
1889 | gsk_transform_to_translate (self: self->next, out_dx, out_dy); |
1890 | |
1891 | self->transform_class->apply_translate (self, out_dx, out_dy); |
1892 | } |
1893 | |
1894 | /** |
1895 | * gsk_transform_transform: |
1896 | * @next: (nullable) (transfer full): Transform to apply @other to |
1897 | * @other: (nullable): Transform to apply |
1898 | * |
1899 | * Applies all the operations from @other to @next. |
1900 | * |
1901 | * Returns: (nullable): The new transform |
1902 | */ |
1903 | GskTransform * |
1904 | gsk_transform_transform (GskTransform *next, |
1905 | GskTransform *other) |
1906 | { |
1907 | if (other == NULL) |
1908 | return next; |
1909 | |
1910 | if (next == NULL) |
1911 | return gsk_transform_ref (self: other); |
1912 | |
1913 | if (gsk_transform_is_identity (self: next)) |
1914 | { |
1915 | /* ref before unref to avoid catastrophe when other == next */ |
1916 | other = gsk_transform_ref (self: other); |
1917 | gsk_transform_unref (self: next); |
1918 | return other; |
1919 | } |
1920 | |
1921 | next = gsk_transform_transform (next, other: other->next); |
1922 | return other->transform_class->apply (other, next); |
1923 | } |
1924 | |
1925 | /** |
1926 | * gsk_transform_invert: |
1927 | * @self: (nullable) (transfer full): Transform to invert |
1928 | * |
1929 | * Inverts the given transform. |
1930 | * |
1931 | * If @self is not invertible, %NULL is returned. |
1932 | * Note that inverting %NULL also returns %NULL, which is |
1933 | * the correct inverse of %NULL. If you need to differentiate |
1934 | * between those cases, you should check @self is not %NULL |
1935 | * before calling this function. |
1936 | * |
1937 | * Returns: (nullable): The inverted transform |
1938 | */ |
1939 | GskTransform * |
1940 | gsk_transform_invert (GskTransform *self) |
1941 | { |
1942 | GskTransform *result = NULL; |
1943 | GskTransform *cur; |
1944 | |
1945 | for (cur = self; cur; cur = cur->next) |
1946 | { |
1947 | result = cur->transform_class->invert (cur, result); |
1948 | if (result == NULL) |
1949 | break; |
1950 | } |
1951 | |
1952 | gsk_transform_unref (self); |
1953 | |
1954 | return result; |
1955 | } |
1956 | |
1957 | /** |
1958 | * gsk_transform_equal: |
1959 | * @first: (nullable): the first transform |
1960 | * @second: (nullable): the second transform |
1961 | * |
1962 | * Checks two transforms for equality. |
1963 | * |
1964 | * Returns: %TRUE if the two transforms perform the same operation |
1965 | */ |
1966 | gboolean |
1967 | gsk_transform_equal (GskTransform *first, |
1968 | GskTransform *second) |
1969 | { |
1970 | if (first == second) |
1971 | return TRUE; |
1972 | |
1973 | if (first == NULL) |
1974 | return gsk_transform_is_identity (self: second); |
1975 | |
1976 | if (second == NULL) |
1977 | return gsk_transform_is_identity (self: first); |
1978 | |
1979 | if (first->transform_class != second->transform_class) |
1980 | return FALSE; |
1981 | |
1982 | if (!gsk_transform_equal (first: first->next, second: second->next)) |
1983 | return FALSE; |
1984 | |
1985 | return first->transform_class->equal (first, second); |
1986 | } |
1987 | |
1988 | /** |
1989 | * gsk_transform_get_category: |
1990 | * @self: (nullable): A `GskTransform` |
1991 | * |
1992 | * Returns the category this transform belongs to. |
1993 | * |
1994 | * Returns: The category of the transform |
1995 | **/ |
1996 | GskTransformCategory |
1997 | (gsk_transform_get_category) (GskTransform *self) |
1998 | { |
1999 | if (self == NULL) |
2000 | return GSK_TRANSFORM_CATEGORY_IDENTITY; |
2001 | |
2002 | return self->category; |
2003 | } |
2004 | |
2005 | /* |
2006 | * gsk_transform_new: (constructor): |
2007 | * |
2008 | * Creates a new identity transform. |
2009 | * |
2010 | * This function is meant to be used by language |
2011 | * bindings. For C code, this is equivalent to using %NULL. |
2012 | * |
2013 | * Returns: A new identity transform |
2014 | */ |
2015 | GskTransform * |
2016 | gsk_transform_new (void) |
2017 | { |
2018 | return gsk_transform_alloc (transform_class: &GSK_IDENTITY_TRANSFORM_CLASS, category: GSK_TRANSFORM_CATEGORY_IDENTITY, NULL); |
2019 | } |
2020 | |
2021 | /** |
2022 | * gsk_transform_transform_bounds: |
2023 | * @self: a `GskTransform` |
2024 | * @rect: a `graphene_rect_t` |
2025 | * @out_rect: (out caller-allocates): return location for the bounds |
2026 | * of the transformed rectangle |
2027 | * |
2028 | * Transforms a `graphene_rect_t` using the given transform @self. |
2029 | * |
2030 | * The result is the bounding box containing the coplanar quad. |
2031 | */ |
2032 | void |
2033 | gsk_transform_transform_bounds (GskTransform *self, |
2034 | const graphene_rect_t *rect, |
2035 | graphene_rect_t *out_rect) |
2036 | { |
2037 | switch (gsk_transform_get_category (self)) |
2038 | { |
2039 | case GSK_TRANSFORM_CATEGORY_IDENTITY: |
2040 | graphene_rect_init_from_rect (r: out_rect, src: rect); |
2041 | break; |
2042 | |
2043 | case GSK_TRANSFORM_CATEGORY_2D_TRANSLATE: |
2044 | { |
2045 | float dx, dy; |
2046 | |
2047 | gsk_transform_to_translate (self, out_dx: &dx, out_dy: &dy); |
2048 | graphene_rect_init (r: out_rect, |
2049 | x: rect->origin.x + dx, |
2050 | y: rect->origin.y + dy, |
2051 | width: rect->size.width, |
2052 | height: rect->size.height); |
2053 | } |
2054 | break; |
2055 | |
2056 | case GSK_TRANSFORM_CATEGORY_2D_AFFINE: |
2057 | { |
2058 | float dx, dy, scale_x, scale_y; |
2059 | |
2060 | gsk_transform_to_affine (self, out_scale_x: &scale_x, out_scale_y: &scale_y, out_dx: &dx, out_dy: &dy); |
2061 | |
2062 | graphene_rect_init (r: out_rect, |
2063 | x: (rect->origin.x * scale_x) + dx, |
2064 | y: (rect->origin.y * scale_y) + dy, |
2065 | width: rect->size.width * scale_x, |
2066 | height: rect->size.height * scale_y); |
2067 | } |
2068 | break; |
2069 | |
2070 | case GSK_TRANSFORM_CATEGORY_UNKNOWN: |
2071 | case GSK_TRANSFORM_CATEGORY_ANY: |
2072 | case GSK_TRANSFORM_CATEGORY_3D: |
2073 | case GSK_TRANSFORM_CATEGORY_2D: |
2074 | default: |
2075 | { |
2076 | graphene_matrix_t mat; |
2077 | |
2078 | gsk_transform_to_matrix (self, out_matrix: &mat); |
2079 | gsk_matrix_transform_bounds (m: &mat, r: rect, res: out_rect); |
2080 | } |
2081 | break; |
2082 | } |
2083 | } |
2084 | |
2085 | /** |
2086 | * gsk_transform_transform_point: |
2087 | * @self: a `GskTransform` |
2088 | * @point: a `graphene_point_t` |
2089 | * @out_point: (out caller-allocates): return location for |
2090 | * the transformed point |
2091 | * |
2092 | * Transforms a `graphene_point_t` using the given transform @self. |
2093 | */ |
2094 | void |
2095 | gsk_transform_transform_point (GskTransform *self, |
2096 | const graphene_point_t *point, |
2097 | graphene_point_t *out_point) |
2098 | { |
2099 | switch (gsk_transform_get_category (self)) |
2100 | { |
2101 | case GSK_TRANSFORM_CATEGORY_IDENTITY: |
2102 | *out_point = *point; |
2103 | break; |
2104 | |
2105 | case GSK_TRANSFORM_CATEGORY_2D_TRANSLATE: |
2106 | { |
2107 | float dx, dy; |
2108 | |
2109 | gsk_transform_to_translate (self, out_dx: &dx, out_dy: &dy); |
2110 | out_point->x = point->x + dx; |
2111 | out_point->y = point->y + dy; |
2112 | } |
2113 | break; |
2114 | |
2115 | case GSK_TRANSFORM_CATEGORY_2D_AFFINE: |
2116 | { |
2117 | float dx, dy, scale_x, scale_y; |
2118 | |
2119 | gsk_transform_to_affine (self, out_scale_x: &scale_x, out_scale_y: &scale_y, out_dx: &dx, out_dy: &dy); |
2120 | |
2121 | out_point->x = (point->x * scale_x) + dx; |
2122 | out_point->y = (point->y * scale_y) + dy; |
2123 | } |
2124 | break; |
2125 | |
2126 | case GSK_TRANSFORM_CATEGORY_UNKNOWN: |
2127 | case GSK_TRANSFORM_CATEGORY_ANY: |
2128 | case GSK_TRANSFORM_CATEGORY_3D: |
2129 | case GSK_TRANSFORM_CATEGORY_2D: |
2130 | default: |
2131 | { |
2132 | graphene_matrix_t mat; |
2133 | |
2134 | gsk_transform_to_matrix (self, out_matrix: &mat); |
2135 | gsk_matrix_transform_point (m: &mat, p: point, res: out_point); |
2136 | } |
2137 | break; |
2138 | } |
2139 | } |
2140 | |
2141 | static guint |
2142 | gsk_transform_parse_float (GtkCssParser *parser, |
2143 | guint n, |
2144 | gpointer data) |
2145 | { |
2146 | float *f = data; |
2147 | double d; |
2148 | |
2149 | if (!gtk_css_parser_consume_number (self: parser, number: &d)) |
2150 | return 0; |
2151 | |
2152 | f[n] = d; |
2153 | return 1; |
2154 | } |
2155 | |
2156 | static guint |
2157 | gsk_transform_parse_scale (GtkCssParser *parser, |
2158 | guint n, |
2159 | gpointer data) |
2160 | { |
2161 | float *f = data; |
2162 | double d; |
2163 | |
2164 | if (!gtk_css_parser_consume_number (self: parser, number: &d)) |
2165 | return 0; |
2166 | |
2167 | f[n] = d; |
2168 | f[1] = d; |
2169 | return 1; |
2170 | } |
2171 | |
2172 | gboolean |
2173 | gsk_transform_parser_parse (GtkCssParser *parser, |
2174 | GskTransform **out_transform) |
2175 | { |
2176 | const GtkCssToken *token; |
2177 | GskTransform *transform = NULL; |
2178 | float f[16] = { 0, }; |
2179 | gboolean parsed_something = FALSE; |
2180 | |
2181 | token = gtk_css_parser_get_token (self: parser); |
2182 | if (gtk_css_token_is_ident (token, ident: "none" )) |
2183 | { |
2184 | gtk_css_parser_consume_token (self: parser); |
2185 | *out_transform = NULL; |
2186 | return TRUE; |
2187 | } |
2188 | |
2189 | while (TRUE) |
2190 | { |
2191 | if (gtk_css_token_is_function (token, ident: "matrix" )) |
2192 | { |
2193 | graphene_matrix_t matrix; |
2194 | if (!gtk_css_parser_consume_function (self: parser, min_args: 6, max_args: 6, parse_func: gsk_transform_parse_float, data: f)) |
2195 | goto fail; |
2196 | |
2197 | graphene_matrix_init_from_2d (m: &matrix, xx: f[0], yx: f[1], xy: f[2], yy: f[3], x_0: f[4], y_0: f[5]); |
2198 | transform = gsk_transform_matrix_with_category (next: transform, |
2199 | matrix: &matrix, |
2200 | category: GSK_TRANSFORM_CATEGORY_2D); |
2201 | } |
2202 | else if (gtk_css_token_is_function (token, ident: "matrix3d" )) |
2203 | { |
2204 | graphene_matrix_t matrix; |
2205 | if (!gtk_css_parser_consume_function (self: parser, min_args: 16, max_args: 16, parse_func: gsk_transform_parse_float, data: f)) |
2206 | goto fail; |
2207 | |
2208 | graphene_matrix_init_from_float (m: &matrix, v: f); |
2209 | transform = gsk_transform_matrix (next: transform, matrix: &matrix); |
2210 | } |
2211 | else if (gtk_css_token_is_function (token, ident: "perspective" )) |
2212 | { |
2213 | if (!gtk_css_parser_consume_function (self: parser, min_args: 1, max_args: 1, parse_func: gsk_transform_parse_float, data: f)) |
2214 | goto fail; |
2215 | |
2216 | transform = gsk_transform_perspective (next: transform, depth: f[0]); |
2217 | } |
2218 | else if (gtk_css_token_is_function (token, ident: "rotate" ) || |
2219 | gtk_css_token_is_function (token, ident: "rotateZ" )) |
2220 | { |
2221 | if (!gtk_css_parser_consume_function (self: parser, min_args: 1, max_args: 1, parse_func: gsk_transform_parse_float, data: f)) |
2222 | goto fail; |
2223 | |
2224 | transform = gsk_transform_rotate (next: transform, angle: f[0]); |
2225 | } |
2226 | else if (gtk_css_token_is_function (token, ident: "rotate3d" )) |
2227 | { |
2228 | graphene_vec3_t axis; |
2229 | |
2230 | if (!gtk_css_parser_consume_function (self: parser, min_args: 4, max_args: 4, parse_func: gsk_transform_parse_float, data: f)) |
2231 | goto fail; |
2232 | |
2233 | graphene_vec3_init (v: &axis, x: f[0], y: f[1], z: f[2]); |
2234 | transform = gsk_transform_rotate_3d (next: transform, angle: f[3], axis: &axis); |
2235 | } |
2236 | else if (gtk_css_token_is_function (token, ident: "rotateX" )) |
2237 | { |
2238 | if (!gtk_css_parser_consume_function (self: parser, min_args: 1, max_args: 1, parse_func: gsk_transform_parse_float, data: f)) |
2239 | goto fail; |
2240 | |
2241 | transform = gsk_transform_rotate_3d (next: transform, angle: f[0], axis: graphene_vec3_x_axis ()); |
2242 | } |
2243 | else if (gtk_css_token_is_function (token, ident: "rotateY" )) |
2244 | { |
2245 | if (!gtk_css_parser_consume_function (self: parser, min_args: 1, max_args: 1, parse_func: gsk_transform_parse_float, data: f)) |
2246 | goto fail; |
2247 | |
2248 | transform = gsk_transform_rotate_3d (next: transform, angle: f[0], axis: graphene_vec3_y_axis ()); |
2249 | } |
2250 | else if (gtk_css_token_is_function (token, ident: "scale" )) |
2251 | { |
2252 | if (!gtk_css_parser_consume_function (self: parser, min_args: 1, max_args: 2, parse_func: gsk_transform_parse_scale, data: f)) |
2253 | goto fail; |
2254 | |
2255 | transform = gsk_transform_scale (next: transform, factor_x: f[0], factor_y: f[1]); |
2256 | } |
2257 | else if (gtk_css_token_is_function (token, ident: "scale3d" )) |
2258 | { |
2259 | if (!gtk_css_parser_consume_function (self: parser, min_args: 3, max_args: 3, parse_func: gsk_transform_parse_float, data: f)) |
2260 | goto fail; |
2261 | |
2262 | transform = gsk_transform_scale_3d (next: transform, factor_x: f[0], factor_y: f[1], factor_z: f[2]); |
2263 | } |
2264 | else if (gtk_css_token_is_function (token, ident: "scaleX" )) |
2265 | { |
2266 | if (!gtk_css_parser_consume_function (self: parser, min_args: 1, max_args: 1, parse_func: gsk_transform_parse_float, data: f)) |
2267 | goto fail; |
2268 | |
2269 | transform = gsk_transform_scale (next: transform, factor_x: f[0], factor_y: 1.f); |
2270 | } |
2271 | else if (gtk_css_token_is_function (token, ident: "scaleY" )) |
2272 | { |
2273 | if (!gtk_css_parser_consume_function (self: parser, min_args: 1, max_args: 1, parse_func: gsk_transform_parse_float, data: f)) |
2274 | goto fail; |
2275 | |
2276 | transform = gsk_transform_scale (next: transform, factor_x: 1.f, factor_y: f[0]); |
2277 | } |
2278 | else if (gtk_css_token_is_function (token, ident: "scaleZ" )) |
2279 | { |
2280 | if (!gtk_css_parser_consume_function (self: parser, min_args: 1, max_args: 1, parse_func: gsk_transform_parse_float, data: f)) |
2281 | goto fail; |
2282 | |
2283 | transform = gsk_transform_scale_3d (next: transform, factor_x: 1.f, factor_y: 1.f, factor_z: f[0]); |
2284 | } |
2285 | else if (gtk_css_token_is_function (token, ident: "translate" )) |
2286 | { |
2287 | f[1] = 0.f; |
2288 | if (!gtk_css_parser_consume_function (self: parser, min_args: 1, max_args: 2, parse_func: gsk_transform_parse_float, data: f)) |
2289 | goto fail; |
2290 | |
2291 | transform = gsk_transform_translate (next: transform, point: &GRAPHENE_POINT_INIT (f[0], f[1])); |
2292 | } |
2293 | else if (gtk_css_token_is_function (token, ident: "translate3d" )) |
2294 | { |
2295 | if (!gtk_css_parser_consume_function (self: parser, min_args: 3, max_args: 3, parse_func: gsk_transform_parse_float, data: f)) |
2296 | goto fail; |
2297 | |
2298 | transform = gsk_transform_translate_3d (next: transform, point: &GRAPHENE_POINT3D_INIT (f[0], f[1], f[2])); |
2299 | } |
2300 | else if (gtk_css_token_is_function (token, ident: "translateX" )) |
2301 | { |
2302 | if (!gtk_css_parser_consume_function (self: parser, min_args: 1, max_args: 1, parse_func: gsk_transform_parse_float, data: f)) |
2303 | goto fail; |
2304 | |
2305 | transform = gsk_transform_translate (next: transform, point: &GRAPHENE_POINT_INIT (f[0], 0.f)); |
2306 | } |
2307 | else if (gtk_css_token_is_function (token, ident: "translateY" )) |
2308 | { |
2309 | if (!gtk_css_parser_consume_function (self: parser, min_args: 1, max_args: 1, parse_func: gsk_transform_parse_float, data: f)) |
2310 | goto fail; |
2311 | |
2312 | transform = gsk_transform_translate (next: transform, point: &GRAPHENE_POINT_INIT (0.f, f[0])); |
2313 | } |
2314 | else if (gtk_css_token_is_function (token, ident: "translateZ" )) |
2315 | { |
2316 | if (!gtk_css_parser_consume_function (self: parser, min_args: 1, max_args: 1, parse_func: gsk_transform_parse_float, data: f)) |
2317 | goto fail; |
2318 | |
2319 | transform = gsk_transform_translate_3d (next: transform, point: &GRAPHENE_POINT3D_INIT (0.f, 0.f, f[0])); |
2320 | } |
2321 | else if (gtk_css_token_is_function (token, ident: "skew" )) |
2322 | { |
2323 | if (!gtk_css_parser_consume_function (self: parser, min_args: 2, max_args: 2, parse_func: gsk_transform_parse_float, data: f)) |
2324 | goto fail; |
2325 | |
2326 | transform = gsk_transform_skew (next: transform, skew_x: f[0], skew_y: f[1]); |
2327 | } |
2328 | else if (gtk_css_token_is_function (token, ident: "skewX" )) |
2329 | { |
2330 | if (!gtk_css_parser_consume_function (self: parser, min_args: 1, max_args: 1, parse_func: gsk_transform_parse_float, data: f)) |
2331 | goto fail; |
2332 | |
2333 | transform = gsk_transform_skew (next: transform, skew_x: f[0], skew_y: 0); |
2334 | } |
2335 | else if (gtk_css_token_is_function (token, ident: "skewY" )) |
2336 | { |
2337 | if (!gtk_css_parser_consume_function (self: parser, min_args: 1, max_args: 1, parse_func: gsk_transform_parse_float, data: f)) |
2338 | goto fail; |
2339 | |
2340 | transform = gsk_transform_skew (next: transform, skew_x: 0, skew_y: f[0]); |
2341 | } |
2342 | else |
2343 | { |
2344 | break; |
2345 | } |
2346 | |
2347 | parsed_something = TRUE; |
2348 | token = gtk_css_parser_get_token (self: parser); |
2349 | } |
2350 | |
2351 | if (!parsed_something) |
2352 | { |
2353 | gtk_css_parser_error_syntax (self: parser, format: "Expected a transform" ); |
2354 | goto fail; |
2355 | } |
2356 | |
2357 | *out_transform = transform; |
2358 | return TRUE; |
2359 | |
2360 | fail: |
2361 | gsk_transform_unref (self: transform); |
2362 | *out_transform = NULL; |
2363 | return FALSE; |
2364 | } |
2365 | |
2366 | /** |
2367 | * gsk_transform_parse: |
2368 | * @string: the string to parse |
2369 | * @out_transform: (out): The location to put the transform in |
2370 | * |
2371 | * Parses the given @string into a transform and puts it in |
2372 | * @out_transform. |
2373 | * |
2374 | * Strings printed via [method@Gsk.Transform.to_string] |
2375 | * can be read in again successfully using this function. |
2376 | * |
2377 | * If @string does not describe a valid transform, %FALSE is |
2378 | * returned and %NULL is put in @out_transform. |
2379 | * |
2380 | * Returns: %TRUE if @string described a valid transform. |
2381 | */ |
2382 | gboolean |
2383 | gsk_transform_parse (const char *string, |
2384 | GskTransform **out_transform) |
2385 | { |
2386 | GtkCssParser *parser; |
2387 | GBytes *bytes; |
2388 | gboolean result; |
2389 | |
2390 | g_return_val_if_fail (string != NULL, FALSE); |
2391 | g_return_val_if_fail (out_transform != NULL, FALSE); |
2392 | |
2393 | bytes = g_bytes_new_static (data: string, size: strlen (s: string)); |
2394 | parser = gtk_css_parser_new_for_bytes (bytes, NULL, NULL, NULL, NULL); |
2395 | |
2396 | result = gsk_transform_parser_parse (parser, out_transform); |
2397 | |
2398 | if (result && !gtk_css_parser_has_token (self: parser, token_type: GTK_CSS_TOKEN_EOF)) |
2399 | { |
2400 | g_clear_pointer (out_transform, gsk_transform_unref); |
2401 | result = FALSE; |
2402 | } |
2403 | gtk_css_parser_unref (self: parser); |
2404 | g_bytes_unref (bytes); |
2405 | |
2406 | return result; |
2407 | } |
2408 | |
2409 | /* Some of the graphene_matrix_transform apis yield unexpected |
2410 | * results with projective matrices, since they silently drop |
2411 | * the w component, so we provide working alternatives here. |
2412 | */ |
2413 | void |
2414 | gsk_matrix_transform_point (const graphene_matrix_t *m, |
2415 | const graphene_point_t *p, |
2416 | graphene_point_t *res) |
2417 | { |
2418 | graphene_vec4_t vec4; |
2419 | float w; |
2420 | |
2421 | graphene_vec4_init (v: &vec4, x: p->x, y: p->y, z: 0.0f, w: 1.0f); |
2422 | graphene_matrix_transform_vec4 (m, v: &vec4, res: &vec4); |
2423 | |
2424 | w = graphene_vec4_get_w (v: &vec4); |
2425 | res->x = graphene_vec4_get_x (v: &vec4) / w; |
2426 | res->y = graphene_vec4_get_y (v: &vec4) / w; |
2427 | } |
2428 | |
2429 | void |
2430 | gsk_matrix_transform_point3d (const graphene_matrix_t *m, |
2431 | const graphene_point3d_t *p, |
2432 | graphene_point3d_t *res) |
2433 | { |
2434 | graphene_vec4_t vec4; |
2435 | float w; |
2436 | |
2437 | graphene_vec4_init (v: &vec4, x: p->x, y: p->y, z: 0.0f, w: 1.0f); |
2438 | graphene_matrix_transform_vec4 (m, v: &vec4, res: &vec4); |
2439 | |
2440 | w = graphene_vec4_get_w (v: &vec4); |
2441 | res->x = graphene_vec4_get_x (v: &vec4) / w; |
2442 | res->y = graphene_vec4_get_y (v: &vec4) / w; |
2443 | res->z = graphene_vec4_get_z (v: &vec4) / w; |
2444 | } |
2445 | |
2446 | void |
2447 | gsk_matrix_transform_rect (const graphene_matrix_t *m, |
2448 | const graphene_rect_t *r, |
2449 | graphene_quad_t *res) |
2450 | { |
2451 | graphene_point_t ret[4]; |
2452 | graphene_rect_t rr; |
2453 | |
2454 | graphene_rect_normalize_r (r, res: &rr); |
2455 | |
2456 | #define TRANSFORM_POINT(matrix, rect, corner, out_p) do {\ |
2457 | graphene_vec4_t __s; \ |
2458 | graphene_point_t __p; \ |
2459 | float w; \ |
2460 | graphene_rect_get_ ## corner (rect, &__p); \ |
2461 | graphene_vec4_init (&__s, __p.x, __p.y, 0.f, 1.f); \ |
2462 | graphene_matrix_transform_vec4 (matrix, &__s, &__s); \ |
2463 | w = graphene_vec4_get_w (&__s); \ |
2464 | out_p.x = graphene_vec4_get_x (&__s) / w; \ |
2465 | out_p.y = graphene_vec4_get_y (&__s) / w; } while (0) |
2466 | |
2467 | TRANSFORM_POINT (m, &rr, top_left, ret[0]); |
2468 | TRANSFORM_POINT (m, &rr, top_right, ret[1]); |
2469 | TRANSFORM_POINT (m, &rr, bottom_right, ret[2]); |
2470 | TRANSFORM_POINT (m, &rr, bottom_left, ret[3]); |
2471 | |
2472 | #undef TRANSFORM_POINT |
2473 | |
2474 | graphene_quad_init (q: res, p1: &ret[0], p2: &ret[1], p3: &ret[2], p4: &ret[3]); |
2475 | } |
2476 | void |
2477 | gsk_matrix_transform_bounds (const graphene_matrix_t *m, |
2478 | const graphene_rect_t *r, |
2479 | graphene_rect_t *res) |
2480 | { |
2481 | graphene_quad_t q; |
2482 | |
2483 | gsk_matrix_transform_rect (m, r, res: &q); |
2484 | graphene_quad_bounds (q: &q, r: res); |
2485 | } |
2486 | |
2487 | /* }}} */ |
2488 | |
2489 | /* vim:set foldmethod=marker expandtab: */ |
2490 | |