Graphics Programming using Allegro

Processing Pixels in Images of Unknown Size

Published:  September 7, 2008
by Richard G. Baldwin

File:  Allegro00150


Preface

General

This lesson is part of a series (see Resources) designed to teach you how to use Allegro to do graphics programming in C++.  My purpose in writing the series is to provide lecture and lab material for a course titled Game Development Using C++ that I teach at Austin Community College in Austin, Texas.  However, if you have stumbled upon this series and you find it useful, you are welcome to study it.

Viewing tip

I recommend that you open another copy of this document in a separate browser window and use the following links to easily find and view the figures and listings while you are reading about them.

Figures

Listings

Supplemental material

I recommend that you also study the other lessons in my extensive collection of online programming tutorials.  You will find a consolidated index at www.DickBaldwin.com.

Preview

In this lesson, you will learn:

Figure 1. Screen output for program named ImageNegate03.

Discussion and sample code

This program is similar to the program named ImageNegate01 from an earlier lesson (see Resources).  However, that program required the programmer to know the size of the image that was being processed.  This program assumes that the programmer does not know the width and height of the image.  Instead, those values are discovered by the program after the image is loaded into memory.

Purpose

The purpose of this program is to show you:

An image file of unknown size

A PCX image file of an unknown size is loaded into memory and displayed in the top half of a graphics window as shown in Figure 1.  A negated version of the image is displayed in the bottom half of the window.

Screen output

The width and height of the image and the width and height of the window respectively are displayed in the upper-left corner of the window.

I have determined experimentally that the width of the window MUST be a multiple of 4.  I have also determined experimentally that the width must be at least 108 pixels.

Minimum allowable width

This second requirement seems to be a characteristic of Windows Vista (or Windows in general) because it is not possible to manually resize a window to make it narrower than approximately 108 pixels.  Also, if you write a Java program to display a frame with a width that is less than about 108 pixels, the frame that is actually displayed is about 108 pixels wide.  Note, however that the program doesn't hang up.

Program hangs up

If the two conditions listed above aren't met with Allegro and Dev-C++, the program hangs up on my HP laptop running Windows Vista Home Premium edition.  I must log out and then log back in order to continue programming in the same project.

Additional details

In view of the possibility that others may be experiencing the same problems, I will provide additional details.  When you successfully compile an Allegro project using Dev-C++, an exe file is created.  If you modify the program code and successfully recompile the program, the old exe file is replaced with a new one.

What actually happens when I experience these problems is that under certain runtime failures (not limited to the two described above) Vista refuses to allow the exe file to be deleted or modified until either the system is rebooted or the current user logs out and then logs back in.  Since it is impossible to replace the exe file with a new one, it is not possible to modify and recompile the program and an "access denied" message occurs when an attempt is made to recompile the program.

A crude workaround

It is very inconvenient to have to log out and log back in during the debugging of a program when certain runtime failures occur.  What I have learned to do when that happens is to:

After awhile, I log out, log back in, and delete all of the old copies of the project.

Another restriction

I have also determined that for PCX or BMP files with a color depth of 16, 24, or 32, you MUST set the video mode and create the onscreen window before you call load_bitmap to load the image file.  Otherwise, the colors may not be correct.  This is also confirmed by the documentation, which states:

"If the file contains a truecolor image, you must set the video mode or call set_color_conversion() before loading it."

Getting the size of the image

As you will see later, in this program, I call the get_clip_rect function to determine the size of the bitmap image.  The requirement described above makes it difficult to set the size of the window based solely on the dimensions of the bitmap's clip rectangle because the clip rectangle can't be obtained until after the image file is loaded.

One solution to the problem

There may be a better solution to this problem than the one that I used in this program, but if so, I haven't discovered it yet.  (For example, there may be a solution involving the use of the set_color_conversion function.)

In any event, while my solution is not elegant, it works.  My solution in this program is to perform the following sequence of operations:

At that point, I have the information that I need to:

Beginning of the program

As is my custom, I will explain the program code in fragments.  A complete listing of the program is provided in Listing 7 near the end of the lesson.

The program begins in Listing 1.

Listing 1. Beginning of the program named ImageNegate03.
#include <allegro.h>
#include <stdio.h>//required to call sprintf

