Did you ever want to capture a portion of your application as a bitmap file on disk? If so, you'll be glad to learn that doing so isn't difficult. Getting it right, though, takes a little work. In this article, we'll show you how to capture a VCL form to a Windows bitmap (BMP) file on disk. We'll also exlain how to get the palette for your form so you are sure the image will be saved properly.
Create a TCanvas object to represent the desktop device context.
| Create a TBitmap object to hold the bitmap in memory.
| Copy the bits representing the form onscreen from the desktop canvas to the
bitmap in memory.
| Save the bitmap in memory to disk
| |
As you know, some VCL components come with a Canvas property and some don't. A PaintBox component has a Canvas property, naturally, so you can draw on the paint box. A Memo component, on the other hand, has no Canvas property. Windows is responsible for drawing the contents of a Memo component, and most mere mortals shouldn't dabble in that area (nor would you want to, in most cases). The good news is that you can create a Canvas for any window, provided you know the window handle.
For example, suppose you were to tempt fate and draw on a Memo component. You could create a TCanvas object for that purpose with this code:
TCanvas* canvas = new TCanvas; canvas->Handle = GetDC(Memo->Handle); // do some stuff delete canvas;When you call delete on the TCanvas object, VCL performs all the nasty cleanup tasks required to properly dispose of a device context. Clean and easy (that's what C++Builder is all about). But the desktop isn't a window, per se. So how do you create a device context for the desktop? The Windows API provides a way. When you call GetDC() with a window handle of 0, Windows happily creates a device context for the desktop. Combining that with the previous code snippet, you can create a canvas for the desktop as follows:
TCanvas* canvas = new TCanvas; canvas->Handle = GetDC(0);Now you can draw on the desktop to your heart's content--but you shouldn't, of course. You should usually consider a desktop canvas a read-only object. That's how we'll use the desktop canvas in this case.
Graphics::TBitmap*
bitmap = new Graphics::TBitmap;
As you can see, you must specify the Graphics namespace so the compiler knows
you're creating a VCL TBitmap object (as opposed to the "other" TBitmap, which
is a typedef for a Windows HBITMAP handle). That's not the whole story, though,
because you also need to set the height and width of the bitmap prior to
writing data to it. Since you'll be copying the form's image to the bitmap, you
can use the height and width of the form to size the bitmap:bitmap->Width = Width; bitmap->Height = Height;You're now ready to get on with the business of copying the form's bitmap bits from the desktop canvas to the bitmap in memory.
TRect src = BoundsRect; TRect dest = Rect(0, 0, Width, Height); bitmap->Canvas->CopyRect(dest, dtCanvas, src);The first line sets the rectangle called src to the size and position of the form's outer dimensions (the BoundsRect property provides these dimensions). You'll use this rectangle as the source of your copy operation. The second line sets the destination rectangle. In this case, the destination rectangle is the entire size of the memory bitmap.
The last line in the code snippet copies the bitmap bits from the source rectangle to the destination rectangle. The CopyRect function of TCanvas does all the work. Behind the scenes, this step translates to a call to the Windows API function BitBlt().
All that remains is to save the bitmap in memory to a disk file. Don't blink or you'll miss it:
bitmap->SaveToFile("form.bmp");
Yes, that's really all there is to it. Now you have a copy of your form
captured on disk.
Figure A: This saved form has a bad palette.
Even in black and white, you can see that something is drastically wrong with this picture. The reason is that the palette information for the 256-color bitmap wasn't saved when the file was saved. To make the bitmap display correctly, you have to obtain the form's palette and assign it to the memory bitmap's Palette property before saving the file.
In this case, VCL doesn't provide an easy way to obtain a form's palette. You'll have to get your hands dirty and use the API. Here's the code:
int nColors = GetDeviceCaps(Canvas->Handle, SIZEPALETTE); LOGPALETTE* logPal = (LOGPALETTE*)new Byte[ sizeof(LOGPALETTE) + (nColors - 1) * sizeof(PALETTEENTRY)]; logPal->palVersion = 0x300; logPal->palNumEntries = (Word)nColors; GetSystemPaletteEntries(Canvas->Handle, 0, nColors, logPal->palPalEntry); bitmap->Palette = CreatePalette(logPal); delete[] logPal;Yikes! Yes, folks, this is the way people used to write Windows programs. In summary, the code snippet gets the number of colors for the current display driver, creates a LOGPALETTE structure using that information, and then gets the palette for the form by calling GetSystemPaletteEntries(). This Windows API function requires a handle to a device context, so you pass the Handle property for the form's canvas. After that, you create a new palette with the CreatePalette() function and assign the result to the memory bitmap's Palette property. Now the color information will be saved when the bitmap is saved to disk.
Note that if you have a single 256-color object on a form, you can take a shortcut and assign that component's Palette to the Palette of the bitmap. For example:
bitmap->Palette = Image1->Picture->Palette;This is simplest method of preserving palette information, but it isn't always the most reliable. The method we've outlined ensures that the entire form's palette will be preserved rather than relying on the palette of a single component. Figure B shows the result when the palette information is saved properly. As you can see, this image shows a dramatic improvement.
Figure B: When we save the form with the proper palette, it looks much better.
Listing A: FORMCAPU.CPP
//---------------------------------------------
#include <vcl\vcl.h>
#pragma hdrstop
#include "FormCapU.h"
//---------------------------------------------
#pragma resource "*.dfm"
TForm1 *Form1;
//---------------------------------------------
__fastcall TForm1::TForm1(TComponent* Owner)
: TForm(Owner)
{
}
//---------------------------------------------
void __fastcall TForm1::Button1Click(
TObject *Sender)
{
// Create TCanvas object for desktop DC.
TCanvas* dtCanvas = new TCanvas;
dtCanvas->Handle = GetDC(0);
// Create new TBitmap object and set its
// size to the size of the form.
Graphics::TBitmap*
bitmap = new Graphics::TBitmap;
bitmap->Width = Width;
bitmap->Height = Height;
// Create palette from form's Canvas; assign
// that palette to bitmap's Palette property.
int nColors =
GetDeviceCaps(Canvas->Handle, SIZEPALETTE);
LOGPALETTE* logPal = (LOGPALETTE*)new Byte[
sizeof(LOGPALETTE) + (nColors - 1) *
sizeof(PALETTEENTRY)];
logPal->palVersion = 0x300;
logPal->palNumEntries = (Word)nColors;
GetSystemPaletteEntries(Canvas->Handle,
0, nColors, logPal->palPalEntry);
bitmap->Palette = CreatePalette(logPal);
delete[] logPal;
// Copy section of screen from
// desktop canvas to the bitmap.
TRect src = BoundsRect;
TRect dest = Rect(0, 0, Width, Height);
bitmap->Canvas->CopyRect(dest, dtCanvas, src);
// Save it to disk.
bitmap->SaveToFile("form.bmp");
// Clean up and go home.
delete bitmap;
delete dtCanvas;
}