]> Pileus Git - ~andy/gtk/blob - gtk/gtkcssshadowvalue.c
blur: Use recording surface for capturing things to blur
[~andy/gtk] / gtk / gtkcssshadowvalue.c
1 /* GTK - The GIMP Toolkit
2  * Copyright (C) 2011 Red Hat, Inc.
3  *
4  * Author: Cosimo Cecchi <cosimoc@gnome.org>
5  *
6  * This library is free software; you can redistribute it and/or
7  * modify it under the terms of the GNU Lesser General Public
8  * License as published by the Free Software Foundation; either
9  * version 2 of the License, or (at your option) any later version.
10  *
11  * This library is distributed in the hope that it will be useful,
12  * but WITHOUT ANY WARRANTY; without even the implied warranty of
13  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
14  * Lesser General Public License for more details.
15  *
16  * You should have received a copy of the GNU Lesser General Public
17  * License along with this library. If not, see <http://www.gnu.org/licenses/>.
18  */
19
20 #include "config.h"
21
22 #include "gtkcssshadowvalueprivate.h"
23
24 #include <math.h>
25
26 #include "gtkcairoblurprivate.h"
27 #include "gtkcssnumbervalueprivate.h"
28 #include "gtkcssrgbavalueprivate.h"
29 #include "gtkstylecontextprivate.h"
30 #include "gtksymboliccolorprivate.h"
31 #include "gtkthemingengineprivate.h"
32 #include "gtkpango.h"
33
34 struct _GtkCssValue {
35   GTK_CSS_VALUE_BASE
36   guint inset :1;
37
38   GtkCssValue *hoffset;
39   GtkCssValue *voffset;
40   GtkCssValue *radius;
41   GtkCssValue *spread;
42
43   GtkCssValue *color;
44 };
45
46 static GtkCssValue *    gtk_css_shadow_value_new (GtkCssValue *hoffset,
47                                                   GtkCssValue *voffset,
48                                                   GtkCssValue *radius,
49                                                   GtkCssValue *spread,
50                                                   gboolean     inset,
51                                                   GtkCssValue *color);
52
53 static void
54 gtk_css_value_shadow_free (GtkCssValue *shadow)
55 {
56   _gtk_css_value_unref (shadow->hoffset);
57   _gtk_css_value_unref (shadow->voffset);
58   _gtk_css_value_unref (shadow->radius);
59   _gtk_css_value_unref (shadow->spread);
60   _gtk_css_value_unref (shadow->color);
61
62   g_slice_free (GtkCssValue, shadow);
63 }
64
65 static GtkCssValue *
66 gtk_css_value_shadow_compute (GtkCssValue        *shadow,
67                               guint               property_id,
68                               GtkStyleContext    *context,
69                               GtkCssDependencies *dependencies)
70 {
71   GtkCssValue *hoffset, *voffset, *radius, *spread, *color;
72   GtkCssDependencies child_deps;
73
74   child_deps = 0;
75   hoffset = _gtk_css_value_compute (shadow->hoffset, property_id, context, &child_deps);
76   *dependencies = _gtk_css_dependencies_union (*dependencies, child_deps);
77
78   child_deps = 0;
79   voffset = _gtk_css_value_compute (shadow->voffset, property_id, context, &child_deps);
80   *dependencies = _gtk_css_dependencies_union (*dependencies, child_deps);
81
82   child_deps = 0;
83   radius = _gtk_css_value_compute (shadow->radius, property_id, context, &child_deps);
84   *dependencies = _gtk_css_dependencies_union (*dependencies, child_deps);
85
86   child_deps = 0;
87   spread = _gtk_css_value_compute (shadow->spread, property_id, context, &child_deps),
88   *dependencies = _gtk_css_dependencies_union (*dependencies, child_deps);
89
90   child_deps = 0;
91   color = _gtk_css_value_compute (shadow->color, property_id, context, &child_deps);
92   *dependencies = _gtk_css_dependencies_union (*dependencies, child_deps);
93
94   return gtk_css_shadow_value_new (hoffset, voffset, radius, spread, shadow->inset, color);
95 }
96
97 static gboolean
98 gtk_css_value_shadow_equal (const GtkCssValue *shadow1,
99                             const GtkCssValue *shadow2)
100 {
101   return shadow1->inset == shadow2->inset
102       && _gtk_css_value_equal (shadow1->hoffset, shadow2->hoffset)
103       && _gtk_css_value_equal (shadow1->voffset, shadow2->voffset)
104       && _gtk_css_value_equal (shadow1->radius, shadow2->radius)
105       && _gtk_css_value_equal (shadow1->spread, shadow2->spread)
106       && _gtk_css_value_equal (shadow1->color, shadow2->color);
107 }
108
109 static GtkCssValue *
110 gtk_css_value_shadow_transition (GtkCssValue *start,
111                                  GtkCssValue *end,
112                                  guint        property_id,
113                                  double       progress)
114 {
115   if (start->inset != end->inset)
116     return NULL;
117
118   return gtk_css_shadow_value_new (_gtk_css_value_transition (start->hoffset, end->hoffset, property_id, progress),
119                                    _gtk_css_value_transition (start->voffset, end->voffset, property_id, progress),
120                                    _gtk_css_value_transition (start->radius, end->radius, property_id, progress),
121                                    _gtk_css_value_transition (start->spread, end->spread, property_id, progress),
122                                    start->inset,
123                                    _gtk_css_value_transition (start->color, end->color, property_id, progress));
124 }
125
126 static void
127 gtk_css_value_shadow_print (const GtkCssValue *shadow,
128                             GString           *string)
129 {
130   _gtk_css_value_print (shadow->hoffset, string);
131   g_string_append_c (string, ' ');
132   _gtk_css_value_print (shadow->voffset, string);
133   g_string_append_c (string, ' ');
134   if (_gtk_css_number_value_get (shadow->radius, 100) != 0 ||
135       _gtk_css_number_value_get (shadow->spread, 100) != 0)
136     {
137       _gtk_css_value_print (shadow->radius, string);
138       g_string_append_c (string, ' ');
139     }
140
141   if (_gtk_css_number_value_get (shadow->spread, 100) != 0)
142     {
143       _gtk_css_value_print (shadow->spread, string);
144       g_string_append_c (string, ' ');
145     }
146
147   _gtk_css_value_print (shadow->color, string);
148
149   if (shadow->inset)
150     g_string_append (string, " inset");
151
152 }
153
154 static const GtkCssValueClass GTK_CSS_VALUE_SHADOW = {
155   gtk_css_value_shadow_free,
156   gtk_css_value_shadow_compute,
157   gtk_css_value_shadow_equal,
158   gtk_css_value_shadow_transition,
159   gtk_css_value_shadow_print
160 };
161
162 static GtkCssValue *
163 gtk_css_shadow_value_new (GtkCssValue *hoffset,
164                           GtkCssValue *voffset,
165                           GtkCssValue *radius,
166                           GtkCssValue *spread,
167                           gboolean     inset,
168                           GtkCssValue *color)
169 {
170   GtkCssValue *retval;
171
172   retval = _gtk_css_value_new (GtkCssValue, &GTK_CSS_VALUE_SHADOW);
173
174   retval->hoffset = hoffset;
175   retval->voffset = voffset;
176   retval->radius = radius;
177   retval->spread = spread;
178   retval->inset = inset;
179   retval->color = color;
180
181   return retval;
182 }                  
183
184 GtkCssValue *
185 _gtk_css_shadow_value_new_for_transition (GtkCssValue *target)
186 {
187   GdkRGBA transparent = { 0, 0, 0, 0 };
188
189   g_return_val_if_fail (target->class == &GTK_CSS_VALUE_SHADOW, NULL);
190
191   return gtk_css_shadow_value_new (_gtk_css_number_value_new (0, GTK_CSS_PX),
192                                    _gtk_css_number_value_new (0, GTK_CSS_PX),
193                                    _gtk_css_number_value_new (0, GTK_CSS_PX),
194                                    _gtk_css_number_value_new (0, GTK_CSS_PX),
195                                    target->inset,
196                                    _gtk_css_rgba_value_new_from_rgba (&transparent));
197 }
198
199 static gboolean
200 value_is_done_parsing (GtkCssParser *parser)
201 {
202   return _gtk_css_parser_is_eof (parser) ||
203          _gtk_css_parser_begins_with (parser, ',') ||
204          _gtk_css_parser_begins_with (parser, ';') ||
205          _gtk_css_parser_begins_with (parser, '}');
206 }
207
208 GtkCssValue *
209 _gtk_css_shadow_value_parse (GtkCssParser *parser)
210 {
211   enum {
212     HOFFSET,
213     VOFFSET,
214     RADIUS,
215     SPREAD,
216     COLOR,
217     N_VALUES
218   };
219   GtkCssValue *values[N_VALUES] = { NULL, };
220   gboolean inset;
221   guint i;
222
223   inset = _gtk_css_parser_try (parser, "inset", TRUE);
224
225   do
226   {
227     if (values[HOFFSET] == NULL &&
228          _gtk_css_parser_has_number (parser))
229       {
230         values[HOFFSET] = _gtk_css_number_value_parse (parser,
231                                                        GTK_CSS_PARSE_LENGTH
232                                                        | GTK_CSS_NUMBER_AS_PIXELS);
233         if (values[HOFFSET] == NULL)
234           goto fail;
235
236         values[VOFFSET] = _gtk_css_number_value_parse (parser,
237                                                        GTK_CSS_PARSE_LENGTH
238                                                        | GTK_CSS_NUMBER_AS_PIXELS);
239         if (values[VOFFSET] == NULL)
240           goto fail;
241
242         if (_gtk_css_parser_has_number (parser))
243           {
244             values[RADIUS] = _gtk_css_number_value_parse (parser,
245                                                           GTK_CSS_PARSE_LENGTH
246                                                           | GTK_CSS_POSITIVE_ONLY
247                                                           | GTK_CSS_NUMBER_AS_PIXELS);
248             if (values[RADIUS] == NULL)
249               goto fail;
250           }
251         else
252           values[RADIUS] = _gtk_css_number_value_new (0.0, GTK_CSS_PX);
253                                                         
254         if (_gtk_css_parser_has_number (parser))
255           {
256             values[SPREAD] = _gtk_css_number_value_parse (parser,
257                                                           GTK_CSS_PARSE_LENGTH
258                                                           | GTK_CSS_NUMBER_AS_PIXELS);
259             if (values[SPREAD] == NULL)
260               goto fail;
261           }
262         else
263           values[SPREAD] = _gtk_css_number_value_new (0.0, GTK_CSS_PX);
264       }
265     else if (!inset && _gtk_css_parser_try (parser, "inset", TRUE))
266       {
267         if (values[HOFFSET] == NULL)
268           goto fail;
269         inset = TRUE;
270         break;
271       }
272     else if (values[COLOR] == NULL)
273       {
274         values[COLOR] = _gtk_css_symbolic_value_new (parser);
275
276         if (values[COLOR] == NULL)
277           goto fail;
278       }
279     else
280       {
281         /* We parsed everything and there's still stuff left?
282          * Pretend we didn't notice and let the normal code produce
283          * a 'junk at end of value' error */
284         goto fail;
285       }
286   }
287   while (values[HOFFSET] == NULL || !value_is_done_parsing (parser));
288
289   if (values[COLOR] == NULL)
290     values[COLOR] = _gtk_css_symbolic_value_new_take_symbolic_color (
291                       gtk_symbolic_color_ref (
292                         _gtk_symbolic_color_get_current_color ()));
293
294   return gtk_css_shadow_value_new (values[HOFFSET], values[VOFFSET],
295                                    values[RADIUS], values[SPREAD],
296                                    inset, values[COLOR]);
297
298 fail:
299   for (i = 0; i < N_VALUES; i++)
300     {
301       if (values[i])
302         _gtk_css_value_unref (values[i]);
303     }
304
305   return NULL;
306 }
307
308 static const cairo_user_data_key_t shadow_key;
309
310 static cairo_t *
311 gtk_css_shadow_value_start_drawing (const GtkCssValue *shadow,
312                                     cairo_t           *cr)
313 {
314   cairo_rectangle_t clip;
315   cairo_surface_t *surface;
316   cairo_t *blur_cr;
317   gdouble radius;
318
319   radius = _gtk_css_number_value_get (shadow->radius, 0);
320   if (radius == 0.0)
321     return cr;
322
323   radius = ceil (radius);
324   cairo_clip_extents (cr, &clip.x, &clip.y, &clip.width, &clip.height);
325   clip.x -= radius;
326   clip.y -= radius;
327   clip.width += 2 * radius;
328   clip.height += 2 * radius;
329
330   /* Create a larger surface to center the blur. */
331   surface = cairo_recording_surface_create (CAIRO_CONTENT_COLOR_ALPHA, &clip);
332   cairo_surface_set_device_offset (surface, - clip.x, - clip.y);
333   blur_cr = cairo_create (surface);
334   cairo_set_user_data (blur_cr, &shadow_key, cairo_reference (cr), (cairo_destroy_func_t) cairo_destroy);
335
336   if (cairo_has_current_point (cr))
337     {
338       double x, y;
339       
340       cairo_get_current_point (cr, &x, &y);
341       cairo_move_to (blur_cr, x, y);
342     }
343
344   return blur_cr;
345 }
346
347 static cairo_t *
348 gtk_css_shadow_value_finish_drawing (const GtkCssValue *shadow,
349                                      cairo_t           *cr)
350 {
351   gdouble radius;
352   cairo_t *original_cr, *image_cr;
353   cairo_surface_t *recording_surface, *image_surface;
354   cairo_rectangle_t ink;
355
356   radius = _gtk_css_number_value_get (shadow->radius, 0);
357   if (radius == 0.0)
358     return cr;
359
360   recording_surface = cairo_get_target (cr);
361   original_cr = cairo_get_user_data (cr, &shadow_key);
362
363   /* Blur the surface. */
364   cairo_recording_surface_ink_extents (recording_surface, &ink.x, &ink.y, &ink.width, &ink.height);
365   ink.width = ceil (ink.width + ink.x - floor (ink.x)) + 2 * ceil (radius);
366   ink.height = ceil (ink.height + ink.y - floor (ink.y)) + 2 * ceil (radius);
367   ink.x = floor (ink.x) + ceil (radius);
368   ink.y = floor (ink.y) + ceil (radius);
369
370   image_surface = cairo_image_surface_create (CAIRO_FORMAT_ARGB32, ink.width, ink.height);
371   cairo_surface_set_device_offset (image_surface, - ink.x, - ink.y);
372   
373   image_cr = cairo_create (image_surface);
374   cairo_set_source_surface (image_cr, recording_surface, 0, 0);
375   cairo_paint (image_cr);
376   cairo_destroy (image_cr);
377
378   _gtk_cairo_blur_surface (image_surface, radius);
379
380   cairo_set_source_surface (original_cr, image_surface, 0, 0);
381   cairo_paint (original_cr);
382
383   cairo_destroy (cr);
384   cairo_surface_destroy (image_surface);
385
386   return original_cr;
387 }
388
389 void
390 _gtk_css_shadow_value_paint_layout (const GtkCssValue *shadow,
391                                     cairo_t           *cr,
392                                     PangoLayout       *layout)
393 {
394   g_return_if_fail (shadow->class == &GTK_CSS_VALUE_SHADOW);
395
396   if (!cairo_has_current_point (cr))
397     cairo_move_to (cr, 0, 0);
398
399   cairo_save (cr);
400
401   cairo_rel_move_to (cr, 
402                      _gtk_css_number_value_get (shadow->hoffset, 0),
403                      _gtk_css_number_value_get (shadow->voffset, 0));
404
405   cr = gtk_css_shadow_value_start_drawing (shadow, cr);
406
407   gdk_cairo_set_source_rgba (cr, _gtk_css_rgba_value_get_rgba (shadow->color));
408   _gtk_pango_fill_layout (cr, layout);
409
410   cr = gtk_css_shadow_value_finish_drawing (shadow, cr);
411
412   cairo_rel_move_to (cr,
413                      - _gtk_css_number_value_get (shadow->hoffset, 0),
414                      - _gtk_css_number_value_get (shadow->voffset, 0));
415   cairo_restore (cr);
416 }
417
418 void
419 _gtk_css_shadow_value_paint_icon (const GtkCssValue *shadow,
420                                   cairo_t           *cr)
421 {
422   cairo_pattern_t *pattern;
423
424   g_return_if_fail (shadow->class == &GTK_CSS_VALUE_SHADOW);
425
426   cairo_save (cr);
427   pattern = cairo_pattern_reference (cairo_get_source (cr));
428
429   cr = gtk_css_shadow_value_start_drawing (shadow, cr);
430
431   gdk_cairo_set_source_rgba (cr, _gtk_css_rgba_value_get_rgba (shadow->color));
432
433   cairo_translate (cr,
434                    _gtk_css_number_value_get (shadow->hoffset, 0),
435                    _gtk_css_number_value_get (shadow->voffset, 0));
436   cairo_mask (cr, pattern);
437
438   cr = gtk_css_shadow_value_finish_drawing (shadow, cr);
439
440   cairo_restore (cr);
441   cairo_pattern_destroy (pattern);
442 }
443
444 void
445 _gtk_css_shadow_value_paint_spinner (const GtkCssValue *shadow,
446                                      cairo_t           *cr,
447                                      gdouble            radius,
448                                      gdouble            progress)
449 {
450   g_return_if_fail (shadow->class == &GTK_CSS_VALUE_SHADOW);
451
452   cairo_save (cr);
453
454   cr = gtk_css_shadow_value_start_drawing (shadow, cr);
455
456   cairo_translate (cr,
457                    _gtk_css_number_value_get (shadow->hoffset, 0),
458                    _gtk_css_number_value_get (shadow->voffset, 0));
459   _gtk_theming_engine_paint_spinner (cr,
460                                      radius, progress,
461                                      _gtk_css_rgba_value_get_rgba (shadow->color));
462
463   cr = gtk_css_shadow_value_finish_drawing (shadow, cr);
464
465   cairo_restore (cr);
466 }
467
468 void
469 _gtk_css_shadow_value_paint_box (const GtkCssValue   *shadow,
470                                  cairo_t             *cr,
471                                  const GtkRoundedBox *padding_box)
472 {
473   GtkRoundedBox box, clip_box;
474   double spread, radius;
475
476   g_return_if_fail (shadow->class == &GTK_CSS_VALUE_SHADOW);
477
478   cairo_save (cr);
479
480   _gtk_rounded_box_path (padding_box, cr);
481   cairo_clip (cr);
482
483   box = *padding_box;
484   _gtk_rounded_box_move (&box,
485                          _gtk_css_number_value_get (shadow->hoffset, 0),
486                          _gtk_css_number_value_get (shadow->voffset, 0));
487   spread = _gtk_css_number_value_get (shadow->spread, 0);
488   _gtk_rounded_box_shrink (&box, spread, spread, spread, spread);
489
490   clip_box = *padding_box;
491   radius = _gtk_css_number_value_get (shadow->radius, 0);
492   _gtk_rounded_box_shrink (&clip_box, -radius, -radius, -radius, -radius);
493
494   cr = gtk_css_shadow_value_start_drawing (shadow, cr);
495
496   cairo_set_fill_rule (cr, CAIRO_FILL_RULE_EVEN_ODD);
497   _gtk_rounded_box_path (&box, cr);
498   _gtk_rounded_box_clip_path (&clip_box, cr);
499
500   gdk_cairo_set_source_rgba (cr, _gtk_css_rgba_value_get_rgba (shadow->color));
501   cairo_fill (cr);
502
503   cr = gtk_css_shadow_value_finish_drawing (shadow, cr);
504
505   cairo_restore (cr);
506 }