int main(){
  int pixel = 0;//temporary storage for a pixel

  //Temporary storage for red, green, and blue
  int red = 0;
  int green = 0;
  int blue = 0;
  
  int width = 0;//image width
  int height = 0;//image height
  int windowWidth = 0;//must be a multiple of 4 && >= 108
  int windowHeight = 0;
  
  //The addresses of the following variables are passed
  // in the call to get_clip_rect, where they are
  // populated with the coordinates of the diagonal
  // corners of the rectangular image.
  int upperLeftX = 0;
  int upperLeftY = 0;
  int lowerRightX = 0;
  int lowerRightY = 0;

  //Typical Allegro setup.
  allegro_init();
  install_keyboard();
  set_color_depth(32);

  //Declare a pointer variable capable of pointing to a
  // bitmap.
  BITMAP *picA = NULL;
  //Load an image file from the current directory.
  picA = load_bitmap("starfish.pcx", NULL);

If there is anything in Listing 1 that I haven't explained before, the comments should be self-explanatory.

Get the clip rectangle for the image

Listing 2 calls the function named get_clip_rect to determine the coordinates of the diagonal corners of the current clip rectangle for the image.

Listing 2. Get the clip rectangle for the image.
  //Get the current clip rectangle for the image. This is
  // (one less than) the size of the image in both
  // dimensions.
  get_clip_rect(picA,
                &upperLeftX,
                &upperLeftY,
                &lowerRightX,
                &lowerRightY);

  //Compute the width and the height from the coordinates
  // of the diagonal corners of the clip rectangle.
  width = lowerRightX  + 1;
  height = lowerRightY + 1;

When the get_clip_rect function returns, the coordinates of the diagonal corners of the rectangle have been stored in the variables named:

Includes the entire image

The clip rectangle has not been modified since the image was loaded.  Therefore, it should include the entire image.  Furthermore, the coordinates of the upper-left corner of the clip rectangle should be 0,0.

Because the coordinates of the upper-left corner are 0,0, the coordinates of the lower right corner should be one less than the width and height of the image respectively.

The final two statements in Listing 2 use the values stored in the variables named lowerRightX and lowerRightY to compute the width and height of the image under the assumption that the coordinates of the upper-left corner are 0,0.

Compute the window dimensions

Listing 3 computes the window dimensions based on the width and height of the bitmap image.

Listing 3. Compute the window dimensions.
  if(width % 4 == 0){
    windowWidth = width;
  }else{
    windowWidth = (width/4)*4 + 4;
  }//end else
  
  if(windowWidth < 108){
    windowWidth = 108;
  }//end if
  
  //Make the height sufficient to contain two copies of
  // the image, one above the other.
  windowHeight = 2 * height;

Abide by the restrictions

The code in Listing 3 guarantees that the width of the window:

Integer division

Note that the computation of the width in the else clause in Listing 3 takes advantage of the fact that the remainder is discarded in integer division in C++.

Numeric results in the screen output

If you examine the numeric results displayed in the upper-left corner of Figure 1, you will see that the width of the window (324) is the next integer value greater than the width of the image (321) that is divisible by four.  You can also see a very narrow black vertical stripe down the right side of Figure 1 where the image wasn't quite wide enough to completely fill the window.

Get and process a new image

The code in Listing 4:

Listing 4. Get and process a new image.
  destroy_bitmap(picA);
  set_gfx_mode(GFX_AUTODETECT_WINDOWED,
               windowWidth,
               windowHeight,
               0,
               0);
  picA = load_bitmap("starfish.pcx", NULL);
  
  //Copy the image to the upper-left corner of the
  // onscreen window.
  blit(picA, screen, 0,0,0,0,width,height);

By destroying the existing bitmap before setting the video mode, the code in Listing 4 satisfies the requirement described earlier.

Display the numeric information in the window

Listing 5 calls the sprintf function four times in succession to display the four numeric dimension values shown in the upper-left corner of Figure 1.

Listing 5. Display the numeric information in the window.
  //Display the width and height of the bitmap and the
  // window in the upper-left corner of the window.
  char charArray[20];
  sprintf(charArray, "%d", width);
  textout_ex(screen,font,charArray,10,10,
                                 makecol(255,255,255),-1);
  sprintf(charArray, "%d", height);
  textout_ex(screen,font,charArray,10,20,
                                 makecol(255,255,255),-1);
  sprintf(charArray, "%d", windowWidth);
  textout_ex(screen,font,charArray,10,30,
                                 makecol(255,255,255),-1);
  sprintf(charArray, "%d", windowHeight);
  textout_ex(screen,font,charArray,10,40,
                                 makecol(255,255,255),-1);

Convert numeric to string

The sprintf function can be used to convert a numeric value to a C-style string, according to a specified format, and to put the individual characters into an array of type char.  In this case, the specified format (%d) is a signed decimal integer.

After the numeric value has been converted to a string in each case in Listing 5, the textout_ex function is called to display the string in a specified location in the window.  You learned about the textout_ex function in an earlier lesson (see Resources).

Negate and display the image

Listing 6 negates the image and displays the negated image in the lower half of the window as shown in Figure 1.

Listing 6. Negate and display the image.
  //Cycle through the bitmap negating each pixel.
  for(int row = 0;row < height;row++){
    for(int column = 0;column < width;column++){
      pixel = getpixel(picA,column,row);
      red = getr(pixel);
      green = getg(pixel);
      blue = getb(pixel);
      putpixel(picA,column,row,makecol(
                             255-red,255-green,255-blue));
    }//end loop row
  }//end loop on column

  //Copy the modified bitmap to the onscreen window
  // immediately below the original image.
  blit(picA, screen, 0,0,0,height,width,height);

  //Block and wait until the user presses a key.
  readkey();
  
  //Destroy bitmap to avoid memory leaks.
  destroy_bitmap(picA);

  return 0;//Return 0 to indicate a successful run.
}//end main function
END_OF_MAIN()

