/* GSK - The GTK Scene Kit
*
* Copyright 2025 Red Hat , Inc .
*
* 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 "gskarithmeticnodeprivate.h"
#include "gskrendernodeprivate.h"
#include "gskrenderreplay.h"
#include "gskcontainernode.h"
#include "gskrectprivate.h"
#include "gdk/gdkcairoprivate.h"
/**
* GskArithmeticNode :
*
* A render node applying the ' arithmetic ' composite operator ,
* as defined in the
* [ CSS filter effects ] ( https : //www.w3.org/TR/filter-effects-1/)
* spec ,
*
* result = k1 * i1 * i1 + k2 * i1 + k3 * i3 + k4
*/
struct _GskArithmeticNode
{
GskRenderNode render_node;
union {
GskRenderNode *children[2 ];
struct {
GskRenderNode *first;
GskRenderNode *second;
};
};
GdkColorState *color_state;
float factors[4 ];
GskRectSnap snap;
};
static void
gsk_arithmetic_node_finalize (GskRenderNode *node)
{
GskArithmeticNode *self = (GskArithmeticNode *) node;
GskRenderNodeClass *parent_class = g_type_class_peek (g_type_parent (GSK_TYPE_ARITHMETIC_NODE));
gsk_render_node_unref (self->first);
gsk_render_node_unref (self->second);
gdk_color_state_unref (self->color_state);
parent_class->finalize (node);
}
static void
gsk_arithmetic_node_draw (GskRenderNode *node,
cairo_t *cr,
GskCairoData *data)
{
GskArithmeticNode *self = (GskArithmeticNode *) node;
cairo_pattern_t *first_pattern, *second_pattern;
cairo_surface_t *first_surface, *second_surface;
cairo_surface_t *first_image, *second_image;
guchar *first_data, *second_data;
int first_width, first_height, first_stride;
int second_stride;
guint32 pixel1, pixel2;
float r1, g1, b1, a1, r2, g2, b2, a2, r, g, b, a;
float k1, k2, k3, k4;
gboolean cs_equal;
GdkColor c, l;
graphene_rect_t bounds;
if (!gsk_cairo_rect_snap (cr, &node->bounds, self->snap, &bounds))
return ;
gdk_cairo_rect (cr, &bounds);
cairo_clip (cr);
if (gdk_cairo_is_all_clipped (cr))
return ;
k1 = self->factors[0 ];
k2 = self->factors[1 ];
k3 = self->factors[2 ];
k4 = self->factors[3 ];
cairo_push_group_with_content (cr, CAIRO_CONTENT_COLOR_ALPHA);
gsk_render_node_draw_full (self->first, cr, data);
first_pattern = cairo_pop_group (cr);
cairo_pattern_get_surface (first_pattern, &first_surface);
first_image = cairo_surface_map_to_image (first_surface, NULL);
first_data = cairo_image_surface_get_data (first_image);
first_width = cairo_image_surface_get_width (first_image);
first_height = cairo_image_surface_get_height (first_image);
first_stride = cairo_image_surface_get_stride (first_image);
cairo_push_group_with_content (cr, CAIRO_CONTENT_COLOR_ALPHA);
gsk_render_node_draw_full (self->second, cr, data);
second_pattern = cairo_pop_group (cr);
cairo_pattern_get_surface (second_pattern, &second_surface);
second_image = cairo_surface_map_to_image (second_surface, NULL);
second_data = cairo_image_surface_get_data (second_image);
second_stride = cairo_image_surface_get_stride (second_image);
g_assert (first_width == cairo_image_surface_get_width (second_image));
g_assert (first_height == cairo_image_surface_get_height (second_image));
cs_equal = gdk_color_state_equal (data->ccs, self->color_state);
for (guint y = 0 ; y < first_height; y++)
{
for (guint x = 0 ; x < first_width; x++)
{
pixel1 = *(guint32 *)(first_data + y * first_stride + 4 * x);
a1 = ((pixel1 >> 24 ) & 0 xff) / 255 .;
pixel2 = *(guint32 *)(second_data + y * second_stride + 4 * x);
a2 = ((pixel2 >> 24 ) & 0 xff) / 255 .;
a = k1 * a1 * a2 + k2 * a1 + k3 * a2 + k4;
a = CLAMP (a, 0 , 1 );
if (a > 0 )
{
r1 = ((pixel1 >> 16 ) & 0 xff) / 255 .;
g1 = ((pixel1 >> 8 ) & 0 xff) / 255 .;
b1 = ((pixel1 >> 0 ) & 0 xff) / 255 .;
if (!cs_equal && a1 > 0 )
{
r1 /= a1;
g1 /= a1;
b1 /= a1;
gdk_color_init (&c, data->ccs, (float []) { r1, g1, b1, a1 });
gdk_color_convert (&l, self->color_state, &c);
r1 = l.r * l.a;
g1 = l.g * l.a;
b1 = l.b * l.a;
}
r2 = ((pixel2 >> 16 ) & 0 xff) / 255 .;
g2 = ((pixel2 >> 8 ) & 0 xff) / 255 .;
b2 = ((pixel2 >> 0 ) & 0 xff) / 255 .;
if (!cs_equal && a2 > 0 )
{
r2 /= a2;
g2 /= a2;
b2 /= a2;
gdk_color_init (&c, data->ccs, (float []) { r2, g2, b2, a2 });
gdk_color_convert (&l, self->color_state, &c);
r2 = l.r * l.a;
g2 = l.g * l.a;
b2 = l.b * l.a;
}
r = k1 * r1 * r2 + k2 * r1 + k3 * r2 + k4;
g = k1 * g1 * g2 + k2 * g1 + k3 * g2 + k4;
b = k1 * b1 * b2 + k2 * b1 + k3 * b2 + k4;
r = CLAMP (r, 0 , a);
g = CLAMP (g, 0 , a);
b = CLAMP (b, 0 , a);
if (!cs_equal)
{
r /= a;
g /= a;
b /= a;
gdk_color_init (&l, self->color_state, (float []) { r, g, b, a });
gdk_color_convert (&c, data->ccs, &l);
r = c.r * c.a;
g = c.g * c.a;
b = c.b * c.a;
}
}
else
{
r = 0 ;
g = 0 ;
b = 0 ;
}
*(guint32 *)(first_data + y * first_stride + 4 * x) =
CLAMP ((int ) roundf (a * 255 ), 0 , 255 ) << 24 |
CLAMP ((int ) roundf (r * 255 ), 0 , 255 ) << 16 |
CLAMP ((int ) roundf (g * 255 ), 0 , 255 ) << 8 |
CLAMP ((int ) roundf (b * 255 ), 0 , 255 ) << 0 ;
}
}
cairo_surface_unmap_image (first_surface, first_image);
cairo_surface_unmap_image (second_surface, second_image);
cairo_set_source (cr, first_pattern);
cairo_paint (cr);
cairo_pattern_destroy (first_pattern);
cairo_pattern_destroy (second_pattern);
}
static void
gsk_arithmetic_node_diff (GskRenderNode *node1,
GskRenderNode *node2,
GskDiffData *data)
{
GskArithmeticNode *self1 = (GskArithmeticNode *) node1;
GskArithmeticNode *self2 = (GskArithmeticNode *) node2;
if (self1->factors[0 ] == self2->factors[0 ] &&
self1->factors[1 ] == self2->factors[1 ] &&
self1->factors[2 ] == self2->factors[2 ] &&
self1->factors[3 ] == self2->factors[3 ] &&
self1->snap == self2->snap &&
gdk_color_state_equal (self1->color_state, self2->color_state))
{
gsk_render_node_diff (self1->first, self2->first, data);
gsk_render_node_diff (self1->second, self2->second, data);
}
else
{
gsk_render_node_diff_impossible (node1, node2, data);
}
}
static GskRenderNode **
gsk_arithmetic_node_get_children (GskRenderNode *node,
gsize *n_children)
{
GskArithmeticNode *self = (GskArithmeticNode *) node;
*n_children = G_N_ELEMENTS (self->children);
return self->children;
}
static GskRenderNode *
gsk_arithmetic_node_replay (GskRenderNode *node,
GskRenderReplay *replay)
{
GskArithmeticNode *self = (GskArithmeticNode *) node;
GskRenderNode *result, *first, *second;
first = gsk_render_replay_filter_node (replay, self->first);
second = gsk_render_replay_filter_node (replay, self->second);
if (first == NULL)
{
if (second == NULL)
return NULL;
first = gsk_container_node_new (NULL, 0 );
}
else if (second == NULL)
{
second = gsk_container_node_new (NULL, 0 );
}
if (first == self->first && second == self->second)
result = gsk_render_node_ref (node);
else
result = gsk_arithmetic_node_new (&node->bounds,
self->snap,
first, second,
self->color_state,
self->factors);
gsk_render_node_unref (first);
gsk_render_node_unref (second);
return result;
}
static void
gsk_arithmetic_node_class_init (gpointer g_class,
gpointer class_data)
{
GskRenderNodeClass *node_class = g_class;
node_class->node_type = GSK_ARITHMETIC_NODE;
node_class->finalize = gsk_arithmetic_node_finalize;
node_class->draw = gsk_arithmetic_node_draw;
node_class->diff = gsk_arithmetic_node_diff;
node_class->get_children = gsk_arithmetic_node_get_children;
node_class->replay = gsk_arithmetic_node_replay;
}
GSK_DEFINE_RENDER_NODE_TYPE (GskArithmeticNode, gsk_arithmetic_node)
/*< private >
* gsk_arithmetic_node_new :
* @ bounds : The bounds for the node
* @ snap : how to snap the rectangle to the pixel grid
* @ first : The first node to be composited
* @ second : The second node to be composited
* @ color_state : The color state to composite in
* @ factors : the 4 factors , often named " k1 " to " k4 "
*
* Creates a ` GskRenderNode ` that will composite the
* @ first and @ second nodes arithmetically .
*
* Returns : ( transfer full ) ( type GskArithmeticNode ) : A new ` GskRenderNode `
*/
GskRenderNode *
gsk_arithmetic_node_new (const graphene_rect_t *bounds,
GskRectSnap snap,
GskRenderNode *first,
GskRenderNode *second,
GdkColorState *color_state,
const float factors[4 ])
{
GskArithmeticNode *self;
GskRenderNode *node;
graphene_rect_t child_bounds;
g_return_val_if_fail (GSK_IS_RENDER_NODE (first), NULL);
g_return_val_if_fail (GSK_IS_RENDER_NODE (second), NULL);
self = gsk_render_node_alloc (GSK_TYPE_ARITHMETIC_NODE);
node = (GskRenderNode *) self;
if (!GDK_IS_DEFAULT_COLOR_STATE (color_state))
{
g_warning ("Arithmetic compositing in %s is not supported" , gdk_color_state_get_name (color_state));
return NULL;
}
self->snap = snap;
self->first = gsk_render_node_ref (first);
self->second = gsk_render_node_ref (second);
self->color_state = gdk_color_state_ref (color_state);
self->factors[0 ] = factors[0 ];
self->factors[1 ] = factors[1 ];
self->factors[2 ] = factors[2 ];
self->factors[3 ] = factors[3 ];
graphene_rect_union (&first->bounds, &second->bounds, &child_bounds);
graphene_rect_intersection (bounds, &child_bounds, &node->bounds);
node->preferred_depth = gdk_memory_depth_merge (gsk_render_node_get_preferred_depth (first),
gsk_render_node_get_preferred_depth (second));
node->is_hdr = gsk_render_node_is_hdr (first) ||
gsk_render_node_is_hdr (second);
node->fully_opaque = !gsk_rect_snap_can_shrink (snap) &&
gsk_render_node_is_fully_opaque (first) &&
gsk_render_node_is_fully_opaque (second) &&
gsk_rect_contains_rect (&first->bounds, bounds) &&
gsk_rect_contains_rect (&second->bounds, bounds) &&
factors[0 ] + factors[1 ] + factors[2 ] + factors[3 ] >= 1 ;
node->contains_subsurface_node = gsk_render_node_contains_subsurface_node (first) ||
gsk_render_node_contains_subsurface_node (second);
node->contains_paste_node = gsk_render_node_contains_paste_node (first) ||
gsk_render_node_contains_paste_node (second);
return node;
}
/*< private >
* gsk_arithmetic_node_get_first_child :
* @ node : ( type GskArithmeticNode ) : a ` GskRenderNode `
*
* Retrieves the first ` GskRenderNode ` child of the @ node .
*
* Returns : ( transfer none ) : the first child node
*/
GskRenderNode *
gsk_arithmetic_node_get_first_child (const GskRenderNode *node)
{
const GskArithmeticNode *self = (const GskArithmeticNode *) node;
return self->first;
}
/*< private >
* gsk_arithmetic_node_get_second_child :
* @ node : ( type GskArithmeticNode ) : a ` GskRenderNode `
*
* Retrieves the second ` GskRenderNode ` child of the @ node .
*
* Returns : ( transfer none ) : the second child node
*/
GskRenderNode *
gsk_arithmetic_node_get_second_child (const GskRenderNode *node)
{
const GskArithmeticNode *self = (const GskArithmeticNode *) node;
return self->second;
}
/*< private >
* gsk_arithmetic_node_get_factors :
* @ node : ( type GskArithmeticNode ) : a ` GskRenderNode `
*
* Retrieves the factors used by @ node .
*
* Returns : ( transfer none ) ( array fixed - size = 4 ) : the factors
*/
const float *
gsk_arithmetic_node_get_factors (const GskRenderNode *node)
{
const GskArithmeticNode *self = (const GskArithmeticNode *) node;
return self->factors;
}
/*< private >
* gsk_arithmetic_node_get_color_state :
* @ node : ( type GskArithmeticNode ) : a ` GskRenderNode `
*
* Retrieves the color state of the @ node .
*
* Returns : ( transfer none ) : the color state
*/
GdkColorState *
gsk_arithmetic_node_get_color_state (const GskRenderNode *node)
{
const GskArithmeticNode *self = (const GskArithmeticNode *) node;
return self->color_state;
}
/*< private >
* gsk_arithmetic_node_get_snap :
* @ node : ( type GskArithmeticNode ) : a ` GskRenderNode `
*
* Retrieves the snap value for this node
*
* Returns : the snap value
**/
GskRectSnap
gsk_arithmetic_node_get_snap (const GskRenderNode *node)
{
const GskArithmeticNode *self = (const GskArithmeticNode *) node;
return self->snap;
}
Messung V0.5 in Prozent C=98 H=96 G=96
¤ Dauer der Verarbeitung: 0.15 Sekunden
(vorverarbeitet am 2026-07-02)
¤
*© Formatika GbR, Deutschland