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
41struct _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
76G_DEFINE_BOXED_TYPE (GskTransform, gsk_transform,
77 gsk_transform_ref,
78 gsk_transform_unref)
79
80static gboolean
81gsk_transform_is_identity (GskTransform *self);
82static GskTransform *
83gsk_transform_matrix_with_category (GskTransform *next,
84 const graphene_matrix_t*matrix,
85 GskTransformCategory category);
86
87static inline gboolean
88gsk_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 */
103static gpointer
104gsk_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
124static void
125gsk_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
135static void
136gsk_identity_transform_finalize (GskTransform *transform)
137{
138}
139
140static void
141gsk_identity_transform_to_matrix (GskTransform *transform,
142 graphene_matrix_t *out_matrix)
143{
144 graphene_matrix_init_identity (m: out_matrix);
145}
146
147static void
148gsk_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
158static void
159gsk_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
167static void
168gsk_identity_transform_apply_translate (GskTransform *transform,
169 float *out_dx,
170 float *out_dy)
171{
172}
173
174static void
175gsk_identity_transform_print (GskTransform *transform,
176 GString *string)
177{
178 g_string_append (string, val: "none");
179}
180
181static GskTransform *
182gsk_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
194static GskTransform *
195gsk_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
207static gboolean
208gsk_identity_transform_equal (GskTransform *first_transform,
209 GskTransform *second_transform)
210{
211 return TRUE;
212}
213
214static 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 **/
242static gboolean
243gsk_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
252typedef struct _GskMatrixTransform GskMatrixTransform;
253
254struct _GskMatrixTransform
255{
256 GskTransform parent;
257
258 graphene_matrix_t matrix;
259};
260
261static void
262gsk_matrix_transform_finalize (GskTransform *self)
263{
264}
265
266static void
267gsk_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
275static void
276gsk_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
303static void
304gsk_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
339static void
340gsk_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
367static void
368string_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
377static void
378gsk_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
396static GskTransform *
397gsk_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
407static GskTransform *
408gsk_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
425static gboolean
426gsk_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
438static 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
453static GskTransform *
454gsk_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 **/
474GskTransform *
475gsk_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
484typedef struct _GskTranslateTransform GskTranslateTransform;
485
486struct _GskTranslateTransform
487{
488 GskTransform parent;
489
490 graphene_point3d_t point;
491};
492
493static void
494gsk_translate_transform_finalize (GskTransform *self)
495{
496}
497
498static void
499gsk_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
507static void
508gsk_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
524static void
525gsk_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
539static void
540gsk_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
552static GskTransform *
553gsk_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
561static GskTransform *
562gsk_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
570static gboolean
571gsk_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
582static void
583gsk_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
604static 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 **/
628GskTransform *
629gsk_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 **/
648GskTransform *
649gsk_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
681typedef struct _GskRotateTransform GskRotateTransform;
682
683struct _GskRotateTransform
684{
685 GskTransform parent;
686
687 float angle;
688};
689
690static void
691gsk_rotate_transform_finalize (GskTransform *self)
692{
693}
694
695static 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
734static void
735gsk_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
749static void
750gsk_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
774static GskTransform *
775gsk_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
783static GskTransform *
784gsk_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
792static gboolean
793gsk_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
802static void
803gsk_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
813static 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
828static inline float
829normalize_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 */
864GskTransform *
865gsk_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
893typedef struct _GskRotate3dTransform GskRotate3dTransform;
894
895struct _GskRotate3dTransform
896{
897 GskTransform parent;
898
899 float angle;
900 graphene_vec3_t axis;
901};
902
903static void
904gsk_rotate3d_transform_finalize (GskTransform *self)
905{
906}
907
908static void
909gsk_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
917static GskTransform *
918gsk_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
926static GskTransform *
927gsk_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
935static gboolean
936gsk_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
946static void
947gsk_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
965static 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 */
992GskTransform *
993gsk_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
1018typedef struct _GskSkewTransform GskSkewTransform;
1019
1020struct _GskSkewTransform
1021{
1022 GskTransform parent;
1023
1024 float skew_x;
1025 float skew_y;
1026};
1027
1028static void
1029gsk_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
1036static void
1037gsk_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
1047static void
1048gsk_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
1073static GskTransform *
1074gsk_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
1082static void
1083gsk_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
1110static GskTransform *
1111gsk_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
1132static gboolean
1133gsk_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
1143static 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 */
1170GskTransform *
1171gsk_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
1192typedef struct _GskScaleTransform GskScaleTransform;
1193
1194struct _GskScaleTransform
1195{
1196 GskTransform parent;
1197
1198 float factor_x;
1199 float factor_y;
1200 float factor_z;
1201};
1202
1203static void
1204gsk_scale_transform_finalize (GskTransform *self)
1205{
1206}
1207
1208static void
1209gsk_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
1217static void
1218gsk_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
1236static void
1237gsk_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
1251static GskTransform *
1252gsk_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
1260static GskTransform *
1261gsk_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
1272static gboolean
1273gsk_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
1284static void
1285gsk_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
1313static 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 **/
1340GskTransform *
1341gsk_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 **/
1359GskTransform *
1360gsk_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
1396typedef struct _GskPerspectiveTransform GskPerspectiveTransform;
1397
1398struct _GskPerspectiveTransform
1399{
1400 GskTransform parent;
1401
1402 float depth;
1403};
1404
1405static void
1406gsk_perspective_transform_finalize (GskTransform *self)
1407{
1408}
1409
1410static void
1411gsk_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
1424static GskTransform *
1425gsk_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
1433static GskTransform *
1434gsk_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
1442static gboolean
1443gsk_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
1452static void
1453gsk_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
1463static 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 */
1494GskTransform *
1495gsk_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 */
1528GskTransform *
1529gsk_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 */
1546void
1547gsk_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 */
1566void
1567gsk_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 */
1599char *
1600gsk_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 */
1620void
1621gsk_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 */
1665void
1666gsk_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 */
1741void
1742gsk_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 */
1820void
1821gsk_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 */
1869void
1870gsk_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 */
1903GskTransform *
1904gsk_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 */
1939GskTransform *
1940gsk_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 */
1966gboolean
1967gsk_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 **/
1996GskTransformCategory
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 */
2015GskTransform *
2016gsk_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 */
2032void
2033gsk_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 */
2094void
2095gsk_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
2141static guint
2142gsk_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
2156static guint
2157gsk_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
2172gboolean
2173gsk_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
2360fail:
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 */
2382gboolean
2383gsk_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 */
2413void
2414gsk_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
2429void
2430gsk_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
2446void
2447gsk_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}
2476void
2477gsk_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

source code of gtk/gsk/gsktransform.c