created 03/17/2008; revised 11/07/2011
This section discusses programs that keep an entire color image in memory as it is worked on.
So far, our color images have been created by writing one byte at a time to a disk file in raster order. Although this works well for some images, in general it is more convenient to have the entire image in memory and to process it in whatever order arises naturally. In a PPM image, each pixel consists of three bytes: one for red, one for green, and one for blue. It is convenient to bundle these three values into a struct:
typedef struct { unsigned char red, grn, blu; } pixel;
The size of this struct is logically three bytes, however, depending on the compiler, a "slack byte" might be appended so that the size is a fullword, four bytes. Don't hard-code any assumption about the size of this struct. (On Dev-cpp it is indeed three bytes long.)
At least conceptually, an image is a 2D array of pixels:
pixel image[nrows][ncols]; /* conceptual image */
However, as with graylevel images, this turns out to be awkward.
In classic ANSI C, the number of columns of
a 2D array must be hard coded in most situations.
Current ANSI C relaxes this requirement, but,
for maximum compatibility, these notes follow the older standard.
In memory, the data for an image is a 1D array of
pixel
structs.
The image is represented in a colorImage
struct.
typedef struct { int nrows; /* Number of rows in the image */ int ncols; /* Number of columns in the image */ pixel *pixels; /* Pointer to a 1D array of pixels */ } colorImage;
The field pixels
points to a one dimensional
array of pixel structs.
This pixel data is allocated dynamically.
For PPM images, pixels are in the range 0..255, so one unsigned byte
will hold the data for one pixel. An image struct
and its pixel
data look like this:
Here the image has 2 rows and 8 columns. The field pixels
points
to a block of 16*3 contiguous bytes in memory that store the pixel data in raster order.
Each pixel consists of three bytes, red, green, and blue, in that order.
(Row and column indexes start at zero.)
Image processing functions take such a struct as a parameter, or, if they change a member of the struct, they take a pointer to the struct:
void writePPMimage ( colorImage img, char *filename ); void readPPMimage ( colorImage *img, char *filename );
The colorImage
struct works for images of any size. This
means that nrows*ncols*sizeof(pixel)
number of bytes must
be allocated for pixels
.
Here is the function:
pixel *newColorImage( colorImage *img, int nrows, int ncols ) { img->nrows = nrows; img->ncols = ncols; /* allocate memory for the pixels. No initialization is done */ img->pixels = (pixel *)malloc( nrows * ncols * sizeof( pixel) ); return img->pixels; }
newColorImage()
uses a pointer to a colorImage
struct.
The pointer is needed so
the fields of the struct can be modified.
nrows
and
ncols
are filled in, then memory is allocated for the pixel data
and the
address of the first of these bytes is placed in the field pixels
.
If memory allocation fails, the function returns NULL
.
Notice that the pixels are not initialized and, in general,
contain garbage.
Here is
a program that constructs a 300 rows by 400 columns image struct:
int main ( int argc, char* argv[] ) { colorImage img ; if ( newColorImage( &x, 300, 400 ) == NULL ) { printf(">>error<< can't allocate memory\n"); return; } system( "pause" ); free( img.pixels ); }
The program constructs the image, pauses, then frees the memory and exits.
The memory that was dynamically allocated should be returned to the system
by calling free()
. It would be nice to have a function that matches
newColorImage()
to do this:
void freeColorImage( colorImage *img ) { img->nrows = 0; img->ncols = 0; free( img->pixels ); img->pixels = NULL; }
Although it is possible for free()
to fail, it happens so rarely
(and there is nothing we can do when it does fail) that this function ignores
that possibility.
Conceptually, an image is a 2D grid of pixels.
However, as with gray level images, this is implemented as a 1D array of pixels.
Consider a pixel at row 1 column 5 of an image with 8 pixels per row.
For a pixel in row 1, all the pixels
of row 0 precede it.
It is at column 5 in its row.
So its location in the 1D array is
pixels[ncols*1 + 5]
which is pixels[13]
. (Remember
that rows and columns are numbered starting at 0).
Reminder: With "address arithmetic" in C,
+1 means to add to the address the number of bytes in an item.
Here, the items are our pixel
struct, of size sizeof( struct pixel )
.
So pixels[ncols*1 + 5]
means to add
(ncols*1 + 5)*sizeof( struct pixel )
to the address in the member pixels
.
Rule: Say that an image has ncols
number of columns.
Then a pixel that is conceptually at row r
column c
will be at pixel number ncols*r + c
,
where pixels are numbered starting
at zero and each pixel is (probably) three bytes long.
setColorPixel()
and getColorPixel()
Here are two functions that encapsulate the calculations that access pixel data of an image:
void setColorPixel( colorImage img, int row, int col, pixel val ) { img.pixels[ img.ncols*row + col ] = val; } pixel getColorPixel( colorImage img, int row, int col ) { return img.pixels[ img.ncols*row + col ] ; }
Here is an example program that tests if the functions work, but does little else:
#include "basicColorImage.c" int main ( int argc, char* argv[] ) { colorImage img ; pixel pix = {255, 0, 0}; if ( newColorImage( &img, 2, 3 ) == NULL ) { printf(">>error<< can't allocate memory\n"); return; } setColorPixel( img, 0, 0, pix ); /* row 0, col 0 */ setColorPixel( img, 0, 1, pix ); /* row 0, col 1 */ setColorPixel( img, 0, 2, pix ); /* row 0, col 2 */ pix.red = 0; pix.grn = 255; setColorPixel( img, 1, 0, pix ); /* row 1, col 0 */ setColorPixel( img, 1, 1, pix ); /* row 1, col 1 */ setColorPixel( img, 1, 2, pix ); /* row 1, col 2 */ pix = getColorPixel( img, 0, 0 ); printf("row 0, col 0: %d %d %d\n", pix.red, pix.grn, pix.blu ); pix = getColorPixel( img, 0, 2 ); printf("row 0, col 2: %d %d %d\n", pix.red, pix.grn, pix.blu ); pix = getColorPixel( img, 1, 2 ); printf("row 1, col 2: %d %d %d\n", pix.red, pix.grn, pix.blu ); system( "pause" ); /* Delete this if desired */ freeImage( &img ); }
In main memory, color images are held in structures of type colorImage
. On
disk, color images are held in files that follow the PPM format.
The following function
takes an image in main memory and writes it to a disk file.
The parameters are a colorImage
structure
(already filled in with values) and a file name.
It creates a disk file with the requested name and writes the data from the structure
to it, following the PPM format.
It then closes the file.
void writePPMimage( colorImage img, char *filename ) { int row, col; FILE *file; pixel pix; /* open the image file for writing */ if ( (file = fopen( filename, "wb") ) == NULL ) { printf("file %s could not be created\n", filename ); exit( EXIT_FAILURE ); } /* write out the PPM Header information */ fprintf( file, "P6\n"); fprintf( file, "# Created by writePPMImage\n"); fprintf( file, "%d %d %d\n", img.ncols, img.nrows, 255 ); /* write the pixel data */ /* sizeof( pixel ) might not be three, */ /* since some compilers may include a slack byte, */ /* so do this pixel by pixel */ for ( row=0; row<img.nrows; row++ ) for ( col=0; col<img.ncols; col++ ) { pix = img.pixels[ img.ncols*row + col ]; fputc( pix.red, file ); fputc( pix.grn, file ); fputc( pix.blu, file ); } /* close the file */ fclose( file ); }
The pixel data must be written out byte-by-byte because
of a possible slack byte in the pixel
structure.
You might want to add a few statements that test for this
possibility and use fwrite()
to write out
the entire block of memory if there are no slack bytes
to worry about.
Another function reads in PPM images from disk. Its prototype is:
void readPPMimage( colorImage *img, char *filename );
The parameters to this function are a pointer
to a colorImage
struct,
and a pointer to the file name.
The header to the PPM file is read and the right amount of
memory is allocated for the pixels.
The number of rows nrows
and number of columns
ncols
of the struct are filled in.
Then all the pixel data is read in.
Now getColorPixel()
and
setColorPixel()
and be used.
The C puzzles of the next section make use of the functions:
pixel *newColorImage( colorImage *img, int nrows, int ncols );
void freeColorImage( colorImage *img );
void setColorPixel ( colorImage img, int row, int col, pixel val );
pixel getColorPixel ( colorImage img, int row, int col );
void writePPMimage ( colorImage img, char *filename );
void readPPMimage ( colorImage *img, char *filename );
The answers to the puzzles #include a file
basicColorImage.c
which contains the above functions and the definitions
of the pixel
struct and the colorImage
struct.
This is done so that the puzzles are independent
of environment.
To get the file basicColorImage.c
click here: Basic Color Image
Functions.
Use a programming editor to copy the code from the web page to a C source file
called basicColorImage.c
in the same directory as your own source code.
One way to use this file is to start your programs with
#include "basicColorImage.c"
and compile as usual.
An even better way is to create a project.
In Dev-C++ and other development environments a project,
bundles of all the source files and other resources
needed to create an application.
Break the file basicColorImage.c
into a header file and
a source file and make them part of your project.
#include the header file at the top of each source file.