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
39G_DEFINE_ABSTRACT_TYPE (GtkCssImage, _gtk_css_image, G_TYPE_OBJECT)
40
41static int
42gtk_css_image_real_get_width (GtkCssImage *image)
43{
44 return 0;
45}
46
47static int
48gtk_css_image_real_get_height (GtkCssImage *image)
49{
50 return 0;
51}
52
53static double
54gtk_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
67static GtkCssImage *
68gtk_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
77static gboolean
78gtk_css_image_real_equal (GtkCssImage *image1,
79 GtkCssImage *image2)
80{
81 return FALSE;
82}
83
84static GtkCssImage *
85gtk_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
100static gboolean
101gtk_css_image_real_is_invalid (GtkCssImage *image)
102{
103 return FALSE;
104}
105
106static gboolean
107gtk_css_image_real_is_dynamic (GtkCssImage *image)
108{
109 return FALSE;
110}
111
112static GtkCssImage *
113gtk_css_image_real_get_dynamic_image (GtkCssImage *image,
114 gint64 monotonic_time)
115{
116 return g_object_ref (image);
117}
118
119static gboolean
120gtk_css_image_real_is_computed (GtkCssImage *image)
121{
122 return FALSE;
123}
124
125static 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
140static void
141_gtk_css_image_init (GtkCssImage *image)
142{
143}
144
145int
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
155int
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
165double
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
175GtkCssImage *
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
193GtkCssImage *
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
223gboolean
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
246void
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
275void
276gtk_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
293gboolean
294gtk_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
305gboolean
306gtk_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
317GtkCssImage *
318gtk_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
330void
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
344char *
345gtk_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 */
358void
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
480cairo_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
510static GType
511gtk_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 **/
554gboolean
555_gtk_css_image_can_parse (GtkCssParser *parser)
556{
557 return gtk_css_image_get_parser_type (parser) != G_TYPE_INVALID;
558}
559
560GtkCssImage *
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
588gboolean
589gtk_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

source code of gtk/gtk/gtkcssimage.c