As I was working on the design of a new challenge for this website, I was looking for some cookbook on low-level image processing in C and couldn't find what I was looking for. So I decided to write this article, which sums up my findings on the subject.
I'll illustrate it with a portion of C code that reads in an image from a file, performs a 90 degree rotation on it, and writes the rotated image out to another file.
First of all, it looks like the 24-bit uncompressed BMP file format is one of the simplest image formats around, although not the more efficient. After an identification block and a short header containing some meta-data about the image (number of rows, number of columns, etc.), pixels in the image are stored sequentially in RGB format.
Although the BMP format is the native bitmap format of Windows, it is free of patents and thus widely used in image processing programs from many other operating systems.
Identification Block & Header
The format of the identification block looks like this:
(bytes) (field)
0 - 1 : Magical number used to identify the file type: 0x424D i.e. 'BM' for BitMap
2 - 5 : The size of the file (in bytes)
6 - 7 : Reserved (zero)
8 - 9 : Reserved (zero)
10 - 13 : Offset (starting address of the byte where the bitmap data can be found): 54 in our case
In the commonly used BMP Version 3 format, the format of the header looks like this:
(bytes) (field)
14 - 17 : The size of the header (40 bytes!)
18 - 21 : The width of the image (in pixels)
22 - 25 : The height of the image (in pixels)
26 - 27 : The number of color planes (1)
28 - 29 : The color depth of the image (number of bits per pixel): 24 in our case
30 - 33 : The compression method: 0 in our case (no compression)
34 - 37 : The size of the raw image data following the header (in bytes)
38 - 41 : The horizontal resolution (in pixels per meter)
42 - 45 : The vertical resolution (in pixels per meter)
46 - 49 : The number of colors used in the image (0 for all)
50 - 53 : The number of important colors used in the image (0 for all)
In the example below, we'll read in and write out these fields one at a time.
This is not necessary, of course, as we will be only interested in the width & height of the image.
As the BMP format originated on Intel-based machines, multibyte fields are stored in little-endian order. This is fine if (like me) you are using an x86 processor; however, you will need to adapt the code below if you want to run it on a big-endian architecture like the PowerPC!
Image Data
Now let's have a look at the encoding of the pixels themselves:
Pixels are stored from left to right starting from the bottom row of the picture, and ending with the top row.
In the 24-bit BMP format, the color of each pixel is represented by 3 bytes (remember there are 8 bits in 1 byte): 1 byte for Blue, 1 byte for Green, 1 byte for Red, in that order. So when I wrote that pixels were stored in RGB format, I would rather have written "BGR" !
Also, I should mention that for some unclear reason, each row of the image is padded to be a multiple of 4 bytes.
Let's look at the example below. We'll read from a file called "in.bmp" and write to a file called "out.bmp". Pixels will be stored in a multidimensional array:
/*
* Read in an image from file "in.bmp"
* Perform a 90 degree rotation on it
* Write the rotated image out to file "out.bmp".
*
* Author: Christophe
*/
#include <stdio.h>
#include <stdlib.h>
typedef unsigned short WORD;
typedef unsigned long DWORD;
/* Identification Block: */
WORD FileType;
DWORD FileSize;
WORD Reserved1;
WORD Reserved2;
DWORD OffBits;
/* Header: */
DWORD Size;
DWORD Width;
DWORD Height;
WORD Planes;
WORD BitCount;
DWORD Compression;
DWORD ImageSize;
DWORD XPelsPerMeter;
DWORD YPelsPerMeter;
DWORD ClrUsed;
DWORD ClrImportant;
typedef struct _RGB { unsigned char R,G,B; } RGB;
RGB **pixel;
int main(int argc, char **argv) {
FILE *f_in, *f_out;
char zero=0;
int i,j;
/* Open input and output files: */
if ( (f_in=(FILE *)fopen("in.bmp","r"))==NULL ) { perror("in.bmp"); exit(1); }
if ( (f_out=(FILE *)fopen("out.bmp","w"))==NULL ) { perror("out.bmp"); exit(1); }
/* Read & Write Identification Block: */
fread(&FileType,2,1,f_in); fwrite(&FileType,2,1,f_out);
if ( FileType != 0x4d42 ) { fprintf(stderr,"Not a BITMAP file!\n"); exit(1); }
fread(&FileSize,4,1,f_in); fwrite(&FileSize,4,1,f_out);
fread(&Reserved1,2,1,f_in); fwrite(&Reserved1,2,1,f_out);
fread(&Reserved2,2,1,f_in); fwrite(&Reserved2,2,1,f_out);
fread(&OffBits,4,1,f_in); fwrite(&OffBits,4,1,f_out);
/* Read & Write Header: */
fread(&Size,4,1,f_in); fwrite(&Size,4,1,f_out);
fread(&Width,4,1,f_in);
fread(&Height,4,1,f_in); /* Here we swap width and height for the rotation ! */
fwrite(&Height,4,1,f_out);
fwrite(&Width,4,1,f_out);
fread(&Planes,2,1,f_in); fwrite(&Planes,2,1,f_out);
fread(&BitCount,2,1,f_in); fwrite(&BitCount,2,1,f_out);
fread(&Compression,4,1,f_in); fwrite(&Compression,4,1,f_out);
fread(&ImageSize,4,1,f_in); fwrite(&ImageSize,4,1,f_out);
fread(&XPelsPerMeter,4,1,f_in); fwrite(&XPelsPerMeter,4,1,f_out);
fread(&YPelsPerMeter,4,1,f_in); fwrite(&YPelsPerMeter,4,1,f_out);
fread(&ClrUsed,4,1,f_in); fwrite(&ClrUsed,4,1,f_out);
fread(&ClrImportant,4,1,f_in); fwrite(&ClrImportant,4,1,f_out);
/* Allocate memory to store the Bitmap Data: */
pixel = (RGB **)malloc(Height*sizeof(RGB *))
if (pixel == NULL) { fprintf(stderr,"Not enough memory!\n"); exit(1); }
for(i=0; i<Height; i++) {
pixel[i] = (RGB *)malloc(Width*sizeof(RGB))
if (pixel[i] == NULL) { fprintf(stderr,"Not enough memory!\n"); exit(1); }
}
/* Read Bitmap Data: */
for(i=0; i<Height; i++) {
for(j=0; j<Width; j++) {
pixel[i][j].B = fgetc(f_in);
pixel[i][j].G = fgetc(f_in);
pixel[i][j].R = fgetc(f_in);
}
for(j*=3; j%4!=0; j++) fgetc(f_in); // Padding row to 4-byte multiple!
}
/* The pixels are now stored in a multidimentional array. */
/* To perform the rotation, we'll just write them out in a different order... */
/* Write Bitmap data: */
for(j=Width-1; j>=0; j--) {
for(i=0; i<Height; i++) {
fwrite(&pixel[i][j].B,1,1,f_out);
fwrite(&pixel[i][j].G,1,1,f_out);
fwrite(&pixel[i][j].R,1,1,f_out);
}
for(i*=3; i%4!=0; i++) fwrite(&zero,1,1,f_out); // Padding row to 4-byte multiple!
}
/* Free memory used to store the Bitmap Data: */
for(i=0; i<Height; i++) free(pixel[i]);
free(pixel);
/* Close files: */
fclose(f_in); fclose(f_out);
}
Of course, there are variants in the format that are not covered here.
Versions 4 and 5 of the BMP format have more fields in the header.
Also, things get a bit more complicated with lower color depths as colors get coded using a palette.
However, this article should give you a nice start for some basic image manipulation.
References:
Wikipedia: BMP file format
FileFormat.info: Microsoft Windows Bitmap File Format Summary
|