[PATCH] GNOME Canvas GdkPixbuf scaling



Hackers,

Attached is a patch that improves the scaling quality of GdkPixbuf
items on the GNOME Canvas.

Rationale:

The old way of doing scaling for GdkPixbufs on the GNOME Canvas for
the gnome_canvas_pixbuf object was to let libart do it with a nearest
neighbor approximation.  The resulting image didn't look too good.

This patch changes the behavior of gnome_canvas_pixbuf to do scaling
using the pixel operations built into gdkpixbuf's.  These operations
allow scaling types like: NEAREST, TILES, BILINEAR and HYPER.  So
essentially when the pixbuf item is rendered it does its scaling
itself, rather then letting libart do it and the scaling algorithm is
now selectable.  The canvas item gets all of the benefits of
gdkpixbuf's built in scaling algorithms including the use of MMX
acceleration.

Note, if the items affine transformation includes a rotate libart
still uses a nearest neighbor approximation to render the item.  So it
will still not look so good, but at least before the rotation the
Pixbuf was scaled decently.

Along with the use of the GdkPixbuf scaling routines I've also added
functionality of scale caches.  These scale caches work with the
canvas item to cache scaled pixbufs so they don't have to be rescaled
each time to item is rendered.  The benefits of these caches is very
apparent when you have a 512x512 pixbuf and change the zoom factor on
the canvas very quickly on a non-superfast machine, or move the item,
or anything else that causes a rerender.

All of the features of this patch are customizable, so that if you
want to use the scaling features of gdkpixbuf without a scale cache
you can do that, or you tune the cache's behavior.  The scale cache
also recognizes hits on scaled pixbufs, so for each time that the
cache scores a hit on a already scaled pixbuf its hit count is
increased, which decreases its chances of being expunged from the
cache due to a size limits.

Detailed Description:

        *Added more arguments to GnomeCanvasPixbuf:

                use_scale_cache (gboolean) - enable scale caching 
                scale_cache_limit (integer) - Maximum number of pixbufs to cache, -1 for unlimited.
                scale_type (enum) - GdkPixbuf scale type (GTK_INTERP_NEAREST, etc.)
        
        * gnome_canvas_pixbuf_render() : Changed rendering of pixbuf
        * to canvas so that if there is a scale component to the items
        * affine transform the scaling is done with gdkpixbuf's
        * functions rather the libarts.  This will allow better visual
        * results after scaling.

        * gdk-pixbuf/gdk-pixbuf-scale-cache.c : New file.  Implements
        * the scale cache facilities.
        
        * gdk-pixbuf/gdk-pixbuf-scale-cache.h : New file.

This patch is against version 0.9.0 of GdkPixbuf.  Since it only deals
with the GNOME Canvas item for GdkPixbuf's it should have no impact on
GTK+'s HEAD branch.

I'd appreciate any feedback or comments.  Thanks,
       
Rusty
-- 
Rusty Conover 
Systems Programmer
Zoot Enterprises Inc, www.zootweb.com

diff -u -r ../../orig-gdk-pixbuf/AUTHORS ./AUTHORS
--- ../../orig-gdk-pixbuf/AUTHORS	Sat Feb  3 16:22:52 2001
+++ ./AUTHORS	Sat Feb  3 16:54:45 2001
@@ -9,3 +9,4 @@
 Cody Russell (bratsche gnome org)
 Arjan van de Ven (arjan fenrus demon nl)
 Michael Zucchi (zucchi zedzone mmc com au)
+Rusty Conover (rconover zootweb com)
Only in .: Makefile.in
Only in .: autogen.sh
Only in .: conftest.S
Only in .: conftest.o
Only in ./demo: Makefile.in
Only in ./doc: Makefile.in
Only in .: foo.patch
diff -u -r ../../orig-gdk-pixbuf/gdk-pixbuf/Makefile.am ./gdk-pixbuf/Makefile.am
--- ../../orig-gdk-pixbuf/gdk-pixbuf/Makefile.am	Sat Feb  3 16:22:52 2001
+++ ./gdk-pixbuf/Makefile.am	Sat Jan 13 23:54:24 2001
@@ -185,6 +185,7 @@
 	gdk-pixbuf-render.c	\
 	gdk-pixbuf-scale.c	\
 	gdk-pixbuf-util.c	\
