ast month, I demonstrated how to set up GDI+ for use in a
VCL application. This month, I’ll show you how to work with bitmaps in GDI+ and
how to use GDI+ drawing routines to effect output to a
TBitmap object.
Raster- vs.
vector-based rendering
Drawing anything to a digital device ultimately boils down
to highlighting certain pixels and turning off others. A digital image is
created by defining a particular pattern of pixels, one which our visual system
interprets as a representation of a scene [1].
In Windows, there are two main
ways to render a digital image to an output device (e.g., a screen, printer).
One approach is to load/create a bitmap and then transfer this bitmap to the
device via, e.g., the
BitBlt() GDI function or the
Graphics::DrawImage() GDI+ method; this technique is typically
called bitmapped or raster-based rendering. The other approach is
to use a higher-level “language” to communicate to the output device the lines,
shapes, and other graphics primitives that you want drawn; this latter technique
is called vector-based rendering.
This month, I'll cover
raster-based rendering with GDI+ bitmaps; and, next time, we'll tackle
vector-based rendering (with metafiles). Raster-based rendering is typically
faster than vector-based rendering, but this speed comes at a cost. Namely,
bitmaps lack scalability—if you want to draw a larger version of a bitmapped
image, you'll need to perform some form of interpolation.
GDI+ bitmaps
Recall that there are three types of bitmaps in the
standard GDI: device-dependent bitmaps (DDBs); device-independent
bitmaps (DIBs); and DIB section bitmaps.; see [2]. Also recall that
whereas the pixel format—i.e., the number of bits with which each pixel is
represented—of a DDB depends on the current screen settings, and whereas a DIB
is not a true graphics object (meaning that you can’t select it into a device
context), a DIB section bitmap suffers from neither of these limitations.
Accordingly, when using the standard GDI, DIB section bitmaps are the preferred
variety for caching and drawing raster-based graphics. Because GDI+ relies on
the standard GDI as its backend, all bitmaps in GDI+ are effectively DIB section
bitmaps.
|
Table 1: Commonly used methods
of the Bitmap class
|
|
Method
|
Description
|
|
UINT GetWidth()
|
Returns
the width of bitmap, in pixels.
|
|
UINT GetHeight()
|
Returns
the height of the bitmap, in pixels.
|
|
PixelFormat GetPixelFormat()
|
Returns
a PixelFormat-type constant [3] that
identifies the number of bits used to represent each pixel.
|
|
Status LockBits(
const Rect* rect,
UINT flags,
PixelFormat
format,
BitmapData*
lockedBitmapData
)
|
Retrieves/specifies a pointer to a buffer to the
bitmap’s pixels; see “Accessing the pixels” later in this article.
|
|
Status UnlockBits(
BitmapData*
lockedBitmapData
)
|
Effects to the bitmap any changes made to the
buffer previously retrieved/specified via the LockBits()
method; see “Accessing the pixels” later in this article.
|
|
Status Save(
const WCHAR* filename,
const CLSID* clsidEncoder,
const EncoderParameters*
encoderParams
= NULL
)
|
Saves the bitmap to a file specified by filename and
in the file-format specified by clsidEncoder; the optional and
file-format-specific encoderParams parameter can be used to
specify attributes with which the file should be saved (e.g., compression
quality for JPEGs).
|
|
You create a (DIB section) bitmap
in GDI+ by using the
Bitmap class. The
Bitmap
class is a descendant of the GDI+
Image class that provides
bitmap-specific functionality such as creating a bitmap from a DDB or DIB, and
accessing and modifying the bitmap’s pixels.
Table 1 lists the
Bitmap class’s key methods (many
of which are inherited from the
Image class); see [4] for a full
listing.
Notice that
Table 1 doesn’t list a method for loading
a bitmap from a file. As I’ll discuss next, loading a bitmap is typically done
by using the
Bitmap constructor.
Bitmap constructors
The
Bitmap class provides 10 constructors which are used to
create GDI+ bitmaps from various sources and/or with various attributes. For
applications in which a bitmap is used as an in-memory drawing surface, you
typically need a bitmap of a specific pixel format and of a specific size.
Here’s the declaration of the
Bitmap constructor that you’d use in that case:
void Bitmap(
INT width,
INT height,
PixelFormat format
);
The
width and
height parameters specify the dimensions of bitmap, in pixels; and the
format parameter specifies the bitmap’s pixel format [3]. For example,
the following code snippet demonstrates how to create a 512x512,
24-bits-per-pixel (bpp) bitmap:
// create a 24-bpp 512x512 bitmap
gdp::Bitmap bitmap(512, 512,
PixelFormat24bppRGB);
GDPCheck(bitmap.GetLastStatus());
// other code here...
Note that all of the pixels of the newly created bitmap are
initially set to zero. (Also, recall that the
GDPCheck()
function was defined in last month’s article.)
Another common requirement is the
ability to load a bitmap from a file; here’s the
Bitmap
constructor to do that:
void Bitmap(
const WCHAR* filename,
BOOL useIcm = FALSE
);
The
filename parameter specifies the name of .BMP file to load,
and the optional
useIcm parameter that specifies whether or not the method
should acknowledge the bitmap’s embedded color-profile information, if present.
Listing A demonstrates how to load a
bitmap from a file; the
Bitmap object is created
on the heap—so that it remains alive throughout the form’s lifetime—and it is
destroyed in the form’s destructor before calling the
GdiPlusShutdown() function.
The
Bitmap
class also provides a constructor to load a bitmap from a resource instead of
from a file. To load a bitmap from a resource, you’d use the following
Bitmap
constructor:
void Bitmap(
HINSTANCE hInstance,
const WCHAR* bitmapName
);
The
hInstance parameter specifies the handle to the (application
or library) instance that contains the resource identified by the
bitmapName parameter. (This method is similar to the
LoadFromResourceName()
method of the
TBitmap class.)
The
Bitmap
class also provides constructors that allow you to create a bitmap based on an
existing DDB, DIB, or even a raw array of pixels:
// creation based on a DDB
// (or DIB section bitmap)
void Bitmap(
HBITMAP hbm,
HPALETTE hpal
);
// creation based on a DIB
void Bitmap(
const BITMAPINFO* gdiBitmapInfo,
VOID* gdiBitmapData
);
// creation based on a raw array
void Bitmap(
INT width,
INT height,
INT stride,
PixelFormat format,
BYTE* scan0
);
For creation based on a DDB, the Bitmap constructor takes
two parameters: a handle to the DDB (hbm; this can also be a handle to a
DIB section bitmap) and a handle to the DDB’s palette (hpal, which can be
NULL if the DDB doesn’t use a palette). The Bitmap object won’t take
ownership of the DDB; rather, it creates it’s own bitmap representation (DIB
section) based on the DDB.
|
Listing A:
Loading a bitmap |
|
//-----------------------------------------------
// in header...
//-----------------------------------------------
#include <memory>
class TForm1 : public TForm
{
// other stuff...
private:
ULONG_PTR gdp_token_;
std::auto_ptr<gdp::Bitmap> bitmap_;
void __fastcall LoadBitmap(WideString
fname);
};
//-----------------------------------------------
// in source...
//-----------------------------------------------
__fastcall TForm1::TForm1(
TComponent* Owner) : TForm(Owner)
{
// initialize the GDI+
library
GDPCheck(
gdp::GdiplusStartup(&gdp_token_,
&gdp::GdiplusStartupInput(), NULL
));
}
__fastcall TForm1::~TForm1()
{
// delete the Bitmap
delete bitmap_.release();
// close the GDI+ library
gdp::GdiplusShutdown(gdp_token_);
}
void __fastcall TForm1::
LoadBitmap(WideString fname)
{
// load the bitmap from a
file
bitmap_.reset(
new gdp::Bitmap(fname.c_bstr())
);
// if the bitmap did not
// load successfully...
gdp::Status const res =
bitmap_->GetLastStatus();
if (res != gdp::Ok)
{
// delete the
invalid bitmap
delete bitmap_.release();
// throw an
exception
throw EGDIPlusError(res);
}
// NOTE: You should check
that
// bitmap_.get() != NULL
before
// trying to access the
bitmap
// elsewhere in your code
} |
For creation based on a DIB, the
constructor also takes two parameters: a pointer to a DIB’s header and color
table (gdiBitmapInfo),
and a pointer to the DIB’s pixels (gdiBitmapData).
Again, the
Bitmap object doesn’t assume ownership of the DIB.
For creation based on a raw array
of pixels, the
Bitmap constructor takes five
parameters: integers that specify the
width and
height
of the image, an integer that specifies how many bytes of the array should be
used for each row (stride),
a PixelFormat-type
variable [3] that specifies the pixel-format of the array (format),
and a pointer to the beginning of the array (scan0).
The
Bitmap
class also provides four other constructors which are used to create a bitmap
based on an
IStream, based on a DirectDraw surface, based on the attributes of
a GDI+
Graphics object, or based on an icon. I won’t discuss these
constructors here, but you can see [5] for more information.
Drawing GDI+ bitmaps to
the screen
Now that you know how to create GDI+ bitmaps, let me show
you draw them to the screen. As I mentioned last month, this is accomplished by
using the
DrawImage() method of the GDI+
Graphics class [6]. There are actually 16 variants of the
DrawImage() method, which allow you to draw the bitmap at (integer and
floating-point) locations, using (integer and floating-point) scaling factors,
and using a callback function.
For example, to draw a bitmap in
its original size at location (x_pos,
y_pos)
in your form’s client area, you’d do the following:
// create a Graphics object
//
associated with the form
gdp::Graphics graphics(Handle);
GDPCheck(graphics.GetLastStatus());
// draw the bitmap to the form
// at location (10, 20)
int const x_pos = 10;
int const y_pos = 20;
graphics.DrawImage(
&bitmap, x_pos, y_pos
);
Here,
bitmap is assumed to be a pre-created
Bitmap object. Similarly, here’s the
code to draw a scaled version of the bitmap:
// create a Graphics object
associated
// with the form
gdp::Graphics graphics(Handle);
GDPCheck(graphics.GetLastStatus());
// draw the bitmap to the form
// at location (10, 20) and in
// 20% of its original size
int const x_pos = 10;
int const y_pos = 20;
graphics.DrawImage(
&bitmap, x_pos, y_pos,
// new width
bitmap.GetWidth() / 5,
// new height
bitmap.GetHeight() / 5
);
And, to draw just a portion of the bitmap, you’d use the
following approach:
// create a Graphics object
associated
// with the form
gdp::Graphics graphics(Handle);
GDPCheck(graphics.GetLastStatus());
// draw the first 75 rows of the
bitmap
// to the form at location (10, 20)
and
// in 150% of its original size
gdp::Rect const RDest(
// target (x, y)
10, 20,
// target width
1.5f * bitmap.GetWidth(),
// target height
1.5f * bitmap.GetHeight()
);
graphics.DrawImage(
&bitmap,
// target (destination) rectangle
RDest,
// source coordinates (chunk of
// the bitmap to draw)
0, 0, bitmap.GetWidth(), 75,
// source chunk is in pixel coords
gdp::UnitPixel
);
This latter version of the
DrawImage() method
operates in a fashion similar to the
StretchBlt() GDI function. Note, however, that although I’ve
used integer-valued coordinates in these examples, the
DrawImage() method also allows you to specify floating-point-valued
coordinates.
Drawing to GDI+ bitmaps
As with DDBs and DIB section bitmaps (but not DIBs), you
can render output to a GDI+ bitmap by using normal drawing routines. In the
standard GDI, you’d effect output to a bitmap object by selecting the bitmap
into a memory device context (DC); you’d then call your drawing function,
specifying a handle to that memory DC as the “target” DC. In GDI+, you use a
similar approach. Instead of creating a memory DC, you create a
Graphics object that’s
associated with the bitmap.
|
Figure A |
|

