Last week I wrote the image structures, with 3 different structures
(image_u8, image_u16, image_f32) for 3 different basic types
(8bit integer, 16bit integer, 32bit floats). As I wanted a common
interface to these structures, I wrote a king of "meta-structure",
using C unions to make some kind of subclass design.
I still think it was a good idea, and it worked, but.... why not make it simple. KISS, Keep It Simple (Stupid!). I guess this pseudo-class design was a consequence of the previous codebase, and I used code tricks instead of imagination.
So, after a midnight revelation (yes, around 03:00, in bed...) and a few days rewriting (again...), the design is now simple, very simple:
new image design
The image definition is:
/**
* generic image structure
*/
typedef struct s_image
{
/* TODO : add bit depth and channels */
mw_dtype dtype; /**< datatype of the stored values */
uint16 ncol; /**< number of columns */
uint16 nrow; /**< number of rows */
void * data; /**< data array */
} mw_image;
Yes, that's it. An image is:
- a data type (an integer, in fact, but users should only use
MW_DT_XXmacros) - a number of rows
- a number or columns
- a data array
Later, we may introduce some other structure fields, if they are needed my many algorithms; but the best way could be to add a simple generic pointer, and let everyone use it as they like (or even register some alloc/dealloc/copy routines).
For color images, the idea is to add a uint8 nchn field for the
number of channels; then, we have the following correspondances:
- 1 channel : grayscale image
- 2 channels : grayscale+alpha image
- 3 channels : RGB image
- 4 channels : RGB+alpha image
But it still is possible to define a 255 channels images, if it makes sense (for the algo and for the memory space).
user interface
This generic data array can be used in different ways:
- high-level, generic
With the macroXY(img, x, y), you access the same point, but through less function calls, so it's faster. As it's in fact three nested( ? : )operations (to adapt the behaviour to the type), you can't use it as alvalue, ie to update a value. - high-level, specific
Forlvalues, or when you know for sure what's the dtype of your image,U8_XY(img, x, y),U16_XY(img, x, y),F32_XY(img, x, y)will work, but still at the cost of two indirections, one sum and one product. - high-level
Withmw_image_getdot(img, x, y)andmw_image_setdot(img, x, y, value), you can access and update the point(x, y)in theimgimage. The functions take care of type casting and array position. This is easy, simple, but won't be very fast. - lower-level
In loops, when speed matters, you can still use a classic array
syntax. But you will need to know the type used in the data array,
and use type casting because it's not possible to use
voidpointers directly. It will have to look like((uint8 *) image->data)[y * ncol + x], but you can also define firstdata = (uint8 *) image->data, then writedata[y * ncol + x]. This also means that you wil need aswitch ()statement, to select between the possible types. - lowest-level
As usual, in C, lowers-level means pointers. Here also, if you cast
image->datato the correct pointer type, everything is fine.
Some profiling is planned, to provide comparative measures of the performance of these levels.
code architecture
This change improves the code architecture, function calls, and dependencies. Now it's possible to implement generic image-processing algorithms without the hassle of dealing with subtypes, unions, and so on. The image file (TXT, PNG, tIFF...) read/write routines are also simplified:

fast loops
By the way, the images are stored by rows, X-style ((0, 0) is the
upper-left corner). So, the efficient way to pass through all the
pixels is:
uint8 * data;
uint16 nrow, ncol;
data = (uint8 *) image->data;
nrow = data->nrow;
ncol = data->ncol;
for (y = 0; y < nrow; y++)
for (x = 0; x < ncol; x++)
do_stuff_with(data[y * ncol + x]);
This ensures a better memory localisation, less CPU cache miss, ... a faster code.
If performance is a matter, pointer arithmetics will save one sum, one product, some tests and some registers at each loop, but the code will be a bit obfuscated:
uint8 * ptr, * stop;
ptr = (uint8 *) image->data;
stop = ptr + image->ncol * image->nrow;
while (ptr < stop)
do_stuff_with((* ptr++));