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.
This commit is contained in:
parent
10afe46195
commit
d024dbd779
@ -8,7 +8,6 @@ dist_jsui_DATA = \
|
|||||||
dnd.js \
|
dnd.js \
|
||||||
docDisplay.js \
|
docDisplay.js \
|
||||||
genericDisplay.js \
|
genericDisplay.js \
|
||||||
gtkutil.js \
|
|
||||||
link.js \
|
link.js \
|
||||||
main.js \
|
main.js \
|
||||||
overlay.js \
|
overlay.js \
|
||||||
|
@ -10,7 +10,6 @@ const Lang = imports.lang;
|
|||||||
const Signals = imports.signals;
|
const Signals = imports.signals;
|
||||||
|
|
||||||
const GenericDisplay = imports.ui.genericDisplay;
|
const GenericDisplay = imports.ui.genericDisplay;
|
||||||
const GtkUtil = imports.ui.gtkutil;
|
|
||||||
const Main = imports.ui.main;
|
const Main = imports.ui.main;
|
||||||
|
|
||||||
const ENTERED_MENU_COLOR = new Clutter.Color();
|
const ENTERED_MENU_COLOR = new Clutter.Color();
|
||||||
@ -70,7 +69,14 @@ AppDisplayItem.prototype = {
|
|||||||
let iconTheme = Gtk.IconTheme.get_default();
|
let iconTheme = Gtk.IconTheme.get_default();
|
||||||
|
|
||||||
let gicon = appInfo.get_icon();
|
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);
|
this._setItemInfo(name, description, icon);
|
||||||
},
|
},
|
||||||
|
|
||||||
|
@ -128,16 +128,7 @@ DocDisplayItem.prototype = {
|
|||||||
if (this._docInfo.get_mime_type() == null || this._docInfo.get_mime_type().indexOf("image/") != 0)
|
if (this._docInfo.get_mime_type() == null || this._docInfo.get_mime_type().indexOf("image/") != 0)
|
||||||
return null;
|
return null;
|
||||||
|
|
||||||
let largePreviewPixbuf = Shell.create_pixbuf_from_image_file(this._docInfo.get_uri(), availableWidth, availableHeight);
|
return Shell.TextureCache.load_uri_sync(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;
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -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;
|
|
||||||
}
|
|
@ -70,6 +70,8 @@ libgnome_shell_la_SOURCES = \
|
|||||||
shell-status-menu.h \
|
shell-status-menu.h \
|
||||||
shell-tray-manager.c \
|
shell-tray-manager.c \
|
||||||
shell-tray-manager.h \
|
shell-tray-manager.h \
|
||||||
|
shell-texture-cache.c \
|
||||||
|
shell-texture-cache.h \
|
||||||
shell-wm.c \
|
shell-wm.c \
|
||||||
shell-wm.h
|
shell-wm.h
|
||||||
|
|
||||||
|
@ -392,190 +392,6 @@ shell_get_thumbnail_for_recent_info(GtkRecentInfo *recent_info)
|
|||||||
return pixbuf;
|
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:
|
* shell_get_categories_for_desktop_file:
|
||||||
|
@ -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_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);
|
GSList *shell_get_categories_for_desktop_file(const char *desktop_file_name);
|
||||||
|
|
||||||
guint16 shell_get_event_key_symbol(ClutterEvent *event);
|
guint16 shell_get_event_key_symbol(ClutterEvent *event);
|
||||||
|
640
src/shell-texture-cache.c
Normal file
640
src/shell-texture-cache.c
Normal file
@ -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;
|
||||||
|
}
|
51
src/shell-texture-cache.h
Normal file
51
src/shell-texture-cache.h
Normal file
@ -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__ */
|
Loading…
Reference in New Issue
Block a user