TweetFollow Us on Twitter




[IMAGE Hersey_rev1.GIF]

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.


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.


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.

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.

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.


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.

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 the image to the port's default resolution, 72 dpi.

Putting all this together, we end up with the GetBestDPI routine in the Final Adventure sample for setting the best resolution with PrGeneral. GetBestDPI obtains the best horizontal and vertical resolutions to use for printing with the selected driver. The function looks like this:

void GetBestDPI(short *pxDPI, short *pyDPI, short xDPI_ceiling,
        short yDPI_ceiling, Boolean wantSquareDPI);

The caller places an ideal resolution pair (what the caller really wants to use) in the parameters pxDPI and pyDPI. This is also where the routine returns the resolutions it decides on. In xDPI_ceiling and yDPI_ceiling, the caller places the maximum resolution desired in either direction. For example, if you didn't want values larger than 300 dpi returned, you'd put 300 in both of these parameters. If wantSquareDPI is true, only square resolutions (those with equal horizontal and vertical components) will be considered.

The printer driver is expected to be closed upon entry to this routine and is therefore opened and closed around the PrGeneral code. If PrGeneral isn't supported by this driver, or if an error occurs, the routine returns 72 x 72 dpi, which is the default for Macintosh printer drivers. If the ideal resolution the caller passes in is available, we choose that, ignoring wantSquareDPI, xDPI_ceiling, and yDPI_ceiling. We figure that the calling routine knows more about the ideal resolution it requests than we do. Here's the code:

void GetBestDPI(short *pxDPI, short *pyDPI, short xDPI_ceiling,
                        short yDPI_ceiling, Boolean wantSquareDPI)
    TGetRslBlk      getResRec;
    Boolean         exactMatch = false;
    short           bestResX, bestResY, xDPI, yDPI,
                    desiredResX, desiredResY, rec;
// Open the driver for our PrGeneral call. Assume we'll return
// 72 x 72 dpi until we find otherwise, and also store the desired
// resolution that the caller passed to us through the pxDPI and
// pyDPI parameters.
    bestResX = bestResY = 72;
    desiredResX = *pxDPI;
    desiredResY = *pyDPI;
    if (!PrError())

// Ask PrGeneral for the resolution records for this driver.
        getResRec.iOpCode = getRslDataOp;
        PrGeneral((Ptr) &getResRec);

        if ((!ResError()) && (!getResRec.iError))

// First check for the exact resolution pair that the caller
// requested.  To begin with, check the range of resolutions
// supported to see if the pair is within that.
            if ((getResRec.xRslRg.iMin <= desiredResX) &&
                    (getResRec.xRslRg.iMax >= desiredResX) &&
                    (getResRec.yRslRg.iMin <= desiredResY) &&
                    (getResRec.yRslRg.iMax >= desiredResY))
                exactMatch = true;
