1 /* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */
2 /*
3 * st-texture-cache.h: Object for loading and caching images as textures
4 *
5 * Copyright 2009, 2010 Red Hat, Inc.
6 * Copyright 2010, Maxim Ermilov
7 *
8 * This program is free software; you can redistribute it and/or modify
9 * it under the terms of the GNU Lesser General Public License as
10 * published by the Free Software Foundation, either version 2.1 of
11 * the License, or (at your option) any later version.
12 *
13 * This program is distributed in the hope it will be useful, but WITHOUT ANY
14 * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
15 * FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for
16 * more details.
17 *
18 * You should have received a copy of the GNU Lesser General Public License
19 * along with this program. If not, see <http://www.gnu.org/licenses/>.
20 */
21
22 #include "config.h"
23
24 #include "st-texture-cache.h"
25 #include "st-private.h"
26 #include <gtk/gtk.h>
27 #include <string.h>
28 #include <glib.h>
29
30 #define CACHE_PREFIX_ICON "icon:"
31 #define CACHE_PREFIX_URI "uri:"
32 #define CACHE_PREFIX_URI_FOR_CAIRO "uri-for-cairo:"
33 #define CACHE_PREFIX_RAW_CHECKSUM "raw-checksum:"
34 #define CACHE_PREFIX_COMPRESSED_CHECKSUM "compressed-checksum:"
35
36 struct _StTextureCachePrivate
37 {
38 GtkIconTheme *icon_theme;
39
40 /* Things that were loaded with a cache policy != NONE */
41 GHashTable *keyed_cache; /* char * -> CoglTexture* */
42
43 /* Presently this is used to de-duplicate requests for GIcons and async URIs. */
44 GHashTable *outstanding_requests; /* char * -> AsyncTextureLoadData * */
45 };
46
47 static void st_texture_cache_dispose (GObject *object);
48 static void st_texture_cache_finalize (GObject *object);
49
50 enum
51 {
52 ICON_THEME_CHANGED,
53
54 LAST_SIGNAL
55 };
56
57 static guint signals[LAST_SIGNAL] = { 0, };
58 G_DEFINE_TYPE(StTextureCache, st_texture_cache, G_TYPE_OBJECT);
59
60 /* We want to preserve the aspect ratio by default, also the default
61 * material for an empty texture is full opacity white, which we
62 * definitely don't want. Skip that by setting 0 opacity.
63 */
64 static ClutterTexture *
65 create_default_texture (void)
66 {
67 ClutterTexture * texture = CLUTTER_TEXTURE (clutter_texture_new ());
Possibly related backtrace: ac558c6b7f46b0a8d58840240d81dcf75057264b MatchResult(frame_number=6, dist=0)
(emitted by gcc) 68 g_object_set (texture, "keep-aspect-ratio", TRUE, "opacity", 0, NULL);
69 return texture;
70 }
71
72 /* Reverse the opacity we added while loading */
73 static void
74 set_texture_cogl_texture (ClutterTexture *clutter_texture, CoglHandle cogl_texture)
75 {
76 clutter_texture_set_cogl_texture (clutter_texture, cogl_texture);
Possibly related backtrace: e65c2b19f3598fa90925d62a7db53a1691100416 MatchResult(frame_number=34, dist=1)
(emitted by gcc) 77 g_object_set (clutter_texture, "opacity", 255, NULL);
78 }
79
80 static void
81 st_texture_cache_class_init (StTextureCacheClass *klass)
82 {
83 GObjectClass *gobject_class = (GObjectClass *)klass;
84
85 gobject_class->dispose = st_texture_cache_dispose;
86 gobject_class->finalize = st_texture_cache_finalize;
87
88 signals[ICON_THEME_CHANGED] =
89 g_signal_new ("icon-theme-changed",
90 G_TYPE_FROM_CLASS (klass),
91 G_SIGNAL_RUN_LAST,
92 0, /* no default handler slot */
93 NULL, NULL, NULL,
94 G_TYPE_NONE, 0);
95 }
96
97 /**
98 * st_texture_cache_clear_uri:
99 * @cache: A #StTextureCache
100 * @uri: URI of cached object
101 *
102 * If the given @uri is known to have been modified
103 * externally, this function may be used to invalidate
104 * the in-memory cache.
105 */
106 void
107 st_texture_cache_clear_uri (StTextureCache *cache,
108 const char *uri)
109 {
110 char *key;
111
112 g_return_if_fail (ST_IS_TEXTURE_CACHE (cache));
113 g_return_if_fail (uri != NULL);
114
115 key = g_strconcat (CACHE_PREFIX_URI, uri, NULL);
116 g_hash_table_remove (cache->priv->keyed_cache, key);
117 g_free (key);
118
119 key = g_strconcat (CACHE_PREFIX_URI_FOR_CAIRO, uri, NULL);
120 g_hash_table_remove (cache->priv->keyed_cache, key);
121 g_free (key);
122 }
123
124 /* Evicts all cached textures for named icons */
125 static void
126 st_texture_cache_evict_icons (StTextureCache *cache)
127 {
128 GHashTableIter iter;
129 gpointer key;
130 gpointer value;
131
132 g_hash_table_iter_init (&iter, cache->priv->keyed_cache);
133 while (g_hash_table_iter_next (&iter, &key, &value))
134 {
135 const char *cache_key = key;
136
137 /* This is too conservative - it takes out all cached textures
138 * for GIcons even when they aren't named icons, but it's not
139 * worth the complexity of parsing the key and calling
140 * g_icon_new_for_string(); icon theme changes aren't normal */
141 if (g_str_has_prefix (cache_key, CACHE_PREFIX_ICON))
142 g_hash_table_iter_remove (&iter);
143 }
144 }
145
146 static void
147 on_icon_theme_changed (GtkIconTheme *icon_theme,
148 StTextureCache *cache)
149 {
150 st_texture_cache_evict_icons (cache);
151 g_signal_emit (cache, signals[ICON_THEME_CHANGED], 0);
152 }
153
154 static void
155 st_texture_cache_init (StTextureCache *self)
156 {
157 self->priv = g_new0 (StTextureCachePrivate, 1);
158
159 self->priv->icon_theme = gtk_icon_theme_get_default ();
160 g_signal_connect (self->priv->icon_theme, "changed",
161 G_CALLBACK (on_icon_theme_changed), self);
162
163 self->priv->keyed_cache = g_hash_table_new_full (g_str_hash, g_str_equal,
164 g_free, cogl_handle_unref);
165 self->priv->outstanding_requests = g_hash_table_new_full (g_str_hash, g_str_equal,
166 g_free, NULL);
167 }
168
169 static void
170 st_texture_cache_dispose (GObject *object)
171 {
172 StTextureCache *self = (StTextureCache*)object;
173
174 if (self->priv->icon_theme)
175 {
176 g_signal_handlers_disconnect_by_func (self->priv->icon_theme,
177 (gpointer) on_icon_theme_changed,
178 self);
179 self->priv->icon_theme = NULL;
180 }
181
182 if (self->priv->keyed_cache)
183 g_hash_table_destroy (self->priv->keyed_cache);
184 self->priv->keyed_cache = NULL;
185
186 if (self->priv->outstanding_requests)
187 g_hash_table_destroy (self->priv->outstanding_requests);
188 self->priv->outstanding_requests = NULL;
189
190 G_OBJECT_CLASS (st_texture_cache_parent_class)->dispose (object);
191 }
192
193 static void
194 st_texture_cache_finalize (GObject *object)
195 {
196 G_OBJECT_CLASS (st_texture_cache_parent_class)->finalize (object);
197 }
198
199 static gboolean
200 compute_pixbuf_scale (gint width,
201 gint height,
202 gint available_width,
203 gint available_height,
204 gint *new_width,
205 gint *new_height)
206 {
207 int scaled_width, scaled_height;
208
209 if (width == 0 || height == 0)
210 return FALSE;
211
212 if (available_width >= 0 && available_height >= 0)
213 {
214 /* This should keep the aspect ratio of the image intact, because if
215 * available_width < (available_height * width) / height
216 * then
217 * (available_width * height) / width < available_height
218 * So we are guaranteed to either scale the image to have an available_width
219 * for width and height scaled accordingly OR have the available_height
220 * for height and width scaled accordingly, whichever scaling results
221 * in the image that can fit both available dimensions.
222 */
223 scaled_width = MIN (available_width, (available_height * width) / height);
224 scaled_height = MIN (available_height, (available_width * height) / width);
225 }
226 else if (available_width >= 0)
227 {
228 scaled_width = available_width;
229 scaled_height = (available_width * height) / width;
230 }
231 else if (available_height >= 0)
232 {
233 scaled_width = (available_height * width) / height;
234 scaled_height = available_height;
235 }
236 else
237 {
238 scaled_width = scaled_height = 0;
239 }
240
241 /* Scale the image only if that will not increase its original dimensions. */
242 if (scaled_width > 0 && scaled_height > 0 && scaled_width < width && scaled_height < height)
243 {
244 *new_width = scaled_width;
245 *new_height = scaled_height;
246 return TRUE;
247 }
248 return FALSE;
249 }
250
251 static void
252 rgba_from_clutter (GdkRGBA *rgba,
253 ClutterColor *color)
254 {
255 rgba->red = color->red / 255.;
256 rgba->green = color->green / 255.;
257 rgba->blue = color->blue / 255.;
258 rgba->alpha = color->alpha / 255.;
259 }
260
261 static GdkPixbuf *
262 impl_load_pixbuf_gicon (GtkIconInfo *info,
263 int size,
264 StIconColors *colors,
265 GError **error)
266 {
267 int scaled_width, scaled_height;
268 GdkPixbuf *pixbuf;
269 int width, height;
270
271 if (colors)
272 {
273 GdkRGBA foreground_color;
274 GdkRGBA success_color;
275 GdkRGBA warning_color;
276 GdkRGBA error_color;
277
278 rgba_from_clutter (&foreground_color, &colors->foreground);
279 rgba_from_clutter (&success_color, &colors->success);
280 rgba_from_clutter (&warning_color, &colors->warning);
281 rgba_from_clutter (&error_color, &colors->error);
282
283 pixbuf = gtk_icon_info_load_symbolic (info,
284 &foreground_color, &success_color,
285 &warning_color, &error_color,
286 NULL, error);
287 }
288 else
289 {
290 pixbuf = gtk_icon_info_load_icon (info, error);
291 }
292
293 if (!pixbuf)
294 return NULL;
295
296 width = gdk_pixbuf_get_width (pixbuf);
297 height = gdk_pixbuf_get_height (pixbuf);
298
299 if (compute_pixbuf_scale (width,
300 height,
301 size, size,
302 &scaled_width, &scaled_height))
303 {
304 GdkPixbuf *scaled = gdk_pixbuf_scale_simple (pixbuf, width, height, GDK_INTERP_BILINEAR);
305 g_object_unref (pixbuf);
306 pixbuf = scaled;
307 }
308 return pixbuf;
309 }
310
311 /* A private structure for keeping width and height. */
312 typedef struct {
313 int width;
314 int height;
315 } Dimensions;
316
317 /* This struct corresponds to a request for an texture.
318 * It's creasted when something needs a new texture,
319 * and destroyed when the texture data is loaded. */
320 typedef struct {
321 StTextureCache *cache;
322 StTextureCachePolicy policy;
323 char *key;
324
325 gboolean enforced_square;
326
327 guint width;
328 guint height;
329 GSList *textures;
330
331 GtkIconInfo *icon_info;
332 StIconColors *colors;
333 char *uri;
334 } AsyncTextureLoadData;
335
336 static void
337 texture_load_data_destroy (gpointer p)
338 {
339 AsyncTextureLoadData *data = p;
340
341 if (data->icon_info)
342 {
343 gtk_icon_info_free (data->icon_info);
344 if (data->colors)
345 st_icon_colors_unref (data->colors);
346 }
347 else if (data->uri)
348 g_free (data->uri);
349
350 if (data->key)
351 g_free (data->key);
352
353 if (data->textures)
354 g_slist_free_full (data->textures, (GDestroyNotify) g_object_unref);
355 }
356
357 /**
358 * on_image_size_prepared:
359 * @pixbuf_loader: #GdkPixbufLoader loading the image
360 * @width: the original width of the image
361 * @height: the original height of the image
362 * @data: pointer to the #Dimensions sructure containing available width and height for the image,
363 * available width or height can be -1 if the dimension is not limited
364 *
365 * Private function.
366 *
367 * Sets the size of the image being loaded to fit the available width and height dimensions,
368 * but never scales up the image beyond its actual size.
369 * Intended to be used as a callback for #GdkPixbufLoader "size-prepared" signal.
370 */
371 static void
372 on_image_size_prepared (GdkPixbufLoader *pixbuf_loader,
373 gint width,
374 gint height,
375 gpointer data)
376 {
377 Dimensions *available_dimensions = data;
378 int available_width = available_dimensions->width;
379 int available_height = available_dimensions->height;
380 int scaled_width;
381 int scaled_height;
382
383 if (compute_pixbuf_scale (width, height, available_width, available_height,
384 &scaled_width, &scaled_height))
385 gdk_pixbuf_loader_set_size (pixbuf_loader, scaled_width, scaled_height);
386 }
387
388 static GdkPixbuf *
389 impl_load_pixbuf_data (const guchar *data,
390 gsize size,
391 int available_width,
392 int available_height,
393 GError **error)
394 {
395 GdkPixbufLoader *pixbuf_loader = NULL;
396 GdkPixbuf *rotated_pixbuf = NULL;
397 GdkPixbuf *pixbuf;
398 gboolean success;
399 Dimensions available_dimensions;
400 int width_before_rotation, width_after_rotation;
401
402 pixbuf_loader = gdk_pixbuf_loader_new ();
403
404 available_dimensions.width = available_width;
405 available_dimensions.height = available_height;
406 g_signal_connect (pixbuf_loader, "size-prepared",
407 G_CALLBACK (on_image_size_prepared), &available_dimensions);
408
409 success = gdk_pixbuf_loader_write (pixbuf_loader, data, size, error);
410 if (!success)
411 goto out;
412 success = gdk_pixbuf_loader_close (pixbuf_loader, error);
413 if (!success)
414 goto out;
415
416 pixbuf = gdk_pixbuf_loader_get_pixbuf (pixbuf_loader);
417
418 width_before_rotation = gdk_pixbuf_get_width (pixbuf);
419
420 rotated_pixbuf = gdk_pixbuf_apply_embedded_orientation (pixbuf);
421 width_after_rotation = gdk_pixbuf_get_width (rotated_pixbuf);
422
423 /* There is currently no way to tell if the pixbuf will need to be rotated before it is loaded,
424 * so we only check that once it is loaded, and reload it again if it needs to be rotated in order
425 * to use the available width and height correctly.
426 * See http://bugzilla.gnome.org/show_bug.cgi?id=579003
427 */
428 if (width_before_rotation != width_after_rotation)
429 {
430 g_object_unref (pixbuf_loader);
431 g_object_unref (rotated_pixbuf);
432 rotated_pixbuf = NULL;
433
434 pixbuf_loader = gdk_pixbuf_loader_new ();
435
436 /* We know that the image will later be rotated, so we reverse the available dimensions. */
437 available_dimensions.width = available_height;
438 available_dimensions.height = available_width;
439 g_signal_connect (pixbuf_loader, "size-prepared",
440 G_CALLBACK (on_image_size_prepared), &available_dimensions);
441
442 success = gdk_pixbuf_loader_write (pixbuf_loader, data, size, error);
443 if (!success)
444 goto out;
445
446 success = gdk_pixbuf_loader_close (pixbuf_loader, error);
447 if (!success)
448 goto out;
449
450 pixbuf = gdk_pixbuf_loader_get_pixbuf (pixbuf_loader);
451
452 rotated_pixbuf = gdk_pixbuf_apply_embedded_orientation (pixbuf);
453 }
454
455 out:
456 if (pixbuf_loader)
457 g_object_unref (pixbuf_loader);
458 return rotated_pixbuf;
459 }
460
461 static GdkPixbuf*
462 decode_image (const char *val)
463 {
464 int i;
465 GError *error = NULL;
466 GdkPixbuf *res = NULL;
467 struct {
468 const char *prefix;
469 const char *mime_type;
470 } formats[] = {
471 { "data:image/x-icon;base64,", "image/x-icon" },
472 { "data:image/png;base64,", "image/png" }
473 };
474
475 g_return_val_if_fail (val, NULL);
476
477 for (i = 0; i < G_N_ELEMENTS (formats); i++)
478 {
479 if (g_str_has_prefix (val, formats[i].prefix))
480 {
481 gsize len;
482 guchar *data = NULL;
483 char *unescaped;
484
485 unescaped = g_uri_unescape_string (val + strlen (formats[i].prefix), NULL);
486 if (unescaped)
487 {
488 data = g_base64_decode (unescaped, &len);
489 g_free (unescaped);
490 }
491
492 if (data)
493 {
494 GdkPixbufLoader *loader;
495
496 loader = gdk_pixbuf_loader_new_with_mime_type (formats[i].mime_type, &error);
497 if (loader &&
498 gdk_pixbuf_loader_write (loader, data, len, &error) &&
499 gdk_pixbuf_loader_close (loader, &error))
500 {
501 res = gdk_pixbuf_loader_get_pixbuf (loader);
502 g_object_ref (res);
503 }
504 g_object_unref (loader);
505 g_free (data);
506 }
507 }
508 }
509 if (!res)
510 {
511 if (error)
512 {
513 g_warning ("%s\n", error->message);
514 g_error_free (error);
515 }
516 else
517 g_warning ("incorrect data uri");
518 }
519 return res;
520 }
521
522 static GdkPixbuf *
523 impl_load_pixbuf_file (const char *uri,
524 int available_width,
525 int available_height,
526 GError **error)
527 {
528 GdkPixbuf *pixbuf = NULL;
529 GFile *file;
530 char *contents = NULL;
531 gsize size;
532
533 if (g_str_has_prefix (uri, "data:"))
534 return decode_image (uri);
535
536 file = g_file_new_for_uri (uri);
537 if (g_file_load_contents (file, NULL, &contents, &size, NULL, error))
538 {
539 pixbuf = impl_load_pixbuf_data ((const guchar *) contents, size,
540 available_width, available_height,
541 error);
542 }
543
544 g_object_unref (file);
545 g_free (contents);
546
547 return pixbuf;
548 }
549
550 static void
551 load_pixbuf_thread (GSimpleAsyncResult *result,
552 GObject *object,
553 GCancellable *cancellable)
554 {
555 GdkPixbuf *pixbuf;
556 AsyncTextureLoadData *data;
557 GError *error = NULL;
558
559 data = g_async_result_get_user_data (G_ASYNC_RESULT (result));
560 g_assert (data != NULL);
561
562 if (data->uri)
563 pixbuf = impl_load_pixbuf_file (data->uri, data->width, data->height, &error);
564 else if (data->icon_info)
565 pixbuf = impl_load_pixbuf_gicon (data->icon_info, data->width, data->colors, &error);
566 else
567 g_assert_not_reached ();
568
569 if (error != NULL)
570 {
571 g_simple_async_result_set_from_error (result, error);
572 return;
573 }
574
575 if (pixbuf)
576 g_simple_async_result_set_op_res_gpointer (result, g_object_ref (pixbuf),
577 g_object_unref);
578 }
579
580 static GdkPixbuf *
581 load_pixbuf_async_finish (StTextureCache *cache, GAsyncResult *result, GError **error)
582 {
583 GSimpleAsyncResult *simple = G_SIMPLE_ASYNC_RESULT (result);
584 if (g_simple_async_result_propagate_error (simple, error))
585 return NULL;
586 return g_simple_async_result_get_op_res_gpointer (simple);
587 }
588
589 static CoglHandle
590 data_to_cogl_handle (const guchar *data,
591 gboolean has_alpha,
592 int width,
593 int height,
594 int rowstride,
595 gboolean add_padding)
596 {
597 CoglHandle texture, offscreen;
598 CoglColor clear_color;
599 guint size;
600
601 size = MAX (width, height);
602
603 if (!add_padding || width == height)
604 return cogl_texture_new_from_data (width,
605 height,
606 COGL_TEXTURE_NONE,
607 has_alpha ? COGL_PIXEL_FORMAT_RGBA_8888 : COGL_PIXEL_FORMAT_RGB_888,
608 COGL_PIXEL_FORMAT_ANY,
609 rowstride,
610 data);
611
612 texture = cogl_texture_new_with_size (size, size,
613 COGL_TEXTURE_NO_SLICING,
614 COGL_PIXEL_FORMAT_ANY);
615
616 offscreen = cogl_offscreen_new_to_texture (texture);
617 cogl_color_set_from_4ub (&clear_color, 0, 0, 0, 0);
618 cogl_push_framebuffer (offscreen);
619 cogl_clear (&clear_color, COGL_BUFFER_BIT_COLOR);
620 cogl_pop_framebuffer ();
621 cogl_handle_unref (offscreen);
622
623 cogl_texture_set_region (texture,
624 0, 0,
625 (size - width) / 2, (size - height) / 2,
626 width, height,
627 width, height,
628 has_alpha ? COGL_PIXEL_FORMAT_RGBA_8888 : COGL_PIXEL_FORMAT_RGB_888,
629 rowstride,
630 data);
631 return texture;
632 }
633
634 static CoglHandle
635 pixbuf_to_cogl_handle (GdkPixbuf *pixbuf,
636 gboolean add_padding)
637 {
638 return data_to_cogl_handle (gdk_pixbuf_get_pixels (pixbuf),
639 gdk_pixbuf_get_has_alpha (pixbuf),
640 gdk_pixbuf_get_width (pixbuf),
641 gdk_pixbuf_get_height (pixbuf),
642 gdk_pixbuf_get_rowstride (pixbuf),
643 add_padding);
644 }
645
646 static cairo_surface_t *
647 pixbuf_to_cairo_surface (GdkPixbuf *pixbuf)
648 {
649 cairo_surface_t *dummy_surface;
650 cairo_pattern_t *pattern;
651 cairo_surface_t *surface;
652 cairo_t *cr;
653
654 dummy_surface = cairo_image_surface_create (CAIRO_FORMAT_ARGB32, 1, 1);
655
656 cr = cairo_create (dummy_surface);
657 gdk_cairo_set_source_pixbuf (cr, pixbuf, 0, 0);
658 pattern = cairo_get_source (cr);
659 cairo_pattern_get_surface (pattern, &surface);
660 cairo_surface_reference (surface);
661 cairo_destroy (cr);
662 cairo_surface_destroy (dummy_surface);
663
664 return surface;
665 }
666
667 static void
668 on_pixbuf_loaded (GObject *source,
669 GAsyncResult *result,
670 gpointer user_data)
671 {
672 GSList *iter;
673 StTextureCache *cache;
674 AsyncTextureLoadData *data;
675 GdkPixbuf *pixbuf;
676 GError *error = NULL;
677 CoglHandle texdata = NULL;
678
679 data = user_data;
680 cache = ST_TEXTURE_CACHE (source);
681
682 g_hash_table_remove (cache->priv->outstanding_requests, data->key);
683
684 pixbuf = load_pixbuf_async_finish (cache, result, &error);
685 if (pixbuf == NULL)
686 goto out;
687
688 texdata = pixbuf_to_cogl_handle (pixbuf, data->enforced_square);
689
690 g_object_unref (pixbuf);
691
692 if (data->policy != ST_TEXTURE_CACHE_POLICY_NONE)
693 {
694 gpointer orig_key, value;
695
696 if (!g_hash_table_lookup_extended (cache->priv->keyed_cache, data->key,
697 &orig_key, &value))
698 {
699 cogl_handle_ref (texdata);
700 g_hash_table_insert (cache->priv->keyed_cache, g_strdup (data->key),
701 texdata);
702 }
703 }
704
705 for (iter = data->textures; iter; iter = iter->next)
706 {
707 ClutterTexture *texture = iter->data;
708 set_texture_cogl_texture (texture, texdata);
709 }
710
711 out:
712 if (texdata)
713 cogl_handle_unref (texdata);
714
715 texture_load_data_destroy (data);
716 g_free (data);
717
718 g_clear_error (&error);
719 }
720
721 static void
722 load_texture_async (StTextureCache *cache,
723 AsyncTextureLoadData *data)
724 {
725 GSimpleAsyncResult *result;
726 result = g_simple_async_result_new (G_OBJECT (cache), on_pixbuf_loaded, data, load_texture_async);
727 g_simple_async_result_run_in_thread (result, load_pixbuf_thread, G_PRIORITY_DEFAULT, NULL);
728 g_object_unref (result);
729 }
730
731 typedef struct {
732 StTextureCache *cache;
733 ClutterTexture *texture;
734 GObject *source;
735 guint notify_signal_id;
736 gboolean weakref_active;
737 } StTextureCachePropertyBind;
738
739 static void
740 st_texture_cache_reset_texture (StTextureCachePropertyBind *bind,
741 const char *propname)
742 {
743 GdkPixbuf *pixbuf;
744 CoglHandle texdata;
745
746 g_object_get (bind->source, propname, &pixbuf, NULL);
747
748 g_return_if_fail (pixbuf == NULL || GDK_IS_PIXBUF (pixbuf));
749
750 if (pixbuf != NULL)
751 {
752 texdata = pixbuf_to_cogl_handle (pixbuf, FALSE);
753 g_object_unref (pixbuf);
754
755 clutter_texture_set_cogl_texture (bind->texture, texdata);
(emitted by gcc) 756 cogl_handle_unref (texdata);
757
758 clutter_actor_set_opacity (CLUTTER_ACTOR (bind->texture), 255);
759 }
760 else
761 clutter_actor_set_opacity (CLUTTER_ACTOR (bind->texture), 0);
762 }
763
764 static void
765 st_texture_cache_on_pixbuf_notify (GObject *object,
766 GParamSpec *paramspec,
767 gpointer data)
768 {
769 StTextureCachePropertyBind *bind = data;
770 st_texture_cache_reset_texture (bind, paramspec->name);
771 }
772
773 static void
774 st_texture_cache_bind_weak_notify (gpointer data,
775 GObject *source_location)
776 {
777 StTextureCachePropertyBind *bind = data;
778 bind->weakref_active = FALSE;
779 g_signal_handler_disconnect (bind->source, bind->notify_signal_id);
780 }
781
782 static void
783 st_texture_cache_free_bind (gpointer data)
784 {
785 StTextureCachePropertyBind *bind = data;
786 if (bind->weakref_active)
787 g_object_weak_unref (G_OBJECT(bind->texture), st_texture_cache_bind_weak_notify, bind);
788 g_free (bind);
789 }
790
791 /**
792 * st_texture_cache_bind_pixbuf_property:
793 * @cache:
794 * @object: A #GObject with a property @property_name of type #GdkPixbuf
795 * @property_name: Name of a property
796 *
797 * Create a #ClutterTexture which tracks the #GdkPixbuf value of a GObject property
798 * named by @property_name. Unlike other methods in StTextureCache, the underlying
799 * CoglHandle is not shared by default with other invocations to this method.
800 *
801 * If the source object is destroyed, the texture will continue to show the last
802 * value of the property.
803 *
804 * Return value: (transfer none): A new #ClutterActor
805 */
806 ClutterActor *
807 st_texture_cache_bind_pixbuf_property (StTextureCache *cache,
808 GObject *object,
809 const char *property_name)
810 {
811 ClutterTexture *texture;
812 gchar *notify_key;
813 StTextureCachePropertyBind *bind;
814
815 texture = CLUTTER_TEXTURE (clutter_texture_new ());
(emitted by gcc) 816
817 bind = g_new0 (StTextureCachePropertyBind, 1);
818 bind->cache = cache;
819 bind->texture = texture;
820 bind->source = object;
821 g_object_weak_ref (G_OBJECT (texture), st_texture_cache_bind_weak_notify, bind);
822 bind->weakref_active = TRUE;
823
824 st_texture_cache_reset_texture (bind, property_name);
825
826 notify_key = g_strdup_printf ("notify::%s", property_name);
827 bind->notify_signal_id = g_signal_connect_data (object, notify_key, G_CALLBACK(st_texture_cache_on_pixbuf_notify),
828 bind, (GClosureNotify)st_texture_cache_free_bind, 0);
829 g_free (notify_key);
830
831 return CLUTTER_ACTOR(texture);
832 }
833
834 /**
835 * st_texture_cache_load: (skip)
836 * @cache: A #StTextureCache
837 * @key: Arbitrary string used to refer to item
838 * @policy: Caching policy
839 * @load: Function to create the texture, if not already cached
840 * @data: User data passed to @load
841 * @error: A #GError
842 *
843 * Load an arbitrary texture, caching it. The string chosen for @key
844 * should be of the form "type-prefix:type-uuid". For example,
845 * "url:file:///usr/share/icons/hicolor/48x48/apps/firefox.png", or
846 * "stock-icon:gtk-ok".
847 *
848 * Returns: (transfer full): A newly-referenced handle to the texture
849 */
850 CoglHandle
851 st_texture_cache_load (StTextureCache *cache,
852 const char *key,
853 StTextureCachePolicy policy,
854 StTextureCacheLoader load,
855 void *data,
856 GError **error)
857 {
858 CoglHandle texture;
859
860 texture = g_hash_table_lookup (cache->priv->keyed_cache, key);
861 if (!texture)
862 {
863 texture = load (cache, key, data, error);
864 if (texture)
865 g_hash_table_insert (cache->priv->keyed_cache, g_strdup (key), texture);
866 else
867 return COGL_INVALID_HANDLE;
868 }
869 cogl_handle_ref (texture);
870 return texture;
871 }
872
873 /**
874 * ensure_request:
875 * @cache:
876 * @key: A cache key
877 * @policy: Cache policy
878 * @request: (out): If no request is outstanding, one will be created and returned here
879 * @texture: A texture to be added to the request
880 *
881 * Check for any outstanding load for the data represented by @key. If there
882 * is already a request pending, append it to that request to avoid loading
883 * the data multiple times.
884 *
885 * Returns: %TRUE iff there is already a request pending
886 */
887 static gboolean
888 ensure_request (StTextureCache *cache,
889 const char *key,
890 StTextureCachePolicy policy,
891 AsyncTextureLoadData **request,
892 ClutterActor *texture)
893 {
894 CoglHandle texdata;
895 AsyncTextureLoadData *pending;
896 gboolean had_pending;
897
898 texdata = g_hash_table_lookup (cache->priv->keyed_cache, key);
899
900 if (texdata != NULL)
901 {
902 /* We had this cached already, just set the texture and we're done. */
903 set_texture_cogl_texture (CLUTTER_TEXTURE (texture), texdata);
904 return TRUE;
905 }
906
907 pending = g_hash_table_lookup (cache->priv->outstanding_requests, key);
908 had_pending = pending != NULL;
909
910 if (pending == NULL)
911 {
912 /* Not cached and no pending request, create it */
913 *request = g_new0 (AsyncTextureLoadData, 1);
914 if (policy != ST_TEXTURE_CACHE_POLICY_NONE)
915 g_hash_table_insert (cache->priv->outstanding_requests, g_strdup (key), *request);
916 }
917 else
918 *request = pending;
919
920 /* Regardless of whether there was a pending request, prepend our texture here. */
921 (*request)->textures = g_slist_prepend ((*request)->textures, g_object_ref (texture));
922
923 return had_pending;
924 }
925
926 static ClutterActor *
927 load_gicon_with_colors (StTextureCache *cache,
928 GIcon *icon,
929 gint size,
930 StIconColors *colors)
931 {
932 AsyncTextureLoadData *request;
933 ClutterActor *texture;
934 char *gicon_string;
935 char *key;
936 GtkIconTheme *theme;
937 GtkIconInfo *info;
938 StTextureCachePolicy policy;
939
940 /* Do theme lookups in the main thread to avoid thread-unsafety */
941 theme = cache->priv->icon_theme;
942
943 info = gtk_icon_theme_lookup_by_gicon (theme, icon, size, GTK_ICON_LOOKUP_USE_BUILTIN);
944 if (info == NULL)
945 return NULL;
946
947 gicon_string = g_icon_to_string (icon);
948 /* A return value of NULL indicates that the icon can not be serialized,
949 * so don't have a unique identifier for it as a cache key, and thus can't
950 * be cached. If it is cachable, we hardcode a policy of FOREVER here for
951 * now; we should actually blow this away on icon theme changes probably */
952 policy = gicon_string != NULL ? ST_TEXTURE_CACHE_POLICY_FOREVER
953 : ST_TEXTURE_CACHE_POLICY_NONE;
954 if (colors)
955 {
956 /* This raises some doubts about the practice of using string keys */
957 key = g_strdup_printf (CACHE_PREFIX_ICON "%s,size=%d,colors=%2x%2x%2x%2x,%2x%2x%2x%2x,%2x%2x%2x%2x,%2x%2x%2x%2x",
958 gicon_string, size,
959 colors->foreground.red, colors->foreground.blue, colors->foreground.green, colors->foreground.alpha,
960 colors->warning.red, colors->warning.blue, colors->warning.green, colors->warning.alpha,
961 colors->error.red, colors->error.blue, colors->error.green, colors->error.alpha,
962 colors->success.red, colors->success.blue, colors->success.green, colors->success.alpha);
963 }
964 else
965 {
966 key = g_strdup_printf (CACHE_PREFIX_ICON "%s,size=%d",
967 gicon_string, size);
968 }
969 g_free (gicon_string);
970
971 texture = (ClutterActor *) create_default_texture ();
972 clutter_actor_set_size (texture, size, size);
973
974 if (ensure_request (cache, key, policy, &request, texture))
975 {
976 /* If there's an outstanding request, we've just added ourselves to it */
977 gtk_icon_info_free (info);
978 g_free (key);
979 }
980 else
981 {
982 /* Else, make a new request */
983
984 request->cache = cache;
985 /* Transfer ownership of key */
986 request->key = key;
987 request->policy = policy;
988 request->colors = colors ? st_icon_colors_ref (colors) : NULL;
989 request->icon_info = info;
990 request->width = request->height = size;
991 request->enforced_square = TRUE;
992
993 load_texture_async (cache, request);
994 }
995
996 return CLUTTER_ACTOR (texture);
997 }
998
999 /**
1000 * st_texture_cache_load_gicon:
1001 * @cache: The texture cache instance
1002 * @theme_node: (allow-none): The #StThemeNode to use for colors, or NULL
1003 * if the icon must not be recolored
1004 * @icon: the #GIcon to load
1005 * @size: Size of themed
1006 *
1007 * This method returns a new #ClutterActor for a given #GIcon. If the
1008 * icon isn't loaded already, the texture will be filled
1009 * asynchronously.
1010 *
1011 * Return Value: (transfer none): A new #ClutterActor for the icon, or %NULL if not found
1012 */
1013 ClutterActor *
1014 st_texture_cache_load_gicon (StTextureCache *cache,
1015 StThemeNode *theme_node,
1016 GIcon *icon,
1017 gint size)
1018 {
1019 return load_gicon_with_colors (cache, icon, size, theme_node ? st_theme_node_get_icon_colors (theme_node) : NULL);
1020 }
1021
1022 static ClutterActor *
1023 load_from_pixbuf (GdkPixbuf *pixbuf)
1024 {
1025 ClutterTexture *texture;
1026 CoglHandle texdata;
1027 int width = gdk_pixbuf_get_width (pixbuf);
1028 int height = gdk_pixbuf_get_height (pixbuf);
1029
1030 texture = create_default_texture ();
1031
1032 clutter_actor_set_size (CLUTTER_ACTOR (texture), width, height);
1033
1034 texdata = pixbuf_to_cogl_handle (pixbuf, FALSE);
1035
1036 set_texture_cogl_texture (texture, texdata);
1037
1038 cogl_handle_unref (texdata);
1039 return CLUTTER_ACTOR (texture);
1040 }
1041
1042 typedef struct {
1043 gchar *path;
1044 gint grid_width, grid_height;
1045 ClutterActor *actor;
1046 } AsyncImageData;
1047
1048 static void
1049 on_data_destroy (gpointer data)
1050 {
1051 AsyncImageData *d = (AsyncImageData *)data;
1052 g_free (d->path);
1053 g_object_unref (d->actor);
1054 g_free (d);
1055 }
1056
1057 static void
1058 on_sliced_image_loaded (GObject *source_object,
1059 GAsyncResult *res,
1060 gpointer user_data)
1061 {
1062 AsyncImageData *data = (AsyncImageData *)user_data;
1063 GSimpleAsyncResult *simple = G_SIMPLE_ASYNC_RESULT (res);
1064 GList *list;
1065
1066 if (g_simple_async_result_propagate_error (simple, NULL))
1067 return;
1068
1069 for (list = g_simple_async_result_get_op_res_gpointer (simple); list; list = g_list_next (list))
1070 {
1071 ClutterActor *actor = load_from_pixbuf (GDK_PIXBUF (list->data));
1072 clutter_actor_hide (actor);
1073 clutter_actor_add_child (data->actor, actor);
1074 }
1075 }
1076
1077 static void
1078 free_glist_unref_gobjects (gpointer p)
1079 {
1080 GList *list = p;
1081 GList *iter;
1082
1083 for (iter = list; iter; iter = iter->next)
1084 g_object_unref (iter->data);
1085 g_list_free (list);
1086 }
1087
1088 static void
1089 load_sliced_image (GSimpleAsyncResult *result,
1090 GObject *object,
1091 GCancellable *cancellable)
1092 {
1093 AsyncImageData *data;
1094 GList *res = NULL;
1095 GdkPixbuf *pix;
1096 gint width, height, y, x;
1097
1098 g_assert (!cancellable);
1099
1100 data = g_object_get_data (G_OBJECT (result), "load_sliced_image");
1101 g_assert (data);
1102
1103 if (!(pix = gdk_pixbuf_new_from_file (data->path, NULL)))
1104 return;
1105
1106 width = gdk_pixbuf_get_width (pix);
1107 height = gdk_pixbuf_get_height (pix);
1108 for (y = 0; y < height; y += data->grid_height)
1109 {
1110 for (x = 0; x < width; x += data->grid_width)
1111 {
1112 GdkPixbuf *pixbuf = gdk_pixbuf_new_subpixbuf (pix, x, y, data->grid_width, data->grid_height);
1113 g_assert (pixbuf != NULL);
1114 res = g_list_append (res, pixbuf);
1115 }
1116 }
1117 /* We don't need the original pixbuf anymore, though the subpixbufs
1118 will hold a reference. */
1119 g_object_unref (pix);
1120 g_simple_async_result_set_op_res_gpointer (result, res, free_glist_unref_gobjects);
1121 }
1122
1123 /**
1124 * st_texture_cache_load_sliced_image:
1125 * @cache: A #StTextureCache
1126 * @path: Path to a filename
1127 * @grid_width: Width in pixels
1128 * @grid_height: Height in pixels
1129 *
1130 * This function reads a single image file which contains multiple images internally.
1131 * The image file will be divided using @grid_width and @grid_height;
1132 * note that the dimensions of the image loaded from @path
1133 * should be a multiple of the specified grid dimensions.
1134 *
1135 * Returns: (transfer none): A new #ClutterActor
1136 */
1137 ClutterActor *
1138 st_texture_cache_load_sliced_image (StTextureCache *cache,
1139 const gchar *path,
1140 gint grid_width,
1141 gint grid_height)
1142 {
1143 AsyncImageData *data;
1144 GSimpleAsyncResult *result;
1145 ClutterActor *actor = clutter_actor_new ();
1146
1147 data = g_new0 (AsyncImageData, 1);
1148 data->grid_width = grid_width;
1149 data->grid_height = grid_height;
1150 data->path = g_strdup (path);
1151 data->actor = actor;
1152 g_object_ref (G_OBJECT (actor));
1153
1154 result = g_simple_async_result_new (G_OBJECT (cache), on_sliced_image_loaded, data, st_texture_cache_load_sliced_image);
1155
1156 g_object_set_data_full (G_OBJECT (result), "load_sliced_image", data, on_data_destroy);
1157 g_simple_async_result_run_in_thread (result, load_sliced_image, G_PRIORITY_DEFAULT, NULL);
1158
1159 g_object_unref (result);
1160
1161 return actor;
1162 }
1163
1164 /**
1165 * st_texture_cache_load_icon_name:
1166 * @cache: The texture cache instance
1167 * @theme_node: (allow-none): a #StThemeNode
1168 * @name: Name of a themed icon
1169 * @size: Size of themed icon
1170 *
1171 * Load a themed icon into a texture. The colors used for symbolic
1172 * icons are derived from @theme_node.
1173 *
1174 * Return Value: (transfer none): A new #ClutterTexture for the icon
1175 */
1176 ClutterActor *
1177 st_texture_cache_load_icon_name (StTextureCache *cache,
1178 StThemeNode *theme_node,
1179 const char *name,
1180 gint size)
1181 {
1182 ClutterActor *texture;
1183 GIcon *themed;
1184
1185 themed = g_themed_icon_new_with_default_fallbacks (name);
1186 texture = load_gicon_with_colors (cache, themed, size,
1187 theme_node ? st_theme_node_get_icon_colors (theme_node) : NULL);
1188 g_object_unref (themed);
1189
1190 if (texture == NULL)
1191 {
1192 texture = (ClutterActor *) create_default_texture ();
1193 clutter_actor_set_size (texture, size, size);
1194 }
1195
1196 return texture;
1197 }
1198
1199 /**
1200 * st_texture_cache_load_uri_async:
1201 * @cache: The texture cache instance
1202 * @uri: uri of the image file from which to create a pixbuf
1203 * @available_width: available width for the image, can be -1 if not limited
1204 * @available_height: available height for the image, can be -1 if not limited
1205 *
1206 * Asynchronously load an image. Initially, the returned texture will have a natural
1207 * size of zero. At some later point, either the image will be loaded successfully
1208 * and at that point size will be negotiated, or upon an error, no image will be set.
1209 *
1210 * Return value: (transfer none): A new #ClutterActor with no image loaded initially.
1211 */
1212 ClutterActor *
1213 st_texture_cache_load_uri_async (StTextureCache *cache,
1214 const gchar *uri,
1215 int available_width,
1216 int available_height)
1217 {
1218 ClutterActor *texture;
1219 AsyncTextureLoadData *request;
1220 StTextureCachePolicy policy;
1221 gchar *key;
1222
1223 key = g_strconcat (CACHE_PREFIX_URI, uri, NULL);
1224
1225 policy = ST_TEXTURE_CACHE_POLICY_NONE; /* XXX */
1226
1227 texture = (ClutterActor *) create_default_texture ();
1228
1229 if (ensure_request (cache, key, policy, &request, texture))
1230 {
1231 /* If there's an outstanding request, we've just added ourselves to it */
1232 g_free (key);
1233 }
1234 else
1235 {
1236 /* Else, make a new request */
1237
1238 request->cache = cache;
1239 /* Transfer ownership of key */
1240 request->key = key;
1241 request->uri = g_strdup (uri);
1242 request->policy = policy;
1243 request->width = available_width;
1244 request->height = available_height;
1245
1246 load_texture_async (cache, request);
1247 }
1248
1249 return CLUTTER_ACTOR (texture);
1250 }
1251
1252 static CoglHandle
1253 st_texture_cache_load_uri_sync_to_cogl_texture (StTextureCache *cache,
1254 StTextureCachePolicy policy,
1255 const gchar *uri,
1256 int available_width,
1257 int available_height,
1258 GError **error)
1259 {
1260 CoglHandle texdata;
1261 GdkPixbuf *pixbuf;
1262 char *key;
1263
1264 key = g_strconcat (CACHE_PREFIX_URI, uri, NULL);
1265
1266 texdata = g_hash_table_lookup (cache->priv->keyed_cache, key);
1267
1268 if (texdata == NULL)
1269 {
1270 pixbuf = impl_load_pixbuf_file (uri, available_width, available_height, error);
1271 if (!pixbuf)
1272 goto out;
1273
1274 texdata = pixbuf_to_cogl_handle (pixbuf, FALSE);
1275 g_object_unref (pixbuf);
1276
1277 if (policy == ST_TEXTURE_CACHE_POLICY_FOREVER)
1278 {
1279 cogl_handle_ref (texdata);
1280 g_hash_table_insert (cache->priv->keyed_cache, g_strdup (key), texdata);
1281 }
1282 }
1283 else
1284 cogl_handle_ref (texdata);
1285
1286 out:
1287 g_free (key);
1288 return texdata;
1289 }
1290
1291 static cairo_surface_t *
1292 st_texture_cache_load_uri_sync_to_cairo_surface (StTextureCache *cache,
1293 StTextureCachePolicy policy,
1294 const gchar *uri,
1295 int available_width,
1296 int available_height,
1297 GError **error)
1298 {
1299 cairo_surface_t *surface;
1300 GdkPixbuf *pixbuf;
1301 char *key;
1302
1303 key = g_strconcat (CACHE_PREFIX_URI_FOR_CAIRO, uri, NULL);
1304
1305 surface = g_hash_table_lookup (cache->priv->keyed_cache, key);
1306
1307 if (surface == NULL)
1308 {
1309 pixbuf = impl_load_pixbuf_file (uri, available_width, available_height, error);
1310 if (!pixbuf)
1311 goto out;
1312
1313 surface = pixbuf_to_cairo_surface (pixbuf);
1314 g_object_unref (pixbuf);
1315
1316 if (policy == ST_TEXTURE_CACHE_POLICY_FOREVER)
1317 {
1318 cairo_surface_reference (surface);
1319 g_hash_table_insert (cache->priv->keyed_cache, g_strdup (key), surface);
1320 }
1321 }
1322 else
1323 cairo_surface_reference (surface);
1324
1325 out:
1326 g_free (key);
1327 return surface;
1328 }
1329
1330 /**
1331 * st_texture_cache_load_file_to_cogl_texture:
1332 * @cache: A #StTextureCache
1333 * @file_path: Path to a file in supported image format
1334 *
1335 * This function synchronously loads the given file path
1336 * into a COGL texture. On error, a warning is emitted
1337 * and %COGL_INVALID_HANDLE is returned.
1338 *
1339 * Returns: (transfer full): a new #CoglHandle
1340 */
1341 CoglHandle
1342 st_texture_cache_load_file_to_cogl_texture (StTextureCache *cache,
1343 const gchar *file_path)
1344 {
1345 CoglHandle texture;
1346 GFile *file;
1347 char *uri;
1348 GError *error = NULL;
1349
1350 file = g_file_new_for_path (file_path);
1351 uri = g_file_get_uri (file);
1352
1353 texture = st_texture_cache_load_uri_sync_to_cogl_texture (cache, ST_TEXTURE_CACHE_POLICY_FOREVER,
1354 uri, -1, -1, &error);
1355 g_object_unref (file);
1356 g_free (uri);
1357
1358 if (texture == NULL)
1359 {
1360 g_warning ("Failed to load %s: %s", file_path, error->message);
(emitted by clang-analyzer)TODO: a detailed trace is available in the data model (not yet rendered in this report)
1361 g_clear_error (&error);
1362 return COGL_INVALID_HANDLE;
1363 }
1364 return texture;
1365 }
1366
1367 /**
1368 * st_texture_cache_load_file_to_cairo_surface:
1369 * @cache: A #StTextureCache
1370 * @file_path: Path to a file in supported image format
1371 *
1372 * This function synchronously loads the given file path
1373 * into a cairo surface. On error, a warning is emitted
1374 * and %NULL is returned.
1375 *
1376 * Returns: (transfer full): a new #cairo_surface_t
1377 */
1378 cairo_surface_t *
1379 st_texture_cache_load_file_to_cairo_surface (StTextureCache *cache,
1380 const gchar *file_path)
1381 {
1382 cairo_surface_t *surface;
1383 GFile *file;
1384 char *uri;
1385 GError *error = NULL;
1386
1387 file = g_file_new_for_path (file_path);
1388 uri = g_file_get_uri (file);
1389
1390 surface = st_texture_cache_load_uri_sync_to_cairo_surface (cache, ST_TEXTURE_CACHE_POLICY_FOREVER,
1391 uri, -1, -1, &error);
1392 g_object_unref (file);
1393 g_free (uri);
1394
1395 if (surface == NULL)
1396 {
1397 g_warning ("Failed to load %s: %s", file_path, error->message);
(emitted by clang-analyzer)TODO: a detailed trace is available in the data model (not yet rendered in this report)
1398 g_clear_error (&error);
1399 return NULL;
1400 }
1401 return surface;
1402 }
1403
1404 /**
1405 * st_texture_cache_load_from_raw:
1406 * @cache: a #StTextureCache
1407 * @data: (array length=len): raw pixel data
1408 * @len: the length of @data
1409 * @has_alpha: whether @data includes an alpha channel
1410 * @width: width in pixels of @data
1411 * @height: width in pixels of @data
1412 * @rowstride: rowstride of @data
1413 * @size: size of icon to return
1414 *
1415 * Creates (or retrieves from cache) an icon based on raw pixel data.
1416 *
1417 * Return value: (transfer none): a new #ClutterActor displaying a
1418 * pixbuf created from @data and the other parameters.
1419 **/
1420 ClutterActor *
1421 st_texture_cache_load_from_raw (StTextureCache *cache,
1422 const guchar *data,
1423 gsize len,
1424 gboolean has_alpha,
1425 int width,
1426 int height,
1427 int rowstride,
1428 int size,
1429 GError **error)
1430 {
1431 ClutterTexture *texture;
1432 CoglHandle texdata;
1433 char *key;
1434 char *checksum;
1435
1436 texture = create_default_texture ();
1437 clutter_actor_set_size (CLUTTER_ACTOR (texture), size, size);
1438
1439 /* In theory, two images of with different width and height could have the same
1440 * pixel data and thus hash the same. (Say, a 16x16 and a 8x32 blank image.)
1441 * We ignore this for now. If anybody hits this problem they should use
1442 * GChecksum directly to compute a checksum including the width and height.
1443 */
1444 checksum = g_compute_checksum_for_data (G_CHECKSUM_SHA1, data, len);
1445 key = g_strdup_printf (CACHE_PREFIX_RAW_CHECKSUM "checksum=%s", checksum);
1446 g_free (checksum);
1447
1448 texdata = g_hash_table_lookup (cache->priv->keyed_cache, key);
1449 if (texdata == NULL)
1450 {
1451 texdata = data_to_cogl_handle (data, has_alpha, width, height, rowstride, TRUE);
1452 g_hash_table_insert (cache->priv->keyed_cache, g_strdup (key), texdata);
1453 }
1454
1455 g_free (key);
1456
1457 set_texture_cogl_texture (texture, texdata);
1458 return CLUTTER_ACTOR (texture);
1459 }
1460
1461 static StTextureCache *instance = NULL;
1462
1463 /**
1464 * st_texture_cache_get_default:
1465 *
1466 * Return value: (transfer none): The global texture cache
1467 */
1468 StTextureCache*
1469 st_texture_cache_get_default (void)
1470 {
1471 if (instance == NULL)
1472 instance = g_object_new (ST_TYPE_TEXTURE_CACHE, NULL);
1473 return instance;
1474 }