[gnome-shell] Bug 582248 - Async loading of pixbufs and icon caching
- From: Colin Walters <walters src gnome org>
- To: svn-commits-list gnome org
- Subject: [gnome-shell] Bug 582248 - Async loading of pixbufs and icon caching
- Date: Wed, 20 May 2009 12:35:36 -0400 (EDT)
commit d024dbd7795371d75d98c0dc91a1bb93ce0a3c30
Author: Colin Walters <walters verbum org>
Date: Wed May 13 14:06:24 2009 -0400
Bug 582248 - Async loading of pixbufs and icon caching
Add a ShellTextureCache class which loads (and can cache)
pixmap->texture conversions. This fixes a problem with the
async code in ClutterTexture that it was lower priority
than animations, and also ensures we're really only
loading these pixbufs once in the icon case.
---
js/ui/Makefile.am | 1 -
js/ui/appDisplay.js | 10 +-
js/ui/docDisplay.js | 11 +-
js/ui/gtkutil.js | 30 --
src/Makefile.am | 2 +
src/shell-global.c | 184 -------------
src/shell-global.h | 4 -
src/shell-texture-cache.c | 640 +++++++++++++++++++++++++++++++++++++++++++++
src/shell-texture-cache.h | 51 ++++
9 files changed, 702 insertions(+), 231 deletions(-)
diff --git a/js/ui/Makefile.am b/js/ui/Makefile.am
index e344982..29f18a1 100644
--- a/js/ui/Makefile.am
+++ b/js/ui/Makefile.am
@@ -8,7 +8,6 @@ dist_jsui_DATA = \
dnd.js \
docDisplay.js \
genericDisplay.js \
- gtkutil.js \
link.js \
main.js \
overlay.js \
diff --git a/js/ui/appDisplay.js b/js/ui/appDisplay.js
index 28a114b..fd47596 100644
--- a/js/ui/appDisplay.js
+++ b/js/ui/appDisplay.js
@@ -10,7 +10,6 @@ const Lang = imports.lang;
const Signals = imports.signals;
const GenericDisplay = imports.ui.genericDisplay;
-const GtkUtil = imports.ui.gtkutil;
const Main = imports.ui.main;
const ENTERED_MENU_COLOR = new Clutter.Color();
@@ -70,7 +69,14 @@ AppDisplayItem.prototype = {
let iconTheme = Gtk.IconTheme.get_default();
let gicon = appInfo.get_icon();
- let icon = GtkUtil.loadIconToTexture(gicon, GenericDisplay.ITEM_DISPLAY_ICON_SIZE, true);
+ let texCache = Shell.TextureCache.get_default();
+ let icon;
+ if (gicon == null)
+ icon = new Clutter.Texture({ width: GenericDisplay.ITEM_DISPLAY_ICON_SIZE,
+ height: GenericDisplay.ITEM_DISPLAY_ICON_SIZE
+ });
+ else
+ icon = texCache.load_gicon(gicon, GenericDisplay.ITEM_DISPLAY_ICON_SIZE);
this._setItemInfo(name, description, icon);
},
diff --git a/js/ui/docDisplay.js b/js/ui/docDisplay.js
index a1fa17c..cf894de 100644
--- a/js/ui/docDisplay.js
+++ b/js/ui/docDisplay.js
@@ -128,16 +128,7 @@ DocDisplayItem.prototype = {
if (this._docInfo.get_mime_type() == null || this._docInfo.get_mime_type().indexOf("image/") != 0)
return null;
- let largePreviewPixbuf = Shell.create_pixbuf_from_image_file(this._docInfo.get_uri(), availableWidth, availableHeight);
-
- if (largePreviewPixbuf == null)
- return null;
-
- let largePreviewIcon = new Clutter.Texture();
-
- Shell.clutter_texture_set_from_pixbuf(largePreviewIcon, largePreviewPixbuf);
-
- return largePreviewIcon;
+ return Shell.TextureCache.load_uri_sync(this._docInfo.get_uri(), availableWidth, availableHeight);
}
};
diff --git a/js/ui/gtkutil.js b/js/ui/gtkutil.js
deleted file mode 100644
index 56a9004..0000000
--- a/js/ui/gtkutil.js
+++ /dev/null
@@ -1,30 +0,0 @@
-/* -*- mode: js2; js2-basic-offset: 4; tab-width: 4; indent-tabs-mode: nil -*- */
-
-const Lang = imports.lang;
-
-const Clutter = imports.gi.Clutter;
-const Gtk = imports.gi.Gtk;
-
-function loadIconToTexture(gicon, size, fallback) {
- let iconTheme = Gtk.IconTheme.get_default();
-
- let path = null;
- let icon = null;
- if (gicon != null) {
- let iconinfo = iconTheme.lookup_by_gicon(gicon, size, Gtk.IconLookupFlags.NO_SVG);
- if (iconinfo)
- path = iconinfo.get_filename();
- }
- if (path) {
- try {
- icon = new Clutter.Texture({ width: size, height: size, load_async: true });
- icon.set_from_file(path);
- return icon;
- } catch (e) {
- icon = null;
- }
- }
- if (icon == null && fallback)
- icon = new Clutter.Texture({ width: size, height: size });
- return icon;
-}
\ No newline at end of file
diff --git a/src/Makefile.am b/src/Makefile.am
index f156228..9fd4d65 100644
--- a/src/Makefile.am
+++ b/src/Makefile.am
@@ -70,6 +70,8 @@ libgnome_shell_la_SOURCES = \
shell-status-menu.h \
shell-tray-manager.c \
shell-tray-manager.h \
+ shell-texture-cache.c \
+ shell-texture-cache.h \
shell-wm.c \
shell-wm.h
diff --git a/src/shell-global.c b/src/shell-global.c
index cd44571..f7c0500 100644
--- a/src/shell-global.c
+++ b/src/shell-global.c
@@ -392,190 +392,6 @@ shell_get_thumbnail_for_recent_info(GtkRecentInfo *recent_info)
return pixbuf;
}
-// A private structure for keeping width and height.
-typedef struct {
- int width;
- int height;
-} Dimensions;
-
-/**
- * on_image_size_prepared:
- *
- * @pixbuf_loader: #GdkPixbufLoader loading the image
- * @width: the original width of the image
- * @height: the original height of the image
- * @data: pointer to the #Dimensions sructure containing available width and height for the image,
- * available width or height can be -1 if the dimension is not limited
- *
- * Private function.
- *
- * Sets the size of the image being loaded to fit the available width and height dimensions,
- * but never scales up the image beyond its actual size.
- * Intended to be used as a callback for #GdkPixbufLoader "size-prepared" signal.
- */
-static void
-on_image_size_prepared (GdkPixbufLoader *pixbuf_loader,
- gint width,
- gint height,
- gpointer data)
-{
- Dimensions *available_dimensions = data;
- int available_width = available_dimensions->width;
- int available_height = available_dimensions->height;
- int scaled_width = -1;
- int scaled_height = -1;
-
- if (width == 0 || height == 0)
- return;
-
- if (available_width >= 0 && available_height >= 0)
- {
- // This should keep the aspect ratio of the image intact, because if
- // available_width < (available_height * width) / height
- // than
- // (available_width * height) / width < available_height
- // So we are guaranteed to either scale the image to have an available_width
- // for width and height scaled accordingly OR have the available_height
- // for height and width scaled accordingly, whichever scaling results
- // in the image that can fit both available dimensions.
- scaled_width = MIN(available_width, (available_height * width) / height);
- scaled_height = MIN(available_height, (available_width * height) / width);
- }
- else if (available_width >= 0)
- {
- scaled_width = available_width;
- scaled_height = (available_width * height) / width;
- }
- else if (available_height >= 0)
- {
- scaled_width = (available_height * width) / height;
- scaled_height = available_height;
- }
-
- // Scale the image only if that will not increase its original dimensions.
- if (scaled_width >= 0 && scaled_height >= 0 && scaled_width < width && scaled_height < height)
- gdk_pixbuf_loader_set_size (pixbuf_loader, scaled_width, scaled_height);
-}
-
-/**
- * shell_create_pixbuf_from_image_file:
- *
- * @uri: uri of the image file from which to create a pixbuf
- * @available_width: available width for the image, can be -1 if not limited
- * @available_height: available height for the image, can be -1 if not limited
- *
- * Return value: (transfer full): #GdkPixbuf with the image file loaded if it was
- * generated succesfully, %NULL otherwise
- * The image is scaled down to fit the available width and height
- * dimensions, but the image is never scaled up beyond its actual size.
- * The pixbuf is rotated according to the associated orientation setting.
- */
-GdkPixbuf *
-shell_create_pixbuf_from_image_file(const char *uri,
- int available_width,
- int available_height)
-{
- GdkPixbufLoader *pixbuf_loader = NULL;
- GdkPixbuf *pixbuf;
- GdkPixbuf *rotated_pixbuf = NULL;
- GFile *file = NULL;
- char *contents = NULL;
- gsize size;
- GError *error = NULL;
- gboolean success;
- Dimensions available_dimensions;
- int width_before_rotation, width_after_rotation;
-
- file = g_file_new_for_uri (uri);
-
- success = g_file_load_contents (file, NULL, &contents, &size, NULL, &error);
-
- if (!success)
- {
- g_warning ("Could not load contents of the file with uri %s: %s", uri, error->message);
- goto out;
- }
-
- pixbuf_loader = gdk_pixbuf_loader_new ();
-
- available_dimensions.width = available_width;
- available_dimensions.height = available_height;
- g_signal_connect (pixbuf_loader, "size-prepared",
- G_CALLBACK (on_image_size_prepared), &available_dimensions);
-
- success = gdk_pixbuf_loader_write (pixbuf_loader,
- (const guchar *) contents,
- size,
- &error);
- if (!success)
- {
- g_warning ("Could not write contents of the file with uri %s to the gdk pixbuf loader: %s", uri, error->message);
- goto out;
- }
-
- success = gdk_pixbuf_loader_close (pixbuf_loader, &error);
- if (!success)
- {
- g_warning ("Could not close the pixbuf loader after writing contents of the file with uri %s: %s", uri, error->message);
- goto out;
- }
-
- pixbuf = gdk_pixbuf_loader_get_pixbuf (pixbuf_loader);
- width_before_rotation = gdk_pixbuf_get_width (pixbuf);
-
- rotated_pixbuf = gdk_pixbuf_apply_embedded_orientation (pixbuf);
- width_after_rotation = gdk_pixbuf_get_width (rotated_pixbuf);
-
- // There is currently no way to tell if the pixbuf will need to be rotated before it is loaded,
- // so we only check that once it is loaded, and reload it again if it needs to be rotated in order
- // to use the available width and height correctly.
- // http://bugzilla.gnome.org/show_bug.cgi?id=579003
- if (width_before_rotation != width_after_rotation)
- {
- g_object_unref (pixbuf_loader);
- g_object_unref (rotated_pixbuf);
- rotated_pixbuf = NULL;
-
- pixbuf_loader = gdk_pixbuf_loader_new ();
-
- // We know that the image will later be rotated, so we reverse the available dimensions.
- available_dimensions.width = available_height;
- available_dimensions.height = available_width;
- g_signal_connect (pixbuf_loader, "size-prepared",
- G_CALLBACK (on_image_size_prepared), &available_dimensions);
-
- success = gdk_pixbuf_loader_write (pixbuf_loader,
- (const guchar *) contents,
- size,
- &error);
- if (!success)
- {
- g_warning ("Could not write contents of the file with uri %s to the gdk pixbuf loader: %s", uri, error->message);
- goto out;
- }
-
- success = gdk_pixbuf_loader_close (pixbuf_loader, &error);
- if (!success)
- {
- g_warning ("Could not close the pixbuf loader after writing contents of the file with uri %s: %s", uri, error->message);
- goto out;
- }
-
- pixbuf = gdk_pixbuf_loader_get_pixbuf (pixbuf_loader);
-
- rotated_pixbuf = gdk_pixbuf_apply_embedded_orientation (pixbuf);
- }
-
- out:
- g_clear_error (&error);
- g_free (contents);
- if (file)
- g_object_unref (file);
- if (pixbuf_loader)
- g_object_unref (pixbuf_loader);
-
- return rotated_pixbuf;
-}
/**
* shell_get_categories_for_desktop_file:
diff --git a/src/shell-global.h b/src/shell-global.h
index a23f377..06c19cc 100644
--- a/src/shell-global.h
+++ b/src/shell-global.h
@@ -38,10 +38,6 @@ gboolean shell_clutter_texture_set_from_pixbuf (ClutterTexture *texture,
GdkPixbuf *shell_get_thumbnail_for_recent_info(GtkRecentInfo *recent_info);
-GdkPixbuf *shell_create_pixbuf_from_image_file(const char *uri,
- int available_width,
- int available_height);
-
GSList *shell_get_categories_for_desktop_file(const char *desktop_file_name);
guint16 shell_get_event_key_symbol(ClutterEvent *event);
diff --git a/src/shell-texture-cache.c b/src/shell-texture-cache.c
new file mode 100644
index 0000000..68aaf21
--- /dev/null
+++ b/src/shell-texture-cache.c
@@ -0,0 +1,640 @@
+#include "shell-texture-cache.h"
+#include "shell-global.h"
+#include <gtk/gtk.h>
+
+typedef struct
+{
+ GIcon *icon;
+ guint size;
+} CacheKey;
+
+struct _ShellTextureCachePrivate
+{
+ GHashTable *gicon_cache; /* CacheKey -> CoglTexture* */
+};
+
+static void shell_texture_cache_dispose (GObject *object);
+static void shell_texture_cache_finalize (GObject *object);
+
+G_DEFINE_TYPE(ShellTextureCache, shell_texture_cache, G_TYPE_OBJECT);
+
+static guint
+cache_key_hash (gconstpointer a)
+{
+ CacheKey *akey = (CacheKey *)a;
+
+ if (akey->icon)
+ return g_icon_hash (akey->icon) + 31*akey->size;
+ g_assert_not_reached ();
+}
+
+static gboolean
+cache_key_equal (gconstpointer a,
+ gconstpointer b)
+{
+ CacheKey *akey = (CacheKey*)a;
+ CacheKey *bkey = (CacheKey*)b;
+
+ if (akey->size != bkey->size)
+ return FALSE;
+ if (akey->icon && bkey->icon)
+ return g_icon_equal (akey->icon, bkey->icon);
+ g_assert_not_reached ();
+}
+
+static void
+cache_key_destroy (gpointer a)
+{
+ CacheKey *akey = (CacheKey*)a;
+ if (akey->icon)
+ g_object_unref (akey->icon);
+ g_free (akey);
+}
+
+static void
+shell_texture_cache_class_init (ShellTextureCacheClass *klass)
+{
+ GObjectClass *gobject_class = (GObjectClass *)klass;
+
+ gobject_class->dispose = shell_texture_cache_dispose;
+ gobject_class->finalize = shell_texture_cache_finalize;
+}
+
+static void
+shell_texture_cache_init (ShellTextureCache *self)
+{
+ self->priv = g_new0 (ShellTextureCachePrivate, 1);
+ self->priv->gicon_cache = g_hash_table_new_full (cache_key_hash, cache_key_equal,
+ cache_key_destroy, cogl_handle_unref);
+}
+
+static void
+shell_texture_cache_dispose (GObject *object)
+{
+ ShellTextureCache *self = (ShellTextureCache*)object;
+
+ if (self->priv->gicon_cache)
+ g_hash_table_destroy (self->priv->gicon_cache);
+ self->priv->gicon_cache = NULL;
+
+ G_OBJECT_CLASS (shell_texture_cache_parent_class)->dispose (object);
+}
+
+static void
+shell_texture_cache_finalize (GObject *object)
+{
+ G_OBJECT_CLASS (shell_texture_cache_parent_class)->finalize (object);
+}
+
+ShellTextureCache*
+shell_texture_cache_new ()
+{
+ return SHELL_TEXTURE_CACHE (g_object_new (SHELL_TYPE_TEXTURE_CACHE,
+ NULL));
+}
+
+typedef struct {
+ char *uri;
+ GIcon *icon;
+ GtkIconInfo *icon_info;
+ gint width;
+ gint height;
+ gpointer user_data;
+} AsyncIconLookupData;
+
+
+static gboolean
+compute_pixbuf_scale (gint width,
+ gint height,
+ gint available_width,
+ gint available_height,
+ gint *new_width,
+ gint *new_height)
+{
+ int scaled_width, scaled_height;
+
+ if (width == 0 || height == 0)
+ return FALSE;
+
+ if (available_width >= 0 && available_height >= 0)
+ {
+ // This should keep the aspect ratio of the image intact, because if
+ // available_width < (available_height * width) / height
+ // than
+ // (available_width * height) / width < available_height
+ // So we are guaranteed to either scale the image to have an available_width
+ // for width and height scaled accordingly OR have the available_height
+ // for height and width scaled accordingly, whichever scaling results
+ // in the image that can fit both available dimensions.
+ scaled_width = MIN (available_width, (available_height * width) / height);
+ scaled_height = MIN (available_height, (available_width * height) / width);
+ }
+ else if (available_width >= 0)
+ {
+ scaled_width = available_width;
+ scaled_height = (available_width * height) / width;
+ }
+ else if (available_height >= 0)
+ {
+ scaled_width = (available_height * width) / height;
+ scaled_height = available_height;
+ }
+ else
+ {
+ scaled_width = scaled_height = 0;
+ }
+
+ // Scale the image only if that will not increase its original dimensions.
+ if (scaled_width >= 0 && scaled_height >= 0 && scaled_width < width && scaled_height < height)
+ {
+ *new_width = scaled_width;
+ *new_height = scaled_height;
+ return TRUE;
+ }
+ return FALSE;
+}
+
+static GdkPixbuf *
+impl_load_pixbuf_gicon (GIcon *icon,
+ GtkIconInfo *info,
+ int size,
+ GError **error)
+{
+ int scaled_width, scaled_height;
+ GdkPixbuf *pixbuf = gtk_icon_info_load_icon (info, error);
+ int width, height;
+
+ if (!pixbuf)
+ return NULL;
+
+ width = gdk_pixbuf_get_width (pixbuf);
+ height = gdk_pixbuf_get_height (pixbuf);
+
+ if (compute_pixbuf_scale (width,
+ height,
+ size, size,
+ &scaled_width, &scaled_height))
+ {
+ GdkPixbuf *scaled = gdk_pixbuf_scale_simple (pixbuf, width, height, GDK_INTERP_BILINEAR);
+ g_object_unref (pixbuf);
+ pixbuf = scaled;
+ }
+ return pixbuf;
+}
+
+// A private structure for keeping width and height.
+typedef struct {
+ int width;
+ int height;
+} Dimensions;
+
+/**
+ * on_image_size_prepared:
+ *
+ * @pixbuf_loader: #GdkPixbufLoader loading the image
+ * @width: the original width of the image
+ * @height: the original height of the image
+ * @data: pointer to the #Dimensions sructure containing available width and height for the image,
+ * available width or height can be -1 if the dimension is not limited
+ *
+ * Private function.
+ *
+ * Sets the size of the image being loaded to fit the available width and height dimensions,
+ * but never scales up the image beyond its actual size.
+ * Intended to be used as a callback for #GdkPixbufLoader "size-prepared" signal.
+ */
+static void
+on_image_size_prepared (GdkPixbufLoader *pixbuf_loader,
+ gint width,
+ gint height,
+ gpointer data)
+{
+ Dimensions *available_dimensions = data;
+ int available_width = available_dimensions->width;
+ int available_height = available_dimensions->height;
+ int scaled_width;
+ int scaled_height;
+
+ if (compute_pixbuf_scale (width, height, available_width, available_height,
+ &scaled_width, &scaled_height))
+ gdk_pixbuf_loader_set_size (pixbuf_loader, scaled_width, scaled_height);
+}
+
+static GdkPixbuf *
+impl_load_pixbuf_file (const char *uri,
+ int available_width,
+ int available_height,
+ GError **error)
+{
+ GdkPixbufLoader *pixbuf_loader = NULL;
+ GdkPixbuf *rotated_pixbuf = NULL;
+ GdkPixbuf *pixbuf;
+ GFile *file = NULL;
+ char *contents = NULL;
+ gsize size;
+ gboolean success;
+ Dimensions available_dimensions;
+ int width_before_rotation, width_after_rotation;
+
+ file = g_file_new_for_uri (uri);
+
+ success = g_file_load_contents (file, NULL, &contents, &size, NULL, error);
+
+ if (!success)
+ {
+ goto out;
+ }
+
+ pixbuf_loader = gdk_pixbuf_loader_new ();
+
+ available_dimensions.width = available_width;
+ available_dimensions.height = available_height;
+ g_signal_connect (pixbuf_loader, "size-prepared",
+ G_CALLBACK (on_image_size_prepared), &available_dimensions);
+
+ success = gdk_pixbuf_loader_write (pixbuf_loader,
+ (const guchar *) contents,
+ size,
+ error);
+ if (!success)
+ goto out;
+ success = gdk_pixbuf_loader_close (pixbuf_loader, error);
+ if (!success)
+ goto out;
+
+ pixbuf = gdk_pixbuf_loader_get_pixbuf (pixbuf_loader);
+
+ width_before_rotation = gdk_pixbuf_get_width (pixbuf);
+
+ rotated_pixbuf = gdk_pixbuf_apply_embedded_orientation (pixbuf);
+ width_after_rotation = gdk_pixbuf_get_width (rotated_pixbuf);
+
+ // There is currently no way to tell if the pixbuf will need to be rotated before it is loaded,
+ // so we only check that once it is loaded, and reload it again if it needs to be rotated in order
+ // to use the available width and height correctly.
+ // http://bugzilla.gnome.org/show_bug.cgi?id=579003
+ if (width_before_rotation != width_after_rotation)
+ {
+ g_object_unref (pixbuf_loader);
+ g_object_unref (rotated_pixbuf);
+ rotated_pixbuf = NULL;
+
+ pixbuf_loader = gdk_pixbuf_loader_new ();
+
+ // We know that the image will later be rotated, so we reverse the available dimensions.
+ available_dimensions.width = available_height;
+ available_dimensions.height = available_width;
+ g_signal_connect (pixbuf_loader, "size-prepared",
+ G_CALLBACK (on_image_size_prepared), &available_dimensions);
+
+ success = gdk_pixbuf_loader_write (pixbuf_loader,
+ (const guchar *) contents,
+ size,
+ error);
+ if (!success)
+ goto out;
+
+ success = gdk_pixbuf_loader_close (pixbuf_loader, error);
+ if (!success)
+ goto out;
+
+ pixbuf = gdk_pixbuf_loader_get_pixbuf (pixbuf_loader);
+
+ rotated_pixbuf = gdk_pixbuf_apply_embedded_orientation (pixbuf);
+ }
+
+out:
+ g_free (contents);
+ if (file)
+ g_object_unref (file);
+ if (pixbuf_loader)
+ g_object_unref (pixbuf_loader);
+ return rotated_pixbuf;
+}
+
+static void
+load_pixbuf_thread (GSimpleAsyncResult *result,
+ GObject *object,
+ GCancellable *cancellable)
+{
+ GdkPixbuf *pixbuf;
+ AsyncIconLookupData *data;
+ GError *error = NULL;
+
+ data = g_simple_async_result_get_op_res_gpointer (result);
+
+ if (data->uri)
+ pixbuf = impl_load_pixbuf_file (data->uri, data->width, data->height, &error);
+ else if (data->icon)
+ pixbuf = impl_load_pixbuf_gicon (data->icon, data->icon_info, data->width, &error);
+ else
+ g_assert_not_reached ();
+
+ if (error != NULL)
+ {
+ g_simple_async_result_set_from_error (result, error);
+ return;
+ }
+
+
+ g_simple_async_result_set_op_res_gpointer (result, g_object_ref (pixbuf),
+ g_object_unref);
+}
+
+static void
+icon_lookup_data_destroy (gpointer p)
+{
+ AsyncIconLookupData *data = p;
+
+ if (data->icon)
+ {
+ g_object_unref (data->icon);
+ gtk_icon_info_free (data->icon_info);
+ }
+ else if (data->uri)
+ g_free (data->uri);
+
+ g_free (data);
+}
+
+/**
+ * load_icon_pixbuf_async:
+ *
+ * Asynchronously load the #GdkPixbuf associated with a #GIcon. Currently
+ * the #GtkIconInfo must have already been provided.
+ */
+static void
+load_icon_pixbuf_async (ShellTextureCache *cache,
+ GIcon *icon,
+ GtkIconInfo *icon_info,
+ gint size,
+ GCancellable *cancellable,
+ GAsyncReadyCallback callback,
+ gpointer user_data)
+{
+ GSimpleAsyncResult *result;
+ AsyncIconLookupData *data;
+
+ data = g_new0 (AsyncIconLookupData, 1);
+ data->icon = g_object_ref (icon);
+ data->icon_info = gtk_icon_info_copy (icon_info);
+ data->width = data->height = size;
+ data->user_data = user_data;
+
+ result = g_simple_async_result_new (G_OBJECT (cache), callback, user_data, load_icon_pixbuf_async);
+
+ g_simple_async_result_set_op_res_gpointer (result, data, icon_lookup_data_destroy);
+ g_simple_async_result_run_in_thread (result, load_pixbuf_thread, G_PRIORITY_DEFAULT, cancellable);
+}
+
+static void
+load_uri_pixbuf_async (ShellTextureCache *cache,
+ const char *uri,
+ guint width,
+ guint height,
+ GCancellable *cancellable,
+ GAsyncReadyCallback callback,
+ gpointer user_data)
+{
+ GSimpleAsyncResult *result;
+ AsyncIconLookupData *data;
+
+ data = g_new0 (AsyncIconLookupData, 1);
+ data->uri = g_strdup (uri);
+ data->width = width;
+ data->height = height;
+ data->user_data = user_data;
+
+ result = g_simple_async_result_new (G_OBJECT (cache), callback, user_data, load_uri_pixbuf_async);
+
+ g_simple_async_result_set_op_res_gpointer (result, data, icon_lookup_data_destroy);
+ g_simple_async_result_run_in_thread (result, load_pixbuf_thread, G_PRIORITY_DEFAULT, cancellable);
+}
+
+static GdkPixbuf *
+load_pixbuf_async_finish (ShellTextureCache *cache, GAsyncResult *result, GError **error)
+{
+ GSimpleAsyncResult *simple = G_SIMPLE_ASYNC_RESULT (result);
+ if (g_simple_async_result_propagate_error (simple, error))
+ return NULL;
+ return g_simple_async_result_get_op_res_gpointer (simple);
+}
+
+typedef struct {
+ char *uri;
+ GIcon *icon;
+ GtkIconInfo *icon_info;
+ guint width;
+ guint height;
+ ClutterTexture *texture;
+} AsyncTextureLoadData;
+
+static CoglHandle
+pixbuf_to_cogl_handle (GdkPixbuf *pixbuf)
+{
+ return cogl_texture_new_from_data (gdk_pixbuf_get_width (pixbuf),
+ gdk_pixbuf_get_height (pixbuf),
+ 63, /* taken from clutter-texture.c default */
+ COGL_TEXTURE_AUTO_MIPMAP,
+ gdk_pixbuf_get_has_alpha (pixbuf) ? COGL_PIXEL_FORMAT_RGBA_8888 : COGL_PIXEL_FORMAT_RGB_888,
+ COGL_PIXEL_FORMAT_ANY,
+ gdk_pixbuf_get_rowstride (pixbuf),
+ gdk_pixbuf_get_pixels (pixbuf));
+}
+
+static void
+on_pixbuf_loaded (GObject *source,
+ GAsyncResult *result,
+ gpointer user_data)
+{
+ ShellTextureCache *cache;
+ AsyncTextureLoadData *data;
+ GdkPixbuf *pixbuf;
+ GError *error = NULL;
+ CoglHandle texdata;
+ CacheKey *key;
+
+ data = user_data;
+ cache = SHELL_TEXTURE_CACHE (source);
+ pixbuf = load_pixbuf_async_finish (cache, result, &error);
+ if (pixbuf == NULL)
+ {
+ /* TODO - we need a "broken image" display of some sort */
+ goto out;
+ }
+
+ texdata = pixbuf_to_cogl_handle (pixbuf);
+
+ if (data->icon)
+ {
+ gpointer orig_key, value;
+
+ key = g_new0 (CacheKey, 1);
+ key->icon = g_object_ref (data->icon);
+ key->size = data->width;
+
+ if (!g_hash_table_lookup_extended (cache->priv->gicon_cache, key,
+ &orig_key, &value))
+ g_hash_table_insert (cache->priv->gicon_cache, key,
+ texdata);
+ else
+ cache_key_destroy (key);
+ }
+
+ clutter_texture_set_cogl_texture (data->texture, texdata);
+
+out:
+ if (data->icon)
+ {
+ gtk_icon_info_free (data->icon_info);
+ g_object_unref (data->icon);
+ }
+ else if (data->uri)
+ g_free (data->uri);
+ /* Alternatively we could weakref and just do nothing if the texture
+ is destroyed */
+ g_object_unref (data->texture);
+
+ g_free (data);
+}
+
+/**
+ * shell_texture_cache_load_gicon:
+ *
+ * This method returns a new #ClutterClone for a given #GIcon. If the
+ * icon isn't loaded already, the texture will be filled asynchronously.
+ *
+ * Return Value: (transfer none): A new #ClutterActor for the icon
+ */
+ClutterActor *
+shell_texture_cache_load_gicon (ShellTextureCache *cache,
+ GIcon *icon,
+ gint size)
+{
+ ClutterTexture *texture;
+ CoglHandle texdata;
+ CacheKey key;
+
+ texture = CLUTTER_TEXTURE (clutter_texture_new ());
+ clutter_actor_set_size (CLUTTER_ACTOR (texture), size, size);
+
+ key.icon = icon;
+ key.size = size;
+ texdata = g_hash_table_lookup (cache->priv->gicon_cache, &key);
+
+ if (texdata == NULL)
+ {
+ GtkIconTheme *theme;
+ GtkIconInfo *info;
+
+ /* Do theme lookups in the main thread to avoid thread-unsafety */
+ theme = gtk_icon_theme_get_default ();
+
+ info = gtk_icon_theme_lookup_by_gicon (theme, icon, size, GTK_ICON_LOOKUP_USE_BUILTIN);
+ if (info != NULL)
+ {
+ AsyncTextureLoadData *data;
+ data = g_new0 (AsyncTextureLoadData, 1);
+
+ data->icon = icon;
+ data->icon_info = info;
+ data->texture = g_object_ref (texture);
+ load_icon_pixbuf_async (cache, icon, info, size, NULL, on_pixbuf_loaded, data);
+ }
+ }
+ else
+ {
+ clutter_texture_set_cogl_texture (texture, texdata);
+ }
+
+ return CLUTTER_ACTOR (texture);
+}
+
+/**
+ * shell_texture_cache_load_uri:
+ *
+ * @cache: The texture cache instance
+ * @uri: uri of the image file from which to create a pixbuf
+ * @available_width: available width for the image, can be -1 if not limited
+ * @available_height: available height for the image, can be -1 if not limited
+ *
+ * Asynchronously load an image. Initially, the returned texture will have a natural
+ * size of zero. At some later point, either the image will be loaded successfully
+ * and at that point size will be negotiated, or upon an error, no image will be set.
+ *
+ * Return value: (transfer none): A new #ClutterActor with no image loaded initially.
+ */
+ClutterActor *
+shell_texture_cache_load_uri_async (ShellTextureCache *cache,
+ const gchar *uri,
+ int available_width,
+ int available_height)
+{
+ ClutterTexture *texture;
+ AsyncTextureLoadData *data;
+
+ texture = CLUTTER_TEXTURE (clutter_texture_new ());
+
+ data = g_new0 (AsyncTextureLoadData, 1);
+ data->uri = g_strdup (uri);
+ data->width = available_width;
+ data->height = available_height;
+ data->texture = g_object_ref (texture);
+ load_uri_pixbuf_async (cache, uri, available_width, available_height, NULL, on_pixbuf_loaded, data);
+
+ return CLUTTER_ACTOR (texture);
+}
+
+/**
+ * shell_texture_cache_load_uri_sync:
+ *
+ * @cache: The texture cache instance
+ * @uri: uri of the image file from which to create a pixbuf
+ * @available_width: available width for the image, can be -1 if not limited
+ * @available_height: available height for the image, can be -1 if not limited
+ * @error: Return location for error
+ *
+ * Synchronously load an image from a uri. The image is scaled down to fit the
+ * available width and height imensions, but the image is never scaled up beyond
+ * its actual size. The pixbuf is rotated according to the associated orientation
+ * setting.
+ *
+ * Return value: (transfer none): A new #ClutterActor with the image file loaded if it was
+ * generated succesfully, %NULL otherwise
+ */
+ClutterActor *
+shell_texture_cache_load_uri_sync (ShellTextureCache *cache,
+ const gchar *uri,
+ int available_width,
+ int available_height,
+ GError **error)
+{
+ ClutterTexture *texture;
+ GdkPixbuf *pixbuf;
+ CoglHandle texdata;
+
+ pixbuf = impl_load_pixbuf_file (uri, available_width, available_height, error);
+ if (!pixbuf)
+ return NULL;
+
+ texture = CLUTTER_TEXTURE (clutter_texture_new ());
+ texdata = pixbuf_to_cogl_handle (pixbuf);
+ clutter_texture_set_cogl_texture (texture, texdata);
+
+ return CLUTTER_ACTOR (texture);
+}
+
+static ShellTextureCache *instance = NULL;
+
+/**
+ * shell_texture_cache_get_default:
+ *
+ * Return value: (transfer none): The global texture cache
+ */
+ShellTextureCache*
+shell_texture_cache_get_default ()
+{
+ if (instance == NULL)
+ instance = g_object_new (SHELL_TYPE_TEXTURE_CACHE, NULL);
+ return instance;
+}
diff --git a/src/shell-texture-cache.h b/src/shell-texture-cache.h
new file mode 100644
index 0000000..68dd7d6
--- /dev/null
+++ b/src/shell-texture-cache.h
@@ -0,0 +1,51 @@
+#ifndef __SHELL_TEXTURE_CACHE_H__
+#define __SHELL_TEXTURE_CACHE_H__
+
+#include <gio/gio.h>
+#include <clutter/clutter.h>
+
+#define SHELL_TYPE_TEXTURE_CACHE (shell_texture_cache_get_type ())
+#define SHELL_TEXTURE_CACHE(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), SHELL_TYPE_TEXTURE_CACHE, ShellTextureCache))
+#define SHELL_TEXTURE_CACHE_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), SHELL_TYPE_TEXTURE_CACHE, ShellTextureCacheClass))
+#define SHELL_IS_TEXTURE_CACHE(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), SHELL_TYPE_TEXTURE_CACHE))
+#define SHELL_IS_TEXTURE_CACHE_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), SHELL_TYPE_TEXTURE_CACHE))
+#define SHELL_TEXTURE_CACHE_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), SHELL_TYPE_TEXTURE_CACHE, ShellTextureCacheClass))
+
+typedef struct _ShellTextureCache ShellTextureCache;
+typedef struct _ShellTextureCacheClass ShellTextureCacheClass;
+
+typedef struct _ShellTextureCachePrivate ShellTextureCachePrivate;
+
+struct _ShellTextureCache
+{
+ GObject parent;
+
+ ShellTextureCachePrivate *priv;
+};
+
+struct _ShellTextureCacheClass
+{
+ GObjectClass parent_class;
+
+};
+
+GType shell_texture_cache_get_type (void) G_GNUC_CONST;
+
+ShellTextureCache* shell_texture_cache_get_default();
+
+ClutterActor *shell_texture_cache_load_gicon (ShellTextureCache *cache,
+ GIcon *icon,
+ gint size);
+
+ClutterActor *shell_texture_cache_load_uri_async (ShellTextureCache *cache,
+ const gchar *filename,
+ int available_width,
+ int available_height);
+
+ClutterActor *shell_texture_cache_load_uri_sync (ShellTextureCache *cache,
+ const gchar *filename,
+ int available_width,
+ int available_height,
+ GError **error);
+
+#endif /* __SHELL_TEXTURE_CACHE_H__ */
[
Date Prev][
Date Next] [
Thread Prev][
Thread Next]
[
Thread Index]
[
Date Index]
[
Author Index]