created 03/17/2008; revised 11/07/2011


Color Images in Memory

This section discusses programs that keep an entire color image in memory as it is worked on.

31. Color Images in Memory

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:

Color Image Struct

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 );

32. Constructing an Image

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.

33. Freeing an Image

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.

34. Accessing Pixels

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.

35. 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 );
}

36. Writing and Reading Images to/from Disk Files

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.

37. Include Files and Projects

The C puzzles of the next section make use of the functions:

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.


contents — Return to the main contents page