Saturday 14 November 2009

Desktop Customisation: A Double-Edged Sword

I can safely say that I am a desktop customisation addict.

The problem is, I'm not entirely sure that it's because I actually enjoy it, or whether it's because I am completely OCD and just perpetually in pursuit of pixel-perfection.

It's fair to say that one of the things I love most about working with Linux (and the open-source community in general) is the extent to which you can customise everything to your liking. And I do mean EVERYTHING. (I think my favourite obscure thing to customise has got to be the spacing between the menu and submenus in Openbox...Awesome!) HOWEVER, if you're a little perfectionistic like yours truly, it can also be the bane of your existence, because after a while, you start to notice every pixel out of place. Or how many times have you downloaded a new GTK theme, only to find that, for instance, the Google search box on your Firefox toolbar ends up being the wrong colour for some unknown reason?

The sad fact of it is that because Linux distributions are largely written and edited by the Linux community, many of whom do their coding in their spare time in front of the telly, there is rarely the attention to detail in GUI design as there is in a commercial OS like Mac OS or Windows. I hate to admit it, but while those desktop environments only give you a few choices of looks, overall, those choices are much cleaner and more complete. Linux distros tend to be easier to customise (in fact, they usually encourage it by installing several default themes), but the overall impact is weaker because they tend to be standalone themes, rather than part of a whole desktop "look". Add to this the awkwardness of GTK vs. Qt, incompatibilities with Firefox, quirks of various panels and window managers and, well, you can get a bit of a mess. So really, desktop customisation is a bit of a double-edged sword: whilst you can tweak your GUI to your heart's content, more often than not, I find that each tweak uncovers something else needing to be tweaked, and it quickly becomes a downward spiral...

Thursday 5 November 2009

Memory usage with imlib2 for Lua

I recently started working with imlib2 bindings for Lua as part of the work I'm doing with learning the whole Cairo thing...Please see my latest post over on the Conky Blog.

However, I ran into an interesting problem when I was writing that script, not having written anything with imlib2 bindings before! As I was testing my photo album script, I found that I had a memory leak...The longer I left my script to run, the more and more my memory usage would ramp up until it topped out and I found I couldn't display any more photos. Eep! Not really what you want from a photo album.

So I thought I'd share with you a little bit about how imlib2 draws pictures, as opposed to the way Cairo does it, because having had a bit of experience with Cairo actually led me to think about image drawing in a way that made it difficult to work with imlib2, because the two are so different.

First of all, why use imlib2 at all when you can, to some extent, use Cairo to draw pictures? Well, the reason I turned my hand to it was because I wanted to be able to render .jpg images in my Conky, and the Cairo bindings we use can only interpret .png at the moment, through cairo_image_surface_create_from_png(...). By contrast, if you load an image using imlib2, it auto-detects the type and scans its own internal libraries to find a suitable decoder. So imlib2 has a much broader range of image type support, with no fiddling.

If you want to manipulate existing images in imlib2, first you need to load the image into memory with imlib_load_image(...). Eventually, if you want to then draw that image onto the Conky window, you'll use the imlib_render_image_on_drawable(...) function. However, if you want to manipulate that image first, you will need to do all your manipulation on a "buffer" image before you render it onto the surface.

Now, I'm not very good (yet!) at working with transformations in Cairo, so I'm used to thinking of things like a plotter...Move to (a, b), draw a line to (x, y) and stroke. So when I want to draw an image to a window, what I really want to do is load an image, select the rectangle to draw it into, and then "fill" it in...But that's not really the way to do it with imlib2. My example will be my photo album script...Essentially, I wanted to load an image and draw it onto the Conky window at a set (smaller) size. However, I found that if I just load the image and then draw the image at a smaller size using the following:
imlib_blend_image_onto_image(image, 0, 0, 0, w_img, h_img, 0, 0, width, height)
imlib_render_image_on_drawable(xc - width/2, yc - height/2)
I wound up with a horrific memory leak...Basically, I was loading the images into memory and LEAVING THEM THERE. AT FULL SIZE. Ouch.

(For those of you who are wondering, the lines above shrink the image from w_img x h_img down to a specified width x height, then draw it on the Conky window at the coordinates in the second line...)

What you need to do to avoid the memory leak is to very carefully manage your memory usage by freeing your images after you're done with them. The good way, then, to draw my reduced images onto the Conky window is to use the following method:
image = imlib_load_image(album_dir .. filename)
if image == nil then return end
imlib_context_set_image(image)

w_img, h_img = imlib_image_get_width(), imlib_image_get_height()
buffer = imlib_create_image(width, height)
imlib_context_set_image(buffer)

imlib_blend_image_onto_image(image, 0, 0, 0, w_img, h_img, 0, 0, width, height)
imlib_context_set_image(image)
imlib_free_image()

imlib_context_set_image(buffer)
imlib_render_image_on_drawable(xc - width/2, yc - height/2)
imlib_free_image()
This code does the following:
  1. loads the given image into memory, in a variable called "image"
  2. checks to see if the image has loaded successfully; if not, breaks out of the function
  3. sets the imlib2 context to be "image"
  4. grabs the dimensions of the loaded image
  5. creates a "buffer" image
  6. sets the context to be "buffer", so we can blend onto it
  7. draws a scaled-down version of the image onto "buffer"
  8. sets the context back to "image", so we can release it
  9. releases the original loaded image, so we are then just left with the scaled down version, still held in memory
  10. sets the context back to "buffer", so we can draw what's on it onto the drawable window
  11. draws whatever's in "buffer" onto the Conky window
  12. releases the buffer image
You can start to see from this how you can also use the buffer image (or a series of them!) to do some fairly sophisticated image & text manipulation before rendering onto the drawable window.

For more information on the functions available to you in imlib2, check out their documentation.

In the meantime, here, have a screenie :)
From Screenshots
You can also grab the script that made it, from the Conky Blog, or my DevArt account.