TweetFollow Us on Twitter

September 94 - Making the Most of QuickDraw GX Bitmaps

Making the Most of QuickDraw GX Bitmaps

DAVID SUROVELL

[IMAGE 048-064_Surovell_final_h1.GIF]

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);
    }
    // Clean up.
    if (aliasHdl != nil)
        DisposeHandle((Handle)aliasHdl);
    if (aliasRecordPtr != nil)
        DisposePtr((Ptr)aliasRecordPtr);

    return targetTag;
}

GXLockShape loads an image into memory, so it might not succeed if there isn't enough memory. And don't forget to check a bitmap shape's space field before processing the shape -- don't assume that bitmap images are always in RGB space.

See Listing 4 for an example of changing a bitmap shape's data directly.

MEMORY ISSUES
Raster surfers and Photoshop junkies know that raster images can be memory hogs; it's easy to run out of application heap when you allocate them. So what happens when QuickDraw GX runs out of memory? It doesn't. Well, almost never. Here are the steps it will go through, in order, to deliver the memory you need:

  1. Flush out-of-date caches.
  2. Flush up-to-date caches.
  3. If allowed, grow the current gxHeap.
  4. Unload shapes and other objects to disk.
  5. Give up, and return an error.

Most QuickDraw developers resort to some sort of GrowZoneProc to handle a tight application heap. QuickDraw GX provides a tiered response to abnormal occurrences. Items 1 through 4 above return notices (in the debugging version of QuickDraw GX); item 5 returns an error. All you have to do is implement a routine to handle the notices and errors.

Listing 4. Directly changing an indexed bitmap shape

void InvertBitmapShape(gxShape sourceBits)
{
    gxBitmap            sourceInfo, *sourceInfoRef;
    gxShapeAttribute    curAttributes;
    unsigned char       *sourcePtr, *rowPtr;
    long                sourceRowSize, structLen, i, j;
    Boolean             isQDGXImage;

    // Make sure that this is an indexed bitmap shape. 
    if (sourceBits == nil)
        return;
    if (GXGetShapeType(sourceBits) != gxBitmapType)
        return;
    GXGetBitmap(sourceBits, &sourceInfo, nil);
    if (sourceInfo.pixelSize > 8)
        return;
    if (sourceInfo.image == gxBitmapFileAliasImageValue)
        return;
    // If the image data was allocated by QuickDraw GX... 
    isQDGXImage = (sourceInfo.image == nil);
    if (isQDGXImage)
    {
        // Load and lock the image data. 
        curAttributes = GXGetShapeAttributes(sourceBits);
        if (!(curAttributes & gxDirectShape))
            GXSetShapeAttributes(sourceBits,
                curAttributes | gxDirectShape);
        GXLockShape(sourceBits);
        if (GXGraphicsError(nil) != 0)
            return;

        // Get a reference to the image data. 
        sourceInfoRef =
            (gxBitmap*)GXGetShapeStructure(sourceBits, &structLen);
        if ((sourceInfoRef == nil) || (structLen < sizeof(gxBitmap)))
            return;

        sourceInfo = *sourceInfoRef;
    }

    // Invert index values, one row at a time. 
    sourcePtr = (unsigned char*)(sourceInfo.image);
    for (i = sourceInfo.height; i > 0; i--)
    {
        rowPtr = sourcePtr;
        sourceRowSize = sourceInfo.rowBytes;
        while (sourceRowSize-- > 0)
        {
            *rowPtr = ~*rowPtr;
            rowPtr++;
        }
        // Skip to the next row.
        sourcePtr = (unsigned char*)sourcePtr + sourceInfo.rowBytes;
    }

    GXChangedShape(sourceBits);
    if (isQDGXImage)
    {
        GXUnlockShape(sourceBits);
        GXSetShapeAttributes(sourceBits, curAttributes);
    }
}

GEOMETRIC OPERATIONS

One of the niftiest features of QuickDraw GX is the ability to perform geometric operations on bitmap shapes. Most of the operators that apply to geometric shapes also apply to bitmaps: rotate, scale, skew, perspective, and clip. In comparison, QuickDraw provides only three geometric operators: scale, clip, and mask.