+	gdk-pixbuf-scale-cache.c
 	$(extra_sources)
 
 libgdk_pixbuf_la_LDFLAGS = -version-info 2:0:0 $(GLIB_LIBS) $(GTK_LIBS)
@@ -217,6 +218,7 @@
 	gdk-pixbuf-features.h		\
 	gdk-pixbuf-xlib.h		\
 	gdk-pixbuf-xlibrgb.h		\
+	gdk-pixbuf-scale-cache.h        \
 	$(CANVAS_PIXBUF_HEADERFILES)
 
 noinst_HEADERS =			\
Only in ../../orig-gdk-pixbuf/gdk-pixbuf: Makefile.in
Only in ../../orig-gdk-pixbuf/gdk-pixbuf: gdk-pixbuf-features.h
Only in ./gdk-pixbuf: gdk-pixbuf-scale-cache.c
Only in ./gdk-pixbuf: gdk-pixbuf-scale-cache.h
diff -u -r ../../orig-gdk-pixbuf/gdk-pixbuf/gnome-canvas-pixbuf.c ./gdk-pixbuf/gnome-canvas-pixbuf.c
--- ../../orig-gdk-pixbuf/gdk-pixbuf/gnome-canvas-pixbuf.c	Sat Feb  3 16:22:52 2001
+++ ./gdk-pixbuf/gnome-canvas-pixbuf.c	Sat Feb  3 16:29:11 2001
@@ -28,8 +28,8 @@
 #include <libart_lgpl/art_rgb_rgba_affine.h>
 #include "gdk-pixbuf-private.h"
 #include "gnome-canvas-pixbuf.h"
+#include "gdk-pixbuf-scale-cache.h"
 
-
 
 /* Private part of the GnomeCanvasPixbuf structure */
 typedef struct {
@@ -61,9 +61,13 @@
 
 	/* Whether the transformation or size have changed */
 	guint need_xform_update : 1;
+	
+	/* Scaling cache data */
+	GdkPixbufScaleCache *scale_cache;
+	GdkInterpType scale_type;
+	gint scale_cache_limit;
 } PixbufPrivate;
 
-
 
 /* Object argument IDs */
 enum {
@@ -78,7 +82,11 @@
 	ARG_X,
 	ARG_X_IN_PIXELS,
 	ARG_Y,
-	ARG_Y_IN_PIXELS
+	ARG_Y_IN_PIXELS,
+	/* scale caching */
+	ARG_USE_SCALE_CACHE,
+	ARG_SCALE_CACHE_LIMIT,
+	ARG_SCALE_TYPE
 };
 
 static void gnome_canvas_pixbuf_class_init (GnomeCanvasPixbufClass *class);
@@ -99,7 +107,6 @@
 
 static GnomeCanvasItemClass *parent_class;
 
-
 
 /**
  * gnome_canvas_pixbuf_get_type:
@@ -169,6 +176,16 @@
 	gtk_object_add_arg_type ("GnomeCanvasPixbuf::y_in_pixels",
 				 GTK_TYPE_BOOL, GTK_ARG_READWRITE, ARG_Y_IN_PIXELS);
 
+	gtk_object_add_arg_type ("GnomeCanvasPixbuf::use_scale_cache",
+				 GTK_TYPE_BOOL, GTK_ARG_READWRITE, ARG_USE_SCALE_CACHE);
+	
+	gtk_object_add_arg_type ("GnomeCanvasPixbuf::scale_cache_limit",
+				 GTK_TYPE_INT, GTK_ARG_READWRITE, ARG_SCALE_CACHE_LIMIT);
+	
+	gtk_object_add_arg_type ("GnomeCanvasPixbuf::scale_type",
+				 GTK_TYPE_ENUM, GTK_ARG_READWRITE, ARG_SCALE_TYPE);
+	
+	
 
 	object_class->destroy = gnome_canvas_pixbuf_destroy;
 	object_class->set_arg = gnome_canvas_pixbuf_set_arg;
@@ -194,6 +211,10 @@
 	priv->height = 0.0;
 	priv->x = 0.0;
 	priv->y = 0.0;
+
+	priv->scale_cache = NULL;
+	priv->scale_type = GDK_INTERP_NEAREST;
+	priv->scale_cache_limit = -1;
 }
 
 /* Destroy handler for the pixbuf canvas item */
