September 94 - Making the Most of QuickDraw GX Bitmaps
Making the Most of QuickDraw GX Bitmaps
DAVID SUROVELL
Besides letting you do a lot of cool things with geometric shapes and typography,
QuickDraw GX has useful tools for manipulating bitmaps. For example, bitmap shapes
(the QuickDraw GX counterpart to pixMaps) can be skewed, rotated, and scaled, and
transforms allow these operations to be performed repeatedly without data loss.
Bitmap shapes can share image data, can be used to clip other shapes, and can reside on
disk instead of in memory. This article tells how you can use QuickDraw GX to improve
the way you handle bitmapped graphics.
New users of QuickDraw GX will probably start by going throughInside Macintosh:
QuickDraw GX Objects or the article "Getting Started With QuickDraw GX" indevelop
Issue 15. If you're mainly a QuickDraw programmer, however, you may have a lot of
questions about how QuickDraw GX applies specifically to bitmaps -- probably the
most commonly used graphic objects. As it turns out, it can do most anything
QuickDraw can do, and quite a few useful and exotic new things besides.
If you have at least a nodding familiarity with QuickDraw GX, this article will give
useful tips on how to apply your knowledge to bitmap shapes. If you're a QuickDraw GX
neophyte, this article will confuse you from time to time, but you may learn enough to
decide to make the leap to QuickDraw GX.
CREATING BITMAP SHAPES
It takes about the same information to create a bitmap shape in QuickDraw GX as it does
to make a pixMap in QuickDraw. The biggest difference is that while QuickDraw insists
that you calculate the size of the image buffer and allocate it explicitly, QuickDraw GX
can optionally allocate it for you when the shape is created. This is illustrated in the
code in Listing 1, which creates an indexed bitmap shape.
For indexed pixelSize values (1, 2, 4, or 8), you set the gxBitmap's space field to
gxIndexedSpace and its set field to a color set (the QuickDraw GX equivalent of a
QuickDraw color table) with an appropriate number of entries. Direct pixelSize
values (16 or 32) require that the set field be nil. Forexample, to make the routine in
Listing 1 create a 16-bit bitmap shape, you would set the gxBitmap's space field to
gxRGB16Space and its set field to nil.
Listing 1. Creating an indexed bitmap shape
gxShape CreateIndexedBitmapShape(long horiz, long vert,
long targetDepth)
{
gxBitmap bitShapeInfo;
gxColorSet targetSet;
gxShape resultShape;
if ((horiz <= 0) || (vert <= 0))
return nil;
if (targetDepth > 8)
return nil;
// Create a familiar "color" gxColorSet.
// (The default gxColorSet is a gray ramp.)
targetSet = GetStandardColorSet(targetDepth);
if (targetSet == nil)
return nil;
// Let QDGX calculate the image buffer block size and
// allocate it.
bitShapeInfo.image = nil;
bitShapeInfo.rowBytes = 0;
bitShapeInfo.width = horiz;
bitShapeInfo.height = vert;
bitShapeInfo.pixelSize = targetDepth;
bitShapeInfo.space = gxIndexedSpace;
bitShapeInfo.set = targetSet;
// Use the default color profile.
bitShapeInfo.profile = nil;
resultShape = GXNewBitmap(&bitShapeInfo, nil);
return resultShape;
}
Note that the gxBitmap's rowBytes is a long, not a short as in QuickDraw. This means
no more convoluted rowByte hacks, no more magic bits needed for flags, and no more
unreasonable limits on image width.
Note also that the gxBitmap contains a profile field, a reference to a gxColorProfile
(essentially an object with ColorSync data wrapped inside). If this field is nil,
QuickDraw GX uses its default profile. Color matching occurs only when the target
view port has the gxEnableMatchPort attribute set -- by default, it's off.
MANIPULATING BITMAP SHAPES
Once a bitmap shape is created, you can access and change its characteristics with
GXGetBitmap and GXSetBitmap.
GXGetBitmap(targetShape, &bitmapInfo, &origin);
// Alter the necessary gxBitmap fields here.
. . .
GXSetBitmap(targetShape, &bitmapInfo, &origin);
GXSetBitmap is similar to QuickDraw's UpdateGWorld; it lets you change bitmap depth,
color specification, and size. To change specific attributes, you may need to modify a
combination of fields.
To change a bitmap's width or height, set the width or height field. If QuickDraw GX
originally allocated the image buffer, you can set rowBytes to 0 and the image field to
nil, and QuickDraw GX will reallocate the buffer. If you allocated the buffer yourself,
you'll have to maintain it yourself.
An image isn't scaled when you change size this way. If you increase the width or
height, the new areas contain undefined values; if you decrease them, the image is
truncated. Bitmap scaling is discussed later in this article.*
To change a bitmap's pixel depth, set the pixelSize field to the desired depth. If the
bitmap needs a new color set (which it will, unless the new depth is greater than 8
bits), create it and assign it to the set field. An example that changes the depth to 4-bit
is shown in Listing 2.
To change a bitmap's color characteristics, just change the set, space, and profile
fields. No changes to pixel data will occur -- all pixel values will be interpreted in the
new color set. To transform pixel values, you'd need to set up a new bitmap shape and
draw the existing bitmap into it. (The offscreen library routine CopyToBitmaps is
ideal for this.)
Listing 2. Changing the depth of a bitmap shape
void ChangeDepthToFour(gxShape bitmapShape)
{
gxBitmap imageInfo;
if ((bitmapShape != nil) &&
(GXGetShapeType(bitmapShape) == gxBitmapType))
{
GXGetBitmap(bitmapShape, &imageInfo, nil);
if (imageInfo.pixelSize != 4)
{
imageInfo.pixelSize = 4;
imageInfo.space = gxIndexedSpace;
imageInfo.set = GetStandardColorSet(4);
GXSetBitmap(bitmapShape, &imageInfo, nil);
}
}
}
USING DISK-BASED PIXEL IMAGES
QuickDraw GX provides support for disk-based bitmap shapes. They're structurally
the same as regular bitmaps, except that their image data is contained in a file, so
they're always drawn from disk. Ten calls to GXDrawShape(diskBitmap) means
QuickDraw GX reads the entire file from disk ten times. (QuickDraw GX can't assume
that you didn't write into the file between accesses.) The idea is that the file system's
disk caches will do the work; if the file wasn't changed, subsequent reads should be
cached.
Make sure the file size is at least as large as the bitmap, or you'll get an
"unexpected end of file" error. *
Disk-based bitmaps have limitations. For one thing, certain routines can't be
performed on them -- GXSetShapePixel, for example. (SeeInside Macintosh:
QuickDraw GX Graphics for the complete list.) You can't use disk-based bitmap shapes
as drawing destinations. If you draw into the data you trigger an error. So how do you
create a disk-based bitmap? As shown in Listing 3, you first set the gxBitmap's image
field to gxBitmapFileAliasImageValue. After creating the bitmap shape, create a tag of
type gxBitmapFileAliasTagType containing an alias record that references the file
containing the target raster data and attach it to the shape.
ACCESSING IMAGE DATA
You can manipulate the image data of bitmap shapes directly. If the image data is
maintained by your application, all you have to do is call GXChangedShape afterward. If
the image data was allocated by QuickDraw GX, it's more complicated:
1. Force the shape to be heap-resident with GXSetShapeAttributes.
2. Lock the shape with GXLockShape and check for an error.
3. Call GXGetShapeStructure to obtain a reference to the image data.
4. Read from or write to the image data as desired.
5. If the image data was changed, call GXChangedShape.
6. Unlock the shape with GXUnlockShape.
7. Call GXSetShapeAttributes to allow the shape to be cached again.
Listing 3. Creating a disk-based bitmap
gxShape CreateDiskBitmap(FSSpec *fsData, gxBitmap *targetBM)
{
gxBitmap localBM;
gxShape targetShape;
gxTag targetTag;
if ((fsData == nil) || (targetBM == nil))
return nil;
targetShape = nil;
targetTag = CreateBitmapAliasTag(fsData, 0L);
if (targetTag != nil)
{
localBM = *targetBM;
localBM.image = gxBitmapFileAliasImageValue;
targetShape = GXNewBitmap(&localBM, nil);
if (targetShape != nil)
GXSetShapeTags(targetShape, gxBitmapFileAliasTagType,
1L, -1L, 1L, &targetTag);
GXDisposeTag(targetTag);
}
return targetShape;
}
gxTag CreateBitmapAliasTag(FSSpec *bitmapFS,
unsigned long fileOffset)
{
struct gxBitmapDataSourceAlias *aliasRecordPtr;
gxTag targetTag;
FSSpec targetFS;
AliasHandle aliasHdl;
OSErr iErr;
long aliasSize, aliasRecordSize;
Boolean wasChanged;
targetTag = nil;
aliasHdl = nil;
aliasRecordPtr = nil;
// Create an alias and resolve it.
iErr = NewAlias(nil, bitmapFS, &aliasHdl);
if (iErr == noErr)
iErr = ResolveAlias(nil, aliasHdl, &targetFS, &wasChanged);
// Build up a compact representation for inclusion into a gxTag.
if (iErr == noErr)
{
aliasSize = GetHandleSize((Handle)aliasHdl);
aliasRecordSize = aliasSize + 2 * sizeof(long);
aliasRecordPtr = (struct gxBitmapDataSourceAlias*)
NewPtr(aliasRecordSize);
iErr = MemError();
}
// Create the gxTag.
if (iErr == noErr)
{
// Create a gxBitmapDataSourceAlias with specified fileOffset
// and appropriate aliasRecordSize and aliasRecord.
aliasRecordPtr->fileOffset = fileOffset;
aliasRecordPtr->aliasRecordSize = aliasSize;
BlockMove(*aliasHdl, &aliasRecordPtr->aliasRecord[0],
aliasSize);
targetTag = GXNewTag(gxBitmapFileAliasTagType,
aliasRecordSize, aliasRecordPtr);
}