ALTERING THE TRANSFORM VERSUS THE GEOMETRY
When you change a bitmap shape's geometry (that is, its actual pixel data), whether by rotating, skewing, applying perspective, or scaling, you normally lose image data -- it's often impossible to return the image to its pristine state.

You can eliminate this data loss by instead applying geometric operators to a shape'stransform. A shape can make use of a 3 x 3 matrix to mathematically change its appearance when rendered without changing the underlying data. This is especially important for bitmaps. Figure 2 shows both possibilities of multiple rotations of a bitmap.

[IMAGE 048-064_Surovell_final_h2.GIF]

Figure 2. Successive rotations of a bitmap

Rotation, translation (change in origin), skew, perspective, and scale operations can all be performed on transforms directly, by GXRotateTransform, GXSkewTransform, and so forth, or indirectly, using the gxMapTransformShape attribute.

When a shape's gxMapTransformShape attribute is set, geometric operations automatically apply to its transform rather than its geometry. Bitmap and picture shapes default to having this attribute set; other shapes begin with it off. This means that if you convert a polygon shape (for example) to a bitmap shape, the gxMapTransformShape attribute won't automatically be set.

[IMAGE 048-064_Surovell_final_h3.GIF]

Figure 3. Effect of GXRotateShape on bitmap geometry

When a QuickDraw GX routine modifies a bitmap shape's geometry, a clip shape is often attached to define the geometric extent of the modified bitmap. More often than not, the bitmap's image buffer is expanded, as shown in Figure 3. Rotating a bitmap's geometry can increase its memory requirements by over 40%.

ROTATION
There aren't many QuickDraw programmers who haven't wished for a simple way to rotate bitmaps. GXRotateShape takes parameters for the target shape, degrees clockwise to rotate, and center point of rotation, as shown in Listing 5.


Listing 5. Rotating a bitmap shape

void RotateBitmap(gxShape targetShape, Fixed theta)
{
    gxBitmap    targetBM;
    gxPoint     origin, shCenter;

    // Determine the bitmap shape's current center point.
    GXGetBitmap(targetShape, &targetBM, &origin);
    shCenter.x = ff(targetBM.width) / 2 + origin.x;
    shCenter.y = ff(targetBM.height) / 2 + origin.y;

    // Rotate it around its center point.
    GXRotateShape(targetShape, theta, shCenter.x, shCenter.y);
}

SKEWING AND PERSPECTIVE
Skewing and perspective are just as much fun as rotation, and even more useful as general-purpose graphic effects. The code in Listing 6 illustrates a simple type of perspective; Figure 4 shows the results of this perspective mapping.

SCALING
You can expand or shrink bitmap shapes, like other shape types, with GXScaleShape. QuickDraw pixMaps are scaled by setting the destination rectangle passed to CopyBits, whereas GXScaleShape uses a scaling factor. To convert your QuickDraw bitmap scaling code into the equivalent QuickDraw GX code, you have to calculate this scaling factor. Listing 7 shows how.

You can flip a bitmap horizontally or vertically by using negative scaling values. *



Listing 6. Applying perspective to a bitmap shape

void TrapezoidalWarp(void)
{
    gxShape bitsShape, warpShape;
    long        trapezoidData[] =
    {
        1L, 4L,
        ff(130), ff(100), ff(170), ff(100),
        ff(200), ff(200), ff(100), ff(200)
    };

    bitsShape = CreateBasicBitmapShape();
    warpShape = GXNewShapeVector(gxPolygonType, trapezoidData);
    if (warpShape != nil)
    {
        ShapeSetPolyMap(bitsShape, warpShape);
        GXDisposeShape(warpShape);
    }
    GXDrawShape(bitsShape);
}

