Add gsk_transform_to_2d_components

This function decomposes a general 2D transform
into skew, scale, rotation and translation.

Tests included.
This commit is contained in:
Matthias Clasen 2021-09-18 02:06:00 -04:00
parent 5742483422
commit 41b810da7f
3 changed files with 194 additions and 13 deletions

View File

@ -121,6 +121,14 @@ gsk_transform_alloc (const GskTransformClass *transform_class,
return self;
}
static void
gsk_transform_finalize (GskTransform *self)
{
self->transform_class->finalize (self);
gsk_transform_unref (self->next);
}
/* }}} */
/* {{{ IDENTITY */
@ -1022,6 +1030,9 @@ 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)
@ -1029,8 +1040,8 @@ gsk_skew_transform_to_matrix (GskTransform *transform,
GskSkewTransform *self = (GskSkewTransform *) transform;
graphene_matrix_init_skew (out_matrix,
self->skew_x / 180.0 * G_PI,
self->skew_y / 180.0 * G_PI);
DEG_TO_RAD (self->skew_x),
DEG_TO_RAD (self->skew_y));
}
static void
@ -1104,8 +1115,8 @@ gsk_skew_transform_invert (GskTransform *transform,
float tx, ty;
graphene_matrix_t matrix;
tx = tanf (self->skew_x / 180.0 * G_PI);
ty = tanf (self->skew_y / 180.0 * G_PI);
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),
@ -1506,14 +1517,6 @@ gsk_transform_perspective (GskTransform *next,
/* }}} */
/* {{{ PUBLIC API */
static void
gsk_transform_finalize (GskTransform *self)
{
self->transform_class->finalize (self);
gsk_transform_unref (self->next);
}
/**
* gsk_transform_ref:
* @self: (nullable): a `GskTransform`
@ -1697,6 +1700,95 @@ gsk_transform_to_2d (GskTransform *self,
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`
@ -1718,7 +1810,7 @@ gsk_transform_to_2d (GskTransform *self,
* &GRAPHENE_POINT_T (dx, dy)),
* sx, sy)
*
* @self must be a 2D transformation. If you are not
* @self must be a 2D affine transformation. If you are not
* sure, use
*
* gsk_transform_get_category() >= %GSK_TRANSFORM_CATEGORY_2D_AFFINE

View File

@ -59,6 +59,15 @@ void gsk_transform_to_2d (GskTransform
float *out_yy,
float *out_dx,
float *out_dy);
GDK_AVAILABLE_IN_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);
GDK_AVAILABLE_IN_ALL
void gsk_transform_to_affine (GskTransform *self,
float *out_scale_x,

View File

@ -657,6 +657,84 @@ test_to_2d (void)
g_assert_cmpfloat (dy, ==, 0.0);
}
static void
test_to_2d_components (void)
{
GskTransform *transform, *transform2;
float skew_x, skew_y, scale_x, scale_y, angle, dx, dy;
graphene_matrix_t m, m2;
transform = gsk_transform_scale (
gsk_transform_rotate (
gsk_transform_translate (NULL, &GRAPHENE_POINT_INIT (10, 20)),
22),
3, 3);
gsk_transform_to_2d_components (transform,
&skew_x, &skew_y,
&scale_x, &scale_y,
&angle,
&dx, &dy);
g_assert_cmpfloat_with_epsilon (skew_x, 0, 0.0001);
g_assert_cmpfloat_with_epsilon (skew_y, 0, 0.0001);
g_assert_cmpfloat_with_epsilon (scale_x, 3, 0.0001);
g_assert_cmpfloat_with_epsilon (scale_y, 3, 0.0001);
g_assert_cmpfloat_with_epsilon (angle, 22, 0.0001);
g_assert_cmpfloat_with_epsilon (dx, 10, 0.0001);
g_assert_cmpfloat_with_epsilon (dy, 20, 0.0001);
gsk_transform_unref (transform);
transform = gsk_transform_skew (
gsk_transform_scale (
gsk_transform_rotate (
gsk_transform_translate (NULL, &GRAPHENE_POINT_INIT (10, 20)),
22),
3, 6),
33, 0);
g_assert_true (gsk_transform_get_category (transform) >= GSK_TRANSFORM_CATEGORY_2D);
gsk_transform_to_2d_components (transform,
&skew_x, &skew_y,
&scale_x, &scale_y,
&angle,
&dx, &dy);
transform2 = gsk_transform_skew (
gsk_transform_scale (
gsk_transform_rotate (
gsk_transform_translate (NULL, &GRAPHENE_POINT_INIT (dx, dy)),
angle),
scale_x, scale_y),
skew_x, skew_y);
gsk_transform_to_matrix (transform, &m);
gsk_transform_to_matrix (transform2, &m2);
g_assert_true (graphene_matrix_near (&m, &m2, 0.001));
gsk_transform_unref (transform);
gsk_transform_unref (transform2);
}
static void
test_transform_point (void)
{
GskTransform *t, *t2;
graphene_point_t p;
t = gsk_transform_scale (gsk_transform_translate (NULL, &GRAPHENE_POINT_INIT (1, 2)), 2, 2);
t2 = gsk_transform_translate (gsk_transform_scale (NULL, 2, 2), &GRAPHENE_POINT_INIT (1, 2));
gsk_transform_transform_point (t, &GRAPHENE_POINT_INIT (1,1), &p);
g_assert_true (graphene_point_equal (&p, &GRAPHENE_POINT_INIT (3, 4)));
gsk_transform_transform_point (t2, &GRAPHENE_POINT_INIT (1,1), &p);
g_assert_true (graphene_point_equal (&p, &GRAPHENE_POINT_INIT (4, 6)));
gsk_transform_unref (t);
gsk_transform_unref (t2);
}
int
main (int argc,
char *argv[])
@ -672,7 +750,9 @@ main (int argc,
g_test_add_func ("/transform/check-axis-aligneness", test_axis_aligned);
g_test_add_func ("/transform/to-affine", test_to_affine);
g_test_add_func ("/transform/bounds", test_transform_bounds);
g_test_add_func ("/transform/point", test_transform_point);
g_test_add_func ("/transform/to-2d", test_to_2d);
g_test_add_func ("/transform/to-2d-components", test_to_2d_components);
return g_test_run ();
}