// If we didn't find an exact match, check the driver's discrete
// resolutions to see if we have one there.
            for (rec = 0;
                    (!exactMatch) && (rec < getResRec.iRslRecCnt);
                if ((getResRec.rgRslRec[rec].iXRsl == desiredResX) &&
                     (getResRec.rgRslRec[rec].iYRsl == desiredResY))
                    exactMatch = true;

// If we found an exact match, use it. Otherwise, loop through each
// resolution record and find the one that best matches our
// criteria.
            if (exactMatch)
                bestResX = desiredResX;
                bestResY = desiredResY;
                for (rec = 0; (rec < getResRec.iRslRecCnt); rec++)
                    xDPI = getResRec.rgRslRec[rec].iXRsl;
                    yDPI = getResRec.rgRslRec[rec].iYRsl;
                    if ((xDPI <= xDPI_ceiling) &&
                            (yDPI <= yDPI_ceiling) &&
                            (!wantSquareDPI || (xDPI == yDPI)) &&
                            ((xDPI * yDPI) > (bestResX * bestResY)))
                        bestResX = xDPI;
                        bestResY = yDPI;
// Return the best resolution pair we found and close the driver.
    *pxDPI = bestResX;
    *pyDPI = bestResY;

The following code returns a rectangle to use when scaling from an image's bounds (srcRect) and resolution (ixDPI, iyDPI) to a printer port's resolution (pxDPI, pyDPI). The resulting rectangle (scaleRect) will have a top left corner of (0, 0).

void GetScaleRect(Rect *srcRect, short ixDPI, short iyDPI,
                        short pxDPI, short pyDPI, Rect *scaleRect)
    Fixed       scale;

    *scaleRect = *srcRect;
    OffsetRect(scaleRect, -scaleRect->left, -scaleRect->top);

    scale = FixRatio(pxDPI, ixDPI);
    scaleRect->right =
        FixMul(scale, (long) scaleRect->right <<16) >>16;
    scale = FixRatio(pyDPI, iyDPI);
    scaleRect->bottom = 
       FixMul(scale, (long) scaleRect->bottom <<16) >>16;

Pictures can include information that a printer driver can't understand, such as transfer modes and structures that have been added to the system since the driver was developed, and sometimes a driver can't reproduce certain operations that work great on the screen. For example, PostScript doesn't understand the concept of transfer modes, so the LaserWriter driver doesn't know what to do when it encounters such modes as blend, ditherCopy, and addMin. Aside from transfer modes, certain QuickDraw operations aren't supported by all drivers. For instance, CopyMask doesn't work with any of Apple's printer drivers as of this writing.

The upshot is that if you only use DrawPicture, some pictures are bound to print incorrectly on various printers because of operator incompatibility. The PICT named Incompatibility Test in the sample code folder demonstrates this problem. Try printing the picture with TeachText and comparing the output to the screen image. A safer approach to printing an image (although one that may require more data to be sent to the printer and thus result in slower printing) is to always send 32-bit-deep data to the printer by banding the image through a GWorld. Of course, if you know your application never needs 32-bit pixMaps, you can just use a GWorld deep enough for the data you'll be printing.

Here's how it works: Create a 32-bit-deep GWorld that has room for one horizontal (or vertical) strip of data of some arbitrary size. In the following example, we use horizontal strips. Call SetGWorld on this GWorld and then DrawPicture, passing the full image's picFrame. All of the picture outside the banding GWorld's bounds rectangle is clipped. The code might look like this:

#define BAND_HEIGHT 144     // 2 inches at 72 dpi.
pictRect = (*imgPICT)->picFrame;
bandRect = pictRect; = 0;
bandRect.bottom = BAND_HEIGHT;

err = NewGWorld(&bandGWorld, 32, &bandRect, nil, nil, 0);

if (err == noErr)
    SetGWorld(bandGWorld, nil);
    destPix = GetGWorldPixMap(bandGWorld);

    DrawPicture(imgPICT, &pictRect);

This results in a band of the original picture being drawn to bandGWorld, which in turn can be copied to the printer port, like so:

srcPix = GetGWorldPixMap(bandGWorld);

CopyBits((BitMap *) *srcPix, &(printPort->gPort.portBits), &bandRect,
    &bandRect, srcCopy, nil);

To create the next band, shift bandGWorld's bounds rectangle down by one bandwidth and repeat the process. For best results, you may want to increase the printer port's resolution with PrGeneral, draw into a GWorld of the same resolution, and then use CopyBits to draw that in the printer port.

When you're working with 32-bit images, it's very useful to implement some sort of banding or picture spooling algorithm, since 32-bit images take up an enormous amount of memory, especially when you need to scale them to higher printer resolutions. All of the program samples on the CD have routines that implement banding and spooling. These routines also handle the special problems introduced when you need to dither and scale during banding.

When you send 32-bit-deep data to the printer driver, you inadvertently solve another problem as well -- worrying about the printer's output characteristics. Printing images as 32-bit deep will give you the best output on all color printers whose drivers return a cGrafPort. You can be sure that when you send 32-bit-deep data the driver and printer will do the right thing -- either print the image 32 bits deep or map it to the device's characteristics, be it an 8-bit device or whatever. You don't need to worry about checking the depth of the printer port or getting its GDevice or color table, which would be futile anyway since the port probably isn't a true cGrafPort.

In general, if you don't know whether an image is 32 bits deep or 8 bits deep, you should print it at 32 bits. This way, you won't lose any color information. Of course, printing 32-bit-deep images means increased printer data and print times, so you may want to let the user have some control over the decision. Getting the best output may not be as important to a user as seeing an 8-bit draft of the image sooner.


A word to the wise: The LaserWriter driver changes your image's color table. You must be prepared for this and know how to prevent its altering your printout.

Suppose you have an 8-bit color image with a custom color table. What happens when you print this with the LaserWriter driver using CopyBits when Color/Grayscale is selected? The driver returns a cGrafPort at PrOpenDoc time. As the drawing begins, the driver makes a copy of your image's color table. It then replaces the first entry in the color table with the current background color, and the last entry with the current foreground color. Once the foreground and background colors have been placed in the color table, the driver sends the image to the printer, passing the indexed RGB value for each pixel.

This means that if your foreground color is not the same as your last color table entry, or your background color is not the same as your first entry, your image may be altered when it prints. The best way to avoid this problem is to keep white in the first color table entry and black in the last, and make sure to set the foreground color to black and the background color to white before drawing.

Because the driver alters your color table, it's not a good idea to invert an image by inverting its color table, as some applications do. Imagine that you have an 8-bit grayscale image of a scanned photograph. Let's say that you want to print an inverted copy of the image and that its color table is a linear ramp of grays, from white to black. The easy -- but incorrect -- approach is to invert the entries in the image's color table and then print the image. The correct approach is to use CopyBits to copy the image over itself using notSrcCopy mode before printing.

Figure 1 compares printouts of an image inverted correctly and incorrectly. Notice that the incorrect method hasn't inverted absolute (or pure) black or white pixels in the image.

Why does the driver alter your color table? Because it's attempting to perform bitmap colorization. This is a feature of CopyBits that's not very well documented and that the LaserWriter driver supports. The version of CopyBits in System 7 will actually colorize an entire pixMap, although the LaserWriter driver has never been upgraded to support this functionality. The improvements to CopyBits colorizing are discussed in "QuickDraw's CopyBits Procedure: Better Than Ever in System 7.0" in develop Issue 6 and in Chapter 17 ofInside Macintosh Volume VI.

[IMAGE Hersey_rev2.GIF]


[IMAGE Hersey_rev3.GIF]

Inverted correctly

[IMAGE Hersey_rev4.GIF]

Inverted incorrectly

Figure 1 Grayscale Image Inverted Correctly and Incorrectly


Most printer drivers today have been updated to return cGrafPorts when color ink is used. The only exception to this rule that I know of is the ImageWriter driver. Because all Apple ImageWriter drivers through version 7.0 return a grafPort, we can't rely on Color QuickDraw calls and structures to give us accurate color images when we have a color ribbon installed. We can draw only eight colors into a grafPort (traditionally called "the original QuickDraw colors").

Printing on the ImageWriter with DrawPicture works perfectly well as long as our picture is made up of original QuickDraw objects (those that appear inInside MacintoshVolume I), each preceded by a call to ForeColor to set the foreground color to one of the eight original QuickDraw colors. For example, the following code will print correctly on an ImageWriter with a color ribbon, whether it's simply sent to the printer port or enclosed in a PicHandle that's then printed with DrawPicture:

SetRect(&bounds, 20, 20, 120, 120);
                                 // Initial object bounds (a square).
BackColor(whiteColor);           // Set background color to white.
ForeColor(cyanColor);            // Set foreground color to cyan.
FillRect(&bounds, gray);     // Fill square with 50% cyan pattern.
OffsetRect(&bounds, 70, 70); // Move down a bit.
ForeColor(blackColor);           // Select black.
FrameRect(&bounds);          // Draw a black square frame.
ForeColor(cyanColor);            // Select cyan.
PaintOval(&bounds);          // Draw a cyan circle in the frame.

The result is shown in Figure 2. Without the calls to ForeColor, our picture would be recorded using our current foreground color for all objects. This is usually black and would cause everything to print as black. If we need to print Color QuickDraw objects on an ImageWriter with a color ribbon, we must first convert them to original QuickDraw objects. In the case of pixMaps, we convert all of the pixMap's colors to the eight original QuickDraw colors and make a bitmap separation of the image for each color. The Color ImageWriter Adventures sample demonstrates how to do this.

First, possibly through banding, we use CopyBits to ditherCopy the source picture into a 4-bit GWorld whose color table is made up of the eight original colors. We obtain this color table by passing a value of 127 to GetCTable, as explained inInside MacintoshVolume V, page 81.

[IMAGE Hersey_rev5.GIF]

Figure 2 Product of Drawing With a Sequence of Calls to ForeColor

If we don't use ditherCopy, the resulting output will have colors determined by threshold comparison. In other words, every color in the original will simply be mapped to one of the eight original QuickDraw colors. This method will make scanned images look fake or "painted," which is not what we're looking for. In most cases, we'd rather have a dithered image that approximates more than eight colors by putting different colors side by side. Since we're printing only eight real colors, dithering is a necessity when using this method. For the curious, the Color ImageWriter Adventures sample allows you to turn dithering off for comparison.

Once our image has been copied to the 4-bit "original color" GWorld, we can start making our separations. We need a Color QuickDraw searchProc that returns the position indicator for black or white, depending on whether or not the color passed matches the color we're looking for. If it does, the routine returns black. Since we'll be copying to a bitmap (in which a 0 pixel value indicates the background color and a 1 pixel value indicates the foreground color), this is all the code it takes:

pascal Boolean OQDSearch(RGBColor *anRGB, long *position)
    *position = 0;          // Initially assume no color.

    if ((anRGB->red == (*gOrgQDCTab)->ctTable[gCurColor] &&
           (anRGB->green ==
               (*gOrgQDCTab)->ctTable[gCurColor] &&
           (anRGB->blue ==
        *position = 1;      // Color it.

    return true;            // To indicate that we've handled the
                            // color processing.

We'll make seven separations (one for each of the eight original QuickDraw colors except white). The code that follows is adapted from the Color ImageWriter Adventures sample and stores the different separations in a picture that uses only original QuickDraw primitives, so it can be sent with DrawPicture to the ImageWriter driver's grafPort with great results.

The process goes like this: Once we have the dithered image in our 4-bit GWorld, we create a 1-bit GWorld using exactly the same dimensions. We'll use this 1-bit GWorld to create our bitmap representations of each color separation. After setting the current GWorld to our 1-bit GWorld,colorSep, we call OpenPicture. This is critical because OpenPicture and OpenCPicture tie each open picture to the current port. (That's why you can have multiple pictures open at once as long as they're in different ports.) If we change ports, we can draw all we want and the calls will not be recorded into our picture. Only when we make the colorSep GWorld the current one will this picture's recording be enabled. Very cool.

PicHandle SeparateColors(PicHandle wPICT, Fixed scaleAmt,
                                 Boolean useDither)
    QDErr           err;
    GWorldPtr       savedGW;
    GDHandle        savedGDH;
    PicHandle       sepsPICT = nil;
    Rect            pictFrame;
    GWorldPtr       OQDGWorld = nil;
    PixMapHandle    srcPix, destPix;
    GWorldPtr       colorSep = nil;
    short           QDColor[7] =
                       {blackColor, yellowColor, magentaColor,
                        redColor, cyanColor, greenColor, blueColor};

// Save the current GWorld and GDevice.
    GetGWorld(&savedGW, &savedGDH);

// Set our global color table to the eight original QuickDraw colors
// and get the picture's frame.
        gOrgQDCTab = GetCTable(127);
        pictFrame = (*wPICT)->picFrame;

// Create a 4-bit GWorld that uses the eight original QuickDraw
// colors. If there are no errors, band the picture, using ditherCopy
// if desired and scaling the amount we need to. The result is a
// representation of the image in the eight original colors.
        err = NewGWorld(&OQDGWorld, 4, &pictFrame, gOrgQDCTab,
             nil, 0);
        if (!err)
            err = BandPicture(wPICT, OQDGWorld, scaleAmt, useDither);
// Create a new 1-bit GWorld for the separations.
        if (!err)
            err = NewGWorld(&colorSep, 1, &pictFrame, nil, nil, 0);
// Set the current GWorld to the 1-bit GWorld and create a picture.
// Note that this means that the picture is tied to the 1-bit GWorld.
// Only when that GWorld is current will data be recorded into the 
// picture.
            if (!err)
                SetGWorld(colorSep, nil);
                srcPix = GetGWorldPixMap(OQDGWorld);
                destPix = GetGWorldPixMap(colorSep);

                sepsPICT = OpenPicture(&pictFrame);

With the picture opened, the separations can be made. We go through each of the eight original colors (except white) and create a separation for that color. To do this, we set the current GWorld to one that's different from our picture's GWorld (to turn off recording). Next we install our searchProc and we CopyBits from the 4-bit GWorld to the 1-bit one. This gives us black bits only where the color in the original matches the current separation color. Then we delete the searchProc and set our current GWorld back to the 1-bit one. This reenables recording into our picture, and we record the foreground color for the current separation, followed by the separation's bitmap using srcOr mode. After all seven passes have been completed, we will get a picture with seven separations in it, overlaying each other to make the composite, which will differ slightly from the original because we lose some information to dithering. (See Figure 3.) We use srcOr mode so that the white is transparent; otherwise the white for each layer would overwrite the color from the previous layer.

                for (gCurColor = 0; (gCurColor < 7) && !err;
                    SetGWorld(savedGW, savedGDH);
                    CopyBits((BitMap *) *srcPix, (BitMap *) *destPix, 
                        &pictFrame, &pictFrame, srcCopy, nil);
                    SetGWorld(colorSep, nil);
                    CopyBits((BitMap *) *destPix,
                        (BitMap *) *destPix, &pictFrame,
                        &pictFrame, srcOr, nil);

// Close the picture, restore our saved GWorld/GDevice, and dispose of our
// GWorlds and the global color table. Finally, return the picture we
// created.

                SetGWorld(savedGW, savedGDH);

    if (gOrgQDCTab) DisposeCTable(gOrgQDCTab);
    if (OQDGWorld) DisposeGWorld(OQDGWorld);
    if (colorSep) DisposeGWorld(colorSep);
    return sepsPICT;

After the seventh separation is made, we can jump into our print loop and print the image with DrawPicture. The result is a nicely dithered color image. For best results, we'd use PrGeneral to set the printer grafPort's resolution higher than 72 dpi. In the case of a printer such as the ImageWriter, though, this has a tremendous performance hit. Here's another good opportunity to provide some user interaction and let the user decide what to do, via a preferences setting or by adding an item to the print job dialog.

[IMAGE Hersey_rev6.GIF]


[IMAGE Hersey_rev7.GIF]


[IMAGE Hersey_rev8.GIF]


[IMAGE Hersey_rev9.GIF]


[IMAGE Hersey_rev10.GIF]


[IMAGE Hersey_rev11.GIF]


[IMAGE Hersey_rev12.GIF]


[IMAGE Hersey_rev13.GIF]


[IMAGE Hersey_rev14.GIF]


Figure 3 Seven Separations Created With Original QuickDraw for a Color Image

This method of printing a picture to an ImageWriter with a color ribbon will achieve great results without doing anything special. However, there are two gotchas with it.

First, if you generate an image at a high resolution and export it to another application, the printing application needs to know to call PrGeneral to boost the printer port's resolution. However, you can export the pictures at 72 dpi, use a picFrame that's correct for 72-dpi display of the image, or use OpenCPicture to store the resolution in the picture. In any of those cases, DrawPicture will do the right thing with the picture, even though the application doesn't. To see this, print out the sample PICT called Separations Test to a color ImageWriter using TeachText. TeachText has no special code to handle ImageWriter printing and yet it prints the PICTs generated by this method just fine. Pictures you create this way will print to a color ImageWriter from any application and can be pasted into word processors and such for color image output. Pretty neat, huh?

But, unfortunately, srcOr mode doesn't necessarily print well with all printer drivers. This means that these way-cool images may not print way-cool on printers other than ImageWriters. This isn't a problem in the sample code because we use this method only if we're printing on an ImageWriter. PICTs that are pasted into a document might be printed on any printer, however, so exporting these pictures could create more problems than it solves.

For more details on how to do the separations, see the Color ImageWriter Adventures sample. The sample prints color pixMaps using this method and allows you to specify high-resolution or low- resolution output. I strongly urge you to print at least one of the sample images using the application in ditherCopy mode and specifying high-resolution output. The results may surprise you, as they did me.


When we're printing pixMaps to a grafPort and the printer doesn't have (or the user doesn't want to use) color capability, we need to use dithering (or more precisely, a special kind of dithering called halftoning) to get any kind of decent output. In other words, we need to convert pixMaps to dithered bitmaps. The Halftone Adventures sample demonstrates three different dithering methods: the CopyBits ditherCopy method, the "true" halftone method, and the lazy person's halftone method. Before we look at these, a note about resolution.

When printing halftoned images, it's best to set the printer to a square resolution (equal horizontal and vertical dpi). The reason is that when we use mixed resolutions, our halftone matrix becomes distorted, and that can distort the printed image. This happens because dots that should be a fixed distance apart are now closer to each other in one direction than in the other.

We can compensate for this distortion when we create our halftone matrix, but it's likely to be a great deal of work, which is only marginally justified. All of the halftone routines in the sample code print using square resolutions. (They call the GetBestDPI routine described earlier with the wantSquareDPI parameter set to true.)

The ditherCopy method uses CopyBits to dither the image to a 1-bit GWorld at device resolution and print that. If you're working with a device that has a low resolution (prints big dots) and a relatively constant physical dot size -- such as the ImageWriter -- then this method works fine. If, however, you're printing to a device that has a high resolution and a variable pixel size (from device to device, or even within the same device across time or due to variations in amount and type of toner, humidity, and paper type), this method results in image distortion. Figure 4 was dithered to a LaserWriter using this method, and the resulting distortion is very noticeable.

The distortion you see in Figure 4 is due to pixel error (the difference between the physical pixel drawn on the page and its size as the driver or rendering system models it). Since dithering must occur at device resolution, it's hard to compensate for the device pixel error when a dithered image is printed. Halftoning, on the other hand, increases the size of each dot, negating the pixel error thatoccurs during printing. Thus halftoning results in better output on devices such as the LaserWriter. This phenomenon is discussed further in "Making the Most of Color on 1-Bit Devices" indevelopIssue 9 and is one of the main reasons that just doing a straight dither is not acceptable for most cases. The ditherCopy method does, however, provide a good benchmark to judge the other methods against.

[IMAGE Hersey_rev15.GIF]

Figure 4 Distorted Sample Output From the ditherCopy Method

The "true" halftone method is described in "Making the Most of Color on 1-Bit Devices" indevelopIssue 9. You can read all about it there and try it out in the Halftone Adventures sample. Note that the routine in the sample code uses 8 x 8 halftones, but the algorithm described in the Issue 9 article is general and will work at any angle, any frequency, and any resolution. Also, since the sample's routine accepts only 8-bit-deep and 32-bit-deep pixMaps, the source image is passed in as a 32-bit- deep pixMap. When you use this sample code, an image may take one to two minutes to render before being printed, but the code can be optimized to increase its speed. Figure 5 provides an example of the kind of output we can expect using this method.

[IMAGE Hersey_rev16.GIF]

Figure 5 Sample Output From the "True" Halftone Method

I came up with the lazy person's halftone method to create fast "halftone-ish" output that looks very good and prints very fast. It works especially well on LaserWriters. Typical images render in 12 seconds or so (before printing), and I'm sure optimization would shorten this time. But note that this is not intended to be a general solution like the "true" halftone method; its usefulness is restricted to halftones at one angle, one frequency, and a square resolution.

Strictly speaking, this isn't halftone generation but rather halftone approximation with patterns. The difference is that in "true" halftoning, a halftone matrix is cookie-cuttered around the image, and adjacent pixels are taken into account when the halftones are created. In this way, the appearance of strong patterns (such as vertical stripes) can be removed. With the method I propose, the output appears to be a 0º 4 x 4 halftone, not a 45º 8 x 8 as in the Halftone Adventures implementation of the true halftone method. While this approach doesn't generate strong patterns, the absence of a 45º halftone is somewhat noticeable on lower-resolution printers like ImageWriters or those with drivers that don't support PrGeneral (and therefore must be used at 72 dpi).

Here's how it works: First, we dither the original image to a 4-bit grayscale GWorld, at 1/4 the optimal printer resolution. This may mean stretching or shrinking the original image. Next we find out how much of the printed image will fit on the paper. We use this information to limit the amount of data we're working with to just the pixels that will end up on paper. If the image extended 5 inches off the right edge of the paper, for example, it would be a waste of time to process that extra 5 inches. Once we have the dithered data and the bounds we're working with, we create a 1-bit GWorld that's four times as big as the 4-bit one. (This also means that it's at our printer port's resolution.) Going through the source (4-bit) image one pixel at a time, we create the halftoned output by matching up each pixel's index value with one of the patterns shown in Figure 6 and drawing that 4 x 4 pattern in our 1-bit GWorld.

For example, if we find a pixel has the index value of 8, the pattern with 8 dots in it is used. With 4 x 4 patterns, we could actually create 17 unique patterns (counting the pattern created when no dots are used). However, this wouldn't be helpful since our image has only 16 shades of gray in it. Therefore, we ignore one, and I chose to drop the pattern for 15. (The pattern designated 15 is really one for 16.) The reason for using the pattern for 16 in the 15 spot is that black in our image will have a value of 15, and we want to make sure that black pixels are rendered as totally black patterns. Otherwise, the resulting image would have no solid black in it.

[IMAGE Hersey_rev17.GIF]

Figure 6 The Patterns Used to Approximate Halftones

Once the entire image has been halftoned, we just CopyBits it to the printer port. Figure 7 provides an example of the kind of output we can expect using this method.

This method works especially well when we're printing at a high resolution. On the LaserWriter at 300 dpi, for example, the 4 x 4 patterns are so small (1/75") that they appear as a single dot. It's hard to believe that the output in Figure 7 was printed in just black! As you can see by comparing this output to the halftone output in Figure 5, there's very little difference between the two, and for speed considerations, the lazy person's method may be a viable alternative.

[IMAGE Hersey_rev18.GIF]

Figure 7 Sample output from the Lazy Person's Halftone Method


We now have the methods that enable us to obtain high-quality output from the whole range of possible printing configurations when we print pixMaps. All we still need is a way to decide which method to use at print time.

To make this decision, we need to determine only three things:

  • Do we have a cGrafPort or a grafPort?
  • If a grafPort, is this an ImageWriter?
  • If an ImageWriter, is a color ribbon installed?

That's it! These things can be determined in ways that will be compatible now and in the future. Let's take a quick look at the questions and how to determine their answers.

We can determine whether we have a cGrafPort or a grafPort by checking the rowBytes value in the port returned by PrOpenDoc. If it's negative (the high bit is set), we have a cGrafPort. Otherwise, we have a grafPort. In C this reads:

Boolean HaveColorPrPort(THPrint hPrint, OSErr *anErr)
    Boolean     haveCGrafPort = false;
    TPPrPort        dummyPort;
    TPrStatus   statusRec;

    if (hPrint)
        // Open a document and check for errors.
        dummyPort = PrOpenDoc(hPrint, nil, nil);
        *anErr = PrError();

        // If no errors, check the port's rowBytes value.
        if (*anErr == noErr)
            haveCGrafPort = (dummyPort->gPort.portBits.rowBytes < 0);

        // We don't want to print yet, so kill the job by setting an
        // error.  Clean up by closing the document and calling
        // PrPicFile to delete any spool file we may have created.
        // Finally, clear the error we set.
            if ((*hPrint)->prJob.bjDocLoop == bSpoolLoop)
                PrPicFile(hPrint, dummyPort, nil, nil, &statusRec);
        *anErr = nilHandleErr;
    return haveCGrafPort;

The routine calls PrOpenDoc, checks the value of the returned port's rowBytes (negative means cGrafPort), and then posts an error to halt printing and calls PrCloseDoc. Finally, it calls PrPicFile to delete any spool file that may have been generated, clears the error it set, and returns true or false depending on whether or not the port we looked at was a cGrafPort. It's not glamorous, but it works.

If as a result of this inquiry we find that we have a cGrafPort, we give the go-ahead to printing with Color QuickDraw calls. If not, we go on to the next question.

We can find out if we're talking to the ImageWriter driver by getting the high byte of a validated print record's prStl.wDev field. If the high byte is 1 or 5, we're using the ImageWriter or the ImageWriter LQ driver. In C:

#define IW_wdevID 1
#define IWLQ_wdevID 5
unsigned char   devID;

devID = (*hPrint)->prStl.wDev >>8;
if ((devID == IW_wdevID) || (devID == IWLQ_wdevID))
    /* Then we have an ImageWriter. */;

This method is described in the Macintosh Technical Note "Optimizing for the LaserWriter -- Techniques" and is strongly discouraged there. So why am I suggesting that you use it? Well, unfortunately, there's no other reliable way to do this. In fact, checking the wDev has begrudgingly become an acceptable thing; developers have become so used to this method that we'd need to give ample warning before breaking it. However, you should expect that one of these days, checking wDevs will not be supported anymore. As soon as Apple provides a better method, you should jump on the code conversion bandwagon and replace all your wDev-snooping code.

It's important to make this checkafterthe cGrafPort check because there are third-party printer drivers for the ImageWriter that support 8-bit color through cGrafPorts. If we first check for an ImageWriter and then jump to the ImageWriter grafPort printing code, we may be sacrificing output quality, since we may have been able to print using the Color QuickDraw methods described for cGrafPorts.

Anyway, if we find that we have an ImageWriter, we go on to the next question. Otherwise, we assume we have a monochrome printer and we accordingly launch the halftoning routine for printing.

In the case of the ImageWriter, we have two options for determining whether a color ribbon is installed: we can either ask the printer or ask the user.

To ask the printer, we would go through the serial driver if the ImageWriter were connected to a serial port or through AppleTalk Printer Access Protocol (PAP) if the printer were an AppleTalk ImageWriter. But this approach has a few problems. First, even if the user has a color ribbon, he may not want to use it. He may be printing rough copies of his work and want to save the color ink until he's ready to make a final copy. Or he may know that his color ribbon is worn out and prints well only in black. A second problem is that the printer must be turned on and selected when we query it, or we'll hang until we time out. The delay is likely to thoroughly annoy our users.

Third, there's a problem with ImageWriter I support: the "ESC ?" query sequence (see theImageWriter Technical Reference Manual) that's used to ask a serial ImageWriter if it has a color ribbon is not supported by the ImageWriter I. This means our query routine will hang until it times out, and we still won't know whether the printer has a color ribbon. A final and more compelling argument against performing the color ribbon query is that the methods that work today are unlikely to work under QuickDraw GX. Whether or not you decide to take advantage of QuickDraw GX's abilities, you should avoid implementing code that will make your application incompatible with it.

So we're left with the option of asking the user. The easiest way to do this is through a preferences setting. A slightly more coding-intensive but preferred approach is to add controls to the print job dialog. This might be a checkbox that simply says "Print in color," a pop-up menu that offers color or black and white (as in the Apple IIGS ImageWriter driver version 4.0), or, as I chose in the Final Adventure sample code, radio buttons for color or halftone output.

Even with this method, there are a few problems. If we add the control to every printer driver's job dialog, it will appear even when printers return cGrafPorts, in which case we'll want to ignore the setting. Also, if a checkbox is added to a driver like the 7.0 LaserWriter driver, the user will see redundant settings: a set of radio buttons for Color/Grayscale versus Black & White printing, and another checkbox for "Print in color." The way to get around this problem is to add the output controls only when the ImageWriter or ImageWriter LQ driver is being used, something we've already discussed how to determine. If we implement this solution, we'll want to store the last selected value for the control and default to it whenever the dialog is displayed. That will spare users from possibly having to click an extra button every time they print. However, if they change ImageWriters between print jobs, the saved flag may be incorrect for the new printer. This is a minor glitch that will become apparent the next time they print.

The bottom line here is that if we determine that our application is dealing with an ImageWriter with a color ribbon installed, we print using the eight original colors. Otherwise, we use our halftoning routine and print in black.

To see how this decision process translates into code, take a look at the DoPrint routine in the Final Adventure sample on the CD. That sample rolls together into one neat package all the methods we've discussed in this article. Study it and give it a try to see how it works.


In this article, we've looked at the problems associated with color printing under the current printing architecture. We've seen that there's a real need for application developers to provide color printing support in their applications. We've also looked at techniques for printing high-quality representations of pictures containing pixMaps. These techniques consist of banding images through GWorlds for color-capable printers and drivers, creating color separations for printing on ImageWriters with color ribbons, and creating dithered halftones for black-and-white output.

I mentioned that these techniques aren't intended for printing pictures that contain text, because when text is converted to pixMaps, all of the font information is lost, and the result is chunky, poor- quality text that's hard to read. You should always draw text separately from bitmaps or pixMaps, if at all possible. One way to do this is to write a routine to split a picture into two pictures: one with pixMaps, bitmaps, and foreground colors, and the other with everything except pixMaps and bitmaps (we'd want foreground colors in both). Once you have the two pictures, you can render the first using the methods discussed in this article and the second with DrawPicture. The order is important if we want the text to appear on top of the pixMap data. Remember to scale both pictures to the grafPort's or cGrafPort's resolution during printing.

As more technologies make use of color on the Macintosh, and more scanners and jumbo color monitors are shipped, users are going to need a way to get realistic hard copies of their screen displays. And although the color capabilities of Apple drivers and printers will continue to improve in both the short and long term (through such technologies as QuickDraw GX, ColorSync, and new printer drivers), interim solutions such as the ones proposed here will be needed for some time to come.

THE STORY BEHIND COLOR QUICKDRAW SUPPORTSo why is it that the LaserWriter didn't support cGrafPorts until the 6.0 LaserWriter driver? And why is it that the 7.0 ImageWriter driver still doesn't support cGrafPort printing?

The first answer is simple. Color QuickDraw didn't exist when the LaserWriter driver was created back in 1985. It wasn't until 32-Bit QuickDraw came on the scene that the driver was revised to support color/grayscale printing. Since the driver wasn't originally designed with Color QuickDraw in mind, this support represented major changes to the source code. As such, it took until version 6.0.2 for most of the glitches to be worked out. Even today, the LaserWriter driver is essentially an old-style QuickDraw driver with Color QuickDraw support patched in.

The ImageWriter driver never was revised, except to add color tables to the print job dialogs in the 6.1 version. Why wasn't the driver revised? Well, for the ImageWriter driver to fully support Color QuickDraw, it would essentially need to be rewritten. Since there's been no overwhelming demand and since color printing solutions are available via the color LaserWriter driver and third-party printers and drivers, no one has rewritten the driver to provide color support.

At some point in the future, all of Apple's printer drivers will support Color QuickDraw calls. But for now, applications should be aware that a printer driver returns either a cGrafPort or a grafPort, and it's the application's responsibility to "do the right thing" regardless of the port type.


  • "Making the Most of Color on 1-Bit Devices" by Konstantin Othmer and Daniel Lipton, develop Issue 9.
  • "Print Hints From Luke & Zz: CopyMask, CopyDeepMask, and LaserWriter Driver 7.0" by Pete ("Luke") Alexander, develop Issue 8.
  • "Print Hints From Luke & Zz: Color Printing With LaserWriter 6.0 Revisited" by Pete ("Luke") Alexander,developIssue 6.
  • "Meet PrGeneral, the Trap That Makes the Most of the Printing Manager" by Pete ("Luke") Alexander,developIssue 3.
  • Macintosh Technical Notes "Optimizing for the LaserWriter -- Techniques" (formerly #72) and "How to Add Items to the Print Dialogs" (formerly #95).
  • Fundamentals of Interactive Computer Graphics by
  • D. Foley and A. Van Dam (Addison-Wesley, 1982). Pretty much the standard in computer graphics books.
  • Graphics Gems edited by A. S. Glassner (Academic Press, 1990). Graphics Gems II edited by J. Arvo (Academic Press, 1991). Lots of quick routines to do neat image processing stuff without the brain-bashing.

DAVE HERSEY is a member of the Printing, Imaging, and Graphics (PIGs) group in Apple Developer Technical Support. Before leaving his boyhood home of Newport, Rhode Island, more than two years ago, Dave churned out code for a number of different software developers, writing applications that ranged from a popular accounting package to flatbed scanner software. When he's not absorbing radiation in front of his computer, Dave enjoys vacationing at the family summer camp in Wayne, Maine (no kidding), watching CNN on his 35-inch television ("It's still not big enough"), and playing Duplos with his nephews. Even with such a busy agenda, Dave still finds time to torment his peers with occasional practical (and impractical) jokes, in true DTS style.*

The LaserWriter's physical resolution is 300 dpi but printer drivers on the Macintosh return a 72-dpi port by default, because 72 dpi is the native resolution of QuickDraw. It's important to realize that unless you explicitly set the port's resolution to 300 dpi, you're working in a 72-dpi port and the effective resolution is cut by more than three quarters. *

The original QuickDraw colors and their predefined constants are listed on page 158 of Inside Macintosh Volume I.*

Color QuickDraw searchProcs are discussed in Inside Macintosh Volume V, pages 145-147.*

Adding controls to the print job dialog is described in the Macintosh Technical Note "How to Add Items to the Print Dialogs" and illustrated by PDlog Expand in the Snippets folder on the Developer CD Series disc.*

THANKS TO OUR TECHNICAL REVIEWERS Pete ("Luke") Alexander, Hugo Ayala, Dan Lipton, Konstantin Othmer, Sean Parent*


Community Search:
MacTech Search:

Software Updates via MacUpdate

jAlbum 19.2 - Create custom photo galler...
With jAlbum, you can create gorgeous custom photo galleries for the Web without writing a line of code! Beginner-friendly, with pro results - Simply drag and drop photos into groups, choose a design... Read more
BlueStacks 4.140.13 - Run Android applic...
BlueStacks App Player lets you run your Android apps fast and fullscreen on your Mac. Feature comparison chart Version 4.140.13: Highlights/Bug Fixes: Feel free to use BlueStacks as your go to... Read more
Adobe Premiere Pro CC 2020 14.0.1 - Digi...
Premiere Pro CC 2020 is available as part of Adobe Creative Cloud for as little as $52.99/month. The price on display is a price for annual by-monthly plan for Adobe Premiere Pro only Adobe Premiere... Read more
VirtualBox 6.1.2 - x86 virtualization so...
VirtualBox is a family of powerful x86 virtualization products for enterprise as well as home use. Not only is VirtualBox an extremely feature rich, high performance product for enterprise customers... Read more
RoboForm 8.6.8 - Password manager; syncs...
RoboForm is a password manager that offers one-click login, mobile syncing, easy form filling, and reliable security. Password Manager. RoboForm remembers your passwords so you don't have to! Just... Read more
Postbox 7.0.11 - Powerful and flexible e...
Postbox is a new email application that helps you organize your work life and get stuff done. It has all the elegance and simplicity of Apple Mail, but with more power and flexibility to manage even... Read more
calibre 4.9.0 - Complete e-book library...
Calibre is a complete e-book library manager. Organize your collection, convert your books to multiple formats, and sync with all of your devices. Let Calibre be your multi-tasking digital librarian... Read more
Notability 4.2 - Note-taking and annotat...
Notability is a powerful note-taker to annotate documents, sketch ideas, record lectures, take notes and more. It combines, typing, handwriting, audio recording, and photos so you can create notes... Read more
FoldersSynchronizer 5.0.1 - Synchronize...
FoldersSynchronizer is a popular and useful utility that synchronizes and backs-up files, folders, disks and boot disks. On each session you can apply special options like Timers, Multiple Folders,... Read more
Sketch 62 - Design app for UX/UI for iOS...
Sketch is an innovative and fresh look at vector drawing. Its intentionally minimalist design is based upon a drawing space of unlimited size and layers, free of palettes, panels, menus, windows, and... Read more

Latest Forum Discussions

See All

The Alliance update to Out There: Omega...
Out There is an old go-to recommendation for a lot of mobile stalwarts, but I could never really get into it. This sci-fi survival game that blended elements of interactive fiction and roguelike mechanics just felt a little off-balance and a little... | Read more »
Animal Fury Destination is an action-adv...
Animal Fury Destination is an action-adventure game from independent, Colombian developer Ignicion Games. It's a 3D action game where you'll play as various different characters as you embark on a quest to stop an evil crow sorcerer. [Read more] | Read more »
Shadowgun War Games Closed Beta Impressi...
Shadowgun: War Games is an upcoming free-to-play multiplayer shooter that’s essentially just an Overwatch knock-off. There are hero characters with special abilities, and you compete in 5-v-5 game modes where the goal is to use superior team... | Read more »
Slingsters is a physics-based puzzler fo...
Slingsters is a physics-based puzzle game where the aim is to collect various different monsters by flinging them from one side of a level to the other and into a box. It's also the first game from Nappy Cat and is available now for iOS and... | Read more »
Spiritwish's latest update sees the...
A sizeable update has hit Nexon's MMORPG Spiritwish today. It brings a new game mode, characters and there will also be a special event to celebrate the update with a firework display. [Read more] | Read more »
Maze Machina, a turn-based puzzler from...
The latest game from Arnold Rauers also known as Tiny Touch Tales is now available. You may be familiar with one of his many excellent titles such as Card Crawl, Enyo and Card Thief. His latest endeavour is called Maze Machina and you can grab it... | Read more »
Mario Kart Tour's Ice Tour races to...
Can you believe Mario Kart Tour is already on its 9th tour? The game only launched back in September, and since then it's become increasingly tricky to keep on top of the amount of new content Nintendo is pumping out. [Read more] | Read more »
Apple Arcade: Ranked - Top 50 [Updated 1...
In case you missed it, I am on a quest to rank every Apple Arcade game there is. [Read more] | Read more »
Marvel Future Fight's latest update...
Marvel Future Fight's latest update has added an all-new team of heroes to recruit and do battle with. The 'Warriors of the Sky' include Blue Dragon, War Tiger, Sun Bird, and Shadow Shell. As is the norm, each character comes with their own unique... | Read more »
Klee: Spacetime Cleaners is a fast-paced...
Klee: Spacetime Cleaners is a fast-paced auto-shooter that sports a cute retro aesthetic thathad racked up an impressive 100,000 pre-registers prior to its release. It's available now for both iOS and Android. [Read more] | Read more »

Price Scanner via

Just in! Apple iMacs on sale for $100-$150 of...
B&H Photo has new 2019 21″ and 27″ 5K iMacs on stock today and on sale for up to $150 off Apple’s MSRP, with prices starting at only $999. These are the same iMacs sold by Apple in their retail... Read more
Save $100 on the 13″ 1.4GHz MacBook Pro at th...
Apple resellers have 13″ 1.4GHz MacBook Pros on sale today for $100 off Apple’s MSRP, and some are including free overnight delivery: (1) Amazon has new 2019 13″ 1.4GHz MacBook Pros on sale for $100... Read more
AT&T offers free 64GB Apple iPhone XS wit...
Open a new line of service with AT&T, and they will include a free 64GB iPhone XS. Credit for the phone is applied monthly over a 30 month lease. The fine print: “Limited Time Requires new line... Read more
New Verizon deal: Apple iPhone XR for $300 of...
Switch to Verizon and sign up with one of their Unlimited plans, and Verizon will take $300 off the price of an Apple iPhone XR (regularly $749), plus get a free $200 prepaid Mastercard. This is an... Read more
Amazon’s popular AirPods sale is back with mo...
Amazon has new 2019 Apple AirPods on sale today ranging up to $40 off MSRP, starting at $129, as part of their popular Apple AirPods sale. Shipping is free: – AirPods Pro: $234.98 $15 off MSRP –... Read more
Apple’s top of the line 10.5″ 256GB WiFi + Ce...
B&H Photo has the top of the line 10.5″ 256GB WiFi + Cellular iPad Air on sale for $599 shipped. That’s $180 off Apple’s MSRP for this model and the cheapest price available. Overnight shipping... Read more
Apple’s refurbished iPad Pros are the cheapes...
Apple has Certified Refurbished 11″ iPad Pros available on their online store for up to $220 off the cost of new models. Prices start at $679. Each iPad comes with a standard Apple one-year warranty... Read more
Just in: Take $100 off the price of the 3.0GH...
Apple resellers are offering new 2018 6-Core Mac minis for $100 off Apple’s MSRP today, only $999. B&H Photo has 6-Core Mac minis on sale for $100 off Apple’s standard MSRP. Overnight shipping is... Read more
Apple has 4-core and 6-core 2018 Mac minis av...
Apple has Certified Refurbished 2018 Mac minis available on their online store for $120-$170 off the cost of new models. Each mini comes with a new outer case plus a standard Apple one-year warranty... Read more
Amazon offers $200 discount on 13″ MacBook Ai...
Amazon has new 2019 13″ MacBook Airs with 256GB SSDs on sale for $200 off Apple’s MSRP, now only $1099, each including free shipping. Be sure to select Amazon as the seller during checkout, rather... Read more

Jobs Board

*Apple* Mobility Pro - Best Buy (United Stat...
**744429BR** **Job Title:** Apple Mobility Pro **Job Category:** Store Associates **Store NUmber or Department:** 000574-Garner-Store **Job Description:** At Best Read more
Geek Squad *Apple* Consultation Professiona...
**757963BR** **Job Title:** Geek Squad Apple Consultation Professional **Job Category:** Store Associates **Store NUmber or Department:** 000433-Henrietta-Store Read more
*Apple* Computing Professional - Best Buy (U...
**754611BR** **Job Title:** Apple Computing Professional **Job Category:** Store Associates **Store NUmber or Department:** 000142-Milpitas-Store **Job Read more
Best Buy *Apple* Computing Master - Best Bu...
**745058BR** **Job Title:** Best Buy Apple Computing Master **Job Category:** Store Associates **Store NUmber or Department:** 001080-Lake Charles-Store **Job Read more
Geek Squad *Apple* Consultation Professiona...
**756640BR** **Job Title:** Geek Squad Apple Consultation Professional **Job Category:** Store Associates **Store NUmber or Department:** 000484-Manchester-Store Read more
All contents are Copyright 1984-2011 by Xplain Corporation. All rights reserved. Theme designed by Icreon.