Board index » cppbuilder » Sending a bitmap to the printer - nothing printed.

Sending a bitmap to the printer - nothing printed.


2004-10-06 11:36:30 AM
cppbuilder34
I;ve been working on a report (I ditched QReport due to it's complexity to
use and limitations) and I've allocated a Graphics::TBitmap which is the
same size of the Printer's Canvas to render the report on it. This bitmap is
also serving as a base for a print preview by performing a stretch draw on
the preview form.
When I'm satisfied with the report, I send the bitmap to the printer with
this code:
.
.
.
RepPrinter->BeginDoc();
RepPrinter->Canvas->Draw(0, 0, PreviewBitmap);
RepPrinter->EndDoc();
However the printer prints nothing at all. I have no choice but to repeat
all the rendering commands on the RepPrinter's canvas, which is tedious and
cumbersome.
Why can't I draw a bitmap directly to a Printer's canvas? T.I.A.
Regards.
 
 

Re:Sending a bitmap to the printer - nothing printed.

"newsreader" < XXXX@XXXXX.COM >wrote:
Quote

[...] Why can't I draw a bitmap directly to a Printer's canvas?
This seems to be a common problem with TPrinter. I say 'seems'
because I've never seen it myself but I've seen many who have
complained.
First let me say that there's a problem with your over-all
approach because the screen dpi and the dpi for the variouse
printers will never match up and the final printed output won't look anything like the preview.
The first time I did something like this, I designed my
functions to take a pointer to a TCanvas. Then I set the font
sizes for the variouse sections [header, footer ect.] to
something that I knew would be over-sized just before I
entered a loop that adjusted the sizes down until everything
fit. The result was good and it eliminates the need to send a
bitmap to the printer but the process was slow - even when
optimized for speed.
That sounds like a workable solution for you that you should
be able to implement with your existing code.
The 'correct' way to do this is to use a DIB and although the
writer of TExcellentPrinter will tell you that you need low
level code to ensure that it works 100% of the time, I have
never known it not to work as expected.
The following is from Damon Chandler [TeamB]. It's rather long
but it explains in detail what you need to work with DIB's and
it was the source of the information that we used to code our
print-preview (sorry - proprietary code). The only thing that
I should add to Damon's info is that we found that you should
always use Lock on the source canvas just prior to using
StrechDIB and the Unlock it after wards. Good luck.
~ JD
I started wrapping the lines and stopped because you'll just
want to paste the anyway and I know that I'd prefer that they
not be there in that case.
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.
[IMG00004.gif]
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 biXPel{*word*237}eter;
LONG biYPel{*word*237}eter;
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 biXPel{*word*237}eter and biYPel{*word*237}eter 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.
Printing bitmaps II: sending a DIB to the printer
by Damon Chandler
Last time, we discussed how to generate a device-independent representation of a bitmap object—that is, how to create a device-independent bitmap (DIB). We also talked a little bit about the format of a DIB and some advantages and disadvantages of a DIB’s device independence. Now that the conceptual groundwork is set, let’s actually do some printing.
Transferring a DIB—the StretchDIBits() function
As I mentioned last time, the main advantage of a DIB’s device independence is that the format of a DIB is well known. Why is this useful? Because the format is universal, you can safely transfer a DIB among different devices. The greatest disadvantage of using a DIB, however, is that you can’t select a DIB into a memory device context, and so you can’t use the BitBlt() or StretchBlt() functions to draw to a DIB, nor can you use these functions to draw a DIB to a device. However, there is an alternative method you can use to render a DIB to a device: the StretchDIBits() function can transfer a DIB to a graphics device. This function is similar to the StretchBlt() function, except whereas the latter accepts a handle to a (memory) device context for the source image, the former accepts a pointer to a BITMAPINFO structure. The following is the declaration of the StretchDIBits() function:
int StretchDIBits(
// handle to the target DC
HDC hdc,
// destination rectangle
int XDest,
int YDest,
int nDestWidth,
int nDestHeight,
// source rectangle
int XSrc,
int YSrc,
int nSrcWidth,
int nSrcHeight,
// pointer to the DIB's pixels
CONST VOID* lpBits,
// pointer to the DIB's BITMAPINFO
CONST BITMAPINFO* lpBitsInfo,
// color-table format identifier
UINT iUsage,
// ternary ROP code
DWORD dwRop
);
The hdc parameter specifies the destination device context—you use this parameter to tell the StretchDIBits() function which device you want the DIB transferred to.
The XDest, YDest, nDestWidth, and nDestHeight parameters specify, in logical coordinates, a bounding rectangle to which the DIB should stretch to fit. Likewise, the XSrc, YSrc, nSrcWidth, and nSrcHeight parameters specify, in pixels, the rectangular portion of the DIB that you want rendered.
The lpBits parameter specifies a pointer to the DIB’s pixels and the lpBitsInfo parameter specifies the address of the DIB’s corresponding BITMAPINFO structure.
The iUsage parameter specifies the type of color table that the DIB contains. Recall from last time that a DIB’s color table can consist of either RGBQUAD-type entries or WORD-type entries. If the DIB’s color table contains RGBQUADs, you specify iUsage as DIB_RGB_COLORS. If the DIB’s color table contains WORDs, you specify iUsage as DIB_PAL_COLORS. And, if the DIB has no color table, you can specify either identifier.
The dwRop parameter specifies an ROP3 code. In most cases, you’ll want to set dwRop to SRCCOPY; this tells the StretchDIBits() function to simply copy the DIB to the target device, ignoring the colors of the destination and brush. (Note that many printers support only the SRCCOPY mode.)
If the StretchDIBits() function is successful, it returns a value that indicates the number of scan lines that were transferred to the device. If the function fails, it returns GDI_ERROR.
Using the StretchDIBits() function
Before we actually use the StretchDIBits() function to transfer a DIB to a printer, let’s look at an example that demonstrates how to draw a DIB to the screen. The first step, of course, is to actually create the DIB. I’ll repeat the code from last time for convenience, here, wrapped in a function called CreateDIB():
LPBITMAPINFO CreateDIB(
IN const Graphics::TBitmap& Bitmap,
OUT unsigned char*& pBits
)
{
//
// use the GetDIBSizes() function to
// gauge the size of the DIB's parts
//
int header_ct_size = 0;
int img_size = 0;
GetDIBSizes(
const_cast<Graphics::TBitmap&>
(Bitmap).Handle,
header_ct_size, img_size
);
// compute the total size
const size_t total_size =
header_ct_size + img_size;
if (total_size>0)
{
//
// memory for the header
// and the color table
//
unsigned char* pHeaderAndCT =
new unsigned char[header_ct_size];
// memory for the pixels
pBits = new unsigned char[img_size];
try
{
//
// use the GetDIB() function
// to get the header, color table
// and a copy of Bitmap's pixels
//
bool got_dib =
GetDIB(
const_cast<Graphics::TBitmap&>
(Bitmap).Handle,
const_cast<Graphics::TBitmap&>
(Bitmap).Palette,
pHeaderAndCT, pBits
);
if (got_dib)
{
return reinterpret_cast
<LPBITMAPINFO>(pHeaderAndCT);
}
}
catch (...)
{
// clean up
delete [] pHeaderAndCT;
delete [] pBits;
throw;
}
}
return NULL;
}
Drawing a DIB to a display device context
Contrary to its name, the StretchDIBits() function isn’t limited to rendering a DIB in only a stretched fashion. If you specify a destination rectangle that’s identical to the source rectangle (and the target DC has a 1:1 mapping mode), then the DIB will be rendered in its original size. Let’s look at a bare-bones example. Here, we’ll create and draw a DIB in response to the click of a button. Assume that Image1 contains the bitmap that‘s to be drawn. Here’s the code:
void __fastcall TForm1::Button1Click(
TObject *Sender)
{
Graphics::TBitmap& Bitmap =
*Image1->Picture->Bitmap;
unsigned char* pBits = NULL;
const LPBITMAPINFO pDIB =
CreateDIB(Bitmap, pBits);
if (pDIB && pBits)
{
try
{
const int num_lines =
StretchDIBits(
// destination DC
Canvas->Handle,
// destination rectangle
0, 0,
pDIB->bmiHeader.biWidth,
abs(pDIB->bmiHeader.biHeight),
// source rectangle
0, 0,
pDIB->bmiHeader.biWidth,
abs(pDIB->bmiHeader.biHeight),
// source image
pBits, pDIB,
// color table type
DIB_RGB_COLORS,
// ROP3 code
SRCCOPY
);
if (num_lines !=
abs(pDIB->bmiHeader.biHeight))
{
throw
Exception("!StretchDIBits");
}
}
catch (...)
{
delete [] pDIB;
delete [] pBits;
throw;
}
delete [] pDIB;
delete [] pBits;
}
}
Note that we passed DIB_RGB_COLORS as the iUsage parameter to the StretchDIBits() function. This specification is indeed valid in our case because the GetDIB() function (which we called from within the CreateDIB() function) always creates a DIB with an RGBQUAD-type color table entry.
If you’ve used the TCanvas::CopyRect() method before, you’ve probably noticed that the StretchDIBits() function isn’t that much different. In contrast to the StretchDIBits() function, however, the CopyRect() method takes care of the palette-related issues when needed (i.e., on palette-based displays). So, before we move on to printing, let’s modify this example to account for palettized displays. We do this by using a combination of the GetDeviceCaps(), SelectPalette(), RealizePalette() functions, and by using the TBitmap::Palette property, like so:
void __fastcall TForm1::Button1Click(
TObject *Sender)
{
Graphics::TBitmap& Bitmap =
*Image1->Picture->Bitmap;
unsigned char* pBits = NULL;
const LPBITMAPINFO pDIB =
CreateDIB(Bitmap, pBits);
if (pDIB && pBits)
{
try
{
// is the display palette-based?
bool uses_palette =
GetDeviceCaps(
Canvas->Handle, RASTERCAPS
) & RC_PALETTE;
HPALETTE hOldPal;
if (uses_palette)
{
//
// select the bitmap's palette
// into the target DC
//
hOldPal =
static_cast<HPALETTE>(
SelectPalette(
Canvas->Handle,
Bitmap.Palette,
FALSE
)
);
// update the system palette
RealizePalette(Canvas->Handle);
}
const int num_lines =
StretchDIBits(
// destination DC
Canvas->Handle,
// destination rectangle
0, 0,
pDIB->bmiHeader.biWidth,
abs(pDIB->bmiHeader.biHeight),
// source rectangle
0, 0,
pDIB->bmiHeader.biWidth,
abs(pDIB->bmiHeader.biHeight),
// source image
pBits, pDIB,
// color table type
DIB_RGB_COLORS,
// ROP3 code
SRCCOPY
);
if (uses_palette)
{
// clean up
SelectPalette(
Canvas->Handle, hOldPal, TRUE
);
}
if (num_lines !=
abs(pDIB->bmiHeader.biHeight))
{
throw
Exception("!StretchDIBits");
}
}
catch (...)
{
delete [] pDIB;
delete [] pBits;
throw;
}
delete [] pDIB;
delete [] pBits;
}
}
The GetDeviceCaps() function is used to query various capabilities of a device. Here we specify the RASTERCAPS identifier and look for the RC_PALETTE bit. The presence of this bit indicates that the specified device is palette-based, in which case we use a combination of the SelectPalette() and RealizePalette() functions to update the display’s system palette. Note that these latter two functions are usually (and more appropriately) used in response to the WM_QUERYNEWPALETTE message. As we’ll examine next, in the case that a printer is palette-based, we’ll use the SelectPalette() and RealizePalette() functions in a similar fashion.
Drawing a DIB to a printer device context
Okay, so we’ve seen how to use the StretchDIBits() function to render a DIB to a display DC. How is this function used to print a DIB? Well, it’s the same deal, just a different target DC. This time, instead of passing a handle to our form’s device context as the StretchDIBits() function’s hdc parameter, we pass a handle to a device context that refers to a specific printer. For example, by using the TPrinter class, this can be done like so:
#include <printers.hpp>
void __fastcall TForm1::Button1Click(
TObject *Sender)
{
Graphics::TBitmap& Bitmap =
*Image1->Picture->Bitmap;
unsigned char* pBits = NULL;
const LPBITMAPINFO pDIB =
CreateDIB(Bitmap, pBits);
if (pDIB && pBits)
{
// start the print job
Printer()->BeginDoc();
try
{
// is the printer palette-based?
bool uses_palette =
GetDeviceCaps(
Printer()->Canvas->Handle,
RASTERCAPS
) & RC_PALETTE;
HPALETTE hOldPal;
if (uses_palette)
{
//
// select the bitmap's palette
// into the target DC
//
hOldPal =
static_cast<HPALETTE>(
SelectPalette(
Printer()->Canvas->Handle,
Bitmap.Palette,
FALSE
)
);
// update the printer's palette
RealizePalette(Canvas->Handle);
}
const int num_lines =
StretchDIBits(
// destination DC
Printer()->Canvas->Handle,
// destination rectangle
0, 0,
pDIB->bmiHeader.biWidth,
abs(pDIB->bmiHeader.biHeight),
// source rectangle
0, 0,
pDIB->bmiHeader.biWidth,
abs(pDIB->bmiHeader.biHeight),
// source image
pBits, pDIB,
// color table type
DIB_RGB_COLORS,
// ROP3 code
SRCCOPY
);
if (uses_palette)
{
// clean up
SelectPalette(
Printer()->Canvas->Handle,
hOldPal, TRUE
);
}
// end the print job
Printer()->EndDoc();
if (num_lines !=
abs(pDIB->bmiHeader.biHeight))
{
throw
Exception("!StretchDIBits");
}
}
catch (...)
{
Printer()->EndDoc();
delete [] pDIB;
delete [] pBits;
throw;
}
delete [] pDIB;
delete [] pBits;
}
}
Note that this code is virtually identical to that of the previous code snippets. The only difference is that here we use the TPrinter::BeginDoc() and EndDoc() methods, and we specify Printer()->Canvas->Handle instead of Canvas->Handle (i.e., Form1->Canvas->Handle ). In fact, that’s pretty much all there is to printing a bitmap. This approach will succeed on most printers. If you do execute this code, however, you’ll likely get a very small print depending on the resolution of your printer and the dimensions of your bitmap. The reason is that we specified the destination rectangle (i.e., the XDest, YDest, nDestWidth, and nDestHeight parameters) in terms of the bitmap’s actual dimensions. Usually, you’ll want the printed bitmap to be of a certain number of inches or to optimally fit within a page.
To determine the correct scaling factor for the destination rectangle, you can use the TPrinter::PageWidth and PageHeight properties; or you can use the GetDeviceCaps() function. Or you can use a combination of these methods. For example, to stretch the bitmap to the size of the paper (i.e., to occupy the entire printable area), you use the PageWidth and PageHeight properties as follows:
const SIZE SPrint = {
Printer()->PageWidth,
Printer()->PageHeight
};
const int num_lines =
StretchDIBits()(
// destination DC
Printer()->Canvas->Handle,
// destination rectangle
0, 0,
SPrint.cx, SPrint.cy,
// source rectangle
0, 0,
pDIB->bmiHeader.biWidth,
abs(pDIB->bmiHeader.biHeight),
// source image
pBits, pDIB,
// color table type
DIB_RGB_COLORS,
// ROP3 code
SRCCOPY
);
Similarly, if you want the printed bitmap as large as possible (on a single page) while preserving the bitmap’s aspect ratio, you use a combination of PageWidth, PageHeight, and GetDeviceCaps(), like so:
// pixels per inch horizontally
const int pix_inchX =
GetDeviceCaps(
Printer()->Canvas->Handle,
LOGPIXELSX
);
// pixels per inch vertically
const int pix_inchY =
GetDeviceCaps(
Printer()->Canvas->Handle,
LOGPIXELSY
);
SIZE SPrint;
SPrint.cx = Printer()->PageWidth;
SPrint.cy =
SPrint.cx * pix_inchY *
abs(pDIB->bmiHeader.biHeight) /
(pix_inchX *pDIB->bmiHeader.biWidth);
const int num_lines =
StretchDIBits()(
// destination DC
Printer()->Canvas->Handle,
// destination rectangle
0, 0,
SPrint.cx, SPrint.cy,
// source rectangle
0, 0,
pDIB->bmiHeader.biWidth,
abs(pDIB->bmiHeader.biHeight),
// source image
pBits, pDIB,
// color table type
DIB_RGB_COLORS,
// ROP3 code
SRCCOPY
);
Paperless proofing
Unless you’re extremely lucky or you have a knack for getting things right the first time, it’ll probably take a few tries to get your printing code fine-tuned. Although you can proof some aspects by simply sending the output (of StretchDIBits() ) to your form’s DC instead of to your printer’s DC, it’s a bit difficult to verify positioning and sizing related code without seeing a printed page. After wasting a few stacks of paper, I finally realized that there’s a simple way to gauge (at least approximately) what the printed output will look like—both in terms of position and dimensions—without actually producing a hard copy.
If you go to this URL: www.cs.wisc.edu/~ghost/ , you can download Ghostscript and GSview. Together, these programs allow you to view Postscript files on screen. This way, you can test your printing code by first displaying a Print common dialog box, and then checking the “Print to file?option. When you confirm the Print dialog box, you’ll be asked to specify a destination file name; your “printed?output will then be directed to the file that you specify. After you’ve printed to the file, you can open it in GSView to see what the output looks like. Of course, for this to work, you’ll need a Postscript printer driver. If you already have a Postscript printer then you’re good to go. Otherwise, pop in your Windows CD-ROM and install a Postscript printer driver (you don’t need an actual printer for this to work). I’ve found the Tektronix Phaser 540 driver to work particularly well.
Conclusion
Is the StretchDIBits() function the only way to print a bitmap? Actually, no. I pointed out last time that there are two functions for transferring a DIB to a device: StretchDIBits() and SetDIBitsToDevice(). The SetDIBitsToDevice() function is similar to StretchDIBits() except the former doesn’t allow stretching. Moreover, the SetDIBitsToDevice() function is supported by fewer devices, so in many cases, the work is simply punted to the StretchDIBits() function.
Next time, I’ll discuss how to make the StretchDIBits -based approach a bit more robust by adding support for a technique called banding. I’ll also show you how to use ICM (Image Color Management) technology to ensure the fidelity of a printed image’s colors.
Printing Bitmaps III: Wrapping Things Up
by Damon Chandler
In the first article of this series, I presented a conceptual overview of three types of bitmaps: DDBs, DIBs, and DIB section bitmaps. Last month, we examined how to use the StretchDIBits() function to send a DIB to the printer. This time, we’ll wrap all of the printing code into a reusable class, and I’ll discuss a technique called "banding" which can enhance the reliability of the StretchDIBits() function.
The TBitmapPrinter class
In the past two articles, we’ve covered a fair bit of conceptual information and code. Now it’s time to consolidate these ideas and procedures into a class; we’ll call this class TBitmapPrinter . Before we get our hands dirty with the implementation, however, let me present a short code snippet that demonstrates what this class should be able to do once it’s complete:
#include <printers.hpp>
#include <memory>
// create a TBitmapPrinter object
std::auto_ptr<TBitmapPrinter>BitmapPrinter(new TBitmapPrinter());
// (1) set the Bitmap
BitmapPrinter->Bitmap = Image1->Picture->Bitmap;
// (2) set the target rectangle
BitmapPrinter->PrintRect = Rect(0, 0,
Printer()->PageWidth,
Printer()->PageHeight);
// (3) print the image
Printer()->BeginDoc();
BitmapPrinter->PrintBitmap(Printer()->Canvas->Handle);
Printer()->EndDoc();
There are three main steps in this code snippet: (1) specifying the bitmap to be printed; (2) specifying the location and dimensions on the page where this bitmap should be printed; and (3) specifying the device context of the device with which this bitmap should be printed.
We’ve actually discussed all of the code necessary to accomplishing these three steps, so we can move through the implementation fairly quickly. Listing A shows the declaration of the TBitmapPrinter class. Here we’ll focus on implementing the TBitmapPrinter::DoSetBitmap() and DoInternalPrintBitmap() member functions.
Setting the Bitmap
From Listing A, you can see that assigning a TBitmap object to the TBitmapPrinter::Bitmap property invokes the DoSetBitmap() member function. The DoSetBitmap() member function will do two things: it will "create" a DIB from the specified TBitmap object, storing the results in the private pDIB_ and pBits_ members; and, it will create a copy of the Bitmap’s palette, storing the result in the private hPal_ member. Here’s the code:
void __fastcall
TBitmapPrinter::DoSetBitmap(const Graphics::TBitmap* Bitmap)
{
DeleteObject(hPal_);
hPal_ = NULL;
delete [] pBits_;
pBits_ = NULL;
delete [] reinterpret_cast<unsigned char*>(pDIB_);
pDIB_ = NULL;
if (Bitmap)
{
pDIB_ =
DoCreateDIB(*Bitmap, pBits_);
hPal_ = CopyPalette(const_cast<Graphics::TBitmap*>
(Bitmap)->Palette);
}
}
Simple enough, right? Actually, most of the work is delegated to the DoCreateDIB() member function, which is identical to the CreateDIB() function that was presented in the last article. (I won’t repeat this function here, but you can see the implementation in the source code. If you don’t have last month’s issue, see www.bridgespublishing.com.) The CopyPalette() function is a VCL utility function that’s declared in graphics.hpp. As its name suggest, this function simply creates a copy of a logical palette.
Printing the Bitmap
Now that we have a DIB (pDIB_ and pBits_), let’s implement the code to print it. From the first code snippet, you can see that this uses the TBitmapPrinter::PrintBitmap() member function. This member function simply calls the DoPrintBitmap() member function, which is defined as follows:
void __fastcall TBitmapPrinter::
DoPrintBitmap(HDC hPrnDC)
{
if (!pDIB_)
{
throw EBitmapPrinterError("No bitmap defined");
}
if (!hPrnDC)
{
throw EBitmapPrinterError("Invalid printer DC");
}
// compute the rectangle to print to
DoCalculatePrintRect(hPrnDC);
// print the bitmap
DoInternalPrintBitmap(hPrnDC);
}
As with the DoSetBitmap() member function, the DoPrintBitmap() member function punts most of its work elsewhere. The DoCalculatePrintRect() member function simply computes the dimensions of the rectangle to print to—by using the GetDeviceCaps() function—and then stores the result in the RPrint_ member. The DoInternalPrintBitmap() member function is the workhorse of the class. It’s from within this member function that the DIB is printed via the StretchDIBits() function.
The DoInternalPrintBitmap function, version 1
We discussed the specifics of the StretchDIBits() function last time, so you should already have an idea of how to define the DoInternalPrintBitmap() member function. Here’s what one version of the function might look like:
void __fastcall TBitmapPrinter::
DoInternalPrintBitmap(HDC hPrnDC)
{
if (IsRectEmpty(&RPrint_))
{
throw EBitmapPrinterError("Invalid target rectangle");
}
if (pDIB_ && pBits_)
{
//
// extract the width and the
// height from the DIB's header
//
const int dib_width =
pDIB_->bmiHeader.biWidth;
const int dib_height =
pDIB_->bmiHeader.biHeight;
// support palettized printers
bool palettized = GetDeviceCaps(hPrnDC, RASTERCAPS) & RC_PALETTE;
if (palettized)
{
hPal_ = SelectPalette(hPrnDC, hPal_, FALSE);
RealizePalette(hPrnDC);
}
//
// set the stretching mode
// to preserve colors
//
SetStretchBltMode(hPrnDC, COLORONCOLOR);
// render the DIB
bool result = GDI_ERROR !=
StretchDIBits(
// draw to the printer's DC
hPrnDC,
// dest rect
RPrint_.left, RPrint_.top,
RPrint_.right - RPrint_.left,
RPrint_.bottom - RPrint_.top,
// source rect (entire DIB)
0, 0, dib_width, dib_height,
// source bits
pBits_,
// source DIB
pDIB_,
//color table contains RGBQUADs
DIB_RGB_COLORS,
// copy source
SRCCOPY
);
// restore the original palette
if (palettized)
{
hPal_ = SelectPalette(hPrnDC, hPal_, TRUE);
}
// report errors
if (!result)
{
throw EBitmapPrinterError("Error printing DIB");
}
}
// report errors
else
{
throw EBitmapPrinterError("Error getting DIB info");
}
}
Notice that this implementation is virtually identical to the code related to StretchDIBits() that I presented last time. And, as I mentioned last time, this approach will work on most printers. However, due to memory limitations on Windows-9x/Me-based systems, there is a chance that the StretchDIBits() function will fail. For example, if the target device is, say, a poster printer, the dimensions of RPrint_ may be extremely large. In this case, you’d be asking the StretchDIBits() function to affect a huge stretch, which can be a recipe for disaster. Let’s see how we can work around this problem by using banding.
The DoInternalPrintBitmap function, version 2
Banding is a technique of dividing an image into a series of strips or "bands", and then rendering each strip separately to draw the entire image. Banding is especially useful for drawing large images because you can limit the amount of stretching (and thus, memory) that’s required for each call to the StretchDIBits() function.
The trickiest part of banding is computing which strip of the DIB to render. This chore is delegated to the TBitmapPrinter::DoCalculateBand() member function, which is defined as follows:
bool __fastcall TBitmapPrinter::
DoCalculateBand(RECT& RBand, RECT& RImage)
{
// compute the printable band area
if (IntersectRect(&RBand, &RBand, &RPrint_))
{
//
// compute the ratio of the print
// width to the image width
//
const double ratioX =
static_cast<double>
(RPrint_.right - RPrint_.left) /
static_cast<double>
(RImage.right - RImage.left);
//
// compute the ratio of the print
// height to the image height
//
const double ratioY =
static_cast<double>
(RPrint_.bottom - RPrint_.top) /
static_cast<double>
(RImage.bottom - RImage.top);
//
// compute the scaled image band
// (this will tell us where in the
// image to blit from)...
//
RImage.left = static_cast<int>(
0.5 + (RBand.left - RPrint_.left)/ ratioX);
RImage.top = static_cast<int>(
0.5 + (RBand.top - RPrint_.top) / ratioY);
RImage.right = RImage.left + static_cast<int>(
0.5 + (RBand.right - RBand.left) / ratioX);
RImage.bottom = RImage.top + static_cast<int>(
0.5 + (RBand.bottom - RBand.top) / ratioY);
return true;
}
return false;
}
The DoComputeBand() member function takes two RECT-type parameters: RBand and RImage. The RBand parameter specifies a strip of the page to which a corresponding strip of the image should be printed. The RImage parameter specifies the dimensions of the image. These dimensions are used with the RPrint_ member to compute horizontal and vertical scaling factors. These scaling factors are then used with RBand and RPrint_ to adjust RImage so that it specifies the strip of the image that should be printed (to RBand). This scheme is illustrated in Figure A.
Figure A
[IMG00001.gif]
The TBitmapPrinter::DoComputeBand() member function clips RBand to RPrint_, and it adjusts RImage to specify the strip of the image that corresponds to RBand.
So, by using the DoComputeBand() member function we’ll know which part of the DIB to render. But, how do we know which part of the page to render this strip to? That is, how do we compute RBand? Well, one technique is to ask the printer driver for this information by using the NEXTBAND escape sequence, like so:
RECT RBand = {0};
//
// ask the printer driver
// to initialize RBand...
//
while (Escape(hPrnDC, NEXTBAND, 0,
NULL, &RBand))
{
RECT RImage =
{0, 0, dib_width, dib_height};
DoComputeBand(RBand, RImage);
//
// StretchDIBits() to hPrnDC...
//
}
unfortunately, the vast majority of printer drivers will initialize RBand with the dimensions of the entire page, which obviates the role of our banding code. In other words, if RBand specifies the entire page, then RImage will indicate the dimensions of the entire image even after the call to DoComputeBand().
To work around this potential problem, let’s help the printer driver out a bit. Specifically, we can use the NEXTBAND escape sequence to ask the printer driver to initialize RBand , and then we can divide this band (which might indicate the entire page) into a series of sub-bands. This way, we can ensure that our banding code will be used, regardless of how the printer driver initializes RBand. This scheme is demonstrated in the following definition of the DoInternalPrintBitmap() member function:
void __fastcall TBitmapPrinter::
DoInternalPrintBitmap(HDC hPrnDC)
{
if (IsRectEmpty(&RPrint_))
{
throw EBitmapPrinterError("Invalid target rectangle");
}
if (pDIB_ && pBits_)
{
//
// extract the width and the
// height from the DIB's header
//
const int dib_width =
pDIB_->bmiHeader.biWidth;
const int dib_height =
pDIB_->bmiHeader.biHeight;
//
// query the printer for NEXTBAND
// capability (not really needed,
// better to be safe than sorry)
//
const int query_cap = NEXTBAND;
int do_bands =
Escape(
hPrnDC, QUERYESCSUPPORT,
sizeof(int), reinterpret_cast
<LPCSTR>(&query_cap),
NULL
);
bool result = true;
if (do_bands)
{
RECT RBand = {0};
//
// ask the printer driver
// to initialize RBand...
//
while (Escape(hPrnDC, NEXTBAND, 0,
NULL, &RBand))
{
if (IsRectEmpty(&RBand)) break;
// limit the band height to 75
const int band_height = min(75L, RBand.bottom - RBand.top);
// compute the number of bands
const int num_bands = 0.5 +
(RBand.bottom - RBand.top) /
static_cast<double>(band_height);
// for each band...
for (int iBand = 0;
iBand < num_bands; ++iBand)
{
RBand.top =
iBand * band_height;
RBand.bottom =
RBand.top + band_height;
RECT RImage = {
0,0,dib_width, dib_height};
if (DoCalculateBand(RBand, RImage))
{
//
// set the stretching mode
// to preserve colors
//
SetStretchBltMode(hPrnDC, COLORONCOLOR);
// for palettized printers
bool palettized =
GetDeviceCaps(hPrnDC, RASTERCAPS) & RC_PALETTE;
if (palettized)
{
hPal_ = SelectPalette(hPrnDC, hPal_, FALSE);
RealizePalette(hPrnDC);
}
// extract the dimensions
const int dst_width =
RBand.right - RBand.left;
const int dst_height =
RBand.bottom - RBand.top;
const int src_width =
RImage.right -RImage.left;
const int src_height =
RImage.bottom -RImage.top;
// render the DIB
result &= GDI_ERROR !=
StretchDIBits(
// printer's DC
hPrnDC,
// dest rect
RBand.left, RBand.top,
dst_width, dst_height,
// source rect
RImage.left, dib_height-
RImage.bottom,
src_width, src_height,
// source bits
pBits_,
// source DIB
pDIB_,
//c.t. contains RGBQUADs
DIB_RGB_COLORS,
// copy source
SRCCOPY
);
// restore the old palette
if (palettized)
{
hPal_ = SelectPalette(hPrnDC, hPal_, TRUE);
}
}
}
}
} else
{ // no banding
//
// other code from
// DoInternalPrintBitmap()
// version 1...
//
}
// report errors
if (!result)
{
throw EBitmapPrinterError("Error printing DIB");
}
}
// report errors
else
{
throw EBitmapPrinterError("Error getting DIB info");
}
}
There are a couple important things to note about this code snippet. First, the limit on the height of each band (which I fixed to 75 device units) is only a hypothetical number. You should examine both the height and the width of RBand to gauge the amount of stretching that’s required; you can then decide whether or not you want to divide the band into sub-bands. Second, avoid using the sub-band division scheme on Windows NT/2000-based systems. I’ve found that the memory requirements of the spooler process increases in direct proportion to the number of sub-bands that you use. You can use the GetVersionEx() function (see msdn.microsoft.com/library/psdk/sysmgmt/sysinfo_49iw.htm) to determine the version of Windows on which your application is running.
Limitations of the TBitmapPrinter class
One of the reasons I declared most of the TBitmapPrinter class’s member functions as virtual was to work around its limitations. One limitation is that the class won’t split the output across multiple pages. You’ll need to implement this functionality manually by either dividing your TBitmap object beforehand (i.e., before assigning it to the TBitmapPrinter::Bitmap property), or by modifying the DoPrintBitmap() and/or DoInternalPrintBitmap() member functions.
Another limitation involves the use of 8-bpp bitmaps and the GetDIBits() GDI function. Specifically, on palettized displays, the GetDIBits() function—which is called from within the GetDIB() VCL function—incorrectly initializes the color table of the resultant DIB with entries of the default system palette (instead of with entries from the bitmap’s palette). If you’re using Borland C++Builder 3.0 or later, you can work around this bug by setting the TBitmap::PixelFormat property to pf15bit before assigning your TBitmap object to the TBitmapPrinter::Bitmap property. Alternatively (or if you’re using Borland C++Builder 1.0), you can manually initialize the DIB’s color table entries (recall the BITMAPINFO::bmiColors data member) with the entries of the bitmap’s palette. To do this, you’ll need to use the GetPalet{*word*249}tries() GDI function (see msdn.microsoft.com/library/psdk/gdi/colors_1l4j.htm and www.deja.com/threadmsg_ct.xp This latter method is actually preferable to adjusting the PixelFormat property because it avoids the overhead of creating a DIB of a greater color depth than is necessary.
Finally, in the rare case that the printer is palette-based, you need to ensure that you have a suitable palette. If your original bitmap uses eight (or less) bits per pixel, then the TBitmap::Palette property will indeed refer to a suitable logical palette; namely, the logical palette that’s created from the color table of the BMP file. Recall, however, that a color table is optional for bitmaps of greater color depths. If you load, say, a 24-bpp bitmap from a file, chances are good that your Bitmap’s palette will contain entries that correspond to the default system palette (a mere 20 colors). In this case, you need to use a color quantification strategy. See the following references for more information on color quantification:
www.acm.org/tog/GraphicsGems/ or ftp.ledalite.com/pub/cquant97.bib.
Conclusion
Although printing a bitmap is far from straightforward, there’s certainly no black magic to it. Just keep the following in mind as you implement your printing code: (1) always use a DIB; (2) always use the StretchDIBits() function; and, (3) use banding when required. If your bitmap fails to print (and an exception wasn’t thrown), the very first place you should look is the return value of the StretchDIBits() function.
You can download the source code for the TBitmapPrinter class along with a sample project from www.bridgespublishing.com. And, as always, if you have any questions, feel free to contact me at XXXX@XXXXX.COM .
Listing A: The declaration of the TBitmapPrinter class
//---------------------------------------
#ifndef BITMAP_PRINTER_H
#define BITMAP_PRINTER_H
//---------------------------------------
#include <vcl.h>
#include <stdexcept>
//---------------------------------------
typedef Exception EBitmapPrinterError;
enum TBitmapPrinterScale {bpsHorz, bpsVert, bpsPage, bpsCustom};
class TBitmapPrinter : public TObject
{
public:
__fastcall TBitmapPrinter();
__fastcall ~TBitmapPrinter();
// main printing member function
void __fastcall PrintBitmap(HDC hPrinterDC)
{DoPrintBitmap(hPrnDC);}
// sets the TBitmap to print
__property const Graphics::TBitmap*
Bitmap = {write = DoSetBitmap};
// gets/sets the target printing rect.
__property RECT PrintRect = {read = RPrint_, write = DoSetRect};
// gets/sets the scaling options
__property TBitmapPrinterScale
PrintScale = {read = scale_, write = DoSetScale};
protected:
// main printing member functions
virtual void __fastcall DoPrintBitmap(HDC hPrnDC);
virtual void __fastcall DoInternalPrintBitmap(HDC hPrnDC);
// gets/sets class properties
virtual void __fastcall DoSetBitmap(const Graphics::TBitmap* Bitmap);
virtual void __fastcall DoSetPrintRect(RECT RPrint);
virtual void __fastcall DoSetScale(TBitmapPrinterScale scale);
// internal utility member functions
virtual LPBITMAPINFO __fastcall DoCreateDIB(const Graphics::TBitmap&
Bitmap, unsigned char*& pBits);
virtual void __fastcall DoCalculatePrintRect(HDC hPrnDC);
virtual bool __fastcall DoCalculateBand(RECT& RBand, RECT& RImage);
private:
// pointer to the DIB (header and CT)
LPBITMAPINFO pDIB_;
// pointer to the DIB's pixels
unsigned char* pBits_;
// handle to the bitmap's palette
HPALETTE hPal_;
// target printing rectangle
RECT RPrint_;
// scaling options
TBitmapPrinterScale scale_;
};
//---------------------------------------
#endif // BITMAP_PRINTER_H
//---------------------------------------
 

Re:Sending a bitmap to the printer - nothing printed.

Thanks JD, you've always been helpful andresourceful.
"JD" < XXXX@XXXXX.COM >wrote in message
Quote

"newsreader" < XXXX@XXXXX.COM >wrote:
This seems to be a common problem with TPrinter. I say 'seems'
because I've never seen it myself but I've seen many who have
complained.

Indeed, actually this is not the first time I'm working with bitmaps on
printer and this "Can't print bitmap" problem does not show up every time.
After looking through all the projects I've been working with printers, I
noticed that it will not work if and only if there is only a Bitmap draw in
between the BeginDoc() and EndDoc() as in my earlier sample code. If I put
in another command like
printer->BeginDic();
printer->Canvas->Draw(0, 0, bitmap);
printer->Canvas->TextOut(0, 0, "just a string"); //this is the extra command
printer->Canvas->EndDoc();
the "extra command" is just anything, from drawing a shape or printing a
text on the printer canvas, then the printer will not ignore the bitmap draw
and print "correctly" - with exception of the extra command's result. So I'm
wondering is this a bug in the TPrinter object???
Quote
First let me say that there's a problem with your over-all
approach because the screen dpi and the dpi for the variouse
printers will never match up and the final printed output won't look
anything like the preview.

nope, my approach is not wrong, I've been using this approach and got what I
want in the end. Please take note, the PreviewBitmap is of the same size of
the RepPrinter's Canvas. I'm basically drawing on a bitmap wich is working
like a pixel to pixel map to the Printer's Canvas, so DPI is not an issue.
Actually I never care about all those details, I just calculate how many
percent of the Page Size each itmes on the report will take and render all
of them using percentage, no pixel (at least at the coding level) is
involved in the rendering process. While I'm rendering on a TBitmap object,
I just simply do a stretch draw of the bitmap on my preview form, to shrink
it down to size of the form. I can get the printer starting to print in less
than 15 seconds after click on the Print button, so It's fast enough
considering I'm re-rendering all the entries on a 4k+ x 6k+ bitmap. I'm sure
my approach will work perfectly on all printers (regardless of their DPI) as
long as the paper size is A4.
Now the problem is, it will not print anything if I tried to pass the
PreviewBitmap to the Printer's canvas directly....I have to create a fuction
to render the report:
void RenderReport(TCanvas *targetCanvas);
and pass the PreviewBitmap's Canvas to it while the user is tuning the
report to their liking with preview, and pass it the Canvas of the Printer
when the user is happy with the report and wants to print it. I got what I
want but still it is not an efficient way of doing it, since theoritically,
I should be able to sava a lot of work by just sending the bitmap to the
printer's canvas.
Anyway, I stll haven't finished reading your reply, it's indeed lengthy.
Will post again if I have any question regarding the code. Thanks.
Regards.
 

{smallsort}

Re:Sending a bitmap to the printer - nothing printed.

Quote
The 'correct' way to do this is to use a DIB and although the
writer of TExcellentPrinter will tell you that you need low
level code to ensure that it works 100% of the time, I have
never known it not to work as expected.
Interestingly, we have customers that purchase our TExcellentImagePrinter
after trying that solution.
TExcelletnImagePrinter can be found here:
www.code4sale.com/joehecht/
Joe
--
Delphi, graphics, and printing specialist available - $35/hr
www.code4sale.com/codeit/index.htm
Joe Hecht Associates
121 Louise Drive
Crestview, FL 32536