void ShapeSetPolyMap(gxShape targetShape, gxShape mappingShape)
{
    gxRectangle     boundsRect;
    gxPolygon       *mapPoly, *targetPoly;
    gxMapping       theMapping;
    gxShape         targetBounds;
    long            ignored;

    if (targetShape == nil)
        return;
    if ((mappingShape == nil)
            || (GXGetShapeType(mappingShape) != gxPolygonType))
        return;

    // Determine the dimensions of the target shape. 
    GXGetShapeBounds(targetShape, 0L, &boundsRect);
    targetBounds = GXNewRectangle(&boundsRect);
    if (targetBounds == nil)
        return;

    // Scale the mapping shape to the dimensions of the target shape.
    GXSetShapeBounds(mappingShape, &boundsRect);
    GXSetShapeType(targetBounds, gxPolygonType);

    // Load & lock both shapes so that their structures can be
    // accessed.
    GXSetShapeAttributes(mappingShape,
        GXGetShapeAttributes(mappingShape) | gxDirectShape);
    GXLockShape(mappingShape);
    GXSetShapeAttributes(targetBounds,
        GXGetShapeAttributes(targetBounds) | gxDirectShape);
    GXLockShape(targetBounds);
    // NOTE: Structure is actually of type gxPolygon. 
    mapPoly = 
        (gxPolygon*)GXGetShapeStructure(mappingShape, &ignored);
    targetPoly =
        (gxPolygon*)GXGetShapeStructure(targetBounds, &ignored);

    if ((mapPoly != nil) && (targetPoly != nil))
    {
        // Skip past the gxPolygons contour count to the first
        // contour.
        mapPoly = (gxPolygon*)((Ptr)mapPoly + sizeof(long));
        targetPoly = (gxPolygon*)((Ptr)targetPoly + sizeof(long));

        // Calculate the desired shape mapping. 
        // PolyToPolyMap() is in "mapping library.c." 
        PolyToPolyMap(targetPoly, mapPoly, &theMapping);
    }

    // Release both shapes from bondage. 
    GXUnlockShape(mappingShape);
    GXSetShapeAttributes(mappingShape,
        GXGetShapeAttributes(mappingShape) & ~gxDirectShape);
    GXUnlockShape(targetBounds);
    GXSetShapeAttributes(targetBounds,
        GXGetShapeAttributes(targetBounds) & ~gxDirectShape);

    // Set the target shape's mapping as desired. 
    GXSetShapeMapping(targetShape, &theMapping);

    GXDisposeShape(targetBounds);
}

[IMAGE 048-064_Surovell_final_h5.GIF]

Figure 4. Applying perspective to a bitmap shape Listing 7. Calculating a scaling factor

void BitmapShapeScaleQDStyle(gxShape targetShape, Rect *qdSourceR,
            Rect *qdDestR)
{
    gxPoint     centerPt;
    fixed       scaleFactorH, scaleFactorV;

    scaleFactorH = FixRatio(qdSourceR.right - qdSourceR.left,
                            qdDestR.right - qdDestR.left);
    scaleFactorV = FixRatio(qdSourceR.bottom - qdSourceR.top,
                            qdDestR.bottom - qdDestR.top);
    centerPt.x = ff((qdDestR.right + qdDestR.left) / 2);
    centerPt.y = ff((qdDestR.bottom + qdDestR.top) / 2);
    GXScaleShape(targetShape, scaleFactorH, scaleFactorV, centerPt.x,
                 centerPt.y);
    GXMoveShapeTo(targetShape, ff(qdDestR.left), ff(qdDestR.top));
    GXDrawShape(targetShape);
}

CLIPPING AND MASKING
QuickDraw GX can do some neat tricks with clipping. These tricks work with bitmap shapes, too. For example, to create a gradient-filled polygon, you can make a rectangular bitmap shape with a gradient and then set the polygon shape as the bitmap's clip shape. (For another example, see Graphical Truffles in this issue.)

You can use 1-bit bitmap shapes as clip shapes, too. The effect is just like that of CopyMask; pixels in the source shape are drawn only where the clipping bitmap pixel value is nonzero. (On this issue's CD, you'll also find example code that does image processing similar to CopyDeepMask using the new transfer modes.)

Clipping occurs in geometry space, before transform mapping, so a bitmap's clip shape should be based on its bounds rectangle, not its rendered location. *

To convert geometric shapes into masking bitmap shapes, you can call the GXSetShapeType routine to convert the shape to a 1-bit mask bitmap.

