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
27 /**
28 * _st_actor_get_preferred_width:
29 * @actor: a #ClutterActor
30 * @for_height: as with clutter_actor_get_preferred_width()
31 * @y_fill: %TRUE if @actor will fill its allocation vertically
32 * @min_width_p: as with clutter_actor_get_preferred_width()
33 * @natural_width_p: as with clutter_actor_get_preferred_width()
34 *
35 * Like clutter_actor_get_preferred_width(), but if @y_fill is %FALSE,
36 * then it will compute a width request based on the assumption that
37 * @actor will be given an allocation no taller than its natural
38 * height.
39 */
40 void
41 _st_actor_get_preferred_width (ClutterActor *actor,
42 gfloat for_height,
43 gboolean y_fill,
44 gfloat *min_width_p,
45 gfloat *natural_width_p)
46 {
47 if (!y_fill && for_height != -1)
48 {
49 ClutterRequestMode mode;
50 gfloat natural_height;
51
52 mode = clutter_actor_get_request_mode (actor);
53 if (mode == CLUTTER_REQUEST_WIDTH_FOR_HEIGHT)
54 {
55 clutter_actor_get_preferred_height (actor, -1, NULL, &natural_height);
56 if (for_height > natural_height)
57 for_height = natural_height;
58 }
59 }
60
61 clutter_actor_get_preferred_width (actor, for_height, min_width_p, natural_width_p);
62 }
63
64 /**
65 * _st_actor_get_preferred_height:
66 * @actor: a #ClutterActor
67 * @for_width: as with clutter_actor_get_preferred_height()
68 * @x_fill: %TRUE if @actor will fill its allocation horizontally
69 * @min_height_p: as with clutter_actor_get_preferred_height()
70 * @natural_height_p: as with clutter_actor_get_preferred_height()
71 *
72 * Like clutter_actor_get_preferred_height(), but if @x_fill is
73 * %FALSE, then it will compute a height request based on the
74 * assumption that @actor will be given an allocation no wider than
75 * its natural width.
76 */
77 void
78 _st_actor_get_preferred_height (ClutterActor *actor,
79 gfloat for_width,
80 gboolean x_fill,
81 gfloat *min_height_p,
82 gfloat *natural_height_p)
83 {
84 if (!x_fill && for_width != -1)
85 {
86 ClutterRequestMode mode;
87 gfloat natural_width;
88
89 mode = clutter_actor_get_request_mode (actor);
90 if (mode == CLUTTER_REQUEST_HEIGHT_FOR_WIDTH)
91 {
92 clutter_actor_get_preferred_width (actor, -1, NULL, &natural_width);
93 if (for_width > natural_width)
94 for_width = natural_width;
95 }
96 }
97
98 clutter_actor_get_preferred_height (actor, for_width, min_height_p, natural_height_p);
99 }
100
101 /**
102 * _st_get_align_factors:
103 * @x_align: an #StAlign
104 * @y_align: an #StAlign
105 * @x_align_out: (out) (allow-none): @x_align as a #gdouble
106 * @y_align_out: (out) (allow-none): @y_align as a #gdouble
107 *
108 * Converts @x_align and @y_align to #gdouble values.
109 */
110 void
111 _st_get_align_factors (StAlign x_align,
112 StAlign y_align,
113 gdouble *x_align_out,
114 gdouble *y_align_out)
115 {
116 if (x_align_out)
117 {
118 switch (x_align)
119 {
120 case ST_ALIGN_START:
121 *x_align_out = 0.0;
122 break;
123
124 case ST_ALIGN_MIDDLE:
125 *x_align_out = 0.5;
126 break;
127
128 case ST_ALIGN_END:
129 *x_align_out = 1.0;
130 break;
131
132 default:
133 g_warn_if_reached ();
134 break;
135 }
136 }
137
138 if (y_align_out)
139 {
140 switch (y_align)
141 {
142 case ST_ALIGN_START:
143 *y_align_out = 0.0;
144 break;
145
146 case ST_ALIGN_MIDDLE:
147 *y_align_out = 0.5;
148 break;
149
150 case ST_ALIGN_END:
151 *y_align_out = 1.0;
152 break;
153
154 default:
155 g_warn_if_reached ();
156 break;
157 }
158 }
159 }
160
161 /**
162 * _st_set_text_from_style:
163 * @text: Target #ClutterText
164 * @theme_node: Source #StThemeNode
165 *
166 * Set various GObject properties of the @text object using
167 * CSS information from @theme_node.
168 */
169 void
170 _st_set_text_from_style (ClutterText *text,
171 StThemeNode *theme_node)
172 {
173
174 ClutterColor color;
175 StTextDecoration decoration;
176 PangoAttrList *attribs;
177 const PangoFontDescription *font;
178 gchar *font_string;
179 StTextAlign align;
180
181 st_theme_node_get_foreground_color (theme_node, &color);
182 clutter_text_set_color (text, &color);
183
184 font = st_theme_node_get_font (theme_node);
185 font_string = pango_font_description_to_string (font);
186 clutter_text_set_font_name (text, font_string);
187 g_free (font_string);
188
189 attribs = pango_attr_list_new ();
190
191 decoration = st_theme_node_get_text_decoration (theme_node);
192 if (decoration & ST_TEXT_DECORATION_UNDERLINE)
193 {
194 PangoAttribute *underline = pango_attr_underline_new (PANGO_UNDERLINE_SINGLE);
195 pango_attr_list_insert (attribs, underline);
196 }
197 if (decoration & ST_TEXT_DECORATION_LINE_THROUGH)
198 {
199 PangoAttribute *strikethrough = pango_attr_strikethrough_new (TRUE);
200 pango_attr_list_insert (attribs, strikethrough);
201 }
202 /* Pango doesn't have an equivalent attribute for _OVERLINE, and we deliberately
203 * skip BLINK (for now...)
204 */
205
206 clutter_text_set_attributes (text, attribs);
207
208 pango_attr_list_unref (attribs);
209
210 align = st_theme_node_get_text_align (theme_node);
211 if(align == ST_TEXT_ALIGN_JUSTIFY) {
212 clutter_text_set_justify (text, TRUE);
213 clutter_text_set_line_alignment (text, PANGO_ALIGN_LEFT);
214 } else {
215 clutter_text_set_justify (text, FALSE);
216 clutter_text_set_line_alignment (text, (PangoAlignment) align);
217 }
218 }
219
220 /**
221 * _st_create_texture_material:
222 * @src_texture: The CoglTexture for the material
223 *
224 * Creates a simple material which contains the given texture as a
225 * single layer.
226 */
227 CoglHandle
228 _st_create_texture_material (CoglHandle src_texture)
229 {
230 static CoglHandle texture_material_template = COGL_INVALID_HANDLE;
231 CoglHandle material;
232
233 g_return_val_if_fail (src_texture != COGL_INVALID_HANDLE,
234 COGL_INVALID_HANDLE);
235
236 /* We use a material that has a dummy texture as a base for all
237 texture materials. The idea is that only the Cogl texture object
238 would be different in the children so it is likely that Cogl will
239 be able to share GL programs between all the textures. */
240 if (G_UNLIKELY (texture_material_template == COGL_INVALID_HANDLE))
241 {
242 static const guint8 white_pixel[] = { 0xff, 0xff, 0xff, 0xff };
243 CoglHandle dummy_texture;
244
245 dummy_texture =
246 cogl_texture_new_from_data (1, 1,
247 COGL_TEXTURE_NONE,
248 COGL_PIXEL_FORMAT_RGBA_8888_PRE,
249 COGL_PIXEL_FORMAT_ANY,
250 4, white_pixel);
251
252 texture_material_template = cogl_material_new ();
253 cogl_material_set_layer (texture_material_template, 0, dummy_texture);
254 cogl_handle_unref (dummy_texture);
255 }
256
257 material = cogl_material_copy (texture_material_template);
258
259 cogl_material_set_layer (material, 0, src_texture);
260
261 return material;
262 }
263
264 /*****
265 * Shadows
266 *****/
267
268 static gdouble *
269 calculate_gaussian_kernel (gdouble sigma,
270 guint 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 *
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 CoglHandle
405 _st_create_shadow_material (StShadow *shadow_spec,
406 CoglHandle src_texture)
407 {
408 static CoglHandle shadow_material_template = COGL_INVALID_HANDLE;
409
410 CoglHandle material;
411 CoglHandle 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, COGL_INVALID_HANDLE);
417 g_return_val_if_fail (src_texture != COGL_INVALID_HANDLE,
418 COGL_INVALID_HANDLE);
419
420 width_in = cogl_texture_get_width (src_texture);
421 height_in = cogl_texture_get_height (src_texture);
422 rowstride_in = (width_in + 3) & ~3;
423
424 pixels_in = g_malloc0 (rowstride_in * height_in);
425
426 cogl_texture_get_data (src_texture, COGL_PIXEL_FORMAT_A_8,
427 rowstride_in, pixels_in);
428
429 pixels_out = blur_pixels (pixels_in, width_in, height_in, rowstride_in,
430 shadow_spec->blur,
431 &width_out, &height_out, &rowstride_out);
432 g_free (pixels_in);
433
434 texture = cogl_texture_new_from_data (width_out,
435 height_out,
436 COGL_TEXTURE_NONE,
437 COGL_PIXEL_FORMAT_A_8,
438 COGL_PIXEL_FORMAT_A_8,
439 rowstride_out,
440 pixels_out);
441
442 g_free (pixels_out);
443
444 if (G_UNLIKELY (shadow_material_template == COGL_INVALID_HANDLE))
445 {
446 shadow_material_template = cogl_material_new ();
447
448 /* We set up the material to blend the shadow texture with the combine
449 * constant, but defer setting the latter until painting, so that we can
450 * take the actor's overall opacity into account. */
451 cogl_material_set_layer_combine (shadow_material_template, 0,
452 "RGBA = MODULATE (CONSTANT, TEXTURE[A])",
453 NULL);
454 }
455
456 material = cogl_material_copy (shadow_material_template);
457
458 cogl_material_set_layer (material, 0, texture);
459
460 cogl_handle_unref (texture);
461
462 return material;
463 }
464
465 CoglHandle
466 _st_create_shadow_material_from_actor (StShadow *shadow_spec,
467 ClutterActor *actor)
468 {
469 CoglHandle shadow_material = COGL_INVALID_HANDLE;
470
471 if (CLUTTER_IS_TEXTURE (actor))
472 {
473 CoglHandle texture;
474
475 texture = clutter_texture_get_cogl_texture (CLUTTER_TEXTURE (actor));
'clutter_texture_get_cogl_texture' is deprecated (declared at /usr/include/clutter-1.0/clutter/deprecated/clutter-texture.h:78)
(emitted by gcc)
476 shadow_material = _st_create_shadow_material (shadow_spec, texture);
477 }
478 else
479 {
480 CoglHandle buffer, offscreen;
481 ClutterActorBox box;
482 CoglColor clear_color;
483 float width, height;
484
485 clutter_actor_get_allocation_box (actor, &box);
486 clutter_actor_box_get_size (&box, &width, &height);
487
488 if (width == 0 || height == 0)
489 return COGL_INVALID_HANDLE;
490
491 buffer = cogl_texture_new_with_size (width,
492 height,
493 COGL_TEXTURE_NO_SLICING,
494 COGL_PIXEL_FORMAT_ANY);
495
496 if (buffer == COGL_INVALID_HANDLE)
497 return COGL_INVALID_HANDLE;
498
499 offscreen = cogl_offscreen_new_to_texture (buffer);
500
501 if (offscreen == COGL_INVALID_HANDLE)
502 {
503 cogl_handle_unref (buffer);
504 return COGL_INVALID_HANDLE;
505 }
506
507 cogl_color_set_from_4ub (&clear_color, 0, 0, 0, 0);
508 cogl_push_framebuffer (offscreen);
509 cogl_clear (&clear_color, COGL_BUFFER_BIT_COLOR);
510 cogl_ortho (0, width, height, 0, 0, 1.0);
511 clutter_actor_paint (actor);
512 cogl_pop_framebuffer ();
513 cogl_handle_unref (offscreen);
514
515 shadow_material = _st_create_shadow_material (shadow_spec, buffer);
516
517 cogl_handle_unref (buffer);
518 }
519
520 return shadow_material;
521 }
522
523 /**
524 * _st_create_shadow_cairo_pattern:
525 * @shadow_spec: the definition of the shadow
526 * @src_pattern: surface pattern for which we create the shadow
527 * (must be a surface pattern)
528 *
529 * This is a utility function for creating shadows used by
530 * st-theme-node.c; it's in this file to share the gaussian
531 * blur implementation. The usage of this function is quite different
532 * depending on whether shadow_spec->inset is %TRUE or not. If
533 * shadow_spec->inset is %TRUE, the caller should pass in a @src_pattern
534 * which is the <i>inverse</i> of what they want shadowed, and must take
535 * care of the spread and offset from the shadow spec themselves. If
536 * shadow_spec->inset is %FALSE then the caller should pass in what they
537 * want shadowed directly, and this function takes care of the spread and
538 * the offset.
539 */
540 cairo_pattern_t *
541 _st_create_shadow_cairo_pattern (StShadow *shadow_spec,
542 cairo_pattern_t *src_pattern)
543 {
544 static cairo_user_data_key_t shadow_pattern_user_data;
545 cairo_t *cr;
546 cairo_surface_t *src_surface;
547 cairo_surface_t *surface_in;
548 cairo_surface_t *surface_out;
549 cairo_pattern_t *dst_pattern;
550 guchar *pixels_in, *pixels_out;
551 gint width_in, height_in, rowstride_in;
552 gint width_out, height_out, rowstride_out;
553 cairo_matrix_t shadow_matrix;
554 int i, j;
555
556 g_return_val_if_fail (shadow_spec != NULL, NULL);
557 g_return_val_if_fail (src_pattern != NULL, NULL);
558
559 cairo_pattern_get_surface (src_pattern, &src_surface);
560
561 width_in = cairo_image_surface_get_width (src_surface);
562 height_in = cairo_image_surface_get_height (src_surface);
563
564 /* We want the output to be a color agnostic alpha mask,
565 * so we need to strip the color channels from the input
566 */
567 if (cairo_image_surface_get_format (src_surface) != CAIRO_FORMAT_A8)
568 {
569 surface_in = cairo_image_surface_create (CAIRO_FORMAT_A8,
570 width_in, height_in);
571
572 cr = cairo_create (surface_in);
573 cairo_set_source_surface (cr, src_surface, 0, 0);
574 cairo_paint (cr);
575 cairo_destroy (cr);
576 }
577 else
578 {
579 surface_in = cairo_surface_reference (src_surface);
580 }
581
582 pixels_in = cairo_image_surface_get_data (surface_in);
583 rowstride_in = cairo_image_surface_get_stride (surface_in);
584
585 pixels_out = blur_pixels (pixels_in, width_in, height_in, rowstride_in,
586 shadow_spec->blur,
587 &width_out, &height_out, &rowstride_out);
588 cairo_surface_destroy (surface_in);
589
590 /* Invert pixels for inset shadows */
591 if (shadow_spec->inset)
592 {
593 for (j = 0; j < height_out; j++)
594 {
595 guchar *p = pixels_out + rowstride_out * j;
596 for (i = 0; i < width_out; i++, p++)
597 *p = ~*p;
598 }
599 }
600
601 surface_out = cairo_image_surface_create_for_data (pixels_out,
602 CAIRO_FORMAT_A8,
603 width_out,
604 height_out,
605 rowstride_out);
606 cairo_surface_set_user_data (surface_out, &shadow_pattern_user_data,
607 pixels_out, (cairo_destroy_func_t) g_free);
608
609 dst_pattern = cairo_pattern_create_for_surface (surface_out);
610 cairo_surface_destroy (surface_out);
611
612 cairo_pattern_get_matrix (src_pattern, &shadow_matrix);
613
614 if (shadow_spec->inset)
615 {
616 /* For inset shadows, offsets and spread radius have already been
617 * applied to the original pattern, so all left to do is shift the
618 * blurred image left, so that it aligns centered under the
619 * unblurred one
620 */
621 cairo_matrix_translate (&shadow_matrix,
622 (width_out - width_in) / 2.0,
623 (height_out - height_in) / 2.0);
624 cairo_pattern_set_matrix (dst_pattern, &shadow_matrix);
625 return dst_pattern;
626 }
627
628 /* Read all the code from the cairo_pattern_set_matrix call
629 * at the end of this function to here from bottom to top,
630 * because each new affine transformation is applied in
631 * front of all the previous ones */
632
633 /* 6. Invert the matrix back */
634 cairo_matrix_invert (&shadow_matrix);
635
636 /* 5. Adjust based on specified offsets */
637 cairo_matrix_translate (&shadow_matrix,
638 shadow_spec->xoffset,
639 shadow_spec->yoffset);
640
641 /* 4. Recenter the newly scaled image */
642 cairo_matrix_translate (&shadow_matrix,
643 - shadow_spec->spread,
644 - shadow_spec->spread);
645
646 /* 3. Scale up the blurred image to fill the spread */
647 cairo_matrix_scale (&shadow_matrix,
648 (width_in + 2.0 * shadow_spec->spread) / width_in,
649 (height_in + 2.0 * shadow_spec->spread) / height_in);
650
651 /* 2. Shift the blurred image left, so that it aligns centered
652 * under the unblurred one */
653 cairo_matrix_translate (&shadow_matrix,
654 - (width_out - width_in) / 2.0,
655 - (height_out - height_in) / 2.0);
656
657 /* 1. Invert the matrix so we can work with it in pattern space
658 */
659 cairo_matrix_invert (&shadow_matrix);
660
661 cairo_pattern_set_matrix (dst_pattern, &shadow_matrix);
662
663 return dst_pattern;
664 }
665
666 void
667 _st_paint_shadow_with_opacity (StShadow *shadow_spec,
668 CoglHandle shadow_material,
669 ClutterActorBox *box,
670 guint8 paint_opacity)
671 {
672 ClutterActorBox shadow_box;
673 CoglColor color;
674
675 g_return_if_fail (shadow_spec != NULL);
676 g_return_if_fail (shadow_material != COGL_INVALID_HANDLE);
677
678 st_shadow_get_box (shadow_spec, box, &shadow_box);
679
680 cogl_color_set_from_4ub (&color,
681 shadow_spec->color.red * paint_opacity / 255,
682 shadow_spec->color.green * paint_opacity / 255,
683 shadow_spec->color.blue * paint_opacity / 255,
684 shadow_spec->color.alpha * paint_opacity / 255);
685 cogl_color_premultiply (&color);
686
687 cogl_material_set_layer_combine_constant (shadow_material, 0, &color);
688
689 cogl_set_source (shadow_material);
690 cogl_rectangle_with_texture_coords (shadow_box.x1, shadow_box.y1,
691 shadow_box.x2, shadow_box.y2,
692 0, 0, 1, 1);
693 }