GtkIconHelper: Convert to using cairo surfaces as base and support scales

We render the source into a cairo_surface_t so that we can render it
with cairo directly, rather than having to convert it from a pixbuf
every time. We also specify the target window when creating the cairo
surface so that rendering can be faster.

Using cairo surfaces also allows us to seamlessly support window scales.

We also add a GTK_IMAGE_SURFACE source type.
This commit is contained in:
Alexander Larsson
2013-06-24 14:13:43 +02:00
parent 635ae9bd91
commit 031e1a98a0
3 changed files with 466 additions and 26 deletions

View File

@ -19,6 +19,7 @@
#include "config.h"
#include <math.h>
#include "gtkiconhelperprivate.h"
G_DEFINE_TYPE (GtkIconHelper, _gtk_icon_helper, G_TYPE_OBJECT)
@ -29,11 +30,13 @@ struct _GtkIconHelperPrivate {
GdkWindow *window;
GdkPixbuf *orig_pixbuf;
int orig_pixbuf_scale;
GdkPixbufAnimation *animation;
GIcon *gicon;
GtkIconSet *icon_set;
gchar *icon_name;
gchar *stock_id;
cairo_surface_t *orig_surface;
GtkIconSize icon_size;
gint pixel_size;
@ -43,6 +46,12 @@ struct _GtkIconHelperPrivate {
GdkPixbuf *rendered_pixbuf;
GtkStateFlags last_rendered_state;
cairo_surface_t *rendered_surface;
gint rendered_surface_width;
gint rendered_surface_height;
GtkStateFlags last_surface_state;
gint last_surface_scale;
};
void
@ -54,6 +63,18 @@ _gtk_icon_helper_clear (GtkIconHelper *self)
g_clear_object (&self->priv->rendered_pixbuf);
g_clear_object (&self->priv->window);
if (self->priv->orig_surface)
{
cairo_surface_destroy (self->priv->orig_surface);
self->priv->orig_surface = NULL;
}
if (self->priv->rendered_surface)
{
cairo_surface_destroy (self->priv->rendered_surface);
self->priv->rendered_surface = NULL;
}
if (self->priv->icon_set != NULL)
{
G_GNUC_BEGIN_IGNORE_DEPRECATIONS;
@ -71,6 +92,8 @@ _gtk_icon_helper_clear (GtkIconHelper *self)
self->priv->storage_type = GTK_IMAGE_EMPTY;
self->priv->icon_size = GTK_ICON_SIZE_INVALID;
self->priv->last_rendered_state = GTK_STATE_FLAG_NORMAL;
self->priv->last_surface_state = GTK_STATE_FLAG_NORMAL;
self->priv->last_surface_scale = 0;
}
void
@ -120,6 +143,7 @@ _gtk_icon_helper_init (GtkIconHelper *self)
self->priv->icon_size = GTK_ICON_SIZE_INVALID;
self->priv->pixel_size = -1;
self->priv->last_rendered_state = GTK_STATE_FLAG_NORMAL;
self->priv->orig_pixbuf_scale = 1;
}
static void
@ -300,6 +324,64 @@ ensure_pixbuf_for_icon_set (GtkIconHelper *self,
G_GNUC_END_IGNORE_DEPRECATIONS;
}
static void
get_surface_size (GtkIconHelper *self,
GtkStyleContext *context,
cairo_surface_t *surface,
int *width,
int *height)
{
double x_scale, y_scale;
if (cairo_surface_get_type (surface) == CAIRO_SURFACE_TYPE_IMAGE)
{
x_scale = y_scale = 1;
#ifdef HAVE_CAIRO_SURFACE_SET_DEVICE_SCALE
cairo_surface_get_device_scale (surface, &x_scale, &y_scale);
#endif
/* Assume any set scaling is icon scale */
*width =
ceil (cairo_image_surface_get_width (surface) / x_scale);
*height =
ceil (cairo_image_surface_get_height (surface) / y_scale);
}
else
ensure_icon_size (self, context, width, height);
}
static void
ensure_pixbuf_from_surface (GtkIconHelper *self,
GtkStyleContext *context)
{
cairo_surface_t *surface;
gint width, height;
cairo_t *cr;
if (!check_invalidate_pixbuf (self, context))
return;
if (self->priv->rendered_pixbuf)
return;
get_surface_size (self, context, self->priv->orig_surface, &width, &height);
surface = cairo_image_surface_create (CAIRO_FORMAT_ARGB32,
width, height);
cr = cairo_create (surface);
cairo_set_source_surface (cr, self->priv->orig_surface, 0, 0);
cairo_paint (cr);
cairo_destroy (cr);
self->priv->rendered_pixbuf =
gdk_pixbuf_get_from_surface (surface, 0, 0, width, height);
cairo_surface_destroy (surface);
}
static void
ensure_pixbuf_at_size (GtkIconHelper *self,
GtkStyleContext *context)
@ -312,17 +394,34 @@ ensure_pixbuf_at_size (GtkIconHelper *self,
if (self->priv->rendered_pixbuf)
return;
if (self->priv->pixel_size != -1 ||
self->priv->icon_size != GTK_ICON_SIZE_INVALID)
if (self->priv->force_scale_pixbuf &&
(self->priv->pixel_size != -1 ||
self->priv->icon_size != GTK_ICON_SIZE_INVALID))
{
ensure_icon_size (self, context, &width, &height);
if (width < gdk_pixbuf_get_width (self->priv->orig_pixbuf) ||
if (self->priv->orig_pixbuf_scale > 1 ||
/* These should divide the orig_pixbuf size by scale, but need not
due to the above scale > 1 check */
width < gdk_pixbuf_get_width (self->priv->orig_pixbuf) ||
height < gdk_pixbuf_get_height (self->priv->orig_pixbuf))
self->priv->rendered_pixbuf =
gdk_pixbuf_scale_simple (self->priv->orig_pixbuf,
width, height,
GDK_INTERP_BILINEAR);
{
width = MIN (width, gdk_pixbuf_get_width (self->priv->orig_pixbuf) / self->priv->orig_pixbuf_scale);
height = MIN (height, gdk_pixbuf_get_height (self->priv->orig_pixbuf) / self->priv->orig_pixbuf_scale);
self->priv->rendered_pixbuf =
gdk_pixbuf_scale_simple (self->priv->orig_pixbuf,
width, height,
GDK_INTERP_BILINEAR);
}
}
else if (self->priv->orig_pixbuf_scale > 1)
{
width = gdk_pixbuf_get_width (self->priv->orig_pixbuf) / self->priv->orig_pixbuf_scale;
height =gdk_pixbuf_get_height (self->priv->orig_pixbuf) / self->priv->orig_pixbuf_scale;
self->priv->rendered_pixbuf =
gdk_pixbuf_scale_simple (self->priv->orig_pixbuf,
width, height,
GDK_INTERP_BILINEAR);
}
if (!self->priv->rendered_pixbuf)
@ -338,11 +437,12 @@ _gtk_icon_helper_ensure_pixbuf (GtkIconHelper *self,
switch (self->priv->storage_type)
{
case GTK_IMAGE_SURFACE:
ensure_pixbuf_from_surface (self, context);
break;
case GTK_IMAGE_PIXBUF:
if (self->priv->force_scale_pixbuf)
ensure_pixbuf_at_size (self, context);
else
pixbuf = g_object_ref (self->priv->orig_pixbuf);
ensure_pixbuf_at_size (self, context);
break;
case GTK_IMAGE_STOCK:
@ -379,24 +479,320 @@ _gtk_icon_helper_ensure_pixbuf (GtkIconHelper *self,
return pixbuf;
}
static gint
get_scale_factor (GtkIconHelper *self,
GtkStyleContext *context)
{
GdkScreen *screen;
if (self->priv->window)
return gdk_window_get_scale_factor (self->priv->window);
screen = gtk_style_context_get_screen (context);
/* else fall back to something that is more likely to be right than
* just returning 1:
*/
return gdk_screen_get_monitor_scale_factor (screen, 0);
}
static gboolean
check_invalidate_surface (GtkIconHelper *self,
GtkStyleContext *context)
{
GtkStateFlags state;
int scale;
state = gtk_style_context_get_state (context);
scale = get_scale_factor (self, context);
if ((self->priv->rendered_surface != NULL) &&
(self->priv->last_surface_state == state) &&
(self->priv->last_surface_scale == scale))
return FALSE;
self->priv->last_surface_state = state;
self->priv->last_surface_scale = scale;
if (self->priv->rendered_surface)
cairo_surface_destroy (self->priv->rendered_surface);
self->priv->rendered_surface = NULL;
return TRUE;
}
static void
ensure_surface_from_surface (GtkIconHelper *self,
GtkStyleContext *context)
{
if (!check_invalidate_surface (self, context))
return;
if (self->priv->rendered_surface)
return;
self->priv->rendered_surface =
cairo_surface_reference (self->priv->orig_surface);
get_surface_size (self, context, self->priv->orig_surface,
&self->priv->rendered_surface_width,
&self->priv->rendered_surface_height);
}
static void
ensure_surface_from_pixbuf (GtkIconHelper *self,
GtkStyleContext *context)
{
gint width, height;
GdkPixbuf *pixbuf;
int scale;
if (!check_invalidate_surface (self, context))
return;
if (self->priv->rendered_surface)
return;
scale = get_scale_factor (self, context);
if (self->priv->force_scale_pixbuf &&
(self->priv->pixel_size != -1 ||
self->priv->icon_size != GTK_ICON_SIZE_INVALID))
{
ensure_icon_size (self, context, &width, &height);
if (scale != self->priv->orig_pixbuf_scale ||
width < gdk_pixbuf_get_width (self->priv->orig_pixbuf) / self->priv->orig_pixbuf_scale ||
height < gdk_pixbuf_get_height (self->priv->orig_pixbuf) / self->priv->orig_pixbuf_scale)
{
width = MIN (width * scale, gdk_pixbuf_get_width (self->priv->orig_pixbuf) * scale / self->priv->orig_pixbuf_scale);
height = MIN (height * scale, gdk_pixbuf_get_height (self->priv->orig_pixbuf) * scale / self->priv->orig_pixbuf_scale);
pixbuf = gdk_pixbuf_scale_simple (self->priv->orig_pixbuf,
width, height,
GDK_INTERP_BILINEAR);
}
else
{
pixbuf = g_object_ref (self->priv->orig_pixbuf);
scale = self->priv->orig_pixbuf_scale;
}
}
else
{
pixbuf = g_object_ref (self->priv->orig_pixbuf);
scale = self->priv->orig_pixbuf_scale;
}
self->priv->rendered_surface_width = (gdk_pixbuf_get_width (pixbuf) + scale - 1) / scale;
self->priv->rendered_surface_height = (gdk_pixbuf_get_height (pixbuf) + scale - 1) / scale;
self->priv->rendered_surface = gdk_cairo_surface_create_from_pixbuf (pixbuf, scale, self->priv->window);
g_object_unref (pixbuf);
}
static void
ensure_surface_for_icon_set (GtkIconHelper *self,
GtkStyleContext *context,
GtkIconSet *icon_set)
{
gint scale;
if (!check_invalidate_surface (self, context))
return;
scale = get_scale_factor (self, context);
self->priv->rendered_surface =
gtk_icon_set_render_icon_surface (icon_set, context,
self->priv->icon_size,
scale, self->priv->window);
if (self->priv->rendered_surface)
get_surface_size (self, context, self->priv->rendered_surface,
&self->priv->rendered_surface_width,
&self->priv->rendered_surface_height);
}
static void
ensure_stated_surface_from_info (GtkIconHelper *self,
GtkStyleContext *context,
GtkIconInfo *info,
int scale)
{
GdkPixbuf *destination = NULL;
cairo_surface_t *surface;
gboolean symbolic;
symbolic = FALSE;
if (info)
destination =
gtk_icon_info_load_symbolic_for_context (info,
context,
&symbolic,
NULL);
if (destination == NULL)
{
GtkIconSet *icon_set;
icon_set = gtk_style_context_lookup_icon_set (context, GTK_STOCK_MISSING_IMAGE);
destination =
gtk_icon_set_render_icon_pixbuf (icon_set, context, self->priv->icon_size);
}
else if (!symbolic)
{
GtkIconSource *source;
GdkPixbuf *rendered;
G_GNUC_BEGIN_IGNORE_DEPRECATIONS;
source = gtk_icon_source_new ();
gtk_icon_source_set_pixbuf (source, destination);
/* The size here is arbitrary; since size isn't
* wildcarded in the source, it isn't supposed to be
* scaled by the engine function
*/
gtk_icon_source_set_size (source,
GTK_ICON_SIZE_SMALL_TOOLBAR);
gtk_icon_source_set_size_wildcarded (source, FALSE);
rendered = gtk_render_icon_pixbuf (context, source, (GtkIconSize) -1);
gtk_icon_source_free (source);
G_GNUC_END_IGNORE_DEPRECATIONS;
g_object_unref (destination);
destination = rendered;
}
surface = NULL;
if (destination)
{
surface = gdk_cairo_surface_create_from_pixbuf (destination, scale, self->priv->window);
self->priv->rendered_surface_width =
(gdk_pixbuf_get_width (destination) + scale - 1) / scale;
self->priv->rendered_surface_height =
(gdk_pixbuf_get_height (destination) + scale - 1) / scale;
}
self->priv->rendered_surface = surface;
}
static void
ensure_surface_for_icon_name_or_gicon (GtkIconHelper *self,
GtkStyleContext *context)
{
GtkIconTheme *icon_theme;
gint width, height, scale;
GtkIconInfo *info;
GtkIconLookupFlags flags;
if (!check_invalidate_surface (self, context))
return;
icon_theme = gtk_icon_theme_get_default ();
flags = get_icon_lookup_flags (self);
ensure_icon_size (self, context, &width, &height);
scale = get_scale_factor (self, context);
if (self->priv->storage_type == GTK_IMAGE_ICON_NAME &&
self->priv->icon_name != NULL)
{
info = gtk_icon_theme_lookup_icon_for_scale (icon_theme,
self->priv->icon_name,
MIN (width, height),
scale, flags);
}
else if (self->priv->storage_type == GTK_IMAGE_GICON &&
self->priv->gicon != NULL)
{
info = gtk_icon_theme_lookup_by_gicon_for_scale (icon_theme,
self->priv->gicon,
MIN (width, height),
scale, flags);
}
else
{
g_assert_not_reached ();
return;
}
ensure_stated_surface_from_info (self, context, info, scale);
if (info)
g_object_unref (info);
}
cairo_surface_t *
_gtk_icon_helper_ensure_surface (GtkIconHelper *self,
GtkStyleContext *context)
{
cairo_surface_t *surface = NULL;
GtkIconSet *icon_set;
switch (self->priv->storage_type)
{
case GTK_IMAGE_SURFACE:
ensure_surface_from_surface (self, context);
break;
case GTK_IMAGE_PIXBUF:
ensure_surface_from_pixbuf (self, context);
break;
case GTK_IMAGE_STOCK:
icon_set = gtk_style_context_lookup_icon_set (context, self->priv->stock_id);
if (icon_set != NULL)
ensure_surface_for_icon_set (self, context, icon_set);
else
surface = NULL;
break;
case GTK_IMAGE_ICON_SET:
icon_set = self->priv->icon_set;
ensure_surface_for_icon_set (self, context, icon_set);
break;
case GTK_IMAGE_ICON_NAME:
case GTK_IMAGE_GICON:
ensure_surface_for_icon_name_or_gicon (self, context);
break;
case GTK_IMAGE_ANIMATION:
case GTK_IMAGE_EMPTY:
default:
surface = NULL;
break;
}
if (surface == NULL &&
self->priv->rendered_surface != NULL)
surface = cairo_surface_reference (self->priv->rendered_surface);
return surface;
}
void
_gtk_icon_helper_get_size (GtkIconHelper *self,
GtkStyleContext *context,
gint *width_out,
gint *height_out)
{
GdkPixbuf *pix;
cairo_surface_t *surface;
gint width, height;
width = height = 0;
pix = _gtk_icon_helper_ensure_pixbuf (self, context);
surface = _gtk_icon_helper_ensure_surface (self, context);
if (pix != NULL)
if (surface != NULL)
{
width = gdk_pixbuf_get_width (pix);
height = gdk_pixbuf_get_height (pix);
g_object_unref (pix);
width = self->priv->rendered_surface_width;
height = self->priv->rendered_surface_height;
cairo_surface_destroy (surface);
}
else if (self->priv->storage_type == GTK_IMAGE_ANIMATION)
{
@ -488,6 +884,19 @@ _gtk_icon_helper_set_animation (GtkIconHelper *self,
}
}
void
_gtk_icon_helper_set_surface (GtkIconHelper *self,
cairo_surface_t *surface)
{
_gtk_icon_helper_clear (self);
if (surface != NULL)
{
self->priv->storage_type = GTK_IMAGE_SURFACE;
self->priv->orig_surface = cairo_surface_reference (surface);
}
}
void
_gtk_icon_helper_set_stock_id (GtkIconHelper *self,
const gchar *stock_id,
@ -585,6 +994,12 @@ _gtk_icon_helper_peek_icon_set (GtkIconHelper *self)
return self->priv->icon_set;
}
cairo_surface_t *
_gtk_icon_helper_peek_surface (GtkIconHelper *self)
{
return self->priv->orig_surface;
}
const gchar *
_gtk_icon_helper_get_stock_id (GtkIconHelper *self)
{
@ -610,14 +1025,13 @@ _gtk_icon_helper_draw (GtkIconHelper *self,
gdouble x,
gdouble y)
{
GdkPixbuf *pixbuf;
cairo_surface_t *surface;
pixbuf = _gtk_icon_helper_ensure_pixbuf (self, context);
if (pixbuf != NULL)
surface = _gtk_icon_helper_ensure_surface (self, context);
if (surface != NULL)
{
gtk_render_icon (context, cr, pixbuf, x, y);
g_object_unref (pixbuf);
gtk_render_icon_surface (context, cr, surface, x, y);
cairo_surface_destroy (surface);
}
}
@ -643,3 +1057,20 @@ _gtk_icon_helper_set_force_scale_pixbuf (GtkIconHelper *self,
_gtk_icon_helper_invalidate (self);
}
}
void
_gtk_icon_helper_set_pixbuf_scale (GtkIconHelper *self,
int scale)
{
if (self->priv->orig_pixbuf_scale != scale)
{
self->priv->orig_pixbuf_scale = scale;
_gtk_icon_helper_invalidate (self);
}
}
int
_gtk_icon_helper_get_pixbuf_scale (GtkIconHelper *self)
{
return self->priv->orig_pixbuf_scale;
}