With GXCheckBitmapColor, you can generate a masking bitmap from an existing bitmap shape. If you pass GXCheckBitmapColor a color set, it puts 0 in the result bitmap for source pixel values that are in the color set. If you pass it a color profile, it puts 0 in the result bitmap for source pixel values that are within the color profile's gamut. The result bitmap can be useful for color correction.

QUICKDRAW GX TRICKS FOR QUICKDRAW DOGS

QuickDraw GX has ways to do almost anything you can do with QuickDraw. All you need to know is how their environments and feature sets compare, and you'll understand how to convert from one to the other.

THE VIEW PORT LIST VERSUS THE GRAPHICS PORT
Most of the time you won't have to concern yourself with view ports at rendering time, because there's no sense of the "current port" as there is in QuickDraw. Here's the recommended method for drawing an existing shape into a new view port:

  1. Copy the shape's transform and install the desired destination view port into the copy.
  2. Call GXDrawShape.
  3. Restore the original transform.
  4. Dispose of the copied transform.

Examples of preserving view port lists can be found in the library routine CopyToBitmaps and in the DrawShapeOffscreen example later in this article (Listing 9).

BITMAPS AND TRANSFER MODES
QuickDraw GX has a lot of transfer modes. This is a good thing, really. Not alltransfer modes are functionally equivalent to those in QuickDraw, but the transferModelibrary is fairly complete. Many of the capabilities of QuickDraw search procedures can be implemented using transfer modes. (The first page ofInside Macintosh: QuickDraw GX Graphics has color pictures of the new transfer modes in action.)

The transfer mode is contained in a shape's ink. Since transfer modes are applied on a per- component basis, you can easily get some groovy effects. For example, you can add the hue of one image to the brightness of another. Usually, though, you'll want all components to use the same mode. The transferMode library routine SetCommonTransfer will do this for you.

There are some differences between QuickDraw GX transfer modes and those found in QuickDraw:

  • Dithering is a view port feature, not a transfer mode. Halftoning is also available on a per-gxViewPort basis. These two features are mutually exclusive; you can't dither and halftone at the same time.
  • Transparency is not a single mode. It's a whole family of modes based on alpha component values.
  • All QuickDraw GX transfer modes occur in color space, while some QuickDraw transfer modes are bitwise.

ONSCREEN BITMAPS
QuickDraw GX maintains a view device list that mirrors the QuickDraw GDevice list. (Utility routines are provided for getting one if you have the other.) The Window Manager is patched in a couple of places so that a window's view port transforms and image memory are maintained when it enters and leaves GDevice real estate.

Drawing a bitmap onscreen obeys the screen GDevice's index entry protections -- QuickDraw GX doesn't use indexes reserved by the Palette Manager for other applications. If you want to draw an image that uses animated palette entries, you'll need to clone references to the destination viewDevice color set and profile, and then insert those references into the bitmap shape before drawing. Example code that does this is on this issue's CD.

COPYBITS IN QUICKDRAW GX
Let's see what it takes to make GXDrawShape do what CopyBits does. CopyBits has several explicit parameters: the source, destination, clipping region, and transfer mode. In QuickDraw GX, the source is the bitmap shape. The destination is defined by the shape's view port list. The clipping region is any shape that you attach to the bitmap shape with GXSetShapeClip. As mentioned before, the transfer mode is contained in the shape's ink.

