July 1998

Displaying your bitmaps quickly

by Andrea Fasano

During application development, you must often manage various graphic elements, such as images and icons. Fortunately, C++ Builder provides the developer with several tools that use these graphic elements. In this article, we'll study the concept of offscreen bitmaps and the double-buffering technique. We'll use these tools to help us efficiently manage the graphics in our program.

 

A simple graphic application

In order to appreciate the advantages of using offscreen bitmaps and double-buffering, we'll first build an application using the normal C++ Builder features. Then, we'll implement the same application using an offscreen bitmap. The application itself will simply filter an image. Specifically, the filter will produce the negative of a given image (for instance, a white picture becomes a black picture). We begin designing our application by following these steps:
  1. Create a new application choosing File | New Application from the main menu.
  2. Choose an Image component from the Component Palette and place it on the form. (The component is located on the Additional tab.)
  3. Now double-click the Image component, Image1, to open the Picture Editor utility.
  4. Click the Load button on the Picture Editor. The Load picture dialog box is displayed. Locate and choose the file SampleImage.bmp (you can find this file in the archive). Click OK.
  5. Press F11 to switch to the Object Inspector and change the AutoSize property of Image1 to True. Notice how Image1 resizes to accommodate the image.
  6. Click on the Standard tab on the Component Palette and choose a Button component. Place the button, Button1, under the image and to the left.
  7. Change the Name property to buttonNormal and the Caption property to Normal.
  8. Add another button, Button2, and place it under and to the right of Image1.
  9. Change Button2's Name and Caption properties to buttonDoubleBuffer, and DoubleBuffer, respectively. At this point the application should resemble Figure A.
Figure A: Your form will first display an image, then display its negative.
[ Figure A ]

 

The slooooww code

Now we're ready to add the code "behind" the buttons. First, double-click the Normal button to create an OnClick event shell. The Code Editor displays the following method:
void __fastcall 
TForm1::buttonNormalClick 
	(Tobject *Sender)
{

}
We'll add the code that produces the negative image here. The steps to achieve a negative image are very simple. First, you must get the color value of each pixel in the image; then you must invert every bit. Afterwards, the new color value will be stored at the same coordinates (x, y) of the old value and displayed on screen. You can read individual pixels simply by using the Pixels property. Just access the Pixels property of the canvas, letting the indexes be the x- and y-coordinates of the pixel you want to read: Image->Picture->Bitmap->Canvas->Pixels[x][y]. Remember that the upper left corner of the canvas is the origin of the coordinate system. Once you've gained the color, you must do a bitwise exclusive-OR between this value and 0x00ffffff to obtain the inverted color (Thankfully, Windows uses only 24-bits to manage colors). So, for a single pixel, you'll write a line of code similar to this one:

 


Image1->Picture->Bitmap->Canvas->Pixels
	[x][y]= Image1->Picture->Bitmap->
		Canvas->Pixels[x][y] ^ 
			0x00ffffff;
Because we must execute this operation for each pixel of the image, we need to scan the entire bitmap using two loops. The first loop is for the horizontal size, and the second is for the vertical size. You can determine these values using the properties Image1-> Picture->Width and Image1->Picture->Height. The final code in the buttonNormalClick method should resemble Listing A. Make sure you add all of the highlighted code.

Listing A: The buttonNormalClick method


void __fastcall TForm1::buttonNormalClick
	(TObject *Sender)
{
 Screen->Cursor = crHourGlass;

 for (int y=0; yPicture->
	Height; y++)
   for (int x=0; xPicture->
	Width; x++)
     Image1->Picture->Bitmap->Canvas->
	Pixels [x][y] =
       Image1->Picture->Bitmap->Canvas->
	Pixels [x][y] ^0x00ffffff;

 Screen->Cursor = crArrow;
}
To designate the beginning and the end of the graphic operation within the function, I inserted two lines of code. The first line, Screen->Cursor = crHourGlass, marks the start of the operation by changing the mouse cursor to a sand-glass. The second line, Screen->Cursor = crArrow, marks the end of the operation by setting the mouse cursor to the normal arrow shape. Now you're ready to test the application for yourself. First, press the [F9] key to compile and run the program. Next, press the Normal button and watch how much time the application takes to finish the graphic operation. (As a reference, the program takes about 10 seconds to produce the negative image on my P166.) Of course, we can't consider this result a good performance, especially because we used a really simple filter. The performance is slow because of the tremendous number of calls necessary to invert the Pixels property. Each time a pixel is read, it's immediately displayed on the screen. Since the sample image is 400x300 pixels, there are a total of 240,000 calls to the Pixels property (each pixel requires one call to read the color and one to write the inverted color) and a total of 120,000 pixels printed. To avoid the performance hit involved in such large numbers, you can use a technique called double-buffering, which we'll discuss next.

