forked from AuroraMiddleware/gtk
2bab983ecf
pango does not currently export api for drawing hex boxes, but by using pango_cairo_show_glyph_string, we can reuse its implementation.
4471 lines
131 KiB
C
4471 lines
131 KiB
C
/* GSK - The GTK Scene Kit
|
|
*
|
|
* Copyright 2016 Endless
|
|
*
|
|
* 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 "gskrendernodeprivate.h"
|
|
|
|
#include "gskcairoblurprivate.h"
|
|
#include "gskdebugprivate.h"
|
|
#include "gskrendererprivate.h"
|
|
#include "gskroundedrectprivate.h"
|
|
#include "gdk/gdktextureprivate.h"
|
|
|
|
static gboolean
|
|
check_variant_type (GVariant *variant,
|
|
const char *type_string,
|
|
GError **error)
|
|
{
|
|
if (!g_variant_is_of_type (variant, G_VARIANT_TYPE (type_string)))
|
|
{
|
|
g_set_error (error, GSK_SERIALIZATION_ERROR, GSK_SERIALIZATION_INVALID_DATA,
|
|
"Wrong variant type, got '%s' but needed '%s",
|
|
g_variant_get_type_string (variant), type_string);
|
|
return FALSE;
|
|
}
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
/*** GSK_COLOR_NODE ***/
|
|
|
|
typedef struct _GskColorNode GskColorNode;
|
|
|
|
struct _GskColorNode
|
|
{
|
|
GskRenderNode render_node;
|
|
|
|
GdkRGBA color;
|
|
};
|
|
|
|
static void
|
|
gsk_color_node_finalize (GskRenderNode *node)
|
|
{
|
|
}
|
|
|
|
static void
|
|
gsk_color_node_draw (GskRenderNode *node,
|
|
cairo_t *cr)
|
|
{
|
|
GskColorNode *self = (GskColorNode *) node;
|
|
|
|
gdk_cairo_set_source_rgba (cr, &self->color);
|
|
|
|
cairo_rectangle (cr,
|
|
node->bounds.origin.x, node->bounds.origin.y,
|
|
node->bounds.size.width, node->bounds.size.height);
|
|
cairo_fill (cr);
|
|
}
|
|
|
|
#define GSK_COLOR_NODE_VARIANT_TYPE "(dddddddd)"
|
|
|
|
static GVariant *
|
|
gsk_color_node_serialize (GskRenderNode *node)
|
|
{
|
|
GskColorNode *self = (GskColorNode *) node;
|
|
|
|
return g_variant_new (GSK_COLOR_NODE_VARIANT_TYPE,
|
|
self->color.red, self->color.green,
|
|
self->color.blue, self->color.alpha,
|
|
(double) node->bounds.origin.x, (double) node->bounds.origin.y,
|
|
(double) node->bounds.size.width, (double) node->bounds.size.height);
|
|
}
|
|
|
|
static GskRenderNode *
|
|
gsk_color_node_deserialize (GVariant *variant,
|
|
GError **error)
|
|
{
|
|
double x, y, w, h;
|
|
GdkRGBA color;
|
|
|
|
if (!check_variant_type (variant, GSK_COLOR_NODE_VARIANT_TYPE, error))
|
|
return NULL;
|
|
|
|
g_variant_get (variant, GSK_COLOR_NODE_VARIANT_TYPE,
|
|
&color.red, &color.green, &color.blue, &color.alpha,
|
|
&x, &y, &w, &h);
|
|
|
|
return gsk_color_node_new (&color, &GRAPHENE_RECT_INIT (x, y, w, h));
|
|
}
|
|
|
|
static const GskRenderNodeClass GSK_COLOR_NODE_CLASS = {
|
|
GSK_COLOR_NODE,
|
|
sizeof (GskColorNode),
|
|
"GskColorNode",
|
|
gsk_color_node_finalize,
|
|
gsk_color_node_draw,
|
|
gsk_color_node_serialize,
|
|
gsk_color_node_deserialize,
|
|
};
|
|
|
|
const GdkRGBA *
|
|
gsk_color_node_peek_color (GskRenderNode *node)
|
|
{
|
|
GskColorNode *self = (GskColorNode *) node;
|
|
|
|
return &self->color;
|
|
}
|
|
|
|
/**
|
|
* gsk_color_node_new:
|
|
* @rgba: a #GdkRGBA specifying a color
|
|
* @bounds: the rectangle to render the color into
|
|
*
|
|
* Creates a #GskRenderNode that will render the color specified by @rgba into
|
|
* the area given by @bounds.
|
|
*
|
|
* Returns: A new #GskRenderNode
|
|
*
|
|
* Since: 3.90
|
|
*/
|
|
GskRenderNode *
|
|
gsk_color_node_new (const GdkRGBA *rgba,
|
|
const graphene_rect_t *bounds)
|
|
{
|
|
GskColorNode *self;
|
|
|
|
g_return_val_if_fail (rgba != NULL, NULL);
|
|
g_return_val_if_fail (bounds != NULL, NULL);
|
|
|
|
self = (GskColorNode *) gsk_render_node_new (&GSK_COLOR_NODE_CLASS, 0);
|
|
|
|
self->color = *rgba;
|
|
graphene_rect_init_from_rect (&self->render_node.bounds, bounds);
|
|
|
|
return &self->render_node;
|
|
}
|
|
|
|
/*** GSK_LINEAR_GRADIENT_NODE ***/
|
|
|
|
typedef struct _GskLinearGradientNode GskLinearGradientNode;
|
|
|
|
struct _GskLinearGradientNode
|
|
{
|
|
GskRenderNode render_node;
|
|
|
|
graphene_point_t start;
|
|
graphene_point_t end;
|
|
|
|
gsize n_stops;
|
|
GskColorStop stops[];
|
|
};
|
|
|
|
static void
|
|
gsk_linear_gradient_node_finalize (GskRenderNode *node)
|
|
{
|
|
}
|
|
|
|
static void
|
|
gsk_linear_gradient_node_draw (GskRenderNode *node,
|
|
cairo_t *cr)
|
|
{
|
|
GskLinearGradientNode *self = (GskLinearGradientNode *) node;
|
|
cairo_pattern_t *pattern;
|
|
gsize i;
|
|
|
|
pattern = cairo_pattern_create_linear (self->start.x, self->start.y,
|
|
self->end.x, self->end.y);
|
|
|
|
if (gsk_render_node_get_node_type (node) == GSK_REPEATING_LINEAR_GRADIENT_NODE)
|
|
cairo_pattern_set_extend (pattern, CAIRO_EXTEND_REPEAT);
|
|
|
|
for (i = 0; i < self->n_stops; i++)
|
|
{
|
|
cairo_pattern_add_color_stop_rgba (pattern,
|
|
self->stops[i].offset,
|
|
self->stops[i].color.red,
|
|
self->stops[i].color.green,
|
|
self->stops[i].color.blue,
|
|
self->stops[i].color.alpha);
|
|
}
|
|
|
|
cairo_set_source (cr, pattern);
|
|
cairo_pattern_destroy (pattern);
|
|
|
|
cairo_rectangle (cr,
|
|
node->bounds.origin.x, node->bounds.origin.y,
|
|
node->bounds.size.width, node->bounds.size.height);
|
|
cairo_fill (cr);
|
|
}
|
|
|
|
#define GSK_LINEAR_GRADIENT_NODE_VARIANT_TYPE "(dddddddda(ddddd))"
|
|
|
|
static GVariant *
|
|
gsk_linear_gradient_node_serialize (GskRenderNode *node)
|
|
{
|
|
GskLinearGradientNode *self = (GskLinearGradientNode *) node;
|
|
GVariantBuilder builder;
|
|
guint i;
|
|
|
|
g_variant_builder_init (&builder, G_VARIANT_TYPE ("a(ddddd)"));
|
|
for (i = 0; i < self->n_stops; i++)
|
|
{
|
|
g_variant_builder_add (&builder, "(ddddd)",
|
|
(double) self->stops[i].offset,
|
|
self->stops[i].color.red, self->stops[i].color.green,
|
|
self->stops[i].color.blue, self->stops[i].color.alpha);
|
|
}
|
|
|
|
return g_variant_new (GSK_LINEAR_GRADIENT_NODE_VARIANT_TYPE,
|
|
(double) node->bounds.origin.x, (double) node->bounds.origin.y,
|
|
(double) node->bounds.size.width, (double) node->bounds.size.height,
|
|
(double) self->start.x, (double) self->start.y,
|
|
(double) self->end.x, (double) self->end.y,
|
|
&builder);
|
|
}
|
|
|
|
static GskRenderNode *
|
|
gsk_linear_gradient_node_real_deserialize (GVariant *variant,
|
|
gboolean repeating,
|
|
GError **error)
|
|
{
|
|
GVariantIter *iter;
|
|
double x, y, w, h, start_x, start_y, end_x, end_y;
|
|
gsize i, n_stops;
|
|
|
|
if (!check_variant_type (variant, GSK_LINEAR_GRADIENT_NODE_VARIANT_TYPE, error))
|
|
return NULL;
|
|
|
|
g_variant_get (variant, GSK_LINEAR_GRADIENT_NODE_VARIANT_TYPE,
|
|
&x, &y, &w, &h,
|
|
&start_x, &start_y, &end_x, &end_y,
|
|
&iter);
|
|
|
|
n_stops = g_variant_iter_n_children (iter);
|
|
GskColorStop *stops = g_newa (GskColorStop, n_stops);
|
|
for (i = 0; i < n_stops; i++)
|
|
{
|
|
double offset;
|
|
g_variant_iter_next (iter, "(ddddd)",
|
|
&offset,
|
|
&stops[i].color.red, &stops[i].color.green,
|
|
&stops[i].color.blue, &stops[i].color.alpha);
|
|
stops[i].offset = offset;
|
|
}
|
|
g_variant_iter_free (iter);
|
|
|
|
return (repeating ? gsk_repeating_linear_gradient_node_new : gsk_linear_gradient_node_new)
|
|
(&GRAPHENE_RECT_INIT (x, y, w, h),
|
|
&GRAPHENE_POINT_INIT (start_x, start_y),
|
|
&GRAPHENE_POINT_INIT (end_x, end_y),
|
|
stops,
|
|
n_stops);
|
|
}
|
|
|
|
static GskRenderNode *
|
|
gsk_linear_gradient_node_deserialize (GVariant *variant,
|
|
GError **error)
|
|
{
|
|
return gsk_linear_gradient_node_real_deserialize (variant, FALSE, error);
|
|
}
|
|
|
|
static GskRenderNode *
|
|
gsk_repeating_linear_gradient_node_deserialize (GVariant *variant,
|
|
GError **error)
|
|
{
|
|
return gsk_linear_gradient_node_real_deserialize (variant, TRUE, error);
|
|
}
|
|
|
|
static const GskRenderNodeClass GSK_LINEAR_GRADIENT_NODE_CLASS = {
|
|
GSK_LINEAR_GRADIENT_NODE,
|
|
sizeof (GskLinearGradientNode),
|
|
"GskLinearGradientNode",
|
|
gsk_linear_gradient_node_finalize,
|
|
gsk_linear_gradient_node_draw,
|
|
gsk_linear_gradient_node_serialize,
|
|
gsk_linear_gradient_node_deserialize,
|
|
};
|
|
|
|
static const GskRenderNodeClass GSK_REPEATING_LINEAR_GRADIENT_NODE_CLASS = {
|
|
GSK_REPEATING_LINEAR_GRADIENT_NODE,
|
|
sizeof (GskLinearGradientNode),
|
|
"GskRepeatingLinearGradientNode",
|
|
gsk_linear_gradient_node_finalize,
|
|
gsk_linear_gradient_node_draw,
|
|
gsk_linear_gradient_node_serialize,
|
|
gsk_repeating_linear_gradient_node_deserialize,
|
|
};
|
|
|
|
/**
|
|
* gsk_linear_gradient_node_new:
|
|
* @bounds: the rectangle to render the linear gradient into
|
|
* @start: the point at which the linear gradient will begin
|
|
* @end: the point at which the linear gradient will finish
|
|
* @color_stops: (array length=n_color_stops): a pointer to an array of #GskColorStop defining the gradient
|
|
* @n_color_stops: the number of elements in @color_stops
|
|
*
|
|
* Creates a #GskRenderNode that will create a linear gradient from the given
|
|
* points and color stops, and render that into the area given by @bounds.
|
|
*
|
|
* Returns: A new #GskRenderNode
|
|
*
|
|
* Since: 3.90
|
|
*/
|
|
GskRenderNode *
|
|
gsk_linear_gradient_node_new (const graphene_rect_t *bounds,
|
|
const graphene_point_t *start,
|
|
const graphene_point_t *end,
|
|
const GskColorStop *color_stops,
|
|
gsize n_color_stops)
|
|
{
|
|
GskLinearGradientNode *self;
|
|
gsize i;
|
|
|
|
g_return_val_if_fail (bounds != NULL, NULL);
|
|
g_return_val_if_fail (start != NULL, NULL);
|
|
g_return_val_if_fail (end != NULL, NULL);
|
|
g_return_val_if_fail (color_stops != NULL, NULL);
|
|
g_return_val_if_fail (n_color_stops >= 2, NULL);
|
|
g_return_val_if_fail (color_stops[0].offset >= 0, NULL);
|
|
for (i = 1; i < n_color_stops; i++)
|
|
g_return_val_if_fail (color_stops[i].offset >= color_stops[i-1].offset, NULL);
|
|
g_return_val_if_fail (color_stops[n_color_stops - 1].offset <= 1, NULL);
|
|
|
|
self = (GskLinearGradientNode *) gsk_render_node_new (&GSK_LINEAR_GRADIENT_NODE_CLASS, sizeof (GskColorStop) * n_color_stops);
|
|
|
|
graphene_rect_init_from_rect (&self->render_node.bounds, bounds);
|
|
graphene_point_init_from_point (&self->start, start);
|
|
graphene_point_init_from_point (&self->end, end);
|
|
|
|
memcpy (&self->stops, color_stops, sizeof (GskColorStop) * n_color_stops);
|
|
self->n_stops = n_color_stops;
|
|
|
|
return &self->render_node;
|
|
}
|
|
|
|
/**
|
|
* gsk_repeating_linear_gradient_node_new:
|
|
* @bounds: the rectangle to render the linear gradient into
|
|
* @start: the point at which the linear gradient will begin
|
|
* @end: the point at which the linear gradient will finish
|
|
* @color_stops: (array length=n_color_stops): a pointer to an array of #GskColorStop defining the gradient
|
|
* @n_color_stops: the number of elements in @color_stops
|
|
*
|
|
* Creates a #GskRenderNode that will create a repeating linear gradient
|
|
* from the given points and color stops, and render that into the area
|
|
* given by @bounds.
|
|
*
|
|
* Returns: A new #GskRenderNode
|
|
*
|
|
* Since: 3.90
|
|
*/
|
|
GskRenderNode *
|
|
gsk_repeating_linear_gradient_node_new (const graphene_rect_t *bounds,
|
|
const graphene_point_t *start,
|
|
const graphene_point_t *end,
|
|
const GskColorStop *color_stops,
|
|
gsize n_color_stops)
|
|
{
|
|
GskLinearGradientNode *self;
|
|
gsize i;
|
|
|
|
g_return_val_if_fail (bounds != NULL, NULL);
|
|
g_return_val_if_fail (start != NULL, NULL);
|
|
g_return_val_if_fail (end != NULL, NULL);
|
|
g_return_val_if_fail (color_stops != NULL, NULL);
|
|
g_return_val_if_fail (n_color_stops >= 2, NULL);
|
|
g_return_val_if_fail (color_stops[0].offset >= 0, NULL);
|
|
for (i = 1; i < n_color_stops; i++)
|
|
g_return_val_if_fail (color_stops[i].offset >= color_stops[i-1].offset, NULL);
|
|
g_return_val_if_fail (color_stops[n_color_stops - 1].offset <= 1, NULL);
|
|
|
|
self = (GskLinearGradientNode *) gsk_render_node_new (&GSK_REPEATING_LINEAR_GRADIENT_NODE_CLASS, sizeof (GskColorStop) * n_color_stops);
|
|
|
|
graphene_rect_init_from_rect (&self->render_node.bounds, bounds);
|
|
graphene_point_init_from_point (&self->start, start);
|
|
graphene_point_init_from_point (&self->end, end);
|
|
|
|
memcpy (&self->stops, color_stops, sizeof (GskColorStop) * n_color_stops);
|
|
self->n_stops = n_color_stops;
|
|
|
|
return &self->render_node;
|
|
}
|
|
|
|
const graphene_point_t *
|
|
gsk_linear_gradient_node_peek_start (GskRenderNode *node)
|
|
{
|
|
GskLinearGradientNode *self = (GskLinearGradientNode *) node;
|
|
|
|
return &self->start;
|
|
}
|
|
|
|
const graphene_point_t *
|
|
gsk_linear_gradient_node_peek_end (GskRenderNode *node)
|
|
{
|
|
GskLinearGradientNode *self = (GskLinearGradientNode *) node;
|
|
|
|
return &self->end;
|
|
}
|
|
|
|
gsize
|
|
gsk_linear_gradient_node_get_n_color_stops (GskRenderNode *node)
|
|
{
|
|
GskLinearGradientNode *self = (GskLinearGradientNode *) node;
|
|
|
|
return self->n_stops;
|
|
}
|
|
|
|
const GskColorStop *
|
|
gsk_linear_gradient_node_peek_color_stops (GskRenderNode *node)
|
|
{
|
|
GskLinearGradientNode *self = (GskLinearGradientNode *) node;
|
|
|
|
return self->stops;
|
|
}
|
|
|
|
/*** GSK_BORDER_NODE ***/
|
|
|
|
typedef struct _GskBorderNode GskBorderNode;
|
|
|
|
struct _GskBorderNode
|
|
{
|
|
GskRenderNode render_node;
|
|
|
|
GskRoundedRect outline;
|
|
float border_width[4];
|
|
GdkRGBA border_color[4];
|
|
};
|
|
|
|
static void
|
|
gsk_border_node_finalize (GskRenderNode *node)
|
|
{
|
|
}
|
|
|
|
static void
|
|
gsk_border_node_draw (GskRenderNode *node,
|
|
cairo_t *cr)
|
|
{
|
|
GskBorderNode *self = (GskBorderNode *) node;
|
|
GskRoundedRect inside;
|
|
|
|
cairo_save (cr);
|
|
|
|
gsk_rounded_rect_init_copy (&inside, &self->outline);
|
|
gsk_rounded_rect_shrink (&inside,
|
|
self->border_width[0], self->border_width[1],
|
|
self->border_width[2], self->border_width[3]);
|
|
|
|
cairo_set_fill_rule (cr, CAIRO_FILL_RULE_EVEN_ODD);
|
|
gsk_rounded_rect_path (&self->outline, cr);
|
|
gsk_rounded_rect_path (&inside, cr);
|
|
|
|
if (gdk_rgba_equal (&self->border_color[0], &self->border_color[1]) &&
|
|
gdk_rgba_equal (&self->border_color[0], &self->border_color[2]) &&
|
|
gdk_rgba_equal (&self->border_color[0], &self->border_color[3]))
|
|
{
|
|
gdk_cairo_set_source_rgba (cr, &self->border_color[0]);
|
|
cairo_fill (cr);
|
|
}
|
|
else
|
|
{
|
|
const graphene_rect_t *bounds = &self->outline.bounds;
|
|
|
|
cairo_clip (cr);
|
|
|
|
/* Top */
|
|
if (self->border_width[0] > 0)
|
|
{
|
|
cairo_move_to (cr, bounds->origin.x, bounds->origin.y);
|
|
cairo_rel_line_to (cr, self->border_width[3], self->border_width[0]);
|
|
cairo_rel_line_to (cr, bounds->size.width - self->border_width[3] - self->border_width[1], 0);
|
|
cairo_rel_line_to (cr, self->border_width[1], - self->border_width[0]);
|
|
gdk_cairo_set_source_rgba (cr, &self->border_color[0]);
|
|
cairo_fill (cr);
|
|
}
|
|
|
|
/* Right */
|
|
if (self->border_width[1] > 0)
|
|
{
|
|
cairo_move_to (cr, bounds->origin.x + bounds->size.width, bounds->origin.y);
|
|
cairo_rel_line_to (cr, - self->border_width[1], self->border_width[0]);
|
|
cairo_rel_line_to (cr, 0, bounds->size.height - self->border_width[0] - self->border_width[2]);
|
|
cairo_rel_line_to (cr, self->border_width[1], self->border_width[2]);
|
|
gdk_cairo_set_source_rgba (cr, &self->border_color[1]);
|
|
cairo_fill (cr);
|
|
}
|
|
|
|
/* Bottom */
|
|
if (self->border_width[2] > 0)
|
|
{
|
|
cairo_move_to (cr, bounds->origin.x, bounds->origin.y + bounds->size.height);
|
|
cairo_rel_line_to (cr, self->border_width[3], - self->border_width[2]);
|
|
cairo_rel_line_to (cr, bounds->size.width - self->border_width[3] - self->border_width[1], 0);
|
|
cairo_rel_line_to (cr, self->border_width[1], self->border_width[2]);
|
|
gdk_cairo_set_source_rgba (cr, &self->border_color[2]);
|
|
cairo_fill (cr);
|
|
}
|
|
|
|
/* Left */
|
|
if (self->border_width[3] > 0)
|
|
{
|
|
cairo_move_to (cr, bounds->origin.x, bounds->origin.y);
|
|
cairo_rel_line_to (cr, self->border_width[3], self->border_width[0]);
|
|
cairo_rel_line_to (cr, 0, bounds->size.height - self->border_width[0] - self->border_width[2]);
|
|
cairo_rel_line_to (cr, - self->border_width[3], self->border_width[2]);
|
|
gdk_cairo_set_source_rgba (cr, &self->border_color[3]);
|
|
cairo_fill (cr);
|
|
}
|
|
}
|
|
|
|
cairo_restore (cr);
|
|
}
|
|
|
|
#define GSK_BORDER_NODE_VARIANT_TYPE "(dddddddddddddddddddddddddddddddd)"
|
|
|
|
static GVariant *
|
|
gsk_border_node_serialize (GskRenderNode *node)
|
|
{
|
|
GskBorderNode *self = (GskBorderNode *) node;
|
|
|
|
return g_variant_new (GSK_BORDER_NODE_VARIANT_TYPE,
|
|
(double) self->outline.bounds.origin.x, (double) self->outline.bounds.origin.y,
|
|
(double) self->outline.bounds.size.width, (double) self->outline.bounds.size.height,
|
|
(double) self->outline.corner[0].width, (double) self->outline.corner[0].height,
|
|
(double) self->outline.corner[1].width, (double) self->outline.corner[1].height,
|
|
(double) self->outline.corner[2].width, (double) self->outline.corner[2].height,
|
|
(double) self->outline.corner[3].width, (double) self->outline.corner[3].height,
|
|
(double) self->border_width[0], (double) self->border_width[1],
|
|
(double) self->border_width[2], (double) self->border_width[3],
|
|
self->border_color[0].red, self->border_color[0].green,
|
|
self->border_color[0].blue, self->border_color[0].alpha,
|
|
self->border_color[1].red, self->border_color[1].green,
|
|
self->border_color[1].blue, self->border_color[1].alpha,
|
|
self->border_color[2].red, self->border_color[2].green,
|
|
self->border_color[2].blue, self->border_color[2].alpha,
|
|
self->border_color[3].red, self->border_color[3].green,
|
|
self->border_color[3].blue, self->border_color[3].alpha);
|
|
}
|
|
|
|
static GskRenderNode *
|
|
gsk_border_node_deserialize (GVariant *variant,
|
|
GError **error)
|
|
{
|
|
double doutline[12], dwidths[4];
|
|
GdkRGBA colors[4];
|
|
|
|
if (!check_variant_type (variant, GSK_BORDER_NODE_VARIANT_TYPE, error))
|
|
return NULL;
|
|
|
|
g_variant_get (variant, GSK_BORDER_NODE_VARIANT_TYPE,
|
|
&doutline[0], &doutline[1], &doutline[2], &doutline[3],
|
|
&doutline[4], &doutline[5], &doutline[6], &doutline[7],
|
|
&doutline[8], &doutline[9], &doutline[10], &doutline[11],
|
|
&dwidths[0], &dwidths[1], &dwidths[2], &dwidths[3],
|
|
&colors[0].red, &colors[0].green, &colors[0].blue, &colors[0].alpha,
|
|
&colors[1].red, &colors[1].green, &colors[1].blue, &colors[1].alpha,
|
|
&colors[2].red, &colors[2].green, &colors[2].blue, &colors[2].alpha,
|
|
&colors[3].red, &colors[3].green, &colors[3].blue, &colors[3].alpha);
|
|
|
|
return gsk_border_node_new (&(GskRoundedRect) {
|
|
.bounds = GRAPHENE_RECT_INIT(doutline[0], doutline[1], doutline[2], doutline[3]),
|
|
.corner = {
|
|
GRAPHENE_SIZE_INIT (doutline[4], doutline[5]),
|
|
GRAPHENE_SIZE_INIT (doutline[6], doutline[7]),
|
|
GRAPHENE_SIZE_INIT (doutline[8], doutline[9]),
|
|
GRAPHENE_SIZE_INIT (doutline[10], doutline[11])
|
|
}
|
|
},
|
|
(float[4]) { dwidths[0], dwidths[1], dwidths[2], dwidths[3] },
|
|
colors);
|
|
}
|
|
|
|
static const GskRenderNodeClass GSK_BORDER_NODE_CLASS = {
|
|
GSK_BORDER_NODE,
|
|
sizeof (GskBorderNode),
|
|
"GskBorderNode",
|
|
gsk_border_node_finalize,
|
|
gsk_border_node_draw,
|
|
gsk_border_node_serialize,
|
|
gsk_border_node_deserialize
|
|
};
|
|
|
|
const GskRoundedRect *
|
|
gsk_border_node_peek_outline (GskRenderNode *node)
|
|
{
|
|
GskBorderNode *self = (GskBorderNode *) node;
|
|
|
|
return &self->outline;
|
|
}
|
|
|
|
const float *
|
|
gsk_border_node_peek_widths (GskRenderNode *node)
|
|
{
|
|
GskBorderNode *self = (GskBorderNode *) node;
|
|
|
|
return self->border_width;
|
|
}
|
|
|
|
const GdkRGBA *
|
|
gsk_border_node_peek_colors (GskRenderNode *node)
|
|
{
|
|
GskBorderNode *self = (GskBorderNode *) node;
|
|
|
|
return self->border_color;
|
|
}
|
|
|
|
/**
|
|
* gsk_border_node_new:
|
|
* @outline: a #GskRoundedRect describing the outline of the border
|
|
* @border_width: the stroke width of the border on the top, right, bottom and
|
|
* left side respectively.
|
|
* @border_color: the color used on the top, right, bottom and left side.
|
|
*
|
|
* Creates a #GskRenderNode that will stroke a border rectangle inside the
|
|
* given @outline. The 4 sides of the border can have different widths and
|
|
* colors.
|
|
*
|
|
* Returns: A new #GskRenderNode
|
|
*
|
|
* Since: 3.90
|
|
*/
|
|
GskRenderNode *
|
|
gsk_border_node_new (const GskRoundedRect *outline,
|
|
const float border_width[4],
|
|
const GdkRGBA border_color[4])
|
|
{
|
|
GskBorderNode *self;
|
|
|
|
g_return_val_if_fail (outline != NULL, NULL);
|
|
g_return_val_if_fail (border_width != NULL, NULL);
|
|
g_return_val_if_fail (border_color != NULL, NULL);
|
|
|
|
self = (GskBorderNode *) gsk_render_node_new (&GSK_BORDER_NODE_CLASS, 0);
|
|
|
|
gsk_rounded_rect_init_copy (&self->outline, outline);
|
|
memcpy (self->border_width, border_width, sizeof (self->border_width));
|
|
memcpy (self->border_color, border_color, sizeof (self->border_color));
|
|
|
|
graphene_rect_init_from_rect (&self->render_node.bounds, &self->outline.bounds);
|
|
|
|
return &self->render_node;
|
|
}
|
|
|
|
/*** GSK_TEXTURE_NODE ***/
|
|
|
|
typedef struct _GskTextureNode GskTextureNode;
|
|
|
|
struct _GskTextureNode
|
|
{
|
|
GskRenderNode render_node;
|
|
|
|
GdkTexture *texture;
|
|
};
|
|
|
|
static void
|
|
gsk_texture_node_finalize (GskRenderNode *node)
|
|
{
|
|
GskTextureNode *self = (GskTextureNode *) node;
|
|
|
|
g_object_unref (self->texture);
|
|
}
|
|
|
|
static void
|
|
gsk_texture_node_draw (GskRenderNode *node,
|
|
cairo_t *cr)
|
|
{
|
|
GskTextureNode *self = (GskTextureNode *) node;
|
|
cairo_surface_t *surface;
|
|
|
|
surface = gdk_texture_download_surface (self->texture);
|
|
|
|
cairo_save (cr);
|
|
|
|
cairo_translate (cr, node->bounds.origin.x, node->bounds.origin.y);
|
|
cairo_scale (cr,
|
|
node->bounds.size.width / gdk_texture_get_width (self->texture),
|
|
node->bounds.size.height / gdk_texture_get_height (self->texture));
|
|
|
|
cairo_set_source_surface (cr, surface, 0, 0);
|
|
cairo_paint (cr);
|
|
|
|
cairo_restore (cr);
|
|
|
|
cairo_surface_destroy (surface);
|
|
}
|
|
|
|
#define GSK_TEXTURE_NODE_VARIANT_TYPE "(dddduuau)"
|
|
|
|
static GVariant *
|
|
gsk_texture_node_serialize (GskRenderNode *node)
|
|
{
|
|
GskTextureNode *self = (GskTextureNode *) node;
|
|
cairo_surface_t *surface;
|
|
GVariant *result;
|
|
|
|
surface = gdk_texture_download_surface (self->texture);
|
|
|
|
g_assert (cairo_image_surface_get_width (surface) * 4 == cairo_image_surface_get_stride (surface));
|
|
|
|
result = g_variant_new ("(dddduu@au)",
|
|
(double) node->bounds.origin.x, (double) node->bounds.origin.y,
|
|
(double) node->bounds.size.width, (double) node->bounds.size.height,
|
|
(guint32) gdk_texture_get_width (self->texture),
|
|
(guint32) gdk_texture_get_height (self->texture),
|
|
g_variant_new_fixed_array (G_VARIANT_TYPE ("u"),
|
|
cairo_image_surface_get_data (surface),
|
|
gdk_texture_get_width (self->texture)
|
|
* gdk_texture_get_height (self->texture),
|
|
sizeof (guint32)));
|
|
|
|
cairo_surface_destroy (surface);
|
|
|
|
return result;
|
|
}
|
|
|
|
static GskRenderNode *
|
|
gsk_texture_node_deserialize (GVariant *variant,
|
|
GError **error)
|
|
{
|
|
GskRenderNode *node;
|
|
GdkTexture *texture;
|
|
double bounds[4];
|
|
guint32 width, height;
|
|
GVariant *pixel_variant;
|
|
gsize n_pixels;
|
|
|
|
if (!check_variant_type (variant, GSK_TEXTURE_NODE_VARIANT_TYPE, error))
|
|
return NULL;
|
|
|
|
g_variant_get (variant, "(dddduu@au)",
|
|
&bounds[0], &bounds[1], &bounds[2], &bounds[3],
|
|
&width, &height, &pixel_variant);
|
|
|
|
/* XXX: Make this work without copying the data */
|
|
texture = gdk_texture_new_for_data (g_variant_get_fixed_array (pixel_variant, &n_pixels, sizeof (guint32)),
|
|
width, height, width * 4);
|
|
g_variant_unref (pixel_variant);
|
|
|
|
node = gsk_texture_node_new (texture, &GRAPHENE_RECT_INIT(bounds[0], bounds[1], bounds[2], bounds[3]));
|
|
|
|
g_object_unref (texture);
|
|
|
|
return node;
|
|
}
|
|
|
|
static const GskRenderNodeClass GSK_TEXTURE_NODE_CLASS = {
|
|
GSK_TEXTURE_NODE,
|
|
sizeof (GskTextureNode),
|
|
"GskTextureNode",
|
|
gsk_texture_node_finalize,
|
|
gsk_texture_node_draw,
|
|
gsk_texture_node_serialize,
|
|
gsk_texture_node_deserialize
|
|
};
|
|
|
|
/**
|
|
* gsk_texture_node_get_texture:
|
|
* @node: a #GskRenderNode
|
|
*
|
|
* Returns: (transfer none): the #GdkTexture
|
|
*
|
|
* Since: 3.94
|
|
*/
|
|
GdkTexture *
|
|
gsk_texture_node_get_texture (GskRenderNode *node)
|
|
{
|
|
GskTextureNode *self = (GskTextureNode *) node;
|
|
|
|
g_return_val_if_fail (GSK_IS_RENDER_NODE_TYPE (node, GSK_TEXTURE_NODE), 0);
|
|
|
|
return self->texture;
|
|
}
|
|
|
|
/**
|
|
* gsk_texture_node_new:
|
|
* @texture: the #GdkTexture
|
|
* @bounds: the rectangle to render the texture into
|
|
*
|
|
* Creates a #GskRenderNode that will render the given
|
|
* @texture into the area given by @bounds.
|
|
*
|
|
* Returns: A new #GskRenderNode
|
|
*
|
|
* Since: 3.90
|
|
*/
|
|
GskRenderNode *
|
|
gsk_texture_node_new (GdkTexture *texture,
|
|
const graphene_rect_t *bounds)
|
|
{
|
|
GskTextureNode *self;
|
|
|
|
g_return_val_if_fail (GDK_IS_TEXTURE (texture), NULL);
|
|
g_return_val_if_fail (bounds != NULL, NULL);
|
|
|
|
self = (GskTextureNode *) gsk_render_node_new (&GSK_TEXTURE_NODE_CLASS, 0);
|
|
|
|
self->texture = g_object_ref (texture);
|
|
graphene_rect_init_from_rect (&self->render_node.bounds, bounds);
|
|
|
|
return &self->render_node;
|
|
}
|
|
|
|
/*** GSK_INSET_SHADOW_NODE ***/
|
|
|
|
typedef struct _GskInsetShadowNode GskInsetShadowNode;
|
|
|
|
struct _GskInsetShadowNode
|
|
{
|
|
GskRenderNode render_node;
|
|
|
|
GskRoundedRect outline;
|
|
GdkRGBA color;
|
|
float dx;
|
|
float dy;
|
|
float spread;
|
|
float blur_radius;
|
|
};
|
|
|
|
static void
|
|
gsk_inset_shadow_node_finalize (GskRenderNode *node)
|
|
{
|
|
}
|
|
|
|
static gboolean
|
|
has_empty_clip (cairo_t *cr)
|
|
{
|
|
double x1, y1, x2, y2;
|
|
|
|
cairo_clip_extents (cr, &x1, &y1, &x2, &y2);
|
|
return x1 == x2 && y1 == y2;
|
|
}
|
|
|
|
static void
|
|
draw_shadow (cairo_t *cr,
|
|
gboolean inset,
|
|
const GskRoundedRect*box,
|
|
const GskRoundedRect*clip_box,
|
|
float radius,
|
|
const GdkRGBA *color,
|
|
GskBlurFlags blur_flags)
|
|
{
|
|
cairo_t *shadow_cr;
|
|
gboolean do_blur;
|
|
|
|
if (has_empty_clip (cr))
|
|
return;
|
|
|
|
gdk_cairo_set_source_rgba (cr, color);
|
|
do_blur = (blur_flags & (GSK_BLUR_X | GSK_BLUR_Y)) != 0;
|
|
if (do_blur)
|
|
shadow_cr = gsk_cairo_blur_start_drawing (cr, radius, blur_flags);
|
|
else
|
|
shadow_cr = cr;
|
|
|
|
cairo_set_fill_rule (shadow_cr, CAIRO_FILL_RULE_EVEN_ODD);
|
|
gsk_rounded_rect_path (box, shadow_cr);
|
|
if (inset)
|
|
cairo_rectangle (shadow_cr,
|
|
clip_box->bounds.origin.x, clip_box->bounds.origin.y,
|
|
clip_box->bounds.size.width, clip_box->bounds.size.height);
|
|
|
|
cairo_fill (shadow_cr);
|
|
|
|
if (do_blur)
|
|
gsk_cairo_blur_finish_drawing (shadow_cr, radius, color, blur_flags);
|
|
}
|
|
|
|
typedef struct {
|
|
float radius;
|
|
graphene_size_t corner;
|
|
} CornerMask;
|
|
|
|
typedef enum {
|
|
TOP,
|
|
RIGHT,
|
|
BOTTOM,
|
|
LEFT
|
|
} Side;
|
|
|
|
static guint
|
|
corner_mask_hash (CornerMask *mask)
|
|
{
|
|
return ((guint)mask->radius << 24) ^
|
|
((guint)(mask->corner.width*4)) << 12 ^
|
|
((guint)(mask->corner.height*4)) << 0;
|
|
}
|
|
|
|
static gboolean
|
|
corner_mask_equal (CornerMask *mask1,
|
|
CornerMask *mask2)
|
|
{
|
|
return
|
|
mask1->radius == mask2->radius &&
|
|
mask1->corner.width == mask2->corner.width &&
|
|
mask1->corner.height == mask2->corner.height;
|
|
}
|
|
|
|
static void
|
|
draw_shadow_corner (cairo_t *cr,
|
|
gboolean inset,
|
|
const GskRoundedRect *box,
|
|
const GskRoundedRect *clip_box,
|
|
float radius,
|
|
const GdkRGBA *color,
|
|
GskCorner corner,
|
|
cairo_rectangle_int_t *drawn_rect)
|
|
{
|
|
float clip_radius;
|
|
int x1, x2, x3, y1, y2, y3, x, y;
|
|
GskRoundedRect corner_box;
|
|
cairo_t *mask_cr;
|
|
cairo_surface_t *mask;
|
|
cairo_pattern_t *pattern;
|
|
cairo_matrix_t matrix;
|
|
float sx, sy;
|
|
static GHashTable *corner_mask_cache = NULL;
|
|
float max_other;
|
|
CornerMask key;
|
|
gboolean overlapped;
|
|
|
|
clip_radius = gsk_cairo_blur_compute_pixels (radius);
|
|
|
|
overlapped = FALSE;
|
|
if (corner == GSK_CORNER_TOP_LEFT || corner == GSK_CORNER_BOTTOM_LEFT)
|
|
{
|
|
x1 = floor (box->bounds.origin.x - clip_radius);
|
|
x2 = ceil (box->bounds.origin.x + box->corner[corner].width + clip_radius);
|
|
x = x1;
|
|
sx = 1;
|
|
max_other = MAX(box->corner[GSK_CORNER_TOP_RIGHT].width, box->corner[GSK_CORNER_BOTTOM_RIGHT].width);
|
|
x3 = floor (box->bounds.origin.x + box->bounds.size.width - max_other - clip_radius);
|
|
if (x2 > x3)
|
|
overlapped = TRUE;
|
|
}
|
|
else
|
|
{
|
|
x1 = floor (box->bounds.origin.x + box->bounds.size.width - box->corner[corner].width - clip_radius);
|
|
x2 = ceil (box->bounds.origin.x + box->bounds.size.width + clip_radius);
|
|
x = x2;
|
|
sx = -1;
|
|
max_other = MAX(box->corner[GSK_CORNER_TOP_LEFT].width, box->corner[GSK_CORNER_BOTTOM_LEFT].width);
|
|
x3 = ceil (box->bounds.origin.x + max_other + clip_radius);
|
|
if (x3 > x1)
|
|
overlapped = TRUE;
|
|
}
|
|
|
|
if (corner == GSK_CORNER_TOP_LEFT || corner == GSK_CORNER_TOP_RIGHT)
|
|
{
|
|
y1 = floor (box->bounds.origin.y - clip_radius);
|
|
y2 = ceil (box->bounds.origin.y + box->corner[corner].height + clip_radius);
|
|
y = y1;
|
|
sy = 1;
|
|
max_other = MAX(box->corner[GSK_CORNER_BOTTOM_LEFT].height, box->corner[GSK_CORNER_BOTTOM_RIGHT].height);
|
|
y3 = floor (box->bounds.origin.y + box->bounds.size.height - max_other - clip_radius);
|
|
if (y2 > y3)
|
|
overlapped = TRUE;
|
|
}
|
|
else
|
|
{
|
|
y1 = floor (box->bounds.origin.y + box->bounds.size.height - box->corner[corner].height - clip_radius);
|
|
y2 = ceil (box->bounds.origin.y + box->bounds.size.height + clip_radius);
|
|
y = y2;
|
|
sy = -1;
|
|
max_other = MAX(box->corner[GSK_CORNER_TOP_LEFT].height, box->corner[GSK_CORNER_TOP_RIGHT].height);
|
|
y3 = ceil (box->bounds.origin.y + max_other + clip_radius);
|
|
if (y3 > y1)
|
|
overlapped = TRUE;
|
|
}
|
|
|
|
drawn_rect->x = x1;
|
|
drawn_rect->y = y1;
|
|
drawn_rect->width = x2 - x1;
|
|
drawn_rect->height = y2 - y1;
|
|
|
|
cairo_rectangle (cr, x1, y1, x2 - x1, y2 - y1);
|
|
cairo_clip (cr);
|
|
|
|
if (inset || overlapped)
|
|
{
|
|
/* Fall back to generic path if inset or if the corner radius
|
|
runs into each other */
|
|
draw_shadow (cr, inset, box, clip_box, radius, color, GSK_BLUR_X | GSK_BLUR_Y);
|
|
return;
|
|
}
|
|
|
|
if (has_empty_clip (cr))
|
|
return;
|
|
|
|
/* At this point we're drawing a blurred outset corner. The only
|
|
* things that affect the output of the blurred mask in this case
|
|
* is:
|
|
*
|
|
* What corner this is, which defines the orientation (sx,sy)
|
|
* and position (x,y)
|
|
*
|
|
* The blur radius (which also defines the clip_radius)
|
|
*
|
|
* The the horizontal and vertical corner radius
|
|
*
|
|
* We apply the first position and orientation when drawing the
|
|
* mask, so we cache rendered masks based on the blur radius and the
|
|
* corner radius.
|
|
*/
|
|
if (corner_mask_cache == NULL)
|
|
corner_mask_cache = g_hash_table_new_full ((GHashFunc)corner_mask_hash,
|
|
(GEqualFunc)corner_mask_equal,
|
|
g_free, (GDestroyNotify)cairo_surface_destroy);
|
|
|
|
key.radius = radius;
|
|
key.corner = box->corner[corner];
|
|
|
|
mask = g_hash_table_lookup (corner_mask_cache, &key);
|
|
if (mask == NULL)
|
|
{
|
|
mask = cairo_surface_create_similar_image (cairo_get_target (cr), CAIRO_FORMAT_A8,
|
|
drawn_rect->width + clip_radius,
|
|
drawn_rect->height + clip_radius);
|
|
mask_cr = cairo_create (mask);
|
|
gsk_rounded_rect_init_from_rect (&corner_box, &GRAPHENE_RECT_INIT (clip_radius, clip_radius, 2*drawn_rect->width, 2*drawn_rect->height), 0);
|
|
corner_box.corner[0] = box->corner[corner];
|
|
gsk_rounded_rect_path (&corner_box, mask_cr);
|
|
cairo_fill (mask_cr);
|
|
gsk_cairo_blur_surface (mask, radius, GSK_BLUR_X | GSK_BLUR_Y);
|
|
cairo_destroy (mask_cr);
|
|
g_hash_table_insert (corner_mask_cache, g_memdup (&key, sizeof (key)), mask);
|
|
}
|
|
|
|
gdk_cairo_set_source_rgba (cr, color);
|
|
pattern = cairo_pattern_create_for_surface (mask);
|
|
cairo_matrix_init_identity (&matrix);
|
|
cairo_matrix_scale (&matrix, sx, sy);
|
|
cairo_matrix_translate (&matrix, -x, -y);
|
|
cairo_pattern_set_matrix (pattern, &matrix);
|
|
cairo_mask (cr, pattern);
|
|
cairo_pattern_destroy (pattern);
|
|
}
|
|
|
|
static void
|
|
draw_shadow_side (cairo_t *cr,
|
|
gboolean inset,
|
|
const GskRoundedRect *box,
|
|
const GskRoundedRect *clip_box,
|
|
float radius,
|
|
const GdkRGBA *color,
|
|
Side side,
|
|
cairo_rectangle_int_t *drawn_rect)
|
|
{
|
|
GskBlurFlags blur_flags = GSK_BLUR_REPEAT;
|
|
gdouble clip_radius;
|
|
int x1, x2, y1, y2;
|
|
|
|
clip_radius = gsk_cairo_blur_compute_pixels (radius);
|
|
|
|
if (side == TOP || side == BOTTOM)
|
|
{
|
|
blur_flags |= GSK_BLUR_Y;
|
|
x1 = floor (box->bounds.origin.x - clip_radius);
|
|
x2 = ceil (box->bounds.origin.x + box->bounds.size.width + clip_radius);
|
|
}
|
|
else if (side == LEFT)
|
|
{
|
|
x1 = floor (box->bounds.origin.x -clip_radius);
|
|
x2 = ceil (box->bounds.origin.x + clip_radius);
|
|
}
|
|
else
|
|
{
|
|
x1 = floor (box->bounds.origin.x + box->bounds.size.width -clip_radius);
|
|
x2 = ceil (box->bounds.origin.x + box->bounds.size.width + clip_radius);
|
|
}
|
|
|
|
if (side == LEFT || side == RIGHT)
|
|
{
|
|
blur_flags |= GSK_BLUR_X;
|
|
y1 = floor (box->bounds.origin.y - clip_radius);
|
|
y2 = ceil (box->bounds.origin.y + box->bounds.size.height + clip_radius);
|
|
}
|
|
else if (side == TOP)
|
|
{
|
|
y1 = floor (box->bounds.origin.y -clip_radius);
|
|
y2 = ceil (box->bounds.origin.y + clip_radius);
|
|
}
|
|
else
|
|
{
|
|
y1 = floor (box->bounds.origin.y + box->bounds.size.height -clip_radius);
|
|
y2 = ceil (box->bounds.origin.y + box->bounds.size.height + clip_radius);
|
|
}
|
|
|
|
drawn_rect->x = x1;
|
|
drawn_rect->y = y1;
|
|
drawn_rect->width = x2 - x1;
|
|
drawn_rect->height = y2 - y1;
|
|
|
|
cairo_rectangle (cr, x1, y1, x2 - x1, y2 - y1);
|
|
cairo_clip (cr);
|
|
draw_shadow (cr, inset, box, clip_box, radius, color, blur_flags);
|
|
}
|
|
|
|
static gboolean
|
|
needs_blur (double radius)
|
|
{
|
|
/* The code doesn't actually do any blurring for radius 1, as it
|
|
* ends up with box filter size 1 */
|
|
if (radius <= 1.0)
|
|
return FALSE;
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
static void
|
|
gsk_inset_shadow_node_draw (GskRenderNode *node,
|
|
cairo_t *cr)
|
|
{
|
|
GskInsetShadowNode *self = (GskInsetShadowNode *) node;
|
|
GskRoundedRect box, clip_box;
|
|
int clip_radius;
|
|
double x1c, y1c, x2c, y2c;
|
|
|
|
/* We don't need to draw invisible shadows */
|
|
if (gdk_rgba_is_clear (&self->color))
|
|
return;
|
|
|
|
cairo_clip_extents (cr, &x1c, &y1c, &x2c, &y2c);
|
|
if (!gsk_rounded_rect_intersects_rect (&self->outline, &GRAPHENE_RECT_INIT (x1c, y1c, x2c - x1c, y2c - y1c)))
|
|
return;
|
|
|
|
clip_radius = gsk_cairo_blur_compute_pixels (self->blur_radius);
|
|
|
|
cairo_save (cr);
|
|
|
|
gsk_rounded_rect_path (&self->outline, cr);
|
|
cairo_clip (cr);
|
|
|
|
gsk_rounded_rect_init_copy (&box, &self->outline);
|
|
gsk_rounded_rect_offset (&box, self->dx, self->dy);
|
|
gsk_rounded_rect_shrink (&box, self->spread, self->spread, self->spread, self->spread);
|
|
|
|
gsk_rounded_rect_init_copy (&clip_box, &self->outline);
|
|
gsk_rounded_rect_shrink (&clip_box, -clip_radius, -clip_radius, -clip_radius, -clip_radius);
|
|
|
|
if (!needs_blur (self->blur_radius))
|
|
draw_shadow (cr, TRUE, &box, &clip_box, self->blur_radius, &self->color, GSK_BLUR_NONE);
|
|
else
|
|
{
|
|
cairo_region_t *remaining;
|
|
cairo_rectangle_int_t r;
|
|
int i;
|
|
|
|
/* For the blurred case we divide the rendering into 9 parts,
|
|
* 4 of the corners, 4 for the horizonat/vertical lines and
|
|
* one for the interior. We make the non-interior parts
|
|
* large enought to fit the full radius of the blur, so that
|
|
* the interior part can be drawn solidly.
|
|
*/
|
|
|
|
/* In the inset case we want to paint the whole clip-box.
|
|
* We could remove the part of "box" where the blur doesn't
|
|
* reach, but computing that is a bit tricky since the
|
|
* rounded corners are on the "inside" of it. */
|
|
r.x = floor (clip_box.bounds.origin.x);
|
|
r.y = floor (clip_box.bounds.origin.y);
|
|
r.width = ceil (clip_box.bounds.origin.x + clip_box.bounds.size.width) - r.x;
|
|
r.height = ceil (clip_box.bounds.origin.y + clip_box.bounds.size.height) - r.y;
|
|
remaining = cairo_region_create_rectangle (&r);
|
|
|
|
/* First do the corners of box */
|
|
for (i = 0; i < 4; i++)
|
|
{
|
|
cairo_save (cr);
|
|
/* Always clip with remaining to ensure we never draw any area twice */
|
|
gdk_cairo_region (cr, remaining);
|
|
cairo_clip (cr);
|
|
draw_shadow_corner (cr, TRUE, &box, &clip_box, self->blur_radius, &self->color, i, &r);
|
|
cairo_restore (cr);
|
|
|
|
/* We drew the region, remove it from remaining */
|
|
cairo_region_subtract_rectangle (remaining, &r);
|
|
}
|
|
|
|
/* Then the sides */
|
|
for (i = 0; i < 4; i++)
|
|
{
|
|
cairo_save (cr);
|
|
/* Always clip with remaining to ensure we never draw any area twice */
|
|
gdk_cairo_region (cr, remaining);
|
|
cairo_clip (cr);
|
|
draw_shadow_side (cr, TRUE, &box, &clip_box, self->blur_radius, &self->color, i, &r);
|
|
cairo_restore (cr);
|
|
|
|
/* We drew the region, remove it from remaining */
|
|
cairo_region_subtract_rectangle (remaining, &r);
|
|
}
|
|
|
|
/* Then the rest, which needs no blurring */
|
|
|
|
cairo_save (cr);
|
|
gdk_cairo_region (cr, remaining);
|
|
cairo_clip (cr);
|
|
draw_shadow (cr, TRUE, &box, &clip_box, self->blur_radius, &self->color, GSK_BLUR_NONE);
|
|
cairo_restore (cr);
|
|
|
|
cairo_region_destroy (remaining);
|
|
}
|
|
|
|
cairo_restore (cr);
|
|
}
|
|
|
|
#define GSK_INSET_SHADOW_NODE_VARIANT_TYPE "(dddddddddddddddddddd)"
|
|
|
|
static GVariant *
|
|
gsk_inset_shadow_node_serialize (GskRenderNode *node)
|
|
{
|
|
GskInsetShadowNode *self = (GskInsetShadowNode *) node;
|
|
|
|
return g_variant_new (GSK_INSET_SHADOW_NODE_VARIANT_TYPE,
|
|
(double) self->outline.bounds.origin.x, (double) self->outline.bounds.origin.y,
|
|
(double) self->outline.bounds.size.width, (double) self->outline.bounds.size.height,
|
|
(double) self->outline.corner[0].width, (double) self->outline.corner[0].height,
|
|
(double) self->outline.corner[1].width, (double) self->outline.corner[1].height,
|
|
(double) self->outline.corner[2].width, (double) self->outline.corner[2].height,
|
|
(double) self->outline.corner[3].width, (double) self->outline.corner[3].height,
|
|
self->color.red, self->color.green,
|
|
self->color.blue, self->color.alpha,
|
|
(double) self->dx, (double) self->dy,
|
|
(double) self->spread, (double) self->blur_radius);
|
|
}
|
|
|
|
static GskRenderNode *
|
|
gsk_inset_shadow_node_deserialize (GVariant *variant,
|
|
GError **error)
|
|
{
|
|
double doutline[12], dx, dy, spread, radius;
|
|
GdkRGBA color;
|
|
|
|
if (!check_variant_type (variant, GSK_INSET_SHADOW_NODE_VARIANT_TYPE, error))
|
|
return NULL;
|
|
|
|
g_variant_get (variant, GSK_INSET_SHADOW_NODE_VARIANT_TYPE,
|
|
&doutline[0], &doutline[1], &doutline[2], &doutline[3],
|
|
&doutline[4], &doutline[5], &doutline[6], &doutline[7],
|
|
&doutline[8], &doutline[9], &doutline[10], &doutline[11],
|
|
&color.red, &color.green, &color.blue, &color.alpha,
|
|
&dx, &dy, &spread, &radius);
|
|
|
|
return gsk_inset_shadow_node_new (&(GskRoundedRect) {
|
|
.bounds = GRAPHENE_RECT_INIT(doutline[0], doutline[1], doutline[2], doutline[3]),
|
|
.corner = {
|
|
GRAPHENE_SIZE_INIT (doutline[4], doutline[5]),
|
|
GRAPHENE_SIZE_INIT (doutline[6], doutline[7]),
|
|
GRAPHENE_SIZE_INIT (doutline[8], doutline[9]),
|
|
GRAPHENE_SIZE_INIT (doutline[10], doutline[11])
|
|
}
|
|
},
|
|
&color, dx, dy, spread, radius);
|
|
}
|
|
|
|
static const GskRenderNodeClass GSK_INSET_SHADOW_NODE_CLASS = {
|
|
GSK_INSET_SHADOW_NODE,
|
|
sizeof (GskInsetShadowNode),
|
|
"GskInsetShadowNode",
|
|
gsk_inset_shadow_node_finalize,
|
|
gsk_inset_shadow_node_draw,
|
|
gsk_inset_shadow_node_serialize,
|
|
gsk_inset_shadow_node_deserialize
|
|
};
|
|
|
|
/**
|
|
* gsk_inset_shadow_node_new:
|
|
* @outline: outline of the region containing the 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
|
|
*
|
|
* Creates a #GskRenderNode that will render an inset shadow
|
|
* into the box given by @outline.
|
|
*
|
|
* Returns: A new #GskRenderNode
|
|
*
|
|
* Since: 3.90
|
|
*/
|
|
GskRenderNode *
|
|
gsk_inset_shadow_node_new (const GskRoundedRect *outline,
|
|
const GdkRGBA *color,
|
|
float dx,
|
|
float dy,
|
|
float spread,
|
|
float blur_radius)
|
|
{
|
|
GskInsetShadowNode *self;
|
|
|
|
g_return_val_if_fail (outline != NULL, NULL);
|
|
g_return_val_if_fail (color != NULL, NULL);
|
|
|
|
self = (GskInsetShadowNode *) gsk_render_node_new (&GSK_INSET_SHADOW_NODE_CLASS, 0);
|
|
|
|
gsk_rounded_rect_init_copy (&self->outline, outline);
|
|
self->color = *color;
|
|
self->dx = dx;
|
|
self->dy = dy;
|
|
self->spread = spread;
|
|
self->blur_radius = blur_radius;
|
|
|
|
graphene_rect_init_from_rect (&self->render_node.bounds, &self->outline.bounds);
|
|
|
|
return &self->render_node;
|
|
}
|
|
|
|
const GskRoundedRect *
|
|
gsk_inset_shadow_node_peek_outline (GskRenderNode *node)
|
|
{
|
|
GskInsetShadowNode *self = (GskInsetShadowNode *) node;
|
|
|
|
g_return_val_if_fail (GSK_IS_RENDER_NODE_TYPE (node, GSK_INSET_SHADOW_NODE), NULL);
|
|
|
|
return &self->outline;
|
|
}
|
|
|
|
const GdkRGBA *
|
|
gsk_inset_shadow_node_peek_color (GskRenderNode *node)
|
|
{
|
|
GskInsetShadowNode *self = (GskInsetShadowNode *) node;
|
|
|
|
g_return_val_if_fail (GSK_IS_RENDER_NODE_TYPE (node, GSK_INSET_SHADOW_NODE), NULL);
|
|
|
|
return &self->color;
|
|
}
|
|
|
|
float
|
|
gsk_inset_shadow_node_get_dx (GskRenderNode *node)
|
|
{
|
|
GskInsetShadowNode *self = (GskInsetShadowNode *) node;
|
|
|
|
g_return_val_if_fail (GSK_IS_RENDER_NODE_TYPE (node, GSK_INSET_SHADOW_NODE), 0.0f);
|
|
|
|
return self->dx;
|
|
}
|
|
|
|
float
|
|
gsk_inset_shadow_node_get_dy (GskRenderNode *node)
|
|
{
|
|
GskInsetShadowNode *self = (GskInsetShadowNode *) node;
|
|
|
|
g_return_val_if_fail (GSK_IS_RENDER_NODE_TYPE (node, GSK_INSET_SHADOW_NODE), 0.0f);
|
|
|
|
return self->dy;
|
|
}
|
|
|
|
float
|
|
gsk_inset_shadow_node_get_spread (GskRenderNode *node)
|
|
{
|
|
GskInsetShadowNode *self = (GskInsetShadowNode *) node;
|
|
|
|
g_return_val_if_fail (GSK_IS_RENDER_NODE_TYPE (node, GSK_INSET_SHADOW_NODE), 0.0f);
|
|
|
|
return self->spread;
|
|
}
|
|
|
|
float
|
|
gsk_inset_shadow_node_get_blur_radius (GskRenderNode *node)
|
|
{
|
|
GskInsetShadowNode *self = (GskInsetShadowNode *) node;
|
|
|
|
g_return_val_if_fail (GSK_IS_RENDER_NODE_TYPE (node, GSK_INSET_SHADOW_NODE), 0.0f);
|
|
|
|
return self->blur_radius;
|
|
}
|
|
|
|
/*** GSK_OUTSET_SHADOW_NODE ***/
|
|
|
|
typedef struct _GskOutsetShadowNode GskOutsetShadowNode;
|
|
|
|
struct _GskOutsetShadowNode
|
|
{
|
|
GskRenderNode render_node;
|
|
|
|
GskRoundedRect outline;
|
|
GdkRGBA color;
|
|
float dx;
|
|
float dy;
|
|
float spread;
|
|
float blur_radius;
|
|
};
|
|
|
|
static void
|
|
gsk_outset_shadow_node_finalize (GskRenderNode *node)
|
|
{
|
|
}
|
|
|
|
static void
|
|
gsk_outset_shadow_get_extents (GskOutsetShadowNode *self,
|
|
float *top,
|
|
float *right,
|
|
float *bottom,
|
|
float *left)
|
|
{
|
|
float clip_radius;
|
|
|
|
clip_radius = gsk_cairo_blur_compute_pixels (self->blur_radius);
|
|
*top = MAX (0, clip_radius + self->spread - self->dy);
|
|
*right = MAX (0, ceil (clip_radius + self->spread + self->dx));
|
|
*bottom = MAX (0, ceil (clip_radius + self->spread + self->dy));
|
|
*left = MAX (0, ceil (clip_radius + self->spread - self->dx));
|
|
}
|
|
|
|
static void
|
|
gsk_outset_shadow_node_draw (GskRenderNode *node,
|
|
cairo_t *cr)
|
|
{
|
|
GskOutsetShadowNode *self = (GskOutsetShadowNode *) node;
|
|
GskRoundedRect box, clip_box;
|
|
int clip_radius;
|
|
double x1c, y1c, x2c, y2c;
|
|
float top, right, bottom, left;
|
|
|
|
/* We don't need to draw invisible shadows */
|
|
if (gdk_rgba_is_clear (&self->color))
|
|
return;
|
|
|
|
cairo_clip_extents (cr, &x1c, &y1c, &x2c, &y2c);
|
|
if (gsk_rounded_rect_contains_rect (&self->outline, &GRAPHENE_RECT_INIT (x1c, y1c, x2c - x1c, y2c - y1c)))
|
|
return;
|
|
|
|
clip_radius = gsk_cairo_blur_compute_pixels (self->blur_radius);
|
|
|
|
cairo_save (cr);
|
|
|
|
gsk_rounded_rect_init_copy (&clip_box, &self->outline);
|
|
gsk_outset_shadow_get_extents (self, &top, &right, &bottom, &left);
|
|
gsk_rounded_rect_shrink (&clip_box, -top, -right, -bottom, -left);
|
|
|
|
cairo_set_fill_rule (cr, CAIRO_FILL_RULE_EVEN_ODD);
|
|
gsk_rounded_rect_path (&self->outline, cr);
|
|
cairo_rectangle (cr,
|
|
clip_box.bounds.origin.x, clip_box.bounds.origin.y,
|
|
clip_box.bounds.size.width, clip_box.bounds.size.height);
|
|
|
|
cairo_clip (cr);
|
|
|
|
gsk_rounded_rect_init_copy (&box, &self->outline);
|
|
gsk_rounded_rect_offset (&box, self->dx, self->dy);
|
|
gsk_rounded_rect_shrink (&box, -self->spread, -self->spread, -self->spread, -self->spread);
|
|
|
|
if (!needs_blur (self->blur_radius))
|
|
draw_shadow (cr, FALSE, &box, &clip_box, self->blur_radius, &self->color, GSK_BLUR_NONE);
|
|
else
|
|
{
|
|
int i;
|
|
cairo_region_t *remaining;
|
|
cairo_rectangle_int_t r;
|
|
|
|
/* For the blurred case we divide the rendering into 9 parts,
|
|
* 4 of the corners, 4 for the horizonat/vertical lines and
|
|
* one for the interior. We make the non-interior parts
|
|
* large enought to fit the full radius of the blur, so that
|
|
* the interior part can be drawn solidly.
|
|
*/
|
|
|
|
/* In the outset case we want to paint the entire box, plus as far
|
|
* as the radius reaches from it */
|
|
r.x = floor (box.bounds.origin.x - clip_radius);
|
|
r.y = floor (box.bounds.origin.y - clip_radius);
|
|
r.width = ceil (box.bounds.origin.x + box.bounds.size.width + clip_radius) - r.x;
|
|
r.height = ceil (box.bounds.origin.y + box.bounds.size.height + clip_radius) - r.y;
|
|
|
|
remaining = cairo_region_create_rectangle (&r);
|
|
|
|
/* First do the corners of box */
|
|
for (i = 0; i < 4; i++)
|
|
{
|
|
cairo_save (cr);
|
|
/* Always clip with remaining to ensure we never draw any area twice */
|
|
gdk_cairo_region (cr, remaining);
|
|
cairo_clip (cr);
|
|
draw_shadow_corner (cr, FALSE, &box, &clip_box, self->blur_radius, &self->color, i, &r);
|
|
cairo_restore (cr);
|
|
|
|
/* We drew the region, remove it from remaining */
|
|
cairo_region_subtract_rectangle (remaining, &r);
|
|
}
|
|
|
|
/* Then the sides */
|
|
for (i = 0; i < 4; i++)
|
|
{
|
|
cairo_save (cr);
|
|
/* Always clip with remaining to ensure we never draw any area twice */
|
|
gdk_cairo_region (cr, remaining);
|
|
cairo_clip (cr);
|
|
draw_shadow_side (cr, FALSE, &box, &clip_box, self->blur_radius, &self->color, i, &r);
|
|
cairo_restore (cr);
|
|
|
|
/* We drew the region, remove it from remaining */
|
|
cairo_region_subtract_rectangle (remaining, &r);
|
|
}
|
|
|
|
/* Then the rest, which needs no blurring */
|
|
|
|
cairo_save (cr);
|
|
gdk_cairo_region (cr, remaining);
|
|
cairo_clip (cr);
|
|
draw_shadow (cr, FALSE, &box, &clip_box, self->blur_radius, &self->color, GSK_BLUR_NONE);
|
|
cairo_restore (cr);
|
|
|
|
cairo_region_destroy (remaining);
|
|
}
|
|
|
|
cairo_restore (cr);
|
|
}
|
|
|
|
#define GSK_OUTSET_SHADOW_NODE_VARIANT_TYPE "(dddddddddddddddddddd)"
|
|
|
|
static GVariant *
|
|
gsk_outset_shadow_node_serialize (GskRenderNode *node)
|
|
{
|
|
GskOutsetShadowNode *self = (GskOutsetShadowNode *) node;
|
|
|
|
return g_variant_new (GSK_OUTSET_SHADOW_NODE_VARIANT_TYPE,
|
|
(double) self->outline.bounds.origin.x, (double) self->outline.bounds.origin.y,
|
|
(double) self->outline.bounds.size.width, (double) self->outline.bounds.size.height,
|
|
(double) self->outline.corner[0].width, (double) self->outline.corner[0].height,
|
|
(double) self->outline.corner[1].width, (double) self->outline.corner[1].height,
|
|
(double) self->outline.corner[2].width, (double) self->outline.corner[2].height,
|
|
(double) self->outline.corner[3].width, (double) self->outline.corner[3].height,
|
|
self->color.red, self->color.green,
|
|
self->color.blue, self->color.alpha,
|
|
(double) self->dx, (double) self->dy,
|
|
(double) self->spread, (double) self->blur_radius);
|
|
}
|
|
|
|
static GskRenderNode *
|
|
gsk_outset_shadow_node_deserialize (GVariant *variant,
|
|
GError **error)
|
|
{
|
|
double doutline[12], dx, dy, spread, radius;
|
|
GdkRGBA color;
|
|
|
|
if (!check_variant_type (variant, GSK_OUTSET_SHADOW_NODE_VARIANT_TYPE, error))
|
|
return NULL;
|
|
|
|
g_variant_get (variant, GSK_INSET_SHADOW_NODE_VARIANT_TYPE,
|
|
&doutline[0], &doutline[1], &doutline[2], &doutline[3],
|
|
&doutline[4], &doutline[5], &doutline[6], &doutline[7],
|
|
&doutline[8], &doutline[9], &doutline[10], &doutline[11],
|
|
&color.red, &color.green, &color.blue, &color.alpha,
|
|
&dx, &dy, &spread, &radius);
|
|
|
|
return gsk_outset_shadow_node_new (&(GskRoundedRect) {
|
|
.bounds = GRAPHENE_RECT_INIT(doutline[0], doutline[1], doutline[2], doutline[3]),
|
|
.corner = {
|
|
GRAPHENE_SIZE_INIT (doutline[4], doutline[5]),
|
|
GRAPHENE_SIZE_INIT (doutline[6], doutline[7]),
|
|
GRAPHENE_SIZE_INIT (doutline[8], doutline[9]),
|
|
GRAPHENE_SIZE_INIT (doutline[10], doutline[11])
|
|
}
|
|
},
|
|
&color, dx, dy, spread, radius);
|
|
}
|
|
|
|
static const GskRenderNodeClass GSK_OUTSET_SHADOW_NODE_CLASS = {
|
|
GSK_OUTSET_SHADOW_NODE,
|
|
sizeof (GskOutsetShadowNode),
|
|
"GskOutsetShadowNode",
|
|
gsk_outset_shadow_node_finalize,
|
|
gsk_outset_shadow_node_draw,
|
|
gsk_outset_shadow_node_serialize,
|
|
gsk_outset_shadow_node_deserialize
|
|
};
|
|
|
|
/**
|
|
* gsk_outset_shadow_node_new:
|
|
* @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
|
|
*
|
|
* Creates a #GskRenderNode that will render an outset shadow
|
|
* around the box given by @outline.
|
|
*
|
|
* Returns: A new #GskRenderNode
|
|
*
|
|
* Since: 3.90
|
|
*/
|
|
GskRenderNode *
|
|
gsk_outset_shadow_node_new (const GskRoundedRect *outline,
|
|
const GdkRGBA *color,
|
|
float dx,
|
|
float dy,
|
|
float spread,
|
|
float blur_radius)
|
|
{
|
|
GskOutsetShadowNode *self;
|
|
float top, right, bottom, left;
|
|
|
|
g_return_val_if_fail (outline != NULL, NULL);
|
|
g_return_val_if_fail (color != NULL, NULL);
|
|
|
|
self = (GskOutsetShadowNode *) gsk_render_node_new (&GSK_OUTSET_SHADOW_NODE_CLASS, 0);
|
|
|
|
gsk_rounded_rect_init_copy (&self->outline, outline);
|
|
self->color = *color;
|
|
self->dx = dx;
|
|
self->dy = dy;
|
|
self->spread = spread;
|
|
self->blur_radius = blur_radius;
|
|
|
|
gsk_outset_shadow_get_extents (self, &top, &right, &bottom, &left);
|
|
|
|
graphene_rect_init_from_rect (&self->render_node.bounds, &self->outline.bounds);
|
|
|
|
self->render_node.bounds.origin.x -= left;
|
|
self->render_node.bounds.origin.y -= top;
|
|
self->render_node.bounds.size.width += left + right;
|
|
self->render_node.bounds.size.height += top + bottom;
|
|
|
|
return &self->render_node;
|
|
}
|
|
|
|
const GskRoundedRect *
|
|
gsk_outset_shadow_node_peek_outline (GskRenderNode *node)
|
|
{
|
|
GskOutsetShadowNode *self = (GskOutsetShadowNode *) node;
|
|
|
|
g_return_val_if_fail (GSK_IS_RENDER_NODE_TYPE (node, GSK_OUTSET_SHADOW_NODE), NULL);
|
|
|
|
return &self->outline;
|
|
}
|
|
|
|
const GdkRGBA *
|
|
gsk_outset_shadow_node_peek_color (GskRenderNode *node)
|
|
{
|
|
GskOutsetShadowNode *self = (GskOutsetShadowNode *) node;
|
|
|
|
g_return_val_if_fail (GSK_IS_RENDER_NODE_TYPE (node, GSK_OUTSET_SHADOW_NODE), NULL);
|
|
|
|
return &self->color;
|
|
}
|
|
|
|
float
|
|
gsk_outset_shadow_node_get_dx (GskRenderNode *node)
|
|
{
|
|
GskOutsetShadowNode *self = (GskOutsetShadowNode *) node;
|
|
|
|
g_return_val_if_fail (GSK_IS_RENDER_NODE_TYPE (node, GSK_OUTSET_SHADOW_NODE), 0.0f);
|
|
|
|
return self->dx;
|
|
}
|
|
|
|
float
|
|
gsk_outset_shadow_node_get_dy (GskRenderNode *node)
|
|
{
|
|
GskOutsetShadowNode *self = (GskOutsetShadowNode *) node;
|
|
|
|
g_return_val_if_fail (GSK_IS_RENDER_NODE_TYPE (node, GSK_OUTSET_SHADOW_NODE), 0.0f);
|
|
|
|
return self->dy;
|
|
}
|
|
|
|
float
|
|
gsk_outset_shadow_node_get_spread (GskRenderNode *node)
|
|
{
|
|
GskOutsetShadowNode *self = (GskOutsetShadowNode *) node;
|
|
|
|
g_return_val_if_fail (GSK_IS_RENDER_NODE_TYPE (node, GSK_OUTSET_SHADOW_NODE), 0.0f);
|
|
|
|
return self->spread;
|
|
}
|
|
|
|
float
|
|
gsk_outset_shadow_node_get_blur_radius (GskRenderNode *node)
|
|
{
|
|
GskOutsetShadowNode *self = (GskOutsetShadowNode *) node;
|
|
|
|
g_return_val_if_fail (GSK_IS_RENDER_NODE_TYPE (node, GSK_OUTSET_SHADOW_NODE), 0.0f);
|
|
|
|
return self->blur_radius;
|
|
}
|
|
|
|
/*** GSK_CAIRO_NODE ***/
|
|
|
|
typedef struct _GskCairoNode GskCairoNode;
|
|
|
|
struct _GskCairoNode
|
|
{
|
|
GskRenderNode render_node;
|
|
|
|
cairo_surface_t *surface;
|
|
};
|
|
|
|
static void
|
|
gsk_cairo_node_finalize (GskRenderNode *node)
|
|
{
|
|
GskCairoNode *self = (GskCairoNode *) node;
|
|
|
|
if (self->surface)
|
|
cairo_surface_destroy (self->surface);
|
|
}
|
|
|
|
static void
|
|
gsk_cairo_node_draw (GskRenderNode *node,
|
|
cairo_t *cr)
|
|
{
|
|
GskCairoNode *self = (GskCairoNode *) node;
|
|
|
|
if (self->surface == NULL)
|
|
return;
|
|
|
|
cairo_set_source_surface (cr, self->surface, node->bounds.origin.x, node->bounds.origin.y);
|
|
cairo_paint (cr);
|
|
}
|
|
|
|
#define GSK_CAIRO_NODE_VARIANT_TYPE "(dddduuau)"
|
|
|
|
static GVariant *
|
|
gsk_cairo_node_serialize (GskRenderNode *node)
|
|
{
|
|
GskCairoNode *self = (GskCairoNode *) node;
|
|
|
|
if (self->surface == NULL)
|
|
{
|
|
return g_variant_new ("(dddduu@au)",
|
|
(double) node->bounds.origin.x, (double) node->bounds.origin.y,
|
|
(double) node->bounds.size.width, (double) node->bounds.size.height,
|
|
(guint32) 0, (guint32) 0,
|
|
g_variant_new_array (G_VARIANT_TYPE ("u"), NULL, 0));
|
|
}
|
|
else if (cairo_image_surface_get_width (self->surface) * 4 == cairo_image_surface_get_stride (self->surface))
|
|
{
|
|
return g_variant_new ("(dddduu@au)",
|
|
(double) node->bounds.origin.x, (double) node->bounds.origin.y,
|
|
(double) node->bounds.size.width, (double) node->bounds.size.height,
|
|
(guint32) cairo_image_surface_get_width (self->surface),
|
|
(guint32) cairo_image_surface_get_height (self->surface),
|
|
g_variant_new_fixed_array (G_VARIANT_TYPE ("u"),
|
|
cairo_image_surface_get_data (self->surface),
|
|
cairo_image_surface_get_width (self->surface)
|
|
* cairo_image_surface_get_height (self->surface),
|
|
sizeof (guint32)));
|
|
}
|
|
else
|
|
{
|
|
int width, height;
|
|
int stride, i;
|
|
guchar *mem_surface, *data;
|
|
GVariant *result;
|
|
|
|
width = cairo_image_surface_get_width (self->surface);
|
|
height = cairo_image_surface_get_height (self->surface);
|
|
stride = cairo_image_surface_get_stride (self->surface);
|
|
data = cairo_image_surface_get_data (self->surface);
|
|
|
|
mem_surface = (guchar *) g_malloc (width * height * 4);
|
|
|
|
for (i = 0; i < height; i++)
|
|
memcpy (mem_surface + i * width * 4, data + i * stride, width * 4);
|
|
|
|
result = g_variant_new ("(dddduu@au)",
|
|
(double) node->bounds.origin.x, (double) node->bounds.origin.y,
|
|
(double) node->bounds.size.width, (double) node->bounds.size.height,
|
|
(guint32) width,
|
|
(guint32) height,
|
|
g_variant_new_fixed_array (G_VARIANT_TYPE ("u"),
|
|
mem_surface,
|
|
width * height,
|
|
sizeof (guint32)));
|
|
g_free (mem_surface);
|
|
|
|
return result;
|
|
}
|
|
}
|
|
|
|
const cairo_user_data_key_t gsk_surface_variant_key;
|
|
|
|
static GskRenderNode *
|
|
gsk_cairo_node_deserialize (GVariant *variant,
|
|
GError **error)
|
|
{
|
|
GskRenderNode *result;
|
|
cairo_surface_t *surface;
|
|
double x, y, width, height;
|
|
guint32 surface_width, surface_height;
|
|
GVariant *pixel_variant;
|
|
gsize n_pixels;
|
|
|
|
if (!check_variant_type (variant, GSK_CAIRO_NODE_VARIANT_TYPE, error))
|
|
return NULL;
|
|
|
|
g_variant_get (variant, "(dddduu@au)",
|
|
&x, &y, &width, &height,
|
|
&surface_width, &surface_height,
|
|
&pixel_variant);
|
|
|
|
if (surface_width == 0 || surface_height == 0)
|
|
{
|
|
g_variant_unref (pixel_variant);
|
|
return gsk_cairo_node_new (&GRAPHENE_RECT_INIT (x, y, width, height));
|
|
}
|
|
|
|
/* XXX: Make this work without copying the data */
|
|
surface = cairo_image_surface_create_for_data ((guchar *) g_variant_get_fixed_array (pixel_variant, &n_pixels, sizeof (guint32)),
|
|
CAIRO_FORMAT_ARGB32,
|
|
surface_width, surface_height, surface_width * 4);
|
|
cairo_surface_set_user_data (surface,
|
|
&gsk_surface_variant_key,
|
|
pixel_variant,
|
|
(cairo_destroy_func_t) g_variant_unref);
|
|
|
|
result = gsk_cairo_node_new_for_surface (&GRAPHENE_RECT_INIT (x, y, width, height), surface);
|
|
|
|
cairo_surface_destroy (surface);
|
|
|
|
return result;
|
|
}
|
|
|
|
static const GskRenderNodeClass GSK_CAIRO_NODE_CLASS = {
|
|
GSK_CAIRO_NODE,
|
|
sizeof (GskCairoNode),
|
|
"GskCairoNode",
|
|
gsk_cairo_node_finalize,
|
|
gsk_cairo_node_draw,
|
|
gsk_cairo_node_serialize,
|
|
gsk_cairo_node_deserialize
|
|
};
|
|
|
|
const cairo_surface_t *
|
|
gsk_cairo_node_peek_surface (GskRenderNode *node)
|
|
{
|
|
GskCairoNode *self = (GskCairoNode *) node;
|
|
|
|
g_return_val_if_fail (GSK_IS_RENDER_NODE_TYPE (node, GSK_CAIRO_NODE), NULL);
|
|
|
|
return self->surface;
|
|
}
|
|
|
|
GskRenderNode *
|
|
gsk_cairo_node_new_for_surface (const graphene_rect_t *bounds,
|
|
cairo_surface_t *surface)
|
|
{
|
|
GskCairoNode *self;
|
|
|
|
g_return_val_if_fail (bounds != NULL, NULL);
|
|
|
|
self = (GskCairoNode *) gsk_render_node_new (&GSK_CAIRO_NODE_CLASS, 0);
|
|
|
|
graphene_rect_init_from_rect (&self->render_node.bounds, bounds);
|
|
self->surface = cairo_surface_reference (surface);
|
|
|
|
return &self->render_node;
|
|
}
|
|
|
|
/**
|
|
* gsk_cairo_node_new:
|
|
* @bounds: the rectangle to render to
|
|
*
|
|
* Creates a #GskRenderNode that will render a cairo surface
|
|
* into the area given by @bounds. You can draw to the cairo
|
|
* surface using gsk_cairo_node_get_draw_context()
|
|
*
|
|
* Returns: A new #GskRenderNode
|
|
*
|
|
* Since: 3.90
|
|
*/
|
|
GskRenderNode *
|
|
gsk_cairo_node_new (const graphene_rect_t *bounds)
|
|
{
|
|
GskCairoNode *self;
|
|
|
|
g_return_val_if_fail (bounds != NULL, NULL);
|
|
|
|
self = (GskCairoNode *) gsk_render_node_new (&GSK_CAIRO_NODE_CLASS, 0);
|
|
|
|
graphene_rect_init_from_rect (&self->render_node.bounds, bounds);
|
|
|
|
return &self->render_node;
|
|
}
|
|
|
|
/**
|
|
* gsk_cairo_node_get_draw_context:
|
|
* @node: a cairo #GskRenderNode
|
|
* @renderer: (nullable): Renderer to optimize for or %NULL for any
|
|
*
|
|
* Creates a Cairo context for drawing using the surface associated
|
|
* to the render node.
|
|
* If no surface exists yet, a surface will be created optimized for
|
|
* rendering to @renderer.
|
|
*
|
|
* Returns: (transfer full): a Cairo context used for drawing; use
|
|
* cairo_destroy() when done drawing
|
|
*
|
|
* Since: 3.90
|
|
*/
|
|
cairo_t *
|
|
gsk_cairo_node_get_draw_context (GskRenderNode *node,
|
|
GskRenderer *renderer)
|
|
{
|
|
GskCairoNode *self = (GskCairoNode *) node;
|
|
int width, height;
|
|
cairo_t *res;
|
|
|
|
g_return_val_if_fail (GSK_IS_RENDER_NODE_TYPE (node, GSK_CAIRO_NODE), NULL);
|
|
g_return_val_if_fail (renderer == NULL || GSK_IS_RENDERER (renderer), NULL);
|
|
|
|
width = ceilf (node->bounds.size.width);
|
|
height = ceilf (node->bounds.size.height);
|
|
|
|
if (width <= 0 || height <= 0)
|
|
{
|
|
cairo_surface_t *surface = cairo_image_surface_create (CAIRO_FORMAT_ARGB32, 0, 0);
|
|
res = cairo_create (surface);
|
|
cairo_surface_destroy (surface);
|
|
}
|
|
else if (self->surface == NULL)
|
|
{
|
|
if (renderer)
|
|
{
|
|
self->surface = gsk_renderer_create_cairo_surface (renderer,
|
|
CAIRO_FORMAT_ARGB32,
|
|
ceilf (node->bounds.size.width),
|
|
ceilf (node->bounds.size.height));
|
|
}
|
|
else
|
|
{
|
|
self->surface = cairo_image_surface_create (CAIRO_FORMAT_ARGB32,
|
|
ceilf (node->bounds.size.width),
|
|
ceilf (node->bounds.size.height));
|
|
}
|
|
res = cairo_create (self->surface);
|
|
}
|
|
else
|
|
{
|
|
res = cairo_create (self->surface);
|
|
}
|
|
|
|
cairo_translate (res, -node->bounds.origin.x, -node->bounds.origin.y);
|
|
|
|
cairo_rectangle (res,
|
|
node->bounds.origin.x, node->bounds.origin.y,
|
|
node->bounds.size.width, node->bounds.size.height);
|
|
cairo_clip (res);
|
|
|
|
if (GSK_DEBUG_CHECK (SURFACE))
|
|
{
|
|
const char *prefix;
|
|
prefix = g_getenv ("GSK_DEBUG_PREFIX");
|
|
if (!prefix || g_str_has_prefix (node->name, prefix))
|
|
{
|
|
cairo_save (res);
|
|
cairo_rectangle (res,
|
|
node->bounds.origin.x + 1, node->bounds.origin.y + 1,
|
|
node->bounds.size.width - 2, node->bounds.size.height - 2);
|
|
cairo_set_line_width (res, 2);
|
|
cairo_set_source_rgb (res, 1, 0, 0);
|
|
cairo_stroke (res);
|
|
cairo_restore (res);
|
|
}
|
|
}
|
|
|
|
return res;
|
|
}
|
|
|
|
/**** GSK_CONTAINER_NODE ***/
|
|
|
|
typedef struct _GskContainerNode GskContainerNode;
|
|
|
|
struct _GskContainerNode
|
|
{
|
|
GskRenderNode render_node;
|
|
|
|
guint n_children;
|
|
GskRenderNode *children[];
|
|
};
|
|
|
|
static void
|
|
gsk_container_node_finalize (GskRenderNode *node)
|
|
{
|
|
GskContainerNode *container = (GskContainerNode *) node;
|
|
guint i;
|
|
|
|
for (i = 0; i < container->n_children; i++)
|
|
gsk_render_node_unref (container->children[i]);
|
|
}
|
|
|
|
static void
|
|
gsk_container_node_draw (GskRenderNode *node,
|
|
cairo_t *cr)
|
|
{
|
|
GskContainerNode *container = (GskContainerNode *) node;
|
|
guint i;
|
|
|
|
for (i = 0; i < container->n_children; i++)
|
|
{
|
|
gsk_render_node_draw (container->children[i], cr);
|
|
}
|
|
}
|
|
|
|
static void
|
|
gsk_container_node_get_bounds (GskContainerNode *container,
|
|
graphene_rect_t *bounds)
|
|
{
|
|
guint i;
|
|
|
|
if (container->n_children == 0)
|
|
{
|
|
graphene_rect_init_from_rect (bounds, graphene_rect_zero());
|
|
return;
|
|
}
|
|
|
|
graphene_rect_init_from_rect (bounds, &container->children[0]->bounds);
|
|
for (i = 1; i < container->n_children; i++)
|
|
graphene_rect_union (bounds, &container->children[i]->bounds, bounds);
|
|
}
|
|
|
|
#define GSK_CONTAINER_NODE_VARIANT_TYPE "a(uv)"
|
|
|
|
static GVariant *
|
|
gsk_container_node_serialize (GskRenderNode *node)
|
|
{
|
|
GskContainerNode *self = (GskContainerNode *) node;
|
|
GVariantBuilder builder;
|
|
guint i;
|
|
|
|
g_variant_builder_init (&builder, G_VARIANT_TYPE (GSK_CONTAINER_NODE_VARIANT_TYPE));
|
|
|
|
for (i = 0; i < self->n_children; i++)
|
|
{
|
|
g_variant_builder_add (&builder, "(uv)",
|
|
(guint32) gsk_render_node_get_node_type (self->children[i]),
|
|
gsk_render_node_serialize_node (self->children[i]));
|
|
}
|
|
|
|
return g_variant_builder_end (&builder);
|
|
}
|
|
|
|
static GskRenderNode *
|
|
gsk_container_node_deserialize (GVariant *variant,
|
|
GError **error)
|
|
{
|
|
GskRenderNode *result;
|
|
GVariantIter iter;
|
|
gsize i, n_children;
|
|
guint32 child_type;
|
|
GVariant *child_variant;
|
|
|
|
if (!check_variant_type (variant, GSK_CONTAINER_NODE_VARIANT_TYPE, error))
|
|
return NULL;
|
|
|
|
i = 0;
|
|
n_children = g_variant_iter_init (&iter, variant);
|
|
GskRenderNode **children = g_newa (GskRenderNode *, n_children);
|
|
|
|
while (g_variant_iter_loop (&iter, "(uv)", &child_type, &child_variant))
|
|
{
|
|
children[i] = gsk_render_node_deserialize_node (child_type, child_variant, error);
|
|
if (children[i] == NULL)
|
|
{
|
|
guint j;
|
|
for (j = 0; j < i; j++)
|
|
gsk_render_node_unref (children[j]);
|
|
g_variant_unref (child_variant);
|
|
return NULL;
|
|
}
|
|
i++;
|
|
}
|
|
|
|
result = gsk_container_node_new (children, n_children);
|
|
|
|
for (i = 0; i < n_children; i++)
|
|
gsk_render_node_unref (children[i]);
|
|
|
|
return result;
|
|
}
|
|
|
|
static const GskRenderNodeClass GSK_CONTAINER_NODE_CLASS = {
|
|
GSK_CONTAINER_NODE,
|
|
sizeof (GskContainerNode),
|
|
"GskContainerNode",
|
|
gsk_container_node_finalize,
|
|
gsk_container_node_draw,
|
|
gsk_container_node_serialize,
|
|
gsk_container_node_deserialize
|
|
};
|
|
|
|
/**
|
|
* gsk_container_node_new:
|
|
* @children: (array length=n_children) (transfer none): The children of the node
|
|
* @n_children: Number of children in the @children array
|
|
*
|
|
* Creates a new #GskRenderNode instance for holding the given @children.
|
|
* The new node will acquire a reference to each of the children.
|
|
*
|
|
* Returns: (transfer full): the new #GskRenderNode
|
|
*
|
|
* Since: 3.90
|
|
*/
|
|
GskRenderNode *
|
|
gsk_container_node_new (GskRenderNode **children,
|
|
guint n_children)
|
|
{
|
|
GskContainerNode *container;
|
|
guint i;
|
|
|
|
container = (GskContainerNode *) gsk_render_node_new (&GSK_CONTAINER_NODE_CLASS, sizeof (GskRenderNode *) * n_children);
|
|
|
|
container->n_children = n_children;
|
|
|
|
for (i = 0; i < container->n_children; i++)
|
|
container->children[i] = gsk_render_node_ref (children[i]);
|
|
|
|
gsk_container_node_get_bounds (container, &container->render_node.bounds);
|
|
|
|
return &container->render_node;
|
|
}
|
|
|
|
/**
|
|
* gsk_container_node_get_n_children:
|
|
* @node: a container #GskRenderNode
|
|
*
|
|
* Retrieves the number of direct children of @node.
|
|
*
|
|
* Returns: the number of children of the #GskRenderNode
|
|
*
|
|
* Since: 3.90
|
|
*/
|
|
guint
|
|
gsk_container_node_get_n_children (GskRenderNode *node)
|
|
{
|
|
GskContainerNode *container = (GskContainerNode *) node;
|
|
|
|
g_return_val_if_fail (GSK_IS_RENDER_NODE_TYPE (node, GSK_CONTAINER_NODE), 0);
|
|
|
|
return container->n_children;
|
|
}
|
|
|
|
/**
|
|
* gsk_container_node_get_child:
|
|
* @node: a container #GskRenderNode
|
|
* @idx: the position of the child to get
|
|
*
|
|
* Gets one of the children of @container.
|
|
*
|
|
* Returns: the @idx'th child of @container
|
|
*
|
|
* Since: 3.90
|
|
*/
|
|
GskRenderNode *
|
|
gsk_container_node_get_child (GskRenderNode *node,
|
|
guint idx)
|
|
{
|
|
GskContainerNode *container = (GskContainerNode *) node;
|
|
|
|
g_return_val_if_fail (GSK_IS_RENDER_NODE_TYPE (node, GSK_CONTAINER_NODE), NULL);
|
|
g_return_val_if_fail (idx < container->n_children, 0);
|
|
|
|
return container->children[idx];
|
|
}
|
|
|
|
/*** GSK_TRANSFORM_NODE ***/
|
|
|
|
typedef struct _GskTransformNode GskTransformNode;
|
|
|
|
struct _GskTransformNode
|
|
{
|
|
GskRenderNode render_node;
|
|
|
|
GskRenderNode *child;
|
|
graphene_matrix_t transform;
|
|
};
|
|
|
|
static void
|
|
gsk_transform_node_finalize (GskRenderNode *node)
|
|
{
|
|
GskTransformNode *self = (GskTransformNode *) node;
|
|
|
|
gsk_render_node_unref (self->child);
|
|
}
|
|
|
|
static void
|
|
gsk_transform_node_draw (GskRenderNode *node,
|
|
cairo_t *cr)
|
|
{
|
|
GskTransformNode *self = (GskTransformNode *) node;
|
|
cairo_matrix_t ctm;
|
|
|
|
if (graphene_matrix_to_2d (&self->transform, &ctm.xx, &ctm.yx, &ctm.xy, &ctm.yy, &ctm.x0, &ctm.y0))
|
|
{
|
|
GSK_NOTE (CAIRO, g_print ("CTM = { .xx = %g, .yx = %g, .xy = %g, .yy = %g, .x0 = %g, .y0 = %g }\n",
|
|
ctm.xx, ctm.yx,
|
|
ctm.xy, ctm.yy,
|
|
ctm.x0, ctm.y0));
|
|
cairo_transform (cr, &ctm);
|
|
|
|
gsk_render_node_draw (self->child, cr);
|
|
}
|
|
else
|
|
{
|
|
cairo_set_source_rgb (cr, 255 / 255., 105 / 255., 180 / 255.);
|
|
cairo_rectangle (cr, node->bounds.origin.x, node->bounds.origin.y, node->bounds.size.width, node->bounds.size.height);
|
|
cairo_fill (cr);
|
|
}
|
|
}
|
|
|
|
#define GSK_TRANSFORM_NODE_VARIANT_TYPE "(dddddddddddddddduv)"
|
|
|
|
static GVariant *
|
|
gsk_transform_node_serialize (GskRenderNode *node)
|
|
{
|
|
GskTransformNode *self = (GskTransformNode *) node;
|
|
float mat[16];
|
|
|
|
graphene_matrix_to_float (&self->transform, mat);
|
|
|
|
return g_variant_new (GSK_TRANSFORM_NODE_VARIANT_TYPE,
|
|
(double) mat[0], (double) mat[1], (double) mat[2], (double) mat[3],
|
|
(double) mat[4], (double) mat[5], (double) mat[6], (double) mat[7],
|
|
(double) mat[8], (double) mat[9], (double) mat[10], (double) mat[11],
|
|
(double) mat[12], (double) mat[13], (double) mat[14], (double) mat[15],
|
|
(guint32) gsk_render_node_get_node_type (self->child),
|
|
gsk_render_node_serialize_node (self->child));
|
|
}
|
|
|
|
static GskRenderNode *
|
|
gsk_transform_node_deserialize (GVariant *variant,
|
|
GError **error)
|
|
{
|
|
graphene_matrix_t transform;
|
|
double mat[16];
|
|
guint32 child_type;
|
|
GVariant *child_variant;
|
|
GskRenderNode *result, *child;
|
|
|
|
if (!check_variant_type (variant, GSK_TRANSFORM_NODE_VARIANT_TYPE, error))
|
|
return NULL;
|
|
|
|
g_variant_get (variant, GSK_TRANSFORM_NODE_VARIANT_TYPE,
|
|
&mat[0], &mat[1], &mat[2], &mat[3],
|
|
&mat[4], &mat[5], &mat[6], &mat[7],
|
|
&mat[8], &mat[9], &mat[10], &mat[11],
|
|
&mat[12], &mat[13], &mat[14], &mat[15],
|
|
&child_type, &child_variant);
|
|
|
|
child = gsk_render_node_deserialize_node (child_type, child_variant, error);
|
|
g_variant_unref (child_variant);
|
|
|
|
if (child == NULL)
|
|
return NULL;
|
|
|
|
graphene_matrix_init_from_float (&transform,
|
|
(float[16]) {
|
|
mat[0], mat[1], mat[2], mat[3],
|
|
mat[4], mat[5], mat[6], mat[7],
|
|
mat[8], mat[9], mat[10], mat[11],
|
|
mat[12], mat[13], mat[14], mat[15]
|
|
});
|
|
|
|
result = gsk_transform_node_new (child, &transform);
|
|
|
|
gsk_render_node_unref (child);
|
|
|
|
return result;
|
|
}
|
|
|
|
static const GskRenderNodeClass GSK_TRANSFORM_NODE_CLASS = {
|
|
GSK_TRANSFORM_NODE,
|
|
sizeof (GskTransformNode),
|
|
"GskTransformNode",
|
|
gsk_transform_node_finalize,
|
|
gsk_transform_node_draw,
|
|
gsk_transform_node_serialize,
|
|
gsk_transform_node_deserialize
|
|
};
|
|
|
|
/**
|
|
* gsk_transform_node_new:
|
|
* @child: The node to transform
|
|
* @transform: The transform to apply
|
|
*
|
|
* Creates a #GskRenderNode that will transform the given @child
|
|
* with the given @transform.
|
|
*
|
|
* Returns: A new #GskRenderNode
|
|
*
|
|
* Since: 3.90
|
|
*/
|
|
GskRenderNode *
|
|
gsk_transform_node_new (GskRenderNode *child,
|
|
const graphene_matrix_t *transform)
|
|
{
|
|
GskTransformNode *self;
|
|
|
|
g_return_val_if_fail (GSK_IS_RENDER_NODE (child), NULL);
|
|
g_return_val_if_fail (transform != NULL, NULL);
|
|
|
|
self = (GskTransformNode *) gsk_render_node_new (&GSK_TRANSFORM_NODE_CLASS, 0);
|
|
|
|
self->child = gsk_render_node_ref (child);
|
|
graphene_matrix_init_from_matrix (&self->transform, transform);
|
|
|
|
graphene_matrix_transform_bounds (&self->transform,
|
|
&child->bounds,
|
|
&self->render_node.bounds);
|
|
return &self->render_node;
|
|
}
|
|
|
|
/**
|
|
* gsk_transform_node_get_child:
|
|
* @node: a transform @GskRenderNode
|
|
*
|
|
* Gets the child node that is getting transformed by the given @node.
|
|
*
|
|
* Returns: (transfer none): The child that is getting transformed
|
|
**/
|
|
GskRenderNode *
|
|
gsk_transform_node_get_child (GskRenderNode *node)
|
|
{
|
|
GskTransformNode *self = (GskTransformNode *) node;
|
|
|
|
g_return_val_if_fail (GSK_IS_RENDER_NODE_TYPE (node, GSK_TRANSFORM_NODE), NULL);
|
|
|
|
return self->child;
|
|
}
|
|
|
|
const graphene_matrix_t *
|
|
gsk_transform_node_peek_transform (GskRenderNode *node)
|
|
{
|
|
GskTransformNode *self = (GskTransformNode *) node;
|
|
|
|
g_return_val_if_fail (GSK_IS_RENDER_NODE_TYPE (node, GSK_TRANSFORM_NODE), NULL);
|
|
|
|
return &self->transform;
|
|
}
|
|
|
|
/*** GSK_OPACITY_NODE ***/
|
|
|
|
typedef struct _GskOpacityNode GskOpacityNode;
|
|
|
|
struct _GskOpacityNode
|
|
{
|
|
GskRenderNode render_node;
|
|
|
|
GskRenderNode *child;
|
|
double opacity;
|
|
};
|
|
|
|
static void
|
|
gsk_opacity_node_finalize (GskRenderNode *node)
|
|
{
|
|
GskOpacityNode *self = (GskOpacityNode *) node;
|
|
|
|
gsk_render_node_unref (self->child);
|
|
}
|
|
|
|
static void
|
|
gsk_opacity_node_draw (GskRenderNode *node,
|
|
cairo_t *cr)
|
|
{
|
|
GskOpacityNode *self = (GskOpacityNode *) node;
|
|
|
|
cairo_save (cr);
|
|
|
|
/* clip so the push_group() creates a smaller surface */
|
|
cairo_rectangle (cr, node->bounds.origin.x, node->bounds.origin.y,
|
|
node->bounds.size.width, node->bounds.size.height);
|
|
cairo_clip (cr);
|
|
|
|
cairo_push_group (cr);
|
|
|
|
gsk_render_node_draw (self->child, cr);
|
|
|
|
cairo_pop_group_to_source (cr);
|
|
cairo_paint_with_alpha (cr, self->opacity);
|
|
|
|
cairo_restore (cr);
|
|
}
|
|
|
|
#define GSK_OPACITY_NODE_VARIANT_TYPE "(duv)"
|
|
|
|
static GVariant *
|
|
gsk_opacity_node_serialize (GskRenderNode *node)
|
|
{
|
|
GskOpacityNode *self = (GskOpacityNode *) node;
|
|
|
|
return g_variant_new (GSK_OPACITY_NODE_VARIANT_TYPE,
|
|
(double) self->opacity,
|
|
(guint32) gsk_render_node_get_node_type (self->child),
|
|
gsk_render_node_serialize_node (self->child));
|
|
}
|
|
|
|
static GskRenderNode *
|
|
gsk_opacity_node_deserialize (GVariant *variant,
|
|
GError **error)
|
|
{
|
|
double opacity;
|
|
guint32 child_type;
|
|
GVariant *child_variant;
|
|
GskRenderNode *result, *child;
|
|
|
|
if (!check_variant_type (variant, GSK_OPACITY_NODE_VARIANT_TYPE, error))
|
|
return NULL;
|
|
|
|
g_variant_get (variant, GSK_OPACITY_NODE_VARIANT_TYPE,
|
|
&opacity,
|
|
&child_type, &child_variant);
|
|
|
|
child = gsk_render_node_deserialize_node (child_type, child_variant, error);
|
|
g_variant_unref (child_variant);
|
|
|
|
if (child == NULL)
|
|
return NULL;
|
|
|
|
result = gsk_opacity_node_new (child, opacity);
|
|
|
|
gsk_render_node_unref (child);
|
|
|
|
return result;
|
|
}
|
|
|
|
static const GskRenderNodeClass GSK_OPACITY_NODE_CLASS = {
|
|
GSK_OPACITY_NODE,
|
|
sizeof (GskOpacityNode),
|
|
"GskOpacityNode",
|
|
gsk_opacity_node_finalize,
|
|
gsk_opacity_node_draw,
|
|
gsk_opacity_node_serialize,
|
|
gsk_opacity_node_deserialize
|
|
};
|
|
|
|
/**
|
|
* gsk_opacity_node_new:
|
|
* @child: The node to draw
|
|
* @opacity: The opacity to apply
|
|
*
|
|
* Creates a #GskRenderNode that will drawn the @child with reduced
|
|
* @opacity.
|
|
*
|
|
* Returns: A new #GskRenderNode
|
|
*
|
|
* Since: 3.90
|
|
*/
|
|
GskRenderNode *
|
|
gsk_opacity_node_new (GskRenderNode *child,
|
|
double opacity)
|
|
{
|
|
GskOpacityNode *self;
|
|
|
|
g_return_val_if_fail (GSK_IS_RENDER_NODE (child), NULL);
|
|
|
|
self = (GskOpacityNode *) gsk_render_node_new (&GSK_OPACITY_NODE_CLASS, 0);
|
|
|
|
self->child = gsk_render_node_ref (child);
|
|
self->opacity = CLAMP (opacity, 0.0, 1.0);
|
|
|
|
graphene_rect_init_from_rect (&self->render_node.bounds, &child->bounds);
|
|
|
|
return &self->render_node;
|
|
}
|
|
|
|
/**
|
|
* gsk_opacity_node_get_child:
|
|
* @node: a opacity @GskRenderNode
|
|
*
|
|
* Gets the child node that is getting opacityed by the given @node.
|
|
*
|
|
* Returns: (transfer none): The child that is getting opacityed
|
|
**/
|
|
GskRenderNode *
|
|
gsk_opacity_node_get_child (GskRenderNode *node)
|
|
{
|
|
GskOpacityNode *self = (GskOpacityNode *) node;
|
|
|
|
g_return_val_if_fail (GSK_IS_RENDER_NODE_TYPE (node, GSK_OPACITY_NODE), NULL);
|
|
|
|
return self->child;
|
|
}
|
|
|
|
double
|
|
gsk_opacity_node_get_opacity (GskRenderNode *node)
|
|
{
|
|
GskOpacityNode *self = (GskOpacityNode *) node;
|
|
|
|
g_return_val_if_fail (GSK_IS_RENDER_NODE_TYPE (node, GSK_OPACITY_NODE), 1.0);
|
|
|
|
return self->opacity;
|
|
}
|
|
|
|
/*** GSK_COLOR_MATRIX_NODE ***/
|
|
|
|
typedef struct _GskColorMatrixNode GskColorMatrixNode;
|
|
|
|
struct _GskColorMatrixNode
|
|
{
|
|
GskRenderNode render_node;
|
|
|
|
GskRenderNode *child;
|
|
graphene_matrix_t color_matrix;
|
|
graphene_vec4_t color_offset;
|
|
};
|
|
|
|
static void
|
|
gsk_color_matrix_node_finalize (GskRenderNode *node)
|
|
{
|
|
GskColorMatrixNode *self = (GskColorMatrixNode *) node;
|
|
|
|
gsk_render_node_unref (self->child);
|
|
}
|
|
|
|
static void
|
|
gsk_color_matrix_node_draw (GskRenderNode *node,
|
|
cairo_t *cr)
|
|
{
|
|
GskColorMatrixNode *self = (GskColorMatrixNode *) node;
|
|
cairo_pattern_t *pattern;
|
|
cairo_surface_t *surface, *image_surface;
|
|
graphene_vec4_t pixel;
|
|
guint32* pixel_data;
|
|
guchar *data;
|
|
gsize x, y, width, height, stride;
|
|
float alpha;
|
|
|
|
cairo_save (cr);
|
|
|
|
/* clip so the push_group() creates a smaller surface */
|
|
cairo_rectangle (cr, node->bounds.origin.x, node->bounds.origin.y,
|
|
node->bounds.size.width, node->bounds.size.height);
|
|
cairo_clip (cr);
|
|
|
|
cairo_push_group (cr);
|
|
|
|
gsk_render_node_draw (self->child, cr);
|
|
|
|
pattern = cairo_pop_group (cr);
|
|
cairo_pattern_get_surface (pattern, &surface);
|
|
image_surface = cairo_surface_map_to_image (surface, NULL);
|
|
|
|
data = cairo_image_surface_get_data (image_surface);
|
|
width = cairo_image_surface_get_width (image_surface);
|
|
height = cairo_image_surface_get_height (image_surface);
|
|
stride = cairo_image_surface_get_stride (image_surface);
|
|
|
|
for (y = 0; y < height; y++)
|
|
{
|
|
pixel_data = (guint32 *) data;
|
|
for (x = 0; x < width; x++)
|
|
{
|
|
alpha = ((pixel_data[x] >> 24) & 0xFF) / 255.0;
|
|
|
|
if (alpha == 0)
|
|
{
|
|
graphene_vec4_init (&pixel, 0.0, 0.0, 0.0, 0.0);
|
|
}
|
|
else
|
|
{
|
|
graphene_vec4_init (&pixel,
|
|
((pixel_data[x] >> 16) & 0xFF) / (255.0 * alpha),
|
|
((pixel_data[x] >> 8) & 0xFF) / (255.0 * alpha),
|
|
( pixel_data[x] & 0xFF) / (255.0 * alpha),
|
|
alpha);
|
|
graphene_matrix_transform_vec4 (&self->color_matrix, &pixel, &pixel);
|
|
}
|
|
|
|
graphene_vec4_add (&pixel, &self->color_offset, &pixel);
|
|
|
|
alpha = graphene_vec4_get_w (&pixel);
|
|
if (alpha > 0.0)
|
|
{
|
|
alpha = MIN (alpha, 1.0);
|
|
pixel_data[x] = (((guint32) (alpha * 255)) << 24) |
|
|
(((guint32) (CLAMP (graphene_vec4_get_x (&pixel), 0, 1) * alpha * 255)) << 16) |
|
|
(((guint32) (CLAMP (graphene_vec4_get_y (&pixel), 0, 1) * alpha * 255)) << 8) |
|
|
((guint32) (CLAMP (graphene_vec4_get_z (&pixel), 0, 1) * alpha * 255));
|
|
}
|
|
else
|
|
{
|
|
pixel_data[x] = 0;
|
|
}
|
|
}
|
|
data += stride;
|
|
}
|
|
|
|
cairo_surface_mark_dirty (image_surface);
|
|
cairo_surface_unmap_image (surface, image_surface);
|
|
|
|
cairo_set_source (cr, pattern);
|
|
cairo_paint (cr);
|
|
|
|
cairo_restore (cr);
|
|
cairo_pattern_destroy (pattern);
|
|
}
|
|
|
|
#define GSK_COLOR_MATRIX_NODE_VARIANT_TYPE "(dddddddddddddddddddduv)"
|
|
|
|
static GVariant *
|
|
gsk_color_matrix_node_serialize (GskRenderNode *node)
|
|
{
|
|
GskColorMatrixNode *self = (GskColorMatrixNode *) node;
|
|
float mat[16], vec[4];
|
|
|
|
graphene_matrix_to_float (&self->color_matrix, mat);
|
|
graphene_vec4_to_float (&self->color_offset, vec);
|
|
|
|
return g_variant_new (GSK_COLOR_MATRIX_NODE_VARIANT_TYPE,
|
|
(double) mat[0], (double) mat[1], (double) mat[2], (double) mat[3],
|
|
(double) mat[4], (double) mat[5], (double) mat[6], (double) mat[7],
|
|
(double) mat[8], (double) mat[9], (double) mat[10], (double) mat[11],
|
|
(double) mat[12], (double) mat[13], (double) mat[14], (double) mat[15],
|
|
(double) vec[0], (double) vec[1], (double) vec[2], (double) vec[3],
|
|
(guint32) gsk_render_node_get_node_type (self->child),
|
|
gsk_render_node_serialize_node (self->child));
|
|
}
|
|
|
|
static GskRenderNode *
|
|
gsk_color_matrix_node_deserialize (GVariant *variant,
|
|
GError **error)
|
|
{
|
|
double mat[16], vec[4];
|
|
guint32 child_type;
|
|
GVariant *child_variant;
|
|
GskRenderNode *result, *child;
|
|
graphene_matrix_t matrix;
|
|
graphene_vec4_t offset;
|
|
|
|
if (!check_variant_type (variant, GSK_COLOR_MATRIX_NODE_VARIANT_TYPE, error))
|
|
return NULL;
|
|
|
|
g_variant_get (variant, GSK_COLOR_MATRIX_NODE_VARIANT_TYPE,
|
|
&mat[0], &mat[1], &mat[2], &mat[3],
|
|
&mat[4], &mat[5], &mat[6], &mat[7],
|
|
&mat[8], &mat[9], &mat[10], &mat[11],
|
|
&mat[12], &mat[13], &mat[14], &mat[15],
|
|
&vec[0], &vec[1], &vec[2], &vec[3],
|
|
&child_type, &child_variant);
|
|
|
|
child = gsk_render_node_deserialize_node (child_type, child_variant, error);
|
|
g_variant_unref (child_variant);
|
|
|
|
if (child == NULL)
|
|
return NULL;
|
|
|
|
graphene_matrix_init_from_float (&matrix,
|
|
(float[16]) {
|
|
mat[0], mat[1], mat[2], mat[3],
|
|
mat[4], mat[5], mat[6], mat[7],
|
|
mat[8], mat[9], mat[10], mat[11],
|
|
mat[12], mat[13], mat[14], mat[15]
|
|
});
|
|
graphene_vec4_init (&offset, vec[0], vec[1], vec[2], vec[3]);
|
|
|
|
result = gsk_color_matrix_node_new (child, &matrix, &offset);
|
|
|
|
gsk_render_node_unref (child);
|
|
|
|
return result;
|
|
}
|
|
|
|
static const GskRenderNodeClass GSK_COLOR_MATRIX_NODE_CLASS = {
|
|
GSK_COLOR_MATRIX_NODE,
|
|
sizeof (GskColorMatrixNode),
|
|
"GskColorMatrixNode",
|
|
gsk_color_matrix_node_finalize,
|
|
gsk_color_matrix_node_draw,
|
|
gsk_color_matrix_node_serialize,
|
|
gsk_color_matrix_node_deserialize
|
|
};
|
|
|
|
/**
|
|
* gsk_color_matrix_node_new:
|
|
* @child: The node to draw
|
|
* @color_matrix: The matrix to apply
|
|
* @color_offset: Values to add to the color
|
|
*
|
|
* Creates a #GskRenderNode that will drawn the @child with reduced
|
|
* @color_matrix.
|
|
*
|
|
* In particular, the node will transform the operation
|
|
* pixel = color_matrix * pixel + color_offset
|
|
* for every pixel.
|
|
*
|
|
* Returns: A new #GskRenderNode
|
|
*
|
|
* Since: 3.90
|
|
*/
|
|
GskRenderNode *
|
|
gsk_color_matrix_node_new (GskRenderNode *child,
|
|
const graphene_matrix_t *color_matrix,
|
|
const graphene_vec4_t *color_offset)
|
|
{
|
|
GskColorMatrixNode *self;
|
|
|
|
g_return_val_if_fail (GSK_IS_RENDER_NODE (child), NULL);
|
|
|
|
self = (GskColorMatrixNode *) gsk_render_node_new (&GSK_COLOR_MATRIX_NODE_CLASS, 0);
|
|
|
|
self->child = gsk_render_node_ref (child);
|
|
graphene_matrix_init_from_matrix (&self->color_matrix, color_matrix);
|
|
graphene_vec4_init_from_vec4 (&self->color_offset, color_offset);
|
|
|
|
graphene_rect_init_from_rect (&self->render_node.bounds, &child->bounds);
|
|
|
|
return &self->render_node;
|
|
}
|
|
|
|
/**
|
|
* gsk_color_matrix_node_get_child:
|
|
* @node: a color matrix @GskRenderNode
|
|
*
|
|
* Gets the child node that is getting its colors modified by the given @node.
|
|
*
|
|
* Returns: (transfer none): The child that is getting its colors modified
|
|
**/
|
|
GskRenderNode *
|
|
gsk_color_matrix_node_get_child (GskRenderNode *node)
|
|
{
|
|
GskColorMatrixNode *self = (GskColorMatrixNode *) node;
|
|
|
|
g_return_val_if_fail (GSK_IS_RENDER_NODE_TYPE (node, GSK_COLOR_MATRIX_NODE), NULL);
|
|
|
|
return self->child;
|
|
}
|
|
|
|
const graphene_matrix_t *
|
|
gsk_color_matrix_node_peek_color_matrix (GskRenderNode *node)
|
|
{
|
|
GskColorMatrixNode *self = (GskColorMatrixNode *) node;
|
|
|
|
g_return_val_if_fail (GSK_IS_RENDER_NODE_TYPE (node, GSK_COLOR_MATRIX_NODE), NULL);
|
|
|
|
return &self->color_matrix;
|
|
}
|
|
|
|
const graphene_vec4_t *
|
|
gsk_color_matrix_node_peek_color_offset (GskRenderNode *node)
|
|
{
|
|
GskColorMatrixNode *self = (GskColorMatrixNode *) node;
|
|
|
|
g_return_val_if_fail (GSK_IS_RENDER_NODE_TYPE (node, GSK_COLOR_MATRIX_NODE), NULL);
|
|
|
|
return &self->color_offset;
|
|
}
|
|
|
|
/*** GSK_REPEAT_NODE ***/
|
|
|
|
typedef struct _GskRepeatNode GskRepeatNode;
|
|
|
|
struct _GskRepeatNode
|
|
{
|
|
GskRenderNode render_node;
|
|
|
|
GskRenderNode *child;
|
|
graphene_rect_t child_bounds;
|
|
};
|
|
|
|
static void
|
|
gsk_repeat_node_finalize (GskRenderNode *node)
|
|
{
|
|
GskRepeatNode *self = (GskRepeatNode *) node;
|
|
|
|
gsk_render_node_unref (self->child);
|
|
}
|
|
|
|
static void
|
|
gsk_repeat_node_draw (GskRenderNode *node,
|
|
cairo_t *cr)
|
|
{
|
|
GskRepeatNode *self = (GskRepeatNode *) node;
|
|
cairo_pattern_t *pattern;
|
|
cairo_surface_t *surface;
|
|
cairo_t *surface_cr;
|
|
|
|
surface = cairo_surface_create_similar (cairo_get_target (cr),
|
|
CAIRO_CONTENT_COLOR_ALPHA,
|
|
ceilf (self->child_bounds.size.width),
|
|
ceilf (self->child_bounds.size.height));
|
|
surface_cr = cairo_create (surface);
|
|
cairo_translate (surface_cr,
|
|
- self->child_bounds.origin.x,
|
|
- self->child_bounds.origin.y);
|
|
gsk_render_node_draw (self->child, surface_cr);
|
|
cairo_destroy (surface_cr);
|
|
|
|
pattern = cairo_pattern_create_for_surface (surface);
|
|
cairo_pattern_set_extend (pattern, CAIRO_EXTEND_REPEAT);
|
|
cairo_pattern_set_matrix (pattern,
|
|
&(cairo_matrix_t) {
|
|
.xx = 1.0,
|
|
.yy = 1.0,
|
|
.x0 = - self->child_bounds.origin.x,
|
|
.y0 = - self->child_bounds.origin.y
|
|
});
|
|
|
|
cairo_set_source (cr, pattern);
|
|
cairo_paint (cr);
|
|
|
|
cairo_pattern_destroy (pattern);
|
|
cairo_surface_destroy (surface);
|
|
}
|
|
|
|
#define GSK_REPEAT_NODE_VARIANT_TYPE "(dddddddduv)"
|
|
|
|
static GVariant *
|
|
gsk_repeat_node_serialize (GskRenderNode *node)
|
|
{
|
|
GskRepeatNode *self = (GskRepeatNode *) node;
|
|
|
|
return g_variant_new (GSK_REPEAT_NODE_VARIANT_TYPE,
|
|
(double) node->bounds.origin.x, (double) node->bounds.origin.y,
|
|
(double) node->bounds.size.width, (double) node->bounds.size.height,
|
|
(double) self->child_bounds.origin.x, (double) self->child_bounds.origin.y,
|
|
(double) self->child_bounds.size.width, (double) self->child_bounds.size.height,
|
|
(guint32) gsk_render_node_get_node_type (self->child),
|
|
gsk_render_node_serialize_node (self->child));
|
|
}
|
|
|
|
static GskRenderNode *
|
|
gsk_repeat_node_deserialize (GVariant *variant,
|
|
GError **error)
|
|
{
|
|
double x, y, width, height, child_x, child_y, child_width, child_height;
|
|
guint32 child_type;
|
|
GVariant *child_variant;
|
|
GskRenderNode *result, *child;
|
|
|
|
if (!check_variant_type (variant, GSK_REPEAT_NODE_VARIANT_TYPE, error))
|
|
return NULL;
|
|
|
|
g_variant_get (variant, GSK_REPEAT_NODE_VARIANT_TYPE,
|
|
&x, &y, &width, &height,
|
|
&child_x, &child_y, &child_width, &child_height,
|
|
&child_type, &child_variant);
|
|
|
|
child = gsk_render_node_deserialize_node (child_type, child_variant, error);
|
|
g_variant_unref (child_variant);
|
|
|
|
if (child == NULL)
|
|
return NULL;
|
|
|
|
result = gsk_repeat_node_new (&GRAPHENE_RECT_INIT (x, y, width, height),
|
|
child,
|
|
&GRAPHENE_RECT_INIT (child_x, child_y, child_width, child_height));
|
|
|
|
gsk_render_node_unref (child);
|
|
|
|
return result;
|
|
}
|
|
|
|
static const GskRenderNodeClass GSK_REPEAT_NODE_CLASS = {
|
|
GSK_REPEAT_NODE,
|
|
sizeof (GskRepeatNode),
|
|
"GskRepeatNode",
|
|
gsk_repeat_node_finalize,
|
|
gsk_repeat_node_draw,
|
|
gsk_repeat_node_serialize,
|
|
gsk_repeat_node_deserialize
|
|
};
|
|
|
|
/**
|
|
* gsk_repeat_node_new:
|
|
* @bounds: The bounds of the area to be painted
|
|
* @child: The child to repeat
|
|
* @child_bounds: (allow-none): The area of the child to repeat or %NULL to
|
|
* use the child's bounds
|
|
*
|
|
* Creates a #GskRenderNode that will repeat the drawing of @child across
|
|
* the given @bounds.
|
|
*
|
|
* Returns: A new #GskRenderNode
|
|
*
|
|
* Since: 3.90
|
|
*/
|
|
GskRenderNode *
|
|
gsk_repeat_node_new (const graphene_rect_t *bounds,
|
|
GskRenderNode *child,
|
|
const graphene_rect_t *child_bounds)
|
|
{
|
|
GskRepeatNode *self;
|
|
|
|
g_return_val_if_fail (bounds != NULL, NULL);
|
|
g_return_val_if_fail (GSK_IS_RENDER_NODE (child), NULL);
|
|
|
|
self = (GskRepeatNode *) gsk_render_node_new (&GSK_REPEAT_NODE_CLASS, 0);
|
|
|
|
graphene_rect_init_from_rect (&self->render_node.bounds, bounds);
|
|
self->child = gsk_render_node_ref (child);
|
|
if (child_bounds)
|
|
graphene_rect_init_from_rect (&self->child_bounds, child_bounds);
|
|
else
|
|
graphene_rect_init_from_rect (&self->child_bounds, &child->bounds);
|
|
|
|
return &self->render_node;
|
|
}
|
|
|
|
GskRenderNode *
|
|
gsk_repeat_node_get_child (GskRenderNode *node)
|
|
{
|
|
GskRepeatNode *self = (GskRepeatNode *) node;
|
|
|
|
g_return_val_if_fail (GSK_IS_RENDER_NODE_TYPE (node, GSK_REPEAT_NODE), NULL);
|
|
|
|
return self->child;
|
|
}
|
|
|
|
const graphene_rect_t *
|
|
gsk_repeat_node_peek_child_bounds (GskRenderNode *node)
|
|
{
|
|
GskRepeatNode *self = (GskRepeatNode *) node;
|
|
|
|
g_return_val_if_fail (GSK_IS_RENDER_NODE_TYPE (node, GSK_REPEAT_NODE), NULL);
|
|
|
|
return &self->child_bounds;
|
|
}
|
|
|
|
/*** GSK_CLIP_NODE ***/
|
|
|
|
typedef struct _GskClipNode GskClipNode;
|
|
|
|
struct _GskClipNode
|
|
{
|
|
GskRenderNode render_node;
|
|
|
|
GskRenderNode *child;
|
|
graphene_rect_t clip;
|
|
};
|
|
|
|
static void
|
|
gsk_clip_node_finalize (GskRenderNode *node)
|
|
{
|
|
GskClipNode *self = (GskClipNode *) node;
|
|
|
|
gsk_render_node_unref (self->child);
|
|
}
|
|
|
|
static void
|
|
gsk_clip_node_draw (GskRenderNode *node,
|
|
cairo_t *cr)
|
|
{
|
|
GskClipNode *self = (GskClipNode *) node;
|
|
|
|
cairo_save (cr);
|
|
|
|
cairo_rectangle (cr,
|
|
self->clip.origin.x, self->clip.origin.y,
|
|
self->clip.size.width, self->clip.size.height);
|
|
cairo_clip (cr);
|
|
|
|
gsk_render_node_draw (self->child, cr);
|
|
|
|
cairo_restore (cr);
|
|
}
|
|
|
|
#define GSK_CLIP_NODE_VARIANT_TYPE "(dddduv)"
|
|
|
|
static GVariant *
|
|
gsk_clip_node_serialize (GskRenderNode *node)
|
|
{
|
|
GskClipNode *self = (GskClipNode *) node;
|
|
|
|
return g_variant_new (GSK_CLIP_NODE_VARIANT_TYPE,
|
|
(double) node->bounds.origin.x, (double) node->bounds.origin.y,
|
|
(double) node->bounds.size.width, (double) node->bounds.size.height,
|
|
(guint32) gsk_render_node_get_node_type (self->child),
|
|
gsk_render_node_serialize_node (self->child));
|
|
}
|
|
|
|
static GskRenderNode *
|
|
gsk_clip_node_deserialize (GVariant *variant,
|
|
GError **error)
|
|
{
|
|
double x, y, width, height;
|
|
guint32 child_type;
|
|
GVariant *child_variant;
|
|
GskRenderNode *result, *child;
|
|
|
|
if (!check_variant_type (variant, GSK_CLIP_NODE_VARIANT_TYPE, error))
|
|
return NULL;
|
|
|
|
g_variant_get (variant, GSK_CLIP_NODE_VARIANT_TYPE,
|
|
&x, &y, &width, &height,
|
|
&child_type, &child_variant);
|
|
|
|
child = gsk_render_node_deserialize_node (child_type, child_variant, error);
|
|
g_variant_unref (child_variant);
|
|
|
|
if (child == NULL)
|
|
return NULL;
|
|
|
|
result = gsk_clip_node_new (child, &GRAPHENE_RECT_INIT(x, y, width, height));
|
|
|
|
gsk_render_node_unref (child);
|
|
|
|
return result;
|
|
}
|
|
|
|
static const GskRenderNodeClass GSK_CLIP_NODE_CLASS = {
|
|
GSK_CLIP_NODE,
|
|
sizeof (GskClipNode),
|
|
"GskClipNode",
|
|
gsk_clip_node_finalize,
|
|
gsk_clip_node_draw,
|
|
gsk_clip_node_serialize,
|
|
gsk_clip_node_deserialize
|
|
};
|
|
|
|
/**
|
|
* gsk_clip_node_new:
|
|
* @child: The node to draw
|
|
* @clip: The clip to apply
|
|
*
|
|
* Creates a #GskRenderNode that will clip the @child to the area
|
|
* given by @clip.
|
|
*
|
|
* Returns: A new #GskRenderNode
|
|
*
|
|
* Since: 3.90
|
|
*/
|
|
GskRenderNode *
|
|
gsk_clip_node_new (GskRenderNode *child,
|
|
const graphene_rect_t *clip)
|
|
{
|
|
GskClipNode *self;
|
|
|
|
g_return_val_if_fail (GSK_IS_RENDER_NODE (child), NULL);
|
|
g_return_val_if_fail (clip != NULL, NULL);
|
|
|
|
self = (GskClipNode *) gsk_render_node_new (&GSK_CLIP_NODE_CLASS, 0);
|
|
|
|
self->child = gsk_render_node_ref (child);
|
|
graphene_rect_normalize_r (clip, &self->clip);
|
|
|
|
graphene_rect_intersection (&self->clip, &child->bounds, &self->render_node.bounds);
|
|
|
|
return &self->render_node;
|
|
}
|
|
|
|
/**
|
|
* gsk_clip_node_get_child:
|
|
* @node: a clip @GskRenderNode
|
|
*
|
|
* Gets the child node that is getting clipped by the given @node.
|
|
*
|
|
* Returns: (transfer none): The child that is getting clipped
|
|
**/
|
|
GskRenderNode *
|
|
gsk_clip_node_get_child (GskRenderNode *node)
|
|
{
|
|
GskClipNode *self = (GskClipNode *) node;
|
|
|
|
g_return_val_if_fail (GSK_IS_RENDER_NODE_TYPE (node, GSK_CLIP_NODE), NULL);
|
|
|
|
return self->child;
|
|
}
|
|
|
|
const graphene_rect_t *
|
|
gsk_clip_node_peek_clip (GskRenderNode *node)
|
|
{
|
|
GskClipNode *self = (GskClipNode *) node;
|
|
|
|
g_return_val_if_fail (GSK_IS_RENDER_NODE_TYPE (node, GSK_CLIP_NODE), NULL);
|
|
|
|
return &self->clip;
|
|
}
|
|
|
|
/*** GSK_ROUNDED_CLIP_NODE ***/
|
|
|
|
typedef struct _GskRoundedClipNode GskRoundedClipNode;
|
|
|
|
struct _GskRoundedClipNode
|
|
{
|
|
GskRenderNode render_node;
|
|
|
|
GskRenderNode *child;
|
|
GskRoundedRect clip;
|
|
};
|
|
|
|
static void
|
|
gsk_rounded_clip_node_finalize (GskRenderNode *node)
|
|
{
|
|
GskRoundedClipNode *self = (GskRoundedClipNode *) node;
|
|
|
|
gsk_render_node_unref (self->child);
|
|
}
|
|
|
|
static void
|
|
gsk_rounded_clip_node_draw (GskRenderNode *node,
|
|
cairo_t *cr)
|
|
{
|
|
GskRoundedClipNode *self = (GskRoundedClipNode *) node;
|
|
|
|
cairo_save (cr);
|
|
|
|
gsk_rounded_rect_path (&self->clip, cr);
|
|
cairo_clip (cr);
|
|
|
|
gsk_render_node_draw (self->child, cr);
|
|
|
|
cairo_restore (cr);
|
|
}
|
|
|
|
#define GSK_ROUNDED_CLIP_NODE_VARIANT_TYPE "(dddddddddddduv)"
|
|
|
|
static GVariant *
|
|
gsk_rounded_clip_node_serialize (GskRenderNode *node)
|
|
{
|
|
GskRoundedClipNode *self = (GskRoundedClipNode *) node;
|
|
|
|
return g_variant_new (GSK_ROUNDED_CLIP_NODE_VARIANT_TYPE,
|
|
(double) self->clip.bounds.origin.x, (double) self->clip.bounds.origin.y,
|
|
(double) self->clip.bounds.size.width, (double) self->clip.bounds.size.height,
|
|
(double) self->clip.corner[0].width, (double) self->clip.corner[0].height,
|
|
(double) self->clip.corner[1].width, (double) self->clip.corner[1].height,
|
|
(double) self->clip.corner[2].width, (double) self->clip.corner[2].height,
|
|
(double) self->clip.corner[3].width, (double) self->clip.corner[3].height,
|
|
(guint32) gsk_render_node_get_node_type (self->child),
|
|
gsk_render_node_serialize_node (self->child));
|
|
}
|
|
|
|
static GskRenderNode *
|
|
gsk_rounded_clip_node_deserialize (GVariant *variant,
|
|
GError **error)
|
|
{
|
|
double doutline[12];
|
|
guint32 child_type;
|
|
GVariant *child_variant;
|
|
GskRenderNode *child, *result;
|
|
|
|
if (!check_variant_type (variant, GSK_ROUNDED_CLIP_NODE_VARIANT_TYPE, error))
|
|
return NULL;
|
|
|
|
g_variant_get (variant, GSK_ROUNDED_CLIP_NODE_VARIANT_TYPE,
|
|
&doutline[0], &doutline[1], &doutline[2], &doutline[3],
|
|
&doutline[4], &doutline[5], &doutline[6], &doutline[7],
|
|
&doutline[8], &doutline[9], &doutline[10], &doutline[11],
|
|
&child_type, &child_variant);
|
|
|
|
child = gsk_render_node_deserialize_node (child_type, child_variant, error);
|
|
g_variant_unref (child_variant);
|
|
|
|
if (child == NULL)
|
|
return NULL;
|
|
|
|
result = gsk_rounded_clip_node_new (child,
|
|
&(GskRoundedRect) {
|
|
.bounds = GRAPHENE_RECT_INIT(doutline[0], doutline[1], doutline[2], doutline[3]),
|
|
.corner = {
|
|
GRAPHENE_SIZE_INIT (doutline[4], doutline[5]),
|
|
GRAPHENE_SIZE_INIT (doutline[6], doutline[7]),
|
|
GRAPHENE_SIZE_INIT (doutline[8], doutline[9]),
|
|
GRAPHENE_SIZE_INIT (doutline[10], doutline[11])
|
|
}
|
|
});
|
|
|
|
gsk_render_node_unref (child);
|
|
|
|
return result;
|
|
}
|
|
|
|
static const GskRenderNodeClass GSK_ROUNDED_CLIP_NODE_CLASS = {
|
|
GSK_ROUNDED_CLIP_NODE,
|
|
sizeof (GskRoundedClipNode),
|
|
"GskRoundedClipNode",
|
|
gsk_rounded_clip_node_finalize,
|
|
gsk_rounded_clip_node_draw,
|
|
gsk_rounded_clip_node_serialize,
|
|
gsk_rounded_clip_node_deserialize
|
|
};
|
|
|
|
/**
|
|
* gsk_rounded_clip_node_new:
|
|
* @child: The node to draw
|
|
* @clip: The clip to apply
|
|
*
|
|
* Creates a #GskRenderNode that will clip the @child to the area
|
|
* given by @clip.
|
|
*
|
|
* Returns: A new #GskRenderNode
|
|
*
|
|
* Since: 3.90
|
|
*/
|
|
GskRenderNode *
|
|
gsk_rounded_clip_node_new (GskRenderNode *child,
|
|
const GskRoundedRect *clip)
|
|
{
|
|
GskRoundedClipNode *self;
|
|
|
|
g_return_val_if_fail (GSK_IS_RENDER_NODE (child), NULL);
|
|
g_return_val_if_fail (clip != NULL, NULL);
|
|
|
|
self = (GskRoundedClipNode *) gsk_render_node_new (&GSK_ROUNDED_CLIP_NODE_CLASS, 0);
|
|
|
|
self->child = gsk_render_node_ref (child);
|
|
gsk_rounded_rect_init_copy (&self->clip, clip);
|
|
|
|
graphene_rect_intersection (&self->clip.bounds, &child->bounds, &self->render_node.bounds);
|
|
|
|
return &self->render_node;
|
|
}
|
|
|
|
/**
|
|
* gsk_rounded_clip_node_get_child:
|
|
* @node: a clip @GskRenderNode
|
|
*
|
|
* Gets the child node that is getting clipped by the given @node.
|
|
*
|
|
* Returns: (transfer none): The child that is getting clipped
|
|
**/
|
|
GskRenderNode *
|
|
gsk_rounded_clip_node_get_child (GskRenderNode *node)
|
|
{
|
|
GskRoundedClipNode *self = (GskRoundedClipNode *) node;
|
|
|
|
g_return_val_if_fail (GSK_IS_RENDER_NODE_TYPE (node, GSK_ROUNDED_CLIP_NODE), NULL);
|
|
|
|
return self->child;
|
|
}
|
|
|
|
const GskRoundedRect *
|
|
gsk_rounded_clip_node_peek_clip (GskRenderNode *node)
|
|
{
|
|
GskRoundedClipNode *self = (GskRoundedClipNode *) node;
|
|
|
|
g_return_val_if_fail (GSK_IS_RENDER_NODE_TYPE (node, GSK_ROUNDED_CLIP_NODE), NULL);
|
|
|
|
return &self->clip;
|
|
}
|
|
|
|
/*** GSK_SHADOW_NODE ***/
|
|
|
|
typedef struct _GskShadowNode GskShadowNode;
|
|
|
|
struct _GskShadowNode
|
|
{
|
|
GskRenderNode render_node;
|
|
|
|
GskRenderNode *child;
|
|
|
|
gsize n_shadows;
|
|
GskShadow shadows[];
|
|
};
|
|
|
|
static void
|
|
gsk_shadow_node_finalize (GskRenderNode *node)
|
|
{
|
|
GskShadowNode *self = (GskShadowNode *) node;
|
|
|
|
gsk_render_node_unref (self->child);
|
|
}
|
|
|
|
static void
|
|
gsk_shadow_node_draw (GskRenderNode *node,
|
|
cairo_t *cr)
|
|
{
|
|
GskShadowNode *self = (GskShadowNode *) node;
|
|
cairo_pattern_t *pattern;
|
|
gsize i;
|
|
|
|
cairo_push_group (cr);
|
|
gsk_render_node_draw (self->child, cr);
|
|
pattern = cairo_pop_group (cr);
|
|
|
|
for (i = 0; i < self->n_shadows; i++)
|
|
{
|
|
GskShadow *shadow = &self->shadows[i];
|
|
|
|
/* We don't need to draw invisible shadows */
|
|
if (gdk_rgba_is_clear (&shadow->color))
|
|
continue;
|
|
|
|
cairo_save (cr);
|
|
gdk_cairo_set_source_rgba (cr, &shadow->color);
|
|
cr = gsk_cairo_blur_start_drawing (cr, shadow->radius, GSK_BLUR_X | GSK_BLUR_Y);
|
|
|
|
cairo_translate (cr, shadow->dx, shadow->dy);
|
|
cairo_mask (cr, pattern);
|
|
|
|
cr = gsk_cairo_blur_finish_drawing (cr, shadow->radius, &shadow->color, GSK_BLUR_X | GSK_BLUR_Y);
|
|
cairo_restore (cr);
|
|
}
|
|
|
|
cairo_set_source (cr, pattern);
|
|
cairo_paint (cr);
|
|
|
|
cairo_pattern_destroy (pattern);
|
|
}
|
|
|
|
static void
|
|
gsk_shadow_node_get_bounds (GskShadowNode *self,
|
|
graphene_rect_t *bounds)
|
|
{
|
|
float top = 0, right = 0, bottom = 0, left = 0;
|
|
gsize i;
|
|
|
|
graphene_rect_init_from_rect (bounds, &self->child->bounds);
|
|
|
|
for (i = 0; i < self->n_shadows; i++)
|
|
{
|
|
float clip_radius = gsk_cairo_blur_compute_pixels (self->shadows[i].radius);
|
|
top = MAX (top, clip_radius - self->shadows[i].dy);
|
|
right = MAX (right, clip_radius + self->shadows[i].dx);
|
|
bottom = MAX (bottom, clip_radius + self->shadows[i].dy);
|
|
left = MAX (left, clip_radius - self->shadows[i].dx);
|
|
}
|
|
|
|
bounds->origin.x -= left;
|
|
bounds->origin.y -= top;
|
|
bounds->size.width += left + right;
|
|
bounds->size.height += top + bottom;
|
|
}
|
|
|
|
#define GSK_SHADOW_NODE_VARIANT_TYPE "(uva(ddddddd))"
|
|
|
|
static GVariant *
|
|
gsk_shadow_node_serialize (GskRenderNode *node)
|
|
{
|
|
GskShadowNode *self = (GskShadowNode *) node;
|
|
GVariantBuilder builder;
|
|
gsize i;
|
|
|
|
g_variant_builder_init (&builder, G_VARIANT_TYPE ("a(ddddddd)"));
|
|
for (i = 0; i < self->n_shadows; i++)
|
|
{
|
|
g_variant_builder_add (&builder, "(ddddddd)",
|
|
self->shadows[i].color.red, self->shadows[i].color.green,
|
|
self->shadows[i].color.blue, self->shadows[i].color.alpha,
|
|
self->shadows[i].dx, self->shadows[i].dy,
|
|
self->shadows[i].radius);
|
|
}
|
|
|
|
return g_variant_new (GSK_SHADOW_NODE_VARIANT_TYPE,
|
|
(guint32) gsk_render_node_get_node_type (self->child),
|
|
gsk_render_node_serialize_node (self->child),
|
|
&builder);
|
|
}
|
|
|
|
static GskRenderNode *
|
|
gsk_shadow_node_deserialize (GVariant *variant,
|
|
GError **error)
|
|
{
|
|
gsize n_shadows;
|
|
guint32 child_type;
|
|
GVariant *child_variant;
|
|
GskRenderNode *result, *child;
|
|
GVariantIter *iter;
|
|
gsize i;
|
|
|
|
if (!check_variant_type (variant, GSK_SHADOW_NODE_VARIANT_TYPE, error))
|
|
return NULL;
|
|
|
|
g_variant_get (variant, GSK_SHADOW_NODE_VARIANT_TYPE,
|
|
&child_type, &child_variant, &iter);
|
|
|
|
child = gsk_render_node_deserialize_node (child_type, child_variant, error);
|
|
g_variant_unref (child_variant);
|
|
|
|
if (child == NULL)
|
|
{
|
|
g_variant_iter_free (iter);
|
|
return NULL;
|
|
}
|
|
|
|
n_shadows = g_variant_iter_n_children (iter);
|
|
GskShadow *shadows = g_newa (GskShadow, n_shadows);
|
|
for (i = 0; i < n_shadows; i++)
|
|
{
|
|
double dx, dy, radius;
|
|
g_variant_iter_next (iter, "(ddddddd)",
|
|
&shadows[i].color.red, &shadows[i].color.green,
|
|
&shadows[i].color.blue, &shadows[i].color.alpha,
|
|
&dx, &dy, &radius);
|
|
shadows[i].dx = dx;
|
|
shadows[i].dy = dy;
|
|
shadows[i].radius = radius;
|
|
}
|
|
g_variant_iter_free (iter);
|
|
|
|
result = gsk_shadow_node_new (child, shadows, n_shadows);
|
|
|
|
gsk_render_node_unref (child);
|
|
|
|
return result;
|
|
}
|
|
|
|
static const GskRenderNodeClass GSK_SHADOW_NODE_CLASS = {
|
|
GSK_SHADOW_NODE,
|
|
sizeof (GskShadowNode),
|
|
"GskShadowNode",
|
|
gsk_shadow_node_finalize,
|
|
gsk_shadow_node_draw,
|
|
gsk_shadow_node_serialize,
|
|
gsk_shadow_node_deserialize
|
|
};
|
|
|
|
/**
|
|
* gsk_shadow_node_new:
|
|
* @child: The node to draw
|
|
* @shadows: (array length=n_shadows): The shadows to apply
|
|
* @n_shadows: number of entries in the @shadows array
|
|
*
|
|
* Creates a #GskRenderNode that will draw a @child with the given
|
|
* @shadows below it.
|
|
*
|
|
* Returns: A new #GskRenderNode
|
|
*
|
|
* Since: 3.90
|
|
*/
|
|
GskRenderNode *
|
|
gsk_shadow_node_new (GskRenderNode *child,
|
|
const GskShadow *shadows,
|
|
gsize n_shadows)
|
|
{
|
|
GskShadowNode *self;
|
|
|
|
g_return_val_if_fail (GSK_IS_RENDER_NODE (child), NULL);
|
|
g_return_val_if_fail (shadows != NULL, NULL);
|
|
g_return_val_if_fail (n_shadows > 0, NULL);
|
|
|
|
self = (GskShadowNode *) gsk_render_node_new (&GSK_SHADOW_NODE_CLASS, n_shadows * sizeof (GskShadow));
|
|
|
|
self->child = gsk_render_node_ref (child);
|
|
memcpy (&self->shadows, shadows, n_shadows * sizeof (GskShadow));
|
|
self->n_shadows = n_shadows;
|
|
|
|
gsk_shadow_node_get_bounds (self, &self->render_node.bounds);
|
|
|
|
return &self->render_node;
|
|
}
|
|
|
|
GskRenderNode *
|
|
gsk_shadow_node_get_child (GskRenderNode *node)
|
|
{
|
|
GskShadowNode *self = (GskShadowNode *) node;
|
|
|
|
g_return_val_if_fail (GSK_IS_RENDER_NODE_TYPE (node, GSK_SHADOW_NODE), NULL);
|
|
|
|
return self->child;
|
|
}
|
|
|
|
const GskShadow *
|
|
gsk_shadow_node_peek_shadow (GskRenderNode *node,
|
|
gsize i)
|
|
{
|
|
GskShadowNode *self = (GskShadowNode *) node;
|
|
|
|
g_return_val_if_fail (GSK_IS_RENDER_NODE_TYPE (node, GSK_SHADOW_NODE), NULL);
|
|
g_return_val_if_fail (i < self->n_shadows, NULL);
|
|
|
|
return &self->shadows[i];
|
|
}
|
|
|
|
gsize
|
|
gsk_shadow_node_get_n_shadows (GskRenderNode *node)
|
|
{
|
|
GskShadowNode *self = (GskShadowNode *) node;
|
|
|
|
g_return_val_if_fail (GSK_IS_RENDER_NODE_TYPE (node, GSK_SHADOW_NODE), 0);
|
|
|
|
return self->n_shadows;
|
|
}
|
|
|
|
/*** GSK_BLEND_NODE ***/
|
|
|
|
typedef struct _GskBlendNode GskBlendNode;
|
|
|
|
struct _GskBlendNode
|
|
{
|
|
GskRenderNode render_node;
|
|
|
|
GskRenderNode *bottom;
|
|
GskRenderNode *top;
|
|
GskBlendMode blend_mode;
|
|
};
|
|
|
|
static cairo_operator_t
|
|
gsk_blend_mode_to_cairo_operator (GskBlendMode blend_mode)
|
|
{
|
|
switch (blend_mode)
|
|
{
|
|
default:
|
|
g_assert_not_reached ();
|
|
case GSK_BLEND_MODE_DEFAULT:
|
|
return CAIRO_OPERATOR_OVER;
|
|
case GSK_BLEND_MODE_MULTIPLY:
|
|
return CAIRO_OPERATOR_MULTIPLY;
|
|
case GSK_BLEND_MODE_SCREEN:
|
|
return CAIRO_OPERATOR_SCREEN;
|
|
case GSK_BLEND_MODE_OVERLAY:
|
|
return CAIRO_OPERATOR_OVERLAY;
|
|
case GSK_BLEND_MODE_DARKEN:
|
|
return CAIRO_OPERATOR_DARKEN;
|
|
case GSK_BLEND_MODE_LIGHTEN:
|
|
return CAIRO_OPERATOR_LIGHTEN;
|
|
case GSK_BLEND_MODE_COLOR_DODGE:
|
|
return CAIRO_OPERATOR_COLOR_DODGE;
|
|
case GSK_BLEND_MODE_COLOR_BURN:
|
|
return CAIRO_OPERATOR_COLOR_BURN;
|
|
case GSK_BLEND_MODE_HARD_LIGHT:
|
|
return CAIRO_OPERATOR_HARD_LIGHT;
|
|
case GSK_BLEND_MODE_SOFT_LIGHT:
|
|
return CAIRO_OPERATOR_SOFT_LIGHT;
|
|
case GSK_BLEND_MODE_DIFFERENCE:
|
|
return CAIRO_OPERATOR_DIFFERENCE;
|
|
case GSK_BLEND_MODE_EXCLUSION:
|
|
return CAIRO_OPERATOR_EXCLUSION;
|
|
case GSK_BLEND_MODE_COLOR:
|
|
return CAIRO_OPERATOR_HSL_COLOR;
|
|
case GSK_BLEND_MODE_HUE:
|
|
return CAIRO_OPERATOR_HSL_HUE;
|
|
case GSK_BLEND_MODE_SATURATION:
|
|
return CAIRO_OPERATOR_HSL_SATURATION;
|
|
case GSK_BLEND_MODE_LUMINOSITY:
|
|
return CAIRO_OPERATOR_HSL_LUMINOSITY;
|
|
}
|
|
}
|
|
|
|
static void
|
|
gsk_blend_node_finalize (GskRenderNode *node)
|
|
{
|
|
GskBlendNode *self = (GskBlendNode *) node;
|
|
|
|
gsk_render_node_unref (self->bottom);
|
|
gsk_render_node_unref (self->top);
|
|
}
|
|
|
|
static void
|
|
gsk_blend_node_draw (GskRenderNode *node,
|
|
cairo_t *cr)
|
|
{
|
|
GskBlendNode *self = (GskBlendNode *) node;
|
|
|
|
cairo_push_group (cr);
|
|
gsk_render_node_draw (self->bottom, cr);
|
|
|
|
cairo_push_group (cr);
|
|
gsk_render_node_draw (self->top, cr);
|
|
|
|
cairo_pop_group_to_source (cr);
|
|
cairo_set_operator (cr, gsk_blend_mode_to_cairo_operator (self->blend_mode));
|
|
cairo_paint (cr);
|
|
|
|
cairo_pop_group_to_source (cr); /* resets operator */
|
|
cairo_paint (cr);
|
|
}
|
|
|
|
#define GSK_BLEND_NODE_VARIANT_TYPE "(uvuvu)"
|
|
|
|
static GVariant *
|
|
gsk_blend_node_serialize (GskRenderNode *node)
|
|
{
|
|
GskBlendNode *self = (GskBlendNode *) node;
|
|
|
|
return g_variant_new (GSK_BLEND_NODE_VARIANT_TYPE,
|
|
(guint32) gsk_render_node_get_node_type (self->bottom),
|
|
gsk_render_node_serialize_node (self->bottom),
|
|
(guint32) gsk_render_node_get_node_type (self->top),
|
|
gsk_render_node_serialize_node (self->top),
|
|
(guint32) self->blend_mode);
|
|
}
|
|
|
|
static GskRenderNode *
|
|
gsk_blend_node_deserialize (GVariant *variant,
|
|
GError **error)
|
|
{
|
|
guint32 bottom_child_type, top_child_type, blend_mode;
|
|
GVariant *bottom_child_variant, *top_child_variant;
|
|
GskRenderNode *bottom_child, *top_child, *result;
|
|
|
|
if (!check_variant_type (variant, GSK_BLEND_NODE_VARIANT_TYPE, error))
|
|
return NULL;
|
|
|
|
g_variant_get (variant, GSK_BLEND_NODE_VARIANT_TYPE,
|
|
&bottom_child_type, &bottom_child_variant,
|
|
&top_child_type, &top_child_variant,
|
|
&blend_mode);
|
|
|
|
bottom_child = gsk_render_node_deserialize_node (bottom_child_type, bottom_child_variant, error);
|
|
g_variant_unref (bottom_child_variant);
|
|
if (bottom_child == NULL)
|
|
{
|
|
g_variant_unref (top_child_variant);
|
|
return NULL;
|
|
}
|
|
|
|
top_child = gsk_render_node_deserialize_node (top_child_type, top_child_variant, error);
|
|
g_variant_unref (top_child_variant);
|
|
if (top_child == NULL)
|
|
{
|
|
gsk_render_node_unref (bottom_child);
|
|
return NULL;
|
|
}
|
|
|
|
result = gsk_blend_node_new (bottom_child, top_child, blend_mode);
|
|
|
|
gsk_render_node_unref (top_child);
|
|
gsk_render_node_unref (bottom_child);
|
|
|
|
return result;
|
|
}
|
|
|
|
static const GskRenderNodeClass GSK_BLEND_NODE_CLASS = {
|
|
GSK_BLEND_NODE,
|
|
sizeof (GskBlendNode),
|
|
"GskBlendNode",
|
|
gsk_blend_node_finalize,
|
|
gsk_blend_node_draw,
|
|
gsk_blend_node_serialize,
|
|
gsk_blend_node_deserialize
|
|
};
|
|
|
|
/**
|
|
* gsk_blend_node_new:
|
|
* @bottom: The bottom node to be drawn
|
|
* @top: The node to be blended onto the @bottom node
|
|
* @blend_mode: The blend mode to use
|
|
*
|
|
* Creates a #GskRenderNode that will use @blend_mode to blend the @top
|
|
* node onto the @bottom node.
|
|
*
|
|
* Returns: A new #GskRenderNode
|
|
*
|
|
* Since: 3.90
|
|
*/
|
|
GskRenderNode *
|
|
gsk_blend_node_new (GskRenderNode *bottom,
|
|
GskRenderNode *top,
|
|
GskBlendMode blend_mode)
|
|
{
|
|
GskBlendNode *self;
|
|
|
|
g_return_val_if_fail (GSK_IS_RENDER_NODE (bottom), NULL);
|
|
g_return_val_if_fail (GSK_IS_RENDER_NODE (top), NULL);
|
|
|
|
self = (GskBlendNode *) gsk_render_node_new (&GSK_BLEND_NODE_CLASS, 0);
|
|
|
|
self->bottom = gsk_render_node_ref (bottom);
|
|
self->top = gsk_render_node_ref (top);
|
|
self->blend_mode = blend_mode;
|
|
|
|
graphene_rect_union (&bottom->bounds, &top->bounds, &self->render_node.bounds);
|
|
|
|
return &self->render_node;
|
|
}
|
|
|
|
GskRenderNode *
|
|
gsk_blend_node_get_bottom_child (GskRenderNode *node)
|
|
{
|
|
GskBlendNode *self = (GskBlendNode *) node;
|
|
|
|
g_return_val_if_fail (GSK_IS_RENDER_NODE_TYPE (node, GSK_BLEND_NODE), NULL);
|
|
|
|
return self->bottom;
|
|
}
|
|
|
|
GskRenderNode *
|
|
gsk_blend_node_get_top_child (GskRenderNode *node)
|
|
{
|
|
GskBlendNode *self = (GskBlendNode *) node;
|
|
|
|
g_return_val_if_fail (GSK_IS_RENDER_NODE_TYPE (node, GSK_BLEND_NODE), NULL);
|
|
|
|
return self->top;
|
|
}
|
|
|
|
GskBlendMode
|
|
gsk_blend_node_get_blend_mode (GskRenderNode *node)
|
|
{
|
|
GskBlendNode *self = (GskBlendNode *) node;
|
|
|
|
g_return_val_if_fail (GSK_IS_RENDER_NODE_TYPE (node, GSK_BLEND_NODE), GSK_BLEND_MODE_DEFAULT);
|
|
|
|
return self->blend_mode;
|
|
}
|
|
|
|
/*** GSK_CROSS_FADE_NODE ***/
|
|
|
|
typedef struct _GskCrossFadeNode GskCrossFadeNode;
|
|
|
|
struct _GskCrossFadeNode
|
|
{
|
|
GskRenderNode render_node;
|
|
|
|
GskRenderNode *start;
|
|
GskRenderNode *end;
|
|
double progress;
|
|
};
|
|
|
|
static void
|
|
gsk_cross_fade_node_finalize (GskRenderNode *node)
|
|
{
|
|
GskCrossFadeNode *self = (GskCrossFadeNode *) node;
|
|
|
|
gsk_render_node_unref (self->start);
|
|
gsk_render_node_unref (self->end);
|
|
}
|
|
|
|
static void
|
|
gsk_cross_fade_node_draw (GskRenderNode *node,
|
|
cairo_t *cr)
|
|
{
|
|
GskCrossFadeNode *self = (GskCrossFadeNode *) node;
|
|
|
|
cairo_push_group (cr);
|
|
gsk_render_node_draw (self->start, cr);
|
|
|
|
cairo_push_group (cr);
|
|
gsk_render_node_draw (self->end, cr);
|
|
|
|
cairo_pop_group_to_source (cr);
|
|
cairo_set_operator (cr, CAIRO_OPERATOR_SOURCE);
|
|
cairo_paint_with_alpha (cr, self->progress);
|
|
|
|
cairo_pop_group_to_source (cr); /* resets operator */
|
|
cairo_paint (cr);
|
|
}
|
|
|
|
#define GSK_CROSS_FADE_NODE_VARIANT_TYPE "(uvuvd)"
|
|
|
|
static GVariant *
|
|
gsk_cross_fade_node_serialize (GskRenderNode *node)
|
|
{
|
|
GskCrossFadeNode *self = (GskCrossFadeNode *) node;
|
|
|
|
return g_variant_new (GSK_CROSS_FADE_NODE_VARIANT_TYPE,
|
|
(guint32) gsk_render_node_get_node_type (self->start),
|
|
gsk_render_node_serialize_node (self->start),
|
|
(guint32) gsk_render_node_get_node_type (self->end),
|
|
gsk_render_node_serialize_node (self->end),
|
|
(double) self->progress);
|
|
}
|
|
|
|
static GskRenderNode *
|
|
gsk_cross_fade_node_deserialize (GVariant *variant,
|
|
GError **error)
|
|
{
|
|
guint32 start_child_type, end_child_type;
|
|
GVariant *start_child_variant, *end_child_variant;
|
|
GskRenderNode *start_child, *end_child, *result;
|
|
double progress;
|
|
|
|
if (!check_variant_type (variant, GSK_CROSS_FADE_NODE_VARIANT_TYPE, error))
|
|
return NULL;
|
|
|
|
g_variant_get (variant, GSK_CROSS_FADE_NODE_VARIANT_TYPE,
|
|
&start_child_type, &start_child_variant,
|
|
&end_child_type, &end_child_variant,
|
|
&progress);
|
|
|
|
start_child = gsk_render_node_deserialize_node (start_child_type, start_child_variant, error);
|
|
g_variant_unref (start_child_variant);
|
|
if (start_child == NULL)
|
|
{
|
|
g_variant_unref (end_child_variant);
|
|
return NULL;
|
|
}
|
|
|
|
end_child = gsk_render_node_deserialize_node (end_child_type, end_child_variant, error);
|
|
g_variant_unref (end_child_variant);
|
|
if (end_child == NULL)
|
|
{
|
|
gsk_render_node_unref (start_child);
|
|
return NULL;
|
|
}
|
|
|
|
result = gsk_cross_fade_node_new (start_child, end_child, progress);
|
|
|
|
gsk_render_node_unref (end_child);
|
|
gsk_render_node_unref (start_child);
|
|
|
|
return result;
|
|
}
|
|
|
|
static const GskRenderNodeClass GSK_CROSS_FADE_NODE_CLASS = {
|
|
GSK_CROSS_FADE_NODE,
|
|
sizeof (GskCrossFadeNode),
|
|
"GskCrossFadeNode",
|
|
gsk_cross_fade_node_finalize,
|
|
gsk_cross_fade_node_draw,
|
|
gsk_cross_fade_node_serialize,
|
|
gsk_cross_fade_node_deserialize
|
|
};
|
|
|
|
/**
|
|
* gsk_cross_fade_node_new:
|
|
* @start: The start node to be drawn
|
|
* @end: The node to be cross_fadeed onto the @start node
|
|
* @progress: How far the fade has progressed from start to end. The value will
|
|
* be clamped to the range [0 ... 1]
|
|
*
|
|
* Creates a #GskRenderNode that will do a cross-fade between @start and @end.
|
|
*
|
|
* Returns: A new #GskRenderNode
|
|
*
|
|
* Since: 3.90
|
|
*/
|
|
GskRenderNode *
|
|
gsk_cross_fade_node_new (GskRenderNode *start,
|
|
GskRenderNode *end,
|
|
double progress)
|
|
{
|
|
GskCrossFadeNode *self;
|
|
|
|
g_return_val_if_fail (GSK_IS_RENDER_NODE (start), NULL);
|
|
g_return_val_if_fail (GSK_IS_RENDER_NODE (end), NULL);
|
|
|
|
self = (GskCrossFadeNode *) gsk_render_node_new (&GSK_CROSS_FADE_NODE_CLASS, 0);
|
|
|
|
self->start = gsk_render_node_ref (start);
|
|
self->end = gsk_render_node_ref (end);
|
|
self->progress = CLAMP (progress, 0.0, 1.0);
|
|
|
|
graphene_rect_union (&start->bounds, &end->bounds, &self->render_node.bounds);
|
|
|
|
return &self->render_node;
|
|
}
|
|
|
|
GskRenderNode *
|
|
gsk_cross_fade_node_get_start_child (GskRenderNode *node)
|
|
{
|
|
GskCrossFadeNode *self = (GskCrossFadeNode *) node;
|
|
|
|
g_return_val_if_fail (GSK_IS_RENDER_NODE_TYPE (node, GSK_CROSS_FADE_NODE), NULL);
|
|
|
|
return self->start;
|
|
}
|
|
|
|
GskRenderNode *
|
|
gsk_cross_fade_node_get_end_child (GskRenderNode *node)
|
|
{
|
|
GskCrossFadeNode *self = (GskCrossFadeNode *) node;
|
|
|
|
g_return_val_if_fail (GSK_IS_RENDER_NODE_TYPE (node, GSK_CROSS_FADE_NODE), NULL);
|
|
|
|
return self->end;
|
|
}
|
|
|
|
double
|
|
gsk_cross_fade_node_get_progress (GskRenderNode *node)
|
|
{
|
|
GskCrossFadeNode *self = (GskCrossFadeNode *) node;
|
|
|
|
g_return_val_if_fail (GSK_IS_RENDER_NODE_TYPE (node, GSK_CROSS_FADE_NODE), 0.0);
|
|
|
|
return self->progress;
|
|
}
|
|
|
|
/*** GSK_TEXT_NODE ***/
|
|
|
|
typedef struct _GskTextNode GskTextNode;
|
|
|
|
struct _GskTextNode
|
|
{
|
|
GskRenderNode render_node;
|
|
|
|
PangoFont *font;
|
|
|
|
GdkRGBA color;
|
|
double x;
|
|
double y;
|
|
|
|
guint num_glyphs;
|
|
PangoGlyphInfo glyphs[];
|
|
};
|
|
|
|
static void
|
|
gsk_text_node_finalize (GskRenderNode *node)
|
|
{
|
|
GskTextNode *self = (GskTextNode *) node;
|
|
|
|
g_object_unref (self->font);
|
|
}
|
|
|
|
#ifndef STACK_BUFFER_SIZE
|
|
#define STACK_BUFFER_SIZE (512 * sizeof (int))
|
|
#endif
|
|
|
|
#define STACK_ARRAY_LENGTH(T) (STACK_BUFFER_SIZE / sizeof(T))
|
|
|
|
static void
|
|
gsk_text_node_draw (GskRenderNode *node,
|
|
cairo_t *cr)
|
|
{
|
|
GskTextNode *self = (GskTextNode *) node;
|
|
PangoGlyphString glyphs;
|
|
|
|
glyphs.num_glyphs = self->num_glyphs;
|
|
glyphs.glyphs = self->glyphs;
|
|
glyphs.log_clusters = NULL;
|
|
|
|
cairo_save (cr);
|
|
|
|
gdk_cairo_set_source_rgba (cr, &self->color);
|
|
cairo_translate (cr, self->x, self->y);
|
|
pango_cairo_show_glyph_string (cr, self->font, &glyphs);
|
|
|
|
cairo_restore (cr);
|
|
}
|
|
|
|
#define GSK_TEXT_NODE_VARIANT_TYPE "(sdddddda(uiiii))"
|
|
|
|
static GVariant *
|
|
gsk_text_node_serialize (GskRenderNode *node)
|
|
{
|
|
GskTextNode *self = (GskTextNode *) node;
|
|
GVariant *v;
|
|
GVariantBuilder builder;
|
|
int i;
|
|
PangoFontDescription *desc;
|
|
char *s;
|
|
|
|
desc = pango_font_describe (self->font);
|
|
s = pango_font_description_to_string (desc);
|
|
|
|
g_variant_builder_init (&builder, G_VARIANT_TYPE ("a(uiiii)"));
|
|
for (i = 0; i < self->num_glyphs; i++)
|
|
{
|
|
PangoGlyphInfo *glyph = &self->glyphs[i];
|
|
g_variant_builder_add (&builder, "(uiiii)",
|
|
glyph->glyph,
|
|
glyph->geometry.width,
|
|
glyph->geometry.x_offset,
|
|
glyph->geometry.y_offset,
|
|
glyph->attr.is_cluster_start);
|
|
}
|
|
|
|
v = g_variant_new (GSK_TEXT_NODE_VARIANT_TYPE,
|
|
s,
|
|
self->color.red,
|
|
self->color.green,
|
|
self->color.blue,
|
|
self->color.alpha,
|
|
self->x,
|
|
self->y,
|
|
&builder);
|
|
|
|
g_free (s);
|
|
pango_font_description_free (desc);
|
|
|
|
return v;
|
|
}
|
|
|
|
static GskRenderNode *
|
|
gsk_text_node_deserialize (GVariant *variant,
|
|
GError **error)
|
|
{
|
|
PangoFont *font;
|
|
PangoGlyphString *glyphs;
|
|
GVariantIter *iter;
|
|
GskRenderNode *result;
|
|
PangoGlyphInfo glyph;
|
|
PangoFontDescription *desc;
|
|
PangoFontMap *fontmap;
|
|
PangoContext *context;
|
|
int cluster_start;
|
|
char *s;
|
|
GdkRGBA color;
|
|
double x, y;
|
|
int i;
|
|
|
|
if (!check_variant_type (variant, GSK_TEXT_NODE_VARIANT_TYPE, error))
|
|
return NULL;
|
|
|
|
g_variant_get (variant, "(&sdddddda(uiiii))",
|
|
&s, &color.red, &color.green, &color.blue, &color.alpha,
|
|
&x, &y, &iter);
|
|
|
|
desc = pango_font_description_from_string (s);
|
|
fontmap = pango_cairo_font_map_get_default ();
|
|
context = pango_font_map_create_context (fontmap);
|
|
font = pango_font_map_load_font (fontmap, context, desc);
|
|
|
|
glyphs = pango_glyph_string_new ();
|
|
pango_glyph_string_set_size (glyphs, g_variant_iter_n_children (iter));
|
|
i = 0;
|
|
while (g_variant_iter_next (iter, "(uiiii)",
|
|
&glyph.glyph, &glyph.geometry.width,
|
|
&glyph.geometry.x_offset, &glyph.geometry.y_offset,
|
|
&cluster_start))
|
|
{
|
|
glyph.attr.is_cluster_start = cluster_start;
|
|
glyphs->glyphs[i] = glyph;
|
|
i++;
|
|
}
|
|
g_variant_iter_free (iter);
|
|
|
|
result = gsk_text_node_new (font, glyphs, &color, x, y);
|
|
|
|
pango_glyph_string_free (glyphs);
|
|
pango_font_description_free (desc);
|
|
g_object_unref (context);
|
|
g_object_unref (font);
|
|
|
|
return result;
|
|
}
|
|
|
|
static const GskRenderNodeClass GSK_TEXT_NODE_CLASS = {
|
|
GSK_TEXT_NODE,
|
|
sizeof (GskTextNode),
|
|
"GskTextNode",
|
|
gsk_text_node_finalize,
|
|
gsk_text_node_draw,
|
|
gsk_text_node_serialize,
|
|
gsk_text_node_deserialize
|
|
};
|
|
|
|
/**
|
|
* gsk_text_node_new:
|
|
* @font: the #PangoFont containing the glyphs
|
|
* @glyphs: the #PangoGlyphString to render
|
|
* @color: the foreground color to render with
|
|
* @x: the x coordinate at which to put the baseline
|
|
* @y: the y coordinate at wihch to put the baseline
|
|
*
|
|
* Creates a render node that renders the given glyphs,
|
|
* Note that @color may not be used if the font contains
|
|
* color glyphs.
|
|
*
|
|
* Returns: a new text node, or %NULL
|
|
*
|
|
* Since: 3.92
|
|
*/
|
|
GskRenderNode *
|
|
gsk_text_node_new (PangoFont *font,
|
|
PangoGlyphString *glyphs,
|
|
const GdkRGBA *color,
|
|
double x,
|
|
double y)
|
|
{
|
|
GskTextNode *self;
|
|
PangoRectangle ink_rect;
|
|
|
|
pango_glyph_string_extents (glyphs, font, &ink_rect, NULL);
|
|
pango_extents_to_pixels (&ink_rect, NULL);
|
|
|
|
/* Don't create nodes with empty bounds */
|
|
if (ink_rect.width == 0 || ink_rect.height == 0)
|
|
return NULL;
|
|
|
|
self = (GskTextNode *) gsk_render_node_new (&GSK_TEXT_NODE_CLASS, sizeof (PangoGlyphInfo) * glyphs->num_glyphs);
|
|
|
|
self->font = g_object_ref (font);
|
|
self->color = *color;
|
|
self->x = x;
|
|
self->y = y;
|
|
self->num_glyphs = glyphs->num_glyphs;
|
|
memcpy (self->glyphs, glyphs->glyphs, sizeof (PangoGlyphInfo) * glyphs->num_glyphs);
|
|
|
|
graphene_rect_init (&self->render_node.bounds,
|
|
x,
|
|
y + ink_rect.y,
|
|
ink_rect.x + ink_rect.width,
|
|
ink_rect.height);
|
|
|
|
return &self->render_node;
|
|
}
|
|
|
|
const GdkRGBA *
|
|
gsk_text_node_peek_color (GskRenderNode *node)
|
|
{
|
|
GskTextNode *self = (GskTextNode *) node;
|
|
|
|
g_return_val_if_fail (GSK_IS_RENDER_NODE_TYPE (node, GSK_TEXT_NODE), NULL);
|
|
|
|
return &self->color;
|
|
}
|
|
|
|
const PangoFont *
|
|
gsk_text_node_peek_font (GskRenderNode *node)
|
|
{
|
|
GskTextNode *self = (GskTextNode *) node;
|
|
|
|
g_return_val_if_fail (GSK_IS_RENDER_NODE_TYPE (node, GSK_TEXT_NODE), NULL);
|
|
|
|
return self->font;
|
|
}
|
|
|
|
guint
|
|
gsk_text_node_get_num_glyphs (GskRenderNode *node)
|
|
{
|
|
GskTextNode *self = (GskTextNode *) node;
|
|
|
|
g_return_val_if_fail (GSK_IS_RENDER_NODE_TYPE (node, GSK_TEXT_NODE), 0);
|
|
|
|
return self->num_glyphs;
|
|
}
|
|
|
|
const PangoGlyphInfo *
|
|
gsk_text_node_peek_glyphs (GskRenderNode *node)
|
|
{
|
|
GskTextNode *self = (GskTextNode *) node;
|
|
|
|
g_return_val_if_fail (GSK_IS_RENDER_NODE_TYPE (node, GSK_TEXT_NODE), NULL);
|
|
|
|
return self->glyphs;
|
|
}
|
|
|
|
float
|
|
gsk_text_node_get_x (GskRenderNode *node)
|
|
{
|
|
GskTextNode *self = (GskTextNode *) node;
|
|
|
|
g_return_val_if_fail (GSK_IS_RENDER_NODE_TYPE (node, GSK_TEXT_NODE), 0.0);
|
|
|
|
return (float)self->x;
|
|
}
|
|
|
|
float
|
|
gsk_text_node_get_y (GskRenderNode *node)
|
|
{
|
|
GskTextNode *self = (GskTextNode *) node;
|
|
|
|
g_return_val_if_fail (GSK_IS_RENDER_NODE_TYPE (node, GSK_TEXT_NODE), 0.0);
|
|
|
|
return (float)self->y;
|
|
}
|
|
|
|
/*** GSK_BLUR_NODE ***/
|
|
|
|
typedef struct _GskBlurNode GskBlurNode;
|
|
|
|
struct _GskBlurNode
|
|
{
|
|
GskRenderNode render_node;
|
|
|
|
GskRenderNode *child;
|
|
double radius;
|
|
};
|
|
|
|
static void
|
|
gsk_blur_node_finalize (GskRenderNode *node)
|
|
{
|
|
GskBlurNode *self = (GskBlurNode *) node;
|
|
|
|
gsk_render_node_unref (self->child);
|
|
}
|
|
|
|
static void
|
|
blur_once (cairo_surface_t *src,
|
|
cairo_surface_t *dest,
|
|
int radius,
|
|
guchar *div_kernel_size)
|
|
{
|
|
int width, height, src_rowstride, dest_rowstride, n_channels;
|
|
guchar *p_src, *p_dest, *c1, *c2;
|
|
gint x, y, i, i1, i2, width_minus_1, height_minus_1, radius_plus_1;
|
|
gint r, g, b, a;
|
|
guchar *p_dest_row, *p_dest_col;
|
|
|
|
width = cairo_image_surface_get_width (src);
|
|
height = cairo_image_surface_get_height (src);
|
|
n_channels = 4;
|
|
radius_plus_1 = radius + 1;
|
|
|
|
/* horizontal blur */
|
|
p_src = cairo_image_surface_get_data (src);
|
|
p_dest = cairo_image_surface_get_data (dest);
|
|
src_rowstride = cairo_image_surface_get_stride (src);
|
|
dest_rowstride = cairo_image_surface_get_stride (dest);
|
|
|
|
width_minus_1 = width - 1;
|
|
for (y = 0; y < height; y++)
|
|
{
|
|
/* calc the initial sums of the kernel */
|
|
r = g = b = a = 0;
|
|
for (i = -radius; i <= radius; i++)
|
|
{
|
|
c1 = p_src + (CLAMP (i, 0, width_minus_1) * n_channels);
|
|
r += c1[0];
|
|
g += c1[1];
|
|
b += c1[2];
|
|
}
|
|
p_dest_row = p_dest;
|
|
for (x = 0; x < width; x++)
|
|
{
|
|
/* set as the mean of the kernel */
|
|
p_dest_row[0] = div_kernel_size[r];
|
|
p_dest_row[1] = div_kernel_size[g];
|
|
p_dest_row[2] = div_kernel_size[b];
|
|
p_dest_row += n_channels;
|
|
|
|
/* the pixel to add to the kernel */
|
|
i1 = x + radius_plus_1;
|
|
if (i1 > width_minus_1)
|
|
i1 = width_minus_1;
|
|
c1 = p_src + (i1 * n_channels);
|
|
|
|
/* the pixel to remove from the kernel */
|
|
i2 = x - radius;
|
|
if (i2 < 0)
|
|
i2 = 0;
|
|
c2 = p_src + (i2 * n_channels);
|
|
|
|
/* calc the new sums of the kernel */
|
|
r += c1[0] - c2[0];
|
|
g += c1[1] - c2[1];
|
|
b += c1[2] - c2[2];
|
|
}
|
|
|
|
p_src += src_rowstride;
|
|
p_dest += dest_rowstride;
|
|
}
|
|
|
|
/* vertical blur */
|
|
p_src = cairo_image_surface_get_data (dest);
|
|
p_dest = cairo_image_surface_get_data (src);
|
|
src_rowstride = cairo_image_surface_get_stride (dest);
|
|
dest_rowstride = cairo_image_surface_get_stride (src);
|
|
|
|
height_minus_1 = height - 1;
|
|
for (x = 0; x < width; x++)
|
|
{
|
|
/* calc the initial sums of the kernel */
|
|
r = g = b = a = 0;
|
|
for (i = -radius; i <= radius; i++)
|
|
{
|
|
c1 = p_src + (CLAMP (i, 0, height_minus_1) * src_rowstride);
|
|
r += c1[0];
|
|
g += c1[1];
|
|
b += c1[2];
|
|
}
|
|
|
|
p_dest_col = p_dest;
|
|
for (y = 0; y < height; y++)
|
|
{
|
|
/* set as the mean of the kernel */
|
|
|
|
p_dest_col[0] = div_kernel_size[r];
|
|
p_dest_col[1] = div_kernel_size[g];
|
|
p_dest_col[2] = div_kernel_size[b];
|
|
p_dest_col += dest_rowstride;
|
|
|
|
/* the pixel to add to the kernel */
|
|
i1 = y + radius_plus_1;
|
|
if (i1 > height_minus_1)
|
|
i1 = height_minus_1;
|
|
c1 = p_src + (i1 * src_rowstride);
|
|
|
|
/* the pixel to remove from the kernel */
|
|
i2 = y - radius;
|
|
if (i2 < 0)
|
|
i2 = 0;
|
|
c2 = p_src + (i2 * src_rowstride);
|
|
/* calc the new sums of the kernel */
|
|
r += c1[0] - c2[0];
|
|
g += c1[1] - c2[1];
|
|
b += c1[2] - c2[2];
|
|
}
|
|
|
|
p_src += n_channels;
|
|
p_dest += n_channels;
|
|
}
|
|
}
|
|
|
|
static void
|
|
blur_image_surface (cairo_surface_t *surface, int radius, int iterations)
|
|
{
|
|
int kernel_size;
|
|
int i;
|
|
guchar *div_kernel_size;
|
|
cairo_surface_t *tmp;
|
|
int width, height;
|
|
|
|
width = cairo_image_surface_get_width (surface);
|
|
height = cairo_image_surface_get_height (surface);
|
|
tmp = cairo_image_surface_create (CAIRO_FORMAT_ARGB32, width, height);
|
|
|
|
kernel_size = 2 * radius + 1;
|
|
div_kernel_size = g_new (guchar, 256 * kernel_size);
|
|
for (i = 0; i < 256 * kernel_size; i++)
|
|
div_kernel_size[i] = (guchar) (i / kernel_size);
|
|
|
|
while (iterations-- > 0)
|
|
blur_once (surface, tmp, radius, div_kernel_size);
|
|
|
|
g_free (div_kernel_size);
|
|
cairo_surface_destroy (tmp);
|
|
}
|
|
|
|
static void
|
|
gsk_blur_node_draw (GskRenderNode *node,
|
|
cairo_t *cr)
|
|
{
|
|
GskBlurNode *self = (GskBlurNode *) node;
|
|
cairo_pattern_t *pattern;
|
|
cairo_surface_t *surface;
|
|
cairo_surface_t *image_surface;
|
|
|
|
cairo_save (cr);
|
|
|
|
/* clip so the push_group() creates a smaller surface */
|
|
cairo_rectangle (cr, node->bounds.origin.x, node->bounds.origin.y,
|
|
node->bounds.size.width, node->bounds.size.height);
|
|
cairo_clip (cr);
|
|
|
|
cairo_push_group (cr);
|
|
|
|
gsk_render_node_draw (self->child, cr);
|
|
|
|
pattern = cairo_pop_group (cr);
|
|
cairo_pattern_get_surface (pattern, &surface);
|
|
image_surface = cairo_surface_map_to_image (surface, NULL);
|
|
blur_image_surface (image_surface, (int)self->radius, 3);
|
|
cairo_surface_mark_dirty (surface);
|
|
cairo_surface_unmap_image (surface, image_surface);
|
|
|
|
cairo_set_source (cr, pattern);
|
|
cairo_paint (cr);
|
|
|
|
cairo_restore (cr);
|
|
cairo_pattern_destroy (pattern);
|
|
}
|
|
|
|
#define GSK_BLUR_NODE_VARIANT_TYPE "(duv)"
|
|
|
|
static GVariant *
|
|
gsk_blur_node_serialize (GskRenderNode *node)
|
|
{
|
|
GskBlurNode *self = (GskBlurNode *) node;
|
|
|
|
return g_variant_new (GSK_BLUR_NODE_VARIANT_TYPE,
|
|
(double) self->radius,
|
|
(guint32) gsk_render_node_get_node_type (self->child),
|
|
gsk_render_node_serialize (self->child));
|
|
}
|
|
|
|
static GskRenderNode *
|
|
gsk_blur_node_deserialize (GVariant *variant,
|
|
GError **error)
|
|
{
|
|
double radius;
|
|
guint32 child_type;
|
|
GVariant *child_variant;
|
|
GskRenderNode *result, *child;
|
|
|
|
g_variant_get (variant, GSK_BLUR_NODE_VARIANT_TYPE,
|
|
&radius, &child_type, &child_variant);
|
|
|
|
child = gsk_render_node_deserialize_node (child_type, child_variant, error);
|
|
g_variant_unref (child_variant);
|
|
|
|
if (child == NULL)
|
|
return NULL;
|
|
|
|
result = gsk_blur_node_new (child, radius);
|
|
|
|
gsk_render_node_unref (child);
|
|
|
|
return result;
|
|
}
|
|
|
|
static const GskRenderNodeClass GSK_BLUR_NODE_CLASS = {
|
|
GSK_BLUR_NODE,
|
|
sizeof (GskBlurNode),
|
|
"GskBlurNode",
|
|
gsk_blur_node_finalize,
|
|
gsk_blur_node_draw,
|
|
gsk_blur_node_serialize,
|
|
gsk_blur_node_deserialize
|
|
};
|
|
|
|
/**
|
|
* gsk_blur_node_new:
|
|
* @child: the child node to blur
|
|
* @radius: the blur radius
|
|
*
|
|
* Creates a render node that blurs the child.
|
|
*
|
|
* Since: 3.92
|
|
*/
|
|
GskRenderNode *
|
|
gsk_blur_node_new (GskRenderNode *child,
|
|
double radius)
|
|
{
|
|
GskBlurNode *self;
|
|
|
|
g_return_val_if_fail (GSK_IS_RENDER_NODE (child), NULL);
|
|
|
|
self = (GskBlurNode *) gsk_render_node_new (&GSK_BLUR_NODE_CLASS, 0);
|
|
|
|
self->child = gsk_render_node_ref (child);
|
|
self->radius = radius;
|
|
|
|
graphene_rect_init_from_rect (&self->render_node.bounds, &child->bounds);
|
|
|
|
return &self->render_node;
|
|
}
|
|
|
|
GskRenderNode *
|
|
gsk_blur_node_get_child (GskRenderNode *node)
|
|
{
|
|
GskBlurNode *self = (GskBlurNode *) node;
|
|
|
|
g_return_val_if_fail (GSK_IS_RENDER_NODE_TYPE (node, GSK_BLUR_NODE), NULL);
|
|
|
|
return self->child;
|
|
}
|
|
|
|
double
|
|
gsk_blur_node_get_radius (GskRenderNode *node)
|
|
{
|
|
GskBlurNode *self = (GskBlurNode *) node;
|
|
|
|
g_return_val_if_fail (GSK_IS_RENDER_NODE_TYPE (node, GSK_BLUR_NODE), 0.0);
|
|
|
|
return self->radius;
|
|
}
|
|
|
|
static const GskRenderNodeClass *klasses[] = {
|
|
[GSK_CONTAINER_NODE] = &GSK_CONTAINER_NODE_CLASS,
|
|
[GSK_CAIRO_NODE] = &GSK_CAIRO_NODE_CLASS,
|
|
[GSK_COLOR_NODE] = &GSK_COLOR_NODE_CLASS,
|
|
[GSK_LINEAR_GRADIENT_NODE] = &GSK_LINEAR_GRADIENT_NODE_CLASS,
|
|
[GSK_REPEATING_LINEAR_GRADIENT_NODE] = &GSK_REPEATING_LINEAR_GRADIENT_NODE_CLASS,
|
|
[GSK_BORDER_NODE] = &GSK_BORDER_NODE_CLASS,
|
|
[GSK_TEXTURE_NODE] = &GSK_TEXTURE_NODE_CLASS,
|
|
[GSK_INSET_SHADOW_NODE] = &GSK_INSET_SHADOW_NODE_CLASS,
|
|
[GSK_OUTSET_SHADOW_NODE] = &GSK_OUTSET_SHADOW_NODE_CLASS,
|
|
[GSK_TRANSFORM_NODE] = &GSK_TRANSFORM_NODE_CLASS,
|
|
[GSK_OPACITY_NODE] = &GSK_OPACITY_NODE_CLASS,
|
|
[GSK_COLOR_MATRIX_NODE] = &GSK_COLOR_MATRIX_NODE_CLASS,
|
|
[GSK_REPEAT_NODE] = &GSK_REPEAT_NODE_CLASS,
|
|
[GSK_CLIP_NODE] = &GSK_CLIP_NODE_CLASS,
|
|
[GSK_ROUNDED_CLIP_NODE] = &GSK_ROUNDED_CLIP_NODE_CLASS,
|
|
[GSK_SHADOW_NODE] = &GSK_SHADOW_NODE_CLASS,
|
|
[GSK_BLEND_NODE] = &GSK_BLEND_NODE_CLASS,
|
|
[GSK_CROSS_FADE_NODE] = &GSK_CROSS_FADE_NODE_CLASS,
|
|
[GSK_TEXT_NODE] = &GSK_TEXT_NODE_CLASS,
|
|
[GSK_BLUR_NODE] = &GSK_BLUR_NODE_CLASS
|
|
};
|
|
|
|
GskRenderNode *
|
|
gsk_render_node_deserialize_node (GskRenderNodeType type,
|
|
GVariant *variant,
|
|
GError **error)
|
|
{
|
|
const GskRenderNodeClass *klass;
|
|
GskRenderNode *result;
|
|
|
|
if (type < G_N_ELEMENTS (klasses))
|
|
klass = klasses[type];
|
|
else
|
|
klass = NULL;
|
|
|
|
if (klass == NULL)
|
|
{
|
|
g_set_error (error, GSK_SERIALIZATION_ERROR, GSK_SERIALIZATION_INVALID_DATA,
|
|
"Type %u is not a valid render node type", type);
|
|
return NULL;
|
|
}
|
|
|
|
result = klass->deserialize (variant, error);
|
|
|
|
return result;
|
|
}
|
|
|
|
GVariant *
|
|
gsk_render_node_serialize_node (GskRenderNode *node)
|
|
{
|
|
return node->node_class->serialize (node);
|
|
}
|
|
|