So, to do a CopyBits-style blit in QuickDraw GX:

  1. Set up the shape's view port list.
  2. Determine the transfer mode (usually just "copy," but it's your choice).
  3. Adjust the shape clip. Don't change the device clip or view port clip.
  4. Adjust the transform if you want to reposition, scale, skew, rotate, or apply perspective to the shape.
  5. Call GXDrawShape.
  6. Clean up as needed.

QuickDraw GX doesn't implement all of the color capabilities of CopyBits. There's no colorizing and no color interpolation for indexed values beyond the end of a bitmap's color set. *

DRAWING OFFSCREEN WITH QUICKDRAW GX
Successive QuickDraw implementations have presented newer and better ways to draw into a offscreen image buffer. The QuickDraw GX offscreen library contains routines to help maintain the data structures necessary to implement the equivalent of a GWorld.

The example in Listing 8 uses the CreateIndexedBitmapShape routine from Listing 1 and the library routine CreateOffscreen to create a fully functional offscreen bitmap.

You might think drawing into a QuickDraw GX offscreen bitmap would be difficult, but it's not. To draw a shape into the offscreen bitmap, set its view port list to the offscreen bitmap's view port and call GXDrawShape (see Listing 9).

Listing 8. Creating an offscreen bitmap

OSErr MakeIndexedOffscreen(offscreen *targetOffWorld, long horiz,
                           long vert, long targetDepth)
{
    gxShape bitsShape;

    if (!CheckArguments(...))
        return paramErr;
    bitsShape = CreateIndexedBitmapShape(horiz, vert, targetDepth);
    if (bitsShape == nil)
        return paramErr;
    CreateOffscreen(targetOffWorld, bitsShape);
    return noErr;
}


Listing 9. Drawing into an offscreen bitmap

void DrawShapeOffscreen(offscreen *offGXWorld, gxShape targetShape)
{
    gxTransform newXform, savedXform;

    if ((offGXWorld == nil) || (targetShape == nil))
        return;
    if (offGXWorld->port == nil)
        return;

    savedXform = GXGetShapeTransform(targetShape);
    newXform = GXCopyToTransform(nil, savedXform);
    GXSetTransformViewPorts(newXform, 1L, &(offGXWorld->port));
    GXSetShapeTransform(targetShape, newXform);

    GXDrawShape(targetShape);

    GXSetShapeTransform(targetShape, savedXform);
    GXDisposeTransform(newXform);
}

BITMAP SHAPES VERSUS PIXMAPS
Sometimes, converting existing QuickDraw code to QuickDraw GX is impractical. If your application needs to use the same data in both offscreen pixMaps and bitmap shapes, it can, provided that the bitmap shape is packed the same as the pixMap -- that is, of identical width, height, pixel depth, and color space.

To use bitmap shape data in a QuickDraw pixMap, build the pixMap with the baseAddr the same as the gxBitmap.image. (Make sure that the bitmap shape is locked down.) To use pixMap data in QuickDraw GX, create a gxBitmap with the image field set to the base address of the source pixMap.

THE QUICKDRAW GX LIBRARIES

Several libraries are included with the QuickDraw GX Software Developer's Kit. They contain, among other things, routines for offscreen rendering and converting image data between QuickDraw and QuickDraw GX. The library code instructs by example and is a good starting point for your own library.

The library code is not completely tested. You should treat it as template code, not a final solution. *

The offscreen library. This library contains support for offscreen bitmaps, copyingbetween bitmap shapes, and simple gradient fills. The offscreen image implementationis basic but solid (it lacks some of the features found in QuickDraw GWorlds, such as automatic longword realignment of images). The utility routine CopyToBitmaps is also useful; it shows a good example of saving a view port list.

The math library. This library contains a number of useful routines for manipulatingmappings. The routine PolyToPolyMap is used in the trapezoidal warp example (Listing 6). The header file math routine.h contains essential macros for conversion between fixed-point, floating-point, and integral values.

The ramp library. Get your gradient fills here. Pleasing to the eye, easy on the code. A gradient-filled bitmap can be rotated and clipped, and voilà! Gradient-filled shapes.

The qd and oval libraries. The qd library has facilities for conversion of bitmap and color data between QuickDraw and QuickDraw GX formats. The oval library has real ovals, not those phony squished QuickDraw things.

The transferMode library. This library facilitates access to a shape's transfer mode information and contains routines for emulating most of the QuickDraw transfer modes. It also contains a bonus -- one of my favorite routines. If you've ever wanted to get the results of a QuickDraw transfer mode on color values without having to use CopyBits, TransmogrifyColor is for you. Check it out.

The storage library. This library implements spooling routines for use with GXFlattenShape and GXUnflattenShape, which you'll need for reading and writing shapes to and from files. These routines detect errors but don't report them, so they're only useful as templates.

The camera library. Perspective is cool, but hard to use unless your math skills are well developed. This library provides nifty 3-D techniques.

AND A FEW MORE THINGS . . .

Here I'll point out some caveats and additional interesting features of QuickDraw GX, just so you know what to look for (and look out for).

EXECUTION OVERHEAD
How fast are QuickDraw GX blits? How slow does an offscreen, 256 x 256, 45º-rotated, 32-bit, YXY, gradient-filled bitmap draw into a window on a 4-bit monitor? How much for all of these shiny pebbles? It depends. Let's look at the issues involved. QuickDraw GX and QuickDraw have much in common here:

  • They're fastest when there's no conversion of value or image location.
  • Common code paths are optimized inside the API: 8-bit to 8-bit, 1-bit to 1-bit, 24-bit to 8-bit, no clipping, rectangle clipped.
  • Blits involving complex transformations are usually orders of magnitude slower.

Some transformations require more processing. QuickDraw GX does only as much work as the transformation matrix mandates. From fastest to slowest, the order is: no transformation (or translation only); scaling; skewing or rotation; perspective.*

The basic performance guidelines are similar to automotive fuel efficiency ratings -- though we have no hard estimates, mileage is better on a smooth highway (no color mapping, skewing, or scaling) than on surface streets.

A transform mutation can require a 3 x 3 matrix operation for each pixel value when rendered. That's a lot of fixed-point multiplications. If execution speed is critical and the mutated version will be used a lot, copy the bitmap shape, mutate the geometry, and draw like crazy. Otherwise, mutate the transform and draw as needed.

SHARED IMAGE BUFFERS
A bitmap shape's raster image buffer can be shared by other bitmap shapes. Just make the source bitmap shape's image field the same as that of another bitmap shape. GXCopyToShape uses this sharing of image buffers. If you need a copy of a bitmap shape (or a picture that contains bitmap shapes) to have its own image buffer, use GXCopyDeepToShape.

USING BITMAPS AS PATTERNS
Bitmap shapes can be used as patterns. Unlike QuickDraw, QuickDraw GX has no limitation on area dimension or size of raster data in a pattern. To do simple tiling, you can just set the bitmap pattern on the shape.

You can align the pattern to all destination view ports simply by setting the gxPortAlignPattern attribute. This forces all shapes drawn with that pattern in a given view port to visually line up with each other. Another pattern attribute, gxPortMapPattern, keeps a pattern from being affected by a shape's transform; this is useful, for example, when you want a shape rotated and its pattern unrotated.

BITMAP SHAPE EQUIVALENCE
You can test QuickDraw GX shapes for equivalence by calling GXEqualShape. However, this routine doesn't account for mapping effects. For example, a bitmap gradient from black to white would be considered not equal to a white-to-black gradient bitmap whose transform is rotated 180º, even though the two shapes would produce identical results when drawn.

SIMPLIFICATION
GXSimplifyShape reduces an indexed bitmap to its simplest representation, even reducing the pixel depth when possible. For example, if an 8-bit-deep bitmap shape contains only 15 colors, GXSimplifyShape will convert it to a 4-bit-deep bitmap. If a bitmap is all one color, it will be converted into a rectangle shape -- it won't be a bitmap shape any more.

SUBSET EDITING
QuickDraw GX provides tools for working with area subsets of bitmaps. A piece can be copied from a source bitmap via GXGetBitmapParts, edited, and then blasted back into the source image with GXSetBitmapParts.

Individual pixel values can be accessed with the GXGetShapePixel and GXSetShapePixel routines. Unlike in QuickDraw, these routines don't need to reference a gxViewDevice to determine the color.

SO GET GOING

As you can see, QuickDraw GX does some really cool things with bitmaps. The transforms alone make it worthwhile -- it's easy to get addicted to rotating and skewing your bitmaps without having to do a lot of work. The new transfer modes are great. All the rest is a bonus. In the future, when memory is cheap and every machine is fast, you'll see more and more Macintosh systems and applications become dependent on QuickDraw GX.

PIXEL VALUE REPRESENTATION

A raster image is, naturally enough, an array of pixel values. For indexed color, each pixel value is an index into an associated color set.

For direct color (16 or 32 bits per pixel), a pixel value is converted directly into a color value by expanding bit fields of the 16- or 32-bit value into three or four 16-bit unsigned integer values.

The expansion of direct pixel values depends on the color space of the raster image and the "packing" of the color components. QuickDraw supports only RGB and a handful of packing schemes, but QuickDraw GX supports a whole family of color spaces and packing formats, some of which are shown in Figure 1. The packing types are defined in the gxColorSpaces enum in the header file graphics types.h. You'll also find definitions for extended color space specifications, such as gxRGB16Space (gxRGBSpace + gxWord5ColorPacking) and gxARGB32Space (gxLong8ColorPacking + gxRGBASpace + gxAlphaFirstPacking). Only explicitly defined permutations are valid -- you can't just make up your own.

[IMAGE 048-064_Surovell_final_h4.GIF]

DAVID SUROVELLWhere there was once one, there now are three: after approximately 1500 years of bachelorhood, David recently married (Jane) and achieved fatherhood (Elliot Ivan). He once wrote a book on QuickDraw, but that was long ago. When he's not sleeping under his desk at Apple, David's passionate avocations include auditioning as a guitarist for bands that fail to play in public, committing brutal fouls in otherwise friendly soccer matches and basketball games, and playing paintball with other rush- hour commuters.*

Thanks to our technical reviewers Pete ("Luke") Alexander, Josh Horwich, and Chris Yerga. *

 

Community Search:
MacTech Search:

Software Updates via MacUpdate

Latest Forum Discussions

See All

Make the passage of time your plaything...
While some of us are still waiting for a chance to get our hands on Ash Prime - yes, don’t remind me I could currently buy him this month I’m barely hanging on - Digital Extremes has announced its next anticipated Prime Form for Warframe. Starting... | Read more »
If you can find it and fit through the d...
The holy trinity of amazing company names have come together, to release their equally amazing and adorable mobile game, Hamster Inn. Published by HyperBeard Games, and co-developed by Mum Not Proud and Little Sasquatch Studios, it's time to... | Read more »
Amikin Survival opens for pre-orders on...
Join me on the wonderful trip down the inspiration rabbit hole; much as Palworld seemingly “borrowed” many aspects from the hit Pokemon franchise, it is time for the heavily armed animal survival to also spawn some illegitimate children as Helio... | Read more »
PUBG Mobile teams up with global phenome...
Since launching in 2019, SpyxFamily has exploded to damn near catastrophic popularity, so it was only a matter of time before a mobile game snapped up a collaboration. Enter PUBG Mobile. Until May 12th, players will be able to collect a host of... | Read more »
Embark into the frozen tundra of certain...
Chucklefish, developers of hit action-adventure sandbox game Starbound and owner of one of the cutest logos in gaming, has released their roguelike deck-builder Wildfrost. Created alongside developers Gaziter and Deadpan Games, Wildfrost will... | Read more »
MoreFun Studios has announced Season 4,...
Tension has escalated in the ever-volatile world of Arena Breakout, as your old pal Randall Fisher and bosses Fred and Perrero continue to lob insults and explosives at each other, bringing us to a new phase of warfare. Season 4, Into The Fog of... | Read more »
Top Mobile Game Discounts
Every day, we pick out a curated list of the best mobile discounts on the App Store and post them here. This list won't be comprehensive, but it every game on it is recommended. Feel free to check out the coverage we did on them in the links below... | Read more »
Marvel Future Fight celebrates nine year...
Announced alongside an advertising image I can only assume was aimed squarely at myself with the prominent Deadpool and Odin featured on it, Netmarble has revealed their celebrations for the 9th anniversary of Marvel Future Fight. The Countdown... | Read more »
HoYoFair 2024 prepares to showcase over...
To say Genshin Impact took the world by storm when it was released would be an understatement. However, I think the most surprising part of the launch was just how much further it went than gaming. There have been concerts, art shows, massive... | Read more »
Explore some of BBCs' most iconic s...
Despite your personal opinion on the BBC at a managerial level, it is undeniable that it has overseen some fantastic British shows in the past, and now thanks to a partnership with Roblox, players will be able to interact with some of these... | Read more »

Price Scanner via MacPrices.net

You can save $300-$480 on a 14-inch M3 Pro/Ma...
Apple has 14″ M3 Pro and M3 Max MacBook Pros in stock today and available, Certified Refurbished, starting at $1699 and ranging up to $480 off MSRP. Each model features a new outer case, shipping is... Read more
24-inch M1 iMacs available at Apple starting...
Apple has clearance M1 iMacs available in their Certified Refurbished store starting at $1049 and ranging up to $300 off original MSRP. Each iMac is in like-new condition and comes with Apple’s... Read more
Walmart continues to offer $699 13-inch M1 Ma...
Walmart continues to offer new Apple 13″ M1 MacBook Airs (8GB RAM, 256GB SSD) online for $699, $300 off original MSRP, in Space Gray, Silver, and Gold colors. These are new MacBook for sale by... Read more
B&H has 13-inch M2 MacBook Airs with 16GB...
B&H Photo has 13″ MacBook Airs with M2 CPUs, 16GB of memory, and 256GB of storage in stock and on sale for $1099, $100 off Apple’s MSRP for this configuration. Free 1-2 day delivery is available... Read more
14-inch M3 MacBook Pro with 16GB of RAM avail...
Apple has the 14″ M3 MacBook Pro with 16GB of RAM and 1TB of storage, Certified Refurbished, available for $300 off MSRP. Each MacBook Pro features a new outer case, shipping is free, and an Apple 1-... Read more
Apple M2 Mac minis on sale for up to $150 off...
Amazon has Apple’s M2-powered Mac minis in stock and on sale for $100-$150 off MSRP, each including free delivery: – Mac mini M2/256GB SSD: $499, save $100 – Mac mini M2/512GB SSD: $699, save $100 –... Read more
Amazon is offering a $200 discount on 14-inch...
Amazon has 14-inch M3 MacBook Pros in stock and on sale for $200 off MSRP. Shipping is free. Note that Amazon’s stock tends to come and go: – 14″ M3 MacBook Pro (8GB RAM/512GB SSD): $1399.99, $200... Read more
Sunday Sale: 13-inch M3 MacBook Air for $999,...
Several Apple retailers have the new 13″ MacBook Air with an M3 CPU in stock and on sale today for only $999 in Midnight. These are the lowest prices currently available for new 13″ M3 MacBook Airs... Read more
Multiple Apple retailers are offering 13-inch...
Several Apple retailers have 13″ MacBook Airs with M2 CPUs in stock and on sale this weekend starting at only $849 in Space Gray, Silver, Starlight, and Midnight colors. These are the lowest prices... Read more
Roundup of Verizon’s April Apple iPhone Promo...
Verizon is offering a number of iPhone deals for the month of April. Switch, and open a new of service, and you can qualify for a free iPhone 15 or heavy monthly discounts on other models: – 128GB... Read more

Jobs Board

Relationship Banker - *Apple* Valley Financ...
Relationship Banker - Apple Valley Financial Center APPLE VALLEY, Minnesota **Job Description:** At Bank of America, we are guided by a common purpose to help Read more
IN6728 Optometrist- *Apple* Valley, CA- Tar...
Date: Apr 9, 2024 Brand: Target Optical Location: Apple Valley, CA, US, 92308 **Requisition ID:** 824398 At Target Optical, we help people see and look great - and Read more
Medical Assistant - Orthopedics *Apple* Hil...
Medical Assistant - Orthopedics Apple Hill York Location: WellSpan Medical Group, York, PA Schedule: Full Time Sign-On Bonus Eligible Remote/Hybrid Regular Apply Now Read more
*Apple* Systems Administrator - JAMF - Activ...
…**Public Trust/Other Required:** None **Job Family:** Systems Administration **Skills:** Apple Platforms,Computer Servers,Jamf Pro **Experience:** 3 + years of Read more
Liquor Stock Clerk - S. *Apple* St. - Idaho...
Liquor Stock Clerk - S. Apple St. Boise Posting Begin Date: 2023/10/10 Posting End Date: 2024/10/14 Category: Retail Sub Category: Customer Service Work Type: Part Read more
All contents are Copyright 1984-2011 by Xplain Corporation. All rights reserved. Theme designed by Icreon.