/* * Copyright © 2019 Benjamin Otte * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library. If not, see . * * Authors: Benjamin Otte */ /** * GskTransform: (ref-func gsk_transform_ref) (unref-func gsk_transform_unref) * * `GskTransform` is an object to describe transform matrices. * * Unlike `graphene_matrix_t`, `GskTransform` retains the steps in how * a transform was constructed, and allows inspecting them. It is modeled * after the way CSS describes transforms. * * `GskTransform` objects are immutable and cannot be changed after creation. * This means code can safely expose them as properties of objects without * having to worry about others changing them. */ #include "config.h" #include "gsktransformprivate.h" /* {{{ Boilerplate */ struct _GskTransformClass { gsize struct_size; const char *type_name; void (* finalize) (GskTransform *transform); void (* to_matrix) (GskTransform *transform, graphene_matrix_t *out_matrix); void (* apply_2d) (GskTransform *transform, float *out_xx, float *out_yx, float *out_xy, float *out_yy, float *out_dx, float *out_dy); void (* apply_affine) (GskTransform *transform, float *out_scale_x, float *out_scale_y, float *out_dx, float *out_dy); void (* apply_translate) (GskTransform *transform, float *out_dx, float *out_dy); void (* print) (GskTransform *transform, GString *string); GskTransform * (* apply) (GskTransform *transform, GskTransform *apply_to); GskTransform * (* invert) (GskTransform *transform, GskTransform *next); /* both matrices have the same type */ gboolean (* equal) (GskTransform *first_transform, GskTransform *second_transform); }; G_DEFINE_BOXED_TYPE (GskTransform, gsk_transform, gsk_transform_ref, gsk_transform_unref) static gboolean gsk_transform_is_identity (GskTransform *self); static GskTransform * gsk_transform_matrix_with_category (GskTransform *next, const graphene_matrix_t*matrix, GskTransformCategory category); static inline gboolean gsk_transform_has_class (GskTransform *self, const GskTransformClass *transform_class) { return self != NULL && self->transform_class == transform_class; } /*< private > * gsk_transform_alloc: * @transform_class: class structure for this self * @category: The category of this transform. Will be used to initialize * the result's category together with &next's category * @next: (transfer full) (nullable): Next transform to multiply with * * Returns: (transfer full): the newly created `GskTransform` */ static gpointer gsk_transform_alloc (const GskTransformClass *transform_class, GskTransformCategory category, GskTransform *next) { GskTransform *self; g_return_val_if_fail (transform_class != NULL, NULL); self = g_atomic_rc_box_alloc0 (transform_class->struct_size); self->transform_class = transform_class; self->category = next ? MIN (category, next->category) : category; if (gsk_transform_is_identity (next)) gsk_transform_unref (next); else self->next = next; return self; } static void gsk_transform_finalize (GskTransform *self) { self->transform_class->finalize (self); gsk_transform_unref (self->next); } /* }}} */ /* {{{ IDENTITY */ static void gsk_identity_transform_finalize (GskTransform *transform) { } static void gsk_identity_transform_to_matrix (GskTransform *transform, graphene_matrix_t *out_matrix) { graphene_matrix_init_identity (out_matrix); } static void gsk_identity_transform_apply_2d (GskTransform *transform, float *out_xx, float *out_yx, float *out_xy, float *out_yy, float *out_dx, float *out_dy) { } static void gsk_identity_transform_apply_affine (GskTransform *transform, float *out_scale_x, float *out_scale_y, float *out_dx, float *out_dy) { } static void gsk_identity_transform_apply_translate (GskTransform *transform, float *out_dx, float *out_dy) { } static void gsk_identity_transform_print (GskTransform *transform, GString *string) { g_string_append (string, "none"); } static GskTransform * gsk_identity_transform_apply (GskTransform *transform, GskTransform *apply_to) { /* We do the following to make sure inverting a non-NULL transform * will return a non-NULL transform. */ if (apply_to) return apply_to; else return gsk_transform_new (); } static GskTransform * gsk_identity_transform_invert (GskTransform *transform, GskTransform *next) { /* We do the following to make sure inverting a non-NULL transform * will return a non-NULL transform. */ if (next) return next; else return gsk_transform_new (); } static gboolean gsk_identity_transform_equal (GskTransform *first_transform, GskTransform *second_transform) { return TRUE; } static const GskTransformClass GSK_IDENTITY_TRANSFORM_CLASS = { sizeof (GskTransform), "GskIdentityTransform", gsk_identity_transform_finalize, gsk_identity_transform_to_matrix, gsk_identity_transform_apply_2d, gsk_identity_transform_apply_affine, gsk_identity_transform_apply_translate, gsk_identity_transform_print, gsk_identity_transform_apply, gsk_identity_transform_invert, gsk_identity_transform_equal, }; /* * gsk_transform_is_identity: * @transform: (nullable): A transform * * Checks if the transform is a representation of the identity * transform. * * This is different from a transform like `scale(2) scale(0.5)` * which just results in an identity transform when simplified. * * Returns: %TRUE if this transform is a representation of * the identity transform **/ static gboolean gsk_transform_is_identity (GskTransform *self) { return self == NULL || (self->transform_class == &GSK_IDENTITY_TRANSFORM_CLASS && gsk_transform_is_identity (self->next)); } /* }}} */ /* {{{ MATRIX */ typedef struct _GskMatrixTransform GskMatrixTransform; struct _GskMatrixTransform { GskTransform parent; graphene_matrix_t matrix; }; static void gsk_matrix_transform_finalize (GskTransform *self) { } static void gsk_matrix_transform_to_matrix (GskTransform *transform, graphene_matrix_t *out_matrix) { GskMatrixTransform *self = (GskMatrixTransform *) transform; graphene_matrix_init_from_matrix (out_matrix, &self->matrix); } static void gsk_matrix_transform_apply_2d (GskTransform *transform, float *out_xx, float *out_yx, float *out_xy, float *out_yy, float *out_dx, float *out_dy) { GskMatrixTransform *self = (GskMatrixTransform *) transform; graphene_matrix_t mat; graphene_matrix_init_from_2d (&mat, *out_xx, *out_yx, *out_xy, *out_yy, *out_dx, *out_dy); graphene_matrix_multiply (&self->matrix, &mat, &mat); /* not using graphene_matrix_to_2d() because it may * fail the is_2d() check due to improper rounding */ *out_xx = graphene_matrix_get_value (&mat, 0, 0); *out_yx = graphene_matrix_get_value (&mat, 0, 1); *out_xy = graphene_matrix_get_value (&mat, 1, 0); *out_yy = graphene_matrix_get_value (&mat, 1, 1); *out_dx = graphene_matrix_get_value (&mat, 3, 0); *out_dy = graphene_matrix_get_value (&mat, 3, 1); } static void gsk_matrix_transform_apply_affine (GskTransform *transform, float *out_scale_x, float *out_scale_y, float *out_dx, float *out_dy) { GskMatrixTransform *self = (GskMatrixTransform *) transform; switch (transform->category) { case GSK_TRANSFORM_CATEGORY_UNKNOWN: case GSK_TRANSFORM_CATEGORY_ANY: case GSK_TRANSFORM_CATEGORY_3D: case GSK_TRANSFORM_CATEGORY_2D: default: g_assert_not_reached (); break; case GSK_TRANSFORM_CATEGORY_2D_AFFINE: *out_dx += *out_scale_x * graphene_matrix_get_x_translation (&self->matrix); *out_dy += *out_scale_y * graphene_matrix_get_y_translation (&self->matrix); *out_scale_x *= graphene_matrix_get_x_scale (&self->matrix); *out_scale_y *= graphene_matrix_get_y_scale (&self->matrix); break; case GSK_TRANSFORM_CATEGORY_2D_TRANSLATE: *out_dx += *out_scale_x * graphene_matrix_get_x_translation (&self->matrix); *out_dy += *out_scale_y * graphene_matrix_get_y_translation (&self->matrix); break; case GSK_TRANSFORM_CATEGORY_IDENTITY: break; } } static void gsk_matrix_transform_apply_translate (GskTransform *transform, float *out_dx, float *out_dy) { GskMatrixTransform *self = (GskMatrixTransform *) transform; switch (transform->category) { case GSK_TRANSFORM_CATEGORY_UNKNOWN: case GSK_TRANSFORM_CATEGORY_ANY: case GSK_TRANSFORM_CATEGORY_3D: case GSK_TRANSFORM_CATEGORY_2D: case GSK_TRANSFORM_CATEGORY_2D_AFFINE: default: g_assert_not_reached (); break; case GSK_TRANSFORM_CATEGORY_2D_TRANSLATE: *out_dx += graphene_matrix_get_x_translation (&self->matrix); *out_dy += graphene_matrix_get_y_translation (&self->matrix); break; case GSK_TRANSFORM_CATEGORY_IDENTITY: break; } } static void string_append_double (GString *string, double d) { char buf[G_ASCII_DTOSTR_BUF_SIZE]; g_ascii_formatd (buf, G_ASCII_DTOSTR_BUF_SIZE, "%g", d); g_string_append (string, buf); } static void gsk_matrix_transform_print (GskTransform *transform, GString *string) { GskMatrixTransform *self = (GskMatrixTransform *) transform; guint i; float f[16]; g_string_append (string, "matrix3d("); graphene_matrix_to_float (&self->matrix, f); for (i = 0; i < 16; i++) { if (i > 0) g_string_append (string, ", "); string_append_double (string, f[i]); } g_string_append (string, ")"); } static GskTransform * gsk_matrix_transform_apply (GskTransform *transform, GskTransform *apply_to) { GskMatrixTransform *self = (GskMatrixTransform *) transform; return gsk_transform_matrix_with_category (apply_to, &self->matrix, transform->category); } static GskTransform * gsk_matrix_transform_invert (GskTransform *transform, GskTransform *next) { GskMatrixTransform *self = (GskMatrixTransform *) transform; graphene_matrix_t inverse; if (!graphene_matrix_inverse (&self->matrix, &inverse)) { gsk_transform_unref (next); return NULL; } return gsk_transform_matrix_with_category (next, &inverse, transform->category); } static gboolean gsk_matrix_transform_equal (GskTransform *first_transform, GskTransform *second_transform) { GskMatrixTransform *first = (GskMatrixTransform *) first_transform; GskMatrixTransform *second = (GskMatrixTransform *) second_transform; if (graphene_matrix_equal_fast (&first->matrix, &second->matrix)) return TRUE; return graphene_matrix_equal (&first->matrix, &second->matrix); } static const GskTransformClass GSK_TRANSFORM_TRANSFORM_CLASS = { sizeof (GskMatrixTransform), "GskMatrixTransform", gsk_matrix_transform_finalize, gsk_matrix_transform_to_matrix, gsk_matrix_transform_apply_2d, gsk_matrix_transform_apply_affine, gsk_matrix_transform_apply_translate, gsk_matrix_transform_print, gsk_matrix_transform_apply, gsk_matrix_transform_invert, gsk_matrix_transform_equal, }; static GskTransform * gsk_transform_matrix_with_category (GskTransform *next, const graphene_matrix_t *matrix, GskTransformCategory category) { GskMatrixTransform *result = gsk_transform_alloc (&GSK_TRANSFORM_TRANSFORM_CLASS, category, next); graphene_matrix_init_from_matrix (&result->matrix, matrix); return &result->parent; } /** * gsk_transform_matrix: * @next: (nullable) (transfer full): the next transform * @matrix: the matrix to multiply @next with * * Multiplies @next with the given @matrix. * * Returns: The new transform **/ GskTransform * gsk_transform_matrix (GskTransform *next, const graphene_matrix_t *matrix) { return gsk_transform_matrix_with_category (next, matrix, GSK_TRANSFORM_CATEGORY_UNKNOWN); } /* }}} */ /* {{{ TRANSLATE */ typedef struct _GskTranslateTransform GskTranslateTransform; struct _GskTranslateTransform { GskTransform parent; graphene_point3d_t point; }; static void gsk_translate_transform_finalize (GskTransform *self) { } static void gsk_translate_transform_to_matrix (GskTransform *transform, graphene_matrix_t *out_matrix) { GskTranslateTransform *self = (GskTranslateTransform *) transform; graphene_matrix_init_translate (out_matrix, &self->point); } static void gsk_translate_transform_apply_2d (GskTransform *transform, float *out_xx, float *out_yx, float *out_xy, float *out_yy, float *out_dx, float *out_dy) { GskTranslateTransform *self = (GskTranslateTransform *) transform; g_assert (self->point.z == 0.0); *out_dx += *out_xx * self->point.x + *out_xy * self->point.y; *out_dy += *out_yx * self->point.x + *out_yy * self->point.y; } static void gsk_translate_transform_apply_affine (GskTransform *transform, float *out_scale_x, float *out_scale_y, float *out_dx, float *out_dy) { GskTranslateTransform *self = (GskTranslateTransform *) transform; g_assert (self->point.z == 0.0); *out_dx += *out_scale_x * self->point.x; *out_dy += *out_scale_y * self->point.y; } static void gsk_translate_transform_apply_translate (GskTransform *transform, float *out_dx, float *out_dy) { GskTranslateTransform *self = (GskTranslateTransform *) transform; g_assert (self->point.z == 0.0); *out_dx += self->point.x; *out_dy += self->point.y; } static GskTransform * gsk_translate_transform_apply (GskTransform *transform, GskTransform *apply_to) { GskTranslateTransform *self = (GskTranslateTransform *) transform; return gsk_transform_translate_3d (apply_to, &self->point); } static GskTransform * gsk_translate_transform_invert (GskTransform *transform, GskTransform *next) { GskTranslateTransform *self = (GskTranslateTransform *) transform; return gsk_transform_translate_3d (next, &GRAPHENE_POINT3D_INIT (-self->point.x, -self->point.y, -self->point.z)); } static gboolean gsk_translate_transform_equal (GskTransform *first_transform, GskTransform *second_transform) { GskTranslateTransform *first = (GskTranslateTransform *) first_transform; GskTranslateTransform *second = (GskTranslateTransform *) second_transform; return G_APPROX_VALUE (first->point.x, second->point.x, FLT_EPSILON) && G_APPROX_VALUE (first->point.y, second->point.y, FLT_EPSILON) && G_APPROX_VALUE (first->point.z, second->point.z, FLT_EPSILON); } static void gsk_translate_transform_print (GskTransform *transform, GString *string) { GskTranslateTransform *self = (GskTranslateTransform *) transform; if (self->point.z == 0) g_string_append (string, "translate("); else g_string_append (string, "translate3d("); string_append_double (string, self->point.x); g_string_append (string, ", "); string_append_double (string, self->point.y); if (self->point.z != 0) { g_string_append (string, ", "); string_append_double (string, self->point.z); } g_string_append (string, ")"); } static const GskTransformClass GSK_TRANSLATE_TRANSFORM_CLASS = { sizeof (GskTranslateTransform), "GskTranslateTransform", gsk_translate_transform_finalize, gsk_translate_transform_to_matrix, gsk_translate_transform_apply_2d, gsk_translate_transform_apply_affine, gsk_translate_transform_apply_translate, gsk_translate_transform_print, gsk_translate_transform_apply, gsk_translate_transform_invert, gsk_translate_transform_equal, }; /** * gsk_transform_translate: * @next: (nullable) (transfer full): the next transform * @point: the point to translate the transform by * * Translates @next in 2-dimensional space by @point. * * Returns: The new transform **/ GskTransform * gsk_transform_translate (GskTransform *next, const graphene_point_t *point) { graphene_point3d_t point3d; graphene_point3d_init (&point3d, point->x, point->y, 0); return gsk_transform_translate_3d (next, &point3d); } /** * gsk_transform_translate_3d: * @next: (nullable) (transfer full): the next transform * @point: the point to translate the transform by * * Translates @next by @point. * * Returns: The new transform **/ GskTransform * gsk_transform_translate_3d (GskTransform *next, const graphene_point3d_t *point) { GskTranslateTransform *result; if (graphene_point3d_equal (point, graphene_point3d_zero ())) return next; if (gsk_transform_has_class (next, &GSK_TRANSLATE_TRANSFORM_CLASS)) { GskTranslateTransform *t = (GskTranslateTransform *) next; GskTransform *r = gsk_transform_translate_3d (gsk_transform_ref (next->next), &GRAPHENE_POINT3D_INIT(t->point.x + point->x, t->point.y + point->y, t->point.z + point->z)); gsk_transform_unref (next); return r; } result = gsk_transform_alloc (&GSK_TRANSLATE_TRANSFORM_CLASS, point->z == 0.0 ? GSK_TRANSFORM_CATEGORY_2D_TRANSLATE : GSK_TRANSFORM_CATEGORY_3D, next); graphene_point3d_init_from_point (&result->point, point); return &result->parent; } /* }}} */ /* {{{ ROTATE */ typedef struct _GskRotateTransform GskRotateTransform; struct _GskRotateTransform { GskTransform parent; float angle; }; static void gsk_rotate_transform_finalize (GskTransform *self) { } static inline void _sincos (float deg, float *out_s, float *out_c) { if (deg == 90.0) { *out_c = 0.0; *out_s = 1.0; } else if (deg == 180.0) { *out_c = -1.0; *out_s = 0.0; } else if (deg == 270.0) { *out_c = 0.0; *out_s = -1.0; } else if (deg == 0.0) { *out_c = 1.0; *out_s = 0.0; } else { float angle = deg * M_PI / 180.0; #ifdef HAVE_SINCOSF sincosf (angle, out_s, out_c); #else *out_s = sinf (angle); *out_c = cosf (angle); #endif } } static void gsk_rotate_transform_to_matrix (GskTransform *transform, graphene_matrix_t *out_matrix) { GskRotateTransform *self = (GskRotateTransform *) transform; float c, s; _sincos (self->angle, &s, &c); graphene_matrix_init_from_2d (out_matrix, c, s, -s, c, 0, 0); } static void gsk_rotate_transform_apply_2d (GskTransform *transform, float *out_xx, float *out_yx, float *out_xy, float *out_yy, float *out_dx, float *out_dy) { GskRotateTransform *self = (GskRotateTransform *) transform; float s, c, xx, xy, yx, yy; _sincos (self->angle, &s, &c); xx = c * *out_xx + s * *out_xy; yx = c * *out_yx + s * *out_yy; xy = -s * *out_xx + c * *out_xy; yy = -s * *out_yx + c * *out_yy; *out_xx = xx; *out_yx = yx; *out_xy = xy; *out_yy = yy; } static GskTransform * gsk_rotate_transform_apply (GskTransform *transform, GskTransform *apply_to) { GskRotateTransform *self = (GskRotateTransform *) transform; return gsk_transform_rotate (apply_to, self->angle); } static GskTransform * gsk_rotate_transform_invert (GskTransform *transform, GskTransform *next) { GskRotateTransform *self = (GskRotateTransform *) transform; return gsk_transform_rotate (next, - self->angle); } static gboolean gsk_rotate_transform_equal (GskTransform *first_transform, GskTransform *second_transform) { GskRotateTransform *first = (GskRotateTransform *) first_transform; GskRotateTransform *second = (GskRotateTransform *) second_transform; return G_APPROX_VALUE (first->angle, second->angle, 0.01f); } static void gsk_rotate_transform_print (GskTransform *transform, GString *string) { GskRotateTransform *self = (GskRotateTransform *) transform; g_string_append (string, "rotate("); string_append_double (string, self->angle); g_string_append (string, ")"); } static const GskTransformClass GSK_ROTATE_TRANSFORM_CLASS = { sizeof (GskRotateTransform), "GskRotateTransform", gsk_rotate_transform_finalize, gsk_rotate_transform_to_matrix, gsk_rotate_transform_apply_2d, NULL, NULL, gsk_rotate_transform_print, gsk_rotate_transform_apply, gsk_rotate_transform_invert, gsk_rotate_transform_equal, }; static inline float normalize_angle (float angle) { if (angle >= 0 && angle < 360) return angle; while (angle >= 360) angle -= 360; while (angle < 0) angle += 360; /* Due to precision issues we may end up with a result that is just * past the allowed range when rounded. For example, something like * -epsilon + 360 when rounded to a float may end up with 360. * So, we handle these cases by returning the exact value 0. */ if (angle >= 360) angle = 0; g_assert (angle < 360.0); g_assert (angle >= 0.0); return angle; } /** * gsk_transform_rotate: * @next: (nullable) (transfer full): the next transform * @angle: the rotation angle, in degrees (clockwise) * * Rotates @next @angle degrees in 2D - or in 3D-speak, around the z axis. * * Returns: The new transform */ GskTransform * gsk_transform_rotate (GskTransform *next, float angle) { GskRotateTransform *result; if (angle == 0.0f) return next; if (gsk_transform_has_class (next, &GSK_ROTATE_TRANSFORM_CLASS)) { GskTransform *r = gsk_transform_rotate (gsk_transform_ref (next->next), ((GskRotateTransform *) next)->angle + angle); gsk_transform_unref (next); return r; } result = gsk_transform_alloc (&GSK_ROTATE_TRANSFORM_CLASS, GSK_TRANSFORM_CATEGORY_2D, next); result->angle = normalize_angle (angle); return &result->parent; } /* }}} */ /* {{{ ROTATE 3D */ typedef struct _GskRotate3dTransform GskRotate3dTransform; struct _GskRotate3dTransform { GskTransform parent; float angle; graphene_vec3_t axis; }; static void gsk_rotate3d_transform_finalize (GskTransform *self) { } static void gsk_rotate3d_transform_to_matrix (GskTransform *transform, graphene_matrix_t *out_matrix) { GskRotate3dTransform *self = (GskRotate3dTransform *) transform; graphene_matrix_init_rotate (out_matrix, self->angle, &self->axis); } static GskTransform * gsk_rotate3d_transform_apply (GskTransform *transform, GskTransform *apply_to) { GskRotate3dTransform *self = (GskRotate3dTransform *) transform; return gsk_transform_rotate_3d (apply_to, self->angle, &self->axis); } static GskTransform * gsk_rotate3d_transform_invert (GskTransform *transform, GskTransform *next) { GskRotate3dTransform *self = (GskRotate3dTransform *) transform; return gsk_transform_rotate_3d (next, - self->angle, &self->axis); } static gboolean gsk_rotate3d_transform_equal (GskTransform *first_transform, GskTransform *second_transform) { GskRotate3dTransform *first = (GskRotate3dTransform *) first_transform; GskRotate3dTransform *second = (GskRotate3dTransform *) second_transform; return G_APPROX_VALUE (first->angle, second->angle, 0.01f) && graphene_vec3_equal (&first->axis, &second->axis); } static void gsk_rotate3d_transform_print (GskTransform *transform, GString *string) { GskRotate3dTransform *self = (GskRotate3dTransform *) transform; float f[3]; guint i; g_string_append (string, "rotate3d("); graphene_vec3_to_float (&self->axis, f); for (i = 0; i < 3; i++) { string_append_double (string, f[i]); g_string_append (string, ", "); } string_append_double (string, self->angle); g_string_append (string, ")"); } static const GskTransformClass GSK_ROTATE3D_TRANSFORM_CLASS = { sizeof (GskRotate3dTransform), "GskRotate3dTransform", gsk_rotate3d_transform_finalize, gsk_rotate3d_transform_to_matrix, NULL, NULL, NULL, gsk_rotate3d_transform_print, gsk_rotate3d_transform_apply, gsk_rotate3d_transform_invert, gsk_rotate3d_transform_equal, }; /** * gsk_transform_rotate_3d: * @next: (nullable) (transfer full): the next transform * @angle: the rotation angle, in degrees (clockwise) * @axis: The rotation axis * * Rotates @next @angle degrees around @axis. * * For a rotation in 2D space, use [method@Gsk.Transform.rotate] * * Returns: The new transform */ GskTransform * gsk_transform_rotate_3d (GskTransform *next, float angle, const graphene_vec3_t *axis) { GskRotate3dTransform *result; if (graphene_vec3_get_x (axis) == 0.0 && graphene_vec3_get_y (axis) == 0.0) return gsk_transform_rotate (next, angle); if (angle == 0.0f) return next; result = gsk_transform_alloc (&GSK_ROTATE3D_TRANSFORM_CLASS, GSK_TRANSFORM_CATEGORY_3D, next); result->angle = normalize_angle (angle); graphene_vec3_init_from_vec3 (&result->axis, axis); return &result->parent; } /* }}} */ /* {{{ SKEW */ typedef struct _GskSkewTransform GskSkewTransform; struct _GskSkewTransform { GskTransform parent; float skew_x; float skew_y; }; static void gsk_skew_transform_finalize (GskTransform *self) { } #define DEG_TO_RAD(x) ((x) / 180.f * G_PI) #define RAD_TO_DEG(x) ((x) * 180.f / G_PI) static void gsk_skew_transform_to_matrix (GskTransform *transform, graphene_matrix_t *out_matrix) { GskSkewTransform *self = (GskSkewTransform *) transform; graphene_matrix_init_skew (out_matrix, DEG_TO_RAD (self->skew_x), DEG_TO_RAD (self->skew_y)); } static void gsk_skew_transform_apply_2d (GskTransform *transform, float *out_xx, float *out_yx, float *out_xy, float *out_yy, float *out_dx, float *out_dy) { graphene_matrix_t sm, mat; gsk_skew_transform_to_matrix (transform, &sm); graphene_matrix_init_from_2d (&mat, *out_xx, *out_yx, *out_xy, *out_yy, *out_dx, *out_dy); graphene_matrix_multiply (&sm, &mat, &mat); *out_xx = graphene_matrix_get_value (&mat, 0, 0); *out_yx = graphene_matrix_get_value (&mat, 0, 1); *out_xy = graphene_matrix_get_value (&mat, 1, 0); *out_yy = graphene_matrix_get_value (&mat, 1, 1); *out_dx = graphene_matrix_get_value (&mat, 3, 0); *out_dy = graphene_matrix_get_value (&mat, 3, 1); } static GskTransform * gsk_skew_transform_apply (GskTransform *transform, GskTransform *apply_to) { GskSkewTransform *self = (GskSkewTransform *) transform; return gsk_transform_skew (apply_to, self->skew_x, self->skew_y); } static void gsk_skew_transform_print (GskTransform *transform, GString *string) { GskSkewTransform *self = (GskSkewTransform *) transform; if (self->skew_y == 0) { g_string_append (string, "skewX("); string_append_double (string, self->skew_x); g_string_append (string, ")"); } else if (self->skew_x == 0) { g_string_append (string, "skewY("); string_append_double (string, self->skew_y); g_string_append (string, ")"); } else { g_string_append (string, "skew("); string_append_double (string, self->skew_x); g_string_append (string, ", "); string_append_double (string, self->skew_y); g_string_append (string, ")"); } } static GskTransform * gsk_skew_transform_invert (GskTransform *transform, GskTransform *next) { GskSkewTransform *self = (GskSkewTransform *) transform; float tx, ty; graphene_matrix_t matrix; tx = tanf (DEG_TO_RAD (self->skew_x)); ty = tanf (DEG_TO_RAD (self->skew_y)); graphene_matrix_init_from_2d (&matrix, 1 / (1 - tx * ty), - ty / (1 - tx * ty), - tx / (1 - tx * ty), 1 / (1 - tx * ty), 0, 0); return gsk_transform_matrix_with_category (next, &matrix, GSK_TRANSFORM_CATEGORY_2D); } static gboolean gsk_skew_transform_equal (GskTransform *first_transform, GskTransform *second_transform) { GskSkewTransform *first = (GskSkewTransform *) first_transform; GskSkewTransform *second = (GskSkewTransform *) second_transform; return G_APPROX_VALUE (first->skew_x, second->skew_x, FLT_EPSILON) && G_APPROX_VALUE (first->skew_y, second->skew_y, FLT_EPSILON); } static const GskTransformClass GSK_SKEW_TRANSFORM_CLASS = { sizeof (GskSkewTransform), "GskSkewTransform", gsk_skew_transform_finalize, gsk_skew_transform_to_matrix, gsk_skew_transform_apply_2d, NULL, NULL, gsk_skew_transform_print, gsk_skew_transform_apply, gsk_skew_transform_invert, gsk_skew_transform_equal, }; /** * gsk_transform_skew: * @next: (nullable) (transfer full): the next transform * @skew_x: skew factor, in degrees, on the X axis * @skew_y: skew factor, in degrees, on the Y axis * * Applies a skew transform. * * Returns: The new transform * * Since: 4.6 */ GskTransform * gsk_transform_skew (GskTransform *next, float skew_x, float skew_y) { GskSkewTransform *result; if (skew_x == 0 && skew_y == 0) return next; result = gsk_transform_alloc (&GSK_SKEW_TRANSFORM_CLASS, GSK_TRANSFORM_CATEGORY_2D, next); result->skew_x = skew_x; result->skew_y = skew_y; return &result->parent; } /* }}} */ /* {{{ SCALE */ typedef struct _GskScaleTransform GskScaleTransform; struct _GskScaleTransform { GskTransform parent; float factor_x; float factor_y; float factor_z; }; static void gsk_scale_transform_finalize (GskTransform *self) { } static void gsk_scale_transform_to_matrix (GskTransform *transform, graphene_matrix_t *out_matrix) { GskScaleTransform *self = (GskScaleTransform *) transform; graphene_matrix_init_scale (out_matrix, self->factor_x, self->factor_y, self->factor_z); } static void gsk_scale_transform_apply_2d (GskTransform *transform, float *out_xx, float *out_yx, float *out_xy, float *out_yy, float *out_dx, float *out_dy) { GskScaleTransform *self = (GskScaleTransform *) transform; g_assert (self->factor_z == 1.0); *out_xx *= self->factor_x; *out_yx *= self->factor_x; *out_xy *= self->factor_y; *out_yy *= self->factor_y; } static void gsk_scale_transform_apply_affine (GskTransform *transform, float *out_scale_x, float *out_scale_y, float *out_dx, float *out_dy) { GskScaleTransform *self = (GskScaleTransform *) transform; g_assert (self->factor_z == 1.0); *out_scale_x *= self->factor_x; *out_scale_y *= self->factor_y; } static GskTransform * gsk_scale_transform_apply (GskTransform *transform, GskTransform *apply_to) { GskScaleTransform *self = (GskScaleTransform *) transform; return gsk_transform_scale_3d (apply_to, self->factor_x, self->factor_y, self->factor_z); } static GskTransform * gsk_scale_transform_invert (GskTransform *transform, GskTransform *next) { GskScaleTransform *self = (GskScaleTransform *) transform; return gsk_transform_scale_3d (next, 1.f / self->factor_x, 1.f / self->factor_y, 1.f / self->factor_z); } static gboolean gsk_scale_transform_equal (GskTransform *first_transform, GskTransform *second_transform) { GskScaleTransform *first = (GskScaleTransform *) first_transform; GskScaleTransform *second = (GskScaleTransform *) second_transform; return G_APPROX_VALUE (first->factor_x, second->factor_x, FLT_EPSILON) && G_APPROX_VALUE (first->factor_y, second->factor_y, FLT_EPSILON) && G_APPROX_VALUE (first->factor_z, second->factor_z, FLT_EPSILON); } static void gsk_scale_transform_print (GskTransform *transform, GString *string) { GskScaleTransform *self = (GskScaleTransform *) transform; if (self->factor_z == 1.0) { g_string_append (string, "scale("); string_append_double (string, self->factor_x); if (self->factor_x != self->factor_y) { g_string_append (string, ", "); string_append_double (string, self->factor_y); } g_string_append (string, ")"); } else { g_string_append (string, "scale3d("); string_append_double (string, self->factor_x); g_string_append (string, ", "); string_append_double (string, self->factor_y); g_string_append (string, ", "); string_append_double (string, self->factor_z); g_string_append (string, ")"); } } static const GskTransformClass GSK_SCALE_TRANSFORM_CLASS = { sizeof (GskScaleTransform), "GskScaleTransform", gsk_scale_transform_finalize, gsk_scale_transform_to_matrix, gsk_scale_transform_apply_2d, gsk_scale_transform_apply_affine, NULL, gsk_scale_transform_print, gsk_scale_transform_apply, gsk_scale_transform_invert, gsk_scale_transform_equal, }; /** * gsk_transform_scale: * @next: (nullable) (transfer full): the next transform * @factor_x: scaling factor on the X axis * @factor_y: scaling factor on the Y axis * * Scales @next in 2-dimensional space by the given factors. * * Use [method@Gsk.Transform.scale_3d] to scale in all 3 dimensions. * * Returns: The new transform **/ GskTransform * gsk_transform_scale (GskTransform *next, float factor_x, float factor_y) { return gsk_transform_scale_3d (next, factor_x, factor_y, 1.0); } /** * gsk_transform_scale_3d: * @next: (nullable) (transfer full): the next transform * @factor_x: scaling factor on the X axis * @factor_y: scaling factor on the Y axis * @factor_z: scaling factor on the Z axis * * Scales @next by the given factors. * * Returns: The new transform **/ GskTransform * gsk_transform_scale_3d (GskTransform *next, float factor_x, float factor_y, float factor_z) { GskScaleTransform *result; if (factor_x == 1 && factor_y == 1 && factor_z == 1) return next; if (gsk_transform_has_class (next, &GSK_SCALE_TRANSFORM_CLASS)) { GskScaleTransform *scale = (GskScaleTransform *) next; GskTransform *r = gsk_transform_scale_3d (gsk_transform_ref (next->next), scale->factor_x * factor_x, scale->factor_y * factor_y, scale->factor_z * factor_z); gsk_transform_unref (next); return r; } result = gsk_transform_alloc (&GSK_SCALE_TRANSFORM_CLASS, factor_z != 1.0 ? GSK_TRANSFORM_CATEGORY_3D : GSK_TRANSFORM_CATEGORY_2D_AFFINE, next); result->factor_x = factor_x; result->factor_y = factor_y; result->factor_z = factor_z; return &result->parent; } /* }}} */ /* {{{ PERSPECTIVE */ typedef struct _GskPerspectiveTransform GskPerspectiveTransform; struct _GskPerspectiveTransform { GskTransform parent; float depth; }; static void gsk_perspective_transform_finalize (GskTransform *self) { } static void gsk_perspective_transform_to_matrix (GskTransform *transform, graphene_matrix_t *out_matrix) { GskPerspectiveTransform *self = (GskPerspectiveTransform *) transform; float f[16] = { 1.f, 0.f, 0.f, 0.f, 0.f, 1.f, 0.f, 0.f, 0.f, 0.f, 1.f, self->depth ? -1.f / self->depth : 0.f, 0.f, 0.f, 0.f, 1.f }; graphene_matrix_init_from_float (out_matrix, f); } static GskTransform * gsk_perspective_transform_apply (GskTransform *transform, GskTransform *apply_to) { GskPerspectiveTransform *self = (GskPerspectiveTransform *) transform; return gsk_transform_perspective (apply_to, self->depth); } static GskTransform * gsk_perspective_transform_invert (GskTransform *transform, GskTransform *next) { GskPerspectiveTransform *self = (GskPerspectiveTransform *) transform; return gsk_transform_perspective (next, - self->depth); } static gboolean gsk_perspective_transform_equal (GskTransform *first_transform, GskTransform *second_transform) { GskPerspectiveTransform *first = (GskPerspectiveTransform *) first_transform; GskPerspectiveTransform *second = (GskPerspectiveTransform *) second_transform; return G_APPROX_VALUE (first->depth, second->depth, 0.001f); } static void gsk_perspective_transform_print (GskTransform *transform, GString *string) { GskPerspectiveTransform *self = (GskPerspectiveTransform *) transform; g_string_append (string, "perspective("); string_append_double (string, self->depth); g_string_append (string, ")"); } static const GskTransformClass GSK_PERSPECTIVE_TRANSFORM_CLASS = { sizeof (GskPerspectiveTransform), "GskPerspectiveTransform", gsk_perspective_transform_finalize, gsk_perspective_transform_to_matrix, NULL, NULL, NULL, gsk_perspective_transform_print, gsk_perspective_transform_apply, gsk_perspective_transform_invert, gsk_perspective_transform_equal, }; /** * gsk_transform_perspective: * @next: (nullable) (transfer full): the next transform * @depth: distance of the z=0 plane. Lower values give a more * flattened pyramid and therefore a more pronounced * perspective effect. * * Applies a perspective projection transform. * * This transform scales points in X and Y based on their Z value, * scaling points with positive Z values away from the origin, and * those with negative Z values towards the origin. Points * on the z=0 plane are unchanged. * * Returns: The new transform */ GskTransform * gsk_transform_perspective (GskTransform *next, float depth) { GskPerspectiveTransform *result; if (gsk_transform_has_class (next, &GSK_PERSPECTIVE_TRANSFORM_CLASS)) { GskTransform *r = gsk_transform_perspective (gsk_transform_ref (next->next), ((GskPerspectiveTransform *) next)->depth + depth); gsk_transform_unref (next); return r; } result = gsk_transform_alloc (&GSK_PERSPECTIVE_TRANSFORM_CLASS, GSK_TRANSFORM_CATEGORY_ANY, next); result->depth = depth; return &result->parent; } /* }}} */ /* {{{ PUBLIC API */ /** * gsk_transform_ref: * @self: (nullable): a `GskTransform` * * Acquires a reference on the given `GskTransform`. * * Returns: (transfer none): the `GskTransform` with an additional reference */ GskTransform * gsk_transform_ref (GskTransform *self) { if (self == NULL) return NULL; return g_atomic_rc_box_acquire (self); } /** * gsk_transform_unref: * @self: (nullable): a `GskTransform` * * Releases a reference on the given `GskTransform`. * * If the reference was the last, the resources associated to the @self are * freed. */ void gsk_transform_unref (GskTransform *self) { if (self == NULL) return; g_atomic_rc_box_release_full (self, (GDestroyNotify) gsk_transform_finalize); } /** * gsk_transform_print: * @self: (nullable): a `GskTransform` * @string: The string to print into * * Converts @self into a human-readable string representation suitable * for printing. * * The result of this function can later be parsed with * [func@Gsk.Transform.parse]. */ void gsk_transform_print (GskTransform *self, GString *string) { g_return_if_fail (string != NULL); if (self == NULL) { g_string_append (string, "none"); return; } if (self->next != NULL) { gsk_transform_print (self->next, string); g_string_append (string, " "); } self->transform_class->print (self, string); } /** * gsk_transform_to_string: * @self: (nullable): a `GskTransform` * * Converts a matrix into a string that is suitable for printing. * * The resulting string can be parsed with [func@Gsk.Transform.parse]. * * This is a wrapper around [method@Gsk.Transform.print]. * * Returns: A new string for @self */ char * gsk_transform_to_string (GskTransform *self) { GString *string; string = g_string_new (""); gsk_transform_print (self, string); return g_string_free (string, FALSE); } /** * gsk_transform_to_matrix: * @self: (nullable): a `GskTransform` * @out_matrix: (out caller-allocates): The matrix to set * * Computes the actual value of @self and stores it in @out_matrix. * * The previous value of @out_matrix will be ignored. */ void gsk_transform_to_matrix (GskTransform *self, graphene_matrix_t *out_matrix) { graphene_matrix_t m; if (self == NULL) { graphene_matrix_init_identity (out_matrix); return; } gsk_transform_to_matrix (self->next, out_matrix); self->transform_class->to_matrix (self, &m); graphene_matrix_multiply (&m, out_matrix, out_matrix); } /** * gsk_transform_to_2d: * @self: a 2D `GskTransform` * @out_xx: (out): return location for the xx member * @out_yx: (out): return location for the yx member * @out_xy: (out): return location for the xy member * @out_yy: (out): return location for the yy member * @out_dx: (out): return location for the x0 member * @out_dy: (out): return location for the y0 member * * Converts a `GskTransform` to a 2D transformation matrix. * * @self must be a 2D transformation. If you are not * sure, use gsk_transform_get_category() >= * %GSK_TRANSFORM_CATEGORY_2D to check. * * The returned values have the following layout: * * ``` * | xx yx | | a b 0 | * | xy yy | = | c d 0 | * | dx dy | | tx ty 1 | * ``` * * This function can be used to convert between a `GskTransform` * and a matrix type from other 2D drawing libraries, in particular * Cairo. */ void gsk_transform_to_2d (GskTransform *self, float *out_xx, float *out_yx, float *out_xy, float *out_yy, float *out_dx, float *out_dy) { *out_xx = 1.0f; *out_yx = 0.0f; *out_xy = 0.0f; *out_yy = 1.0f; *out_dx = 0.0f; *out_dy = 0.0f; if (self == NULL) return; if (G_UNLIKELY (self->category < GSK_TRANSFORM_CATEGORY_2D)) { char *s = gsk_transform_to_string (self); g_warning ("Given transform \"%s\" is not a 2D transform.", s); g_free (s); return; } gsk_transform_to_2d (self->next, out_xx, out_yx, out_xy, out_yy, out_dx, out_dy); self->transform_class->apply_2d (self, out_xx, out_yx, out_xy, out_yy, out_dx, out_dy); } /** * gsk_transform_to_2d_components: * @self: a `GskTransform` * @out_skew_x: (out): return location for the skew factor * in the x direction * @out_skew_y: (out): return location for the skew factor * in the y direction * @out_scale_x: (out): return location for the scale * factor in the x direction * @out_scale_y: (out): return location for the scale * factor in the y direction * @out_angle: (out): return location for the rotation angle * @out_dx: (out): return location for the translation * in the x direction * @out_dy: (out): return location for the translation * in the y direction * * Converts a `GskTransform` to 2D transformation factors. * * To recreate an equivalent transform from the factors returned * by this function, use * * gsk_transform_skew ( * gsk_transform_scale ( * gsk_transform_rotate ( * gsk_transform_translate (NULL, &GRAPHENE_POINT_T (dx, dy)), * angle), * scale_x, scale_y), * skew_x, skew_y) * * @self must be a 2D transformation. If you are not sure, use * * gsk_transform_get_category() >= %GSK_TRANSFORM_CATEGORY_2D * * to check. * * Since: 4.6 */ void gsk_transform_to_2d_components (GskTransform *self, float *out_skew_x, float *out_skew_y, float *out_scale_x, float *out_scale_y, float *out_angle, float *out_dx, float *out_dy) { float a, b, c, d, e, f; gsk_transform_to_2d (self, &a, &b, &c, &d, &e, &f); *out_dx = e; *out_dy = f; #define sign(f) ((f) < 0 ? -1 : 1) if (a != 0 || b != 0) { float det = a * d - b * c; float r = sqrtf (a*a + b*b); *out_angle = RAD_TO_DEG (sign (b) * acosf (a / r)); *out_scale_x = r; *out_scale_y = det / r; *out_skew_x = RAD_TO_DEG (atanf ((a*c + b*d) / (r*r))); *out_skew_y = 0; } else if (c != 0 || d != 0) { float det = a * d - b * c; float s = sqrtf (c*c + d*d); *out_angle = RAD_TO_DEG (G_PI/2 - sign (d) * acosf (-c / s)); *out_scale_x = det / s; *out_scale_y = s; *out_skew_x = 0; *out_skew_y = RAD_TO_DEG (atanf ((a*c + b*d) / (s*s))); } else { *out_angle = 0; *out_scale_x = 0; *out_scale_y = 0; *out_skew_x = 0; *out_skew_y = 0; } } /** * gsk_transform_to_affine: * @self: a `GskTransform` * @out_scale_x: (out): return location for the scale * factor in the x direction * @out_scale_y: (out): return location for the scale * factor in the y direction * @out_dx: (out): return location for the translation * in the x direction * @out_dy: (out): return location for the translation * in the y direction * * Converts a `GskTransform` to 2D affine transformation factors. * * To recreate an equivalent transform from the factors returned * by this function, use * * gsk_transform_scale (gsk_transform_translate (NULL, * &GRAPHENE_POINT_T (dx, dy)), * sx, sy) * * @self must be a 2D affine transformation. If you are not * sure, use * * gsk_transform_get_category() >= %GSK_TRANSFORM_CATEGORY_2D_AFFINE * * to check. */ void gsk_transform_to_affine (GskTransform *self, float *out_scale_x, float *out_scale_y, float *out_dx, float *out_dy) { *out_scale_x = 1.0f; *out_scale_y = 1.0f; *out_dx = 0.0f; *out_dy = 0.0f; if (self == NULL) return; if (G_UNLIKELY (self->category < GSK_TRANSFORM_CATEGORY_2D_AFFINE)) { char *s = gsk_transform_to_string (self); g_warning ("Given transform \"%s\" is not an affine 2D transform.", s); g_free (s); return; } gsk_transform_to_affine (self->next, out_scale_x, out_scale_y, out_dx, out_dy); self->transform_class->apply_affine (self, out_scale_x, out_scale_y, out_dx, out_dy); } /** * gsk_transform_to_translate: * @self: a `GskTransform` * @out_dx: (out): return location for the translation * in the x direction * @out_dy: (out): return location for the translation * in the y direction * * Converts a `GskTransform` to a translation operation. * * @self must be a 2D transformation. If you are not * sure, use * * gsk_transform_get_category() >= %GSK_TRANSFORM_CATEGORY_2D_TRANSLATE * * to check. */ void gsk_transform_to_translate (GskTransform *self, float *out_dx, float *out_dy) { *out_dx = 0.0f; *out_dy = 0.0f; if (self == NULL) return; if (G_UNLIKELY (self->category < GSK_TRANSFORM_CATEGORY_2D_TRANSLATE)) { char *s = gsk_transform_to_string (self); g_warning ("Given transform \"%s\" is not an affine 2D translation.", s); g_free (s); return; } gsk_transform_to_translate (self->next, out_dx, out_dy); self->transform_class->apply_translate (self, out_dx, out_dy); } /** * gsk_transform_transform: * @next: (nullable) (transfer full): Transform to apply @other to * @other: (nullable): Transform to apply * * Applies all the operations from @other to @next. * * Returns: The new transform */ GskTransform * gsk_transform_transform (GskTransform *next, GskTransform *other) { if (other == NULL) return next; if (next == NULL) return gsk_transform_ref (other); if (gsk_transform_is_identity (next)) { /* ref before unref to avoid catastrophe when other == next */ other = gsk_transform_ref (other); gsk_transform_unref (next); return other; } next = gsk_transform_transform (next, other->next); return other->transform_class->apply (other, next); } /** * gsk_transform_invert: * @self: (nullable) (transfer full): Transform to invert * * Inverts the given transform. * * If @self is not invertible, %NULL is returned. * Note that inverting %NULL also returns %NULL, which is * the correct inverse of %NULL. If you need to differentiate * between those cases, you should check @self is not %NULL * before calling this function. * * Returns: (nullable): The inverted transform */ GskTransform * gsk_transform_invert (GskTransform *self) { GskTransform *result = NULL; GskTransform *cur; for (cur = self; cur; cur = cur->next) { result = cur->transform_class->invert (cur, result); if (result == NULL) break; } gsk_transform_unref (self); return result; } /** * gsk_transform_equal: * @first: (nullable): the first transform * @second: (nullable): the second transform * * Checks two transforms for equality. * * Returns: %TRUE if the two transforms perform the same operation */ gboolean gsk_transform_equal (GskTransform *first, GskTransform *second) { if (first == second) return TRUE; if (first == NULL) return gsk_transform_is_identity (second); if (second == NULL) return gsk_transform_is_identity (first); if (first->transform_class != second->transform_class) return FALSE; if (!gsk_transform_equal (first->next, second->next)) return FALSE; return first->transform_class->equal (first, second); } /** * gsk_transform_get_category: * @self: (nullable): A `GskTransform` * * Returns the category this transform belongs to. * * Returns: The category of the transform **/ GskTransformCategory (gsk_transform_get_category) (GskTransform *self) { if (self == NULL) return GSK_TRANSFORM_CATEGORY_IDENTITY; return self->category; } /* * gsk_transform_new: (constructor): * * Creates a new identity transform. * * This function is meant to be used by language * bindings. For C code, this is equivalent to using %NULL. * * Returns: A new identity transform */ GskTransform * gsk_transform_new (void) { return gsk_transform_alloc (&GSK_IDENTITY_TRANSFORM_CLASS, GSK_TRANSFORM_CATEGORY_IDENTITY, NULL); } /** * gsk_transform_transform_bounds: * @self: a `GskTransform` * @rect: a `graphene_rect_t` * @out_rect: (out caller-allocates): return location for the bounds * of the transformed rectangle * * Transforms a `graphene_rect_t` using the given transform @self. * * The result is the bounding box containing the coplanar quad. */ void gsk_transform_transform_bounds (GskTransform *self, const graphene_rect_t *rect, graphene_rect_t *out_rect) { switch (gsk_transform_get_category (self)) { case GSK_TRANSFORM_CATEGORY_IDENTITY: graphene_rect_init_from_rect (out_rect, rect); break; case GSK_TRANSFORM_CATEGORY_2D_TRANSLATE: { float dx, dy; gsk_transform_to_translate (self, &dx, &dy); graphene_rect_init (out_rect, rect->origin.x + dx, rect->origin.y + dy, rect->size.width, rect->size.height); } break; case GSK_TRANSFORM_CATEGORY_2D_AFFINE: { float dx, dy, scale_x, scale_y; gsk_transform_to_affine (self, &scale_x, &scale_y, &dx, &dy); graphene_rect_init (out_rect, (rect->origin.x * scale_x) + dx, (rect->origin.y * scale_y) + dy, rect->size.width * scale_x, rect->size.height * scale_y); } break; case GSK_TRANSFORM_CATEGORY_UNKNOWN: case GSK_TRANSFORM_CATEGORY_ANY: case GSK_TRANSFORM_CATEGORY_3D: case GSK_TRANSFORM_CATEGORY_2D: default: { graphene_matrix_t mat; gsk_transform_to_matrix (self, &mat); gsk_matrix_transform_bounds (&mat, rect, out_rect); } break; } } /** * gsk_transform_transform_point: * @self: a `GskTransform` * @point: a `graphene_point_t` * @out_point: (out caller-allocates): return location for * the transformed point * * Transforms a `graphene_point_t` using the given transform @self. */ void gsk_transform_transform_point (GskTransform *self, const graphene_point_t *point, graphene_point_t *out_point) { switch (gsk_transform_get_category (self)) { case GSK_TRANSFORM_CATEGORY_IDENTITY: *out_point = *point; break; case GSK_TRANSFORM_CATEGORY_2D_TRANSLATE: { float dx, dy; gsk_transform_to_translate (self, &dx, &dy); out_point->x = point->x + dx; out_point->y = point->y + dy; } break; case GSK_TRANSFORM_CATEGORY_2D_AFFINE: { float dx, dy, scale_x, scale_y; gsk_transform_to_affine (self, &scale_x, &scale_y, &dx, &dy); out_point->x = (point->x * scale_x) + dx; out_point->y = (point->y * scale_y) + dy; } break; case GSK_TRANSFORM_CATEGORY_UNKNOWN: case GSK_TRANSFORM_CATEGORY_ANY: case GSK_TRANSFORM_CATEGORY_3D: case GSK_TRANSFORM_CATEGORY_2D: default: { graphene_matrix_t mat; gsk_transform_to_matrix (self, &mat); gsk_matrix_transform_point (&mat, point, out_point); } break; } } static guint gsk_transform_parse_float (GtkCssParser *parser, guint n, gpointer data) { float *f = data; double d; if (!gtk_css_parser_consume_number (parser, &d)) return 0; f[n] = d; return 1; } static guint gsk_transform_parse_scale (GtkCssParser *parser, guint n, gpointer data) { float *f = data; double d; if (!gtk_css_parser_consume_number (parser, &d)) return 0; f[n] = d; f[1] = d; return 1; } gboolean gsk_transform_parser_parse (GtkCssParser *parser, GskTransform **out_transform) { const GtkCssToken *token; GskTransform *transform = NULL; float f[16] = { 0, }; gboolean parsed_something = FALSE; token = gtk_css_parser_get_token (parser); if (gtk_css_token_is_ident (token, "none")) { gtk_css_parser_consume_token (parser); *out_transform = NULL; return TRUE; } while (TRUE) { if (gtk_css_token_is_function (token, "matrix")) { graphene_matrix_t matrix; if (!gtk_css_parser_consume_function (parser, 6, 6, gsk_transform_parse_float, f)) goto fail; graphene_matrix_init_from_2d (&matrix, f[0], f[1], f[2], f[3], f[4], f[5]); transform = gsk_transform_matrix_with_category (transform, &matrix, GSK_TRANSFORM_CATEGORY_2D); } else if (gtk_css_token_is_function (token, "matrix3d")) { graphene_matrix_t matrix; if (!gtk_css_parser_consume_function (parser, 16, 16, gsk_transform_parse_float, f)) goto fail; graphene_matrix_init_from_float (&matrix, f); transform = gsk_transform_matrix (transform, &matrix); } else if (gtk_css_token_is_function (token, "perspective")) { if (!gtk_css_parser_consume_function (parser, 1, 1, gsk_transform_parse_float, f)) goto fail; transform = gsk_transform_perspective (transform, f[0]); } else if (gtk_css_token_is_function (token, "rotate") || gtk_css_token_is_function (token, "rotateZ")) { if (!gtk_css_parser_consume_function (parser, 1, 1, gsk_transform_parse_float, f)) goto fail; transform = gsk_transform_rotate (transform, f[0]); } else if (gtk_css_token_is_function (token, "rotate3d")) { graphene_vec3_t axis; if (!gtk_css_parser_consume_function (parser, 4, 4, gsk_transform_parse_float, f)) goto fail; graphene_vec3_init (&axis, f[0], f[1], f[2]); transform = gsk_transform_rotate_3d (transform, f[3], &axis); } else if (gtk_css_token_is_function (token, "rotateX")) { if (!gtk_css_parser_consume_function (parser, 1, 1, gsk_transform_parse_float, f)) goto fail; transform = gsk_transform_rotate_3d (transform, f[0], graphene_vec3_x_axis ()); } else if (gtk_css_token_is_function (token, "rotateY")) { if (!gtk_css_parser_consume_function (parser, 1, 1, gsk_transform_parse_float, f)) goto fail; transform = gsk_transform_rotate_3d (transform, f[0], graphene_vec3_y_axis ()); } else if (gtk_css_token_is_function (token, "scale")) { if (!gtk_css_parser_consume_function (parser, 1, 2, gsk_transform_parse_scale, f)) goto fail; transform = gsk_transform_scale (transform, f[0], f[1]); } else if (gtk_css_token_is_function (token, "scale3d")) { if (!gtk_css_parser_consume_function (parser, 3, 3, gsk_transform_parse_float, f)) goto fail; transform = gsk_transform_scale_3d (transform, f[0], f[1], f[2]); } else if (gtk_css_token_is_function (token, "scaleX")) { if (!gtk_css_parser_consume_function (parser, 1, 1, gsk_transform_parse_float, f)) goto fail; transform = gsk_transform_scale (transform, f[0], 1.f); } else if (gtk_css_token_is_function (token, "scaleY")) { if (!gtk_css_parser_consume_function (parser, 1, 1, gsk_transform_parse_float, f)) goto fail; transform = gsk_transform_scale (transform, 1.f, f[0]); } else if (gtk_css_token_is_function (token, "scaleZ")) { if (!gtk_css_parser_consume_function (parser, 1, 1, gsk_transform_parse_float, f)) goto fail; transform = gsk_transform_scale_3d (transform, 1.f, 1.f, f[0]); } else if (gtk_css_token_is_function (token, "translate")) { f[1] = 0.f; if (!gtk_css_parser_consume_function (parser, 1, 2, gsk_transform_parse_float, f)) goto fail; transform = gsk_transform_translate (transform, &GRAPHENE_POINT_INIT (f[0], f[1])); } else if (gtk_css_token_is_function (token, "translate3d")) { if (!gtk_css_parser_consume_function (parser, 3, 3, gsk_transform_parse_float, f)) goto fail; transform = gsk_transform_translate_3d (transform, &GRAPHENE_POINT3D_INIT (f[0], f[1], f[2])); } else if (gtk_css_token_is_function (token, "translateX")) { if (!gtk_css_parser_consume_function (parser, 1, 1, gsk_transform_parse_float, f)) goto fail; transform = gsk_transform_translate (transform, &GRAPHENE_POINT_INIT (f[0], 0.f)); } else if (gtk_css_token_is_function (token, "translateY")) { if (!gtk_css_parser_consume_function (parser, 1, 1, gsk_transform_parse_float, f)) goto fail; transform = gsk_transform_translate (transform, &GRAPHENE_POINT_INIT (0.f, f[0])); } else if (gtk_css_token_is_function (token, "translateZ")) { if (!gtk_css_parser_consume_function (parser, 1, 1, gsk_transform_parse_float, f)) goto fail; transform = gsk_transform_translate_3d (transform, &GRAPHENE_POINT3D_INIT (0.f, 0.f, f[0])); } else if (gtk_css_token_is_function (token, "skew")) { if (!gtk_css_parser_consume_function (parser, 2, 2, gsk_transform_parse_float, f)) goto fail; transform = gsk_transform_skew (transform, f[0], f[1]); } else if (gtk_css_token_is_function (token, "skewX")) { if (!gtk_css_parser_consume_function (parser, 1, 1, gsk_transform_parse_float, f)) goto fail; transform = gsk_transform_skew (transform, f[0], 0); } else if (gtk_css_token_is_function (token, "skewY")) { if (!gtk_css_parser_consume_function (parser, 1, 1, gsk_transform_parse_float, f)) goto fail; transform = gsk_transform_skew (transform, 0, f[0]); } else { break; } parsed_something = TRUE; token = gtk_css_parser_get_token (parser); } if (!parsed_something) { gtk_css_parser_error_syntax (parser, "Expected a transform"); goto fail; } *out_transform = transform; return TRUE; fail: gsk_transform_unref (transform); *out_transform = NULL; return FALSE; } /** * gsk_transform_parse: * @string: the string to parse * @out_transform: (out): The location to put the transform in * * Parses the given @string into a transform and puts it in * @out_transform. * * Strings printed via [method@Gsk.Transform.to_string] * can be read in again successfully using this function. * * If @string does not describe a valid transform, %FALSE is * returned and %NULL is put in @out_transform. * * Returns: %TRUE if @string described a valid transform. */ gboolean gsk_transform_parse (const char *string, GskTransform **out_transform) { GtkCssParser *parser; GBytes *bytes; gboolean result; g_return_val_if_fail (string != NULL, FALSE); g_return_val_if_fail (out_transform != NULL, FALSE); bytes = g_bytes_new_static (string, strlen (string)); parser = gtk_css_parser_new_for_bytes (bytes, NULL, NULL, NULL, NULL); result = gsk_transform_parser_parse (parser, out_transform); if (result && !gtk_css_parser_has_token (parser, GTK_CSS_TOKEN_EOF)) { g_clear_pointer (out_transform, gsk_transform_unref); result = FALSE; } gtk_css_parser_unref (parser); g_bytes_unref (bytes); return result; } /* Some of the graphene_matrix_transform apis yield unexpected * results with projective matrices, since they silently drop * the w component, so we provide working alternatives here. */ void gsk_matrix_transform_point (const graphene_matrix_t *m, const graphene_point_t *p, graphene_point_t *res) { graphene_vec4_t vec4; float w; graphene_vec4_init (&vec4, p->x, p->y, 0.0f, 1.0f); graphene_matrix_transform_vec4 (m, &vec4, &vec4); w = graphene_vec4_get_w (&vec4); res->x = graphene_vec4_get_x (&vec4) / w; res->y = graphene_vec4_get_y (&vec4) / w; } void gsk_matrix_transform_point3d (const graphene_matrix_t *m, const graphene_point3d_t *p, graphene_point3d_t *res) { graphene_vec4_t vec4; float w; graphene_vec4_init (&vec4, p->x, p->y, 0.0f, 1.0f); graphene_matrix_transform_vec4 (m, &vec4, &vec4); w = graphene_vec4_get_w (&vec4); res->x = graphene_vec4_get_x (&vec4) / w; res->y = graphene_vec4_get_y (&vec4) / w; res->z = graphene_vec4_get_z (&vec4) / w; } void gsk_matrix_transform_rect (const graphene_matrix_t *m, const graphene_rect_t *r, graphene_quad_t *res) { graphene_point_t ret[4]; graphene_rect_t rr; graphene_rect_normalize_r (r, &rr); #define TRANSFORM_POINT(matrix, rect, corner, out_p) do {\ graphene_vec4_t __s; \ graphene_point_t __p; \ float w; \ graphene_rect_get_ ## corner (rect, &__p); \ graphene_vec4_init (&__s, __p.x, __p.y, 0.f, 1.f); \ graphene_matrix_transform_vec4 (matrix, &__s, &__s); \ w = graphene_vec4_get_w (&__s); \ out_p.x = graphene_vec4_get_x (&__s) / w; \ out_p.y = graphene_vec4_get_y (&__s) / w; } while (0) TRANSFORM_POINT (m, &rr, top_left, ret[0]); TRANSFORM_POINT (m, &rr, top_right, ret[1]); TRANSFORM_POINT (m, &rr, bottom_right, ret[2]); TRANSFORM_POINT (m, &rr, bottom_left, ret[3]); #undef TRANSFORM_POINT graphene_quad_init (res, &ret[0], &ret[1], &ret[2], &ret[3]); } void gsk_matrix_transform_bounds (const graphene_matrix_t *m, const graphene_rect_t *r, graphene_rect_t *res) { graphene_quad_t q; gsk_matrix_transform_rect (m, r, &q); graphene_quad_bounds (&q, res); } /* }}} */ /* vim:set foldmethod=marker expandtab: */