37f53a42da
The shadow pipeline is created doing software blurring of the texture so the shadow spec blur parameter should be scaled accordingly with the texture scaling otherwise we won't take enough pixels in account creating stronger shadows. https://bugzilla.gnome.org/show_bug.cgi?id=765011 https://gitlab.gnome.org/GNOME/gnome-shell/merge_requests/5
734 lines
24 KiB
C
734 lines
24 KiB
C
/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */
|
|
/*
|
|
* st-private.h: Private declarations and functions
|
|
*
|
|
* Copyright 2009, 2010 Red Hat, Inc.
|
|
* Copyright 2010 Florian Müllner
|
|
* Copyright 2010 Intel Corporation
|
|
* Copyright 2010 Giovanni Campagna
|
|
*
|
|
* This program is free software; you can redistribute it and/or modify it
|
|
* under the terms and conditions of the GNU Lesser General Public License,
|
|
* version 2.1, as published by the Free Software Foundation.
|
|
*
|
|
* This program is distributed in the hope 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 program. If not, see <http://www.gnu.org/licenses/>.
|
|
*/
|
|
#include <math.h>
|
|
#include <string.h>
|
|
|
|
#include "st-private.h"
|
|
|
|
/**
|
|
* _st_actor_get_preferred_width:
|
|
* @actor: a #ClutterActor
|
|
* @for_height: as with clutter_actor_get_preferred_width()
|
|
* @y_fill: %TRUE if @actor will fill its allocation vertically
|
|
* @min_width_p: as with clutter_actor_get_preferred_width()
|
|
* @natural_width_p: as with clutter_actor_get_preferred_width()
|
|
*
|
|
* Like clutter_actor_get_preferred_width(), but if @y_fill is %FALSE,
|
|
* then it will compute a width request based on the assumption that
|
|
* @actor will be given an allocation no taller than its natural
|
|
* height.
|
|
*/
|
|
void
|
|
_st_actor_get_preferred_width (ClutterActor *actor,
|
|
gfloat for_height,
|
|
gboolean y_fill,
|
|
gfloat *min_width_p,
|
|
gfloat *natural_width_p)
|
|
{
|
|
if (!y_fill && for_height != -1)
|
|
{
|
|
ClutterRequestMode mode;
|
|
gfloat natural_height;
|
|
|
|
mode = clutter_actor_get_request_mode (actor);
|
|
if (mode == CLUTTER_REQUEST_WIDTH_FOR_HEIGHT)
|
|
{
|
|
clutter_actor_get_preferred_height (actor, -1, NULL, &natural_height);
|
|
if (for_height > natural_height)
|
|
for_height = natural_height;
|
|
}
|
|
}
|
|
|
|
clutter_actor_get_preferred_width (actor, for_height, min_width_p, natural_width_p);
|
|
}
|
|
|
|
/**
|
|
* _st_actor_get_preferred_height:
|
|
* @actor: a #ClutterActor
|
|
* @for_width: as with clutter_actor_get_preferred_height()
|
|
* @x_fill: %TRUE if @actor will fill its allocation horizontally
|
|
* @min_height_p: as with clutter_actor_get_preferred_height()
|
|
* @natural_height_p: as with clutter_actor_get_preferred_height()
|
|
*
|
|
* Like clutter_actor_get_preferred_height(), but if @x_fill is
|
|
* %FALSE, then it will compute a height request based on the
|
|
* assumption that @actor will be given an allocation no wider than
|
|
* its natural width.
|
|
*/
|
|
void
|
|
_st_actor_get_preferred_height (ClutterActor *actor,
|
|
gfloat for_width,
|
|
gboolean x_fill,
|
|
gfloat *min_height_p,
|
|
gfloat *natural_height_p)
|
|
{
|
|
if (!x_fill && for_width != -1)
|
|
{
|
|
ClutterRequestMode mode;
|
|
gfloat natural_width;
|
|
|
|
mode = clutter_actor_get_request_mode (actor);
|
|
if (mode == CLUTTER_REQUEST_HEIGHT_FOR_WIDTH)
|
|
{
|
|
clutter_actor_get_preferred_width (actor, -1, NULL, &natural_width);
|
|
if (for_width > natural_width)
|
|
for_width = natural_width;
|
|
}
|
|
}
|
|
|
|
clutter_actor_get_preferred_height (actor, for_width, min_height_p, natural_height_p);
|
|
}
|
|
|
|
/**
|
|
* _st_set_text_from_style:
|
|
* @text: Target #ClutterText
|
|
* @theme_node: Source #StThemeNode
|
|
*
|
|
* Set various GObject properties of the @text object using
|
|
* CSS information from @theme_node.
|
|
*/
|
|
void
|
|
_st_set_text_from_style (ClutterText *text,
|
|
StThemeNode *theme_node)
|
|
{
|
|
|
|
ClutterColor color;
|
|
StTextDecoration decoration;
|
|
PangoAttrList *attribs = NULL;
|
|
const PangoFontDescription *font;
|
|
StTextAlign align;
|
|
gdouble spacing;
|
|
gchar *font_features;
|
|
|
|
st_theme_node_get_foreground_color (theme_node, &color);
|
|
clutter_text_set_color (text, &color);
|
|
|
|
font = st_theme_node_get_font (theme_node);
|
|
clutter_text_set_font_description (text, (PangoFontDescription *) font);
|
|
|
|
attribs = pango_attr_list_new ();
|
|
|
|
decoration = st_theme_node_get_text_decoration (theme_node);
|
|
if (decoration)
|
|
{
|
|
if (decoration & ST_TEXT_DECORATION_UNDERLINE)
|
|
{
|
|
PangoAttribute *underline = pango_attr_underline_new (PANGO_UNDERLINE_SINGLE);
|
|
pango_attr_list_insert (attribs, underline);
|
|
}
|
|
if (decoration & ST_TEXT_DECORATION_LINE_THROUGH)
|
|
{
|
|
PangoAttribute *strikethrough = pango_attr_strikethrough_new (TRUE);
|
|
pango_attr_list_insert (attribs, strikethrough);
|
|
}
|
|
/* Pango doesn't have an equivalent attribute for _OVERLINE, and we deliberately
|
|
* skip BLINK (for now...)
|
|
*/
|
|
}
|
|
|
|
spacing = st_theme_node_get_letter_spacing (theme_node);
|
|
if (spacing)
|
|
{
|
|
PangoAttribute *letter_spacing = pango_attr_letter_spacing_new ((int)(.5 + spacing) * PANGO_SCALE);
|
|
pango_attr_list_insert (attribs, letter_spacing);
|
|
}
|
|
|
|
font_features = st_theme_node_get_font_features (theme_node);
|
|
if (font_features)
|
|
{
|
|
pango_attr_list_insert (attribs, pango_attr_font_features_new (font_features));
|
|
g_free (font_features);
|
|
}
|
|
|
|
clutter_text_set_attributes (text, attribs);
|
|
|
|
if (attribs)
|
|
pango_attr_list_unref (attribs);
|
|
|
|
align = st_theme_node_get_text_align (theme_node);
|
|
if (align == ST_TEXT_ALIGN_JUSTIFY)
|
|
{
|
|
clutter_text_set_justify (text, TRUE);
|
|
clutter_text_set_line_alignment (text, PANGO_ALIGN_LEFT);
|
|
}
|
|
else
|
|
{
|
|
clutter_text_set_justify (text, FALSE);
|
|
clutter_text_set_line_alignment (text, (PangoAlignment) align);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* _st_create_texture_pipeline:
|
|
* @src_texture: The CoglTexture for the pipeline
|
|
*
|
|
* Creates a simple pipeline which contains the given texture as a
|
|
* single layer.
|
|
*/
|
|
CoglPipeline *
|
|
_st_create_texture_pipeline (CoglTexture *src_texture)
|
|
{
|
|
static CoglPipeline *texture_pipeline_template = NULL;
|
|
CoglPipeline *pipeline;
|
|
|
|
g_return_val_if_fail (src_texture != NULL, NULL);
|
|
|
|
/* The only state used in the pipeline that would affect the shader
|
|
generation is the texture type on the layer. Therefore we create
|
|
a template pipeline which sets this state and all texture
|
|
pipelines are created as a copy of this. That way Cogl can find
|
|
the shader state for the pipeline more quickly by looking at the
|
|
pipeline ancestry instead of resorting to the shader cache. */
|
|
if (G_UNLIKELY (texture_pipeline_template == NULL))
|
|
{
|
|
CoglContext *ctx =
|
|
clutter_backend_get_cogl_context (clutter_get_default_backend ());
|
|
|
|
texture_pipeline_template = cogl_pipeline_new (ctx);
|
|
cogl_pipeline_set_layer_null_texture (texture_pipeline_template,
|
|
0, /* layer */
|
|
COGL_TEXTURE_TYPE_2D);
|
|
}
|
|
|
|
pipeline = cogl_pipeline_copy (texture_pipeline_template);
|
|
|
|
if (src_texture != NULL)
|
|
cogl_pipeline_set_layer_texture (pipeline, 0, src_texture);
|
|
|
|
return pipeline;
|
|
}
|
|
|
|
/*****
|
|
* Shadows
|
|
*****/
|
|
|
|
static gdouble *
|
|
calculate_gaussian_kernel (gdouble sigma,
|
|
guint n_values)
|
|
{
|
|
gdouble *ret, sum;
|
|
gdouble exp_divisor;
|
|
int half, i;
|
|
|
|
g_return_val_if_fail (sigma > 0, NULL);
|
|
|
|
half = n_values / 2;
|
|
|
|
ret = g_malloc (n_values * sizeof (gdouble));
|
|
sum = 0.0;
|
|
|
|
exp_divisor = 2 * sigma * sigma;
|
|
|
|
/* n_values of 1D Gauss function */
|
|
for (i = 0; i < (int)n_values; i++)
|
|
{
|
|
ret[i] = exp (-(i - half) * (i - half) / exp_divisor);
|
|
sum += ret[i];
|
|
}
|
|
|
|
/* normalize */
|
|
for (i = 0; i < (int)n_values; i++)
|
|
ret[i] /= sum;
|
|
|
|
return ret;
|
|
}
|
|
|
|
static guchar *
|
|
blur_pixels (guchar *pixels_in,
|
|
gint width_in,
|
|
gint height_in,
|
|
gint rowstride_in,
|
|
gdouble blur,
|
|
gint *width_out,
|
|
gint *height_out,
|
|
gint *rowstride_out)
|
|
{
|
|
guchar *pixels_out;
|
|
gdouble sigma;
|
|
|
|
/* The CSS specification defines (or will define) the blur radius as twice
|
|
* the Gaussian standard deviation. See:
|
|
*
|
|
* http://lists.w3.org/Archives/Public/www-style/2010Sep/0002.html
|
|
*/
|
|
sigma = blur / 2.;
|
|
|
|
if ((guint) blur == 0)
|
|
{
|
|
*width_out = width_in;
|
|
*height_out = height_in;
|
|
*rowstride_out = rowstride_in;
|
|
pixels_out = g_memdup (pixels_in, *rowstride_out * *height_out);
|
|
}
|
|
else
|
|
{
|
|
gdouble *kernel;
|
|
guchar *line;
|
|
gint n_values, half;
|
|
gint x_in, y_in, x_out, y_out, i;
|
|
|
|
n_values = (gint) 5 * sigma;
|
|
half = n_values / 2;
|
|
|
|
*width_out = width_in + 2 * half;
|
|
*height_out = height_in + 2 * half;
|
|
*rowstride_out = (*width_out + 3) & ~3;
|
|
|
|
pixels_out = g_malloc0 (*rowstride_out * *height_out);
|
|
line = g_malloc0 (*rowstride_out);
|
|
|
|
kernel = calculate_gaussian_kernel (sigma, n_values);
|
|
|
|
/* vertical blur */
|
|
for (x_in = 0; x_in < width_in; x_in++)
|
|
for (y_out = 0; y_out < *height_out; y_out++)
|
|
{
|
|
guchar *pixel_in, *pixel_out;
|
|
gint i0, i1;
|
|
|
|
y_in = y_out - half;
|
|
|
|
/* We read from the source at 'y = y_in + i - half'; clamp the
|
|
* full i range [0, n_values) so that y is in [0, height_in).
|
|
*/
|
|
i0 = MAX (half - y_in, 0);
|
|
i1 = MIN (height_in + half - y_in, n_values);
|
|
|
|
pixel_in = pixels_in + (y_in + i0 - half) * rowstride_in + x_in;
|
|
pixel_out = pixels_out + y_out * *rowstride_out + (x_in + half);
|
|
|
|
for (i = i0; i < i1; i++)
|
|
{
|
|
*pixel_out += *pixel_in * kernel[i];
|
|
pixel_in += rowstride_in;
|
|
}
|
|
}
|
|
|
|
/* horizontal blur */
|
|
for (y_out = 0; y_out < *height_out; y_out++)
|
|
{
|
|
memcpy (line, pixels_out + y_out * *rowstride_out, *rowstride_out);
|
|
|
|
for (x_out = 0; x_out < *width_out; x_out++)
|
|
{
|
|
gint i0, i1;
|
|
guchar *pixel_out, *pixel_in;
|
|
|
|
/* We read from the source at 'x = x_out + i - half'; clamp the
|
|
* full i range [0, n_values) so that x is in [0, width_out).
|
|
*/
|
|
i0 = MAX (half - x_out, 0);
|
|
i1 = MIN (*width_out + half - x_out, n_values);
|
|
|
|
pixel_in = line + x_out + i0 - half;
|
|
pixel_out = pixels_out + *rowstride_out * y_out + x_out;
|
|
|
|
*pixel_out = 0;
|
|
for (i = i0; i < i1; i++)
|
|
{
|
|
*pixel_out += *pixel_in * kernel[i];
|
|
pixel_in++;
|
|
}
|
|
}
|
|
}
|
|
g_free (kernel);
|
|
g_free (line);
|
|
}
|
|
|
|
return pixels_out;
|
|
}
|
|
|
|
CoglPipeline *
|
|
_st_create_shadow_pipeline (StShadow *shadow_spec,
|
|
CoglTexture *src_texture,
|
|
float resource_scale)
|
|
{
|
|
ClutterBackend *backend = clutter_get_default_backend ();
|
|
CoglContext *ctx = clutter_backend_get_cogl_context (backend);
|
|
CoglError *error = NULL;
|
|
|
|
static CoglPipeline *shadow_pipeline_template = NULL;
|
|
|
|
CoglPipeline *pipeline;
|
|
CoglTexture *texture;
|
|
guchar *pixels_in, *pixels_out;
|
|
gint width_in, height_in, rowstride_in;
|
|
gint width_out, height_out, rowstride_out;
|
|
|
|
g_return_val_if_fail (shadow_spec != NULL, NULL);
|
|
g_return_val_if_fail (src_texture != NULL, NULL);
|
|
|
|
width_in = cogl_texture_get_width (src_texture);
|
|
height_in = cogl_texture_get_height (src_texture);
|
|
rowstride_in = (width_in + 3) & ~3;
|
|
|
|
pixels_in = g_malloc0 (rowstride_in * height_in);
|
|
|
|
cogl_texture_get_data (src_texture, COGL_PIXEL_FORMAT_A_8,
|
|
rowstride_in, pixels_in);
|
|
|
|
pixels_out = blur_pixels (pixels_in, width_in, height_in, rowstride_in,
|
|
shadow_spec->blur * resource_scale,
|
|
&width_out, &height_out, &rowstride_out);
|
|
g_free (pixels_in);
|
|
|
|
texture = COGL_TEXTURE (cogl_texture_2d_new_from_data (ctx, width_out, height_out,
|
|
COGL_PIXEL_FORMAT_A_8,
|
|
rowstride_out,
|
|
pixels_out,
|
|
&error));
|
|
|
|
if (error)
|
|
{
|
|
g_warning ("Failed to allocate texture: %s", error->message);
|
|
cogl_error_free (error);
|
|
}
|
|
|
|
g_free (pixels_out);
|
|
|
|
if (G_UNLIKELY (shadow_pipeline_template == NULL))
|
|
{
|
|
shadow_pipeline_template = cogl_pipeline_new (ctx);
|
|
|
|
/* We set up the pipeline to blend the shadow texture with the combine
|
|
* constant, but defer setting the latter until painting, so that we can
|
|
* take the actor's overall opacity into account. */
|
|
cogl_pipeline_set_layer_combine (shadow_pipeline_template, 0,
|
|
"RGBA = MODULATE (CONSTANT, TEXTURE[A])",
|
|
NULL);
|
|
}
|
|
|
|
pipeline = cogl_pipeline_copy (shadow_pipeline_template);
|
|
cogl_pipeline_set_layer_texture (pipeline, 0, texture);
|
|
|
|
if (texture)
|
|
cogl_object_unref (texture);
|
|
|
|
return pipeline;
|
|
}
|
|
|
|
CoglPipeline *
|
|
_st_create_shadow_pipeline_from_actor (StShadow *shadow_spec,
|
|
ClutterActor *actor)
|
|
{
|
|
ClutterContent *image = NULL;
|
|
CoglPipeline *shadow_pipeline = NULL;
|
|
float resource_scale;
|
|
float width, height;
|
|
|
|
g_return_val_if_fail (clutter_actor_has_allocation (actor), NULL);
|
|
|
|
clutter_actor_get_size (actor, &width, &height);
|
|
|
|
if (width == 0 || height == 0)
|
|
return NULL;
|
|
|
|
if (!clutter_actor_get_resource_scale (actor, &resource_scale))
|
|
return NULL;
|
|
|
|
width = ceilf (width * resource_scale);
|
|
height = ceilf (height * resource_scale);
|
|
|
|
image = clutter_actor_get_content (actor);
|
|
if (image && CLUTTER_IS_IMAGE (image))
|
|
{
|
|
CoglTexture *texture;
|
|
|
|
texture = clutter_image_get_texture (CLUTTER_IMAGE (image));
|
|
if (texture &&
|
|
cogl_texture_get_width (texture) == width &&
|
|
cogl_texture_get_height (texture) == height)
|
|
shadow_pipeline = _st_create_shadow_pipeline (shadow_spec, texture,
|
|
resource_scale);
|
|
}
|
|
|
|
if (shadow_pipeline == NULL)
|
|
{
|
|
CoglTexture *buffer;
|
|
CoglOffscreen *offscreen;
|
|
CoglFramebuffer *fb;
|
|
CoglContext *ctx;
|
|
CoglColor clear_color;
|
|
CoglError *catch_error = NULL;
|
|
float x, y;
|
|
|
|
ctx = clutter_backend_get_cogl_context (clutter_get_default_backend ());
|
|
buffer = cogl_texture_2d_new_with_size (ctx, width, height);
|
|
|
|
if (buffer == NULL)
|
|
return NULL;
|
|
|
|
offscreen = cogl_offscreen_new_with_texture (buffer);
|
|
fb = COGL_FRAMEBUFFER (offscreen);
|
|
|
|
if (!cogl_framebuffer_allocate (fb, &catch_error))
|
|
{
|
|
cogl_error_free (catch_error);
|
|
cogl_object_unref (offscreen);
|
|
cogl_object_unref (buffer);
|
|
return NULL;
|
|
}
|
|
|
|
cogl_color_init_from_4ub (&clear_color, 0, 0, 0, 0);
|
|
clutter_actor_get_position (actor, &x, &y);
|
|
|
|
/* XXX: There's no way to render a ClutterActor to an offscreen
|
|
* as it uses the implicit API. */
|
|
G_GNUC_BEGIN_IGNORE_DEPRECATIONS;
|
|
cogl_push_framebuffer (fb);
|
|
G_GNUC_END_IGNORE_DEPRECATIONS;
|
|
|
|
cogl_framebuffer_clear (fb, COGL_BUFFER_BIT_COLOR, &clear_color);
|
|
cogl_framebuffer_translate (fb, -x, -y, 0);
|
|
cogl_framebuffer_orthographic (fb, 0, 0, width, height, 0, 1.0);
|
|
cogl_framebuffer_scale (fb, resource_scale, resource_scale, 1);
|
|
|
|
clutter_actor_set_opacity_override (actor, 255);
|
|
clutter_actor_paint (actor);
|
|
clutter_actor_set_opacity_override (actor, -1);
|
|
|
|
G_GNUC_BEGIN_IGNORE_DEPRECATIONS;
|
|
cogl_pop_framebuffer ();
|
|
G_GNUC_END_IGNORE_DEPRECATIONS;
|
|
|
|
cogl_object_unref (fb);
|
|
|
|
shadow_pipeline = _st_create_shadow_pipeline (shadow_spec, buffer,
|
|
resource_scale);
|
|
|
|
cogl_object_unref (buffer);
|
|
}
|
|
|
|
return shadow_pipeline;
|
|
}
|
|
|
|
/**
|
|
* _st_create_shadow_cairo_pattern:
|
|
* @shadow_spec: the definition of the shadow
|
|
* @src_pattern: surface pattern for which we create the shadow
|
|
* (must be a surface pattern)
|
|
*
|
|
* This is a utility function for creating shadows used by
|
|
* st-theme-node.c; it's in this file to share the gaussian
|
|
* blur implementation. The usage of this function is quite different
|
|
* depending on whether shadow_spec->inset is %TRUE or not. If
|
|
* shadow_spec->inset is %TRUE, the caller should pass in a @src_pattern
|
|
* which is the <i>inverse</i> of what they want shadowed, and must take
|
|
* care of the spread and offset from the shadow spec themselves. If
|
|
* shadow_spec->inset is %FALSE then the caller should pass in what they
|
|
* want shadowed directly, and this function takes care of the spread and
|
|
* the offset.
|
|
*/
|
|
cairo_pattern_t *
|
|
_st_create_shadow_cairo_pattern (StShadow *shadow_spec_in,
|
|
cairo_pattern_t *src_pattern)
|
|
{
|
|
g_autoptr(StShadow) shadow_spec = NULL;
|
|
static cairo_user_data_key_t shadow_pattern_user_data;
|
|
cairo_t *cr;
|
|
cairo_surface_t *src_surface;
|
|
cairo_surface_t *surface_in;
|
|
cairo_surface_t *surface_out;
|
|
cairo_pattern_t *dst_pattern;
|
|
guchar *pixels_in, *pixels_out;
|
|
gint width_in, height_in, rowstride_in;
|
|
gint width_out, height_out, rowstride_out;
|
|
cairo_matrix_t shadow_matrix;
|
|
double xscale_in, yscale_in;
|
|
int i, j;
|
|
|
|
g_return_val_if_fail (shadow_spec_in != NULL, NULL);
|
|
g_return_val_if_fail (src_pattern != NULL, NULL);
|
|
|
|
if (cairo_pattern_get_surface (src_pattern, &src_surface) != CAIRO_STATUS_SUCCESS)
|
|
/* The most likely reason we can't get the pattern is that sizing went hairwire
|
|
* and the caller tried to create a surface too big for memory, leaving us with
|
|
* a pattern in an error state; we return a transparent pattern for the shadow.
|
|
*/
|
|
return cairo_pattern_create_rgba(1.0, 1.0, 1.0, 0.0);
|
|
|
|
width_in = cairo_image_surface_get_width (src_surface);
|
|
height_in = cairo_image_surface_get_height (src_surface);
|
|
|
|
cairo_surface_get_device_scale (src_surface, &xscale_in, &yscale_in);
|
|
|
|
if (xscale_in != 1.0 || yscale_in != 1.0)
|
|
{
|
|
/* Scale the shadow specifications in a temporary copy so that
|
|
* we can work everywhere in absolute surface coordinates */
|
|
double scale = (xscale_in + yscale_in) / 2.0;
|
|
shadow_spec = st_shadow_new (&shadow_spec_in->color,
|
|
shadow_spec_in->xoffset * xscale_in,
|
|
shadow_spec_in->yoffset * yscale_in,
|
|
shadow_spec_in->blur * scale,
|
|
shadow_spec_in->spread * scale,
|
|
shadow_spec_in->inset);
|
|
}
|
|
else
|
|
{
|
|
shadow_spec = st_shadow_ref (shadow_spec_in);
|
|
}
|
|
|
|
/* We want the output to be a color agnostic alpha mask,
|
|
* so we need to strip the color channels from the input
|
|
*/
|
|
if (cairo_image_surface_get_format (src_surface) != CAIRO_FORMAT_A8)
|
|
{
|
|
surface_in = cairo_image_surface_create (CAIRO_FORMAT_A8,
|
|
width_in, height_in);
|
|
|
|
cr = cairo_create (surface_in);
|
|
cairo_set_source_surface (cr, src_surface, 0, 0);
|
|
cairo_paint (cr);
|
|
cairo_destroy (cr);
|
|
}
|
|
else
|
|
{
|
|
surface_in = cairo_surface_reference (src_surface);
|
|
}
|
|
|
|
pixels_in = cairo_image_surface_get_data (surface_in);
|
|
rowstride_in = cairo_image_surface_get_stride (surface_in);
|
|
|
|
pixels_out = blur_pixels (pixels_in, width_in, height_in, rowstride_in,
|
|
shadow_spec->blur,
|
|
&width_out, &height_out, &rowstride_out);
|
|
cairo_surface_destroy (surface_in);
|
|
|
|
/* Invert pixels for inset shadows */
|
|
if (shadow_spec->inset)
|
|
{
|
|
for (j = 0; j < height_out; j++)
|
|
{
|
|
guchar *p = pixels_out + rowstride_out * j;
|
|
for (i = 0; i < width_out; i++, p++)
|
|
*p = ~*p;
|
|
}
|
|
}
|
|
|
|
surface_out = cairo_image_surface_create_for_data (pixels_out,
|
|
CAIRO_FORMAT_A8,
|
|
width_out,
|
|
height_out,
|
|
rowstride_out);
|
|
cairo_surface_set_device_scale (surface_out, xscale_in, yscale_in);
|
|
cairo_surface_set_user_data (surface_out, &shadow_pattern_user_data,
|
|
pixels_out, (cairo_destroy_func_t) g_free);
|
|
|
|
dst_pattern = cairo_pattern_create_for_surface (surface_out);
|
|
cairo_surface_destroy (surface_out);
|
|
|
|
cairo_pattern_get_matrix (src_pattern, &shadow_matrix);
|
|
|
|
if (shadow_spec->inset)
|
|
{
|
|
/* Scale the matrix in surface absolute coordinates */
|
|
cairo_matrix_scale (&shadow_matrix, 1.0 / xscale_in, 1.0 / yscale_in);
|
|
|
|
/* For inset shadows, offsets and spread radius have already been
|
|
* applied to the original pattern, so all left to do is shift the
|
|
* blurred image left, so that it aligns centered under the
|
|
* unblurred one
|
|
*/
|
|
cairo_matrix_translate (&shadow_matrix,
|
|
(width_out - width_in) / 2.0,
|
|
(height_out - height_in) / 2.0);
|
|
|
|
/* Scale back the matrix in original coordinates */
|
|
cairo_matrix_scale (&shadow_matrix, xscale_in, yscale_in);
|
|
|
|
cairo_pattern_set_matrix (dst_pattern, &shadow_matrix);
|
|
return dst_pattern;
|
|
}
|
|
|
|
/* Read all the code from the cairo_pattern_set_matrix call
|
|
* at the end of this function to here from bottom to top,
|
|
* because each new affine transformation is applied in
|
|
* front of all the previous ones */
|
|
|
|
/* 6. Invert the matrix back */
|
|
cairo_matrix_invert (&shadow_matrix);
|
|
|
|
/* Scale the matrix in surface absolute coordinates */
|
|
cairo_matrix_scale (&shadow_matrix, 1.0 / xscale_in, 1.0 / yscale_in);
|
|
|
|
/* 5. Adjust based on specified offsets */
|
|
cairo_matrix_translate (&shadow_matrix,
|
|
shadow_spec->xoffset,
|
|
shadow_spec->yoffset);
|
|
|
|
/* 4. Recenter the newly scaled image */
|
|
cairo_matrix_translate (&shadow_matrix,
|
|
- shadow_spec->spread,
|
|
- shadow_spec->spread);
|
|
|
|
/* 3. Scale up the blurred image to fill the spread */
|
|
cairo_matrix_scale (&shadow_matrix,
|
|
(width_in + 2.0 * shadow_spec->spread) / width_in,
|
|
(height_in + 2.0 * shadow_spec->spread) / height_in);
|
|
|
|
/* 2. Shift the blurred image left, so that it aligns centered
|
|
* under the unblurred one */
|
|
cairo_matrix_translate (&shadow_matrix,
|
|
- (width_out - width_in) / 2.0,
|
|
- (height_out - height_in) / 2.0);
|
|
|
|
/* Scale back the matrix in scaled coordinates */
|
|
cairo_matrix_scale (&shadow_matrix, xscale_in, yscale_in);
|
|
|
|
/* 1. Invert the matrix so we can work with it in pattern space
|
|
*/
|
|
cairo_matrix_invert (&shadow_matrix);
|
|
|
|
cairo_pattern_set_matrix (dst_pattern, &shadow_matrix);
|
|
|
|
return dst_pattern;
|
|
}
|
|
|
|
void
|
|
_st_paint_shadow_with_opacity (StShadow *shadow_spec,
|
|
CoglFramebuffer *framebuffer,
|
|
CoglPipeline *shadow_pipeline,
|
|
ClutterActorBox *box,
|
|
guint8 paint_opacity)
|
|
{
|
|
ClutterActorBox shadow_box;
|
|
CoglColor color;
|
|
|
|
g_return_if_fail (shadow_spec != NULL);
|
|
g_return_if_fail (shadow_pipeline != NULL);
|
|
|
|
st_shadow_get_box (shadow_spec, box, &shadow_box);
|
|
|
|
cogl_color_init_from_4ub (&color,
|
|
shadow_spec->color.red * paint_opacity / 255,
|
|
shadow_spec->color.green * paint_opacity / 255,
|
|
shadow_spec->color.blue * paint_opacity / 255,
|
|
shadow_spec->color.alpha * paint_opacity / 255);
|
|
cogl_color_premultiply (&color);
|
|
cogl_pipeline_set_layer_combine_constant (shadow_pipeline, 0, &color);
|
|
cogl_framebuffer_draw_rectangle (framebuffer,
|
|
shadow_pipeline,
|
|
shadow_box.x1, shadow_box.y1,
|
|
shadow_box.x2, shadow_box.y2);
|
|
}
|