gtk/gsk/gsktransform.c
Sebastian Dröge c44badd26a Mark various GskTransform functions as nullable in their return value
Various transforms are normalized with their next transform, and if they
end up being the identity transform will return NULL.

For example a translation by (x,y,z) and followed by (-x,-y,-z) will
result in NULL.
2022-01-14 11:57:23 +02:00

2490 lines
70 KiB
C

/*
* 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 <http://www.gnu.org/licenses/>.
*
* Authors: Benjamin Otte <otte@gnome.org>
*/
/**
* 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,
};
/*<private>
* 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: (nullable): 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: (nullable): 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: (nullable): 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: (nullable): 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: (nullable): 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: (nullable): 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: (nullable): 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: (nullable) (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: (nullable): 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: */