GdkPixbuf as a Replacement for Imlib

Federico Mena Quintero

    federico@gimp.org
  

This article describes the architecture of GdkPixbuf, the image loading library that will be used in the next major version of GNOME. Older versions of GNOME, as of October 1999, used the Imlib library, which has numerous design limitations. This article also describes the differences between Imlib and GdkPixbuf so that application developers can take these considerations into account when updating their applications.


Table of Contents
Introduction
Memory Management
Image Loading and Creation
Image Transformations
Rendering
Differences Between Imlib and the GdkPixbuf Framework
References

Introduction

As of October 1999, GNOME programs and the core libraries use the Imlib library for loading and rendering images. Unfortunately, Imlib has several important design limitations that make it hard to write efficient and highly modular applications.

GdkPixbuf is a new GNOME library designed to solve part of Imlib's problems. The GdkPixbuf library provides a basic, reference counted structure called GdkPixbuf. This structure points to a block of image data, has fields that describe the format of the image data, and also contains a reference count. The library also provides a simple mechanism for loading images from files, and a more sophisticated mechanism for loading images progressively from arbitrary buffers. It also provides utility functions to transform pixbufs and render them to GDK drawables.

The GdkPixbuf framework is thus composed of the following parts. Not all of the functionality is provided by the GdkPixbuf library itself; parts are provided by the Libart and GdkRGB libraries.

ArtPixBuf. This is a structure in the Libart library that contains a buffer for image data and information about how the data is laid out in memory. Currently it support RGB images with optional transparency (alpha channel) and 8 bits of information per channel.

GdkPixbuf. This is a simple structure that wraps an ArtPixBuf and extends it with reference counting capabilities. This is necessary to write applications that need to share image data between different portions of the code, while maintaining correctness in memory management.

In addition, the GdkPixbuf library provides loaders for common image formats. Images can be loaded synchronously from a file, or progressively from user-supplied buffers. This lets simple applications load images with a single function call, and it also lets more sophisticated applications load an image progressively as they obtain more data. For example, a web browser will read chunks of data from the network and feed them progressively to a GdkPixbufLoader object. The loader will take care of parsing the image data as it gets it.

Libart transformations. The Libart library provides generalized affine transformations for image buffers, so images can be rotated, scaled, and sheared in any way.

GdkRGB. This part of the GDK library handles fast color reduction and dithering of images to render them to an X drawable.

The following sections explore these parts in detail, and they describe how to modify existing applications that use Imlib to use the new framework.


Memory Management

The most important feature of the GdkPixbuf library is that it uses a reference counting scheme for memory management. Each GdkPixbuf structure keeps a reference count. When a portion of the code needs to keep a pointer to a pixbuf structure, it should call gdk_pixbuf_ref() to add one to its reference count. When it is done using the pixbuf, it should call gdk_pixbuf_unref(). This will subtract one from the pixbuf's reference count. When the reference count of a pixbuf drops to zero, the image data is freed and the pixbuf structure is destroyed.

A GdkPixbuf structure is simply an ArtPixBuf plus an integer that holds the reference count. Thus, the most basic function to create a GdkPixbuf is gdk_pixbuf_new_from_art_pixbuf(). This wraps an ArtPixBuf with a GdkPixbuf and sets the reference count to 1.

An ArtPixBuf structure contains the following information:

The purpose of storing a pointer to a function to free the image data is to allow the user to have different allocation strategies for images. Image data that is compiled into the application as a static char array, for example, does not need to be freed. In this case, the destroy notification function can be specified as NULL so that nothing will be called to free the data. Image data that is allocated using malloc() needs to be released using free(), so the destroy notification function needs to call free() on the image data.


Image Loading and Creation

There are two basic ways to obtain an image; one is by loading it from a file or a user-supplied buffer, and another is to create an empty image from scratch.


Trivial File Loading

The GdkPixbuf library provides the gdk_pixbuf_new_from_file() function that you can use to load an image file synchronously. The application will block until the image is finished loading. This function returns a newly-created GdkPixbuf structure with a reference count of 1, or NULL if the image could not be loaded due to an invalid file name or if not enough memory was available to allocate the data buffer.

This code trivially loads an image and destroys it when it is done:
	GdkPixbuf *pixbuf;

	pixbuf = gdk_pixbuf_new_from_file ("bouncing-monkey.jpg");

	if (!pixbuf) {
		g_message ("Could not load the image!");
		exit (EXIT_FAILURE);
	}

	...
	/* Do something with the image until we are done */
	...

	/* Free the resources */
	gdk_pixbuf_unref (pixbuf);
	


Progressive Loading

Applications like web browsers cannot simply load images synchronously from a file; they must create the image gradually as they read data from an arbitrary source. An image viewer designed to read large images may want to notify the user about the loading progress while it parses a large image. These applications can all use the progressive loading features in the GdkPixbuf library.

The GdkPixbuf library provides the GdkPixbufLoader class. Applications can feed such a loader with little chunks of data, and the loader will take care of parsing the data gradually. The application can ask the loader object for the partially-filled GdkPixbuf structure so that it can display or otherwise manipulate it. In addition, GdkPixbufLoader objects emit several signals that applications can use to display partial images more efficiently.

