From 3a3b325f8ebba43fafac574046630a02eae21146 Mon Sep 17 00:00:00 2001 From: Benjamin Otte Date: Tue, 5 Mar 2019 09:27:30 +0100 Subject: transform: Add perspective() This commit adds gsk_transform_perspective(), gtk_snapshot_perspective() and support for perspective() in the CSS syntax. --- docs/reference/gsk/gsk4-sections.txt | 1 + docs/reference/gtk/gtk4-sections.txt | 1 + gsk/gsktransform.c | 114 +++++++++++++++++++++++++++++++ gsk/gsktransform.h | 3 + gtk/gtkcsstransformvalue.c | 129 +++++++++++++++++++++++++++++------ gtk/gtksnapshot.c | 25 ++++++- gtk/gtksnapshot.h | 3 + testsuite/gtk/transform.c | 4 ++ 8 files changed, 259 insertions(+), 21 deletions(-) diff --git a/docs/reference/gsk/gsk4-sections.txt b/docs/reference/gsk/gsk4-sections.txt index 980be45b99..c66c32cfcb 100644 --- a/docs/reference/gsk/gsk4-sections.txt +++ b/docs/reference/gsk/gsk4-sections.txt @@ -171,6 +171,7 @@ gsk_transform_rotate gsk_transform_rotate_3d gsk_transform_scale gsk_transform_scale_3d +gsk_transform_perspective gsk_transform_equal diff --git a/docs/reference/gtk/gtk4-sections.txt b/docs/reference/gtk/gtk4-sections.txt index 2b15df5708..911135eb39 100644 --- a/docs/reference/gtk/gtk4-sections.txt +++ b/docs/reference/gtk/gtk4-sections.txt @@ -4388,6 +4388,7 @@ gtk_snapshot_rotate gtk_snapshot_rotate_3d gtk_snapshot_scale gtk_snapshot_scale_3d +gtk_snapshot_perspective gtk_snapshot_append_node gtk_snapshot_append_cairo gtk_snapshot_append_texture diff --git a/gsk/gsktransform.c b/gsk/gsktransform.c index b434d4cb31..a4da1cfad3 100644 --- a/gsk/gsktransform.c +++ b/gsk/gsktransform.c @@ -1095,6 +1095,120 @@ gsk_transform_scale_3d (GskTransform *next, 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, -1.f / self->depth, + 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 first->depth == second->depth; +} + +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: (allow-none): 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 matrix + **/ +GskTransform * +gsk_transform_perspective (GskTransform *next, + float depth) +{ + GskPerspectiveTransform *result; + + result = gsk_transform_alloc (&GSK_PERSPECTIVE_TRANSFORM_CLASS, + GSK_TRANSFORM_CATEGORY_ANY, + next); + + result->depth = depth; + + return &result->parent; +} + /*** PUBLIC API ***/ static void diff --git a/gsk/gsktransform.h b/gsk/gsktransform.h index 19552a0add..6cbe3659f2 100644 --- a/gsk/gsktransform.h +++ b/gsk/gsktransform.h @@ -105,6 +105,9 @@ GskTransform * gsk_transform_scale_3d (GskTransform float factor_x, float factor_y, float factor_z); +GDK_AVAILABLE_IN_ALL +GskTransform * gsk_transform_perspective (GskTransform *next, + float depth); GDK_AVAILABLE_IN_ALL void gsk_transform_transform_bounds (GskTransform *self, diff --git a/gtk/gtkcsstransformvalue.c b/gtk/gtkcsstransformvalue.c index 83aadfcf37..51e9a06fdc 100644 --- a/gtk/gtkcsstransformvalue.c +++ b/gtk/gtkcsstransformvalue.c @@ -35,7 +35,8 @@ typedef enum { GTK_CSS_TRANSFORM_SCALE, GTK_CSS_TRANSFORM_SKEW, GTK_CSS_TRANSFORM_SKEW_X, - GTK_CSS_TRANSFORM_SKEW_Y + GTK_CSS_TRANSFORM_SKEW_Y, + GTK_CSS_TRANSFORM_PERSPECTIVE } GtkCssTransformType; union _GtkCssTransform { @@ -66,6 +67,10 @@ union _GtkCssTransform { GtkCssTransformType type; GtkCssValue *skew; } skew_x, skew_y; + struct { + GtkCssTransformType type; + GtkCssValue *depth; + } perspective; }; struct _GtkCssValue { @@ -110,6 +115,9 @@ gtk_css_transform_clear (GtkCssTransform *transform) case GTK_CSS_TRANSFORM_SKEW_Y: _gtk_css_value_unref (transform->skew_y.skew); break; + case GTK_CSS_TRANSFORM_PERSPECTIVE: + _gtk_css_value_unref (transform->perspective.depth); + break; case GTK_CSS_TRANSFORM_NONE: default: g_assert_not_reached (); @@ -117,7 +125,7 @@ gtk_css_transform_clear (GtkCssTransform *transform) } } -static void +static gboolean gtk_css_transform_init_identity (GtkCssTransform *transform, GtkCssTransformType type) { @@ -152,13 +160,18 @@ gtk_css_transform_init_identity (GtkCssTransform *transform, case GTK_CSS_TRANSFORM_SKEW_Y: transform->skew_y.skew = _gtk_css_number_value_new (0, GTK_CSS_DEG); break; + case GTK_CSS_TRANSFORM_PERSPECTIVE: + return FALSE; + case GTK_CSS_TRANSFORM_NONE: default: g_assert_not_reached (); - break; + return FALSE; } transform->type = type; + + return TRUE; } static GskTransform * @@ -198,7 +211,7 @@ gtk_css_transform_apply (const GtkCssTransform *transform, _gtk_css_number_value_get (transform->scale.x, 1), _gtk_css_number_value_get (transform->scale.y, 1), _gtk_css_number_value_get (transform->scale.z, 1)); - break; + case GTK_CSS_TRANSFORM_SKEW: graphene_matrix_init_skew (&skew, _gtk_css_number_value_get (transform->skew.x, 100) / 180.0f * G_PI, @@ -217,6 +230,10 @@ gtk_css_transform_apply (const GtkCssTransform *transform, _gtk_css_number_value_get (transform->skew_y.skew, 100) / 180.0f * G_PI); return gsk_transform_matrix (next, &skew); + case GTK_CSS_TRANSFORM_PERSPECTIVE: + return gsk_transform_perspective (next, + _gtk_css_number_value_get (transform->perspective.depth, 100)); + case GTK_CSS_TRANSFORM_NONE: default: g_assert_not_reached (); @@ -303,6 +320,9 @@ gtk_css_transform_compute (GtkCssTransform *dest, case GTK_CSS_TRANSFORM_SKEW_Y: dest->skew_y.skew = _gtk_css_value_compute (src->skew_y.skew, property_id, provider, style, parent_style); return dest->skew_y.skew == src->skew_y.skew; + case GTK_CSS_TRANSFORM_PERSPECTIVE: + dest->perspective.depth = _gtk_css_value_compute (src->perspective.depth, property_id, provider, style, parent_style); + return dest->perspective.depth == src->perspective.depth; case GTK_CSS_TRANSFORM_NONE: default: g_assert_not_reached (); @@ -389,6 +409,8 @@ gtk_css_transform_equal (const GtkCssTransform *transform1, return _gtk_css_value_equal (transform1->skew_x.skew, transform2->skew_x.skew); case GTK_CSS_TRANSFORM_SKEW_Y: return _gtk_css_value_equal (transform1->skew_y.skew, transform2->skew_y.skew); + case GTK_CSS_TRANSFORM_PERSPECTIVE: + return _gtk_css_value_equal (transform1->perspective.depth, transform2->perspective.depth); case GTK_CSS_TRANSFORM_NONE: default: g_assert_not_reached (); @@ -416,7 +438,8 @@ gtk_css_value_transform_equal (const GtkCssValue *value1, { GtkCssTransform transform; - gtk_css_transform_init_identity (&transform, larger->transforms[i].type); + if (!gtk_css_transform_init_identity (&transform, larger->transforms[i].type)) + return FALSE; if (!gtk_css_transform_equal (&larger->transforms[i], &transform)) { @@ -430,6 +453,38 @@ gtk_css_value_transform_equal (const GtkCssValue *value1, return TRUE; } +static void +gtk_css_transform_transition_default (GtkCssTransform *result, + const GtkCssTransform *start, + const GtkCssTransform *end, + guint property_id, + double progress) +{ + graphene_matrix_t start_mat, end_mat; + GskTransform *trans; + + result->type = GTK_CSS_TRANSFORM_MATRIX; + + if (start) + trans = gtk_css_transform_apply (start, NULL); + else + trans = NULL; + gsk_transform_to_matrix (trans, &start_mat); + gsk_transform_unref (trans); + + if (end) + trans = gtk_css_transform_apply (end, NULL); + else + trans = NULL; + gsk_transform_to_matrix (trans, &end_mat); + gsk_transform_unref (trans); + + graphene_matrix_interpolate (&start_mat, + &end_mat, + progress, + &result->matrix.matrix); +} + static void gtk_css_transform_transition (GtkCssTransform *result, const GtkCssTransform *start, @@ -473,6 +528,9 @@ gtk_css_transform_transition (GtkCssTransform *result, case GTK_CSS_TRANSFORM_SKEW_Y: result->skew_y.skew = _gtk_css_value_transition (start->skew_y.skew, end->skew_y.skew, property_id, progress); break; + case GTK_CSS_TRANSFORM_PERSPECTIVE: + gtk_css_transform_transition_default (result, start, end, property_id, progress); + break; case GTK_CSS_TRANSFORM_NONE: default: g_assert_not_reached (); @@ -546,25 +604,45 @@ gtk_css_value_transform_transition (GtkCssValue *start, { GtkCssTransform transform; - gtk_css_transform_init_identity (&transform, start->transforms[i].type); - gtk_css_transform_transition (&result->transforms[i], - &start->transforms[i], - &transform, - property_id, - progress); - gtk_css_transform_clear (&transform); + if (gtk_css_transform_init_identity (&transform, start->transforms[i].type)) + { + gtk_css_transform_transition (&result->transforms[i], + &start->transforms[i], + &transform, + property_id, + progress); + gtk_css_transform_clear (&transform); + } + else + { + gtk_css_transform_transition_default (&result->transforms[i], + &start->transforms[i], + NULL, + property_id, + progress); + } } for (; i < end->n_transforms; i++) { GtkCssTransform transform; - gtk_css_transform_init_identity (&transform, end->transforms[i].type); - gtk_css_transform_transition (&result->transforms[i], - &transform, - &end->transforms[i], - property_id, - progress); - gtk_css_transform_clear (&transform); + if (gtk_css_transform_init_identity (&transform, end->transforms[i].type)) + { + gtk_css_transform_transition (&result->transforms[i], + &transform, + &end->transforms[i], + property_id, + progress); + gtk_css_transform_clear (&transform); + } + else + { + gtk_css_transform_transition_default (&result->transforms[i], + NULL, + &end->transforms[i], + property_id, + progress); + } } g_assert (i == MAX (start->n_transforms, end->n_transforms)); @@ -678,6 +756,11 @@ gtk_css_transform_print (const GtkCssTransform *transform, _gtk_css_value_print (transform->skew_y.skew, string); g_string_append (string, ")"); break; + case GTK_CSS_TRANSFORM_PERSPECTIVE: + g_string_append (string, "perspective("); + _gtk_css_value_print (transform->perspective.depth, string); + g_string_append (string, ")"); + break; case GTK_CSS_TRANSFORM_NONE: default: g_assert_not_reached (); @@ -1046,6 +1129,14 @@ gtk_css_transform_parse (GtkCssTransform *transform, if (transform->skew_y.skew == NULL) return FALSE; } + else if (_gtk_css_parser_try (parser, "perspective(", TRUE)) + { + transform->type = GTK_CSS_TRANSFORM_PERSPECTIVE; + + transform->perspective.depth = _gtk_css_number_value_parse (parser, GTK_CSS_PARSE_LENGTH); + if (transform->perspective.depth == NULL) + return FALSE; + } else { _gtk_css_parser_error (parser, "unknown syntax for transform"); diff --git a/gtk/gtksnapshot.c b/gtk/gtksnapshot.c index cdf14f6b0c..dde973a867 100644 --- a/gtk/gtksnapshot.c +++ b/gtk/gtksnapshot.c @@ -1354,7 +1354,7 @@ gtk_snapshot_rotate_3d (GtkSnapshot *snapshot, * @factor_x: scaling factor on the X axis * @factor_y: scaling factor on the Y axis * - * Scales @@snapshot's coordinate system in 2-dimensional space by + * Scales @snapshot's coordinate system in 2-dimensional space by * the given factors. * * Use gtk_snapshot_scale_3d() to scale in all 3 dimensions. @@ -1379,7 +1379,7 @@ gtk_snapshot_scale (GtkSnapshot *snapshot, * @factor_y: scaling factor on the Y axis * @factor_z: scaling factor on the Z axis * - * Scales @@snapshot's coordinate system by the given factors. + * Scales @snapshot's coordinate system by the given factors. */ void gtk_snapshot_scale_3d (GtkSnapshot *snapshot, @@ -1395,6 +1395,27 @@ gtk_snapshot_scale_3d (GtkSnapshot *snapshot, state->transform = gsk_transform_scale_3d (state->transform, factor_x, factor_y, factor_z); } +/** + * gtk_snapshot_perspective: + * @snapshot: a #GtkSnapshot + * @depth: distance of the z=0 plane + * + * Applies a perspective projection transform. + * + * See gsk_transform_perspective() for a discussion on the details. + */ +void +gtk_snapshot_perspective (GtkSnapshot *snapshot, + float depth) +{ + GtkSnapshotState *state; + + g_return_if_fail (GTK_IS_SNAPSHOT (snapshot)); + + state = gtk_snapshot_get_current_state (snapshot); + state->transform = gsk_transform_perspective (state->transform, depth); +} + void gtk_snapshot_append_node_internal (GtkSnapshot *snapshot, GskRenderNode *node) diff --git a/gtk/gtksnapshot.h b/gtk/gtksnapshot.h index d0e8dd9a4f..91c050d048 100644 --- a/gtk/gtksnapshot.h +++ b/gtk/gtksnapshot.h @@ -134,6 +134,9 @@ void gtk_snapshot_scale_3d (GtkSnapshot float factor_y, float factor_z); GDK_AVAILABLE_IN_ALL +void gtk_snapshot_perspective (GtkSnapshot *snapshot, + float depth); +GDK_AVAILABLE_IN_ALL void gtk_snapshot_append_node (GtkSnapshot *snapshot, GskRenderNode *node); GDK_AVAILABLE_IN_ALL diff --git a/testsuite/gtk/transform.c b/testsuite/gtk/transform.c index b3db0881cf..6026463b90 100644 --- a/testsuite/gtk/transform.c +++ b/testsuite/gtk/transform.c @@ -85,6 +85,7 @@ static struct { { GSK_TRANSFORM_CATEGORY_3D }, { GSK_TRANSFORM_CATEGORY_2D_AFFINE }, { GSK_TRANSFORM_CATEGORY_3D }, + { GSK_TRANSFORM_CATEGORY_ANY }, }; static GskTransform * @@ -117,6 +118,9 @@ apply_test_transform (GskTransform *transform, case 7: return gsk_transform_scale_3d (transform, 2, 3, 5); + case 8: + return gsk_transform_perspective (transform, 5); + default: g_assert_not_reached (); return NULL; -- cgit v1.2.1