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.