Winter 92 - MAKING THE MOST OF COLOR ON 1-BIT DEVICES
MAKING THE MOST OF COLOR ON 1-BIT DEVICES
KONSTANTIN OTHMER AND DANIEL LIPTON
Macintosh developers faced with the dilemma of which platform to develop software
for--machines with the original QuickDraw or those with Color QuickDraw--can
always choose to write code that runs adequately on the lower-end machines and gives
additional functionality when running on the higher-end machines. While this sounds
like a simple and elegant solution, it generally requires a great deal of development and
testing effort. To make this effort easier and the outcome more satisfying, we offer
techniques to save color images and process them for display on 1- bit
(black-and-white) devices.
Suppose you're writing a program that controls a 24-bit color scanner and you'd like
it to work on all Macintosh computers. The problem you'll run into is that machines
with the original QuickDraw (those based on the 68000 microprocessor) only have
support for bitmaps, thus severely crippling the potential of your scanner. But don't
despair. In our continuing quest to add Color QuickDraw functionality to machines with
original QuickDraw, we've worked out techniques to save color images and process
them for display, albeit in black and white, on the latter machines. We've also come up
with a technique to address the problem of a laser printer's inability to resolve single
pixels, which results in distorted image output. This article and the accompanying
sample code (on theDeveloper CD Series disc) share these techniques with you.
SAVING COLOR IMAGES
The key to saving color images is using pictures. Recall that a picture (or PICT) in
QuickDraw is a transcript of calls to routines that draw something--anything. A PICT
created on one Macintosh can be displayed on any other Macintosh (provided the
version of system software on the machine doing the displaying is the same as or later
than the version on the machine that created the picture). For example, on a Macintosh
Plus you can draw a PICT containing an 8-bit image that was created on a Macintosh II.
With System 7, you can even display PICTs containing 16-bit and 32-bit pixMaps on
machines with original QuickDraw. (Of course, they will only be displayed as 1-bit
images there.)
Creating a picture normally requires three steps:
1. Call OpenPicture to begin picture recording.
2. Perform the drawing commands you want to record.
3. Call ClosePicture to end picture recording.
The catch is that the only drawing commands that can be recorded into a picture are
those available on the Macintosh on which your application is running. Thus, using
this procedure on a machine with original QuickDraw provides no way to save color
pixMaps into a picture, since there's no call to draw a pixMap. In other words, you
can't create an 8-bit PICT on a Macintosh Plus and see it in color on a Macintosh II.
But that's exactly what would make a developer's life easier--the ability to create a
PICT containing deep pixMap information on a machine without Color QuickDraw. With
this ability, you could capture a color image in its full glory for someone with a Color
QuickDraw machine to see, while still being able to display a 1-bit version on a
machine with original QuickDraw.
To get around the limitations of the normal procedure, we came up with a routine
called CreatePICT2 to manually create a PICT containing color information. Your
application can display the picture using DrawPicture. Now, you may be wondering
whether creating your own pictures is advisable. After all, Apple frowns on developers
who directly modify private data structures, and isn't that what's going on here? To
ease your mind, see "But Don't I Need a License to Do This?
The parameters to CreatePICT2 are similar to those for the QuickDraw bottleneck
procedure stdBits. The difference is that CreatePICT2 returns a PicHandle and does not
use a maskRgn.
The first thing the routine does is calculate a worst-case memory scenario and allocate
that amount of storage. If the memory isn't available, the routine aborts, returning a
NIL PicHandle. You could easily extend this routine to spool the picture to disk if the
memory is not available, but that's left as an exercise for you. (Hint : Rather than
writing out the data inline as is done here, call a function that saves a specified
number of bytes in the picture. Have that routine write the data to disk. Essentially,
you need an equivalent to the putPicData bottleneck.)
At this point the size of the picture is not known (since there's no way to know how
well the pixMap will compress) so we simply skip the picSize field and put out the
picture frame. Next is the picHeader. CreatePICT2 creates version $02FF pictures,
with a header that has version $FFFF. This version of the header tells QuickDraw to
ignore the header data. (OpenCPicture, available originally in 32-Bit QuickDraw
version 1.2 and in Color QuickDraw in System 7, still creates version $02FF
pictures, but the header version is now $FFFE and contains picture resolution
information.)
In addition, the bounds of the clipping region of the current port are put in the picture.
Without this, the default clipping region is wide open, and some versions of QuickDraw
have trouble drawing pictures with wide-open clipping regions.
Next we put out an opcode--either $98 (PackBitsRect) or $9A (DirectBitsRect),
depending on whether the pixMap is indexed or direct. Then the pixMap, srcRect,
dstRect, and mode are put in the picture using the (are you ready for this?)
PutOutPixMapSrcRectDstRectAndMode routine. Finally, either
PutOutPackedDirectPixData or PutOutPackedIndexedPixData is called to put out the
pixel data.
There's an important difference between indexed and direct pixMaps here. The
baseAddr field is skipped when putting out indexed pixMaps and is set to $000000FF
for direct pixMaps. This is done because machines without support for direct pixMaps
(opcode $9A) read a word from the picture, skip that many bytes, and continue
picture parsing. When such a machine encounters the $000000FF baseAddr, the
number of bytes skipped is $0000 and the next opcode is $00FF, which ends the
picture playback. A graceful exit from a tough situation.
An interesting fact buried in the PutOutPixMapSrcRectDstRectAndMode routine is the
value of packType. All in-memory pixMaps (that aren't in a picture) are assumed to
be unpacked. Thus, you can set the packType field to specify the type of packing the
pixMap should get when put in a picture. "The Low-Down on Image Compression
(develop Issue 6, page 43) gives details of the different pixMap compression schemes
used by QuickDraw. Note that all of QuickDraw's existing packing schemes lose no
image quality. QuickTime (the new INIT described in detail in the lead article indevelop
Issue 7) adds many new packing methods, most of which sacrifice some image quality
to achieve much higher compression. Anyway, these routines support only the default
packing formats: 1 (or unpacked) for any pixMap with rowBytes less than 8, 0 for all
other indexed pixMaps, and 4 for 32-bit direct pixMaps with rowBytes greater than
8. Note that these routines do not support 16-bit pixMaps.
Finally, the end-of-picture opcode is put out and the handle is resized to the amount
actually used.
PicHandle CreatePICT2(PixMap *srcBits, Rect *srcRect, Rect *dstRect,
short mode)
PicHandle myPic;
short myRowBytes;
short *picPtr;
short iii;
long handleSize;
#define CLIPSIZE 12
#define PIXMAPRECSIZE 50
#define HEADERSIZE 40
#define MAXCOLORTABLESIZE 256*8+8
#define OPCODEMISCSIZE 2+8+8+2 /* opcode+srcRect+dstRect+mode */
#define ENDOFPICTSIZE 2
#define PICSIZE PIXMAPRECSIZE + HEADERSIZE + MAXCOLORTABLESIZE +
ENDOFPICTSIZE + OPCODEMISCSIZE + CLIPSIZE
myRowBytes = srcBits->rowBytes & 0x3fff;
/* Allocate worst-case memory scenario using PackBits packing. */
myPic = (PicHandle) NewHandle(PICSIZE + (long)
((myRowBytes/127)+2+myRowBytes)*(long)(srcBits->bounds.bottom
- srcBits->bounds.top));
if(!myPic)
return(0);
/* Skip picSize and put out picFrame (10 bytes). */
picPtr = (short *) (((long)*myPic) + 2);
*picPtr++ = dstRect->top;
*picPtr++ = dstRect->left;
*picPtr++ = dstRect->bottom;
*picPtr++ = dstRect->right;