Double-buffering

Double-buffering is a simple trick: All the changes applied to the source image aren't immediately displayed. Instead, they're stored in a buffer, the offscreen bitmap. Then, at the end of the graphic operation, the entire buffer is copied onto the screen. This technique is illustrated in Figure B.

Figure B: The original image is copied, updated, copied back, and redisplayed.
[ Figure B ]

Now, let's see how we implement this very useful technique. First, note that you'll use an offscreen bitmap as a buffer. We can describe an offscreen bitmap as simply a work area not visible on the form itself; it also serves as the destination of some graphic operations. The offscreen bitmap stores the changes to the original image. Using a non-visible working area offers many advantages. A primary benefit occurs because a non-visible work area greatly reduces the execution time needed by graphic operations. To create an offscreen bitmap, you add its declaration to the definition of your form, like so:

…
Public:
 Graphics::TBitmap *DoubleBuffer;
…
You also must remember to initialize and destroy DoubleBuffer when the application starts and ends. So, select Form1 and switch to the Object Inspector. Locate the OnCreate event and double-click it. Then add the following highlighted code into the Code Editor:
void __fastcall TForm1::FormCreate
	(TObject *Sender)
{
 DoubleBuffer = new Graphics::TBitmap;
}
Now repeat the preceding steps for the OnDestroy event, and add this line of code:

void __fastcall TForm1::FormDestroy
	(TObject *Sender)
{
 delete DoubleBuffer;
}
Next, double-click the buttonDoubleBuffer button to create its OnClick event shell. We'll use the double buffer technique to place the code that produces a negative image inside the buttonDoubleBufferClick method. To do so, we'll use three logical steps:
  1. Copy the source image (Image1) to DoubleBuffer.
  2. Apply the filter to DoubleBuffer.
  3. Copy DoubleBuffer to the destination image (Image1).
To perform the first step, we can use the Assign method that copies the bitmap image contained in its Source parameter to the bitmap object. Therefore, you'll write the first line of code similar to this one:
DoubleBuffer->Assign
	(Image1->Picture->Bitmap);
The second step is very easy to develop. You can reuse the same code that lies inside the buttonNormalClick method. Of course, in this case, you must use DoubleBuffer instead of Image1->Picture->Bitmap. The last step is the opposite of the first one. You assign DoubleBuffer to Image1->Picture->Bitmap. The finished buttonDoubleBufferClick method should resemble Listing B. Be sure you add all the highlighted code.

Listing B: The buttonDoubleBufferClick method

void __fastcall TForm1::buttonDouble
	BufferClick(TObject *Sender)
{
 DoubleBuffer->Assign 
	(Image1->Picture->Bitmap);
 Screen->Cursor = crHourGlass;

 for (int y=0; y
	Height; y++)
   for (int x=0; x
	Width; x++)
     DoubleBuffer->Canvas->Pixels 
	[x][y] =
            DoubleBuffer->Canvas->Pixels
	 [x][y] ^ 0x00ffffff;

 Screen->Cursor = crArrow;
 Image1->Picture->Bitmap->Assign \
	(DoubleBuffer);
}
Now it's time to see the offscreen bitmap technique at work. Compile and run the application, and press the DoubleBuffer button. How much time does the application take to produce the negative image? On my PC, it takes less than two seconds! The graphic operation now is about five times faster than before.

Conclusions

In this article, we studied the double buffer technique to make our bitmaps display more quickly. You could use this technique in many other situations and really increase your overall performance. In addition, you saw how to use the Assign method to copy the bitmap contained in one object to another object. As you can see, VCL graphic components offer many other methods to execute the copy. We'll explore these methods in future articles.