Bitmaps with ResEdit
Volume Number: 6
Issue Number: 2
Column Tag: Resource Roundup
Related Info: Resource Manager Picture Utilities Quickdraw
List Manager
BitMaps with ResEdit
By Mike Scanlin, T/Maker Company, Mountain View, CA
Note: Source code files accompanying article are located on MacTech CD-ROM or
source code disks.
This article consists of two parts: first, a custom resource type modeled after the
BitMap data structure (along with a couple of MPW C and Lightspeed Pascal routines to
manipulate these custom resources) and second, an editor you can paste into ResEdit
that allows you to do fatBit editing of these custom resource types.
AN OVERSIGHT
It has always seemed to me that the people at Apple were too preoccupied with
feeding the dogs and cows the day they decided on the standard resource types because
they forgot one. They gave us ‘ICON’, ‘SICN’, ‘CURS’, and other types of resources to
represent bit images, but nothing general purpose for plain old BitMaps. It seems like
it’s a bit overkill to use a ‘PICT’ to represent a BitMap. We have the BitMap data type,
but no corresponding resource type. What follows is a description of one possible
structure of a resource type for BitMaps.
(Note: all of the C source code in this article is MPW C 3.0 and assumes ‘short’ is
a 16 bit quantity. You shouldn’t have to change anything if you’re using Lightspeed C.
The Pascal source code is Lightspeed Pascal and should be compatible with other
Pascals.)
Most of you are probably familiar with the BitMap data type (see Inside
Macintosh, vol. I, pgs. 144-5 for more information):
In C:
/* 1 */
struct BitMap {
Ptr baseAddr; /* ptr to bit image */
short rowBytes; /* row width */
Rect bounds; /* boundary rect */
};
In Pascal:
{2}
TYPE BitMap = RECORD
baseAddr: Ptr;
rowBytes: integer;
bounds: Rect;
END;
where baseAddr is a pointer to a variable length, non-relocatable bit image; rowBytes
is the number of bytes in a single row (which must be even); and bounds is the
rectangle enclosing the active area (the dimensions of which don’t have to be a multiple
of 16, 8 or anything else). Also, the upper left corner of bounds does not have to be
(0,0); it can be anything you want it to be.
Each row of a bit image is some multiple of 16 bits wide (but not all of the bits
have to be used -- there can be extra bits along the right side). Because rowBytes
must be even it can be thought of as twice the number of 16 bit words in a row. So a bit
image with a width of 16 pixels or less has a rowBytes equal to 2; a width of 17 to 32
pixels has a rowBytes of 4; etc. The formula for converting a BitMap’s width into
rowBytes is: rowBytes = (((bounds.right - bounds.left - 1) DIV 16) + 1) * 2.
Now, if we expand on the BitMap structure a little and put the bit image at the end
we get the structure for the BTMP resource type:
In C:
/* 3 */
struct BTMP {
Ptr baseAddr; /* ptr to bit image */
short rowBytes; /* row width */
Rect bounds; /* boundary rect */
short bitImage[]; /* bit image */
};
Because the syntax bitImage[] declares a variable length array that isn’t known
at compile time, the compiler allocates zero bytes for that field; hence, the values
sizeof(BitMap) and sizeof(BTMP) are the same.
The actual size (in bytes) of the bitImage field is calculated from its height and
rowBytes and is equal to ((bounds.bottom - bounds.top) * rowBytes). So the total size
of a BTMP is equal to sizeof(BTMP) + ((bounds.bottom - bounds.top) * rowBytes).
Because a BTMP inherits all of the BitMap fields, some people probably prefer
this alternate definition for BTMP:
/* 4 */
struct BTMP {
BitMap map;
short bitImage[];
};
This is analogous to a GrafPort being the first field of a WindowRecord. While
this second definition is the theoretically preferred definition, I personally prefer not
to have to go through the map field to get at the baseAddr, rowBytes and bounds fields,
but there’s no reason why my parents dropping me on my head when I was little should
affect the kind reader. Use which ever definition makes you happy; the data is exactly
the same. If you choose the second definition, you will have to make slight changes to
the source code. Change references to the BitMap fields from something like
“theBitMapPtr->baseAddr” to “theBitMapPtr->map.baseAddr”.
In Pascal, the structure is:
{5}
TYPE BTMP = RECORD
baseAddr: Ptr;
rowBytes: integer;
bounds: Rect;
bitImage: array [0..0] of integer;
END;
or, for those with healthy psychological records:
{6}
TYPE BTMP = RECORD
map: BitMap;
bitImage: array [0..0] of integer;
END;
In Pascal the syntax “array [0..0] of integer” will cause the compiler to allocate
space for one integer, which is not what we want. The values sizeof(BitMap) and
sizeof(BTMP) are no longer the same. This is why you will find a “- sizeof(integer)”
in the Pascal sources (PascalDemoBTMP.p) in two places. For example, the total size
of a BTMP in Pascal is equal to sizeof(BTMP) - sizeof(integer) + ((bounds.bottom -
bounds.top) * rowBytes).
HOW TO USE A ‘BTMP’ RESOURCE
You can use a ‘BTMP’ resource anywhere you would use a BitMap as long as you
remember two important points: it must be a locked object when you use it as a BitMap
(if you pass it to something like CopyBits()) and you must be sure that the baseAddr
field is set up correctly before using it.
The function PlotBitMap() (see ResBTMPEd.c for the C version or
PascalDemoBTMP.p for the Pascal version) works just like PlotIcon() except that it
has one more parameter. You pass it a pointer to a Rect, a handle to a ‘BTMP’ and a
transfer mode. It will lock the object (if it’s not already), set up the baseAddr field,
transfer the bits via PlotPBitMap(), clear the baseAddr field and then restore the
handle’s locked bit state. The reason the baseAddr field is cleared is for cleanliness
only. I like to have my place holder fields set to zero when they’re not being used to
make sure I know they’re uninitialized when I’m debugging.
The lower level function PlotPBitMap() takes a pointer to a Rect, a pointer to a
locked ‘BTMP’ resource and a transfer mode. It uses CopyBits() to transfer the bits so
the image will be scaled (i.e. the Rect you pass it doesn’t have to equal the bounds
Rect). The transfer mode is a parameter rather than a hardcoded value in the call to
CopyBits() to allow for greater flexibility. This allows you to overlay BitMaps (with
srcOr) or punch holes in BitMaps (with srcBic) or do whatever other creative things
you can think of doing with the transfer modes. Of course, there is no reason why the
transfer mode has to be a parameter and if you’re always going to use srcCopy and want
a slightly leaner version of PlotBitMap() then you can remove mode from the
parameter list and hardcode srcCopy as the transfer mode.
To lock the ‘BTMP’ and set up the baseAddr field before calling PlotPBitMap(),
PlotBitMap() calls the LockBitMap() function. LockBitMap() locks the BitMap object
and sets the baseAddr field to point to the beginning of the bit image. It returns the
original value of the byte containing the flags of the object’s master pointer.
Besides being called by PlotBitMap() there are times when you might want to
call LockBitMap() directly. An example would be to create a region from a ‘BTMP’. The
Toolbox routine BitMapRgn() takes a BitMap and region handle as parameters. You can
pass the address of a ‘BTMP’ to BitMapRgn() after the ‘BTMP’ is locked down and the
baseAddr field has been set up.
(BitMapRgn() is described in Macintosh Technical Note #193: So many BitMaps,
so little time. It is available as part of the 32-Bit QuickDraw INIT, or is available
from Apple Software Licensing. Also, Ted Cohen’s article “Convert PICTs to Regions”,
MacTutor, June 1989, contains a function MakeRgn() which converts a BitMap image
into a QuickDraw region.)
CREATING A ‘BTMP’ RESOURCE
Okay, great. But now you are probably asking yourself “Where do I get these
‘BTMP’s?” Do I order them through the APDA catalog? Are they available in the
delicatessen section of my local super market? Or the frozen code section?
This issue of MacTutor provides two ways to create ‘BTMP’ resources. Michael
Ogawa’s article “Creating Resources From SuperPaint” explains how you can create a
plug-in command for SuperPaint 2.0 that will create ‘BTMP’s from the selection area.
Alternatively, follow along through the rest of this article to learn how to create
‘BTMP’ resources via a custom ResEdit editor.
‘BTMP’ RESOURCES
In Rez format, the definition of the ‘BTMP’ resource is:
/* 7 */
type ‘BTMP’ {
unsigned hex longint = 0;
rowBytes:
unsigned integer;
rect;
array { hex string [$$Word(rowBytes)];
};
};
An example of what a ‘BTMP’ in Rez format looks like is this:
/* 8 */
resource ‘BTMP’ (129) {
4,
{0, 0, 14, 19},
{ $”04 30 00 00",
$”0A 50 00 00",
$”0B 90 00 00",
$”08 20 00 00",
$”12 20 00 00",
$”20 20 00 00",
$”40 10 00 00",
$”80 0C 00 00",
$”80 03 E0 00",
$”7E 00 60 00",
$”01 00 20 00",
$”01 00 00 00",
$”00 C0 00 00",
$”00 E0 00 00
}
};
But since it is physically unsatisfying to create and edit BitMaps as a series of
hex strings there is a need for a better way. Wouldn’t it be nice if you could edit the
above data like this instead:
Wouldn’t it be nice if you could change the size of the fatBits from 8 to 3 pixels
or invert the image with a single menu command to get this:
Wouldn’t it be nice if I’d get on with it