1 /* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */
2 /*
3  * st-private.h: Private declarations and functions
4  *
5  * Copyright 2009, 2010 Red Hat, Inc.
6  * Copyright 2010 Florian Müllner
7  * Copyright 2010 Intel Corporation
8  * Copyright 2010 Giovanni Campagna
9  *
10  * This program is free software; you can redistribute it and/or modify it
11  * under the terms and conditions of the GNU Lesser General Public License,
12  * version 2.1, as published by the Free Software Foundation.
13  *
14  * This program is distributed in the hope it will be useful, but WITHOUT ANY
15  * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
16  * FOR A PARTICULAR PURPOSE.  See the GNU Lesser General Public License for
17  * more details.
18  *
19  * You should have received a copy of the GNU Lesser General Public License
20  * along with this program. If not, see <http://www.gnu.org/licenses/>.
21  */
22 #include <math.h>
23 #include <string.h>
24 
25 #include "st-private.h"
26 #include "st-cogl-wrapper.h"
27 
28 /**
29  * _st_actor_get_preferred_width:
30  * @actor: a #ClutterActor
31  * @for_height: as with clutter_actor_get_preferred_width()
32  * @y_fill: %TRUE if @actor will fill its allocation vertically
33  * @min_width_p: as with clutter_actor_get_preferred_width()
34  * @natural_width_p: as with clutter_actor_get_preferred_width()
35  *
36  * Like clutter_actor_get_preferred_width(), but if @y_fill is %FALSE,
37  * then it will compute a width request based on the assumption that
38  * @actor will be given an allocation no taller than its natural
39  * height.
40  */
41 void
_st_actor_get_preferred_width(ClutterActor * actor,gfloat for_height,gboolean y_fill,gfloat * min_width_p,gfloat * natural_width_p)42 _st_actor_get_preferred_width  (ClutterActor *actor,
43                                 gfloat        for_height,
44                                 gboolean      y_fill,
45                                 gfloat       *min_width_p,
46                                 gfloat       *natural_width_p)
47 {
48   if (!y_fill && for_height != -1)
49     {
50       ClutterRequestMode mode;
51       gfloat natural_height;
52 
53       mode = clutter_actor_get_request_mode (actor);
54       if (mode == CLUTTER_REQUEST_WIDTH_FOR_HEIGHT)
55         {
56           clutter_actor_get_preferred_height (actor, -1, NULL, &natural_height);
57           if (for_height > natural_height)
58             for_height = natural_height;
59         }
60     }
61 
62   clutter_actor_get_preferred_width (actor, for_height, min_width_p, natural_width_p);
63 }
64 
65 /**
66  * _st_actor_get_preferred_height:
67  * @actor: a #ClutterActor
68  * @for_width: as with clutter_actor_get_preferred_height()
69  * @x_fill: %TRUE if @actor will fill its allocation horizontally
70  * @min_height_p: as with clutter_actor_get_preferred_height()
71  * @natural_height_p: as with clutter_actor_get_preferred_height()
72  *
73  * Like clutter_actor_get_preferred_height(), but if @x_fill is
74  * %FALSE, then it will compute a height request based on the
75  * assumption that @actor will be given an allocation no wider than
76  * its natural width.
77  */
78 void
_st_actor_get_preferred_height(ClutterActor * actor,gfloat for_width,gboolean x_fill,gfloat * min_height_p,gfloat * natural_height_p)79 _st_actor_get_preferred_height (ClutterActor *actor,
80                                 gfloat        for_width,
81                                 gboolean      x_fill,
82                                 gfloat       *min_height_p,
83                                 gfloat       *natural_height_p)
84 {
85   if (!x_fill && for_width != -1)
86     {
87       ClutterRequestMode mode;
88       gfloat natural_width;
89 
90       mode = clutter_actor_get_request_mode (actor);
91       if (mode == CLUTTER_REQUEST_HEIGHT_FOR_WIDTH)
92         {
93           clutter_actor_get_preferred_width (actor, -1, NULL, &natural_width);
94           if (for_width > natural_width)
95             for_width = natural_width;
96         }
97     }
98 
99   clutter_actor_get_preferred_height (actor, for_width, min_height_p, natural_height_p);
100 }
101 
102 
103 /**
104  * _st_get_align_factors:
105  * @x_align: an #StAlign
106  * @y_align: an #StAlign
107  * @x_align_out: (out) (allow-none): @x_align as a #gdouble
108  * @y_align_out: (out) (allow-none): @y_align as a #gdouble
109  *
110  * Converts @x_align and @y_align to #gdouble values.
111  */
112 void
_st_get_align_factors(StAlign x_align,StAlign y_align,gdouble * x_align_out,gdouble * y_align_out)113 _st_get_align_factors (StAlign   x_align,
114                        StAlign   y_align,
115                        gdouble  *x_align_out,
116                        gdouble  *y_align_out)
117 {
118   if (x_align_out)
119     {
120       switch (x_align)
121         {
122         case ST_ALIGN_START:
123           *x_align_out = 0.0;
124           break;
125 
126         case ST_ALIGN_MIDDLE:
127           *x_align_out = 0.5;
128           break;
129 
130         case ST_ALIGN_END:
131           *x_align_out = 1.0;
132           break;
133 
134         default:
135           g_warn_if_reached ();
136           break;
137         }
138     }
139 
140   if (y_align_out)
141     {
142       switch (y_align)
143         {
144         case ST_ALIGN_START:
145           *y_align_out = 0.0;
146           break;
147 
148         case ST_ALIGN_MIDDLE:
149           *y_align_out = 0.5;
150           break;
151 
152         case ST_ALIGN_END:
153           *y_align_out = 1.0;
154           break;
155 
156         default:
157           g_warn_if_reached ();
158           break;
159         }
160     }
161 }
162 
163 /**
164  * _st_set_text_from_style:
165  * @text: Target #ClutterText
166  * @theme_node: Source #StThemeNode
167  *
168  * Set various GObject properties of the @text object using
169  * CSS information from @theme_node.
170  */
171 void
_st_set_text_from_style(ClutterText * text,StThemeNode * theme_node)172 _st_set_text_from_style (ClutterText *text,
173                          StThemeNode *theme_node)
174 {
175 
176   ClutterColor color;
177   StTextDecoration decoration;
178   PangoAttrList *attribs = NULL;
179   const PangoFontDescription *font;
180   gchar *font_string;
181   StTextAlign align;
182 
183   st_theme_node_get_foreground_color (theme_node, &color);
184   clutter_text_set_color (text, &color);
185 
186   font = st_theme_node_get_font (theme_node);
187   font_string = pango_font_description_to_string (font);
188   clutter_text_set_font_name (text, font_string);
189   g_free (font_string);
190 
191   decoration = st_theme_node_get_text_decoration (theme_node);
192 
193   if (decoration)
194     {
195       attribs = pango_attr_list_new ();
196 
197       if (decoration & ST_TEXT_DECORATION_UNDERLINE)
198         {
199           PangoAttribute *underline = pango_attr_underline_new (PANGO_UNDERLINE_SINGLE);
200           pango_attr_list_insert (attribs, underline);
201         }
202       if (decoration & ST_TEXT_DECORATION_LINE_THROUGH)
203         {
204           PangoAttribute *strikethrough = pango_attr_strikethrough_new (TRUE);
205           pango_attr_list_insert (attribs, strikethrough);
206         }
207       /* Pango doesn't have an equivalent attribute for _OVERLINE, and we deliberately
208        * skip BLINK (for now...)
209        */
210     }
211 
212   clutter_text_set_attributes (text, attribs);
213 
214   if (attribs)
215     pango_attr_list_unref (attribs);
216 
217   align = st_theme_node_get_text_align (theme_node);
218   if(align == ST_TEXT_ALIGN_JUSTIFY) {
219     clutter_text_set_justify (text, TRUE);
220     clutter_text_set_line_alignment (text, PANGO_ALIGN_LEFT);
221   } else {
222     clutter_text_set_justify (text, FALSE);
223     clutter_text_set_line_alignment (text, (PangoAlignment) align);
224   }
225 }
226 
227 /**
228  * _st_create_texture_pipeline:
229  * @src_texture: The CoglTexture for the material
230  *
231  * Creates a simple material which contains the given texture as a
232  * single layer.
233  */
234 CoglPipeline *
_st_create_texture_pipeline(CoglTexture * src_texture)235 _st_create_texture_pipeline (CoglTexture *src_texture)
236 {
237   static CoglPipeline *texture_pipeline_template = NULL;
238   CoglPipeline *pipeline;
239 
240   g_return_val_if_fail (src_texture != NULL, NULL);
241 
242   /* The only state used in the pipeline that would affect the shader
243      generation is the texture type on the layer. Therefore we create
244      a template pipeline which sets this state and all texture
245      pipelines are created as a copy of this. That way Cogl can find
246      the shader state for the pipeline more quickly by looking at the
247      pipeline ancestry instead of resorting to the shader cache. */
248   if (G_UNLIKELY (texture_pipeline_template == NULL))
249     {
250       texture_pipeline_template = cogl_pipeline_new (st_get_cogl_context());
251       cogl_pipeline_set_layer_null_texture (texture_pipeline_template,
252                                             0, /* layer */
253                                             COGL_TEXTURE_TYPE_2D);
254     }
255 
256   pipeline = cogl_pipeline_copy (texture_pipeline_template);
257 
258   if (src_texture != NULL)
259     cogl_pipeline_set_layer_texture (pipeline, 0, src_texture);
260 
261   return pipeline;
262 }
263 
264 /*****
265  * Shadows
266  *****/
267 
268 static gdouble *
calculate_gaussian_kernel(float sigma,gint n_values)269 calculate_gaussian_kernel (float   sigma,
270                            gint     n_values)
271 {
272   gdouble *ret, sum;
273   gdouble exp_divisor;
274   gint half, i;
275 
276   g_return_val_if_fail (sigma > 0, NULL);
277 
278   half = n_values / 2;
279 
280   ret = g_malloc (n_values * sizeof (gdouble));
281   sum = 0.0;
282 
283   exp_divisor = 2 * sigma * sigma;
284 
285   /* n_values of 1D Gauss function */
286   for (i = 0; i < n_values; i++)
287     {
288       ret[i] = exp (-(i - half) * (i - half) / exp_divisor);
289       sum += ret[i];
290     }
291 
292   /* normalize */
293   for (i = 0; i < n_values; i++)
294     ret[i] /= sum;
295 
296   return ret;
297 }
298 
299 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)300 blur_pixels (guchar  *pixels_in,
301              gint     width_in,
302              gint     height_in,
303              gint     rowstride_in,
304              gdouble  blur,
305              gint    *width_out,
306              gint    *height_out,
307              gint    *rowstride_out)
308 {
309   guchar *pixels_out;
310   float   sigma;
311 
312   /* The CSS specification defines (or will define) the blur radius as twice
313    * the Gaussian standard deviation. See:
314    *
315    * http://lists.w3.org/Archives/Public/www-style/2010Sep/0002.html
316    */
317   sigma = blur / 2.;
318 
319   if ((guint) blur == 0)
320     {
321       *width_out  = width_in;
322       *height_out = height_in;
323       *rowstride_out = rowstride_in;
324       pixels_out = g_memdup (pixels_in, *rowstride_out * *height_out);
325     }
326   else
327     {
328       gdouble *kernel;
329       guchar  *line;
330       gint     n_values, half;
331       gint     x_in, y_in, x_out, y_out, i;
332 
333       n_values = (gint) 5 * sigma;
334       half = n_values / 2;
335 
336       *width_out  = width_in  + 2 * half;
337       *height_out = height_in + 2 * half;
338       *rowstride_out = (*width_out + 3) & ~3;
339 
340       pixels_out = g_malloc0 (*rowstride_out * *height_out);
341       line       = g_malloc0 (*rowstride_out);
342 
343       kernel = calculate_gaussian_kernel (sigma, n_values);
344 
345       /* vertical blur */
346       for (x_in = 0; x_in < width_in; x_in++)
347         for (y_out = 0; y_out < *height_out; y_out++)
348           {
349             guchar *pixel_in, *pixel_out;
350             gint i0, i1;
351 
352             y_in = y_out - half;
353 
354             /* We read from the source at 'y = y_in + i - half'; clamp the
355              * full i range [0, n_values) so that y is in [0, height_in).
356              */
357             i0 = MAX (half - y_in, 0);
358             i1 = MIN (height_in + half - y_in, n_values);
359 
360             pixel_in  =  pixels_in + (y_in + i0 - half) * rowstride_in + x_in;
361             pixel_out =  pixels_out + y_out * *rowstride_out + (x_in + half);
362 
363             for (i = i0; i < i1; i++)
364               {
365                 *pixel_out += *pixel_in * kernel[i];
366                 pixel_in += rowstride_in;
367               }
368           }
369 
370       /* horizontal blur */
371       for (y_out = 0; y_out < *height_out; y_out++)
372         {
373           memcpy (line, pixels_out + y_out * *rowstride_out, *rowstride_out);
374 
375           for (x_out = 0; x_out < *width_out; x_out++)
376             {
377               gint i0, i1;
378               guchar *pixel_out, *pixel_in;
379 
380               /* We read from the source at 'x = x_out + i - half'; clamp the
381                * full i range [0, n_values) so that x is in [0, width_out).
382                */
383               i0 = MAX (half - x_out, 0);
384               i1 = MIN (*width_out + half - x_out, n_values);
385 
386               pixel_in  = line + x_out + i0 - half;
387               pixel_out = pixels_out + *rowstride_out * y_out + x_out;
388 
389               *pixel_out = 0;
390               for (i = i0; i < i1; i++)
391                 {
392                   *pixel_out += *pixel_in * kernel[i];
393                   pixel_in++;
394                 }
395             }
396         }
397       g_free (kernel);
398       g_free (line);
399     }
400 
401   return pixels_out;
402 }
403 
404 CoglPipeline *
_st_create_shadow_pipeline(StShadow * shadow_spec,CoglTexture * src_texture)405 _st_create_shadow_pipeline (StShadow     *shadow_spec,
406                             CoglTexture  *src_texture)
407 {
408   static CoglPipeline *shadow_pipeline_template = NULL;
409 
410   CoglPipeline *pipeline;
411   CoglPipeline *texture;
412   guchar *pixels_in, *pixels_out;
413   gint width_in, height_in, rowstride_in;
414   gint width_out, height_out, rowstride_out;
415 
416   g_return_val_if_fail (shadow_spec != NULL, NULL);
417   g_return_val_if_fail (src_texture != NULL, NULL);
418 
419   width_in  = cogl_texture_get_width  (src_texture);
420   height_in = cogl_texture_get_height (src_texture);
421   rowstride_in = (width_in + 3) & ~3;
422 
423   pixels_in  = g_malloc0 (rowstride_in * height_in);
424 
425   cogl_texture_get_data (src_texture, COGL_PIXEL_FORMAT_A_8,
426                          rowstride_in, pixels_in);
427 
428   pixels_out = blur_pixels (pixels_in, width_in, height_in, rowstride_in,
429                             shadow_spec->blur,
430                             &width_out, &height_out, &rowstride_out);
431   g_free (pixels_in);
432 
433   texture = st_cogl_texture_new_from_data_wrapper (width_out, height_out,
434                                                    COGL_TEXTURE_NONE,
435                                                    COGL_PIXEL_FORMAT_A_8,
436                                                    COGL_PIXEL_FORMAT_A_8,
437                                                    rowstride_out,
438                                                    pixels_out);
439 
440   g_free (pixels_out);
441 
442   if (G_UNLIKELY (shadow_pipeline_template == NULL))
443     {
444       shadow_pipeline_template = cogl_pipeline_new (st_get_cogl_context());
445 
446       /* We set up the pipeline to blend the shadow texture with the combine
447        * constant, but defer setting the latter until painting, so that we can
448        * take the actor's overall opacity into account. */
449       cogl_pipeline_set_layer_combine (shadow_pipeline_template, 0,
450                                        "RGBA = MODULATE (CONSTANT, TEXTURE[A])",
451                                        NULL);
452     }
453 
454   pipeline = cogl_pipeline_copy (shadow_pipeline_template);
455 
456   cogl_pipeline_set_layer_texture (pipeline, 0, texture);
457 
458   if (texture)
459     cogl_object_unref (texture);
460 
461   return pipeline;
462 }
463 
464 CoglPipeline *
_st_create_shadow_pipeline_from_actor(StShadow * shadow_spec,ClutterActor * actor)465 _st_create_shadow_pipeline_from_actor (StShadow     *shadow_spec,
466                                        ClutterActor *actor)
467 {
468   CoglPipeline *shadow_pipeline = NULL;
469 
470   if (CLUTTER_IS_TEXTURE (actor))
471     {
472       CoglTexture *texture;
473 
474       texture = clutter_texture_get_cogl_texture (CLUTTER_TEXTURE (actor));
475       if (texture)
476         shadow_pipeline = _st_create_shadow_pipeline (shadow_spec, texture);
477     }
478   else
479     {
480       CoglTexture *buffer;
481       CoglOffscreen *offscreen;
482       CoglFramebuffer *fb;
483       ClutterActorBox box;
484       CoglColor clear_color;
485       float width, height;
486       CoglError *catch_error;
487 
488       clutter_actor_get_allocation_box (actor, &box);
489       clutter_actor_box_get_size (&box, &width, &height);
490 
491       if (width == 0 || height == 0)
492         return NULL;
493 
494       buffer = st_cogl_texture_new_with_size_wrapper (width, height,
495                                                       COGL_TEXTURE_NO_SLICING,
496                                                       COGL_PIXEL_FORMAT_ANY);
497 
498       if (buffer == NULL)
499         return NULL;
500 
501       offscreen = cogl_offscreen_new_with_texture (buffer);
502       fb = COGL_FRAMEBUFFER (offscreen);
503       catch_error = NULL;
504 
505       if (!cogl_framebuffer_allocate (fb, &catch_error))
506         {
507           cogl_error_free (catch_error);
508           cogl_object_unref (offscreen);
509           cogl_object_unref (buffer);
510           return NULL;
511         }
512 
513       cogl_color_init_from_4ub (&clear_color, 0, 0, 0, 0);
514 
515       /* XXX: There's no way to render a ClutterActor to an offscreen
516        * as it uses the implicit API. */
517       G_GNUC_BEGIN_IGNORE_DEPRECATIONS;
518       cogl_push_framebuffer (fb);
519       G_GNUC_END_IGNORE_DEPRECATIONS;
520 
521       cogl_framebuffer_clear (fb, COGL_BUFFER_BIT_COLOR, &clear_color);
522       cogl_framebuffer_translate (fb, -box.x1, -box.y1, 0);
523       cogl_framebuffer_orthographic (fb, 0, 0, width, height, 0, 1.0);
524       clutter_actor_paint (actor);
525 
526       G_GNUC_BEGIN_IGNORE_DEPRECATIONS;
527       cogl_pop_framebuffer ();
528       G_GNUC_END_IGNORE_DEPRECATIONS;
529 
530       cogl_object_unref (fb);
531 
532       shadow_pipeline = _st_create_shadow_pipeline (shadow_spec, buffer);
533 
534       cogl_object_unref (buffer);
535     }
536 
537   return shadow_pipeline;
538 }
539 
540 /**
541  * _st_create_shadow_cairo_pattern:
542  * @shadow_spec: the definition of the shadow
543  * @src_pattern: surface pattern for which we create the shadow
544  *               (must be a surface pattern)
545  *
546  * This is a utility function for creating shadows used by
547  * st-theme-node.c; it's in this file to share the gaussian
548  * blur implementation. The usage of this function is quite different
549  * depending on whether shadow_spec->inset is %TRUE or not. If
550  * shadow_spec->inset is %TRUE, the caller should pass in a @src_pattern
551  * which is the <i>inverse</i> of what they want shadowed, and must take
552  * care of the spread and offset from the shadow spec themselves. If
553  * shadow_spec->inset is %FALSE then the caller should pass in what they
554  * want shadowed directly, and this function takes care of the spread and
555  * the offset.
556  */
557 cairo_pattern_t *
_st_create_shadow_cairo_pattern(StShadow * shadow_spec,cairo_pattern_t * src_pattern)558 _st_create_shadow_cairo_pattern (StShadow        *shadow_spec,
559                                  cairo_pattern_t *src_pattern)
560 {
561   static cairo_user_data_key_t shadow_pattern_user_data;
562   cairo_t *cr;
563   cairo_surface_t *src_surface;
564   cairo_surface_t *surface_in;
565   cairo_surface_t *surface_out;
566   cairo_pattern_t *dst_pattern;
567   guchar          *pixels_in, *pixels_out;
568   gint             width_in, height_in, rowstride_in;
569   gint             width_out, height_out, rowstride_out;
570   cairo_matrix_t   shadow_matrix;
571   int i, j;
572 
573   g_return_val_if_fail (shadow_spec != NULL, NULL);
574   g_return_val_if_fail (src_pattern != NULL, NULL);
575 
576   if (cairo_pattern_get_surface (src_pattern, &src_surface) != CAIRO_STATUS_SUCCESS)
577     /* The most likely reason we can't get the pattern is that sizing went hairwire
578      * and the caller tried to create a surface too big for memory, leaving us with
579      * a pattern in an error state; we return a transparent pattern for the shadow.
580      */
581     return cairo_pattern_create_rgba(1.0, 1.0, 1.0, 0.0);
582 
583   width_in  = cairo_image_surface_get_width  (src_surface);
584   height_in = cairo_image_surface_get_height (src_surface);
585 
586   /* We want the output to be a color agnostic alpha mask,
587    * so we need to strip the color channels from the input
588    */
589   if (cairo_image_surface_get_format (src_surface) != CAIRO_FORMAT_A8)
590     {
591       surface_in = cairo_image_surface_create (CAIRO_FORMAT_A8,
592                                                width_in, height_in);
593 
594       cr = cairo_create (surface_in);
595       cairo_set_source_surface (cr, src_surface, 0, 0);
596       cairo_paint (cr);
597       cairo_destroy (cr);
598     }
599   else
600     {
601       surface_in = cairo_surface_reference (src_surface);
602     }
603 
604   pixels_in = cairo_image_surface_get_data (surface_in);
605   rowstride_in = cairo_image_surface_get_stride (surface_in);
606 
607   pixels_out = blur_pixels (pixels_in, width_in, height_in, rowstride_in,
608                             shadow_spec->blur,
609                             &width_out, &height_out, &rowstride_out);
610   cairo_surface_destroy (surface_in);
611 
612   /* Invert pixels for inset shadows */
613   if (shadow_spec->inset)
614     {
615       for (j = 0; j < height_out; j++)
616         {
617           guchar *p = pixels_out + rowstride_out * j;
618           for (i = 0; i < width_out; i++, p++)
619             *p = ~*p;
620         }
621     }
622 
623   surface_out = cairo_image_surface_create_for_data (pixels_out,
624                                                      CAIRO_FORMAT_A8,
625                                                      width_out,
626                                                      height_out,
627                                                      rowstride_out);
628   cairo_surface_set_user_data (surface_out, &shadow_pattern_user_data,
629                                pixels_out, (cairo_destroy_func_t) g_free);
630 
631   dst_pattern = cairo_pattern_create_for_surface (surface_out);
632   cairo_surface_destroy (surface_out);
633 
634   cairo_pattern_get_matrix (src_pattern, &shadow_matrix);
635 
636   if (shadow_spec->inset)
637     {
638       /* For inset shadows, offsets and spread radius have already been
639        * applied to the original pattern, so all left to do is shift the
640        * blurred image left, so that it aligns centered under the
641        * unblurred one
642        */
643       cairo_matrix_translate (&shadow_matrix,
644                               (width_out - width_in) / 2.0,
645                               (height_out - height_in) / 2.0);
646       cairo_pattern_set_matrix (dst_pattern, &shadow_matrix);
647       return dst_pattern;
648     }
649 
650   /* Read all the code from the cairo_pattern_set_matrix call
651    * at the end of this function to here from bottom to top,
652    * because each new affine transformation is applied in
653    * front of all the previous ones */
654 
655   /* 6. Invert the matrix back */
656   cairo_matrix_invert (&shadow_matrix);
657 
658   /* 5. Adjust based on specified offsets */
659   cairo_matrix_translate (&shadow_matrix,
660                           shadow_spec->xoffset,
661                           shadow_spec->yoffset);
662 
663   /* 4. Recenter the newly scaled image */
664   cairo_matrix_translate (&shadow_matrix,
665                           - shadow_spec->spread,
666                           - shadow_spec->spread);
667 
668   /* 3. Scale up the blurred image to fill the spread */
669   cairo_matrix_scale (&shadow_matrix,
670                       (width_in + 2.0 * shadow_spec->spread) / width_in,
671                       (height_in + 2.0 * shadow_spec->spread) / height_in);
672 
673   /* 2. Shift the blurred image left, so that it aligns centered
674    * under the unblurred one */
675   cairo_matrix_translate (&shadow_matrix,
676                           - (width_out - width_in) / 2.0,
677                           - (height_out - height_in) / 2.0);
678 
679   /* 1. Invert the matrix so we can work with it in pattern space
680    */
681   cairo_matrix_invert (&shadow_matrix);
682 
683   cairo_pattern_set_matrix (dst_pattern, &shadow_matrix);
684 
685   return dst_pattern;
686 }
687 
688 void
_st_paint_shadow_with_opacity(StShadow * shadow_spec,CoglPipeline * shadow_pipeline,CoglFramebuffer * fb,ClutterActorBox * box,guint8 paint_opacity)689 _st_paint_shadow_with_opacity (StShadow        *shadow_spec,
690                                CoglPipeline    *shadow_pipeline,
691                                CoglFramebuffer *fb,
692                                ClutterActorBox *box,
693                                guint8           paint_opacity)
694 {
695   ClutterActorBox shadow_box;
696   CoglColor color;
697   guint8 adjusted;
698 
699   g_return_if_fail (shadow_spec != NULL);
700   g_return_if_fail (shadow_pipeline != NULL);
701 
702   st_shadow_get_box (shadow_spec, box, &shadow_box);
703 
704   adjusted = paint_opacity / 255;
705   cogl_color_init_from_4ub (&color,
706                             shadow_spec->color.red   * adjusted,
707                             shadow_spec->color.green * adjusted,
708                             shadow_spec->color.blue  * adjusted,
709                             shadow_spec->color.alpha * adjusted);
710   cogl_pipeline_set_layer_combine_constant (shadow_pipeline, 0, &color);
711   cogl_framebuffer_draw_rectangle (fb, shadow_pipeline,
712                                    shadow_box.x1, shadow_box.y1,
713                                    shadow_box.x2, shadow_box.y2);
714 }
715