/* GTK - The GIMP Toolkit
* Copyright ( C ) 2016 Benjamin Otte < otte @ gnome . org >
*
* 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 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/>.
*/
#include "config.h"
#include "gtksnapshot.h"
#include "gtksnapshotprivate.h"
#include "gtkcsscolorvalueprivate.h"
#include "gtkcssshadowvalueprivate.h"
#include "gtkdebug.h"
#include "gtkrendernodepaintableprivate.h"
#include "gsktransformprivate.h"
#include "gdk/gdkrgbaprivate.h"
#include "gdk/gdkcolorstateprivate.h"
#include "gsk/gskarithmeticnodeprivate.h"
#include "gsk/gskbordernodeprivate.h"
#include "gsk/gskclipnodeprivate.h"
#include "gsk/gskcolormatrixnodeprivate.h"
#include "gsk/gskcolornodeprivate.h"
#include "gsk/gskconicgradientnodeprivate.h"
#include "gsk/gskdisplacementnodeprivate.h"
#include "gsk/gskinsetshadownodeprivate.h"
#include "gsk/gskisolationnodeprivate.h"
#include "gsk/gsklineargradientnodeprivate.h"
#include "gsk/gskoutsetshadownodeprivate.h"
#include "gsk/gskpastenodeprivate.h"
#include "gsk/gskradialgradientnodeprivate.h"
#include "gsk/gskrendernodeprivate.h"
#include "gsk/gskrepeatnodeprivate.h"
#include "gsk/gskroundedclipnodeprivate.h"
#include "gsk/gskroundedrectprivate.h"
#include "gsk/gskstrokeprivate.h"
#include "gsk/gsktextnodeprivate.h"
#include "gsk/gsktexturenodeprivate.h"
#include "gsk/gsktexturescalenodeprivate.h"
#include "gsk/gskturbulencenodeprivate.h"
#include "gsk/gskrectprivate.h"
#include "gtk/gskpangoprivate.h"
#define GDK_ARRAY_NAME gtk_snapshot_nodes
#define GDK_ARRAY_TYPE_NAME GtkSnapshotNodes
#define GDK_ARRAY_ELEMENT_TYPE GskRenderNode *
#define GDK_ARRAY_FREE_FUNC gsk_render_node_unref
#include "gdk/gdkarrayimpl.c"
/**
* GtkSnapshot :
*
* Assists in creating [ class @ Gsk . RenderNode ] s for widgets .
*
* It functions in a similar way to a cairo context , and maintains a stack
* of render nodes and their associated transformations .
*
* The node at the top of the stack is the one that ` gtk_snapshot_append_ … ( ) `
* functions operate on . Use the ` gtk_snapshot_push_ … ( ) ` functions and
* [ method @ Snapshot . pop ] to change the current node .
*
* The typical way to obtain a ` GtkSnapshot ` object is as an argument to
* the [ vfunc @ Gtk . Widget . snapshot ] vfunc . If you need to create your own
* ` GtkSnapshot ` , use [ ctor @ Gtk . Snapshot . new ] .
*
* Note that ` GtkSnapshot ` applies some optimizations , so the node
* it produces may not match the API calls 1 : 1 . For example , it will
* omit clip nodes if the child node is entirely contained within the
* clip rectangle .
*/
typedef struct _GtkSnapshotState GtkSnapshotState;
typedef struct _GtkSnapshotProperties GtkSnapshotProperties;
typedef GskRenderNode * (* GtkSnapshotCollectFunc) (GtkSnapshot *snapshot,
GtkSnapshotState *state,
GskRenderNode **nodes,
guint n_nodes);
typedef void (* GtkSnapshotClearFunc) (GtkSnapshotState *state);
struct _GtkSnapshotProperties {
GskRectSnap snap;
};
struct _GtkSnapshotState {
guint start_node_index;
guint n_nodes;
GskTransform * transform;
GtkSnapshotProperties props;
GtkSnapshotCollectFunc collect_func;
GtkSnapshotClearFunc clear_func;
union {
struct {
double opacity;
} opacity;
struct {
double radius;
} blur;
struct {
graphene_matrix_t matrix;
graphene_vec4_t offset;
GskRectSnap snap;
} color_matrix;
struct {
GskComponentTransfer *red;
GskComponentTransfer *green;
GskComponentTransfer *blue;
GskComponentTransfer *alpha;
} component_transfer;
struct {
graphene_rect_t bounds;
graphene_rect_t child_bounds;
GskRepeat repeat;
GskRectSnap snap;
GskRectSnap child_snap;
} repeat;
struct {
graphene_rect_t bounds;
GskRectSnap snap;
} clip;
G_GNUC_BEGIN_IGNORE_DEPRECATIONS
struct {
GskGLShader *shader;
GBytes *args;
graphene_rect_t bounds;
GskRenderNode **nodes;
GskRenderNode *internal_nodes[4 ];
} glshader;
G_GNUC_END_IGNORE_DEPRECATIONS
struct {
graphene_rect_t bounds;
int node_idx;
int n_children;
} glshader_texture;
struct {
GskRoundedRect bounds;
GskRectSnap snap;
} rounded_clip;
struct {
GskPath *path;
GskFillRule fill_rule;
} fill;
struct {
GskPath *path;
GskStroke stroke;
} stroke;
struct {
gsize n_shadows;
GskShadowEntry *shadows;
GskShadowEntry a_shadow; /* Used if n_shadows == 1 */
} shadow;
struct {
GskBlendMode blend_mode;
GskRenderNode *bottom_node;
} blend;
struct {
double progress;
GskRenderNode *start_node;
} cross_fade;
struct {
char *message;
} debug;
struct {
GskMaskMode mask_mode;
GskRenderNode *mask_node;
} mask;
struct {
GdkSubsurface *subsurface;
} subsurface;
struct {
GskPorterDuff op;
GskRenderNode *mask;
} composite;
struct {
GskIsolation features;
} isolation;
struct {
graphene_rect_t bounds;
GskRectSnap snap;
GskRenderNode *displacement_node;
GdkColorChannel channels[2 ];
graphene_size_t max;
graphene_size_t scale;
graphene_point_t offset;
} displacement;
struct {
graphene_rect_t bounds;
GskRectSnap snap;
GskRenderNode *first_node;
GdkColorState *color_state;
float factors[4 ];
} arithmetic;
} data;
};
static void gtk_snapshot_state_clear (GtkSnapshotState *state);
#define GDK_ARRAY_NAME gtk_snapshot_states
#define GDK_ARRAY_TYPE_NAME GtkSnapshotStates
#define GDK_ARRAY_ELEMENT_TYPE GtkSnapshotState
#define GDK_ARRAY_FREE_FUNC gtk_snapshot_state_clear
#define GDK_ARRAY_BY_VALUE 1
#define GDK_ARRAY_PREALLOC 16
#define GDK_ARRAY_NO_MEMSET 1
#include "gdk/gdkarrayimpl.c"
/* This is a nasty little hack. We typedef GtkSnapshot to the fake object GdkSnapshot
* so that we don ' t need to typecast between them .
* After all , the GdkSnapshot only exist so poor language bindings don ' t trip . Hardcore
* C code can just blatantly ignore such layering violations with a typedef .
*/
struct _GdkSnapshot {
GObject parent_instance; /* it's really GdkSnapshot, but don't tell anyone! */
GtkSnapshotStates state_stack;
GtkSnapshotNodes nodes;
};
struct _GtkSnapshotClass {
GObjectClass parent_class; /* it's really GdkSnapshotClass, but don't tell anyone! */
};
G_DEFINE_TYPE (GtkSnapshot, gtk_snapshot, GDK_TYPE_SNAPSHOT)
static void
gtk_snapshot_dispose (GObject *object)
{
GtkSnapshot *snapshot = GTK_SNAPSHOT (object);
if (!gtk_snapshot_states_is_empty (&snapshot->state_stack))
{
GskRenderNode *node = gtk_snapshot_to_node (snapshot);
g_clear_pointer (&node, gsk_render_node_unref);
}
g_assert (gtk_snapshot_states_is_empty (&snapshot->state_stack));
g_assert (gtk_snapshot_nodes_is_empty (&snapshot->nodes));
G_OBJECT_CLASS (gtk_snapshot_parent_class)->dispose (object);
}
static void
gtk_snapshot_class_init (GtkSnapshotClass *klass)
{
GObjectClass *gobject_class = G_OBJECT_CLASS (klass);
gobject_class->dispose = gtk_snapshot_dispose;
}
static GskRenderNode *
gtk_snapshot_collect_default (GtkSnapshot *snapshot,
GtkSnapshotState *state,
GskRenderNode **nodes,
guint n_nodes)
{
GskRenderNode *node;
if (n_nodes == 0 )
{
node = NULL;
}
else if (n_nodes == 1 )
{
node = gsk_render_node_ref (nodes[0 ]);
}
else
{
node = gsk_container_node_new (nodes, n_nodes);
}
return node;
}
static GtkSnapshotState *
gtk_snapshot_push_state (GtkSnapshot *snapshot,
GskTransform *transform,
GtkSnapshotCollectFunc collect_func,
GtkSnapshotClearFunc clear_func)
{
const gsize n_states = gtk_snapshot_states_get_size (&snapshot->state_stack);
GtkSnapshotState *state;
gtk_snapshot_states_set_size (&snapshot->state_stack, n_states + 1 );
state = gtk_snapshot_states_get (&snapshot->state_stack, n_states);
if (n_states > 0 )
{
GtkSnapshotState *prev;
prev = gtk_snapshot_states_get (&snapshot->state_stack, n_states - 1 );
state->props = prev->props;
}
else
{
state->props = (GtkSnapshotProperties) {
.snap = GSK_RECT_SNAP_NONE,
};
}
state->transform = gsk_transform_ref (transform);
state->collect_func = collect_func;
state->clear_func = clear_func;
state->start_node_index = gtk_snapshot_nodes_get_size (&snapshot->nodes);
state->n_nodes = 0 ;
return state;
}
static GtkSnapshotState *
gtk_snapshot_get_current_state (const GtkSnapshot *snapshot)
{
gsize size = gtk_snapshot_states_get_size (&snapshot->state_stack);
g_assert (size > 0 );
return gtk_snapshot_states_get (&snapshot->state_stack, size - 1 );
}
static GtkSnapshotState *
gtk_snapshot_get_previous_state (const GtkSnapshot *snapshot)
{
gsize size = gtk_snapshot_states_get_size (&snapshot->state_stack);
g_assert (size > 1 );
return gtk_snapshot_states_get (&snapshot->state_stack, size - 2 );
}
/* n == 0 => current, n == 1, previous, etc */
static GtkSnapshotState *
gtk_snapshot_get_nth_previous_state (const GtkSnapshot *snapshot,
int n)
{
gsize size = gtk_snapshot_states_get_size (&snapshot->state_stack);
g_assert (size > n);
return gtk_snapshot_states_get (&snapshot->state_stack, size - (1 + n));
}
static void
gtk_snapshot_state_clear (GtkSnapshotState *state)
{
if (state->clear_func)
state->clear_func (state);
gsk_transform_unref (state->transform);
}
static void
gtk_snapshot_init (GtkSnapshot *self)
{
gtk_snapshot_states_init (&self->state_stack);
gtk_snapshot_nodes_init (&self->nodes);
gtk_snapshot_push_state (self,
NULL,
gtk_snapshot_collect_default,
NULL);
}
/**
* gtk_snapshot_new :
*
* Creates a new ` GtkSnapshot ` .
*
* Returns : a newly - allocated ` GtkSnapshot `
*/
GtkSnapshot *
gtk_snapshot_new (void )
{
return g_object_new (GTK_TYPE_SNAPSHOT, NULL);
}
/**
* gtk_snapshot_free_to_node : ( skip )
* @ snapshot : ( transfer full ) : a ` GtkSnapshot `
*
* Returns the node that was constructed by @ snapshot
* and frees @ snapshot .
*
* See also [ method @ Gtk . Snapshot . to_node ] .
*
* Returns : ( transfer full ) ( nullable ) : a newly - created [ class @ Gsk . RenderNode ]
*/
GskRenderNode *
gtk_snapshot_free_to_node (GtkSnapshot *snapshot)
{
GskRenderNode *result;
result = gtk_snapshot_to_node (snapshot);
g_object_unref (snapshot);
return result;
}
/**
* gtk_snapshot_free_to_paintable : ( skip )
* @ snapshot : ( transfer full ) : a ` GtkSnapshot `
* @ size : ( nullable ) : The size of the resulting paintable
* or % NULL to use the bounds of the snapshot
*
* Returns a paintable for the node that was
* constructed by @ snapshot and frees @ snapshot .
*
* Returns : ( transfer full ) ( nullable ) : a newly - created [ iface @ Gdk . Paintable ]
*/
GdkPaintable *
gtk_snapshot_free_to_paintable (GtkSnapshot *snapshot,
const graphene_size_t *size)
{
GdkPaintable *result;
result = gtk_snapshot_to_paintable (snapshot, size);
g_object_unref (snapshot);
return result;
}
static GskRenderNode *
gtk_snapshot_collect_autopush_transform (GtkSnapshot *snapshot,
GtkSnapshotState *state,
GskRenderNode **nodes,
guint n_nodes)
{
GskRenderNode *node, *transform_node;
GtkSnapshotState *previous_state;
previous_state = gtk_snapshot_get_previous_state (snapshot);
previous_state->props = state->props;
node = gtk_snapshot_collect_default (snapshot, state, nodes, n_nodes);
if (node == NULL)
return NULL;
transform_node = gsk_transform_node_new (node, previous_state->transform);
gsk_render_node_unref (node);
return transform_node;
}
static void
gtk_snapshot_autopush_transform (GtkSnapshot *snapshot)
{
gtk_snapshot_push_state (snapshot,
NULL,
gtk_snapshot_collect_autopush_transform,
NULL);
}
static gboolean
gtk_snapshot_state_should_autopop (const GtkSnapshotState *state)
{
return state->collect_func == gtk_snapshot_collect_autopush_transform;
}
static GskRenderNode *
gtk_snapshot_collect_debug (GtkSnapshot *snapshot,
GtkSnapshotState *state,
GskRenderNode **nodes,
guint n_nodes)
{
GskRenderNode *node, *debug_node;
node = gtk_snapshot_collect_default (snapshot, state, nodes, n_nodes);
if (node == NULL)
return NULL;
debug_node = gsk_debug_node_new (node, state->data.debug.message);
state->data.debug.message = NULL;
gsk_render_node_unref (node);
return debug_node;
}
static void
gtk_snapshot_clear_debug (GtkSnapshotState *state)
{
g_clear_pointer (&state->data.debug.message, g_free);
}
/**
* gtk_snapshot_push_debug :
* @ snapshot : a ` GtkSnapshot `
* @ message : a printf - style format string
* @ . . . : arguments for @ message
*
* Inserts a debug node with a message .
*
* Debug nodes don ' t affect the rendering at all , but can be
* helpful in identifying parts of a render node tree dump ,
* for example in the GTK inspector .
*/
void
gtk_snapshot_push_debug (GtkSnapshot *snapshot,
const char *message,
...)
{
GtkSnapshotState *current_state = gtk_snapshot_get_current_state (snapshot);
if (GTK_DEBUG_CHECK (SNAPSHOT))
{
va_list args;
GtkSnapshotState *state;
state = gtk_snapshot_push_state (snapshot,
current_state->transform,
gtk_snapshot_collect_debug,
gtk_snapshot_clear_debug);
va_start (args, message);
state->data.debug.message = g_strdup_vprintf (message, args);
va_end (args);
}
else
{
gtk_snapshot_push_state (snapshot,
current_state->transform,
gtk_snapshot_collect_default,
NULL);
}
}
static GskRenderNode *
gtk_snapshot_collect_opacity (GtkSnapshot *snapshot,
GtkSnapshotState *state,
GskRenderNode **nodes,
guint n_nodes)
{
GskRenderNode *node, *opacity_node;
node = gtk_snapshot_collect_default (snapshot, state, nodes, n_nodes);
if (node == NULL)
return NULL;
if (state->data.opacity.opacity == 0 .0 )
{
GdkRGBA color = GDK_RGBA ("00000000" );
graphene_rect_t bounds;
gsk_render_node_get_bounds (node, &bounds);
opacity_node = gsk_color_node_new (&color, &bounds);
gsk_render_node_unref (node);
}
else
{
opacity_node = gsk_opacity_node_new (node, state->data.opacity.opacity);
gsk_render_node_unref (node);
}
return opacity_node;
}
/**
* gtk_snapshot_push_opacity :
* @ snapshot : a ` GtkSnapshot `
* @ opacity : the opacity to use
*
* Modifies the opacity of an image .
*
* The image is recorded until the next call to [ method @ Gtk . Snapshot . pop ] .
*/
void
gtk_snapshot_push_opacity (GtkSnapshot *snapshot,
double opacity)
{
GtkSnapshotState *current_state = gtk_snapshot_get_current_state (snapshot);
GtkSnapshotState *state;
state = gtk_snapshot_push_state (snapshot,
current_state->transform,
gtk_snapshot_collect_opacity,
NULL);
state->data.opacity.opacity = CLAMP (opacity, 0 .0 , 1 .0 );
}
static GskRenderNode *
gtk_snapshot_collect_isolation (GtkSnapshot *snapshot,
GtkSnapshotState *state,
GskRenderNode **nodes,
guint n_nodes)
{
GskRenderNode *node, *isolation_node;
GskIsolation features;
node = gtk_snapshot_collect_default (snapshot, state, nodes, n_nodes);
if (node == NULL)
return NULL;
features = gsk_isolation_features_simplify_for_node (state->data.isolation.features, node);
if (features == 0 )
return node;
isolation_node = gsk_isolation_node_new (node, features);
gsk_render_node_unref (node);
return isolation_node;
}
/**
* gtk_snapshot_push_isolation :
* @ snapshot : a ` GtkSnapshot `
* @ features : features that are isolated
*
* Isolates the following drawing operations from previous ones .
*
* You can express " everything but these flags " in a forward compatible
* way by using bit math :
* ` GSK_ISOLATION_ALL & ~ ( GSK_ISOLATION_BACKGROUND | GSK_ISOLATION_COPY_PASTE ) `
* will isolate everything but background and copy / paste .
*
* For what isolation features exist , see [ flags @ Gsk . Isolation ] .
*
* Content is isolated until the next call to [ method @ Gtk . Snapshot . pop ] .
*
* Since : 4 . 22
*/
void
gtk_snapshot_push_isolation (GtkSnapshot *snapshot,
GskIsolation features)
{
GtkSnapshotState *current_state = gtk_snapshot_get_current_state (snapshot);
GtkSnapshotState *state;
state = gtk_snapshot_push_state (snapshot,
current_state->transform,
gtk_snapshot_collect_isolation,
NULL);
state->data.isolation.features = features;
}
static GskRenderNode *
gtk_snapshot_collect_color_matrix (GtkSnapshot *snapshot,
GtkSnapshotState *state,
GskRenderNode **nodes,
guint n_nodes)
{
GskRenderNode *node, *result;
node = gtk_snapshot_collect_default (snapshot, state, nodes, n_nodes);
if (node == NULL)
return NULL;
result = gsk_color_matrix_node_new2 (&node->bounds,
state->data.color_matrix.snap,
node,
GDK_COLOR_STATE_SRGB,
&state->data.color_matrix.matrix,
&state->data.color_matrix.offset);
gsk_render_node_unref (node);
return result;
}
/**
* gtk_snapshot_push_color_matrix :
* @ snapshot : a ` GtkSnapshot `
* @ color_matrix : the color matrix to use
* @ color_offset : the color offset to use
*
* Modifies the colors of an image by applying an affine transformation
* in RGB space .
*
* In particular , the colors will be transformed by applying
*
* pixel = transpose ( color_matrix ) * pixel + color_offset
*
* for every pixel . The transformation operates on unpremultiplied
* colors , with color components ordered R , G , B , A .
*
* The image is recorded until the next call to [ method @ Gtk . Snapshot . pop ] .
*/
void
gtk_snapshot_push_color_matrix (GtkSnapshot *snapshot,
const graphene_matrix_t *color_matrix,
const graphene_vec4_t *color_offset)
{
const GtkSnapshotState *current_state = gtk_snapshot_get_current_state (snapshot);
GtkSnapshotState *state;
state = gtk_snapshot_push_state (snapshot,
current_state->transform,
gtk_snapshot_collect_color_matrix,
NULL);
graphene_matrix_init_from_matrix (&state->data.color_matrix.matrix, color_matrix);
graphene_vec4_init_from_vec4 (&state->data.color_matrix.offset, color_offset);
state->data.color_matrix.snap = state->props.snap;
}
static GskRenderNode *
gtk_snapshot_collect_component_transfer (GtkSnapshot *snapshot,
GtkSnapshotState *state,
GskRenderNode **nodes,
guint n_nodes)
{
GskRenderNode *node, *result;
node = gtk_snapshot_collect_default (snapshot, state, nodes, n_nodes);
if (node == NULL)
return NULL;
result = gsk_component_transfer_node_new (node,
state->data.component_transfer.red,
state->data.component_transfer.green,
state->data.component_transfer.blue,
state->data.component_transfer.alpha);
gsk_render_node_unref (node);
return result;
}
static void
gtk_snapshot_clear_component_transfer (GtkSnapshotState *state)
{
gsk_component_transfer_free (state->data.component_transfer.red);
gsk_component_transfer_free (state->data.component_transfer.green);
gsk_component_transfer_free (state->data.component_transfer.blue);
gsk_component_transfer_free (state->data.component_transfer.alpha);
}
/**
* gtk_snapshot_push_component_transfer :
* @ snapshot : a ` GtkSnapshot `
* @ red : the transfer for the red component
* @ green : the transfer for the green component
* @ blue : the transfer for the blue component
* @ alpha : the transfer for the alpha component
*
* Modifies the colors of an image by applying a transfer
* function for each component .
*
* The transfer functions operate on unpremultiplied colors .
*
* The image is recorded until the next call to [ method @ Gtk . Snapshot . pop ] .
*
* Since : 4 . 20
*/
void
gtk_snapshot_push_component_transfer (GtkSnapshot *snapshot,
const GskComponentTransfer *red,
const GskComponentTransfer *green,
const GskComponentTransfer *blue,
const GskComponentTransfer *alpha)
{
const GtkSnapshotState *current_state = gtk_snapshot_get_current_state (snapshot);
GtkSnapshotState *state;
state = gtk_snapshot_push_state (snapshot,
current_state->transform,
gtk_snapshot_collect_component_transfer,
gtk_snapshot_clear_component_transfer);
state->data.component_transfer.red = gsk_component_transfer_copy (red);
state->data.component_transfer.green = gsk_component_transfer_copy (green);
state->data.component_transfer.blue = gsk_component_transfer_copy (blue);
state->data.component_transfer.alpha = gsk_component_transfer_copy (alpha);
}
static GskRenderNode *
gtk_snapshot_collect_repeat (GtkSnapshot *snapshot,
GtkSnapshotState *state,
GskRenderNode **nodes,
guint n_nodes)
{
GskRenderNode *node, *repeat_node;
const graphene_rect_t *child_bounds = &state->data.repeat.child_bounds;
node = gtk_snapshot_collect_default (snapshot, state, nodes, n_nodes);
if (node == NULL)
return NULL;
repeat_node = gsk_repeat_node_new2 (&state->data.repeat.bounds,
state->data.repeat.snap,
node,
child_bounds->size.width > 0 ? child_bounds : NULL,
state->data.repeat.child_snap,
state->data.repeat.repeat);
gsk_render_node_unref (node);
return repeat_node;
}
static GskRenderNode *
gtk_snapshot_collect_discard_repeat (GtkSnapshot *snapshot,
GtkSnapshotState *state,
GskRenderNode **nodes,
guint n_nodes)
{
/* Drop the node and return nothing. */
return NULL;
}
static void
gtk_graphene_rect_scale_affine (const graphene_rect_t *rect,
float scale_x,
float scale_y,
float dx,
float dy,
graphene_rect_t *res)
{
res->origin.x = scale_x * rect->origin.x + dx;
res->origin.y = scale_y * rect->origin.y + dy;
res->size.width = scale_x * rect->size.width;
res->size.height = scale_y * rect->size.height;
if (scale_x < 0 || scale_y < 0 )
graphene_rect_normalize (res);
}
typedef enum {
ENSURE_POSITIVE_SCALE = (1 << 0 ),
ENSURE_UNIFORM_SCALE = (1 << 1 ),
} GtkEnsureFlags;
static void
gtk_snapshot_ensure_affine_with_flags (GtkSnapshot *snapshot,
GtkEnsureFlags flags,
float *scale_x,
float *scale_y,
float *dx,
float *dy)
{
const GtkSnapshotState *state = gtk_snapshot_get_current_state (snapshot);
if (gsk_transform_get_category (state->transform) < GSK_TRANSFORM_CATEGORY_2D_AFFINE)
{
gtk_snapshot_autopush_transform (snapshot);
state = gtk_snapshot_get_current_state (snapshot);
gsk_transform_to_affine (state->transform, scale_x, scale_y, dx, dy);
}
else if (gsk_transform_get_category (state->transform) == GSK_TRANSFORM_CATEGORY_2D_AFFINE)
{
gsk_transform_to_affine (state->transform, scale_x, scale_y, dx, dy);
if (((flags & ENSURE_POSITIVE_SCALE) && (*scale_x < 0 .0 || *scale_y < 0 .0 )) ||
((flags & ENSURE_UNIFORM_SCALE) && (*scale_x != *scale_y)))
{
gtk_snapshot_autopush_transform (snapshot);
state = gtk_snapshot_get_current_state (snapshot);
gsk_transform_to_affine (state->transform, scale_x, scale_y, dx, dy);
}
}
else
{
gsk_transform_to_affine (state->transform, scale_x, scale_y, dx, dy);
}
}
static void
gtk_snapshot_ensure_affine (GtkSnapshot *snapshot,
float *scale_x,
float *scale_y,
float *dx,
float *dy)
{
gtk_snapshot_ensure_affine_with_flags (snapshot,
ENSURE_POSITIVE_SCALE,
scale_x, scale_y,
dx, dy);
}
static void
gtk_snapshot_ensure_translate (GtkSnapshot *snapshot,
float *dx,
float *dy)
{
const GtkSnapshotState *state = gtk_snapshot_get_current_state (snapshot);
if (gsk_transform_get_category (state->transform) < GSK_TRANSFORM_CATEGORY_2D_TRANSLATE)
{
gtk_snapshot_autopush_transform (snapshot);
state = gtk_snapshot_get_current_state (snapshot);
}
gsk_transform_to_translate (state->transform, dx, dy);
}
static void
gtk_snapshot_ensure_identity (GtkSnapshot *snapshot)
{
const GtkSnapshotState *state = gtk_snapshot_get_current_state (snapshot);
if (gsk_transform_get_category (state->transform) < GSK_TRANSFORM_CATEGORY_IDENTITY)
gtk_snapshot_autopush_transform (snapshot);
}
static GskRenderNode *
gtk_snapshot_collect_blur (GtkSnapshot *snapshot,
GtkSnapshotState *state,
GskRenderNode **nodes,
guint n_nodes)
{
GskRenderNode *node, *blur_node;
double radius;
node = gtk_snapshot_collect_default (snapshot, state, nodes, n_nodes);
if (node == NULL)
return NULL;
radius = state->data.blur.radius;
if (radius == 0 .0 )
return node;
if (radius < 0 )
return node;
blur_node = gsk_blur_node_new (node, radius);
gsk_render_node_unref (node);
return blur_node;
}
/**
* gtk_snapshot_push_blur :
* @ snapshot : a ` GtkSnapshot `
* @ radius : the blur radius to use . Must be positive
*
* Blurs an image .
*
* The image is recorded until the next call to [ method @ Gtk . Snapshot . pop ] .
*/
void
gtk_snapshot_push_blur (GtkSnapshot *snapshot,
double radius)
{
const GtkSnapshotState *current_state = gtk_snapshot_get_current_state (snapshot);
GtkSnapshotState *state;
float dx, dy, scale_x, scale_y;
gtk_snapshot_ensure_affine_with_flags (snapshot,
ENSURE_POSITIVE_SCALE | ENSURE_UNIFORM_SCALE,
&scale_x, &scale_y,
&dx, &dy);
state = gtk_snapshot_push_state (snapshot,
current_state->transform,
gtk_snapshot_collect_blur,
NULL);
state->data.blur.radius = radius * scale_x;
}
void
gtk_snapshot_push_repeat2 (GtkSnapshot *snapshot,
const graphene_rect_t *bounds,
const graphene_rect_t *child_bounds,
GskRectSnap child_snap,
GskRepeat repeat)
{
GtkSnapshotState *state, *current_state;
gboolean empty_child_bounds = FALSE ;
graphene_rect_t real_child_bounds = { { 0 } };
float scale_x, scale_y, dx, dy;
gtk_snapshot_ensure_affine (snapshot, &scale_x, &scale_y, &dx, &dy);
if (child_bounds)
{
gtk_graphene_rect_scale_affine (child_bounds, scale_x, scale_y, dx, dy, &real_child_bounds);
if (real_child_bounds.size.width <= 0 || real_child_bounds.size.height <= 0 )
empty_child_bounds = TRUE ;
}
current_state = gtk_snapshot_get_current_state (snapshot);
state = gtk_snapshot_push_state (snapshot,
current_state->transform,
empty_child_bounds
? gtk_snapshot_collect_discard_repeat
: gtk_snapshot_collect_repeat,
NULL);
gtk_graphene_rect_scale_affine (bounds, scale_x, scale_y, dx, dy, &state->data.repeat.bounds);
state->data.repeat.child_bounds = real_child_bounds;
state->data.repeat.repeat = repeat;
state->data.repeat.snap = state->props.snap;
state->data.repeat.child_snap = child_snap;
}
/**
* gtk_snapshot_push_repeat :
* @ snapshot : a ` GtkSnapshot `
* @ bounds : the bounds within which to repeat
* @ child_bounds : ( nullable ) : the bounds of the child or % NULL
* to use the full size of the collected child node
*
* Creates a node that repeats the child node .
*
* The child is recorded until the next call to [ method @ Gtk . Snapshot . pop ] .
*/
void
gtk_snapshot_push_repeat (GtkSnapshot *snapshot,
const graphene_rect_t *bounds,
const graphene_rect_t *child_bounds)
{
GtkSnapshotState *current_state = gtk_snapshot_get_current_state (snapshot);
gtk_snapshot_push_repeat2 (snapshot,
bounds,
child_bounds,
current_state->props.snap,
GSK_REPEAT_REPEAT);
}
static GskRenderNode *
gtk_snapshot_collect_clip (GtkSnapshot *snapshot,
GtkSnapshotState *state,
GskRenderNode **nodes,
guint n_nodes)
{
GskRenderNode *node, *clip_node;
node = gtk_snapshot_collect_default (snapshot, state, nodes, n_nodes);
if (node == NULL)
return NULL;
/* Check if the child node will even be clipped */
if (graphene_rect_contains_rect (&state->data.clip.bounds, &node->bounds))
return node;
if (state->data.clip.bounds.size.width == 0 ||
state->data.clip.bounds.size.height == 0 )
{
gsk_render_node_unref (node);
return NULL;
}
clip_node = gsk_clip_node_new2 (node, &state->data.clip.bounds, state->data.clip.snap);
gsk_render_node_unref (node);
return clip_node;
}
/**
* gtk_snapshot_push_clip :
* @ snapshot : a ` GtkSnapshot `
* @ bounds : the rectangle to clip to
*
* Clips an image to a rectangle .
*
* The image is recorded until the next call to [ method @ Gtk . Snapshot . pop ] .
*/
void
gtk_snapshot_push_clip (GtkSnapshot *snapshot,
const graphene_rect_t *bounds)
{
GtkSnapshotState *state, *current_state;
float scale_x, scale_y, dx, dy;
gtk_snapshot_ensure_affine (snapshot, &scale_x, &scale_y, &dx, &dy);
current_state = gtk_snapshot_get_current_state (snapshot);
state = gtk_snapshot_push_state (snapshot,
current_state->transform,
gtk_snapshot_collect_clip,
NULL);
gtk_graphene_rect_scale_affine (bounds, scale_x, scale_y, dx, dy, &state->data.clip.bounds);
state->data.clip.snap = state->props.snap;
}
G_GNUC_BEGIN_IGNORE_DEPRECATIONS
static GskRenderNode *
gtk_snapshot_collect_gl_shader (GtkSnapshot *snapshot,
GtkSnapshotState *state,
GskRenderNode **collected_nodes,
guint n_collected_nodes)
{
GskRenderNode *shader_node = NULL;
GskRenderNode **nodes;
int n_children;
n_children = gsk_gl_shader_get_n_textures (state->data.glshader.shader);
shader_node = NULL;
if (n_collected_nodes != 0 )
g_warning ("Unexpected children when popping gl shader." );
if (state->data.glshader.nodes)
nodes = state->data.glshader.nodes;
else
nodes = &state->data.glshader.internal_nodes[0 ];
if (state->data.glshader.bounds.size.width != 0 &&
state->data.glshader.bounds.size.height != 0 )
shader_node = gsk_gl_shader_node_new (state->data.glshader.shader,
&state->data.glshader.bounds,
state->data.glshader.args,
nodes, n_children);
return shader_node;
}
static void
gtk_snapshot_clear_gl_shader (GtkSnapshotState *state)
{
GskRenderNode **nodes;
guint i, n_children;
n_children = gsk_gl_shader_get_n_textures (state->data.glshader.shader);
if (state->data.glshader.nodes)
nodes = state->data.glshader.nodes;
else
nodes = &state->data.glshader.internal_nodes[0 ];
g_object_unref (state->data.glshader.shader);
g_bytes_unref (state->data.glshader.args);
for (i = 0 ; i < n_children; i++)
gsk_render_node_unref (nodes[i]);
g_free (state->data.glshader.nodes);
}
static GskRenderNode *
gtk_snapshot_collect_gl_shader_texture (GtkSnapshot *snapshot,
GtkSnapshotState *state,
GskRenderNode **nodes,
guint n_nodes)
{
GskRenderNode *child_node;
GdkRGBA transparent = { 0 , 0 , 0 , 0 };
int n_children, node_idx;
GtkSnapshotState *glshader_state;
GskRenderNode **out_nodes;
child_node = gtk_snapshot_collect_default (snapshot, state, nodes, n_nodes);
if (child_node == NULL)
child_node = gsk_color_node_new (&transparent, &state->data.glshader_texture.bounds);
n_children = state->data.glshader_texture.n_children;
node_idx = state->data.glshader_texture.node_idx;
glshader_state = gtk_snapshot_get_nth_previous_state (snapshot, n_children - node_idx);
g_assert (glshader_state->collect_func == gtk_snapshot_collect_gl_shader);
if (glshader_state->data.glshader.nodes)
out_nodes = glshader_state->data.glshader.nodes;
else
out_nodes = &glshader_state->data.glshader.internal_nodes[0 ];
out_nodes[node_idx] = child_node;
return NULL;
}
/**
* gtk_snapshot_push_gl_shader :
* @ snapshot : a ` GtkSnapshot `
* @ shader : The code to run
* @ bounds : the rectangle to render into
* @ take_args : ( transfer full ) : Data block with arguments for the shader .
*
* Push a [ class @ Gsk . GLShaderNode ] .
*
* The node uses the given [ class @ Gsk . GLShader ] and uniform values
* Additionally this takes a list of @ n_children other nodes
* which will be passed to the [ class @ Gsk . GLShaderNode ] .
*
* The @ take_args argument is a block of data to use for uniform
* arguments , as per types and offsets defined by the @ shader .
* Normally this is generated by [ method @ Gsk . GLShader . format_args ]
* or [ struct @ Gsk . ShaderArgsBuilder ] .
*
* The snapshotter takes ownership of @ take_args , so the caller should
* not free it after this .
*
* If the renderer doesn ' t support GL shaders , or if there is any
* problem when compiling the shader , then the node will draw pink .
* You should use [ method @ Gsk . GLShader . compile ] to ensure the @ shader
* will work for the renderer before using it .
*
* If the shader requires textures ( see [ method @ Gsk . GLShader . get_n_textures ] ) ,
* then it is expected that you call [ method @ Gtk . Snapshot . gl_shader_pop_texture ]
* the number of times that are required . Each of these calls will generate
* a node that is added as a child to the ` GskGLShaderNode ` , which in turn
* will render these offscreen and pass as a texture to the shader .
*
* Once all textures ( if any ) are pop : ed , you must call the regular
* [ method @ Gtk . Snapshot . pop ] .
*
* If you want to use pre - existing textures as input to the shader rather
* than rendering new ones , use [ method @ Gtk . Snapshot . append_texture ] to
* push a texture node . These will be used directly rather than being
* re - rendered .
*
* For details on how to write shaders , see [ class @ Gsk . GLShader ] .
*
* Deprecated : 4 . 16 : GTK ' s new Vulkan - focused rendering
* does not support this feature . Use [ class @ Gtk . GLArea ] for
* OpenGL rendering .
*/
void
gtk_snapshot_push_gl_shader (GtkSnapshot *snapshot,
GskGLShader *shader,
const graphene_rect_t *bounds,
GBytes *take_args)
{
GtkSnapshotState *state, *current_state;
float scale_x, scale_y, dx, dy;
graphene_rect_t transformed_bounds;
int n_children = gsk_gl_shader_get_n_textures (shader);
gtk_snapshot_ensure_affine (snapshot, &scale_x, &scale_y, &dx, &dy);
current_state = gtk_snapshot_get_current_state (snapshot);
state = gtk_snapshot_push_state (snapshot,
current_state->transform,
gtk_snapshot_collect_gl_shader,
gtk_snapshot_clear_gl_shader);
gtk_graphene_rect_scale_affine (bounds, scale_x, scale_y, dx, dy, &transformed_bounds);
state->data.glshader.bounds = transformed_bounds;
state->data.glshader.shader = g_object_ref (shader);
state->data.glshader.args = take_args; /* Takes ownership */
if (n_children <= G_N_ELEMENTS (state->data.glshader.internal_nodes))
state->data.glshader.nodes = NULL;
else
state->data.glshader.nodes = g_new (GskRenderNode *, n_children);
for (int i = 0 ; i < n_children; i++)
{
current_state = gtk_snapshot_get_current_state (snapshot);
state = gtk_snapshot_push_state (snapshot,
current_state->transform,
gtk_snapshot_collect_gl_shader_texture,
NULL);
state->data.glshader_texture.bounds = transformed_bounds;
state->data.glshader_texture.node_idx = n_children - 1 - i;/* We pop in reverse order */
state->data.glshader_texture.n_children = n_children;
}
}
static GskRenderNode *
gtk_snapshot_collect_rounded_clip (GtkSnapshot *snapshot,
GtkSnapshotState *state,
GskRenderNode **nodes,
guint n_nodes)
{
GskRenderNode *node, *clip_node;
node = gtk_snapshot_collect_default (snapshot, state, nodes, n_nodes);
if (node == NULL)
return NULL;
/* If the given radius is 0 in all corners, we can just create a normal clip node */
if (gsk_rounded_rect_is_rectilinear (&state->data.rounded_clip.bounds))
{
/* ... and do the same optimization */
if (graphene_rect_contains_rect (&state->data.rounded_clip.bounds.bounds, &node->bounds))
return node;
clip_node = gsk_clip_node_new2 (node,
&state->data.rounded_clip.bounds.bounds,
state->data.rounded_clip.snap);
}
else
{
if (gsk_rounded_rect_contains_rect (&state->data.rounded_clip.bounds, &node->bounds))
return node;
clip_node = gsk_rounded_clip_node_new2 (node,
&state->data.rounded_clip.bounds,
state->data.rounded_clip.snap);
}
if (clip_node->bounds.size.width == 0 ||
clip_node->bounds.size.height == 0 )
{
gsk_render_node_unref (node);
gsk_render_node_unref (clip_node);
return NULL;
}
gsk_render_node_unref (node);
return clip_node;
}
G_GNUC_END_IGNORE_DEPRECATIONS
/**
* gtk_snapshot_push_rounded_clip :
* @ snapshot : a ` GtkSnapshot `
* @ bounds : the rounded rectangle to clip to
*
* Clips an image to a rounded rectangle .
*
* The image is recorded until the next call to [ method @ Gtk . Snapshot . pop ] .
*/
void
gtk_snapshot_push_rounded_clip (GtkSnapshot *snapshot,
const GskRoundedRect *bounds)
{
GtkSnapshotState *state, *current_state;
float scale_x, scale_y, dx, dy;
gtk_snapshot_ensure_affine (snapshot, &scale_x, &scale_y, &dx, &dy);
current_state = gtk_snapshot_get_current_state (snapshot);
state = gtk_snapshot_push_state (snapshot,
current_state->transform,
gtk_snapshot_collect_rounded_clip,
NULL);
gsk_rounded_rect_scale_affine (&state->data.rounded_clip.bounds, bounds, scale_x, scale_y, dx, dy);
state->data.rounded_clip.snap = state->props.snap;
}
static GskRenderNode *
gtk_snapshot_collect_fill (GtkSnapshot *snapshot,
GtkSnapshotState *state,
GskRenderNode **nodes,
guint n_nodes)
{
GskRenderNode *node, *fill_node;
node = gtk_snapshot_collect_default (snapshot, state, nodes, n_nodes);
if (node == NULL)
return NULL;
fill_node = gsk_fill_node_new (node,
state->data.fill.path,
state->data.fill.fill_rule);
if (fill_node->bounds.size.width == 0 ||
fill_node->bounds.size.height == 0 )
{
gsk_render_node_unref (node);
gsk_render_node_unref (fill_node);
return NULL;
}
gsk_render_node_unref (node);
return fill_node;
}
static void
gtk_snapshot_clear_fill (GtkSnapshotState *state)
{
gsk_path_unref (state->data.fill.path);
}
/**
* gtk_snapshot_push_fill :
* @ snapshot : a ` GtkSnapshot `
* @ path : The path describing the area to fill
* @ fill_rule : The fill rule to use
*
* Fills the area given by @ path and @ fill_rule with an image and discards everything
* outside of it .
*
* The image is recorded until the next call to [ method @ Gtk . Snapshot . pop ] .
*
* If you want to fill the path with a color , [ method @ Gtk . Snapshot . append_fill ]
* than rendering new ones , use [ method @ Gtk . Snapshot . append_fill ]
* may be more convenient .
*
* Since : 4 . 14
*/
void
gtk_snapshot_push_fill (GtkSnapshot *snapshot,
GskPath *path,
GskFillRule fill_rule)
{
GtkSnapshotState *state, *current_state;
gtk_snapshot_ensure_identity (snapshot);
current_state = gtk_snapshot_get_current_state (snapshot);
state = gtk_snapshot_push_state (snapshot,
current_state->transform,
gtk_snapshot_collect_fill,
gtk_snapshot_clear_fill);
state->data.fill.path = gsk_path_ref (path);
state->data.fill.fill_rule = fill_rule;
}
/**
* gtk_snapshot_append_fill :
* @ snapshot : a ` GtkSnapshot `
* @ path : The path describing the area to fill
* @ fill_rule : The fill rule to use
* @ color : the color to fill the path with
*
* A convenience method to fill a path with a color .
*
* See [ method @ Gtk . Snapshot . push_fill ] if you need
* to fill a path with more complex content than
* a color .
*
* Since : 4 . 14
*/
void
gtk_snapshot_append_fill (GtkSnapshot *snapshot,
GskPath *path,
GskFillRule fill_rule,
const GdkRGBA *color)
{
graphene_rect_t bounds;
if (!gsk_path_get_bounds (path, &bounds))
return ;
gtk_snapshot_push_fill (snapshot, path, fill_rule);
gtk_snapshot_append_color (snapshot, color, &bounds);
gtk_snapshot_pop (snapshot);
}
static GskRenderNode *
gtk_snapshot_collect_stroke (GtkSnapshot *snapshot,
GtkSnapshotState *state,
GskRenderNode **nodes,
guint n_nodes)
{
GskRenderNode *node, *stroke_node;
node = gtk_snapshot_collect_default (snapshot, state, nodes, n_nodes);
if (node == NULL)
return NULL;
stroke_node = gsk_stroke_node_new (node,
state->data.stroke.path,
&state->data.stroke.stroke);
if (stroke_node->bounds.size.width == 0 ||
stroke_node->bounds.size.height == 0 )
{
gsk_render_node_unref (node);
gsk_render_node_unref (stroke_node);
return NULL;
}
gsk_render_node_unref (node);
return stroke_node;
}
static void
gtk_snapshot_clear_stroke (GtkSnapshotState *state)
{
gsk_path_unref (state->data.stroke.path);
gsk_stroke_clear (&state->data.stroke.stroke);
}
/**
* gtk_snapshot_push_stroke :
* @ snapshot : a # GtkSnapshot
* @ path : The path to stroke
* @ stroke : The stroke attributes
*
* Strokes the given @ path with the attributes given by @ stroke and
* an image .
*
* The image is recorded until the next call to [ method @ Gtk . Snapshot . pop ] .
*
* Note that the strokes are subject to the same transformation as
* everything else , so uneven scaling will cause horizontal and vertical
* strokes to have different widths .
*
* If you want to stroke the path with a color , [ method @ Gtk . Snapshot . append_stroke ]
* may be more convenient .
*
* Since : 4 . 14
*/
void
gtk_snapshot_push_stroke (GtkSnapshot *snapshot,
GskPath *path,
const GskStroke *stroke)
{
GtkSnapshotState *state, *current_state;
gtk_snapshot_ensure_identity (snapshot);
current_state = gtk_snapshot_get_current_state (snapshot);
state = gtk_snapshot_push_state (snapshot,
current_state->transform,
gtk_snapshot_collect_stroke,
gtk_snapshot_clear_stroke);
state->data.stroke.path = gsk_path_ref (path);
state->data.stroke.stroke = GSK_STROKE_INIT_COPY (stroke);
}
static GskRenderNode *
gtk_snapshot_collect_shadow (GtkSnapshot *snapshot,
GtkSnapshotState *state,
GskRenderNode **nodes,
guint n_nodes)
{
GskRenderNode *node, *shadow_node;
node = gtk_snapshot_collect_default (snapshot, state, nodes, n_nodes);
if (node == NULL)
return NULL;
shadow_node = gsk_shadow_node_new2 (node,
state->data.shadow.shadows != NULL
? state->data.shadow.shadows
: &state->data.shadow.a_shadow,
state->data.shadow.n_shadows);
gsk_render_node_unref (node);
return shadow_node;
}
/**
* gtk_snapshot_append_stroke :
* @ snapshot : a ` GtkSnapshot `
* @ path : The path describing the area to fill
* @ stroke : The stroke attributes
* @ color : the color to fill the path with
*
* A convenience method to stroke a path with a color .
*
* See [ method @ Gtk . Snapshot . push_stroke ] if you need
* to stroke a path with more complex content than
* a color .
*
* Since : 4 . 14
*/
void
gtk_snapshot_append_stroke (GtkSnapshot *snapshot,
GskPath *path,
const GskStroke *stroke,
const GdkRGBA *color)
{
graphene_rect_t bounds;
gsk_path_get_stroke_bounds (path, stroke, &bounds);
gtk_snapshot_push_stroke (snapshot, path, stroke);
gtk_snapshot_append_color (snapshot, color, &bounds);
gtk_snapshot_pop (snapshot);
}
static void
gtk_snapshot_clear_shadow (GtkSnapshotState *state)
{
if (state->data.shadow.shadows != 0 )
for (gsize i = 0 ; i < state->data.shadow.n_shadows; i++)
gdk_color_finish (&state->data.shadow.shadows[i].color);
else
gdk_color_finish (&state->data.shadow.a_shadow.color);
g_free (state->data.shadow.shadows);
}
/**
* gtk_snapshot_push_shadow :
* @ snapshot : a ` GtkSnapshot `
* @ shadow : ( array length = n_shadows ) : the first shadow specification
* @ n_shadows : number of shadow specifications
*
* Applies a shadow to an image .
*
* The image is recorded until the next call to [ method @ Gtk . Snapshot . pop ] .
*/
void
gtk_snapshot_push_shadow (GtkSnapshot *snapshot,
const GskShadow *shadow,
gsize n_shadows)
{
GskShadowEntry *shadow2;
g_return_if_fail (n_shadows > 0 );
shadow2 = g_new (GskShadowEntry, n_shadows);
for (gsize i = 0 ; i < n_shadows; i++)
{
gdk_color_init_from_rgba (&shadow2[i].color, &shadow[i].color);
graphene_point_init (&shadow2[i].offset, shadow[i].dx,shadow[i].dy);
shadow2[i].radius = shadow[i].radius;
}
gtk_snapshot_push_shadows (snapshot, shadow2, n_shadows);
for (gsize i = 0 ; i < n_shadows; i++)
gdk_color_finish (&shadow2[i].color);
g_free (shadow2);
}
/*< private >
* gtk_snapshot_push_shadows :
* @ snapshot : a ` GtkSnapshot `
* @ shadow : ( array length = n_shadows ) : the first shadow specification
* @ n_shadows : number of shadow specifications
*
* Applies a shadow to an image .
*
* The image is recorded until the next call to [ method @ Gtk . Snapshot . pop ] .
*/
void
gtk_snapshot_push_shadows (GtkSnapshot *snapshot,
const GskShadowEntry *shadow,
gsize n_shadows)
{
GtkSnapshotState *state;
GskTransform *transform;
float scale_x, scale_y, dx, dy;
gsize i;
gtk_snapshot_ensure_affine_with_flags (snapshot,
ENSURE_POSITIVE_SCALE | ENSURE_UNIFORM_SCALE,
&scale_x, &scale_y,
&dx, &dy);
transform = gsk_transform_scale (gsk_transform_translate (NULL, &GRAPHENE_POINT_INIT (dx, dy)), scale_x, scale_y);
state = gtk_snapshot_push_state (snapshot,
transform,
gtk_snapshot_collect_shadow,
gtk_snapshot_clear_shadow);
state->data.shadow.n_shadows = n_shadows;
if (n_shadows == 1 )
{
state->data.shadow.shadows = NULL;
gdk_color_init_copy (&state->data.shadow.a_shadow.color, &shadow->color);
graphene_point_init (&state->data.shadow.a_shadow.offset,
shadow->offset.x * scale_x,
shadow->offset.y * scale_y);
state->data.shadow.a_shadow.radius = shadow->radius * scale_x;
}
else
{
state->data.shadow.shadows = g_malloc (sizeof (GskShadowEntry) * n_shadows);
memcpy (state->data.shadow.shadows, shadow, sizeof (GskShadowEntry) * n_shadows);
for (i = 0 ; i < n_shadows; i++)
{
gdk_color_init_copy (&state->data.shadow.shadows[i].color, &shadow[i].color);
graphene_point_init (&state->data.shadow.shadows[i].offset,
shadow[i].offset.x * scale_x,
shadow[i].offset.y * scale_y);
state->data.shadow.shadows[i].radius = shadow[i].radius * scale_x;
}
}
gsk_transform_unref (transform);
}
static GskRenderNode *
gtk_snapshot_collect_blend_top (GtkSnapshot *snapshot,
GtkSnapshotState *state,
GskRenderNode **nodes,
guint n_nodes)
{
GskRenderNode *bottom_node, *top_node, *blend_node;
GdkRGBA transparent = { 0 , 0 , 0 , 0 };
top_node = gtk_snapshot_collect_default (snapshot, state, nodes, n_nodes);
bottom_node = state->data.blend.bottom_node != NULL
? gsk_render_node_ref (state->data.blend.bottom_node)
: NULL;
/* XXX: Is this necessary? Do we need a NULL node? */
if (top_node == NULL)
top_node = gsk_color_node_new (&transparent,
bottom_node ? &bottom_node->bounds
: &GRAPHENE_RECT_INIT (0 , 0 , 0 , 0 ));
if (bottom_node == NULL)
bottom_node = gsk_color_node_new (&transparent, &top_node->bounds);
blend_node = gsk_blend_node_new (bottom_node, top_node, state->data.blend.blend_mode);
gsk_render_node_unref (top_node);
gsk_render_node_unref (bottom_node);
return blend_node;
}
static void
gtk_snapshot_clear_blend_top (GtkSnapshotState *state)
{
g_clear_pointer (&(state->data.blend.bottom_node), gsk_render_node_unref);
}
static GskRenderNode *
gtk_snapshot_collect_blend_bottom (GtkSnapshot *snapshot,
GtkSnapshotState *state,
GskRenderNode **nodes,
guint n_nodes)
{
GtkSnapshotState *prev_state = gtk_snapshot_get_previous_state (snapshot);
g_assert (prev_state->collect_func == gtk_snapshot_collect_blend_top);
prev_state->data.blend.bottom_node = gtk_snapshot_collect_default (snapshot, state, nodes, n_nodes);
return NULL;
}
/**
* gtk_snapshot_push_blend :
* @ snapshot : a ` GtkSnapshot `
* @ blend_mode : blend mode to use
*
* Blends together two images with the given blend mode .
*
* Until the first call to [ method @ Gtk . Snapshot . pop ] , the
* bottom image for the blend operation will be recorded .
* After that call , the top image to be blended will be
* recorded until the second call to [ method @ Gtk . Snapshot . pop ] .
*
* Calling this function requires two subsequent calls
* to [ method @ Gtk . Snapshot . pop ] .
*/
void
gtk_snapshot_push_blend (GtkSnapshot *snapshot,
GskBlendMode blend_mode)
{
GtkSnapshotState *current_state = gtk_snapshot_get_current_state (snapshot);
GtkSnapshotState *top_state;
top_state = gtk_snapshot_push_state (snapshot,
current_state->transform,
gtk_snapshot_collect_blend_top,
gtk_snapshot_clear_blend_top);
top_state->data.blend.blend_mode = blend_mode;
gtk_snapshot_push_state (snapshot,
top_state->transform,
gtk_snapshot_collect_blend_bottom,
NULL);
}
static GskRenderNode *
gtk_snapshot_collect_mask_source (GtkSnapshot *snapshot,
GtkSnapshotState *state,
GskRenderNode **nodes,
guint n_nodes)
{
GskRenderNode *source_child, *mask_child, *mask_node;
mask_child = state->data.mask.mask_node;
source_child = gtk_snapshot_collect_default (snapshot, state, nodes, n_nodes);
if (source_child == NULL)
return NULL;
if (mask_child)
mask_node = gsk_mask_node_new (source_child, mask_child, state->data.mask.mask_mode);
else if (state->data.mask.mask_mode == GSK_MASK_MODE_INVERTED_ALPHA)
mask_node = gsk_render_node_ref (source_child);
else
mask_node = NULL;
gsk_render_node_unref (source_child);
return mask_node;
}
static void
gtk_snapshot_clear_mask_source (GtkSnapshotState *state)
{
g_clear_pointer (&(state->data.mask.mask_node), gsk_render_node_unref);
}
static GskRenderNode *
gtk_snapshot_collect_mask_mask (GtkSnapshot *snapshot,
GtkSnapshotState *state,
GskRenderNode **nodes,
guint n_nodes)
{
GtkSnapshotState *prev_state = gtk_snapshot_get_previous_state (snapshot);
g_assert (prev_state->collect_func == gtk_snapshot_collect_mask_source);
prev_state->data.mask.mask_node = gtk_snapshot_collect_default (snapshot, state, nodes, n_nodes);
return NULL;
}
/**
* gtk_snapshot_push_mask :
* @ snapshot : a # GtkSnapshot
* @ mask_mode : mask mode to use
*
* Until the first call to [ method @ Gtk . Snapshot . pop ] , the
* mask image for the mask operation will be recorded .
*
* After that call , the source image will be recorded until
* the second call to [ method @ Gtk . Snapshot . pop ] .
*
* Calling this function requires 2 subsequent calls to gtk_snapshot_pop ( ) .
*
* Since : 4 . 10
*/
void
gtk_snapshot_push_mask (GtkSnapshot *snapshot,
GskMaskMode mask_mode)
{
GtkSnapshotState *current_state = gtk_snapshot_get_current_state (snapshot);
GtkSnapshotState *source_state;
source_state = gtk_snapshot_push_state (snapshot,
current_state->transform,
gtk_snapshot_collect_mask_source,
gtk_snapshot_clear_mask_source);
source_state->data.mask.mask_mode = mask_mode;
gtk_snapshot_push_state (snapshot,
source_state->transform,
gtk_snapshot_collect_mask_mask,
NULL);
}
static GskRenderNode *
gtk_snapshot_collect_copy (GtkSnapshot *snapshot,
GtkSnapshotState *state,
GskRenderNode **nodes,
guint n_nodes)
{
GskRenderNode *node, *copy_node;
node = gtk_snapshot_collect_default (snapshot, state, nodes, n_nodes);
if (node == NULL)
return NULL;
copy_node = gsk_copy_node_new (node);
gsk_render_node_unref (node);
return copy_node;
}
/**
* gtk_snapshot_push_copy :
* @ snapshot : a ` GtkSnapshot `
*
* Stores the current rendering state for later pasting via
* [ method @ Gtk . Snapshot . append_paste ] .
*
* Pasting is possible until the matching call to [ method @ Gtk . Snapshot . pop ] .
*
* Since : 4 . 22
*/
void
gtk_snapshot_push_copy (GtkSnapshot *snapshot)
{
GtkSnapshotState *current_state;
/* need identity here because the coords are used
* by pastes */
gtk_snapshot_ensure_identity (snapshot);
current_state = gtk_snapshot_get_current_state (snapshot);
gtk_snapshot_push_state (snapshot,
current_state->transform,
gtk_snapshot_collect_copy,
NULL);
}
static GskRenderNode *
gtk_snapshot_collect_composite_child (GtkSnapshot *snapshot,
GtkSnapshotState *state,
GskRenderNode **nodes,
guint n_nodes)
{
GskRenderNode *child, *mask, *result;
mask = state->data.composite.mask;
child = gtk_snapshot_collect_default (snapshot, state, nodes, n_nodes);
if (child == NULL)
return NULL;
if (mask == NULL)
{
gsk_render_node_unref (child);
return NULL;
}
result = gsk_composite_node_new (child, mask, state->data.composite.op);
gsk_render_node_unref (child);
return result;
}
static void
gtk_snapshot_clear_composite_child (GtkSnapshotState *state)
{
g_clear_pointer (&(state->data.composite.mask), gsk_render_node_unref);
}
static GskRenderNode *
gtk_snapshot_collect_composite_mask (GtkSnapshot *snapshot,
GtkSnapshotState *state,
GskRenderNode **nodes,
guint n_nodes)
{
GtkSnapshotState *prev_state = gtk_snapshot_get_previous_state (snapshot);
g_assert (prev_state->collect_func == gtk_snapshot_collect_composite_child);
prev_state->data.composite.mask = gtk_snapshot_collect_default (snapshot, state, nodes, n_nodes);
return NULL;
}
/**
* gtk_snapshot_push_composite :
* @ snapshot : a # GtkSnapshot
* @ op : The Porter / Duff compositing operator to use
*
* Until the first call to [ method @ Gtk . Snapshot . pop ] , the
* mask image for the mask operation will be recorded .
*
* After that call , the child image will be recorded until
* the second call to [ method @ Gtk . Snapshot . pop ] .
*
* Calling this function requires 2 subsequent calls to gtk_snapshot_pop ( ) .
*
* Since : 4 . 22
*/
void
gtk_snapshot_push_composite (GtkSnapshot *snapshot,
GskPorterDuff op)
{
GtkSnapshotState *current_state = gtk_snapshot_get_current_state (snapshot);
GtkSnapshotState *child_state;
child_state = gtk_snapshot_push_state (snapshot,
current_state->transform,
gtk_snapshot_collect_composite_child,
gtk_snapshot_clear_composite_child);
child_state->data.composite.op= op;
gtk_snapshot_push_state (snapshot,
child_state->transform,
gtk_snapshot_collect_composite_mask,
NULL);
}
static GskRenderNode *
gtk_snapshot_collect_cross_fade_end (GtkSnapshot *snapshot,
GtkSnapshotState *state,
GskRenderNode **nodes,
guint n_nodes)
{
GskRenderNode *start_node, *end_node, *node;
end_node = gtk_snapshot_collect_default (snapshot, state, nodes, n_nodes);
start_node = state->data.cross_fade.start_node;
state->data.cross_fade.start_node = NULL;
if (state->data.cross_fade.progress <= 0 .0 )
{
node = start_node;
if (end_node)
gsk_render_node_unref (end_node);
}
else if (state->data.cross_fade.progress >= 1 .0 )
{
node = end_node;
if (start_node)
gsk_render_node_unref (start_node);
}
else if (start_node && end_node)
{
node = gsk_cross_fade_node_new (start_node, end_node, state->data.cross_fade.progress);
gsk_render_node_unref (start_node);
gsk_render_node_unref (end_node);
}
else if (start_node)
{
node = gsk_opacity_node_new (start_node, 1 .0 - state->data.cross_fade.progress);
gsk_render_node_unref (start_node);
}
else if (end_node)
{
node = gsk_opacity_node_new (end_node, state->data.cross_fade.progress);
gsk_render_node_unref (end_node);
}
else
{
node = NULL;
}
return node;
}
static void
gtk_snapshot_clear_cross_fade_end (GtkSnapshotState *state)
{
g_clear_pointer (&state->data.cross_fade.start_node, gsk_render_node_unref);
}
static GskRenderNode *
gtk_snapshot_collect_cross_fade_start (GtkSnapshot *snapshot,
GtkSnapshotState *state,
GskRenderNode **nodes,
guint n_nodes)
{
GtkSnapshotState *prev_state = gtk_snapshot_get_previous_state (snapshot);
g_assert (prev_state->collect_func == gtk_snapshot_collect_cross_fade_end);
prev_state->data.cross_fade.start_node = gtk_snapshot_collect_default (snapshot, state, nodes, n_nodes);
return NULL;
}
/**
* gtk_snapshot_push_cross_fade :
* @ snapshot : a ` GtkSnapshot `
* @ progress : progress between 0 . 0 and 1 . 0
*
* Snapshots a cross - fade operation between two images with the
* given @ progress .
*
* Until the first call to [ method @ Gtk . Snapshot . pop ] , the start image
* will be snapshot . After that call , the end image will be recorded
* until the second call to [ method @ Gtk . Snapshot . pop ] .
*
* Calling this function requires two subsequent calls
* to [ method @ Gtk . Snapshot . pop ] .
*/
void
gtk_snapshot_push_cross_fade (GtkSnapshot *snapshot,
double progress)
{
const GtkSnapshotState *current_state = gtk_snapshot_get_current_state (snapshot);
GtkSnapshotState *end_state;
end_state = gtk_snapshot_push_state (snapshot,
current_state->transform,
gtk_snapshot_collect_cross_fade_end,
gtk_snapshot_clear_cross_fade_end);
end_state->data.cross_fade.progress = progress;
gtk_snapshot_push_state (snapshot,
end_state->transform,
gtk_snapshot_collect_cross_fade_start,
NULL);
}
static GskRenderNode *
gtk_snapshot_collect_displacement (GtkSnapshot *snapshot,
GtkSnapshotState *state,
GskRenderNode **nodes,
guint n_nodes)
{
GskRenderNode *child_node, *node;
child_node = gtk_snapshot_collect_default (snapshot, state, nodes, n_nodes);
if (child_node == NULL)
return NULL;
if (state->data.displacement.displacement_node == NULL)
state->data.displacement.displacement_node = gsk_container_node_new (NULL, 0 );
node = gsk_displacement_node_new (&state->data.displacement.bounds,
state->data.displacement.snap,
child_node,
state->data.displacement.displacement_node,
state->data.displacement.channels,
&state->data.displacement.max,
&state->data.displacement.scale,
&state->data.displacement.offset);
g_object_unref (child_node);
return node;
}
static void
gtk_snapshot_clear_displacement (GtkSnapshotState *state)
{
g_clear_pointer (&state->data.displacement.displacement_node, gsk_render_node_unref);
}
static GskRenderNode *
gtk_snapshot_collect_displacement_displacement (GtkSnapshot *snapshot,
GtkSnapshotState *state,
GskRenderNode **nodes,
guint n_nodes)
{
GtkSnapshotState *prev_state = gtk_snapshot_get_previous_state (snapshot);
g_assert (prev_state->collect_func == gtk_snapshot_collect_displacement);
prev_state->data.displacement.displacement_node = gtk_snapshot_collect_default (snapshot, state, nodes, n_nodes);
return NULL;
}
/*<private>
* gtk_snapshot_push_displacement :
* @ snapshot : a ` GtkSnapshot `
* @ bounds : The rectangle to apply to
* @ channels : Which channels to usefor the displacement in horizontal and
* vertical direction respectively .
* @ max : The maximum displacement in units
* @ scale : The scale to apply to the displacement value
* @ offset : The offset to apply to the displacement value
*
* Snapshots a displacement operation that will use a displacement
* mask to displace a given image . This is modeled after [ SVG ' s feDisplacementMap
* filter ] ( https : //www.w3.org/TR/SVG11/filters.html#feDisplacementMapElement).
*
* Until the first call to [ method @ Gtk . Snapshot . pop ] , the displacement
* mask will be snapshot . After that call , the image to be displaced will be
* recorded until the second call to [ method @ Gtk . Snapshot . pop ] .
*
* The amount to displace is determine by sampling the displacement
* at every coordinate , converting its value into the given colorstate and
* applying the formula ` value = scale * ( value - offset ) ` and clamping the
* resulting value to be between ` - max ` and ` max ` .
*
* Calling this function requires two subsequent calls
* to [ method @ Gtk . Snapshot . pop ] .
*/
void
gtk_snapshot_push_displacement (GtkSnapshot *snapshot,
const graphene_rect_t *bounds,
const GdkColorChannel channels[2 ],
const graphene_size_t *max,
const graphene_size_t *scale,
const graphene_point_t *offset)
{
const GtkSnapshotState *current_state = gtk_snapshot_get_current_state (snapshot);
GtkSnapshotState *state;
float dx, dy, scale_x, scale_y;
gtk_snapshot_ensure_affine (snapshot, &scale_x, &scale_y, &dx, &dy);
state = gtk_snapshot_push_state (snapshot,
current_state->transform,
gtk_snapshot_collect_displacement,
gtk_snapshot_clear_displacement);
gtk_graphene_rect_scale_affine (bounds, scale_x, scale_y, dx, dy, &state->data.displacement.bounds);
state->data.displacement.channels[0 ] = channels[0 ];
state->data.displacement.channels[1 ] = channels[1 ];
state->data.displacement.max = GRAPHENE_SIZE_INIT (max->width * scale_x,
max->height * scale_y);
state->data.displacement.scale = GRAPHENE_SIZE_INIT (scale->width * scale_x,
scale->height * scale_y);
state->data.displacement.offset = GRAPHENE_POINT_INIT (offset->x * scale_x,
offset->y * scale_y);
gtk_snapshot_push_state (snapshot,
state->transform,
gtk_snapshot_collect_displacement_displacement,
NULL);
}
static GskRenderNode *
gtk_snapshot_collect_arithmetic (GtkSnapshot *snapshot,
GtkSnapshotState *state,
GskRenderNode **nodes,
guint n_nodes)
{
GskRenderNode *second_node, *node;
second_node = gtk_snapshot_collect_default (snapshot, state, nodes, n_nodes);
if (state->data.arithmetic.first_node == NULL && second_node == NULL)
return NULL;
if (state->data.arithmetic.first_node == NULL)
state->data.arithmetic.first_node = gsk_container_node_new (NULL, 0 );
if (second_node == NULL)
second_node = gsk_container_node_new (NULL, 0 );
node = gsk_arithmetic_node_new (&state->data.arithmetic.bounds,
state->data.arithmetic.snap,
state->data.arithmetic.first_node,
second_node,
state->data.arithmetic.color_state,
state->data.arithmetic.factors);
gsk_render_node_unref (second_node);
return node;
}
static void
gtk_snapshot_clear_arithmetic (GtkSnapshotState *state)
{
g_clear_pointer (&state->data.arithmetic.first_node, gsk_render_node_unref);
g_clear_pointer (&state->data.arithmetic.color_state, gdk_color_state_unref);
}
static GskRenderNode *
gtk_snapshot_collect_arithmetic_first (GtkSnapshot *snapshot,
GtkSnapshotState *state,
GskRenderNode **nodes,
guint n_nodes)
{
GtkSnapshotState *prev_state = gtk_snapshot_get_previous_state (snapshot);
g_assert (prev_state->collect_func == gtk_snapshot_collect_arithmetic);
prev_state->data.arithmetic.first_node = gtk_snapshot_collect_default (snapshot, state, nodes, n_nodes);
return NULL;
}
/*<private>
* gtk_snapshot_push_arithmetic :
* @ snapshot : a ` GtkSnapshot `
* @ bounds : The rectangle to apply to
* @ color_state : The color state to composite the 2 nodes in
* @ factors : the 4 factors , often named " k1 " to " k4 "
*
* Snapshots 2 children and composites them algorithmically with the given
* factors . This
* Snapshots a displacement operation that will use a displacement
* mask to displace a given image . This is modeled after [ SVG ' s feComposite
* filter with using operator = arithmetic ] ( https : //drafts.csswg.org/filter-effects/#elementdef-fecomposite)
*
* Until the first call to [ method @ Gtk . Snapshot . pop ] , the first child will
* be snapshot . After that call , the second child will be recorded until
* the second call to [ method @ Gtk . Snapshot . pop ] .
*
* Calling this function requires two subsequent calls
* to [ method @ Gtk . Snapshot . pop ] .
*/
void
gtk_snapshot_push_arithmetic (GtkSnapshot *snapshot,
const graphene_rect_t *bounds,
GdkColorState *color_state,
const float factors[4 ])
{
const GtkSnapshotState *current_state = gtk_snapshot_get_current_state (snapshot);
GtkSnapshotState *state;
float dx, dy, scale_x, scale_y;
g_return_if_fail (snapshot != NULL);
g_return_if_fail (bounds != NULL);
g_return_if_fail (color_state != NULL);
g_return_if_fail (factors != NULL);
gtk_snapshot_ensure_affine (snapshot, &scale_x, &scale_y, &dx, &dy);
state = gtk_snapshot_push_state (snapshot,
current_state->transform,
gtk_snapshot_collect_arithmetic,
gtk_snapshot_clear_arithmetic);
gtk_graphene_rect_scale_affine (bounds, scale_x, scale_y, dx, dy, &state->data.arithmetic.bounds);
state->data.arithmetic.color_state = gdk_color_state_ref (color_state);
state->data.arithmetic.factors[0 ] = factors[0 ];
state->data.arithmetic.factors[1 ] = factors[1 ];
state->data.arithmetic.factors[2 ] = factors[2 ];
state->data.arithmetic.factors[3 ] = factors[3 ];
state->data.arithmetic.snap = state->props.snap;
gtk_snapshot_push_state (snapshot,
state->transform,
gtk_snapshot_collect_arithmetic_first,
NULL);
}
static GskRenderNode *
gtk_snapshot_pop_one (GtkSnapshot *snapshot)
{
GtkSnapshotState *state;
guint state_index;
GskRenderNode *node;
if (gtk_snapshot_states_is_empty (&snapshot->state_stack))
{
g_warning ("Too many gtk_snapshot_pop() calls." );
return NULL;
}
state = gtk_snapshot_get_current_state (snapshot);
state_index = gtk_snapshot_states_get_size (&snapshot->state_stack) - 1 ;
if (state->collect_func)
{
node = state->collect_func (snapshot,
state,
(GskRenderNode **) gtk_snapshot_nodes_index (&snapshot->nodes, state->start_node_index),
state->n_nodes);
/* The collect func may not modify the state stack... */
g_assert (state_index == gtk_snapshot_states_get_size (&snapshot->state_stack) - 1 );
/* Remove all the state's nodes from the list of nodes */
g_assert (state->start_node_index + state->n_nodes == gtk_snapshot_nodes_get_size (&snapshot->nodes));
gtk_snapshot_nodes_splice (&snapshot->nodes, state->start_node_index, state->n_nodes, FALSE , NULL, 0 );
}
else
{
GtkSnapshotState *previous_state;
node = NULL;
/* move the nodes to the parent */
previous_state = gtk_snapshot_get_previous_state (snapshot);
previous_state->n_nodes += state->n_nodes;
g_assert (previous_state->start_node_index + previous_state->n_nodes == gtk_snapshot_nodes_get_size (&snapshot->nodes));
}
gtk_snapshot_states_splice (&snapshot->state_stack, state_index, 1 , FALSE , NULL, 0 );
return node;
}
static void
gtk_snapshot_append_node_internal (GtkSnapshot *snapshot,
GskRenderNode *node)
{
GtkSnapshotState *current_state;
current_state = gtk_snapshot_get_current_state (snapshot);
if (current_state)
{
if (gsk_render_node_get_node_type (node) == GSK_CONTAINER_NODE)
{
GskRenderNode **children;
gsize i, n_children;
children = gsk_render_node_get_children (node, &n_children);
for (i = 0 ; i < n_children; i++)
gsk_render_node_ref (children[i]);
gtk_snapshot_nodes_splice (&snapshot->nodes,
gtk_snapshot_nodes_get_size (&snapshot->nodes),
0 ,
FALSE ,
children,
n_children);
current_state->n_nodes += n_children;
gsk_render_node_unref (node);
}
else
{
gtk_snapshot_nodes_append (&snapshot->nodes, node);
current_state->n_nodes ++;
}
}
else
{
g_critical ("Tried appending a node to an already finished snapshot." );
}
}
static GskRenderNode *
gtk_snapshot_pop_internal (GtkSnapshot *snapshot,
gboolean is_texture_pop)
{
GtkSnapshotState *state;
GskRenderNode *node;
guint forgotten_restores = 0 ;
for (state = gtk_snapshot_get_current_state (snapshot);
gtk_snapshot_state_should_autopop (state) ||
state->collect_func == NULL;
state = gtk_snapshot_get_current_state (snapshot))
{
if (state->collect_func == NULL)
forgotten_restores++;
node = gtk_snapshot_pop_one (snapshot);
if (node)
gtk_snapshot_append_node_internal (snapshot, node);
}
if (forgotten_restores)
{
g_warning ("Too many gtk_snapshot_save() calls. %u saves remaining." , forgotten_restores);
}
if (is_texture_pop && (state->collect_func != gtk_snapshot_collect_gl_shader_texture))
{
g_critical ("Unexpected call to gtk_snapshot_gl_shader_pop_texture()." );
return NULL;
}
else if (!is_texture_pop && (state->collect_func == gtk_snapshot_collect_gl_shader_texture))
{
g_critical ("Expected a call to gtk_snapshot_gl_shader_pop_texture()." );
return NULL;
}
return gtk_snapshot_pop_one (snapshot);
}
/*<private>
* gtk_snapshot_push_collect :
*
* Pushes state so a later pop_collect call can collect all nodes
* appended until that point .
*/
void
gtk_snapshot_push_collect (GtkSnapshot *snapshot)
{
GtkSnapshotState *state;
gtk_snapshot_push_state (snapshot,
NULL,
gtk_snapshot_collect_default,
NULL);
state = gtk_snapshot_get_current_state (snapshot);
state->props = (GtkSnapshotProperties) {
.snap = GSK_RECT_SNAP_NONE,
};
}
GskRenderNode *
gtk_snapshot_pop_collect (GtkSnapshot *snapshot)
{
GskRenderNode *result = gtk_snapshot_pop_internal (snapshot, FALSE );
return result;
}
/**
* gtk_snapshot_to_node :
* @ snapshot : a ` GtkSnapshot `
*
* Returns the render node that was constructed
* by @ snapshot .
*
* Note that this function may return % NULL if nothing has been
* added to the snapshot or if its content does not produce pixels
* to be rendered .
*
* After calling this function , it is no longer possible to
* add more nodes to @ snapshot . The only function that should
* be called after this is [ method @ GObject . Object . unref ] .
*
* Returns : ( transfer full ) ( nullable ) : the constructed ` GskRenderNode ` or
* % NULL if there are no nodes to render .
*/
GskRenderNode *
gtk_snapshot_to_node (GtkSnapshot *snapshot)
{
GskRenderNode *result;
result = gtk_snapshot_pop_internal (snapshot, FALSE );
/* We should have exactly our initial state */
if (!gtk_snapshot_states_is_empty (&snapshot->state_stack))
{
g_warning ("Too many gtk_snapshot_push() calls. %zu states remaining." ,
gtk_snapshot_states_get_size (&snapshot->state_stack));
}
gtk_snapshot_states_clear (&snapshot->state_stack);
gtk_snapshot_nodes_clear (&snapshot->nodes);
return result;
}
/**
* gtk_snapshot_to_paintable :
* @ snapshot : a ` GtkSnapshot `
* @ size : ( nullable ) : The size of the resulting paintable
* or % NULL to use the bounds of the snapshot
*
* Returns a paintable encapsulating the render node
* that was constructed by @ snapshot .
*
* After calling this function , it is no longer possible to
* add more nodes to @ snapshot . The only function that should
* be called after this is [ method @ GObject . Object . unref ] .
*
* Returns : ( transfer full ) ( nullable ) : a new ` GdkPaintable `
*/
GdkPaintable *
gtk_snapshot_to_paintable (GtkSnapshot *snapshot,
const graphene_size_t *size)
{
GskRenderNode *node;
GdkPaintable *paintable;
graphene_rect_t bounds;
node = gtk_snapshot_to_node (snapshot);
if (size)
{
graphene_size_init_from_size (&bounds.size, size);
}
else if (node)
{
gsk_render_node_get_bounds (node, &bounds);
bounds.size.width += bounds.origin.x;
bounds.size.height += bounds.origin.y;
}
else
{
bounds.size.width = 0 ;
bounds.size.height = 0 ;
}
bounds.origin.x = 0 ;
bounds.origin.y = 0 ;
paintable = gtk_render_node_paintable_new (node, &bounds);
g_clear_pointer (&node, gsk_render_node_unref);
return paintable;
}
/**
* gtk_snapshot_pop :
* @ snapshot : a ` GtkSnapshot `
*
* Removes the top element from the stack of render nodes ,
* and appends it to the node underneath it .
*/
void
gtk_snapshot_pop (GtkSnapshot *snapshot)
{
GskRenderNode *node;
node = gtk_snapshot_pop_internal (snapshot, FALSE );
if (node)
gtk_snapshot_append_node_internal (snapshot, node);
}
/**
* gtk_snapshot_gl_shader_pop_texture :
* @ snapshot : a ` GtkSnapshot `
*
* Removes the top element from the stack of render nodes and
* adds it to the nearest [ class @ Gsk . GLShaderNode ] below it .
*
* This must be called the same number of times as the number
* of textures is needed for the shader in
* [ method @ Gtk . Snapshot . push_gl_shader ] .
*
* Deprecated : 4 . 16 : GTK ' s new Vulkan - focused rendering
* does not support this feature . Use [ class @ Gtk . GLArea ] for
* OpenGL rendering .
*/
void
gtk_snapshot_gl_shader_pop_texture (GtkSnapshot *snapshot)
{
G_GNUC_UNUSED GskRenderNode *node;
node = gtk_snapshot_pop_internal (snapshot, TRUE );
g_assert (node == NULL);
}
/**
* gtk_snapshot_save :
* @ snapshot : a ` GtkSnapshot `
*
* Makes a copy of the current state of @ snapshot and saves it
* on an internal stack .
*
* When [ method @ Gtk . Snapshot . restore ] is called , @ snapshot will
* be restored to the saved state .
*
* Multiple calls to [ method @ Gtk . Snapshot . save ] and [ method @ Gtk . Snapshot . restore ]
* can be nested ; each call to ` gtk_snapshot_restore ( ) ` restores the state from
* the matching paired ` gtk_snapshot_save ( ) ` .
*
* It is necessary to clear all saved states with corresponding
* calls to ` gtk_snapshot_restore ( ) ` .
*/
void
gtk_snapshot_save (GtkSnapshot *snapshot)
{
GtkSnapshotState *current_state;
g_return_if_fail (GTK_IS_SNAPSHOT (snapshot));
current_state = gtk_snapshot_get_current_state (snapshot);
gtk_snapshot_push_state (snapshot,
current_state->transform,
NULL,
NULL);
}
/**
* gtk_snapshot_restore :
* @ snapshot : a ` GtkSnapshot `
*
* Restores @ snapshot to the state saved by a preceding call to
* [ method @ Snapshot . save ] and removes that state from the stack of
* saved states .
*/
void
gtk_snapshot_restore (GtkSnapshot *snapshot)
{
GtkSnapshotState *state;
GskRenderNode *node;
for (state = gtk_snapshot_get_current_state (snapshot);
gtk_snapshot_state_should_autopop (state);
state = gtk_snapshot_get_current_state (snapshot))
{
node = gtk_snapshot_pop_one (snapshot);
if (node)
gtk_snapshot_append_node_internal (snapshot, node);
}
if (state->collect_func != NULL)
{
g_warning ("Too many gtk_snapshot_restore() calls." );
return ;
}
node = gtk_snapshot_pop_one (snapshot);
g_assert (node == NULL);
}
/**
* gtk_snapshot_set_snap :
* @ self : a ` GtkSnapshot `
* @ snap : the snapping mode to use
*
* Sets the snapping mode to use when appending snappable content
* to the snapshot .
*
* The snap mode is part of the current state , so [ method @ Snapshot . save ]
* and [ method @ Snapshot . restore ] can be used to remember a snap mode .
*
* Since : 4 . 24
**/
void
gtk_snapshot_set_snap (GtkSnapshot *self,
GskRectSnap snap)
{
GtkSnapshotState *state;
g_return_if_fail (GTK_IS_SNAPSHOT (self));
state = gtk_snapshot_get_current_state (self);
state->props.snap = snap;
}
/**
* gtk_snapshot_transform :
* @ snapshot : a ` GtkSnapshot `
* @ transform : ( nullable ) : the transform to apply
*
* Transforms @ snapshot ' s coordinate system with the given @ transform .
*/
void
gtk_snapshot_transform (GtkSnapshot *snapshot,
GskTransform *transform)
{
GtkSnapshotState *state;
g_return_if_fail (GTK_IS_SNAPSHOT (snapshot));
state = gtk_snapshot_get_current_state (snapshot);
state->transform = gsk_transform_transform (state->transform, transform);
}
/**
* gtk_snapshot_transform_matrix :
* @ snapshot : a ` GtkSnapshot `
* @ matrix : the matrix to multiply the transform with
*
* Transforms @ snapshot ' s coordinate system with the given @ matrix .
*/
void
gtk_snapshot_transform_matrix (GtkSnapshot *snapshot,
const graphene_matrix_t *matrix)
{
GtkSnapshotState *state;
g_return_if_fail (GTK_IS_SNAPSHOT (snapshot));
g_return_if_fail (matrix != NULL);
state = gtk_snapshot_get_current_state (snapshot);
state->transform = gsk_transform_matrix (state->transform, matrix);
}
/**
* gtk_snapshot_translate :
* @ snapshot : a ` GtkSnapshot `
* @ point : the point to translate the snapshot by
*
* Translates @ snapshot ' s coordinate system by @ point in 2 - dimensional space .
*/
void
gtk_snapshot_translate (GtkSnapshot *snapshot,
const graphene_point_t *point)
{
GtkSnapshotState *state;
g_return_if_fail (GTK_IS_SNAPSHOT (snapshot));
g_return_if_fail (point != NULL);
state = gtk_snapshot_get_current_state (snapshot);
state->transform = gsk_transform_translate (state->transform, point);
}
/**
* gtk_snapshot_translate_3d :
* @ snapshot : a ` GtkSnapshot `
* @ point : the point to translate the snapshot by
*
* Translates @ snapshot ' s coordinate system by @ point .
*/
void
gtk_snapshot_translate_3d (GtkSnapshot *snapshot,
const graphene_point3d_t *point)
{
GtkSnapshotState *state;
g_return_if_fail (GTK_IS_SNAPSHOT (snapshot));
g_return_if_fail (point != NULL);
state = gtk_snapshot_get_current_state (snapshot);
state->transform = gsk_transform_translate_3d (state->transform, point);
}
/**
* gtk_snapshot_rotate :
* @ snapshot : a ` GtkSnapshot `
* @ angle : the rotation angle , in degrees ( clockwise )
*
* Rotates @ @ snapshot ' s coordinate system by @ angle degrees in 2 D space -
* or in 3 D speak , rotates around the Z axis . The rotation happens around
* the origin point of ( 0 , 0 ) in the @ snapshot ' s current coordinate system .
*
* To rotate around axes other than the Z axis , use [ method @ Gsk . Transform . rotate_3d ] .
*/
void
gtk_snapshot_rotate (GtkSnapshot *snapshot,
float angle)
{
GtkSnapshotState *state;
g_return_if_fail (GTK_IS_SNAPSHOT (snapshot));
state = gtk_snapshot_get_current_state (snapshot);
state->transform = gsk_transform_rotate (state->transform, angle);
}
/**
* gtk_snapshot_rotate_3d :
* @ snapshot : a ` GtkSnapshot `
* @ angle : the rotation angle , in degrees ( clockwise )
* @ axis : The rotation axis
*
* Rotates @ snapshot ' s coordinate system by @ angle degrees around @ axis .
*
* For a rotation in 2 D space , use [ method @ Gsk . Transform . rotate ] .
*/
void
gtk_snapshot_rotate_3d (GtkSnapshot *snapshot,
float angle,
const graphene_vec3_t *axis)
{
GtkSnapshotState *state;
g_return_if_fail (GTK_IS_SNAPSHOT (snapshot));
g_return_if_fail (axis != NULL);
state = gtk_snapshot_get_current_state (snapshot);
state->transform = gsk_transform_rotate_3d (state->transform, angle, axis);
}
/**
* gtk_snapshot_scale :
* @ snapshot : a ` GtkSnapshot `
* @ 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
* the given factors .
*
* Use [ method @ Gtk . Snapshot . scale_3d ] to scale in all 3 dimensions .
*/
void
gtk_snapshot_scale (GtkSnapshot *snapshot,
float factor_x,
float factor_y)
{
GtkSnapshotState *state;
g_return_if_fail (GTK_IS_SNAPSHOT (snapshot));
state = gtk_snapshot_get_current_state (snapshot);
state->transform = gsk_transform_scale (state->transform, factor_x, factor_y);
}
/**
* gtk_snapshot_scale_3d :
* @ snapshot : a ` GtkSnapshot `
* @ 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 @ snapshot ' s coordinate system by the given factors .
*/
void
gtk_snapshot_scale_3d (GtkSnapshot *snapshot,
float factor_x,
float factor_y,
float factor_z)
{
GtkSnapshotState *state;
g_return_if_fail (GTK_IS_SNAPSHOT (snapshot));
state = gtk_snapshot_get_current_state (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 [ method @ 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);
}
/**
* gtk_snapshot_append_node :
* @ snapshot : a ` GtkSnapshot `
* @ node : a ` GskRenderNode `
*
* Appends @ node to the current render node of @ snapshot ,
* without changing the current node .
*
* If @ snapshot does not have a current node yet , @ node
* will become the initial node .
*/
void
gtk_snapshot_append_node (GtkSnapshot *snapshot,
GskRenderNode *node)
{
g_return_if_fail (snapshot != NULL);
g_return_if_fail (GSK_IS_RENDER_NODE (node));
gtk_snapshot_ensure_identity (snapshot);
gtk_snapshot_append_node_internal (snapshot, gsk_render_node_ref (node));
}
/*< private>
* gtk_snapshot_append_node_scaled :
* @ snapshot : a ` GtkSnapshot `
* @ node : a ` GskRenderNode `
* @ from : first rectangle
* @ to : second rectangle
*
* Appends @ node to the current render node of @ snapshot ,
* without changing the current node , with a transform
* that maps @ from to @ to .
*
* If @ snapshot does not have a current node yet , @ node
* will become the initial node .
*/
void
gtk_snapshot_append_node_scaled (GtkSnapshot *snapshot,
GskRenderNode *node,
graphene_rect_t *from,
graphene_rect_t *to)
{
if (gsk_render_node_get_node_type (node) == GSK_TEXTURE_NODE &&
gsk_rect_equal (from, &node->bounds))
{
gtk_snapshot_append_texture (snapshot, gsk_texture_node_get_texture (node), to);
}
else if (gsk_rect_equal (from, to))
{
gtk_snapshot_append_node (snapshot, node);
}
else
{
gtk_snapshot_save (snapshot);
gtk_snapshot_translate (snapshot, &GRAPHENE_POINT_INIT (to->origin.x,
to->origin.y));
gtk_snapshot_scale (snapshot, to->size.width / from->size.width,
to->size.height / from->size.height);
gtk_snapshot_translate (snapshot, &GRAPHENE_POINT_INIT (- from->origin.x,
- from->origin.y));
gtk_snapshot_append_node (snapshot, node);
gtk_snapshot_restore (snapshot);
}
}
/**
* gtk_snapshot_append_cairo :
* @ snapshot : a ` GtkSnapshot `
* @ bounds : the bounds for the new node
*
* Creates a new [ class @ Gsk . CairoNode ] and appends it to the current
* render node of @ snapshot , without changing the current node .
*
* Returns : a ` cairo_t ` suitable for drawing the contents of
* the newly created render node
*/
cairo_t *
gtk_snapshot_append_cairo (GtkSnapshot *snapshot,
const graphene_rect_t *bounds)
{
GskRenderNode *node;
graphene_rect_t real_bounds;
float scale_x, scale_y, dx, dy;
cairo_t *cr;
g_return_val_if_fail (snapshot != NULL, NULL);
g_return_val_if_fail (bounds != NULL, NULL);
gtk_snapshot_ensure_affine (snapshot, &scale_x, &scale_y, &dx, &dy);
gtk_graphene_rect_scale_affine (bounds, scale_x, scale_y, dx, dy, &real_bounds);
node = gsk_cairo_node_new (&real_bounds);
gtk_snapshot_append_node_internal (snapshot, node);
cr = gsk_cairo_node_get_draw_context (node);
cairo_scale (cr, scale_x, scale_y);
cairo_translate (cr, dx, dy);
return cr;
}
/**
* gtk_snapshot_append_texture :
* @ snapshot : a ` GtkSnapshot `
* @ texture : the texture to render
* @ bounds : the bounds for the new node
*
* Creates a new render node drawing the @ texture
* into the given @ bounds and appends it to the
* current render node of @ snapshot .
*
* If the texture needs to be scaled to fill @ bounds ,
* linear filtering is used . See [ method @ Gtk . Snapshot . append_scaled_texture ]
* if you need other filtering , such as nearest - neighbour .
*/
void
gtk_snapshot_append_texture (GtkSnapshot *snapshot,
GdkTexture *texture,
const graphene_rect_t *bounds)
{
const GtkSnapshotState *state = gtk_snapshot_get_current_state (snapshot);
GskRenderNode *node;
graphene_rect_t real_bounds;
float scale_x, scale_y, dx, dy;
g_return_if_fail (snapshot != NULL);
g_return_if_fail (GDK_IS_TEXTURE (texture));
g_return_if_fail (bounds != NULL);
gtk_snapshot_ensure_affine (snapshot, &scale_x, &scale_y, &dx, &dy);
gtk_graphene_rect_scale_affine (bounds, scale_x, scale_y, dx, dy, &real_bounds);
node = gsk_texture_node_new2 (texture, &real_bounds, state->props.snap);
gtk_snapshot_append_node_internal (snapshot, node);
}
/**
* gtk_snapshot_append_scaled_texture :
* @ snapshot : a ` GtkSnapshot `
* @ texture : the texture to render
* @ filter : the filter to use
* @ bounds : the bounds for the new node
*
* Creates a new render node drawing the @ texture
* into the given @ bounds and appends it to the
* current render node of @ snapshot .
*
* In contrast to [ method @ Gtk . Snapshot . append_texture ] ,
* this function provides control about how the filter
* that is used when scaling .
*
* Since : 4 . 10
*/
void
gtk_snapshot_append_scaled_texture (GtkSnapshot *snapshot,
GdkTexture *texture,
GskScalingFilter filter,
const graphene_rect_t *bounds)
{
const GtkSnapshotState *state = gtk_snapshot_get_current_state (snapshot);
GskRenderNode *node;
g_return_if_fail (snapshot != NULL);
g_return_if_fail (GDK_IS_TEXTURE (texture));
g_return_if_fail (bounds != NULL);
gtk_snapshot_ensure_identity (snapshot);
node = gsk_texture_scale_node_new2 (texture, bounds, state->props.snap, filter);
gtk_snapshot_append_node_internal (snapshot, node);
}
/**
* gtk_snapshot_append_color :
* @ snapshot : a ` GtkSnapshot `
* @ color : the color to draw
* @ bounds : the bounds for the new node
*
* Creates a new render node drawing the @ color into the
* given @ bounds and appends it to the current render node
* of @ snapshot .
*
* You should try to avoid calling this function if
* @ color is transparent .
*/
void
gtk_snapshot_append_color (GtkSnapshot *snapshot,
const GdkRGBA *color,
const graphene_rect_t *bounds)
{
GdkColor color2;
gdk_color_init_from_rgba (&color2, color);
gtk_snapshot_add_color (snapshot, &color2, bounds);
}
/*< private >
* gtk_snapshot_add_color :
* @ snapshot : a ` GtkSnapshot `
* @ color : the color to draw
* @ bounds : the bounds for the new node
*
* Creates a new render node drawing the @ color into the
* given @ bounds and appends it to the current render node
* of @ snapshot .
*/
void
gtk_snapshot_add_color (GtkSnapshot *snapshot,
const GdkColor *color,
const graphene_rect_t *bounds)
{
const GtkSnapshotState *state = gtk_snapshot_get_current_state (snapshot);
GskRenderNode *node;
graphene_rect_t real_bounds;
float scale_x, scale_y, dx, dy;
g_return_if_fail (snapshot != NULL);
g_return_if_fail (color != NULL);
g_return_if_fail (bounds != NULL);
gtk_snapshot_ensure_affine (snapshot, &scale_x, &scale_y, &dx, &dy);
gtk_graphene_rect_scale_affine (bounds, scale_x, scale_y, dx, dy, &real_bounds);
node = gsk_color_node_new2 (color, &real_bounds, state->props.snap);
gtk_snapshot_append_node_internal (snapshot, node);
}
void
gtk_snapshot_append_text (GtkSnapshot *snapshot,
PangoFont *font,
PangoGlyphString *glyphs,
const GdkRGBA *color,
float x,
float y)
{
GdkColor color2;
gdk_color_init_from_rgba (&color2, color);
gtk_snapshot_add_text (snapshot, font, glyphs, &color2, x, y);
gdk_color_finish (&color2);
}
void
gtk_snapshot_add_text (GtkSnapshot *snapshot,
PangoFont *font,
PangoGlyphString *glyphs,
const GdkColor *color,
float x,
float y)
{
GskRenderNode *node;
float dx, dy;
g_return_if_fail (snapshot != NULL);
g_return_if_fail (PANGO_IS_FONT (font));
g_return_if_fail (glyphs != NULL);
g_return_if_fail (color != NULL);
gtk_snapshot_ensure_translate (snapshot, &dx, &dy);
node = gsk_text_node_new2 (font,
glyphs,
color,
&GRAPHENE_POINT_INIT (x + dx, y + dy));
if (node == NULL)
return ;
gtk_snapshot_append_node_internal (snapshot, node);
}
/**
* gtk_snapshot_append_linear_gradient :
* @ snapshot : a ` GtkSnapshot `
* @ bounds : the rectangle to render the linear gradient into
* @ start_point : the point at which the linear gradient will begin
* @ end_point : the point at which the linear gradient will finish
* @ stops : ( array length = n_stops ) : the color stops defining the gradient
* @ n_stops : the number of elements in @ stops
*
* Appends a linear gradient node with the given stops to @ snapshot .
*/
void
gtk_snapshot_append_linear_gradient (GtkSnapshot *snapshot,
const graphene_rect_t *bounds,
const graphene_point_t *start_point,
const graphene_point_t *end_point,
const GskColorStop *stops,
gsize n_stops)
{
GskGradient *gradient;
gradient = gsk_gradient_new ();
gsk_gradient_add_color_stops (gradient, stops, n_stops);
gtk_snapshot_add_linear_gradient (snapshot, bounds,
start_point, end_point,
gradient);
gsk_gradient_free (gradient);
}
/*< private >
* gtk_snapshot_add_linear_gradient :
* @ snapshot : a ` GtkSnapshot `
* @ bounds : the rectangle to render the linear gradient into
* @ start_point : the point at which the linear gradient will begin
* @ end_point : the point at which the linear gradient will finish
* @ gradient : the gradient specification
*
* Appends a linear gradient node with the given stops to @ snapshot .
*/
void
gtk_snapshot_add_linear_gradient (GtkSnapshot *snapshot,
const graphene_rect_t *bounds,
const graphene_point_t *start_point,
const graphene_point_t *end_point,
const GskGradient *gradient)
{
const GtkSnapshotState *state = gtk_snapshot_get_current_state (snapshot);
GskRenderNode *node;
graphene_rect_t real_bounds;
float scale_x, scale_y, dx, dy;
const GdkColor *color;
g_return_if_fail (snapshot != NULL);
g_return_if_fail (start_point != NULL);
g_return_if_fail (end_point != NULL);
gtk_snapshot_ensure_affine_with_flags (snapshot,
ENSURE_POSITIVE_SCALE | ENSURE_UNIFORM_SCALE,
&scale_x, &scale_y,
&dx, &dy);
gtk_graphene_rect_scale_affine (bounds, scale_x, scale_y, dx, dy, &real_bounds);
color = gsk_gradient_check_single_color (gradient);
if (color == NULL)
{
graphene_point_t real_start_point, real_end_point;
real_start_point.x = scale_x * start_point->x + dx;
real_start_point.y = scale_y * start_point->y + dy;
real_end_point.x = scale_x * end_point->x + dx;
real_end_point.y = scale_y * end_point->y + dy;
node = gsk_linear_gradient_node_new2 (&real_bounds,
state->props.snap,
&real_start_point,
&real_end_point,
gradient);
}
else
{
node = gsk_color_node_new2 (color, &real_bounds, state->props.snap);
}
gtk_snapshot_append_node_internal (snapshot, node);
}
/**
* gtk_snapshot_append_repeating_linear_gradient :
* @ snapshot : a ` GtkSnapshot `
* @ bounds : the rectangle to render the linear gradient into
* @ start_point : the point at which the linear gradient will begin
* @ end_point : the point at which the linear gradient will finish
* @ stops : ( array length = n_stops ) : the color stops defining the gradient
* @ n_stops : the number of elements in @ stops
*
* Appends a repeating linear gradient node with the given stops to @ snapshot .
*/
void
gtk_snapshot_append_repeating_linear_gradient (GtkSnapshot *snapshot,
const graphene_rect_t *bounds,
const graphene_point_t *start_point,
const graphene_point_t *end_point,
const GskColorStop *stops,
gsize n_stops)
{
GskGradient *gradient;
gradient = gsk_gradient_new ();
gsk_gradient_add_color_stops (gradient, stops, n_stops);
gsk_gradient_set_repeat (gradient, GSK_REPEAT_REPEAT);
gtk_snapshot_add_linear_gradient (snapshot, bounds,
start_point, end_point,
gradient);
gsk_gradient_free (gradient);
}
/**
* gtk_snapshot_append_conic_gradient :
* @ snapshot : a ` GtkSnapshot `
* @ bounds : the rectangle to render the gradient into
* @ center : the center point of the conic gradient
* @ rotation : the clockwise rotation in degrees of the starting angle .
* 0 means the starting angle is the top .
* @ stops : ( array length = n_stops ) : the color stops defining the gradient
* @ n_stops : the number of elements in @ stops
*
* Appends a conic gradient node with the given stops to @ snapshot .
*/
void
gtk_snapshot_append_conic_gradient (GtkSnapshot *snapshot,
const graphene_rect_t *bounds,
const graphene_point_t *center,
float rotation,
const GskColorStop *stops,
gsize n_stops)
{
GskGradient *gradient;
gradient = gsk_gradient_new ();
gsk_gradient_add_color_stops (gradient, stops, n_stops);
gtk_snapshot_add_conic_gradient (snapshot, bounds,
center, rotation,
gradient);
gsk_gradient_free (gradient);
}
/*< private >
* gtk_snapshot_add_conic_gradient :
* @ snapshot : a ` GtkSnapshot `
* @ bounds : the rectangle to render the gradient into
* @ center : the center point of the conic gradient
* @ rotation : the clockwise rotation in degrees of the starting angle .
* 0 means the starting angle is the top .
* @ gradient : the gradient specification
*
* Appends a conic gradient node with the given stops to @ snapshot .
*/
void
gtk_snapshot_add_conic_gradient (GtkSnapshot *snapshot,
const graphene_rect_t *bounds,
const graphene_point_t *center,
float rotation,
const GskGradient *gradient)
{
const GtkSnapshotState *state = gtk_snapshot_get_current_state (snapshot);
GskRenderNode *node;
graphene_rect_t real_bounds;
float dx, dy;
const GdkColor *color;
g_return_if_fail (snapshot != NULL);
g_return_if_fail (center != NULL);
gtk_snapshot_ensure_translate (snapshot, &dx, &dy);
graphene_rect_offset_r (bounds, dx, dy, &real_bounds);
color = gsk_gradient_check_single_color (gradient);
if (color == NULL)
node = gsk_conic_gradient_node_new2 (&real_bounds,
state->props.snap,
&GRAPHENE_POINT_INIT(
center->x + dx,
center->y + dy
),
rotation,
gradient);
else
node = gsk_color_node_new2 (color, &real_bounds, state->props.snap);
gtk_snapshot_append_node_internal (snapshot, node);
}
/**
* gtk_snapshot_append_radial_gradient :
* @ snapshot : a ` GtkSnapshot `
* @ bounds : the rectangle to render the readial gradient into
* @ center : the center point for the radial gradient
* @ hradius : the horizontal radius
* @ vradius : the vertical radius
* @ start : the start position ( on the horizontal axis )
* @ end : the end position ( on the horizontal axis )
* @ stops : ( array length = n_stops ) : the color stops defining the gradient
* @ n_stops : the number of elements in @ stops
*
* Appends a radial gradient node with the given stops to @ snapshot .
*/
void
gtk_snapshot_append_radial_gradient (GtkSnapshot *snapshot,
const graphene_rect_t *bounds,
const graphene_point_t *center,
float hradius,
float vradius,
float start,
float end,
const GskColorStop *stops,
gsize n_stops)
{
GskGradient *gradient;
gradient = gsk_gradient_new ();
gsk_gradient_add_color_stops (gradient, stops, n_stops);
gtk_snapshot_add_radial_gradient (snapshot,
bounds,
center, hradius * start,
center, hradius * end,
hradius / vradius,
gradient);
gsk_gradient_free (gradient);
}
/*< private>
* gtk_snapshot_add_radial_gradient :
* @ snapshot : a ` GtkSnapshot `
* @ bounds : the rectangle to render the readial gradient into
* @ start_center : the center for the start circle
* @ start_radius : the radius for the start circle
* @ end_center : the center for the end circle
* @ end_radius : the radius for the end circle
* @ aspect_ratio : the aspect ratio of the circles
* @ gradient : the gradient specification
*
* Appends a radial gradient node with the given stops to @ snapshot .
*/
void
gtk_snapshot_add_radial_gradient (GtkSnapshot *snapshot,
const graphene_rect_t *bounds,
const graphene_point_t *start_center,
float start_radius,
const graphene_point_t *end_center,
float end_radius,
float aspect_ratio,
const GskGradient *gradient)
{
const GtkSnapshotState *state = gtk_snapshot_get_current_state (snapshot);
GskRenderNode *node;
graphene_rect_t real_bounds;
float scale_x, scale_y, dx, dy;
const GdkColor *color;
g_return_if_fail (snapshot != NULL);
g_return_if_fail (start_center != NULL);
g_return_if_fail (start_radius >= 0 );
g_return_if_fail (end_center != NULL);
g_return_if_fail (end_radius >= 0 );
g_return_if_fail (aspect_ratio > 0 );
gtk_snapshot_ensure_affine (snapshot, &scale_x, &scale_y, &dx, &dy);
gtk_graphene_rect_scale_affine (bounds, scale_x, scale_y, dx, dy, &real_bounds);
color = gsk_gradient_check_single_color (gradient);
if (color && gsk_radial_gradient_fills_plane (start_center, start_radius,
end_center, end_radius))
{
node = gsk_color_node_new2 (color, &real_bounds, state->props.snap);
}
else
{
graphene_point_t real_start;
graphene_point_t real_end;
real_start.x = scale_x * start_center->x + dx;
real_start.y = scale_y * start_center->y + dy;
real_end.x = scale_x * end_center->x + dx;
real_end.y = scale_y * end_center->y + dy;
node = gsk_radial_gradient_node_new2 (&real_bounds,
state->props.snap,
&real_start, start_radius * scale_x,
&real_end, end_radius * scale_x,
aspect_ratio * (scale_x / scale_y),
gradient);
}
gtk_snapshot_append_node_internal (snapshot, node);
}
/**
* gtk_snapshot_append_repeating_radial_gradient :
* @ snapshot : a ` GtkSnapshot `
* @ bounds : the rectangle to render the readial gradient into
* @ center : the center point for the radial gradient
* @ hradius : the horizontal radius
* @ vradius : the vertical radius
* @ start : the start position ( on the horizontal axis )
* @ end : the end position ( on the horizontal axis )
* @ stops : ( array length = n_stops ) : the color stops defining the gradient
* @ n_stops : the number of elements in @ stops
*
* Appends a repeating radial gradient node with the given stops to @ snapshot .
*/
void
gtk_snapshot_append_repeating_radial_gradient (GtkSnapshot *snapshot,
const graphene_rect_t *bounds,
const graphene_point_t *center,
float hradius,
float vradius,
float start,
float end,
const GskColorStop *stops,
gsize n_stops)
{
GskGradient *gradient;
gradient = gsk_gradient_new ();
gsk_gradient_set_repeat (gradient, GSK_REPEAT_REPEAT);
gsk_gradient_add_color_stops (gradient, stops, n_stops);
gtk_snapshot_add_radial_gradient (snapshot, bounds,
center, hradius * start,
center, hradius * end,
hradius / vradius,
gradient);
gsk_gradient_free (gradient);
}
/**
* gtk_snapshot_append_border :
* @ snapshot : a ` GtkSnapshot `
* @ outline : the outline of the border
* @ border_width : ( array fixed - size = 4 ) : the stroke width of the border on
* the top , right , bottom and left side respectively .
* @ border_color : ( array fixed - size = 4 ) : the color used on the top , right ,
* bottom and left side .
*
* Appends a stroked border rectangle inside the given @ outline .
*
* The four sides of the border can have different widths and colors .
*/
void
gtk_snapshot_append_border (GtkSnapshot *snapshot,
const GskRoundedRect *outline,
const float border_width[4 ],
const GdkRGBA border_color[4 ])
{
GdkColor color[4 ];
for (int i = 0 ; i < 4 ; i++)
gdk_color_init_from_rgba (&color[i], &border_color[i]);
gtk_snapshot_add_border (snapshot, outline, border_width, GSK_RECT_SNAP_NONE, color);
for (int i = 0 ; i < 4 ; i++)
gdk_color_finish (&color[i]);
}
/*< private >
* gtk_snapshot_add_border :
* @ snapshot : a ` GtkSnapshot `
* @ outline : the outline of the border
* @ border_width : ( array fixed - size = 4 ) : the stroke width of the border on
* the top , right , bottom and left side respectively .
* @ border_snap : how to snap the border to the pixel grid
* @ border_color : ( array fixed - size = 4 ) : the color used on the top , right ,
* bottom and left side .
*
* Appends a stroked border rectangle inside the given @ outline .
*
* The four sides of the border can have different widths and colors .
*/
void
gtk_snapshot_add_border (GtkSnapshot *snapshot,
const GskRoundedRect *outline,
const float border_width[4 ],
GskRectSnap border_snap,
const GdkColor border_color[4 ])
{
const GtkSnapshotState *state = gtk_snapshot_get_current_state (snapshot);
GskRenderNode *node;
GskRoundedRect real_outline;
float scale_x, scale_y, dx, dy;
g_return_if_fail (snapshot != NULL);
g_return_if_fail (outline != NULL);
g_return_if_fail (border_width != NULL);
g_return_if_fail (border_color != NULL);
gtk_snapshot_ensure_affine (snapshot, &scale_x, &scale_y, &dx, &dy);
gsk_rounded_rect_scale_affine (&real_outline, outline, scale_x, scale_y, dx, dy);
node = gsk_border_node_new2 (&real_outline,
state->props.snap,
(float [4 ]) {
border_width[0 ] * scale_y,
border_width[1 ] * scale_x,
border_width[2 ] * scale_y,
border_width[3 ] * scale_x,
},
border_snap,
border_color);
gtk_snapshot_append_node_internal (snapshot, node);
}
/**
* gtk_snapshot_append_inset_shadow :
* @ snapshot : a ` GtkSnapshot `
* @ outline : outline of the region surrounded by shadow
* @ color : color of the shadow
* @ dx : horizontal offset of shadow
* @ dy : vertical offset of shadow
* @ spread : how far the shadow spreads towards the inside
* @ blur_radius : how much blur to apply to the shadow
*
* Appends an inset shadow into the box given by @ outline .
*/
void
gtk_snapshot_append_inset_shadow (GtkSnapshot *snapshot,
const GskRoundedRect *outline,
const GdkRGBA *color,
float dx,
float dy,
float spread,
float blur_radius)
{
GdkColor color2;
gdk_color_init_from_rgba (&color2, color);
gtk_snapshot_add_inset_shadow (snapshot,
outline,
&color2,
&GRAPHENE_POINT_INIT (dx, dy),
spread, blur_radius);
gdk_color_finish (&color2);
}
/*< private >
* gtk_snapshot_add_inset_shadow :
* @ snapshot : a ` GtkSnapshot `
* @ outline : outline of the region surrounded by shadow
* @ color : color of the shadow
* @ offset : offset of shadow
* @ spread : how far the shadow spreads towards the inside
* @ blur_radius : how much blur to apply to the shadow
*
* Appends an inset shadow into the box given by @ outline .
*/
void
gtk_snapshot_add_inset_shadow (GtkSnapshot *snapshot,
const GskRoundedRect *outline,
const GdkColor *color,
const graphene_point_t *offset,
float spread,
float blur_radius)
{
const GtkSnapshotState *state = gtk_snapshot_get_current_state (snapshot);
GskRenderNode *node;
GskRoundedRect real_outline;
float scale_x, scale_y, x, y;
g_return_if_fail (snapshot != NULL);
g_return_if_fail (outline != NULL);
g_return_if_fail (color != NULL);
g_return_if_fail (offset != NULL);
gtk_snapshot_ensure_affine (snapshot, &scale_x, &scale_y, &x, &y);
gsk_rounded_rect_scale_affine (&real_outline, outline, scale_x, scale_y, x, y);
node = gsk_inset_shadow_node_new2 (&real_outline,
state->props.snap,
color,
&GRAPHENE_POINT_INIT (scale_x * offset->x,
scale_y * offset->y),
spread,
blur_radius);
gtk_snapshot_append_node_internal (snapshot, node);
}
/**
* gtk_snapshot_append_outset_shadow :
* @ snapshot : a ` GtkSnapshot `
* @ outline : outline of the region surrounded by shadow
* @ color : color of the shadow
* @ dx : horizontal offset of shadow
* @ dy : vertical offset of shadow
* @ spread : how far the shadow spreads towards the outside
* @ blur_radius : how much blur to apply to the shadow
*
* Appends an outset shadow node around the box given by @ outline .
*/
void
gtk_snapshot_append_outset_shadow (GtkSnapshot *snapshot,
const GskRoundedRect *outline,
const GdkRGBA *color,
float dx,
float dy,
float spread,
float blur_radius)
{
GdkColor color2;
gdk_color_init_from_rgba (&color2, color);
gtk_snapshot_add_outset_shadow (snapshot,
outline,
&color2,
&GRAPHENE_POINT_INIT (dx, dy),
spread, blur_radius);
gdk_color_finish (&color2);
}
/*< private >
* gtk_snapshot_add_outset_shadow :
* @ snapshot : a ` GtkSnapshot `
* @ outline : outline of the region surrounded by shadow
* @ color : color of the shadow
* @ offset : offset of shadow
* @ spread : how far the shadow spreads towards the outside
* @ blur_radius : how much blur to apply to the shadow
*
* Appends an outset shadow node around the box given by @ outline .
*/
void
gtk_snapshot_add_outset_shadow (GtkSnapshot *snapshot,
const GskRoundedRect *outline,
const GdkColor *color,
const graphene_point_t *offset,
float spread,
float blur_radius)
{
const GtkSnapshotState *state = gtk_snapshot_get_current_state (snapshot);
GskRenderNode *node;
GskRoundedRect real_outline;
float scale_x, scale_y, x, y;
g_return_if_fail (snapshot != NULL);
g_return_if_fail (outline != NULL);
g_return_if_fail (color != NULL);
g_return_if_fail (offset != NULL);
gtk_snapshot_ensure_affine (snapshot, &scale_x, &scale_y, &x, &y);
gsk_rounded_rect_scale_affine (&real_outline, outline, scale_x, scale_y, x, y);
node = gsk_outset_shadow_node_new2 (&real_outline,
state->props.snap,
color,
&GRAPHENE_POINT_INIT (scale_x * offset->x,
scale_y * offset->y),
spread,
blur_radius);
gtk_snapshot_append_node_internal (snapshot, node);
}
/**
* gtk_snapshot_append_paste :
* @ snapshot : a ` GtkSnapshot `
* @ bounds : the bounds for the new node
* @ nth : the index of the copy , with 0 being the latest
* copy , 1 being the copy before that , and so on .
*
* Creates a new render node that pastes the contents
* copied by a previous call to [ method @ Gtk . Snapshot . push_copy ]
*
* Since : 4 . 22
*/
void
gtk_snapshot_append_paste (GtkSnapshot *snapshot,
const graphene_rect_t *bounds,
gsize nth)
{
const GtkSnapshotState *state = gtk_snapshot_get_current_state (snapshot);
GskRenderNode *node;
g_return_if_fail (snapshot != NULL);
g_return_if_fail (bounds != NULL);
/* need identity here because the bounds are used
* in the copy and the paste coordinate system. */
gtk_snapshot_ensure_identity (snapshot);
node = gsk_paste_node_new2 (bounds, state->props.snap, nth);
gtk_snapshot_append_node_internal (snapshot, node);
}
static GskRenderNode *
gtk_snapshot_collect_subsurface (GtkSnapshot *snapshot,
GtkSnapshotState *state,
GskRenderNode **nodes,
guint n_nodes)
{
GskRenderNode *node, *subsurface_node;
node = gtk_snapshot_collect_default (snapshot, state, nodes, n_nodes);
if (node == NULL)
return NULL;
subsurface_node = gsk_subsurface_node_new (node, state->data.subsurface.subsurface);
gsk_render_node_unref (node);
return subsurface_node;
}
static void
gtk_snapshot_clear_subsurface (GtkSnapshotState *state)
{
g_object_unref (state->data.subsurface.subsurface);
}
void
gtk_snapshot_push_subsurface (GtkSnapshot *snapshot,
GdkSubsurface *subsurface)
{
const GtkSnapshotState *current_state = gtk_snapshot_get_current_state (snapshot);
GtkSnapshotState *state;
state = gtk_snapshot_push_state (snapshot,
current_state->transform,
gtk_snapshot_collect_subsurface,
gtk_snapshot_clear_subsurface);
state->data.subsurface.subsurface = g_object_ref (subsurface);
}
/*< private >
* gtk_snapshot_add_turbulence :
* @ snapshot : a ` GtkSnapshot `
* @ bounds : the bounds for the new node
* @ color_state : the color state to add noise in
* @ base_freq : the base frequencies
* @ num_octaves : The number of octaves of noise
* @ seed : The random seed
* @ noise_type : The type of noise pattern
* @ stitch_tiles : Whether to enable tile stitching
*
* Creates a new render node drawing the @ color into the
* given @ bounds and appends it to the current render node
* of @ snapshot .
*/
void
gtk_snapshot_add_turbulence (GtkSnapshot *snapshot,
const graphene_rect_t *bounds,
GdkColorState *color_state,
const graphene_size_t *base_freq,
unsigned int num_octaves,
int seed,
GskNoiseType noise_type,
gboolean stitch_tiles)
{
const GtkSnapshotState *state = gtk_snapshot_get_current_state (snapshot);
GskRenderNode *node;
g_return_if_fail (snapshot != NULL);
g_return_if_fail (bounds != NULL);
g_return_if_fail (color_state != NULL);
gtk_snapshot_ensure_identity (snapshot);
node = gsk_turbulence_node_new (bounds,
state->props.snap,
color_state,
base_freq,
num_octaves,
seed,
noise_type,
stitch_tiles);
gtk_snapshot_append_node_internal (snapshot, node);
}
Messung V0.5 in Prozent C=98 H=97 G=97
¤ Dauer der Verarbeitung: 0.111 Sekunden
(vorverarbeitet am 2026-07-03)
¤
*© Formatika GbR, Deutschland