In the following example, a file is read in chunks that are fed one by one into a pixbuf loader.
	#define BUFSIZE 1024

	GdkPixbufLoader *loader;
	FILE *file;
	guchar buf[BUFSIZE];
	int num_read;
	GdkPixbuf *pixbuf;

	file = fopen ("humongous-image.png", "r");
	if (!file) {
		g_message ("Could not open the file");
		exit (EXIT_FAILURE);
	}

	loader = gdk_pixbuf_loader_new ();

	do {
		num_read = fread (buf, 1, BUFSIZE, file);
		if (num_read < BUFSIZE)
			if (ferror (file)) {
				g_message ("Error when reading the file");
				exit (EXIT_FAILURE);
			}

		if (!gdk_pixbuf_loader_write (loader, buf, num_read)) {
			g_message ("Error while parsing the data");
			exit (EXIT_FAILURE);
		}
	} while (num_read > 0);

	fclose (file);
	gdk_pixbuf_loader_close (loader);

	pixbuf = gdk_pixbuf_loader_get_pixbuf (loader);
	if (!pixbuf) {
		g_message ("Could not create the pixbuf");
		exit (EXIT_FAILURE);
	}

	gdk_pixbuf_ref (pixbuf); /* make sure we keep a reference to it */
	gtk_object_destroy (GTK_OBJECT (loader));

	...
	/* Do something with the pixbuf */
	...

	gdk_pixbuf_unref (pixbuf);
	
In a real application the image data would be read from within a Glib input handler to keep the user interface from blocking while a large image is being read. A program that needs to display images progressively as they load may want to connect to the different signals that GdkPixbufLoader emits, so that it can get notification when areas of the image are updated.


In-Memory Data

If you have an existing RGB buffer in memory, you can simply use the gdk_pixbuf_new_from_data() function to wrap it with a GdkPixbuf. You have to provide a destroy notification function that will be called to free the image data when the pixbuf's reference count drops to zero. Applications that store static image data at compile time can specify NULL as the destroy notification function so that the data will not be freed.

As a special case, the GdkPixbuf library provides the gdk_pixbuf_new_from_xpm_data() function to create a pixbuf from inline XPM data. This makes it easy to include icon and other small images in application code.


Creating an Empty Image

As a convenience, the GdkPixbuf library provides the gdk_pixbuf_new() function to allocate an empty image buffer and wrap it with a GdkPixbuf. This is equivalent to allocating an image data buffer by hand and handing it off to gdk_pixbuf_new_from_data().

As a convenience, gdk_pixbuf_new() will compute an “ideal” rowstride so that the data can be rendered in the most efficient way possible. The rowstride of an image buffer is the number of bytes in each scan line, which means that scan lines are aligned at least on a 8-bit boundaries. Applications may prefer to align them on 32-bit boundaries for better performance — GdkRGB has special-case code to render images with this alignment faster. The gdk_pixbuf_new() function will automatically align scan lines to the values that produce the faster results.


Image Transformations

Libart has functions to apply generalized affine transformations on images. An affine transformation can describe rotation, scaling, shearing, and translation, and these can be combined in any way possible. The GdkPixbuf library provides convenience wrappers to create new pixbufs by transforming portions of existing pixbufs. It also provides wrappers to perform common operations like scaling and rotation so that the user does not have to worry about affine transformations at all.


Rendering

The GdkRGB library, which is part of GDK, can be used to render pixbuf data very quickly and with very high-quality results. GdkRGB takes care of color reduction and dithering to the highest-quality visual that the X server supports. It uses an ordered dithering algorithm so that partial portions of images can be re-rendered with stable visual results.

The GdkPixbuf library provides several convenience functions to render portions of a pixbuf to an arbitrary drawable. The most high-level function is gdk_pixbuf_render_to_drawable_alpha(), which renders a rectangular portion of a pixbuf to a given drawable. It takes care of creating the proper graphics contexts and clipping masks for images with transparency. [1]

There are lower-level functions for applications that need to do their own rendering. The gdk_pixbuf_render_to_drawable() function renders a pixbuf to a drawable and ignores alpha information. The gdk_pixbuf_render_threshold_alpha() function thresholds the opacity information of a pixbuf to produce a bi-level image (black and white). This can be used as a clipping mask to draw pixbufs “by hand”.


Differences Between Imlib and the GdkPixbuf Framework

Generally, applications that use Imlib do not have to be changed extensively to use GdkPixbuf; its simple and flexible API makes things easy. This section describes the differences between Imlib and GdkPixbuf; you should take these into account when modifying your applications to use GdkPixbuf.

Memory Management. This is the most important difference between Imlib and GdkPixbuf. Imlib has implicit reference counting that is unfortunately tied to its rendering and cache mechanisms. GdkPixbuf has a simple, explicit reference counting mechanism. The general idea is that you initially get a GdkPixbuf structure with a reference count of 1. You gdk_pixbuf_ref() it when you need to keep a pointer to it, and you gdk_pixbuf_unref() it when you are done.

Rendering. Imlib always creates pixmaps and renders images to them; there is no way to render an image to an existing drawable. GdkPixbuf only renders images to existing drawables, making its API more flexible. Also, Imlib logs the pixmaps it creates into its pixmap cache, often making it hard to do sharing of resources between different parts of an application. Since GdkPixbuf never creates drawables on its own, it is up to the application to do any form of resource management it wants.

Caching. At this point GdkPixbuf does not provide a caching mechanism for image loading or rendering. However, reference counting makes it easy to share resources between different parts of an application. Generally it is easier and more flexible to let an application determine the caching policy it wants. In the future the GdkPixbuf library may provide caching facilities for image loading and rendering.

Transformations. Imlib supports scaling and rotation of images in 90-degree increments. GdkPixbuf uses Libart's generalized affine transformations for arbitrary combinations of scaling, rotation, and shearing.


References

Notes

[1]

When the X Window System gets an alpha channel extension, this function will use it automatically.