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

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

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)
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.

1 comment:

  1. Have try out photo_album widget and other widgets together? I added photo_album widget on conky_widgets.lua script and set all the options, but I had error message regarding "album_dir" and all the other widgets not display either. If you know anything about this, Would you post a solution?
    Thank you so much.