This page is about how Cairo can be better integrated in GTK+. NOTE: This page is probably mostly irrelevant by now.
In the beginning, there was GTK+, then the back end specific drawing functions got split into a separate library - GDK. Then gdk-pixbuf was built to easily handle in memory images. Life was primitive but good. Colors consisted of 256 shades of red, green and blue and an optional alpha channel and in that order. Pixels were indivisible, either you drew on a pixel or you didn't. Rectangles and points either covered the full pixel or they didn't.
Then Cairo came. Cairo turned all the basic axioms about graphics inside Gtk+ upside down. Shapes didn't have to match pixel boundaries and you could draw lines that ran in the middle or between pixels if you wanted to. No longer were you limited to 16 million colors because Cairo offered an infinite (well, floating point, to be precise) amount of red, green, blue shades.
Cairo is better suited for near-future display systems than GDK, but lacks the deep-level integration in Gtk+ that GDK has. GDK is oriented on blocks of memory which represent images and are unscalable. The higher the resolution in displays gets, the less it matters to draw pixel exact images (only the boundaries are still relevant; you don't want to overflow an image into another). With Cairo, all that stuff can be done; it should deprecate GDK in the long run as the drawing backend for Gtk+, but to get there, some things need to change.
There are around 5 tasks that: a) should be completed to make the Cairo integration as smooth as possible and b) done as additional work for final integration.
GDK graphics types needs to be fixed. Cairo deals with fractions such that a rectangle in Cairo can be (0.5,0.5)x[12.3,100.55], but GDK's rectangles are all integers, i.e. (0,0)x[15,20]. The GdkRectangle type should be fixed or deprecated and cairo_rectangle_t should take its place.
gdk-pixbuf only handles the pixel formats packed RGB and RGBA. Both of these formats are inefficient memory and performance wise.
They are memory inefficient because all data is stored in true color. For example, a black and white image contains two colors, so only one bit per pixel is really needed but GdkPixbuf uses 24 bits for each pixel non the less.
Most Xservers uses the color format ARGB so to blit a GdkPixbuf you need to convert each pixel from RGBA to ARGB which means that you are taking an unnecessary performance hit. It is even worse if you are blitting pixbufs using Cairo because then you have to convert from premultiplied to non-premultiplied alpha.
Neither format is compatible with Cairo. A new type to represent and load image data in memory is needed. All the loaders can probably be reused, but a new frontend to the library needs to be created.
Cairo doesn't have a good equivalent to GDK_INTERP_BILINEAR. It's bilinear filtering kind of... sucks. It needs to be made good.
- Cairo's software fall-backs are very slow. Those needs to be speeded up so that they match gdk-pixbuf because performance regressions suck.
Gtk+ themes need to support Cairo surfaces so that e.g. insensitivity can be rendered without workarounds (e.g. insensitivity for a TreeView cellrenderer rendering Cairo image surfaces)
Workarounds & Solutions
- Here is Python code for loading images into Cairo surfaces:
def load_surface_from_file(filename): ''' Load an arbitrary image file into a Cairo image surface. ''' pixbuf = gtk.gdk.pixbuf_new_from_file(filename) format = cairo.FORMAT_RGB24 if pixbuf.get_has_alpha(): format = cairo.FORMAT_ARGB32 width = pixbuf.get_width() height = pixbuf.get_height() image = cairo.ImageSurface(format, width, height) context = cairo.Context(image) gdkcontext = gtk.gdk.CairoContext(context) gdkcontext.set_source_pixbuf(pixbuf, 0, 0) gdkcontext.paint() return image
But the workaround is not good enough, loading images this way is many times slower than if it would have been possible to load images directly into Cairo surfaces. Performance regressions are not good.
CairoImageSurface cellrenderer (in gtkmm)
Bug 395578 – Request: gdk_pixbuf_to_cairo_surface() - Havoc thinks out loud about adding a new image loader API.
GdkPixbuf vs. Cairo, new image library needed? - Mailing list thread in which a new image library is suggested.
How to convert Cairo content to GdkPixbuf #2 - Gtkmm mailing list
Comments by MatthiasClasen
Comments on the claims in the "Problems" section:
- The GDK/cairo integration was consciously designed by not wrapping all of cairo inside GDK. On the flip side that means that it is not necessary to deprecate GDK api in favour of cairo. Just move on to using cairo where it makes sense. GDK rectangles have very little to do with cairo rectangles, apart from the fact that they both represent, well, rectangles.
- It is nonsense to say that the formats are "inefficient". You may argue that the supported formats are not the best for interoperating with cairo (not the least because pixman does not support these formats). But it only costs you a single copy to convert to a cairo-supported format. And that is the exact same cost that your proposed solution has (a frontend reusing the same loaders), since - surprise - the pixbuf loaders only support the pixbuf formats. If you want to avoid the one-time conversion cost, you need to either teach pixman about the pixbuf formats or teach the pixbuf loaders to directly load into cairo-supported formats.
- This one I agree with. Cairo needs better filtering and scaling.
- This does not make much sense. There is very little overlap between what "cairo fallbacks" do and what gdk-pixbuf does. What operations did you have in mind here ?
- Not sufficiently clear what you mean here. GTK+ rendering code creates cairo contexts on the fly all the time.
Comments by MiloszDerezynski
Matthias, in reply to your reply:
The problem is that theme engines rely on older Gtk/Gdk APIs and types. Without (unneccessarily) pasting code from gtkcellrendererpixbuf.c, fact is that you can't pass a Cairo image surface to a theme engine to render it as whatever the engine thinks should look insensitive; so in my CellRendererImageSurface, i have to resort to temporarily rendering the image as a pixbuf and rendering insensitivity using the theme engine the same way Gtk+'s stock CellRendererPixbuf does when the cell (or treeview) are insensitive.
Comments by BjornLindqvist
Replying to Mattias comments:
I agree that "just moving on to cairo where it makes sense" is good advice. Naturally, it should always make sense to move on to Cairo. For example, gtk_tooltip_set_tip_area should probably support a cairo_rectangle_t parameter instead of a GdkRectangle and GtkStyles should be defined using cairo's color format.
- I have tried to make it clearer why I think that the formats are inefficient both in time and in memory use. The time cost of converting from an RGB pixbuf to CAIRO_FORMAT_RGB24 is one extra allocation and one format conversion for each pixel. Converting from RGBA pixbufs to CAIRO_FORMAT_ARGB32 is more expensive because you have to convert from premultiplied to non-premultiplied alpha. Yes, it is only a one time cost, but a typical GNOME desktop loads thousands of images which makes the cost significant. It is true that a frontend library has the exact same problem, so the loaders have to be fixed to work with more efficient pixel formats.
- Drawing in particular. gdk_draw_pixbuf() is (thanks to Xshm) much faster than the equivalent cairo operations. The gdk_pixbuf_composite*() family is also faster than the cairo equivalents.
Thanks for your criticism.