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 "gtkcairoblurprivate.h"
25 #include "gtkcsscolorvalueprivate.h"
26 #include "gtkcssnumbervalueprivate.h"
27 #include "gtkcssrgbavalueprivate.h"
28 #include "gtkstylecontextprivate.h"
29 #include "gtkrenderprivate.h"
30 #include "gtkpango.h"
31
32 #include "fallback-c89.c"
33 #include <float.h>
34
35 #define CORNER_MASK_CACHE_MAX_SIZE 2000U
36
37 struct _GtkCssValue {
38 GTK_CSS_VALUE_BASE
39 guint inset :1;
40
41 GtkCssValue *hoffset;
42 GtkCssValue *voffset;
43 GtkCssValue *radius;
44 GtkCssValue *spread;
45
46 GtkCssValue *color;
47 };
48
49 static GtkCssValue * gtk_css_shadow_value_new (GtkCssValue *hoffset,
50 GtkCssValue *voffset,
51 GtkCssValue *radius,
52 GtkCssValue *spread,
53 gboolean inset,
54 GtkCssValue *color);
55
56 static void
gtk_css_value_shadow_free(GtkCssValue * shadow)57 gtk_css_value_shadow_free (GtkCssValue *shadow)
58 {
59 _gtk_css_value_unref (shadow->hoffset);
60 _gtk_css_value_unref (shadow->voffset);
61 _gtk_css_value_unref (shadow->radius);
62 _gtk_css_value_unref (shadow->spread);
63 _gtk_css_value_unref (shadow->color);
64
65 g_slice_free (GtkCssValue, shadow);
66 }
67
68 static GtkCssValue *
gtk_css_value_shadow_compute(GtkCssValue * shadow,guint property_id,GtkStyleProviderPrivate * provider,GtkCssStyle * style,GtkCssStyle * parent_style)69 gtk_css_value_shadow_compute (GtkCssValue *shadow,
70 guint property_id,
71 GtkStyleProviderPrivate *provider,
72 GtkCssStyle *style,
73 GtkCssStyle *parent_style)
74 {
75 GtkCssValue *hoffset, *voffset, *radius, *spread, *color;
76
77 hoffset = _gtk_css_value_compute (shadow->hoffset, property_id, provider, style, parent_style);
78 voffset = _gtk_css_value_compute (shadow->voffset, property_id, provider, style, parent_style);
79 radius = _gtk_css_value_compute (shadow->radius, property_id, provider, style, parent_style);
80 spread = _gtk_css_value_compute (shadow->spread, property_id, provider, style, parent_style),
81 color = _gtk_css_value_compute (shadow->color, property_id, provider, style, parent_style);
82
83 if (hoffset == shadow->hoffset &&
84 voffset == shadow->voffset &&
85 radius == shadow->radius &&
86 spread == shadow->spread &&
87 color == shadow->color)
88 {
89 _gtk_css_value_unref (hoffset);
90 _gtk_css_value_unref (voffset);
91 _gtk_css_value_unref (radius);
92 _gtk_css_value_unref (spread);
93 _gtk_css_value_unref (color);
94
95 return _gtk_css_value_ref (shadow);
96 }
97
98 return gtk_css_shadow_value_new (hoffset, voffset, radius, spread, shadow->inset, color);
99 }
100
101 static gboolean
gtk_css_value_shadow_equal(const GtkCssValue * shadow1,const GtkCssValue * shadow2)102 gtk_css_value_shadow_equal (const GtkCssValue *shadow1,
103 const GtkCssValue *shadow2)
104 {
105 return shadow1->inset == shadow2->inset
106 && _gtk_css_value_equal (shadow1->hoffset, shadow2->hoffset)
107 && _gtk_css_value_equal (shadow1->voffset, shadow2->voffset)
108 && _gtk_css_value_equal (shadow1->radius, shadow2->radius)
109 && _gtk_css_value_equal (shadow1->spread, shadow2->spread)
110 && _gtk_css_value_equal (shadow1->color, shadow2->color);
111 }
112
113 static GtkCssValue *
gtk_css_value_shadow_transition(GtkCssValue * start,GtkCssValue * end,guint property_id,double progress)114 gtk_css_value_shadow_transition (GtkCssValue *start,
115 GtkCssValue *end,
116 guint property_id,
117 double progress)
118 {
119 if (start->inset != end->inset)
120 return NULL;
121
122 return gtk_css_shadow_value_new (_gtk_css_value_transition (start->hoffset, end->hoffset, property_id, progress),
123 _gtk_css_value_transition (start->voffset, end->voffset, property_id, progress),
124 _gtk_css_value_transition (start->radius, end->radius, property_id, progress),
125 _gtk_css_value_transition (start->spread, end->spread, property_id, progress),
126 start->inset,
127 _gtk_css_value_transition (start->color, end->color, property_id, progress));
128 }
129
130 static void
gtk_css_value_shadow_print(const GtkCssValue * shadow,GString * string)131 gtk_css_value_shadow_print (const GtkCssValue *shadow,
132 GString *string)
133 {
134 _gtk_css_value_print (shadow->hoffset, string);
135 g_string_append_c (string, ' ');
136 _gtk_css_value_print (shadow->voffset, string);
137 g_string_append_c (string, ' ');
138 if (_gtk_css_number_value_get (shadow->radius, 100) != 0 ||
139 _gtk_css_number_value_get (shadow->spread, 100) != 0)
140 {
141 _gtk_css_value_print (shadow->radius, string);
142 g_string_append_c (string, ' ');
143 }
144
145 if (_gtk_css_number_value_get (shadow->spread, 100) != 0)
146 {
147 _gtk_css_value_print (shadow->spread, string);
148 g_string_append_c (string, ' ');
149 }
150
151 _gtk_css_value_print (shadow->color, string);
152
153 if (shadow->inset)
154 g_string_append (string, " inset");
155
156 }
157
158 static const GtkCssValueClass GTK_CSS_VALUE_SHADOW = {
159 gtk_css_value_shadow_free,
160 gtk_css_value_shadow_compute,
161 gtk_css_value_shadow_equal,
162 gtk_css_value_shadow_transition,
163 gtk_css_value_shadow_print
164 };
165
166 static GtkCssValue *
gtk_css_shadow_value_new(GtkCssValue * hoffset,GtkCssValue * voffset,GtkCssValue * radius,GtkCssValue * spread,gboolean inset,GtkCssValue * color)167 gtk_css_shadow_value_new (GtkCssValue *hoffset,
168 GtkCssValue *voffset,
169 GtkCssValue *radius,
170 GtkCssValue *spread,
171 gboolean inset,
172 GtkCssValue *color)
173 {
174 GtkCssValue *retval;
175
176 retval = _gtk_css_value_new (GtkCssValue, >K_CSS_VALUE_SHADOW);
177
178 retval->hoffset = hoffset;
179 retval->voffset = voffset;
180 retval->radius = radius;
181 retval->spread = spread;
182 retval->inset = inset;
183 retval->color = color;
184
185 return retval;
186 }
187
188 GtkCssValue *
_gtk_css_shadow_value_new_for_transition(GtkCssValue * target)189 _gtk_css_shadow_value_new_for_transition (GtkCssValue *target)
190 {
191 GdkRGBA transparent = { 0, 0, 0, 0 };
192
193 g_return_val_if_fail (target->class == >K_CSS_VALUE_SHADOW, NULL);
194
195 return gtk_css_shadow_value_new (_gtk_css_number_value_new (0, GTK_CSS_PX),
196 _gtk_css_number_value_new (0, GTK_CSS_PX),
197 _gtk_css_number_value_new (0, GTK_CSS_PX),
198 _gtk_css_number_value_new (0, GTK_CSS_PX),
199 target->inset,
200 _gtk_css_rgba_value_new_from_rgba (&transparent));
201 }
202
203 static gboolean
value_is_done_parsing(GtkCssParser * parser)204 value_is_done_parsing (GtkCssParser *parser)
205 {
206 return _gtk_css_parser_is_eof (parser) ||
207 _gtk_css_parser_begins_with (parser, ',') ||
208 _gtk_css_parser_begins_with (parser, ';') ||
209 _gtk_css_parser_begins_with (parser, '}');
210 }
211
212 GtkCssValue *
_gtk_css_shadow_value_parse(GtkCssParser * parser,gboolean box_shadow_mode)213 _gtk_css_shadow_value_parse (GtkCssParser *parser,
214 gboolean box_shadow_mode)
215 {
216 enum {
217 HOFFSET,
218 VOFFSET,
219 RADIUS,
220 SPREAD,
221 COLOR,
222 N_VALUES
223 };
224 GtkCssValue *values[N_VALUES] = { NULL, };
225 gboolean inset;
226 guint i;
227
228 if (box_shadow_mode)
229 inset = _gtk_css_parser_try (parser, "inset", TRUE);
230 else
231 inset = FALSE;
232
233 do
234 {
235 if (values[HOFFSET] == NULL &&
236 gtk_css_number_value_can_parse (parser))
237 {
238 values[HOFFSET] = _gtk_css_number_value_parse (parser,
239 GTK_CSS_PARSE_LENGTH
240 | GTK_CSS_NUMBER_AS_PIXELS);
241 if (values[HOFFSET] == NULL)
242 goto fail;
243
244 values[VOFFSET] = _gtk_css_number_value_parse (parser,
245 GTK_CSS_PARSE_LENGTH
246 | GTK_CSS_NUMBER_AS_PIXELS);
247 if (values[VOFFSET] == NULL)
248 goto fail;
249
250 if (gtk_css_number_value_can_parse (parser))
251 {
252 values[RADIUS] = _gtk_css_number_value_parse (parser,
253 GTK_CSS_PARSE_LENGTH
254 | GTK_CSS_POSITIVE_ONLY
255 | GTK_CSS_NUMBER_AS_PIXELS);
256 if (values[RADIUS] == NULL)
257 goto fail;
258 }
259 else
260 values[RADIUS] = _gtk_css_number_value_new (0.0, GTK_CSS_PX);
261
262 if (box_shadow_mode && gtk_css_number_value_can_parse (parser))
263 {
264 values[SPREAD] = _gtk_css_number_value_parse (parser,
265 GTK_CSS_PARSE_LENGTH
266 | GTK_CSS_NUMBER_AS_PIXELS);
267 if (values[SPREAD] == NULL)
268 goto fail;
269 }
270 else
271 values[SPREAD] = _gtk_css_number_value_new (0.0, GTK_CSS_PX);
272 }
273 else if (!inset && box_shadow_mode && _gtk_css_parser_try (parser, "inset", TRUE))
274 {
275 if (values[HOFFSET] == NULL)
276 goto fail;
277 inset = TRUE;
278 break;
279 }
280 else if (values[COLOR] == NULL)
281 {
282 values[COLOR] = _gtk_css_color_value_parse (parser);
283
284 if (values[COLOR] == NULL)
285 goto fail;
286 }
287 else
288 {
289 /* We parsed everything and there's still stuff left?
290 * Pretend we didn't notice and let the normal code produce
291 * a 'junk at end of value' error */
292 goto fail;
293 }
294 }
295 while (values[HOFFSET] == NULL || !value_is_done_parsing (parser));
296
297 if (values[COLOR] == NULL)
298 values[COLOR] = _gtk_css_color_value_new_current_color ();
299
300 return gtk_css_shadow_value_new (values[HOFFSET], values[VOFFSET],
301 values[RADIUS], values[SPREAD],
302 inset, values[COLOR]);
303
304 fail:
305 for (i = 0; i < N_VALUES; i++)
306 {
307 if (values[i])
308 _gtk_css_value_unref (values[i]);
309 }
310
311 return NULL;
312 }
313
314 static gboolean
needs_blur(const GtkCssValue * shadow)315 needs_blur (const GtkCssValue *shadow)
316 {
317 double radius = _gtk_css_number_value_get (shadow->radius, 0);
318
319 /* The code doesn't actually do any blurring for radius 1, as it
320 * ends up with box filter size 1 */
321 if (radius <= 1.0)
322 return FALSE;
323
324 return TRUE;
325 }
326
327 static const cairo_user_data_key_t original_cr_key;
328
329 static cairo_t *
gtk_css_shadow_value_start_drawing(const GtkCssValue * shadow,cairo_t * cr,GtkBlurFlags blur_flags)330 gtk_css_shadow_value_start_drawing (const GtkCssValue *shadow,
331 cairo_t *cr,
332 GtkBlurFlags blur_flags)
333 {
334 cairo_rectangle_int_t clip_rect;
335 cairo_surface_t *surface;
336 cairo_t *blur_cr;
337 gdouble radius, clip_radius;
338 gdouble x_scale, y_scale;
339 gboolean blur_x = (blur_flags & GTK_BLUR_X) != 0;
340 gboolean blur_y = (blur_flags & GTK_BLUR_Y) != 0;
341
342 if (!needs_blur (shadow))
343 return cr;
344
345 gdk_cairo_get_clip_rectangle (cr, &clip_rect);
346
347 radius = _gtk_css_number_value_get (shadow->radius, 0);
348 clip_radius = _gtk_cairo_blur_compute_pixels (radius);
349
350 x_scale = y_scale = 1;
351 cairo_surface_get_device_scale (cairo_get_target (cr), &x_scale, &y_scale);
352
353 if (blur_flags & GTK_BLUR_REPEAT)
354 {
355 if (!blur_x)
356 clip_rect.width = 1;
357 if (!blur_y)
358 clip_rect.height = 1;
359 }
360
361 /* Create a larger surface to center the blur. */
362 surface = cairo_surface_create_similar_image (cairo_get_target (cr),
363 CAIRO_FORMAT_A8,
364 x_scale * (clip_rect.width + (blur_x ? 2 * clip_radius : 0)),
365 y_scale * (clip_rect.height + (blur_y ? 2 * clip_radius : 0)));
366 cairo_surface_set_device_scale (surface, x_scale, y_scale);
367 cairo_surface_set_device_offset (surface,
368 x_scale * ((blur_x ? clip_radius: 0) - clip_rect.x),
369 y_scale * ((blur_y ? clip_radius: 0) - clip_rect.y));
370
371 blur_cr = cairo_create (surface);
372 cairo_set_user_data (blur_cr, &original_cr_key, cairo_reference (cr), (cairo_destroy_func_t) cairo_destroy);
373
374 if (cairo_has_current_point (cr))
375 {
376 double x, y;
377
378 cairo_get_current_point (cr, &x, &y);
379 cairo_move_to (blur_cr, x, y);
380 }
381
382 return blur_cr;
383 }
384
385 void
mask_surface_repeat(cairo_t * cr,cairo_surface_t * surface)386 mask_surface_repeat (cairo_t *cr,
387 cairo_surface_t *surface)
388 {
389 cairo_pattern_t *pattern;
390
391 pattern = cairo_pattern_create_for_surface (surface);
392 cairo_pattern_set_extend (pattern, CAIRO_EXTEND_REPEAT);
393
394 cairo_mask (cr, pattern);
395
396 cairo_pattern_destroy (pattern);
397 }
398
399 static cairo_t *
gtk_css_shadow_value_finish_drawing(const GtkCssValue * shadow,cairo_t * cr,GtkBlurFlags blur_flags)400 gtk_css_shadow_value_finish_drawing (const GtkCssValue *shadow,
401 cairo_t *cr,
402 GtkBlurFlags blur_flags)
403 {
404 gdouble radius;
405 cairo_t *original_cr;
406 cairo_surface_t *surface;
407 gdouble x_scale;
408
409 if (!needs_blur (shadow))
410 return cr;
411
412 original_cr = cairo_get_user_data (cr, &original_cr_key);
413
414 /* Blur the surface. */
415 surface = cairo_get_target (cr);
416 radius = _gtk_css_number_value_get (shadow->radius, 0);
417
418 x_scale = 1;
419 cairo_surface_get_device_scale (cairo_get_target (cr), &x_scale, NULL);
420
421 _gtk_cairo_blur_surface (surface, x_scale * radius, blur_flags);
422
423 gdk_cairo_set_source_rgba (original_cr, _gtk_css_rgba_value_get_rgba (shadow->color));
424 if (blur_flags & GTK_BLUR_REPEAT)
425 mask_surface_repeat (original_cr, surface);
426 else
427 cairo_mask_surface (original_cr, surface, 0, 0);
428
429 cairo_destroy (cr);
430
431 cairo_surface_destroy (surface);
432
433 return original_cr;
434 }
435
436 static const cairo_user_data_key_t radius_key;
437 static const cairo_user_data_key_t layout_serial_key;
438
G_DEFINE_QUARK(GtkCssShadowValue pango_cached_blurred_surface,pango_cached_blurred_surface)439 G_DEFINE_QUARK (GtkCssShadowValue pango_cached_blurred_surface, pango_cached_blurred_surface)
440
441 static cairo_surface_t *
442 get_cached_pango_surface (PangoLayout *layout,
443 const GtkCssValue *shadow)
444 {
445 cairo_surface_t *cached_surface = g_object_get_qdata (G_OBJECT (layout), pango_cached_blurred_surface_quark ());
446 guint cached_radius, cached_serial;
447 guint radius, serial;
448
449 if (!cached_surface)
450 return NULL;
451
452 radius = _gtk_css_number_value_get (shadow->radius, 0);
453 cached_radius = GPOINTER_TO_UINT (cairo_surface_get_user_data (cached_surface, &radius_key));
454 if (radius != cached_radius)
455 return NULL;
456
457 serial = pango_layout_get_serial (layout);
458 cached_serial = GPOINTER_TO_UINT (cairo_surface_get_user_data (cached_surface, &layout_serial_key));
459 if (serial != cached_serial)
460 return NULL;
461
462 return cached_surface;
463 }
464
465 static cairo_surface_t *
make_blurred_pango_surface(cairo_t * existing_cr,PangoLayout * layout,const GtkCssValue * shadow)466 make_blurred_pango_surface (cairo_t *existing_cr,
467 PangoLayout *layout,
468 const GtkCssValue *shadow)
469 {
470 cairo_surface_t *surface;
471 cairo_t *cr;
472 gdouble radius, clip_radius;
473 gdouble x_scale, y_scale;
474 PangoRectangle ink_rect;
475
476 radius = _gtk_css_number_value_get (shadow->radius, 0);
477
478 pango_layout_get_pixel_extents (layout, &ink_rect, NULL);
479 clip_radius = _gtk_cairo_blur_compute_pixels (radius);
480 x_scale = y_scale = 1;
481 cairo_surface_get_device_scale (cairo_get_target (existing_cr), &x_scale, &y_scale);
482
483 surface = cairo_surface_create_similar_image (cairo_get_target (existing_cr),
484 CAIRO_FORMAT_A8,
485 x_scale * (ink_rect.width + 2 * clip_radius),
486 y_scale * (ink_rect.height + 2 * clip_radius));
487 cairo_surface_set_device_scale (surface, x_scale, y_scale);
488 cairo_surface_set_device_offset (surface,
489 x_scale * (-ink_rect.x + clip_radius),
490 y_scale * (-ink_rect.y + clip_radius));
491 cr = cairo_create (surface);
492 cairo_move_to (cr, 0, 0);
493 _gtk_pango_fill_layout (cr, layout);
494 _gtk_cairo_blur_surface (surface, radius * x_scale, GTK_BLUR_X | GTK_BLUR_Y);
495
496 cairo_destroy (cr);
497
498 return surface;
499 }
500
501 static cairo_surface_t *
get_blurred_pango_surface(cairo_t * cr,PangoLayout * layout,const GtkCssValue * shadow)502 get_blurred_pango_surface (cairo_t *cr,
503 PangoLayout *layout,
504 const GtkCssValue *shadow)
505 {
506 cairo_surface_t *surface;
507 guint radius, serial;
508
509 surface = get_cached_pango_surface (layout, shadow);
510 if (!surface)
511 {
512 surface = make_blurred_pango_surface (cr, layout, shadow);
513
514 /* Cache the surface on the PangoLayout */
515 radius = _gtk_css_number_value_get (shadow->radius, 0);
516 cairo_surface_set_user_data (surface, &radius_key, GUINT_TO_POINTER (radius), NULL);
517
518 serial = pango_layout_get_serial (layout);
519 cairo_surface_set_user_data (surface, &layout_serial_key, GUINT_TO_POINTER (serial), NULL);
520
521 g_object_set_qdata_full (G_OBJECT (layout), pango_cached_blurred_surface_quark (),
522 surface, (GDestroyNotify) cairo_surface_destroy);
523 }
524
525 return surface;
526 }
527
528 void
_gtk_css_shadow_value_paint_layout(const GtkCssValue * shadow,cairo_t * cr,PangoLayout * layout)529 _gtk_css_shadow_value_paint_layout (const GtkCssValue *shadow,
530 cairo_t *cr,
531 PangoLayout *layout)
532 {
533 g_return_if_fail (shadow->class == >K_CSS_VALUE_SHADOW);
534
535 /* We don't need to draw invisible shadows */
536 if (gtk_rgba_is_clear (_gtk_css_rgba_value_get_rgba (shadow->color)))
537 return;
538
539 if (!cairo_has_current_point (cr))
540 cairo_move_to (cr, 0, 0);
541
542 cairo_save (cr);
543
544 if (needs_blur (shadow))
545 {
546 cairo_surface_t *blurred_surface = get_blurred_pango_surface (cr, layout, shadow);
547 double x, y;
548 cairo_get_current_point (cr, &x, &y);
549 cairo_translate (cr, x, y);
550 cairo_translate (cr,
551 _gtk_css_number_value_get (shadow->hoffset, 0),
552 _gtk_css_number_value_get (shadow->voffset, 0));
553
554 gdk_cairo_set_source_rgba (cr, _gtk_css_rgba_value_get_rgba (shadow->color));
555 cairo_mask_surface (cr, blurred_surface, 0, 0);
556 }
557 else
558 {
559 /* The no blur case -- just paint directly. */
560 cairo_rel_move_to (cr,
561 _gtk_css_number_value_get (shadow->hoffset, 0),
562 _gtk_css_number_value_get (shadow->voffset, 0));
563 gdk_cairo_set_source_rgba (cr, _gtk_css_rgba_value_get_rgba (shadow->color));
564 _gtk_pango_fill_layout (cr, layout);
565 cairo_rel_move_to (cr,
566 - _gtk_css_number_value_get (shadow->hoffset, 0),
567 - _gtk_css_number_value_get (shadow->voffset, 0));
568 }
569
570 cairo_restore (cr);
571 }
572
573 void
_gtk_css_shadow_value_paint_icon(const GtkCssValue * shadow,cairo_t * cr)574 _gtk_css_shadow_value_paint_icon (const GtkCssValue *shadow,
575 cairo_t *cr)
576 {
577 cairo_pattern_t *pattern;
578
579 g_return_if_fail (shadow->class == >K_CSS_VALUE_SHADOW);
580
581 /* We don't need to draw invisible shadows */
582 if (gtk_rgba_is_clear (_gtk_css_rgba_value_get_rgba (shadow->color)))
583 return;
584
585 cairo_save (cr);
586 pattern = cairo_pattern_reference (cairo_get_source (cr));
587
588 gdk_cairo_set_source_rgba (cr, _gtk_css_rgba_value_get_rgba (shadow->color));
589 cr = gtk_css_shadow_value_start_drawing (shadow, cr, GTK_BLUR_X | GTK_BLUR_Y);
590
591 cairo_translate (cr,
592 _gtk_css_number_value_get (shadow->hoffset, 0),
593 _gtk_css_number_value_get (shadow->voffset, 0));
594 cairo_mask (cr, pattern);
595
596 cr = gtk_css_shadow_value_finish_drawing (shadow, cr, GTK_BLUR_X | GTK_BLUR_Y);
597
598 cairo_restore (cr);
599 cairo_pattern_destroy (pattern);
600 }
601
602 gboolean
_gtk_css_shadow_value_get_inset(const GtkCssValue * shadow)603 _gtk_css_shadow_value_get_inset (const GtkCssValue *shadow)
604 {
605 g_return_val_if_fail (shadow->class == >K_CSS_VALUE_SHADOW, FALSE);
606
607 return shadow->inset;
608 }
609
610 void
_gtk_css_shadow_value_get_geometry(const GtkCssValue * shadow,gdouble * hoffset,gdouble * voffset,gdouble * radius,gdouble * spread)611 _gtk_css_shadow_value_get_geometry (const GtkCssValue *shadow,
612 gdouble *hoffset,
613 gdouble *voffset,
614 gdouble *radius,
615 gdouble *spread)
616 {
617 g_return_if_fail (shadow->class == >K_CSS_VALUE_SHADOW);
618
619 if (hoffset != NULL)
620 *hoffset = _gtk_css_number_value_get (shadow->hoffset, 0);
621 if (voffset != NULL)
622 *voffset = _gtk_css_number_value_get (shadow->voffset, 0);
623
624 if (radius != NULL)
625 *radius = _gtk_css_number_value_get (shadow->radius, 0);
626 if (spread != NULL)
627 *spread = _gtk_css_number_value_get (shadow->spread, 0);
628 }
629
630 static gboolean
has_empty_clip(cairo_t * cr)631 has_empty_clip (cairo_t *cr)
632 {
633 double x1, y1, x2, y2;
634
635 cairo_clip_extents (cr, &x1, &y1, &x2, &y2);
636 return x1 == x2 && y1 == y2;
637 }
638
639 static void
draw_shadow(const GtkCssValue * shadow,cairo_t * cr,GtkRoundedBox * box,GtkRoundedBox * clip_box,GtkBlurFlags blur_flags)640 draw_shadow (const GtkCssValue *shadow,
641 cairo_t *cr,
642 GtkRoundedBox *box,
643 GtkRoundedBox *clip_box,
644 GtkBlurFlags blur_flags)
645 {
646 cairo_t *shadow_cr;
647 gboolean do_blur;
648
649 if (has_empty_clip (cr))
650 return;
651
652 gdk_cairo_set_source_rgba (cr, _gtk_css_rgba_value_get_rgba (shadow->color));
653 do_blur = (blur_flags & (GTK_BLUR_X | GTK_BLUR_Y)) != 0;
654 if (do_blur)
655 shadow_cr = gtk_css_shadow_value_start_drawing (shadow, cr, blur_flags);
656 else
657 shadow_cr = cr;
658
659 cairo_set_fill_rule (shadow_cr, CAIRO_FILL_RULE_EVEN_ODD);
660 _gtk_rounded_box_path (box, shadow_cr);
661 if (shadow->inset)
662 _gtk_rounded_box_clip_path (clip_box, shadow_cr);
663
664 cairo_fill (shadow_cr);
665
666 if (do_blur)
667 gtk_css_shadow_value_finish_drawing (shadow, shadow_cr, blur_flags);
668 }
669
670 typedef struct {
671 gint radius;
672 /* rounded box corner */
673 gint corner_horizontal;
674 gint corner_vertical;
675 } CornerMask;
676
677 static guint
corner_mask_hash(CornerMask * mask)678 corner_mask_hash (CornerMask *mask)
679 {
680 return ((guint)mask->radius) << 24 ^
681 ((guint)mask->corner_horizontal) << 12 ^
682 ((guint)mask->corner_vertical) << 0;
683 }
684
685 static gboolean
corner_mask_equal(CornerMask * mask1,CornerMask * mask2)686 corner_mask_equal (CornerMask *mask1,
687 CornerMask *mask2)
688 {
689 return
690 mask1->radius == mask2->radius &&
691 mask1->corner_horizontal == mask2->corner_horizontal &&
692 mask1->corner_vertical == mask2->corner_vertical;
693 }
694
695 static gint
truncate_to_int(double val)696 truncate_to_int (double val)
697 {
698 if (isnan (val))
699 return 0;
700 if (val >= G_MAXINT)
701 return G_MAXINT;
702 if (val <= G_MININT)
703 return G_MININT;
704 return (gint) val;
705 }
706
707 static inline gint
round_to_int(double val)708 round_to_int (double val)
709 {
710 return truncate_to_int (val + (val > 0 ? 0.5 : -0.5));
711 }
712
713 static inline gint
quantize_to_int(double val)714 quantize_to_int (double val)
715 {
716 const double precision_factor = 10.0;
717 return round_to_int (val * precision_factor);
718 }
719
720 static void
draw_shadow_corner(const GtkCssValue * shadow,cairo_t * cr,GtkRoundedBox * box,GtkRoundedBox * clip_box,GtkCssCorner corner,cairo_rectangle_int_t * drawn_rect)721 draw_shadow_corner (const GtkCssValue *shadow,
722 cairo_t *cr,
723 GtkRoundedBox *box,
724 GtkRoundedBox *clip_box,
725 GtkCssCorner corner,
726 cairo_rectangle_int_t *drawn_rect)
727 {
728 gdouble radius, clip_radius;
729 int x1, x2, x3, y1, y2, y3, x, y;
730 GtkRoundedBox corner_box;
731 cairo_t *mask_cr;
732 cairo_surface_t *mask;
733 cairo_pattern_t *pattern;
734 cairo_matrix_t matrix;
735 double sx, sy;
736 static GHashTable *corner_mask_cache = NULL;
737 double max_other;
738 CornerMask key;
739 gboolean overlapped;
740
741 radius = _gtk_css_number_value_get (shadow->radius, 0);
742 clip_radius = _gtk_cairo_blur_compute_pixels (radius);
743
744 overlapped = FALSE;
745 if (corner == GTK_CSS_TOP_LEFT || corner == GTK_CSS_BOTTOM_LEFT)
746 {
747 x1 = floor (box->box.x - clip_radius);
748 x2 = ceil (box->box.x + box->corner[corner].horizontal + clip_radius);
749 x = x1;
750 sx = 1;
751 max_other = MAX(box->corner[GTK_CSS_TOP_RIGHT].horizontal, box->corner[GTK_CSS_BOTTOM_RIGHT].horizontal);
752 x3 = floor (box->box.x + box->box.width - max_other - clip_radius);
753 if (x2 > x3)
754 overlapped = TRUE;
755 }
756 else
757 {
758 x1 = floor (box->box.x + box->box.width - box->corner[corner].horizontal - clip_radius);
759 x2 = ceil (box->box.x + box->box.width + clip_radius);
760 x = x2;
761 sx = -1;
762 max_other = MAX(box->corner[GTK_CSS_TOP_LEFT].horizontal, box->corner[GTK_CSS_BOTTOM_LEFT].horizontal);
763 x3 = ceil (box->box.x + max_other + clip_radius);
764 if (x3 > x1)
765 overlapped = TRUE;
766 }
767
768 if (corner == GTK_CSS_TOP_LEFT || corner == GTK_CSS_TOP_RIGHT)
769 {
770 y1 = floor (box->box.y - clip_radius);
771 y2 = ceil (box->box.y + box->corner[corner].vertical + clip_radius);
772 y = y1;
773 sy = 1;
774 max_other = MAX(box->corner[GTK_CSS_BOTTOM_LEFT].vertical, box->corner[GTK_CSS_BOTTOM_RIGHT].vertical);
775 y3 = floor (box->box.y + box->box.height - max_other - clip_radius);
776 if (y2 > y3)
777 overlapped = TRUE;
778 }
779 else
780 {
781 y1 = floor (box->box.y + box->box.height - box->corner[corner].vertical - clip_radius);
782 y2 = ceil (box->box.y + box->box.height + clip_radius);
783 y = y2;
784 sy = -1;
785 max_other = MAX(box->corner[GTK_CSS_TOP_LEFT].vertical, box->corner[GTK_CSS_TOP_RIGHT].vertical);
786 y3 = ceil (box->box.y + max_other + clip_radius);
787 if (y3 > y1)
788 overlapped = TRUE;
789 }
790
791 drawn_rect->x = x1;
792 drawn_rect->y = y1;
793 drawn_rect->width = x2 - x1;
794 drawn_rect->height = y2 - y1;
795
796 cairo_rectangle (cr, x1, y1, x2 - x1, y2 - y1);
797 cairo_clip (cr);
798
799 if (shadow->inset || overlapped)
800 {
801 /* Fall back to generic path if inset or if the corner radius
802 runs into each other */
803 draw_shadow (shadow, cr, box, clip_box, GTK_BLUR_X | GTK_BLUR_Y);
804 return;
805 }
806
807 if (has_empty_clip (cr))
808 return;
809
810 /* At this point we're drawing a blurred outset corner. The only
811 * things that affect the output of the blurred mask in this case
812 * is:
813 *
814 * What corner this is, which defines the orientation (sx,sy)
815 * and position (x,y)
816 *
817 * The blur radius (which also defines the clip_radius)
818 *
819 * The horizontal and vertical corner radius
820 *
821 * We apply the first position and orientation when drawing the
822 * mask, so we cache rendered masks based on the blur radius and the
823 * corner radius.
824 */
825 if (corner_mask_cache == NULL)
826 corner_mask_cache = g_hash_table_new_full ((GHashFunc)corner_mask_hash,
827 (GEqualFunc)corner_mask_equal,
828 g_free, (GDestroyNotify)cairo_surface_destroy);
829
830 key.radius = quantize_to_int (radius);
831 key.corner_horizontal = quantize_to_int (box->corner[corner].horizontal);
832 key.corner_vertical = quantize_to_int (box->corner[corner].vertical);
833
834 mask = g_hash_table_lookup (corner_mask_cache, &key);
835 if (mask == NULL)
836 {
837 mask = cairo_surface_create_similar_image (cairo_get_target (cr), CAIRO_FORMAT_A8,
838 drawn_rect->width + clip_radius,
839 drawn_rect->height + clip_radius);
840 mask_cr = cairo_create (mask);
841 _gtk_rounded_box_init_rect (&corner_box, clip_radius, clip_radius, 2*drawn_rect->width, 2*drawn_rect->height);
842 corner_box.corner[0] = box->corner[corner];
843 _gtk_rounded_box_path (&corner_box, mask_cr);
844 cairo_fill (mask_cr);
845 _gtk_cairo_blur_surface (mask, radius, GTK_BLUR_X | GTK_BLUR_Y);
846 cairo_destroy (mask_cr);
847
848 if (g_hash_table_size (corner_mask_cache) >= CORNER_MASK_CACHE_MAX_SIZE)
849 {
850 GHashTableIter iter;
851 guint i = 0;
852
853 g_hash_table_iter_init (&iter, corner_mask_cache);
854 while (g_hash_table_iter_next (&iter, NULL, NULL))
855 if (i++ % 4 == 0)
856 g_hash_table_iter_remove (&iter);
857 }
858 g_hash_table_insert (corner_mask_cache, g_memdup (&key, sizeof (key)), mask);
859 }
860
861 gdk_cairo_set_source_rgba (cr, _gtk_css_rgba_value_get_rgba (shadow->color));
862 pattern = cairo_pattern_create_for_surface (mask);
863 cairo_matrix_init_identity (&matrix);
864 cairo_matrix_scale (&matrix, sx, sy);
865 cairo_matrix_translate (&matrix, -x, -y);
866 cairo_pattern_set_matrix (pattern, &matrix);
867 cairo_mask (cr, pattern);
868 cairo_pattern_destroy (pattern);
869 }
870
871 static void
draw_shadow_side(const GtkCssValue * shadow,cairo_t * cr,GtkRoundedBox * box,GtkRoundedBox * clip_box,GtkCssSide side,cairo_rectangle_int_t * drawn_rect)872 draw_shadow_side (const GtkCssValue *shadow,
873 cairo_t *cr,
874 GtkRoundedBox *box,
875 GtkRoundedBox *clip_box,
876 GtkCssSide side,
877 cairo_rectangle_int_t *drawn_rect)
878 {
879 GtkBlurFlags blur_flags = GTK_BLUR_REPEAT;
880 gdouble radius, clip_radius;
881 int x1, x2, y1, y2;
882
883 radius = _gtk_css_number_value_get (shadow->radius, 0);
884 clip_radius = _gtk_cairo_blur_compute_pixels (radius);
885
886 if (side == GTK_CSS_TOP || side == GTK_CSS_BOTTOM)
887 {
888 blur_flags |= GTK_BLUR_Y;
889 x1 = floor (box->box.x - clip_radius);
890 x2 = ceil (box->box.x + box->box.width + clip_radius);
891 }
892 else if (side == GTK_CSS_LEFT)
893 {
894 x1 = floor (box->box.x -clip_radius);
895 x2 = ceil (box->box.x + clip_radius);
896 }
897 else
898 {
899 x1 = floor (box->box.x + box->box.width -clip_radius);
900 x2 = ceil (box->box.x + box->box.width + clip_radius);
901 }
902
903 if (side == GTK_CSS_LEFT || side == GTK_CSS_RIGHT)
904 {
905 blur_flags |= GTK_BLUR_X;
906 y1 = floor (box->box.y - clip_radius);
907 y2 = ceil (box->box.y + box->box.height + clip_radius);
908 }
909 else if (side == GTK_CSS_TOP)
910 {
911 y1 = floor (box->box.y -clip_radius);
912 y2 = ceil (box->box.y + clip_radius);
913 }
914 else
915 {
916 y1 = floor (box->box.y + box->box.height -clip_radius);
917 y2 = ceil (box->box.y + box->box.height + clip_radius);
918 }
919
920 drawn_rect->x = x1;
921 drawn_rect->y = y1;
922 drawn_rect->width = x2 - x1;
923 drawn_rect->height = y2 - y1;
924
925 cairo_rectangle (cr, x1, y1, x2 - x1, y2 - y1);
926 cairo_clip (cr);
927 draw_shadow (shadow, cr, box, clip_box, blur_flags);
928 }
929
930 void
_gtk_css_shadow_value_paint_box(const GtkCssValue * shadow,cairo_t * cr,const GtkRoundedBox * padding_box)931 _gtk_css_shadow_value_paint_box (const GtkCssValue *shadow,
932 cairo_t *cr,
933 const GtkRoundedBox *padding_box)
934 {
935 GtkRoundedBox box, clip_box;
936 double spread, radius, clip_radius, x, y, outside;
937 double x1c, y1c, x2c, y2c;
938
939 g_return_if_fail (shadow->class == >K_CSS_VALUE_SHADOW);
940
941 /* We don't need to draw invisible shadows */
942 if (gtk_rgba_is_clear (_gtk_css_rgba_value_get_rgba (shadow->color)))
943 return;
944
945 cairo_clip_extents (cr, &x1c, &y1c, &x2c, &y2c);
946 if ((shadow->inset && !_gtk_rounded_box_intersects_rectangle (padding_box, x1c, y1c, x2c, y2c)) ||
947 (!shadow->inset && _gtk_rounded_box_contains_rectangle (padding_box, x1c, y1c, x2c, y2c)))
948 return;
949
950 cairo_save (cr);
951
952 spread = _gtk_css_number_value_get (shadow->spread, 0);
953 radius = _gtk_css_number_value_get (shadow->radius, 0);
954 clip_radius = _gtk_cairo_blur_compute_pixels (radius);
955 x = _gtk_css_number_value_get (shadow->hoffset, 0);
956 y = _gtk_css_number_value_get (shadow->voffset, 0);
957
958 if (shadow->inset)
959 {
960 _gtk_rounded_box_path (padding_box, cr);
961 cairo_clip (cr);
962 }
963 else
964 {
965 cairo_set_fill_rule (cr, CAIRO_FILL_RULE_EVEN_ODD);
966 _gtk_rounded_box_path (padding_box, cr);
967 outside = spread + clip_radius + MAX (fabs (x), fabs (y));
968 clip_box = *padding_box;
969 _gtk_rounded_box_grow (&clip_box, outside, outside, outside, outside);
970 _gtk_rounded_box_clip_path (&clip_box, cr);
971
972 cairo_clip (cr);
973 }
974
975 box = *padding_box;
976 _gtk_rounded_box_move (&box, x, y);
977
978 if (shadow->inset)
979 _gtk_rounded_box_shrink (&box, spread, spread, spread, spread);
980 else /* Outset */
981 _gtk_rounded_box_grow (&box, spread, spread, spread, spread);
982
983 clip_box = *padding_box;
984 _gtk_rounded_box_shrink (&clip_box, -clip_radius, -clip_radius, -clip_radius, -clip_radius);
985
986 if (!needs_blur (shadow))
987 draw_shadow (shadow, cr, &box, &clip_box, GTK_BLUR_NONE);
988 else
989 {
990 int i;
991 cairo_region_t *remaining;
992 cairo_rectangle_int_t r;
993
994 /* For the blurred case we divide the rendering into 9 parts,
995 * 4 of the corners, 4 for the horizonat/vertical lines and
996 * one for the interior. We make the non-interior parts
997 * large enought to fit the full radius of the blur, so that
998 * the interior part can be drawn solidly.
999 */
1000
1001 if (shadow->inset)
1002 {
1003 /* In the inset case we want to paint the whole clip-box.
1004 * We could remove the part of "box" where the blur doesn't
1005 * reach, but computing that is a bit tricky since the
1006 * rounded corners are on the "inside" of it. */
1007 r.x = floor (clip_box.box.x);
1008 r.y = floor (clip_box.box.y);
1009 r.width = ceil (clip_box.box.x + clip_box.box.width) - r.x;
1010 r.height = ceil (clip_box.box.y + clip_box.box.height) - r.y;
1011 remaining = cairo_region_create_rectangle (&r);
1012 }
1013 else
1014 {
1015 /* In the outset case we want to paint the entire box, plus as far
1016 * as the radius reaches from it */
1017 r.x = floor (box.box.x - clip_radius);
1018 r.y = floor (box.box.y - clip_radius);
1019 r.width = ceil (box.box.x + box.box.width + clip_radius) - r.x;
1020 r.height = ceil (box.box.y + box.box.height + clip_radius) - r.y;
1021
1022 remaining = cairo_region_create_rectangle (&r);
1023 }
1024
1025 /* First do the corners of box */
1026 for (i = 0; i < 4; i++)
1027 {
1028 cairo_save (cr);
1029 /* Always clip with remaining to ensure we never draw any area twice */
1030 gdk_cairo_region (cr, remaining);
1031 cairo_clip (cr);
1032 draw_shadow_corner (shadow, cr, &box, &clip_box, i, &r);
1033 cairo_restore (cr);
1034
1035 /* We drew the region, remove it from remaining */
1036 cairo_region_subtract_rectangle (remaining, &r);
1037 }
1038
1039 /* Then the sides */
1040 for (i = 0; i < 4; i++)
1041 {
1042 cairo_save (cr);
1043 /* Always clip with remaining to ensure we never draw any area twice */
1044 gdk_cairo_region (cr, remaining);
1045 cairo_clip (cr);
1046 draw_shadow_side (shadow, cr, &box, &clip_box, i, &r);
1047 cairo_restore (cr);
1048
1049 /* We drew the region, remove it from remaining */
1050 cairo_region_subtract_rectangle (remaining, &r);
1051 }
1052
1053 /* Then the rest, which needs no blurring */
1054
1055 cairo_save (cr);
1056 gdk_cairo_region (cr, remaining);
1057 cairo_clip (cr);
1058 draw_shadow (shadow, cr, &box, &clip_box, GTK_BLUR_NONE);
1059 cairo_restore (cr);
1060
1061 cairo_region_destroy (remaining);
1062 }
1063
1064 cairo_restore (cr);
1065 }
1066