Press any key to terminate

Listing 6 also terminates the program when the user presses any key.

I have explained code similar to Listing 6 in previous lessons (see Resources) so further explanation of that code in this lesson should not be necessary.

Summary

In this lesson, you learned:

Complete program listing

A complete listing of the program is shown in Listing 7.

Listing 7. Source code for program named ImageNegate03.
/*Project ImageNegate03

This program is similar to ImageNegate01. However, this
version assumes that the programmer does not know the
width and height of the image.

The purpose of this program is to show how to load an
image file into memory, how to determine the size of the
image, how to create a negative of the image, and how to
display both the original and the negative image in an
onscreen window.

A PCX image file of an unknown size is loaded into memory
and displayed in the top half of a graphics window on the
screen. A negated version of the image is displayed in the
bottom half of the window.

The width and height of the image and the width and height
of the window respectively are displayed in the upper-left
corner of the window. For a particular file named
starfish.pcx, the following output is superimposed on the
original image:
321
327
324
654

I have determined experimentally that the width of the
window MUST be a multiple of 4. I have also determined
experimentally that the width must be at least 108 pixels.
This second requirement seems to be a characteristic of
Windows Vista because it is not possible to manually
resize a window to make it narrower than approximately
108 pixels. If these two conditions aren't met, the
program hangs up my Windows Vista Home Premium edition OS
requiring me to log out and then log back in order to
continue programming in Dev-C++.

I have also determined experimentally that for PCX or BMP
files with a color depth of 16, 24, or 32, you MUST create
the onscreen window before you call load_bitmap to load
the image file. Otherwise, the colors may not be correct.
This makes it difficult to set the size of the window
based solely on the dimensions of the clip rectangle of
the image. The clip rectangle can't be obtained until
after the image file is loaded.

There may be a better solution to this problem than mine,
but if so, I haven't discovered it yet.  While my
solution is not elegant, it works. My solution in this
program is to perform the following sequence of
operations:

Load the image file into a bitmap.
Get the height and width of the bitmap.
Destroy the bitmap.
Create the window of the correct size.
Load the image file again into a second bitmap.
Display the image in the top of the window.
Negate the image in the bitmap.
Display the negated image in the bottom of the window.

Pressing any key causes the program to terminate.
*/
#include <allegro.h>
#include <stdio.h>//required to call sprintf

