March 93 - ADVENTURES IN COLOR PRINTING
ADVENTURES IN COLOR PRINTING
DAVE HERSEY
Along with Color QuickDraw came the need for applications to support printing of
pixMaps. Users need (and expect) to be able to produce realistic hard copies of their
color screen displays. The challenge for developers is to ensure high-quality output
regardless of the printing configuration being used. This article and its accompanying
sample programs show you how.
Consider a 24-bit color image we've just scanned in. We'd like this image to print in
color on all color printers, whether they're color LaserWriters, ImageWriters with
color ribbons, or color ink jet printers. Similarly, we'd like to generate output that
represents the source image as closely as possible when we're using grayscale
printers such as the LaserWriter IIg with PhotoGrade, or monochrome printers such
as LaserWriters without PhotoGrade, StyleWriters, and ImageWriters with black
ribbons. And, of course, we'd like our images to look great even when the user has
chosen black-and- white printing on a color-capable printer.
The challenge of producing high-quality output regardless of the printing
configuration should ideally be handled at the driver level, through new printer
drivers or solutions such as ColorSync or QuickDraw GX. But until every system
makes use of these new technologies, we're stuck with the task of working around the
pitfalls of the present printing architecture. The key is to determine the printing
configuration we're working with and then supply the routine that ensures the
highest- quality output in that particular case.
This article and the sample code that accompanies it on theDeveloper CD Seriesdisc
will show you how to print pixMaps (or pictures containing pixMaps) faithfully on
any printer by building in a combination of approaches to cover all cases. The results
will be far better than any you can get by a "one size fits all" approach. I'll discuss
how to make use of Color QuickDraw when a printer driver can support it, how to
render color images with original QuickDraw on printers whose drivers don't support
Color QuickDraw (such as the ImageWriter), and how to convert color images to high-
resolution halftone images for printing on monochrome printers.
The methods in this article apply equally well to PostScript and QuickDraw printers,
and they work correctly whether or not the new printing solutions are in place. Note,
however, that without some extra work (see the end of this article) these methods may
not be optimal for printing pictures that contain text. When text is converted to
pixMaps, all of the font information is lost, and the result can often be chunky,
poor-quality text that's hard to read.
All of the techniques described here require you to have 32-Bit QuickDraw available.
This covers any Macintosh with 32-Bit QuickDraw in ROM and any machine with Color
QuickDraw in ROM that either is running System 7 or has the 32-Bit QuickDraw INIT
installed. If you have only ColorQuickDraw available (the version that predates
32-Bit QuickDraw), you can still use all of the techniques described here as long as
you implement a GWorld structure and replacements for the calls OpenCPicture,
NewGWorld, DisposeGWorld, and CopyBits with ditherCopy mode. Methods to apply
when Color QuickDraw is not available are discussed in "Making the Most of Color on
1- Bit Devices" indevelopIssue 9. Together, the present article and the article in Issue
9 give you solutions that cover printing in any situation.
THE "ONE SIZE FITS ALL" APPROACH: A BAD IDEA
Many applications today that deal with pixMap images don't worry about addressing all
the possible variations in printing configurations. This is unfortunate because the "one
size fits all" approach can severely limit an application's potential.
Under the current printing architecture, if you provide just one printing method in
your application based on assumptions about the printing configuration most likely to
be used, you're bound to frustrate and annoy some users. For example, imagine a user
with a color laser printer who for some special purpose wants to print a color image
in black and white. If your application has failed to take this printing possibility into
account, the user will end up with a hideous Black Blob that looks nothing like the
original. Or picture a user with an ImageWriter who decides to invest in a color
ribbon so that she can print color images with her favorite paint program, only to
discover that because the program doesn't provide for this possibility, the result is --
you guessed it -- a hideous Black Blob. Or consider the users who find that documents
containing color images that print just fine on their LaserWriters at work, print
terribly on their StyleWriters, ImageWriters, or Personal LaserWriters at home.
These frustrated users will end up clogging your customer service hotline with the
kind of calls you don't want to get. The moral of the story is that under the current
printing architecture it's not enough to provide just one method to print your images.
Far superior to the "one size fits all" approach is the strategy of providing printing
routines to address the whole range of printing configurations your application might
encounter. Then all your application has to do at print time is to determine which
printing configuration it's dealing with and provide the appropriate printing routine.
That's what this article is about.
We start by looking over the possible printing configurations; then we consider
routines to address each of these configurations; and finally, we look at how an
application can determine which printing configuration it's facing.
THE POSSIBLE PRINTING CONFIGURATIONS
When you're printing from the Macintosh, there are three distinct types of printer
drivers that you might encounter:
• Printer drivers that support Color QuickDraw calls. For example, the
LaserWriter driver 6.0 and later in Color/Grayscale mode, printing to color,
grayscale, or monochrome laser printers; and drivers for a number of
third-party color laser printers, ink jet printers, film recorders, and so
forth.
• Drivers for color-capable printers that don't support Color QuickDraw
calls or data structures. For example, the ImageWriter drivers through
version 7.0 printing to an ImageWriter with a color ribbon installed.
• Drivers for monochrome printers that don't support Color QuickDraw
calls or data structures. For example, the ImageWriter drivers through
version 7.0 printing to an ImageWriter with a black ribbon installed, the
StyleWriter using the 7.2.2 driver, and laser printers using the LaserWriter
driver 5.2 (or 6.0 and later in Black & White mode).
Note that what matters to you isn't the printer being used, but the printer driver.
Thus, for example, if you print Color QuickDraw to a LaserWriter IINT using the
version 5.2 driver (which doesn't have the Color/Grayscale option), you'll end up
with nothing but stark black shapes because there's no Color QuickDraw support in the
driver. The same printer using the 7.0 driver with the Color/Grayscale option
selected will produce excellent results in response to the very same drawingcommands
-- same printer, but totally different results depending on the driver. Another good
example is the ImageWriter. Versions of the ImageWriter driver through version 7.0
don't support Color QuickDraw calls, but there are third-party drivers for the
ImageWriter that do.
Note also that in the category of drivers that support Color QuickDraw calls, no
distinction needs to be made between grayscale and color printers. Based on your
experience with Color QuickDraw on the screen, you might have the impression that a
color image should be converted to a grayscale image before printing to a noncolor
device, or that you need to get the printer port's color table, GDevice, or bit depth, and
map your images to those before printing. But in fact, this is not only unnecessary but
also undesirable in the printing environment. If the driver supports Color QuickDraw,
you don't need to worry about whether your images will be printing on a color or a
grayscale printer.
ABOUT PRINTER DRIVER PORTS AND COLOR QUICKDRAW SUPPORT
While I've categorized printer drivers by whether or not they support Color
QuickDraw, what we're really concerned with is whether they give us a cGrafPort or a
grafPort to draw in. The port I'm referring to here is the TPPrPort that the driver
returns to the application through PrOpenDoc. Printer drivers that give us a
cGrafPort support Color QuickDraw calls, because a cGrafPort is capable of handling
multibit pixels. On the other hand, printer drivers that give us a grafPort don't
support Color QuickDraw calls.
Drawing with Color QuickDraw in a grafPort, while possible, will yield disappointing
results. Consider what happens if you try to CopyBits a 24-bit-deep image to the
ImageWriter (assuming you're not using ditherCopy mode in System 7). Since you're
copying to a driver port that's capable of only two colors, every one of the pixels in
your image will become either your foreground color or your background color,
whichever its value is closest to. In the usual case of a black foreground and a white
background, you'll end up with the Black Blob effect -- all colors with luminance
values of at least 50% black draw black and everything else draws white.
Although the situation is improving, at present most of the drivers that Apple ships
return grafPorts. (See "The Story Behind Color QuickDraw Support" for the whys
and wherefores.) The LaserWriter drivers version 6.0 and later are capable of
providing a cGrafPort for your application to draw into, but note that if the user
selects Black & White mode in the color LaserWriter driver's print job dialog, even
that driver returns a grafPort; a cGrafPort is returned only when the user has chosen
Color/Grayscale mode.
Let me warn you up front that the printer driver port isn't necessarily a true
cGrafPort or grafPort -- that is, one that's valid outside the context of the Printing
Manager. In the case of Apple's printer drivers, it never is. The fact is that drivers
have a lot of leeway when it comes to the port structure they return. Since the driver
needs to replace the port's QuickDraw bottleneck procedures in order to direct the data
to a printer, there's no need for many of the fields that you would use if you were
drawing to a true grafPort or cGrafPort, such as a window on the screen. In fact, when
you make a call like
CopyBits(&bitMap, &printPort->gPort.portBits, &srcRect,
&destRect, srcCopy, nil);
the data most likely won't even end up in the driver port's bitmap. In fact, the bitmap
structure may not even exist. There's no need for it to. All that matters is that as you
draw into the grafPort or cGrafPort, your drawing commands are intercepted,
possibly translated, and then redirected to the printer.
So don't assume that the printer driver's port is a true grafPort or cGrafPort, or that
the values therein have anything to do with how your image will print. You should view
the printer driver's port as a private structure, with the only public fields being the
actual pointer to the grafPort or cGrafPort (your TPPrPort pointer) and its port's
portBits bitmap. Even then, SetPort and CopyBits are the only calls you should pass
those values to.
THE PROBLEM AT HAND
To get back to the problem at hand, we need printing routines to address each of the
three possible printing configurations. The rest of this article is devoted to describing
those routines and outlining how to determine at print time which routine is
appropriate. The routines are demonstrated by four samples in the Adventures in Color
Printing folder on theDeveloper CD Seriesdisc.
Note that all the samples implement the technique of loading and storing print records
from job to job. All printing applications should implement some sort of handling like
this so that when users attempt to print documents, their last used settings are
available, rather than the driver's defaults.
All samples work under System 6 or 7. Remember that to use the methods described
here, you must have 32-Bit QuickDraw available, or if you have only Color
QuickDraw (the version that predates 32-Bit QuickDraw) available, you must
implement a GWorld structure (which is the same thing as a cGrafPort) and
replacements for the calls OpenCPicture, NewGWorld, DisposeGWorld, and CopyBits
with ditherCopy mode.
PRINTING WITH COLOR QUICKDRAW SUPPORT
The easiest color printing situation you'll come across occurs when a printer driver
gives you a cGrafPort to work in. To generate the best results we first need to deal with
setting the resolution and scaling the image. Then we want to band our image through a
32-bit-deep GWorld to avoid the potential problem of operator incompatibility. The
Color Adventures sample code demonstrates how we go about this. As mentioned
earlier, grayscale printing in a cGrafPort shouldn't be treated any differently from
color printing in a cGrafPort.
SETTING RESOLUTION AND SCALING THE IMAGE
When we print an image, a couple of different scaling operations are involved. First,
our application sets the printer driver port's resolution and, if necessary, scales the
image to that resolution; then the printer driver scales the image to the device's
physical (output) resolution during printing. The amount an image is scaled when
copied to the printer port is calculated as follows:
scaleAmt = (sourceDPI / destinationDPI) * (scaling factor from Page Setup dialog)
To achieve the highest-quality output, our image's resolution should ideally be the
same as the printer's physical resolution. If our image's resolution doesn't match the
printer's resolution, we can scale the image before printing, change the port's
resolution to match the image resolution, or do a combination of both (scale the source
image and the port).
Here's how we proceed: First, we need to know the resolution of our source image. Most
PICT files on the Macintosh are rendered at 72 dpi, but that needn't be the case, and in
the case of scanned images is actually rather unlikely. The GetImageRes routine in the
Color Adventures sample shows how to find the resolution of any PICT. If the
OpenCPicture call was used to create the picture, the resolution information is stored
right in the picture header for easy retrieval. Otherwise, we need to determine the
resolution by parsing the picture.
Once we have the image resolution, we need to know how close the printer can be set to
that resolution. We can determine the supported resolutions for a particular printer
using PrGeneral, as discussed in the article "Meet PrGeneral" indevelopIssue 3 and
inInside MacintoshVolume V. As noted in those sources, when we call PrGeneral with
the GetRslData opcode, drivers that support PrGeneral will return a list of discrete
resolutions and possibly a range of supported resolutions that we can also specify.
So, for example, if PrGeneral told us that we were capable of printing our 300-dpi
image at 300 dpi, we would set the printer port's resolution to 300 dpi x 300 dpi by
using PrGeneral with the setRsl opcode. Then all we'd need to do would be to draw the
image at its original size. That's the easy case.
If we're printing to a device none of whose supported resolutions match our image's
resolution, the best choice is usually the pair of horizontal and vertical resolutions
that when multiplied yield thelargest product. We'll need to scale the image to that
resolution before printing. While this method of choosing resolutions isn't foolproof,
it should typically give us the best results. Of course, if someone comes out with a
driver for a printer that supports a resolution pair such as 600 dpi x 72 dpi, where
there's a big difference between the horizontal and the vertical resolution, there might
be problems with such an approach. Many times, we'll want the horizontal and vertical
resolutions to be equal. The section on setting resolution under "Printing in Black and
White" later in this article discusses this further.
We'll probably also want to put a ceiling on the resolution of the printer port.
Otherwise, if we're printing to a Linotronic we may have to scale our 72-dpi images
up about 3528 percent to 2540 dpi, and that will take a long, long, long time to print
and require an enormous amount of memory. Of course there may be times when 2540
dpi is exactly what we want. We can always provide the user with a list of supported
output resolutions to choose from.
Finally, suppose that we can't set the printer resolution because we're using a driver
that doesn't support PrGeneral. We can tell this because after our call to PrGeneral,
ResError is set to resNotFoundErr. In this case, we have only one recourse -- to scale