By creating a GDI+
Graphics object that’s
associated with a GDI+
Bitmap, you can first draw to the
bitmap, and then (using another
Graphics
object) draw the bitmap to the screen. |
The following code snippet
demonstrates how to create a
Graphics object that’s associated
with a bitmap and how to use that
Graphics object to render output
to the bitmap (and then to the screen); the result of this code is depicted in
Figure A:
// create a 24-bpp 128x256 bitmap
int const bmp_cx = 128;
int const bmp_cy = 256;
gdp::Bitmap bitmap(bmp_cx, bmp_cy,
PixelFormat24bppRGB);
GDPCheck(bitmap.GetLastStatus());
// create a Graphics object that's
// associated with the bitmap
gdp::Graphics bmp_graphics(&bitmap);
GDPCheck(bmp_graphics.GetLastStatus());
// create a brush with a horizontal
// white-to-black gradient that
// repeats after bmp_cx units
gdp::LinearGradientBrush brush(
// gradient starting point
gdp::Point(0, 0),
// gradient ending/restarting
point
gdp::Point(bmp_cx, 0),
// white to start
gdp::Color(255, 255, 255),
// black to end
gdp::Color(0, 0, 0)
);
GDPCheck(brush.GetLastStatus());
// fill the bitmap using the brush
bmp_graphics.FillRectangle(&brush,
0, 0, bmp_cx, bmp_cy);
// create a Graphics object that's
// associated with the form
// and then draw the bitmap to the
form
gdp::Graphics graphics(Handle);
GDPCheck(graphics.GetLastStatus());
graphics.DrawImage(&bitmap, 0, 0);
Note that this technique of first rendering output to a
bitmap and then rendering the bitmap to the screen is known as double
buffering; the bitmap serves as a so-called “back buffer” and the screen
serves as the front or “primary buffer.” Double buffering is useful for
situations in which repetitive, complex rendering is required: Instead of
placing the time-consuming drawing code within, for example, the form’s
OnPaint event handler, it makes more sense (assuming your drawing code
doesn’t change) to execute the expensive code only once, storing the output in a
bitmap, and then drawing this bitmap to the screen.
As note that the
Graphics
constructor which takes a
Bitmap pointer as it’s single
parameter (as used in this code) will fail—yielding a generic out-of-memory
error message—if the
Bitmap’s pixel format is
PixelFormatUndefined,
PixelFormatDontCare,
PixelFormat1bppIndexed,
PixelFormat4bppIndexed,
PixelFormat8bppIndexed,
PixelFormat16bppGrayScale, or
PixelFormat16bppARGB1555. This is a documented limitation [6].
Accessing the bitmap’s
pixels
One of the main advantages of using a DIB section bitmap
over a DDB is that the standard GDI allows direct access to the DIB section’s
pixels. Accordingly, GDI+ also permits direct access to a
Bitmap’s
pixel via the
Bitmap::LockBits() method.
To use the
LockBits() method, you specify: (1) the portion of the bitmap’s
pixels to which you want access, (2) the type of access desired (reading,
writing, or both), (3) the format of the pixels (more on this shortly), and (4)
a pointer to a
BitmapData object, which the
LockBits()
method will fill with information about the bitmap’s pixels. And, once you’re
done examining and/or modifying the pixels, you use the
Bitmap::UnlockBits() method to effect the changes.
For example, to convert a 32-bpp
bitmap to grayscale, you’d use the
Bitmap::LockBits() and
UnlockBits() methods as follows:
// load the bitmap from a file
gdp::Bitmap bitmap(L"c:/pict_32bpp.bmp");
GDPCheck(bitmap.GetLastStatus());
// define the area of the bitmap to
lock
UINT const bmp_cx = bitmap.GetWidth();
UINT const bmp_cy = bitmap.GetHeight();
gdp::Rect bmp_rect(0, 0, bmp_cx, bmp_cy);
// grab a pointer to the pixels
gdp::BitmapData bmp_data;
GDPCheck(bitmap.LockBits(
// portion of the bitmap to lock
&bmp_rect,
// read-/write-access flags
gdp::ImageLockModeRead |
gdp::ImageLockModeWrite,
// desired format of output pixels
bitmap.GetPixelFormat(),
// filled with bitmap data
(including
// a pointer to the pixels)
&bmp_data
));
unsigned char* p_pixels =
static_cast<unsigned char*>
(bmp_data.Scan0);
for (UINT y = 0; y < bmp_cy; ++y)
{
// grab an RGBQUAD* to each row
RGBQUAD* p_scanline =
reinterpret_cast<RGBQUAD*>(
p_pixels + (y * bmp_data.Stride)
);
for (UINT x = 0; x < bmp_cx; ++x)
{
// compute grayscale
unsigned short const gray_val =
0.299f * p_scanline[x].rgbRed +
0.587f * p_scanline[x].rgbGreen +
0.114f * p_scanline[x].rgbBlue;
// change the pixel
p_scanline[x].rgbRed =
p_scanline[x].rgbGreen =
p_scanline[x].rgbBlue =
(gray_val > 255) ?
255 : gray_val;
}
}
// unlock the bitmap (commits any
// changes made to the pixels)
bitmap.UnlockBits(&bmp_data);
Note that the desired pixel format, which is specified via
the third parameter to
LockBits(), doesn’t have to be the
same as the pixel format of the bitmap. However, for 16-bpp, 24-bpp, and 32-bpp
bitmaps,
LockBits() will fail if you specify an output pixel-format that
requires a color table (PixelFormat1bppIndexed,
PixelFormat4bppIndexed, or
PixelFormat8bppIndexed).
Furthermore, if you request the data in a different format than that of the
bitmap (e.g., if you request
PixelFormat24bppRGB for a 32-bpp
bitmap), GDI+ will have to create a temporary array to accommodate your new
format. Although the documentation for the
LockBits() method states that a temporary array is always
created, I’ve found that this is not the case when you request the pixels in the
same format as that of the bitmap. In short, it’s generally best to avoid format
conversions when using
LockBits()—i.e., pass the return
value of the
Bitmap::GetPixelFormat() as the
third parameter to
LockBits().
|
Listing B:
Saving a bitmap |
|
void GetEncoderCLSID(
CLSID& enc_clsid,
WideString mime_type
)
{
UINT num_encs, size_encs;
// get the number of installed
encoders and
// the size in bytes required
to hold the
// ImageCodecInfo data for
these encoders
GDPCheck(
gdp::GetImageEncodersSize(&num_encs, &size_encs)
);
if (num_encs < 1)
{
throw EGDIPlusError(
"No GDI+ encoders
installed."
);
}
// create a buffer to hold the
encoders' data
unsigned char* const p_buffer =
new
unsigned char[size_encs];
try
{
// cast the buffer to
ImageCodecInfo*
gdp::ImageCodecInfo* const p_encs =
reinterpret_cast<gdp::ImageCodecInfo*>
(p_buffer);
// get the ImageCodecInfo
for the encoders
GDPCheck(gdp::GetImageEncoders(
num_encs, size_encs, p_encs
));
// run through the array of
ImageCodecInfo,
// looking for the CLSID of
the desired encoder
UINT idx;
for (idx = 0; idx < num_encs; ++idx)
{
if (WideString(p_encs[idx].MimeType) ==
mime_type)
{
enc_clsid = p_encs[idx].Clsid;
break;
}
}
// check
that we found the encoder for
// the specific MIME type
if (idx == num_encs)
{
throw EGDIPlusError(
"The specified encoder
is not installed."
);
}
}
__finally
{
// free the buffer
delete [] p_buffer;
}
}
void SaveBitmap(
gdp::Bitmap& bitmap,
WideString fname,
WideString mime_type,
gdp::EncoderParameters* p_save_options = NULL
)
{
// get the CLSID of the
encoder
CLSID enc_clsid;
GetEncoderCLSID(enc_clsid, mime_type);
// save the bitmap using the
encoder
// (see [8] for info on using
save_options)
GDPCheck(
bitmap.Save(fname.c_bstr(), &enc_clsid,
p_save_options)
);
} |
Saving bitmaps
After you’ve created and manipulated a GDI+ bitmap, you can
save the output to a file by using the
Bitmap::Save() method. The
Save() method is actually inherited from the
Image class, which allows you to
save the bitmap to any file-format for which GDI+ provides an encoder (BMPs,
GIFs, JPEGs, PNGs, and TIFFs). In order to save a bitmap to specific format,
however, you need to specify the
CLSID of the encoder; this is accomplished by using the
GetImageEncoders() GDI+ function.
Listing B provides utility functions for retrieving the
CLSID of an encoder given its MIME-type string, and for
saving a bitmap using that encoder.
Using GDI+ with TBitmap
objects
Now that you know how to use the GDI+
Bitmap
class, let’s switch gears and discuss how to use GDI+ to draw to a
TBitmap
object. In many ways, the
TBitmap class is easier to use
than GDI+’s
Bitmap class. On the other hand,
because the VCL relies on the standard GDI, rendering fancy lines, shapes, and
text and performing basic image-processing operations (namely, geometric
transformations) is much easier in GDI+.
Fortunately, as C++Builder
developers, we get the best of both worlds—you can easily instruct GDI+ to
render its output to a
TBitmap object.
Drawing to a TBitmap
using GDI+
The use GDI+ to draw to a
TBitmap
object, you simply use the
Graphics constructor that takes an
HDC
(handle to a device context) as one of its parameters. Typically, this
constructor is used to create a
Graphics object that
sends its output to the DC of a window; however, the DC doesn’t have to be a
window’s DC, it can also be a memory DC that’s associated with a
TBitmap
object (i.e.,
Bitmap->Canvas->Handle).
The following code snippet
demonstrates how to use GDI+ to render a gradient background and some text to
the
TBitmap object that’s held in a
TImage
(Image1);
the output of this code is depicted in Figure B:
// grab a reference to the
//
TBitmap that's held within
// Image1 and then resize it
Graphics::TBitmap& Bitmap =
*Image1->Picture->Bitmap;
Bitmap.PixelFormat = pf24bit;
Bitmap.Width = 256;
Bitmap.Height = 256;
// local scope
{
// create a Graphics object
// associated with the
//
TBitmap
gdp::Graphics bmp_graphics(
Bitmap.Canvas->Handle
);
GDPCheck(
bmp_graphics.
GetLastStatus()
);
// create a brush with a
//
horizontal red-to-blue
//
gradient
gdp::LinearGradientBrush
brush(
// gradient starting point
|
Figure B |
|

By creating a GDI+
Graphics
object that’s associated with the memory DC
of a
TBitmap object, you can use GDI+ to
render output to a
TBitmap
object. Here, the
TBitmap
is simply displayed via a
TImage
control. |
gdp::Point(0, 0),
//
gradient ending/restarting
// point
gdp::Point(Bitmap.Width, 0),
// red to start
gdp::Color(255, 0, 0),
// blue to end
gdp::Color(0, 0, 255)
);
GDPCheck(brush.GetLastStatus());
// fill the TBitmap using the
brush
bmp_graphics.FillRectangle(&brush,
0, 0, Bitmap.Width, Bitmap.Height);
// create a 25pt Tahoma font
gdp::Font const font(
L"Tahoma",
// font name
25, // font
size
gdp::FontStyleRegular //
attributes
);
GDPCheck(font.GetLastStatus());
// draw some rotated green text
// to the TBitmap
bmp_graphics.RotateTransform(
45.0f, gdp::MatrixOrderAppend
);
bmp_graphics.DrawString(
L"GDI+ on a TBitmap!",
-1,
&font, gdp::PointF(25, -30),
&gdp::SolidBrush(
gdp::Color(0, 255, 0))
);
}
// redraw the TBitmap
Image1->Refresh();
Note that I declared
bmp_graphics in a
local-scope sub-block so that the
Graphics object is destroyed before the
TBitmap
object is assigned to the
TImage. This isn’t strictly
required for this example because the
TImage class simply draws the
bitmap to the screen; it doesn’t draw to the bitmap. In general, however,
you need to make sure that all GDI+
Graphics objects associated with the
TBitmap’s
Canvas
are destroyed before calling a GDI routine that may alter the bitmap (see [7]
for more information on mixing GDI and GDI+ code).
Converting a TBitmap to
a GDI+ Bitmap
As I discussed earlier, a very useful feature in GDI+ is
the ability to save a bitmap to various formats (see
Listing B). Although the
TPicture class is not too far behind in this regard (with the proper
third-party libraries), it’s often more convenient to use GDI+.
In order to save a
TBitmap
to a specific image-file format using GDI+, you need to create a
Bitmap
object based on the
TBitmap object; here’s the code to
do that:
gdp::Bitmap* TBitmap2Bitmap(
Graphics::TBitmap& VCLBitmap
)
{
// use the GDI+ Bitmap constructor
// that takes a handle to a DDB
or
// DIB section bitmap and a handle
to
// the DDB or DIB section's
palette
gdp::Bitmap* p_bitmap =
new gdp::Bitmap(
// handle to the GDI bitmap
VCLBitmap.Handle,
// handle to the GDI palette
VCLBitmap.Palette
);
// validate the GDI+ bitmap
gdp::Status const res =
p_bitmap->GetLastStatus();
if (res != gdp::Ok)
{
delete p_bitmap;
throw EGDIPlusError(res);
}
// return a pointer the GDI+
bitmap
// (caller assumes ownership)
return p_bitmap;
}
After the
Bitmap object is created, you can
simply use the technique presented in Listing B
to save the
Bitmap to the desired format.
Conclusion
In this article I’ve demonstrated how to work with GDI+ Bitmaps and how to use GDI+ to draw to a TBitmap. The source code for this article is available
for download from
http://www.bcbjournal.org. Next
time, we’ll switch gears and examine GDI+ metafiles and GDI+ vector-based
drawing routines.

References
1. D. Marr and E. Hildreth, "Theory of edge detection,"
Proc. R. Soc. Lond. B, 207, 1980.
2. D. Chandler,
"Printing Bitmaps, Part I," C++Builder
Dev. Journal, 5 (1), 2001. [link]
3.
http://tinyurl.com/2v8zc
4.
http://tinyurl.com/222mb
5.
http://tinyurl.com/2zg9q
6.
http://tinyurl.com/3bxqv
7.
http://tinyurl.com/32bvo
8.
http://tinyurl.com/27z75