int main(){
  int pixel = 0;//temporary storage for a pixel

  //Temporary storage for red, green, and blue
  int red = 0;
  int green = 0;
  int blue = 0;
  
  int width = 0;//image width
  int height = 0;//image height
  int windowWidth = 0;//must be a multiple of 4 && >= 108
  int windowHeight = 0;
  
  //The addresses of the following variables are passed
  // in the call to get_clip_rect, where they are
  // populated with the coordinates of the diagonal
  // corners of the rectangular image.
  int upperLeftX = 0;
  int upperLeftY = 0;
  int lowerRightX = 0;
  int lowerRightY = 0;

  //Typical Allegro setup.
  allegro_init();
  install_keyboard();
  set_color_depth(32);

  //Declare a pointer variable capable of pointing to a
  // bitmap.
  BITMAP *picA = NULL;
  //Load an image file from the current directory.
  picA = load_bitmap("starfish.pcx", NULL);
  
  //Get the current clip rectangle for the image. This is
  // (one less than) the size of the image in both
  // dimensions.
  get_clip_rect(picA,
                &upperLeftX,
                &upperLeftY,
                &lowerRightX,
                &lowerRightY);

  //Compute the width and the height from the coordinates
  // of the diagonal corners of the clip rectangle.
  width = lowerRightX  + 1;
  height = lowerRightY + 1;
  
  //Compute the window dimensions based on the width
  // and height of the bitmap. Guarantee that the width
  // is a multiple of 4 large enough to contain the
  // image and is not less than 108 pixels.
  if(width % 4 == 0){
    windowWidth = width;
  }else{
    windowWidth = (width/4)*4 + 4;
  }//end else
  
  if(windowWidth < 108){
    windowWidth = 108;
  }//end if
  
  //Make the height sufficient to contain two copies of
  // the image, one above the other.
  windowHeight = 2 * height;
  
  //Destroy the bitmap, create the display window, and
  // reload the bitmap.
  destroy_bitmap(picA);
  set_gfx_mode(GFX_AUTODETECT_WINDOWED,
               windowWidth,
               windowHeight,
               0,
               0);
  picA = load_bitmap("starfish.pcx", NULL);
  
  //Copy the image to the upper-left corner of the
  // onscreen window.
  blit(picA, screen, 0,0,0,0,width,height);

  //Display the width and height of the bitmap and the
  // window in the upper-left corner of the window.
  char charArray[20];
  sprintf(charArray, "%d", width);
  textout_ex(screen,font,charArray,10,10,
                                 makecol(255,255,255),-1);
  sprintf(charArray, "%d", height);
  textout_ex(screen,font,charArray,10,20,
                                 makecol(255,255,255),-1);
  sprintf(charArray, "%d", windowWidth);
  textout_ex(screen,font,charArray,10,30,
                                 makecol(255,255,255),-1);
  sprintf(charArray, "%d", windowHeight);
  textout_ex(screen,font,charArray,10,40,
                                 makecol(255,255,255),-1);

  //Cycle through the bitmap negating each pixel.
  for(int row = 0;row < height;row++){
    for(int column = 0;column < width;column++){
      pixel = getpixel(picA,column,row);
      red = getr(pixel);
      green = getg(pixel);
      blue = getb(pixel);
      putpixel(picA,column,row,makecol(
                             255-red,255-green,255-blue));
    }//end loop row
  }//end loop on column

  //Copy the modified bitmap to the onscreen window
  // immediately below the original image.
  blit(picA, screen, 0,0,0,height,width,height);

  //Block and wait until the user presses a key.
  readkey();
  
  //Destroy bitmap to avoid memory leaks.
  destroy_bitmap(picA);

  return 0;//Return 0 to indicate a successful run.
}//end main function
END_OF_MAIN()

 

Resources


Copyright

Copyright 2008, Richard G. Baldwin.  Reproduction in whole or in part in any form or medium without express written permission from Richard Baldwin is prohibited.

About the author

Richard Baldwin is a college professor (at Austin Community College in Austin, TX) and private consultant whose primary focus is a combination of Java, C#, and XML. In addition to the many platform and/or language independent benefits of Java and C# applications, he believes that a combination of Java, C#, and XML will become the primary driving force in the delivery of structured information on the Web.

Richard has participated in numerous consulting projects and he frequently provides onsite training at the high-tech companies located in and around Austin, Texas.  He is the author of Baldwin's Programming Tutorials, which have gained a worldwide following among experienced and aspiring programmers. He has also published articles in JavaPro magazine.

In addition to his programming expertise, Richard has many years of practical experience in Digital Signal Processing (DSP).  His first job after he earned his Bachelor's degree was doing DSP in the Seismic Research Department of Texas Instruments.  (TI is still a world leader in DSP.)  In the following years, he applied his programming and DSP expertise to other interesting areas including sonar and underwater acoustics.

Richard holds an MSEE degree from Southern Methodist University and has many years of experience in the application of computer technology to real-world problems.

Baldwin@DickBaldwin.com

-end-