« August 2007 | Main | November 2007 »

October 2007 Archives

October 6, 2007

Reading and Writing Image Files with imlib

I thought it had been a bit too long since we'd had an entry on codelore about code. So, here it is. A while ago, I was trying to write some code to do some image processing. The first step, to read in an image file, proved to be immensely, horribly, painfully difficult. There's a bunch of libraries around, but the problem of "please give me a 2D array of pixels" is somehow not a use-case they anticipated.

I don't quite know how this dire situation came about, and I'm not that interested (though I expect one could learn a lot from API design by doing that research). Instead I just wanted to share my solution to this problem in case someone else wants to be able to read and write image files.

The solution that I found is a library called imlib (sources here). Now, it's not perfect; notably it's thread-unsafe by really quite extensive design (why?!), but it does have a simple way to load images of pretty much any interesting type, and it has methods to get pixels and draw lines (which I will use as a poor but workable substitute for having a pixel setting method). I won't talk too much more about it, I really just wanted this to get out there so that the next person to search for "reading and writing images 2d array" will not be as disappointed as I was. Note also that the industry-standard ImageMagick comes pretty close to this level of simplicity, and if you're working in C++, the Magick++ interface actually looks pretty nifty. What follows is the simplest thing I could get to work in C.

/**
 * Image loading and saving example code. This code is free for any purpose;
 * knock yourself out. No warranties expressed or implied. Build this example
 * program with a Makefile like this:
 * 
 *  img: main.c
 *      gcc `imlib2-config --cflags` `imlib2-config --libs` -o img main.c
 * 
 * Author: James Gregory <codelore@james.id.au>
 */

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <libgen.h>

#include <Imlib2.h>

#define pixelVal double

struct matrix
{
int width, height;
pixelVal **vals;
};

struct image
{
int width, height;

struct matrix *red;
struct matrix *green;
struct matrix *blue;
};

struct matrix *newMatrix(int width, int height)
{
int i;
struct matrix *mat = (struct matrix *)calloc(1, sizeof(struct matrix));

mat->vals = (pixelVal **)calloc(width, sizeof(pixelVal *));
mat->width = width;
mat->height = height;

for(i = 0; i < width; i++)
{
mat->vals[i] = (pixelVal*)calloc(height, sizeof(pixelVal));
}
return mat;
}

struct image *loadImage(const char *filename)
{
Imlib_Image img;
int i, j, width, height;

struct image *result = (struct image*)calloc(1, sizeof(struct image));

img = imlib_load_image(filename);
imlib_context_set_image(img);

width = imlib_image_get_width();
height = imlib_image_get_height();

result->width = width;
result->height = height;

result->red = newMatrix(width, height);
result->green = newMatrix(width, height);
result->blue = newMatrix(width, height);

for(i = 0; i < width; i++)
{
for(j = 0; j < height; j++)
{
Imlib_Color col;
imlib_image_query_pixel(i, j, &col);

result->red->vals[i][j] = (pixelVal)col.red;
result->green->vals[i][j] = (pixelVal)col.green;
result->blue->vals[i][j] = (pixelVal)col.blue;
}
}

return result;
}

void saveImage(struct image *img, const char *filename)
{
Imlib_Image out;
int i, j;

out = imlib_create_image(img->width, img->height);
imlib_context_set_image(out);

for(i = 0; i < img->width; i++)
{
for(j = 0; j < img->height; j++)
{
int red = (int)img->red->vals[i][j];
int green = (int)img->green->vals[i][j];
int blue = (int)img->blue->vals[i][j];

red = red <= 255 ? red : 255;
green = green <= 255 ? green : 255;
blue = blue <= 255 ? blue : 255;

imlib_context_set_color(red, green, blue, 255);
imlib_image_draw_line(i, j, i, j, 0);
}
}

imlib_save_image(filename);
}

int main(int argc, char *argv[])
{
struct image *img;

if(argc != 3)
{
fprintf(stderr, "Usage: %s <input file> <output file>\n", basename(argv[0]));
exit(-1);
}

img = loadImage(argv[1]);
saveImage(img, argv[2]);
}


(apologies for the awful code listing. Anyone with killer ideas for posting code on Movable Type blogs, let me know)

That should build a little program to convert an image file between image formats extremely slowly; something every programmer should have in their arsenal.

Now, some of you might be asking why I chose to split the red, green and blue channels into separate matrices. An excellent question. The reason is that the particular application I was working on had the luxury of being able to treat each channel separately, so from a code clarity perspective it made sense: I wrote my function to process one channel and then a wrapper to call it three times, but perhaps more interestingly it also gave me a speed advantage: it meant that I was able to fit a much larger portion of the working set into L2 cache. Unfortunately in the example above where you read and then immediately write the file you get almost the worst possible memory access pattern. If you are in the business of writing programs to do that, let me give you some advice: Stop. It's already been done.

Anyway, I hope that proves useful to someone. Feel free to use it however you wish.

October 15, 2007

E: Dynamic MMap ran out of room

The solution to this one always seems to be harder to find than I remember, so I'm writing it down here. The problem is that you add some sources to apt, and then you do an update, and apt helpfully reports

E: Dynamic MMap ran out of room

As far as I can tell, it's complaining because it has tried to use my favourite syscall (no, really, it's awesome), mmap to load its dependency database into memory lazily, and a sanity limit has been reached.

The solution, on Ubuntu, is to put the following into a file called /etc/apt/apt.conf.d/00local (previously I've used /etc/apt/preferences; Ubuntu seems to adopt a more structured configuration system):

APT::Cache-Limit 50000000;

And generally you want that number to be something smaller than the amount of memory you have available on the system, though I suspect in practice it rarely matters (the kernel will over-commit on memory, but it's only a problem if your request touches more memory than you have; single package installation should thus be fine, dist-upgrades probably won't be. Note: haven't tested that theory).

This, incidentally, is an example of a bad error message. It's not appalling; the message does uniquely identify a particular fault, and as such you can find what you need with a minimum of googling. But it could be a lot better: in this case the application could have known what limit to use, and could have informed you that it's doing this for your own safety (the limit will be in place to prevent swapping), and could have even told you how to disable this behaviour (as above). More puzzling to me is why it doesn't do this dynamically based on the amount of physical memory available; it could even adopt a different algorithm when it detects low-memory conditions.

But anyway, there's the solution. I hope that you never have to go looking for it, but there it is nonetheless.

About October 2007

This page contains all entries posted to Code Lore in October 2007. They are listed from oldest to newest.

August 2007 is the previous archive.

November 2007 is the next archive.

Many more can be found on the main index page or by looking through the archives.

Powered by
Movable Type 3.35