1 | /* |
2 | * Copyright © 2011 Red Hat Inc. |
3 | * |
4 | * This library is free software; you can redistribute it and/or |
5 | * modify it under the terms of the GNU Lesser General Public |
6 | * License as published by the Free Software Foundation; either |
7 | * version 2.1 of the License, or (at your option) any later version. |
8 | * |
9 | * This library is distributed in the hope that it will be useful, |
10 | * but WITHOUT ANY WARRANTY; without even the implied warranty of |
11 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU |
12 | * Lesser General Public License for more details. |
13 | * |
14 | * You should have received a copy of the GNU Lesser General Public |
15 | * License along with this library. If not, see <http://www.gnu.org/licenses/>. |
16 | * |
17 | * Authors: Benjamin Otte <otte@gnome.org> |
18 | */ |
19 | |
20 | #include "config.h" |
21 | |
22 | #include "gtkcssimageprivate.h" |
23 | |
24 | #include "gtkcssstyleprivate.h" |
25 | #include "gtksnapshot.h" |
26 | #include "gtkprivate.h" |
27 | |
28 | /* for the types only */ |
29 | #include "gtk/gtkcssimageconicprivate.h" |
30 | #include "gtk/gtkcssimagecrossfadeprivate.h" |
31 | #include "gtk/gtkcssimagefallbackprivate.h" |
32 | #include "gtk/gtkcssimageiconthemeprivate.h" |
33 | #include "gtk/gtkcssimagelinearprivate.h" |
34 | #include "gtk/gtkcssimageradialprivate.h" |
35 | #include "gtk/gtkcssimageurlprivate.h" |
36 | #include "gtk/gtkcssimagescaledprivate.h" |
37 | #include "gtk/gtkcssimagerecolorprivate.h" |
38 | |
39 | G_DEFINE_ABSTRACT_TYPE (GtkCssImage, _gtk_css_image, G_TYPE_OBJECT) |
40 | |
41 | static int |
42 | gtk_css_image_real_get_width (GtkCssImage *image) |
43 | { |
44 | return 0; |
45 | } |
46 | |
47 | static int |
48 | gtk_css_image_real_get_height (GtkCssImage *image) |
49 | { |
50 | return 0; |
51 | } |
52 | |
53 | static double |
54 | gtk_css_image_real_get_aspect_ratio (GtkCssImage *image) |
55 | { |
56 | int width, height; |
57 | |
58 | width = _gtk_css_image_get_width (image); |
59 | height = _gtk_css_image_get_height (image); |
60 | |
61 | if (width && height) |
62 | return (double) width / height; |
63 | else |
64 | return 0; |
65 | } |
66 | |
67 | static GtkCssImage * |
68 | gtk_css_image_real_compute (GtkCssImage *image, |
69 | guint property_id, |
70 | GtkStyleProvider *provider, |
71 | GtkCssStyle *style, |
72 | GtkCssStyle *parent_style) |
73 | { |
74 | return g_object_ref (image); |
75 | } |
76 | |
77 | static gboolean |
78 | gtk_css_image_real_equal (GtkCssImage *image1, |
79 | GtkCssImage *image2) |
80 | { |
81 | return FALSE; |
82 | } |
83 | |
84 | static GtkCssImage * |
85 | gtk_css_image_real_transition (GtkCssImage *start, |
86 | GtkCssImage *end, |
87 | guint property_id, |
88 | double progress) |
89 | { |
90 | if (progress <= 0.0) |
91 | return g_object_ref (start); |
92 | else if (progress >= 1.0) |
93 | return end ? g_object_ref (end) : NULL; |
94 | else if (_gtk_css_image_equal (image1: start, image2: end)) |
95 | return g_object_ref (start); |
96 | else |
97 | return _gtk_css_image_cross_fade_new (start, end, progress); |
98 | } |
99 | |
100 | static gboolean |
101 | gtk_css_image_real_is_invalid (GtkCssImage *image) |
102 | { |
103 | return FALSE; |
104 | } |
105 | |
106 | static gboolean |
107 | gtk_css_image_real_is_dynamic (GtkCssImage *image) |
108 | { |
109 | return FALSE; |
110 | } |
111 | |
112 | static GtkCssImage * |
113 | gtk_css_image_real_get_dynamic_image (GtkCssImage *image, |
114 | gint64 monotonic_time) |
115 | { |
116 | return g_object_ref (image); |
117 | } |
118 | |
119 | static gboolean |
120 | gtk_css_image_real_is_computed (GtkCssImage *image) |
121 | { |
122 | return FALSE; |
123 | } |
124 | |
125 | static void |
126 | _gtk_css_image_class_init (GtkCssImageClass *klass) |
127 | { |
128 | klass->get_width = gtk_css_image_real_get_width; |
129 | klass->get_height = gtk_css_image_real_get_height; |
130 | klass->get_aspect_ratio = gtk_css_image_real_get_aspect_ratio; |
131 | klass->compute = gtk_css_image_real_compute; |
132 | klass->equal = gtk_css_image_real_equal; |
133 | klass->transition = gtk_css_image_real_transition; |
134 | klass->is_invalid = gtk_css_image_real_is_invalid; |
135 | klass->is_dynamic = gtk_css_image_real_is_dynamic; |
136 | klass->get_dynamic_image = gtk_css_image_real_get_dynamic_image; |
137 | klass->is_computed = gtk_css_image_real_is_computed; |
138 | } |
139 | |
140 | static void |
141 | _gtk_css_image_init (GtkCssImage *image) |
142 | { |
143 | } |
144 | |
145 | int |
146 | _gtk_css_image_get_width (GtkCssImage *image) |
147 | { |
148 | GtkCssImageClass *klass; |
149 | |
150 | klass = GTK_CSS_IMAGE_GET_CLASS (image); |
151 | |
152 | return klass->get_width (image); |
153 | } |
154 | |
155 | int |
156 | _gtk_css_image_get_height (GtkCssImage *image) |
157 | { |
158 | GtkCssImageClass *klass; |
159 | |
160 | klass = GTK_CSS_IMAGE_GET_CLASS (image); |
161 | |
162 | return klass->get_height (image); |
163 | } |
164 | |
165 | double |
166 | _gtk_css_image_get_aspect_ratio (GtkCssImage *image) |
167 | { |
168 | GtkCssImageClass *klass; |
169 | |
170 | klass = GTK_CSS_IMAGE_GET_CLASS (image); |
171 | |
172 | return klass->get_aspect_ratio (image); |
173 | } |
174 | |
175 | GtkCssImage * |
176 | _gtk_css_image_compute (GtkCssImage *image, |
177 | guint property_id, |
178 | GtkStyleProvider *provider, |
179 | GtkCssStyle *style, |
180 | GtkCssStyle *parent_style) |
181 | { |
182 | GtkCssImageClass *klass; |
183 | |
184 | gtk_internal_return_val_if_fail (GTK_IS_CSS_IMAGE (image), NULL); |
185 | gtk_internal_return_val_if_fail (GTK_IS_CSS_STYLE (style), NULL); |
186 | gtk_internal_return_val_if_fail (parent_style == NULL || GTK_IS_CSS_STYLE (parent_style), NULL); |
187 | |
188 | klass = GTK_CSS_IMAGE_GET_CLASS (image); |
189 | |
190 | return klass->compute (image, property_id, provider, style, parent_style); |
191 | } |
192 | |
193 | GtkCssImage * |
194 | _gtk_css_image_transition (GtkCssImage *start, |
195 | GtkCssImage *end, |
196 | guint property_id, |
197 | double progress) |
198 | { |
199 | GtkCssImageClass *klass; |
200 | |
201 | gtk_internal_return_val_if_fail (start == NULL || GTK_IS_CSS_IMAGE (start), NULL); |
202 | gtk_internal_return_val_if_fail (end == NULL || GTK_IS_CSS_IMAGE (end), NULL); |
203 | |
204 | progress = CLAMP (progress, 0.0, 1.0); |
205 | |
206 | if (start == NULL) |
207 | { |
208 | if (end == NULL) |
209 | return NULL; |
210 | else |
211 | { |
212 | start = end; |
213 | end = NULL; |
214 | progress = 1.0 - progress; |
215 | } |
216 | } |
217 | |
218 | klass = GTK_CSS_IMAGE_GET_CLASS (start); |
219 | |
220 | return klass->transition (start, end, property_id, progress); |
221 | } |
222 | |
223 | gboolean |
224 | _gtk_css_image_equal (GtkCssImage *image1, |
225 | GtkCssImage *image2) |
226 | { |
227 | GtkCssImageClass *klass; |
228 | |
229 | gtk_internal_return_val_if_fail (image1 == NULL || GTK_IS_CSS_IMAGE (image1), FALSE); |
230 | gtk_internal_return_val_if_fail (image2 == NULL || GTK_IS_CSS_IMAGE (image2), FALSE); |
231 | |
232 | if (image1 == image2) |
233 | return TRUE; |
234 | |
235 | if (image1 == NULL || image2 == NULL) |
236 | return FALSE; |
237 | |
238 | if (G_OBJECT_TYPE (image1) != G_OBJECT_TYPE (image2)) |
239 | return FALSE; |
240 | |
241 | klass = GTK_CSS_IMAGE_GET_CLASS (image1); |
242 | |
243 | return klass->equal (image1, image2); |
244 | } |
245 | |
246 | void |
247 | _gtk_css_image_draw (GtkCssImage *image, |
248 | cairo_t *cr, |
249 | double width, |
250 | double height) |
251 | { |
252 | GtkSnapshot *snapshot; |
253 | GskRenderNode *node; |
254 | |
255 | gtk_internal_return_if_fail (GTK_IS_CSS_IMAGE (image)); |
256 | gtk_internal_return_if_fail (cr != NULL); |
257 | gtk_internal_return_if_fail (width > 0); |
258 | gtk_internal_return_if_fail (height > 0); |
259 | |
260 | cairo_save (cr); |
261 | |
262 | snapshot = gtk_snapshot_new (); |
263 | gtk_css_image_snapshot (image, snapshot, width, height); |
264 | node = gtk_snapshot_free_to_node (snapshot); |
265 | |
266 | if (node != NULL) |
267 | { |
268 | gsk_render_node_draw (node, cr); |
269 | gsk_render_node_unref (node); |
270 | } |
271 | |
272 | cairo_restore (cr); |
273 | } |
274 | |
275 | void |
276 | gtk_css_image_snapshot (GtkCssImage *image, |
277 | GtkSnapshot *snapshot, |
278 | double width, |
279 | double height) |
280 | { |
281 | GtkCssImageClass *klass; |
282 | |
283 | gtk_internal_return_if_fail (GTK_IS_CSS_IMAGE (image)); |
284 | gtk_internal_return_if_fail (snapshot != NULL); |
285 | gtk_internal_return_if_fail (width > 0); |
286 | gtk_internal_return_if_fail (height > 0); |
287 | |
288 | klass = GTK_CSS_IMAGE_GET_CLASS (image); |
289 | |
290 | klass->snapshot (image, snapshot, width, height); |
291 | } |
292 | |
293 | gboolean |
294 | gtk_css_image_is_invalid (GtkCssImage *image) |
295 | { |
296 | GtkCssImageClass *klass; |
297 | |
298 | gtk_internal_return_val_if_fail (GTK_IS_CSS_IMAGE (image), FALSE); |
299 | |
300 | klass = GTK_CSS_IMAGE_GET_CLASS (image); |
301 | |
302 | return klass->is_invalid (image); |
303 | } |
304 | |
305 | gboolean |
306 | gtk_css_image_is_dynamic (GtkCssImage *image) |
307 | { |
308 | GtkCssImageClass *klass; |
309 | |
310 | gtk_internal_return_val_if_fail (GTK_IS_CSS_IMAGE (image), FALSE); |
311 | |
312 | klass = GTK_CSS_IMAGE_GET_CLASS (image); |
313 | |
314 | return klass->is_dynamic (image); |
315 | } |
316 | |
317 | GtkCssImage * |
318 | gtk_css_image_get_dynamic_image (GtkCssImage *image, |
319 | gint64 monotonic_time) |
320 | { |
321 | GtkCssImageClass *klass; |
322 | |
323 | gtk_internal_return_val_if_fail (GTK_IS_CSS_IMAGE (image), NULL); |
324 | |
325 | klass = GTK_CSS_IMAGE_GET_CLASS (image); |
326 | |
327 | return klass->get_dynamic_image (image, monotonic_time); |
328 | } |
329 | |
330 | void |
331 | _gtk_css_image_print (GtkCssImage *image, |
332 | GString *string) |
333 | { |
334 | GtkCssImageClass *klass; |
335 | |
336 | gtk_internal_return_if_fail (GTK_IS_CSS_IMAGE (image)); |
337 | gtk_internal_return_if_fail (string != NULL); |
338 | |
339 | klass = GTK_CSS_IMAGE_GET_CLASS (image); |
340 | |
341 | klass->print (image, string); |
342 | } |
343 | |
344 | char * |
345 | gtk_css_image_to_string (GtkCssImage *image) |
346 | { |
347 | GString *str = g_string_new (init: "" ); |
348 | |
349 | _gtk_css_image_print (image, string: str); |
350 | |
351 | return g_string_free (string: str, FALSE); |
352 | } |
353 | |
354 | |
355 | /* Applies the algorithm outlined in |
356 | * http://dev.w3.org/csswg/css3-images/#default-sizing |
357 | */ |
358 | void |
359 | _gtk_css_image_get_concrete_size (GtkCssImage *image, |
360 | double specified_width, |
361 | double specified_height, |
362 | double default_width, |
363 | double default_height, |
364 | double *concrete_width, |
365 | double *concrete_height) |
366 | { |
367 | double image_width, image_height, image_aspect; |
368 | |
369 | gtk_internal_return_if_fail (GTK_IS_CSS_IMAGE (image)); |
370 | gtk_internal_return_if_fail (specified_width >= 0); |
371 | gtk_internal_return_if_fail (specified_height >= 0); |
372 | gtk_internal_return_if_fail (default_width > 0); |
373 | gtk_internal_return_if_fail (default_height > 0); |
374 | gtk_internal_return_if_fail (concrete_width != NULL); |
375 | gtk_internal_return_if_fail (concrete_height != NULL); |
376 | |
377 | /* If the specified size is a definite width and height, |
378 | * the concrete object size is given that width and height. |
379 | */ |
380 | if (specified_width && specified_height) |
381 | { |
382 | *concrete_width = specified_width; |
383 | *concrete_height = specified_height; |
384 | return; |
385 | } |
386 | |
387 | image_width = _gtk_css_image_get_width (image); |
388 | image_height = _gtk_css_image_get_height (image); |
389 | image_aspect = _gtk_css_image_get_aspect_ratio (image); |
390 | |
391 | /* If the specified size has neither a definite width nor height, |
392 | * and has no additional constraints, the dimensions of the concrete |
393 | * object size are calculated as follows: |
394 | */ |
395 | if (specified_width == 0.0 && specified_height == 0.0) |
396 | { |
397 | /* If the object has only an intrinsic aspect ratio, |
398 | * the concrete object size must have that aspect ratio, |
399 | * and additionally be as large as possible without either |
400 | * its height or width exceeding the height or width of the |
401 | * default object size. |
402 | */ |
403 | if (image_aspect > 0 && image_width == 0 && image_height == 0) |
404 | { |
405 | if (image_aspect * default_height > default_width) |
406 | { |
407 | *concrete_width = default_width; |
408 | *concrete_height = default_width / image_aspect; |
409 | } |
410 | else |
411 | { |
412 | *concrete_width = default_height * image_aspect; |
413 | *concrete_height = default_height; |
414 | } |
415 | } |
416 | else |
417 | { |
418 | /* Otherwise, the width and height of the concrete object |
419 | * size is the same as the object's intrinsic width and |
420 | * intrinsic height, if they exist. |
421 | * If the concrete object size is still missing a width or |
422 | * height, and the object has an intrinsic aspect ratio, |
423 | * the missing dimension is calculated from the present |
424 | * dimension and the intrinsic aspect ratio. |
425 | * Otherwise, the missing dimension is taken from the default |
426 | * object size. |
427 | */ |
428 | if (image_width) |
429 | *concrete_width = image_width; |
430 | else if (image_aspect) |
431 | *concrete_width = image_height * image_aspect; |
432 | else |
433 | *concrete_width = default_width; |
434 | |
435 | if (image_height) |
436 | *concrete_height = image_height; |
437 | else if (image_aspect) |
438 | *concrete_height = image_width / image_aspect; |
439 | else |
440 | *concrete_height = default_height; |
441 | } |
442 | |
443 | return; |
444 | } |
445 | |
446 | /* If the specified size has only a width or height, but not both, |
447 | * then the concrete object size is given that specified width or height. |
448 | * The other dimension is calculated as follows: |
449 | * If the object has an intrinsic aspect ratio, the missing dimension of |
450 | * the concrete object size is calculated using the intrinsic aspect-ratio |
451 | * and the present dimension. |
452 | * Otherwise, if the missing dimension is present in the object's intrinsic |
453 | * dimensions, the missing dimension is taken from the object's intrinsic |
454 | * dimensions. |
455 | * Otherwise, the missing dimension of the concrete object size is taken |
456 | * from the default object size. |
457 | */ |
458 | if (specified_width) |
459 | { |
460 | *concrete_width = specified_width; |
461 | if (image_aspect) |
462 | *concrete_height = specified_width / image_aspect; |
463 | else if (image_height) |
464 | *concrete_height = image_height; |
465 | else |
466 | *concrete_height = default_height; |
467 | } |
468 | else |
469 | { |
470 | *concrete_height = specified_height; |
471 | if (image_aspect) |
472 | *concrete_width = specified_height * image_aspect; |
473 | else if (image_width) |
474 | *concrete_width = image_width; |
475 | else |
476 | *concrete_width = default_width; |
477 | } |
478 | } |
479 | |
480 | cairo_surface_t * |
481 | _gtk_css_image_get_surface (GtkCssImage *image, |
482 | cairo_surface_t *target, |
483 | int surface_width, |
484 | int surface_height) |
485 | { |
486 | cairo_surface_t *result; |
487 | cairo_t *cr; |
488 | |
489 | gtk_internal_return_val_if_fail (GTK_IS_CSS_IMAGE (image), NULL); |
490 | gtk_internal_return_val_if_fail (surface_width > 0, NULL); |
491 | gtk_internal_return_val_if_fail (surface_height > 0, NULL); |
492 | |
493 | if (target) |
494 | result = cairo_surface_create_similar (other: target, |
495 | content: CAIRO_CONTENT_COLOR_ALPHA, |
496 | width: surface_width, |
497 | height: surface_height); |
498 | else |
499 | result = cairo_image_surface_create (format: CAIRO_FORMAT_ARGB32, |
500 | width: surface_width, |
501 | height: surface_height); |
502 | |
503 | cr = cairo_create (target: result); |
504 | _gtk_css_image_draw (image, cr, width: surface_width, height: surface_height); |
505 | cairo_destroy (cr); |
506 | |
507 | return result; |
508 | } |
509 | |
510 | static GType |
511 | gtk_css_image_get_parser_type (GtkCssParser *parser) |
512 | { |
513 | static const struct { |
514 | const char *prefix; |
515 | GType (* type_func) (void); |
516 | } image_types[] = { |
517 | { "url" , _gtk_css_image_url_get_type }, |
518 | { "-gtk-icontheme" , _gtk_css_image_icon_theme_get_type }, |
519 | { "-gtk-scaled" , _gtk_css_image_scaled_get_type }, |
520 | { "-gtk-recolor" , _gtk_css_image_recolor_get_type }, |
521 | { "linear-gradient" , _gtk_css_image_linear_get_type }, |
522 | { "repeating-linear-gradient" , _gtk_css_image_linear_get_type }, |
523 | { "radial-gradient" , _gtk_css_image_radial_get_type }, |
524 | { "repeating-radial-gradient" , _gtk_css_image_radial_get_type }, |
525 | { "conic-gradient" , gtk_css_image_conic_get_type }, |
526 | { "cross-fade" , gtk_css_image_cross_fade_get_type }, |
527 | { "image" , _gtk_css_image_fallback_get_type } |
528 | }; |
529 | guint i; |
530 | |
531 | for (i = 0; i < G_N_ELEMENTS (image_types); i++) |
532 | { |
533 | if (gtk_css_parser_has_function (self: parser, name: image_types[i].prefix)) |
534 | return image_types[i].type_func (); |
535 | } |
536 | |
537 | if (gtk_css_parser_has_token (self: parser, token_type: GTK_CSS_TOKEN_URL)) |
538 | return _gtk_css_image_url_get_type (); |
539 | |
540 | return G_TYPE_INVALID; |
541 | } |
542 | |
543 | /** |
544 | * _gtk_css_image_can_parse: |
545 | * @parser: a css parser |
546 | * |
547 | * Checks if the parser can potentially parse the given stream as an |
548 | * image from looking at the first token of @parser. This is useful for |
549 | * implementing shorthand properties. A successful parse of an image |
550 | * can not be guaranteed. |
551 | * |
552 | * Returns: %TRUE if it looks like an image. |
553 | **/ |
554 | gboolean |
555 | _gtk_css_image_can_parse (GtkCssParser *parser) |
556 | { |
557 | return gtk_css_image_get_parser_type (parser) != G_TYPE_INVALID; |
558 | } |
559 | |
560 | GtkCssImage * |
561 | _gtk_css_image_new_parse (GtkCssParser *parser) |
562 | { |
563 | GtkCssImageClass *klass; |
564 | GtkCssImage *image; |
565 | GType image_type; |
566 | |
567 | gtk_internal_return_val_if_fail (parser != NULL, NULL); |
568 | |
569 | image_type = gtk_css_image_get_parser_type (parser); |
570 | if (image_type == G_TYPE_INVALID) |
571 | { |
572 | gtk_css_parser_error_syntax (self: parser, format: "Not a valid image" ); |
573 | return NULL; |
574 | } |
575 | |
576 | image = g_object_new (object_type: image_type, NULL); |
577 | |
578 | klass = GTK_CSS_IMAGE_GET_CLASS (image); |
579 | if (!klass->parse (image, parser)) |
580 | { |
581 | g_object_unref (object: image); |
582 | return NULL; |
583 | } |
584 | |
585 | return image; |
586 | } |
587 | |
588 | gboolean |
589 | gtk_css_image_is_computed (GtkCssImage *image) |
590 | { |
591 | GtkCssImageClass *klass = GTK_CSS_IMAGE_GET_CLASS (image); |
592 | |
593 | return klass->is_computed (image); |
594 | } |
595 | |