Printing bitmaps, part I
By Damon Chandler
“How do a I print a bitmap?” This question is almost as ubiquitous in the Borland forums as “How do I assign an event handler to a dynamically-created control?” Unfortunately, while the answer to the latter is simple, printing a bitmap is tricky business.
Originally, I had planned to cover the topic of printing bitmaps in a single article. The problem is that this subject requires knowledge of the different bitmap types supported by Windows. As such, I’ve decided to spread the topic of printing bitmaps over a series of articles. In this first installment, I’ll get you up to speed with the different bitmaps types you are likely to encounter when printing. This article will not actually get as far as printing bitmaps, but will lay the necessary foundation. Later in this series I’ll cover topics such as printing, banding, proofing, and Image Color Management (ICM).
Device-dependent bitmaps
In Windows there are three types of bitmaps: device-dependent bitmaps (DDBs), device-independent bitmaps (DIBs), and DIB section bitmaps. Let’s first examine the simplest case, the DDB.
A DDB is a true GDI graphic object. You can select a DDB into a (memory) device context, you can use the GetObject() function with a DDB, and there’s a dedicated type, HBITMAP, which is used to store a handle to a DDB. For this reason, you’ll often see the term “bitmap object” used to refer to a DDB.
DDBs are, of course, device dependent. Internally, a DDB is stored in a format known only to the device driver. (Note that this is not the case with monochrome DDBs.) Because the format of a (color) DDB is specific to a particular device, DDBs are also commonly referred to as “compatible bitmaps”.
To create a DDB, you use the CreateCompatibleBitmap() function. It is declared as follows:
HBITMAP
CreateCompatibleBitmap(hdc, nWidth, nHeight);
The nWidth and hHeight parameters specify the width and the height of the DDB, respectively. The hdc parameter is a handle to a device context; you use this handle to tell the CreateCompatibleBitmap() function the device with which you want the DDB to be compatible. In turn, the CreateCompatibleBitmap() function will instruct the corresponding device driver to create the DDB. Again, there’s no dogma as to how the device driver should create and store this DDB.
There is an advantage to a DDB’s device-dependence: because there’s no conversion involved, drawing a DDB to its compatible device is extremely fast. This is, however, the only advantage. Because a DDB’s format is proprietary, you can’t access a DDB’s pixels, nor can you reliably blit (copy) a DDB to another device. This is the main reason printing bitmaps is so problematic—before you can print, you need to convert your DDB into a universal format that all device drivers can understand. This universal format is known as a DIB, a device-independent bitmap that’s understood by any device context.
Device-independent bitmaps
Before we get into the specifics of DIBs, there are a few things that you should keep in mind. The first thing to note is that a DIB is not a true GDI graphic object. You can’t select a DIB into a device context, nor are there any functions for creating a DIB from scratch. The second thing to note is that there are no functions for drawing to a DIB. Remember, you can’t select a DIB into a device context, so you can’t use any GDI drawing function to draw anything to a DIB. There are, however, a couple of functions that you can use to render a DIB to a device. Namely, you can use the StretchDIBits() or the SetDIBitsToDevice() function to transfer a DIB to a device.
A universal format
I have established that a DIB is not a GDI graphic object. What, then, is a DIB? Simply put, it’s a composite entity that’s composed of three parts: a header, an optional color table, and an array of pixels. While this format this not truly universal, it is well known. This layout is depicted in Figure A.
Figure A: A header, a color table, and an array of pixels define a DIB.
Note from Figure A that the image appears upside-down. As it turns out, the pixels of a DIB are often stored in this fashion. This variety, which is known as a “bottom-up” DIB, is a remnant from the OS/2 days. Also, the pixels of a DIB don’t always immediately follow the DIB’s color table. The pixels can actually be stored in a separate block of memory. This is not the case with the header and the color table; the color table, if present, must always come right after the header.
The BITMAPINFO structure
As indicated in Figure A, a DIB’s header and color table are stored together in a special structure called BITMAPINFO. Here’s the declaration of this structure:
typedef
struct tagBITMAPINFO {
BITMAPINFOHEADER bmiHeader;
RGBQUAD bmiColors[1];
} BITMAPINFO, FAR *LPBITMAPINFO, *PBITMAPINFO;
The BITMAPINFO structure contains two data members: a BITMAPINFOHEADER structure, and a variable-length array of RGBQUADs. As you’ve probably guessed, the first data member holds the DIB’s header, and the array of RGBQUADs holds the DIB’s color table.
The header
The header contains vital information about a DIB, such as it’s width, height, and color-depth. Indeed, without this information, there would be no way to decode the DIB’s pixels. There are actually three structures that can be used to store a DIB’s header: BITMAPINFOHEADER, BITMAPV4HEADER, and BITMAPV5HEADER. Because the latter two are used only with ICM, in this article I’ll focus strictly on the BITMAPINFOHEADER structure. Here’s what it looks like:
typedef
struct tagBITMAPINFOHEADER {
DWORD biSize;
LONG biWidth;
LONG biHeight;
WORD biPlanes;
WORD biBitCount;
DWORD biCompression;
DWORD biSizeImage;
LONG biXPelsPerMeter;
LONG biYPelsPerMeter;
DWORD biClrUsed;
DWORD biClrImportant;
} BITMAPINFOHEADER, FAR *LPBITMAPINFOHEADER,
*PBITMAPINFOHEADER;
Don’t be intimidated by the sheer number of data members in this structure. It turns out that most of these are usually set to zero.
The biSize data member specifies the size of the header, and is usually set to sizeof(BITMAPINFOHEADER). Sometimes, however, additional information is stored immediately following the header, in which case, the biSize data member is increased accordingly. (In fact, this is the notion behind the BITMAPV4HEADER, and BITMAPV5HEADER structures—they store additional information at the end of the stock BITMAPINFOHEADER-type header.)
The biWidth and biHeight data members specify the width and height of the DIB (in pixels), respectively. The biPlanes data member specifies the number of color planes that the DIB uses; this data member must always be set to 1. The biBitCount data member specifies the number of bits that are used for each pixel: 1, 4, 8, 16, 24, or 32.
The biCompression data member specifies the type of compression that’s used, if any. Usually, this data member is set to BI_RGB (0), indicating that the DIB is uncompressed. Alternatively, you can set biCompression to BI_BITFIELDS, indicating that the DIB is uncompressed and the first three entries of its data members contain DWORD-type values that are used as color masks. This identifier applies only to 16- or 32-bit DIBs. You can also biCompression to BI_RLE4 or BI_RLE8 to specify a run-length encoded 4-bit or 8-bit DIB, respectively. Or, you can specify this data member as BI_JPEG or BI_PNG if the DIB is compressed in the same fashion as a JPEG or PNG image, respectively.
The biSizeImage data member specifies the total number of bytes that are required for the DIB’s pixels. For uncompressed DIBs, this data member is usually set to zero.
The biXPelsPerMeter and biYPelsPerMeter data members specify the number of pixels per meter in the horizontal and vertical directions, respectively. These data members are usually set to zero.
The biClrUsed data member specifies the number of entries that the DIB’s color table contains. Typically, this data member is set to zero, indicating that the color table contains the maximum number of colors for the DIB’s color depth. For example, the color table of an 8-bit DIB can contain up to 256 entries. Likewise, the color table of a 4-bit DIB can contain up to 16 entries. As we’ll discuss shortly, a color table is required for 1-, 4-, or 8-bit DIBs, but is optional for 16-, 24-, and 32-bit DIBs.
The biClrImportant data member specifies the number of colors in the DIB’s color table that are “important”. Usually, this data member is set to zero, indicating that all of the DIB’s color table entries are important.
The color table
DIBs that represent each pixel using 1, 4, or 8 bits, must have a corresponding color table. For these types of DIBs, each pixel refers not to a literal RGB value, but instead to a color-table index value:
pixel_value
= pixels[x][y]
color = color_table[index]
There are actually two color table varieties—those that are made up of an array of RGBQUADs, and those that are composed an array of WORDs. In the latter case, each color table entry (i.e., WORD) refers to an index into a logical palette. Naturally, this type of color table is rare because it obviates the DIB’s device independence. The RGBQUAD structure, on the other hand, conveys a literal RGB value. Here is that declaration:
typedef
struct tagRGBQUAD {
BYTE rgbBlue;
BYTE rgbGreen;
BYTE rgbRed;
BYTE rgbReserved;
} RGBQUAD, FAR* LPRGBQUAD;
The rgbBlue, rgbGreen, and rgbRed data members specify the blue, green, and red intensities, respectively. (This odd ordering is another hangover from OS/2.) The rgbReserved data member is usually set to zero, but if you want to store extra information here (a translucency percentage, for example), you’re free to do so.
As I pointed out earlier, not all DIBs have a color table. A color table is required for 1-, 4-, and 8-bit DIBs, but is optional for DIB of greater color-depths. You can tell whether a 16-, 24-, or 32-bit DIB uses a color table by examining the biClrUsed data member of the DIB’s header. If this data member is zero, the DIB doesn’t have a color table. This isn’t case for 1-, 4-, and 8-bit DIBs; when the biClrUsed data member zero for these types, you should assume that the color table contains the maximum amount of entries allowed for the color-depth. You can compute this value like so (bmi is the DIB’s header):
const
short num_entries = 1 <<
bmi.biClrUsed;
The pixels
A DIB’s pixels are used to define the actual image. How these pixels are stored depends on the number of bits that are used to represent each pixel (i.e., the DIB’s color-depth). For example, a 1-bit DIB represents each pixel using only a single bit. A 4-bit DIB uses four bits to represent each pixel.
An 8-bit DIB represents each pixel using a full byte. This type of DIB is one of the easiest varieties to work with because you don’t need to use bit-shifting or hexadecimal masks to decode each pixel. Just remember, each pixel conveys an index into the DIB’s color table, not an actual color.
16-bit DIBs are often cumbersome to work with because each pixel value is packed into a WORD. To decode the RGB value from each WORD you need to use three hexadecimal-masks: 0xF800 for red, 0x07E0 for green, and 0x001F for blue. These “bit field” masks tell you how many bits are allocated for each color channel (5 bits; the most significant bit is ignored) and how these bits are packed (blue, green, and then red). There is, however, a catch: if the biCompression data member of the DIB’s header indicates BI_BITFIELDS, you need to grab the first three entries from the color table and use them as the masks.
The 24-bit variety is perhaps the easiest variety to work with because you can treat each pixel as an RGBTRIPLE. This structure is declared like this:
typedef
struct tagRGBTRIPLE {
BYTE rgbtBlue;
BYTE rgbtGreen;
BYTE rgbtRed;
} RGBTRIPLE;
In this case, because each pixel refers to an actual RGB value, there’s no need for a color table. Also note that the bits for each color channel are packed as indicated in the RGBTRIPLE structure: eight for blue, then eight for green, and then eight for red (in that order).
For the 32-bit case, each pixel is often stored as an RGBQUAD, making this variety appealing as well. However, as with the 16-bit case, there’s a catch. If the biCompression data member of the DIB’s header indicates BI_BITFIELDS, you should use the first three entries of the DIB’s color table to determine how many bits are allocated to each color channel.
DIB section bitmaps
A DIB section bitmap (often simply referred to as a “DIB section”) is a hybrid between a DDB and a DIB. DIB sections contain both DDB and DIB components, as indicated by its corresponding structure, DIBSECTION. Here is it’s declaration:
typedef
struct tagDIBSECTION {
BITMAP dsBm;
BITMAPINFOHEADER dsBmih;
DWORD dsBitfields[3];
HANDLE dshSection;
DWORD dsOffset;
} DIBSECTION, FAR *LPDIBSECTION, *PDIBSECTION;
The dsBm data member is a BITMAP structure that describes the DIB section’s DDB component. The dsBmih data member is a BITMAPINFOHEADER structure that describes the DIB section’s DIB component. For 16-bit and 32-bit DIB sections, there’s no need to extract the DWORD-type masks from the color table—this information is conveniently presented via the dsBitfields data member. The dshSection data member specifies a handle to an optional memory-mapped file that can be used to store the DIB section’s pixels. Accordingly, the dsOffset data member specifies the location of these pixels within the memory-mapped file.
From its DDB component, a DIB section bitmap inherits the properties of a true GDI graphic object—you can select a DIB section into a memory DC and then use your favorite GDI function to draw to it (or to draw the DIB section to another DC).
From its DIB component, a DIB section bitmap inherits a bit of device-independence. That is, the color-depth of a DIB section bitmap is not limited to that of a specific device. Moreover, as with DIBs, DIB sections offer direct pixel access. This is the reason why the TBitmap class from C++Builder 3 and later offers the ScanLine property. Internally, TBitmap relies on DIB sections. In contrast, the TBitmap class from C++Builder 1 is based only on DDBs, and thus, there’s no ScanLine property in this version.
Creating a DIB from a DDB or DIB section
As it turns out, the VCL (namely, the graphics.pas unit) simplifies the process of creating a DIB from a DDB or DIB section bitmap. Specifically, you use the GetDIBSizes() and GetDIB() functions. Here’s what the former looks like:
void
__fastcall GetDIBSizes(HBITMAP
Bitmap, int &InfoHeaderSize,int &ImageSize);
The Bitmap parameter, of type HBITMAP, specifies a handle to the DDB or DIB section bitmap whose required sizes (when converted to a DIB) you’re interested in retrieving. This information is returned via the InfoHeaderSize and ImageSize parameters. The InfoHeaderSize parameter refers to an int-type variable that receives the size (in bytes) of the DIB’s header and color table. The ImageSize parameter refers to another int-type variable that receives the size (in bytes) required to hold the DIB’s pixels. So, you can use the GetDIBSizes() function to determine how much memory to allocate for the DIB.
Once you’ve called the GetDIBSizes() function and allocated a sufficiently large chunk of memory, you can then use the GetDIB() function to actually obtain the DIB. Here is the declaration for GetDIB():
bool
__fastcall GetDIB(HBITMAP Bitmap, HPALETTE Palette,
void *BitmapInfo, void *Bits);
As with that of the GetDIBSizes() function, the Bitmap parameter specifies a handle to the DDB or DIB section from which you’re interested in creating a DIB. The Palette parameter specifies a handle to the DDB’s or the DIB section’s logical palette, if required. The BitmapInfo parameter is a pointer to a previously allocated block of memory that receives the DIB’s header and color table. Likewise, the Bits parameter is a pointer to a previously allocated block of memory that receives a copy of the DDB’s or the DIB section’s pixels. Again, to determine how much memory to allocate for these data, you use the GetDIBSizes() function. Here’s a simple example of how to use these functions:
//
use the GetDIBSizes() function to gauge the size of the DIB's parts
size_t header_ct_size = 0;
size_t image_size = 0;
GetDIBSizes(Bitmap->Handle, header_ct_size, image_size
);
// compute the total size
const size_t total_size = header_ct_size + image_size;
if (total_size > 0)
{
// allocate memory for the header
unsigned char* pHeaderAndCT =
new unsigned
char[header_ct_size];
// allocate memory for the pixels
unsigned char* pBits =
new unsigned
char[image_size];
try
{
// use the GetDIB()
function to get the header,
color table,
// and a copy of
Bitmap’s pixels
bool got_dib =
GetDIB(Bitmap->Handle, Bitmap->Palette,
pHeaderAndCT,
pBits);
if (got_dib)
{
// grab a
pointer to the BITMAPINFO structure
LPBITMAPINFO
lpbi = reinterpret_cast<LPBITMAPINFO>(pHeaderAndCT);
// grab a
pointer to the
color table
LPRGBQUAD
pColorTable = lpbi->bmiColors;
// work
with the DIB...
}
}
catch (...)
{
// clean up
delete []
pHeaderAndCT;
delete [] pBits;
throw;
}
// clean up
delete [] pHeaderAndCT;
delete [] pBits
}
Remember, a DIB is not a GDI graphic object, so the process of “creation” is actually a simple initialization. That is, the GetDIB() function calls the GDI GetDIBits() function, which in turn, instructs the device driver to convert the DDB from its internal format to the universal format (i.e., a DIB).
Conclusion
Well we didn’t do any printing yet, but we got the preliminaries out of the way. The take-home message is that the format of a DIB is well known, and so you can safely transfer a DIB from one device to another. From the previous code snippet, you now know how to create a DIB from a DDB or a DIB section bitmap. Next time, I’ll show you how to send this DIB to the printer.