History of Icons
Volume Number: 8
Issue Number: 5
Column Tag: Pascal Workshop
Related Info: Resource Manager Picture Utilities Quickdraw
Color Quickdraw
Icon Family
The history of icons, from 1984 until now
By Steve Sheets, MacTutor Regular Contributing Author
Note: Source code files accompanying article are located on MacTech CD-ROM or
source code disks.
When the Macintosh was originally introduced, one of the major new concepts
that it presented was the notion of an “icon.” The Macintosh was the first mass
marketed computer system that used the concept of an icon extensively. Users learned,
for the first time, how to work in an Icon-based environment. While the concept of an
icon has not changed much for the user, for the programmer the technical aspect of an
icon has changed with each new volume of Inside Mac.
This article is intended to be an explanation of how Icons are programmed on all
Macintosh platforms. The information will start with the simplest definition of Icons
(ICON & ICN#), such as those used by the original Macintosh 128K, and continue on to
explain the more complicated color icon structures (cicn), and then will explain the
new and poorly documented icon family structures (icl8, icl4, ics#, ics4, ics8).
Various routines will be given that will function on all versions of the Macintosh and
will provide Update, Selection and Mousedown detection capability.
For the purpose of this article, an icon is defined as a graphic entity on the
screen that you can directly manipulate using the mouse. Icons can be clicked on,
moved around, thrown out, and redesigned. Most of these functions are so simple to do
that even my two year old daughter can perform the manipulations. Icons form the
basis of user friendliness in action, and are essential for the direct-manipulation type
of interface the Macintosh uses.
Original Black & White Icons
Originally the Macintosh was a black and white machine only, so all icons were
strictly black and white images, exactly 32 by 32 pixels in size. The number of bytes
it takes to display a row of a black and white icon image is commonly referred to as the
Rowbytes. Since each pixel could be represented as a bit (set or one equals black, clear
or zero equals white), a 32 pixel row could be represented with 4 bytes of memory
(i.e., it’s Rowbytes value is 4). Thus the entire 32 line icon takes up 128 bytes of
memory. The data structure containing an icon usually is manipulated as a Handle
structure. Such a handle uses the ‘ICON’ Restype when the handle is stored in a
resource file or copied to the scrapbook.
{ 1 }
Ticon = ARRAY[1..4,1..32] of 0..255;
TiconPtr = ^Ticon;
TiconHdl = ^TiconPtr;
The following is what this kind of icon looks like. Also displayed is a blown up
version of the same icon so you can see the individual pixels.
Figure 1
The following routine will draw a black and white icon in the current GrafPort:
{2}
procedure DrawBWIcon (theIcon: Handle;
thePoint: Point);
var
myRect: Rect;
myBitMap: BitMap;
myPort: GrafPtr;
begin
if theIcon <> nil then
if theIcon^ <> nil then begin
Hlock(theIcon);
with myBitMap, bounds do begin
baseaddr := StripAddress(theIcon^);
rowbytes := 4;
topleft := thePoint;
right := left + 32;
bottom := top + 32;
end;
GetPort(myPort);
CopyBits(myBitMap, myPort^.portBits, myBitMap.bounds,
myBitMap.bounds, srcCopy, nil);
Hunlock(theIcon);
end;
end;
The procedure given always draws the icon in the current GrafPort so that given
a specified point, the pixel is drawn so that the top left pixel is that point. This is
fairly arbitrary. The routine could be written so that the point given defines the
center of the icon. The routine could also be written so that the icon is drawn in a given
(possibly offscreen and/or non- current) GrafPort or BitMap. Any method that is used
is fine, as long as all routines use the same method. All routines provided in this
article draw in the current Grafport at a specified point.
The one problem with this type of icon is that the black and white image is copied
exactly over the existing GrafPort. All white pixels are copied exactly to the GrafPort
regardless of whether or not the pixel is part of the image being displayed. This can
cause problems when you draw an icon on top of a non-white background. For example,
imagine some icons drawn on various backgrounds.
Figure 2
When drawing the document icon, you would be drawing the document image as
well as the white area around it. While this would be correct on a white background,
when you draw on any other background, this is not the image you want to show. You
want only the document portion to appear. Thus you need additional information to
explain which pixels to transfer. There needs to be a way to distinguish which of the
pixels of the icon actually needs to be drawn. Unless the icon represents a perfect 32
by 32 square pixel image, pixels of the icon may not need to be transferred.
To solve this problem, the ‘ICN#’ (commonly pronounced ‘Icon number’) data
structure was also developed. This structure can contain one or more 32 by 32 black
and white icons. Each icon is identical to the ‘ICON’ structure defined above. While the
‘ICN#’ can contain any number of icons, in practice, it contains two icons used to
display a single image. The first icon is the black and white image; the second icon is
the mask of this image.
type
TiconArray = array[0..0] of Ticon;
TiconArrayPtr = ^TiconArray;
TiconArrayHdl = ^TiconArrayPtr;
A mask is a second icon map that provides additional drawing information for the
first icon map (the image). All the black pixels of a mask represent spots where the
image should be drawn, and the white pixels represented spots where the background
should be seen. A mask is always associated with some image (be it black and white or
color).
The following is an icon and it’s mask.
Icon Mask
Figure 3
Since the mask is provided, the icon can be drawn on top of any background
(black, white, gray, etc.). The following shows how the icon would appear. Notice that
only the pixels defined by the mask, not the entire 32 by 32 area, are affected.
Figure 4
The following routine draws an icon with mask at a given point:
{3}
procedure DrawIconMask (theIcon: Handle;
thePoint: Point;
theFlag: BOOLEAN);
var myIconBitMap, myMaskBitMap: BitMap;
myPort: GrafPtr;
begin
if theIcon <> nil then
if theIcon^ <> nil then begin
Hlock(theIcon);
with myIconBitMap, bounds do begin
baseaddr := StripAddress(theIcon^);
rowbytes := 4;
topleft := thePoint;
right := left + 32;
bottom := top + 32;
end;
with myMaskBitMap, bounds do begin
baseaddr := Ptr(ord4(StripAddress(theIcon^))+$80);
rowbytes := 4;
topleft := thePoint;
right := left + 32;
bottom := top + 32;
end;
GetPort(myPort);
CopyBits(myMaskBitMap, myPort^.portBits,
myMaskBitMap.bounds, myMaskBitMap.bounds,
srcBic, nil);
CopyBits(myIconBitMap, myPort^.portBits,
myIconBitMap.bounds, myIconBitMap.bounds,
srcOr, nil);
if theFlag then begin
BitClr(Ptr(HiliteMode), pHiliteBit);
CopyBits(myMaskBitMap, myPort^.portBits,
myMaskBitMap.bounds, myMaskBitMap.bounds,
srcXor, nil);
end;
Hunlock(theIcon);
end;
end;
The flag parameter indicates whether the icon should be draw selected (inverted)
or not. If an icon is already drawn from a given point, then the following routine can be
used to reverse the selection of the image.
{4}
procedure SelectIconMask (theIcon: Handle;
thePoint: Point);
var mySize, myRowbytes, myMaskOffset: INTEGER;
myMaskBitMap: BitMap;
myPort: GrafPtr;
begin
if theIcon <> nil then
if theIcon^ <> nil then begin
Hlock(theIcon);
with myMaskBitMap, bounds do begin
baseaddr := Ptr(ord4(StripAddress(theIcon^))+$80);
rowbytes := 4;
topleft := thePoint;
right := left + 32;
bottom := top + 32;
end;
GetPort(myPort);
BitClr(Ptr(HiliteMode), pHiliteBit);
CopyBits(myMaskBitMap, myPort^.portBits,
myMaskBitMap.bounds, myMaskBitMap.bounds,
srcXor, nil);
Hunlock(theIcon);
end;
end;
One last routine is needed to manipulate the ‘ICN#’ structure. The following is
the routine to determine if a spot is inside the mask of the icon (i.e., a mouse down at
this point would fall within the icon mask) at a given point. In the following routine,
thePoint is the position the icon is drawn from, while theSpot is the pixel to check.
Instead of just checking if theSpot is inside the entire 32 by 32 icon area, this methods
checks to see if theSpot is on top of the actual image as defined by the mask.
{5}
function PtInIconMask (theIcon: Handle;
thePoint, theSpot: Point): BOOLEAN;
var myRect: Rect;
myFlag: BOOLEAN;
myPtr: Ptr;
begin
myFlag := FALSE;
if theIcon <> nil then
if theIcon^ <> nil then begin
with myRect do begin
topleft := thePoint;
right := left + 32;
bottom := top + 32;