@@ -216,13 +237,17 @@
 	if (priv->pixbuf)
 		gdk_pixbuf_unref (priv->pixbuf);
 
+	if (priv->scale_cache) 
+		gdk_pixbuf_scale_cache_destroy(priv->scale_cache);
+		
 	g_free (priv);
 
 	if (GTK_OBJECT_CLASS (parent_class)->destroy)
 		(* GTK_OBJECT_CLASS (parent_class)->destroy) (object);
+
+	
 }
 
-
 
 /* Set_arg handler for the pixbuf canvas item */
 static void
@@ -242,12 +267,29 @@
 	case ARG_PIXBUF:
 		pixbuf = GTK_VALUE_POINTER (*arg);
 		if (pixbuf != priv->pixbuf) {
+
+			/* deal with the scaling cache issues */
+			if(priv->scale_cache) {
+				gdk_pixbuf_scale_cache_destroy(priv->scale_cache);					
+			}
+
+
 			if (pixbuf) {
 				g_return_if_fail (pixbuf->colorspace == GDK_COLORSPACE_RGB);
 				g_return_if_fail (pixbuf->n_channels == 3 || pixbuf->n_channels == 4);
 				g_return_if_fail (pixbuf->bits_per_sample == 8);
 
 				gdk_pixbuf_ref (pixbuf);
+
+				if(priv->scale_cache) {
+					priv->scale_cache = gdk_pixbuf_scale_cache_new(pixbuf);
+					gdk_pixbuf_scale_cache_limit_set(priv->scale_cache, priv->scale_cache_limit);
+				}
+
+			} else {
+				if(priv->scale_cache) {
+					priv->scale_cache = NULL;
+				}
 			}
 
 			if (priv->pixbuf)
@@ -323,6 +365,30 @@
 		priv->need_xform_update = TRUE;
 		gnome_canvas_item_request_update (item);
 		break;
+		
+	case ARG_USE_SCALE_CACHE:
+		if(GTK_VALUE_BOOL(*arg)) {
+			if(!priv->scale_cache) {
+				priv->scale_cache = gdk_pixbuf_scale_cache_new(priv->pixbuf);
+			}
+		} else {
+			if(priv->scale_cache) {
+				gdk_pixbuf_scale_cache_destroy(priv->scale_cache);
+				priv->scale_cache = NULL;
+			}
+		}
+		break;
+
+	case ARG_SCALE_CACHE_LIMIT:
+		if(priv->scale_cache) {
+			priv->scale_cache_limit = GTK_VALUE_INT(*arg);
+			gdk_pixbuf_scale_cache_limit_set(priv->scale_cache, priv->scale_cache_limit);
+		}
+		break;
+
+	case ARG_SCALE_TYPE:
+		priv->scale_type = GTK_VALUE_ENUM(*arg);
+		break;
 
 	default:
 		break;
@@ -384,6 +450,20 @@
 		GTK_VALUE_BOOL (*arg) = priv->y_in_pixels;
 		break;
 
+	case ARG_USE_SCALE_CACHE:
+		GTK_VALUE_BOOL (*arg) = (priv->scale_cache) ? TRUE: FALSE;
+		break;
+
+	case ARG_SCALE_CACHE_LIMIT:
+
+		g_return_if_fail(priv->scale_cache != NULL);
+		GTK_VALUE_INT (*arg) = gdk_pixbuf_scale_cache_limit_get(priv->scale_cache);
+		break;
+		
+	case ARG_SCALE_TYPE:
+		GTK_VALUE_ENUM (*arg) = priv->scale_type;
+		break;
+
 	default:
 		arg->type = GTK_TYPE_INVALID;
 		break;
@@ -720,8 +800,13 @@
 {
 	GnomeCanvasPixbuf *gcp;
 	PixbufPrivate *priv;
+	GdkPixbuf *pixbuf_to_render = NULL;
+	gboolean explicit_unref = FALSE;
+	gboolean use_cached = FALSE;
 	double i2c[6], render_affine[6];
 
+
+
 	gcp = GNOME_CANVAS_PIXBUF (item);
 	priv = gcp->priv;
 
@@ -732,13 +817,59 @@
 	compute_render_affine (gcp, render_affine, i2c);
         gnome_canvas_buf_ensure_buf (buf);
 
-	if (priv->pixbuf->has_alpha)
+	/* ok so here is the deal,
+
+	   raph's scaling functions in libart_gpl do nearest neighbor scaling
+	   and rotating and things.  This isn't good enough quality for production
+	   use.  So I've decided to cancel out the scaling factors in the affine, 
+	   then do a real gdkpixbuf scale to those dimensions then only use
+	   nearest neighbor for rotations and translations, which it should be
+	   okay for. 
+	*/
+	
+	
+
+	if(fabs(render_affine[0]-1.0) > GNOME_CANVAS_EPSILON || fabs(render_affine[3]-1.0) > GNOME_CANVAS_EPSILON) {
+		/* we need to do some scaling ! */
+		const gdouble scaled_height = priv->pixbuf->height*render_affine[3];
+		const gdouble scaled_width = priv->pixbuf->width*render_affine[0];
+		
+		/* need to check that the scaled pixbuf will have a height and
+		   width greater then zero */
+		
+		
+		if(scaled_height > 1.0 && scaled_width > 1.0) {
+			if(priv->scale_cache) {
+				pixbuf_to_render = gdk_pixbuf_scale_cache_retrieve_scaled(priv->scale_cache,
+											  render_affine[0],
+											  render_affine[3],
+											  priv->scale_type);
+			} else {
+				pixbuf_to_render = gdk_pixbuf_scale_simple(priv->pixbuf,
+									   scaled_width,
+									   scaled_height,
+									   priv->scale_type);
+				explicit_unref = TRUE;
+			}
+			/* clear out the scaling affine */
+			render_affine[0] = 1.0;
+			render_affine[3] = 1.0;		
+		}
+	}
+	
+
+	if(!pixbuf_to_render) {
+		pixbuf_to_render = priv->pixbuf;
+	}
+
+
+	if (pixbuf_to_render->has_alpha)
 		art_rgb_rgba_affine (buf->buf,
 				     buf->rect.x0, buf->rect.y0, buf->rect.x1, buf->rect.y1,
 				     buf->buf_rowstride,
-				     priv->pixbuf->pixels,
-				     priv->pixbuf->width, priv->pixbuf->height,
-				     priv->pixbuf->rowstride,
+				     pixbuf_to_render->pixels,
+				     pixbuf_to_render->width, pixbuf_to_render->height,
+				     pixbuf_to_render->rowstride,
 				     render_affine,
 				     ART_FILTER_NEAREST,
 				     NULL);
@@ -746,12 +877,16 @@
 		art_rgb_affine (buf->buf,
 				buf->rect.x0, buf->rect.y0, buf->rect.x1, buf->rect.y1,
 				buf->buf_rowstride,
-				priv->pixbuf->pixels,
-				priv->pixbuf->width, priv->pixbuf->height,
-				priv->pixbuf->rowstride,
+				pixbuf_to_render->pixels,
+				pixbuf_to_render->width, pixbuf_to_render->height,
+				pixbuf_to_render->rowstride,
 				render_affine,
 				ART_FILTER_NEAREST,
 				NULL);
+
+	if(explicit_unref) {
+		gdk_pixbuf_unref(pixbuf_to_render);
+	}
 
 	buf->is_bg = FALSE;
 }
Only in ../../orig-gdk-pixbuf/gdk-pixbuf/pixops: Makefile.in
Only in ./gdk-pixbuf: sc.old.file
Only in ../../orig-gdk-pixbuf/: gdk-pixbuf.spec
Only in .: gdk_pixbufConf.sh
Only in .: gdk_pixbuf_canvas_scale.patch
Only in .: gdk_pixbuf_xlibConf.sh
Only in .: gnomecanvaspixbufConf.sh

Attachment: gdk-pixbuf-scale-cache.c
Description: gdk-pixbuf-scale-cache.c

Attachment: gdk-pixbuf-scale-cache.h
Description: gdk-pixbuf-scale-cache.h



[Date Prev][Date Next]   [Thread Prev][Thread Next]   [Thread Index] [Date Index] [Author Index]