TweetFollow Us on Twitter

Color Animation
Volume Number:9
Issue Number:9
Column Tag:C Workshop

Simple Real-time Color Frame Animation

Using GWorlds and the Pallette Manager

By Scott B. Steinman, O.D., Phd., St. Louis, Missouri

Note: Source code files accompanying article are located on MacTech CD-ROM or source code disks.

About the author

Dr. Scott Steinman is an assistant professor at Washington University and has designed and developed their new computer-based Infant Vision Clinic. A software developer since 1982, Dr. Steinman is completing a textbook on the Prograph iconic programming language coauthored by Kevin Carver. He now programs in C++, Prograph and LabView. AppleLink: SBSTEINMAN. AOL: SBSTEINMAN


It’s commonly assumed by users of other computers that the Macintosh computer can't be used for real-time color animation. The Macintosh lacks dedicated graphics co-processors, such as the Amiga computer's Blitter chip, that speed up critical drawing operations. On the Amiga and IBM-compatible computers, a sequence of animated images may be both created and displayed in real time on a frame-by-frame basis by using video memory page-flipping techniques. The lack of dedicated graphics coprocessors and dual video memory banks in the Macintosh places the burden of generating images directly upon the CPU; it must execute QuickDraw graphics routines on the fly. As we all know, QuickDraw routines can be far from what their name implies. While graphics accelerator boards such as Apple’s 8•24GC do speed up some drawing routines, QuickDraw operations still can't be executed quickly enough for both real-time generation and display of animation frames.

The overhead of executing QuickDraw drawing routines may be circumvented by generating the individual frames of the animation sequence in advance and storing them in an array of off-screen pixel maps, stored in memory but not displayed. When the animation sequence is to be shown to the program user, each frame is quickly displayed by transferring the frame’s offscreen pixel map contents to the active graphics device’s memory by calling CopyBits. By reducing the graphics operations required during each animation frame interval to a single copying procedure, the animation may be carried out in real time.

Information about color animation on the Mac has only recently become available with the release of Inside Macintosh, volume VI (and the new Inside Macintosh:Devices volume). The program code explained in this article makes use of several Toolbox Managers to facilitate the presentation and storage of animated color images in real time. I should first clarify what I mean by “real time”. Unlike the QuickTime multimedia manager’s definition of “real time” as 30 frames per second, the programs I use in my laboratory require presentation of animated images at the rate of 67 frames per second -- one frame per vertical retrace of the AppleColor RGB monitor. This article will describe simple techniques for the generation of animation sequences, saving animation frames to disk for later retrieval, and displaying animation sequences in real-time. The frame images are written to disk files as a collection of PICT2 color picture resources along with a resource to preserve pertinent settings for playback of the animation sequence. The code is written in Think C 5.0.4 and Resorcerer 1.1.1. and makes use of several features of System 7.0, as well as highlights of the Vertical Retrace Manager, 32-bit QuickDraw, the Picture Utilities Package, the Standard File Package and the Memory Manager. The same techniques presented here may be used for monochrome (1-bit) images under System 7.0.

The GWorld Off-Screen Graphics Environment

Until recently, programmers have had to make major modifications to black-and-white programs to take advantage of the color environment of Macintosh computers. Programs written for monochrome Macs used simple BitMap representations of graphics to be shown on the monitor screen, where each bit in memory storage corresponded to one pixel on the screen. With the introduction of color, programmers had to keep track of complicated inter-relationships between color pixel maps, color tables, inverse color tables, graphics device records and color graphics ports. Color graphics programming proved to be difficult enough that many programmers just kept using black-and-white graphics.

Apple has now provided simpler ways of accessing the new color environment. One of these is the GWorld off-screen graphics environment that is part of 32-bit QuickDraw (Inside Macintosh, Volume VI). The other is the Palette Manager, which simplifies the specification and modification of color (Inside Macintosh, Volumes V and VI). Before delving into the workings of the real-time animation code listings, a brief introduction to some aspects of GWorlds, the Palette Manager and the Resource Manager is required. Later in this article, the Standard File Package and Picture Utilities Package, which are used to store the animation sequence in a disk file, are also discussed. The reader is referred to the excellent paper by Rensick for a discussion of the Vertical Retrace Manager, used here to accurately time the playback of the animation sequence.

Real-time animation has always been somewhat difficult on the Mac, even with just black and white (1-bit) graphics. To display an animated image, a program had to attempt to draw each animation frame within a single screen refresh interval (15 msec on the AppleColor monitor). If the drawing time exceeded this interval, the image would appear to flash or shear, or even worse, the animation would slow down to a crawl. QuickDraw drawing routines are still relatively slow, preventing direct drawing of an animation frame’s contents directly to the screen in real time, especially if the image is complex. These speed constraints can be bypassed with off-screen BitMaps or PixMaps. Drawing to off-screen memory is faster than drawing to the screen, and the contents of this memory may then be copied quickly to the screen. Similarly, animation can be accelerated by drawing each frame’s image in advance to an element of an array of off-screen BitMaps (or PixMaps), then quickly copying the contents of each BitMap to the screen in turn using the ToolBox CopyBits routine. Each call to CopyBits can be executed within one screen refresh interval (see the Discussion section for performance tests).

The GWorld graphics world construct, new to 32-bit QuickDraw, is an enhanced equivalent of the PixMap record. It contains not only a map of an image’s pixels and their colors, but also color table (CTable) and graphics device (GDevice) records. GWorlds encapsulate and insulate the inner workings of these records from the user. The programmer needs only to create a GWorld graphics environment with a call to the NewGWorld routine and later dispose of it with a call to DisposeGWorld. In the case of color frame animation, an array of GWorlds may be created where each GWorld is assigned to hold one animation frame’s image. Rather than attempting to draw each frame directly on the screen, we instead draw them in advance into an array of off-screen GWorld records. When the animation sequence is to be displayed, all we need do is copy the GWorld contents to the screen one-by-one in real time.

As a bonus, the GWorld record does more than just store our animated images; it also accelerates the animation display itself. When off-screen images are copied from memory to the screen, the speed of the transfer is affected not only by image size and number of colors, but also by the position of the image in memory. In other words, on what bit (within a byte) or byte (within a long word) of memory does the leftmost pixel of the image fall? If there is a mismatch in the position in memory between the image in the PixMap and the desired copy of the image on the screen, CopyBits must account for it, and therefore works slower. Images whose PixMaps are aligned to long word boundaries are transferred by CopyBits faster than those that are not. GWorlds allow some control over the speed of image transfers by correcting for these factors. If the programmer converts the desired GWorld bounding rectangle to global QuickDraw coordinates before passing it to NewGWorld (see Inside Macintosh, Volume VI), the NewGWorld routine will automatically optimize the graphics image position within the GWorld pixel map so that CopyBits will work as quickly as possible. This optimization doesn’t affect where the graphics will appear on the screen, but only the internal representation of the graphics image within the GWorld PixMap.

GWorlds and the Palette Manager

Display monitors are represented in the Macintosh operating system by a GDevice data record that contains, among other things, the color environment for that monitor. This ensures that each monitor of a multiple-monitor system preserves its own individual color environment. Offscreen graphics are also represented using a GDevice (graphics device) record, but in this case the off-screen device does not exist in a physical sense. When a GWorld is created with NewGWorld, the programmer may choose to create a new off-screen graphics device to hold the GWorld’s image, or to use a pre-existing GDevice. This option allows an array of GWorlds to share a single graphics device rather than create one off-screen device for each GWorld. The programmer must also provide a CTable record for the GWorld’s graphics device, either explicitly by passing a color table record to NewGWorld or implicitly by using the CTable color table of the physical graphics device on which the GWorld’s image will ultimately be displayed.

Because GWorlds contain graphics device information, the Palette Manager routines used for graphics devices are made available to them. In the frame animation program, parallel updates in color information of the animation display window and of each animation frame are accomplished easily using the same Palette Manager calls. This means that the color environment of the display window and the animation frames is always kept synchronized. The frame’s image therefore blends seamlessly into the window background.

Pictures and Resource Files

Macintosh graphics may be specified in two ways: either as a PixMap, in which the graphics are represented as arrays of pixels on the screen, or as a Picture, collections of encoded drawing instructions which may be used at any time to reconstruct the pixel form of the graphics image. Single color graphics images are typically stored as Pictures in the PICT file format. The equivalent file format for animation sequences is the PICS file, which is essentially a collection of Pictures with added information such as animation playback speed. These standard file formats differ in that a PICT graphics file stores the images in the file's data fork, while the PICS animation file stores the individual frame images in the file's resource fork. The color information for the images is stored in each case within the PICT data or resources themselves -- no separate color table (‘clut’) or palette (‘pltt’) resources are provided. If a programmer wishes to extract the color information in a standard data-fork-based PICT file to set the palette of the window that will display the file’s picture, the image must be picked apart piecemeal with complicated and fairly cryptic custom QuickDraw bottleneck routines supplied by the programmer (Sheets, 1988). Special spooling techniques must be used just to read and write the Picture data of PICT data files (see Inside Macintosh, Volumes V and VI).

A more straightforward way of saving and restoring the individual graphics images and the color environment is to save the images as PICT resources. Their color information can be easily retrieved from the PICTs themselves using the new System 7.0 Picture Utilities Package. Using PICT resources may reduce to some extent direct compatibility with commercial paint packages that use data fork PICT storage, but the programmer may more easily restore the color environment before playing an animation sequence from disk. A resource editor such as ResEdit or Resorcerer may be used to transfer graphics created by commercial paint programs to PICT resources format via the clipboard. In addition, commercial animation packages store information in the PICS format.

The GetPictInfo function is used to obtain the color table of the animation sequence file’s PICT resources so that we may reset the color environment of our off-screen GWorlds and display window after reading in an animation file. This allows the program code that will display an animated visual stimulus previously stored in a disk file to easily set the window background color equal to that of the animation frame. This is very important when the animated image is to be displayed on a background without a visible border between the animated image and the background. By retrieving the CTable from the PICT resources, the proper background color is maintained every time a stored animated image is presented.

Resources also provide an elegant way to store relevant playback parameters in the animation file. A PICS animation information (‘INFO’) resource may be created to store a structure containing the animation frame presentation rate, use of color, frame size, etc. This resource is read from the file before the animation is to be presented to the user, which serves to ensure the proper presentation of the animation. An added benefit of storing the playback parameters as resources is that this data may be easily examined and modified later in the animation file using ResEdit or Resorcerer to create new permutations of the animated image. Were the playback parameters stored in the data fork of the animation file, modifications (with a hexadecimal file editor) would be difficult at best.

The Color Frame Animation Program

Using the techniques outlined above, 100 frames of animation containing a 200 by 200 pixel frame area in 8-bit (256 color) gray-scale have been animated in real-time1 (67 frames/second) on a Mac IIfx computer. As discussed below, it is possible to animate even larger images in real-time. The program code listed here is part of a program for visual motion experiments that is composed of two interdependent parts. The first part calculates and draws the animation frames for a moving stimulus, which are then stored to a disk file. The second part presents the moving stimuli to the user and tallies responses (Steinman and Nawrot, 1992). Several crucial variables are shown here as global variables rather than function parameters in order to simplify the function declarations, but the use of global variables should be limited in practice. In my research applications, I encapsulate the animation and animation file access code into a C++ class called CAnimation that can be subclassed to produce different animated images.

Main.c is the file that contains the program’s main function. This listing opens with a series of variable declarations that deal with the construction of an animation sequence The most important variable is an array of pointers to GWorlds called gFrames that holds the animation sequence frame images in PixMap form. These PixMaps are transferred to the screen when the animation is to be shown to the user. By the way, the extern declaration (gFramePICTs) is for an array of handles to Pictures for storage of the animation frames to disk. Next are gPalette, a handle to a palette, and gCTable, a handle to a color table, which both will specify gray levels used in the display window and animation frames. The animation code was written for vision experiments to present gray-scale stimuli whose contrast levels are set individually by the experimenter. The animation settings are stored in a custom Settings structure, and a custom Flags structure holds general program execution flags.

CheckEnvironment takes advantage of the Gestalt Manager to check whether or not we have the proper environment for running this program. In this case, it means ensuring that System 7, the Slot Manager, the Palette Manager, 32-bit QuickDraw and a floating-point chip are present. The SetMonitors function calls the HasDepth Toolbox routine to check if we can set the monitor color depth to a desired depth of 4 bits (16 colors), then SetDepth actually does so.

The InitParams function initializes major animation parameters and nulls the handles and pointers used by later memory allocation. Since animation sequences require large amounts of memory, the program depends heavily upon the Memory Manager to make the most efficient use of available memory. Most data structures are allocated dynamically and moved to high memory whenever possible to allow the Memory Manager access to as much contiguous RAM as possible. The MaximizeHeap function further assures that free space is available for storing animation sequences. It checks how much heap space may be freed up by purging memory blocks and compacting the heap, then actually performs the purging and compaction to make as much contiguous heap space available to our program as possible. The function CleanUp frees the memory allocated for the animation sequences in PixMap and Picture formats, the CTable and the Palette. This function is called upon program exit, either when the experimenter has completed the construction of stimuli or running an experiment, or if a serious error such as insufficient memory occurs during run-time. The function ErrorHandler presents error messages to the user by retrieving error message strings from a STR# resource.

The program header file, FrameAnim.h, provides general definitions such as resource ID number constants for the frame images, color environment and parameters that will be saved in a disk file (see listing Files.c). The resource ID number kGrayCTableID identifies one color table resource stored in the Macintosh ROM. The ROM contains several color table resources containing gray-scale “ramps” whose ID number is simply the color depth plus 32. In the present program, a depth of 4 bits (16 gray levels) is used, and so the ROM gray-scale resource ID number is 4 plus 32, or 36. Also included are the user interface resource IDs, a series of color-specific definitions, including palette indices for the gray levels to be defined by the program. The first entry of a palette (entry 0) is defined by Apple Computer to be white, while the last entry (in the 16 color palette, entry number 15) must be black. The remaining palette entries (1-14) may be used by the animation program, although only two entries (entries kPalBkgnd and kPalTarget, used for the background and target colors, respectively) are used in the present code examples. Finally, animation-specific constants are defined. kMaxFrames is the maximum allowable number of animation frames. Although this has been defined here to be 60 frames, the animation sequence may be composed of any number of frames, limited only by the amount of available application heap memory and disk space. The speed of the animation is limited only by the color depth and image size, not by the number of animation frames.

The header file also contains important data type definitions including the specification of a structure to store the animation playback parameters as a resource in the animation file. The reader is directed to the article by Sheets for further information on the PICS animation format. Although some of the fields of the structure are not used by the present program, they are included for compatibility with commercial PICS animation programs. The PICSInfoRec structure contains information on the animation sequence’s use of color, frame size and frame playback delay. The Settings structure includes key information about the animated targets, such as the target gray level, the number of frames in the animation sequence and the delay between the presentation of each frame, as well as which monitor to display the target on in a multi-monitor system.

Some variables, such as the frame delay, must also be kept in a structure that can be accessed easily by the vertical blank interrupt code. The time-critical video interrupt routines (listed in VBlank.c) need to access this information quickly. The XVBLTask structure encapsulates the VBLTask record passed to Vertical Retrace Manager routines, the state of the CPU’s A5 register, and copies of the most critical animation playback variables. This information must all be passed as a unit to the interrupt task service routine, because interrupt routines take no arguments and may only receive information by means of global variables retrieved from the processor’s A0 register during the interrupt task. We’ll return to these issues when we discuss the VBlank.c file.

The color environment is manipulated by the code in Setup.c. InitPalette sets up a gray-scale palette and color table to be shared by the display window (gMainWindow) and the animation frame GWorlds (gFrames). A grayscale ramp color table is obtained from the Macintosh system ROM with a call to GetCTable and saved in gCTable. The NewPalette routine then creates a gray-scale palette from gCTable. Two flags, pmTolerant and pmExplicit, tell the Palette Manager how the palette will be used (see van Brink, 1990 and Inside Macintosh, Volume V, for a full discussion of Palette Manager routines). They dictate how a color that is requested from the palette for a drawing operation relates to the colors actually present in the display device’s color look-up table. The pmTolerant flag states that a given palette entry has a “close match” present in the look-up table (precisely how close it must be is given in the last parameter passed to NewPalette). The pmExplicit flag states that a requested palette entry has a corresponding color in the device color look-up table at the same entry number in the table. These two flags (and the last NewPalette parameter) in a sense describe the degree of “noise” that’s accepted in the colors the system will actually provide when the programmer requests a specific color. Although it is not intuitive, a combination of the pmTolerant and pmExplicit flags is required for proper reconstruction of the color environment from our animation file. This flag combination ensures that a color requested from the palette exactly matches its corresponding device look-up table color entry; in other words, the color asked for is precisely the color obtained. The call to NSetPalette assigns the new gPalette palette to a specific window, in this case, the gMainWindow window in which the animation will be displayed. Finally, the palette is activated for use in the display window.

The UpdateWindowColors function resets the color palette if the user requests a new background or target color. It directly replaces specific color entries in the palette with new color values. SetEntryColor places the new color value (in RGBColor form) at a given entry position (kPalTarget or kPalBkgnd) in the gPalette palette. The changes to the palette are then mirrored in the gCTable color table that is used to create animation frame GWorlds via the Palette2CTab routine. Finally, the updated palette is activated for use. The corresponding changes to the GWorlds’ color environment are handled by the UpdateGWorldColors function in the Animation.c file.

By the way, the GetNumMonitors function simply uses the GetMainDevice and GetNextDevice Toolbox routines to check if there are one or two monitors connected to our Macintosh. If there are two, our program will give us the option of displaying the animation on the main monitor or the second monitor.

Listing Animation.c is the heart of the animation program. It begins with a Rect declaration that defines the drawing area for the animation frame images. sDrawArea is the bounds of an animation frame image referenced to the coordinates of the display window in which the animation will be presented. gBounds defines the extent of the frame image within the PixMap of each GWorld, expressed in the GWorld’s local QuickDraw coordinates. These rectangles that define the bounds of the image drawing and storage areas are set in the SetDrawCoords and SetOrigin functions.

The PrepareFilm function allocates and initializes the off-screen GWorlds that hold the animation frames in pixel map form for later display. First, the display window palette is updated to ensure that GWorlds are created using the most recent color values. Next, the SetDrawCoords function is called to define the extent of the frame image stored in the GWorlds. This value, gBounds, is defined in local QuickDraw coordinates. As discussed above in the section about GWorld off-screen graphics environments, several optimizations are performed upon the GWorld image if the bounding rectangle is converted to global QuickDraw coordinates before passing it to NewGWorld. Therefore, the bounding rectangle is converted to global coordinates and stored in the gGlobBounds variable. An array of new GWorlds is created using the NewGWorld routine (after destroying any previously used GWorlds and compacting the heap). The first GWorld in the array will by default be assigned an off-screen graphics device by passing no flags to NewGWorld. By passing no color table handle to NewGWorld and specifying a color depth of zero, NewGWorld is forced to use the color table and depth of the graphics device on which the animation will be later displayed (see van Ortiz, 1990, for a description of NewGWorld input parameters). The GWorlds will therefore be set to a depth of 4 (for 16 colors) and the default color table of a 16-color device since the monitor has already been set by SetMonitorDepth in Main.c to a depth of 4. The NoPurgePixels routine ensures that the pixel map memory of the GWorld won’t be destroyed without our knowledge if the Memory Manager must free up memory during program execution. After creating the first GWorld in the sequence, we can find out the amount of memory needed for a GWorld and its associated PixMap via GetPtrSize and GetHandleSize, respectively, then use this information to free a memory block large enough to hold the remaining GWorlds by passing it to MaximizeHeap. The remaining GWorlds in the gAnimFrames array are then created using the GDevice of the first GWorld, obtained with the GetGWorldDevice routine. In this manner, all of the GWorlds share the same graphics device, saving memory.

The DoFilm function creates the individual animation frame images. The PrepareFilm function is first called to prepare GWorlds to house the animation frames. The current drawing port and device are saved by a call to GetGWorld. To draw directly into the GWorld pixel maps, the memory handle that points to the pixel map must be locked using the LockPixels routine, just as other memory handles in Macintosh programs must be locked with the HLock routine. The current drawing port and device are then set to those of the GWorld we wish to draw in with SetGWorld. Next, the PlaceTargets function, which does the actual drawing, is called. Finally, the previous drawing port and device are restored and the GWorld pixel map memory handle is unlocked. The drawing pen colors are reset to black and white for proper operation of CopyBits (CopyBits will alter the colors of the image it transfers if the pen colors are not set to these values).

PlaceTargets carries out the actual drawing of the animation frame images into both the GWorld pixel maps (for display of the animation sequence) and the Picture records (to be saved in the animation file on disk). First, the size of the target, a simple rectangle in this example program, is calculated for this frame. The target will grow and shrink in the animation. Then the image to be stored in the GWorld is drawn. The drawing pen size is set, and the GWorld pixel map contents are erased using the background color. The pen color is then set to the target color and the target is drawn.

In order to save the animation frame in Picture format in a resource file, a Picture of the frame must be created. Unfortunately, Picture instructions do not understand Palette Manager calls, and the older Color Manager equivalents must be used. To do so, the target and background colors must first be converted to the RGBColor form understood by the Color Manager. The GetEntryColor routine converts palette entries into RGBColors. A Picture record is then opened for filling with drawing instructions using the OpenPicture call. To copy the frame’s contents into the Picture, we repeat the drawing instructions that create the animated target for that frame. Since a Picture record has been opened, the image is recreated in the Picture. Simple, isn’t it? The frame drawing instructions are the same as those used to draw into the GWorld except that they use the Color Manager routine RGBForeColor instead of PmForeColor. The Picture sequence is finally closed via ClosePicture.

The PlayFilm function is called when the animation sequence is to be presented to the user. It first hides the mouse pointer from view, then locks the GWorld PixMaps so that we may access them. The SetUpVBlankAnimation function is called to initiate the animation. Among other things, it initializes the frameIndex frame counter in the XVBLTask structure to the first frame of the animation sequence. The animation display is enabled by setting the vblCount field of the XVBLTask structure to a non-zero value. A loop is now entered that copies each frame’s image from its GWorld to the display window until the frame counter frameIndex has reached the final frame number; that is, when the entire animation sequence has been shown. Within the loop, the showFrame flag in the XVBLTask structure is set to false to prevent the next frame from being copied to the screen until the next vertical retrace interrupt has occurred. Our VBlank interrupt routine (in file VBlank.c) sets this flag to true whenever a VBlank interrupt occurs, allowing a new frame to be displayed. When the display loop exits, the ShutDownVBlankAnimation function is called to preventing further animation. The background is then erased by calling PaintRect, and the mouse pointer is revealed again.

UpdateGWorldColors is used to change each animation frame GWorld’s color information whenever the display window’s color environment is modified. This keeps the color environment of the frame and the window in sync. UpdateGWorldColors treats each GWorld as a color port with its own palette. The same Palette Manager calls are used for each GWorld as for a window.

Finally, the DisposeFrames function frees the memory used to store the animation sequence frame images in the GWorlds.

Listing VBlank.c contains the video interrupt-based animation timing routines. We’ll concentrate on the portions of this code that are specific to presenting color frame animation. For a full discussion of millisecond and vertical retrace timing, consult Rensick’s article. SetUpVBlankAnimation sets up the video interrupt task in which we will do the actual animation. A custom XVBLTaskRec structure is defined in the header file FrameAnim.h to store both the VBL task record, the contents of the CPU’s A5 register, and additional animation playback parameters that we’ll need to access during the interrupt task. Why have I made such a fuss about storing the contents of the A5 register in this structure? The A5 register holds a pointer to the beginning of a memory block that holds our application’s global variables. If you wish to access any global variable’s values during an interrupt service routine, you must ensure that the value in the A5 register is valid. Unfortunately, when an interrupt occurs, the operating system takes over, and its A5 setting may not be the same as that of your program. Therefore, you must save the contents in the A5 register before the interrupt occurs, then use it to access your program’s global variable space.

Why global variables? Interrupt task routines do not take any arguments! The only way to pass information to them is by way of a global variable. More what is more important, we only get to pass one global variable (through its address) which is passed onto the A0 register of the CPU. We have defined a trap, GetVBLRec, in the header file to push the contents of the A0 register onto the stack (and therefore into a local variable within our VideoProc interrupt service routine, described below).

We must now fill the fields of the XVBLTaskRec structure with their required values. SetUpVBlankAnimation first finds the slot number of the NuBus slot holding our Macintosh’s graphics board (or the slot number assigned to the internal video output). The task procedure address is set to that of the VideoProc procedure that regulates our animation. The vblCount field of the video task record, which determines how often the VideoProc will be executed, will eventually be given a value equal to frameDelay, the number of screen refreshes between animation frames. In other words, once every frameDelay vertical blanks, a new animation frame will be displayed. The value of vblCount therefore determines in part the velocity of a moving target shown in an animation sequence. However, for now we set this field to 0, which will inhibit the display of the animation until we are ready. The current setting of the A5 register is saved in the a5 field. The frames field is set to the number of frames in the animation sequence and the index to the currently displayed animation frame in the frame array (frameIndex) is initialized to -1 (it will be set to 0 on the first video interrupt). The showFrame flag is set to false to ensure that when the display loop in file Animation.c is executed, a new frame won’t be displayed until a video interrupt has in fact occurred.

The VideoProc function is the core of the real-time animation technique. This function is called once every frameDelay vertical blanks. It retrieves a pointer to the XVBLTaskRec, stored by the operating system’s VBL interrupt handler, from the processor register A0. VideoProc can then access the XVBLTaskRec structure’s contents. If we haven’t yet shown the last frame of the animation sequence, we increment the frame index, and set the showFrame flag to true and vblCount to the frame delay. If the entire sequence has been shown, we inhibit showing any more animation by setting showFrame to false and vblCount to 0. When the VideoProc function exits, the program returns control to the frame display loop in PlayFilm. With the showFrame flag set to true, CopyBits can be called and the GWorld image is transferred to screen memory. The video interrupt task is finally removed from the interrupt queue by a call to ShutDownVBlankAnimation, which removes the video interrupt task from the interrupt queue, restoring the interrupt queue to its original state.

The last major code fragment presented here, listing Files.c, shows how the newly created animation sequence and playback parameters are stored to disk and retrieved later when the animation sequence is to be displayed to the user. This code makes use of System 7.0’s new Standard File Package, which simplifies the presentation of dialogs to users for selecting parent folders and file names for files to be saved or opened. Recent changes to the File Manager mirror the simplification of the Standard File Package to new routines for the creation, opening, reading and writing of files. All of the new functions take advantage of a new StandardFileReply structure, which combines the old fName, parID and volID variables that describe the location of a file on a storage volume (along with other information), into one simple to use structure.

SaveFilm is responsible for setting up the saving of the animation information to a disk file. It calls the Standard File Package’s StandardPutFile routine to present the typical Save File dialog. Unless the user cancels the selection of a file name for the new file, FSpCreateResFile is called to create a resource file (a file with a resource fork) to hold our animation information. The creator of this file is ‘FRMA’, our animation program’s signature. The file type is set to ‘PICS’, the standard animation file format. The WriteFilm function is then called.

WriteFilm performs the actual storage of the information into the file. Some of the code within this function is required due to an easily overlooked feature of the Resource Manager. When a resource file is opened, a table of the resources in the file called a “resource map” is created, which effectively links each resource in the file to a handle to a data record in memory that holds the contents of the resource. WriteFilm creates a resource file and uses AddResource to add handles to a playback parameter structure and each animation frame Picture to the resource map of the file. When CloseResFile is later called, this information is automatically stored as resources in the disk file. However, CloseResFile also destroys the resource map, which has the side-effect of freeing the memory handles in the resource map. These handles still point to the array of animation frame Pictures and the animation playback structure, so these structures are now destroyed. While this is not a problem for data structures that are not used later, you must avoid destruction of any data that might be referenced at some later point in your program. This could have adverse effects on program execution (in other words, the dreaded “bomb”). Therefore, you should use a copy of critical information for storage in a resource file. Only the copy would be destroyed when CloseResFile frees the resource map, leaving the original data intact. Luckily in our frame animation program, the Pictures and playback information can be disposed of once the animation sequence is saved to disk.

WriteFilm first allocates storage for a PICSInfoRec structure (sPicInfo) to hold values of the global playback parameter variables declared in Main.c. These variables must be encased in a structure to be saved in one block to a resource. The resource fork of the file selected and created in SaveImage is opened for use with a call to FSpOpenResFile. The sPicInfo structure is then added to the resource file map via AddResource. Each frame of the animation sequence, stored in Picture format in the framePICT array, is then transferred to a separate resource by a call to AddResource. AddResource is passed a handle to an animation frame Picture, a definition of the type of resource to be saved (in this case, a ‘PICT’ Picture resource), a unique ID number for the resource as defined in the header file shown in FrameAnim.h, and a optional name for the resource that helps the programmer when viewing or modifying these resources with ResEdit or Resorcerer. UpdateResFile automatically forces the resources in our new resource file to be updated (physically written to the file). CloseResFile releases the file’s resource map and closes the resource file. Unfortunately, when the handles in the resource map are freed, the handles are not reset to “null” (the value of an unused handle or pointer). If a memory-freeing function were called at any time after saving the resource file and these handles were not set to “null”, the function would attempt to free the memory locations contained in the handles, which the Memory Manager could by now have assigned to hold other information! The WriteImage function therefore explicitly sets these handles to “null” to prevent a potential catastrophe.

Reading a previously-saved animation sequence file involves similar steps as those carried out in SaveFilm and WriteFilm. The OpenFilm function calls StandardGetFile to present the standard Get File dialog, then ReadFilm to read the contents of the animation resource file. The ReadFilm function starts by freeing memory that has been previously allocated for animation frames, the palette and the color table, so that these may be replaced by new ones created from the animation file. The resource file selected in OpenFilm is then opened, and a PICSInfoRec structure created by reading the corresponding resource from the animation file. Because the playback parameter structure (sPicInfo) is stored in a custom resource, there is no dedicated ToolBox routine for reading the contents of this resource. Instead, the generic Get1Resource routine is used to create and initialize the memory that will store the contents of this custom ‘INFO’ resource. The memory block returned by this routine is typecast to a PICSInfoRec structure. The global variable frameDelay is set with the speed member of the PICSInfoRec structure and the refresh rate. A new set of GWorld frames is then created as well. The frame images, stored in Picture form in ‘PICT‘ resources in the animation file, are read with a loop containing calls to GetPicture. The number of iterations through this loop is set by counting the number of ‘PICT’ resources in the file; that is, the number of frames in the animation sequence. DetachResource allows the Picture structures (now stored in the gFramePICTs array) to remain in memory and not be destroyed after the resource file is closed and the file’s resource map is freed.

A new palette and color table are created from these Pictures by using the new Picture Utilities Package’s GetPictInfo routine, which can read the colors presaent in a ‘PICT’ resource into either a Color Table or a Palette. By passing the returnColorTable flag, we choose to retrieve the colors of the first frame of the animation sequence into a Color Table. We can either get a table of all the exact colors or the best-fitting colors to those in the Picture. In our case, we select the exact colors with the systemMethod flag and reset bkgndGray and targetGray with these color values. The call to NewColorsFromPICT function does the equivalent of the UpdateWindowColors function to reset the color table and palette for our animation sequence. We also read the Picture’s size into the Settings structure’s frameSize field. The frame images in the Pictures are then drawn into the animation sequence GWorlds. Finally, the resource file is closed.

The DrawPICTToGWorld function transfers the animation frame images from the framePICT array into an array of GWorlds (gFrames). For each animation frame in turn, the pixel map of the GWorld is drawn into simply by calling the DrawPicture routine, which converts the Picture instructions into PixMap images. The DrawPicture call is enclosed by the appropriate memory locking and port-setting procedures required when accessing GWorld pixel maps.

Files.c concludes with the DisposFramePicts function to free the memory allocated for the framePICT array. The individual frames of the sequence are deallocated via the KillPicture routine, which specifically destroys Picture structures specifically.

The final few listings, which won’t be discussed here, contain the dialog box functions (Dialogs.c), the event-handling functions (Events.c) and the Rez-format resource listings (FrameAnim.r).


Real-time color animation may be accomplished by methods other than frame animation. Baro and Hughes describe a color-cycling animation technique using the Palette Manager on single images. However, there are many types of animated stimuli that cannot easily be created by palette modifications alone, the most important being targets that move along paths or change shape. Frame animation is better suited for these types of targets. By precomputing the graphics images of each frame, either programmatically or with a commercial paint package, we remove the need to perform time-consuming drawing operations when presenting animation. At playback, only one fast CopyBits operation is required, allowing larger, more complicated images to be used. By using a single transfer routine call to perform the animation, the Vertical Retrace Manager may be used for accurate timing of the animation presentation, ensuring precise frame timing and target velocities.

Unfortunately, frame animation does introduce some limitations upon the complexity of the stimulus. The allowable physical size and color depth of the animation frames constrain the overall size of the target and the number of colors that can be displayed at once (see Macintosh Technical Notes # 277). These limitations arise from the upper envelope of how much graphics memory may be transferred by the CopyBits routine during one vertical retrace interval. The larger the image and the higher its color depth, the longer it takes to transfer the image from off-screen memory to the screen. If the transfer time is not kept brief, the transfer may take more than one retrace period, slowing down the animation and introducing inaccuracies in visual motion velocity. When testing the animation code presented here on a Macintosh IIfx computer, it was found that when using 4-bit color (16 color) displays, real-time animation was possible only on images limited to 350 by 350 pixels in size. When 8-bit color (256 color) displays were used, the largest possible image size for real-time animation was reduced to 230 by 230 pixels. If only simple targets are to be presented, 2-bit color allows the display of full-screen (640 by 480 pixels) real-time stimuli in which the target color may still be manipulated somewhat. The amount of available memory also places an upper limit on the number of frames and the size and depth of the frames that may be displayed in an animation sequence.

Although the current program has been tested on the Macintosh IIfx, IIci, IIsi, and II, both with and without the Apple 8•24GC QuickDraw accelerator graphics board, there is no guarantee that color Macintoshes with slower CPUs, such as the Macintosh LC, will be able to transfer large multi-colored images quickly enough. Unfortunately, I was not able to test my code on such machines. On these models, smaller images of fewer colors may be required, or the programmer may have to restrict animation to palette animation techniques alone. The installation of a QuickDraw accelerator board will not significantly increase the speed of the animation display because only a single CopyBits call is executed within the vertical retrace interrupt task. However, it would speed up the initial drawing of the images to the off-screen GWorlds.

For those owners of monochrome Macintoshes, the code I’ve presented will still work. The latest version of 32-bit QuickDraw included in System 7.0 (see Inside Macintosh, volume VI) will automatically create a black and white BitMap within GWorld records created with NewGWorld when run on a monochrome Mac. Simply remove the palette and color table routines from my program and join the fun!

Despite the current restrictions on the use of color frame animation, the techniques described here allow the creation of complex moving stimuli in real-time. The code listings are intended only to provide a framework with which an animation program may be written. Several improvements could be made on it, including the simultaneous use of frame animation and palette animation, which I’ve done in my laboratory for vision experiments. The reader is referred to the recent article by Rich Collyer for tips on how to use Palette animation together with GWorlds.


1 The duration of each animation frame was confirmed using millisecond-resolution timing routines (Rensick, 1990). An animation sequence was generated and displayed, with the timer started at the onset of the animation display and stopped at the end of the sequence. The total elapsed time was then divided by the number of frames to get the single frame time. A single frame was found to take 15 msec, the duration of a vertical retrace on the AppleColor RGB monitor.


Anstis, Stuart and Paradiso, Michael. “Programs for visual psychophysics on the Amiga: a tutorial,” Behavior Research Methods, Instruments & Computers, 1989, v.21, p.548ff.

Baro, John A. and Hughes, Howard C. “The display and animation of full-color images in real time on the Macintosh computer,” Behavior Research Methods, Instruments & Computers, 1991, v.23, p. 537ff.

Collyer, Rich. “Palette Manager animation,” Develop, Winter 1991, p.78ff.

Inside Macintosh: Volumes I-VI.

Macintosh Technical Notes # 276, “Gimme depth or gimme death,” June 1990.

Macintosh Technical Notes # 277, “Of time and space and CopyBits,” June 1990.

Ortiz, Guillermo. “Braving offscreen worlds,” Develop, January 1990, p.28ff.

Rensick, Ronald A. “Toolbox-based routines for Macintosh timing and display,” Behavior Research Methods, Instruments & Computers, 1990, v.22, p.105ff.

Sheets, Steven. “Color screen dump FKEY,” MacTutor, February 1988, p.52ff.

Sheets, Steven. “Animating PICS,” MacTutor, March 1991, p.64ff.

Steinman, Scott B. and Nawrot, Mark. “Real-time color frame animation for visual psychophysics on the Macintosh computer,” Behavior Research Methods, Instrumentation and Computers, 1992, v.24, p.439ff.

Steinman, Scott B. and Nawrot, Mark. “Software for the generation and display of random-dot cinematograms on the Macintosh computer,” Behavior Research Methods, Instrumentation and Computers, 1992, v.24, p.573ff.

“Think Reference,” version 2.0, Symantec Corporation.

van Brink, David. “All about the Palette Manager,” Develop, January 1990, p.22ff.

Wenderoth, Peter. “Software-based visual psychophysics using the Commodore Amiga with Deluxe Paint III,” Behavior Research Methods, Instruments & Computers, 1990, v.22, p.383ff.

Program Listings

As with all articles in MacTech Magazine, only the most relevant code is shown here. For complete listings, see the MacTech Magazine Source Code disk or our online areas. For more information, see page 2 of this issue.

Excerpts from Listing:  Main.c
// •----------- Set Monitor Color Depth ------
// • Changes monitor depth to match 
// • desired depth of animation frames.
static void SetMonitorDepth( void )
 PixMapHandle  pmh = NullHandle;
 gDevice = GetMainDevice();
 if (gSettings.numMonitors == 2 &&
 gSettings.displayMonitor == kSecondary)
 gDevice = GetNextDevice( gDevice );

 HLock( (Handle) gDevice );
 pmh = (**gDevice).gdPMap;
 HLock( (Handle) pmh );
 gSettings.currDepth = (**pmh).pixelSize;
 HUnlock( (Handle) pmh );
 HUnlock( (Handle) gDevice ); 
 if (HasDepth( gDevice, kDepth, 1, 1 ) != 0)
 SetDepth( gDevice, kDepth, 1, 1 );
 ErrorHandler( kNoDepthMsg, 
 (char *) NilString, (char *) NilString,                       (char 
*) NilString );

// • ---------- Free As Much Heap As Possible 
// • Get more contiguous heap space.
void MaximizeHeap( const long bytesNeeded )
 long totalBytes, contigBytes;
 // • If enough contiguous heap space, exit.
 if (MaxBlock() >= bytesNeeded) 
 // • See how much space is possible.
 PurgeSpace( &totalBytes, &contigBytes );
 // • Free all purgeable, unlocked blocks and compact memory. 
 PurgeMem( totalBytes );
 CompactMem( totalBytes );
Listing:  Animation.c
// •••••••••••••••••••••••••••••••••••••••••••
// • Program:  FrameAnim  File: Animation.c
// • © 1993 by Dr. Scott Steinman. All Rights Reserved.
// •••••••••••••••••••••••••••••••••••••••••••
#include "FrameAnim.h"

// • ---------- External Globals -------------
// • From Main.c file
extern CWindowPtrgMainWindow; 
extern GWorldPtr gFrames[]; 
extern PaletteHandle gPalette;
extern CTabHandlegCTable;
extern Settings  gSettings; 

// • From Files.c file    
extern PicHandle gFramePICTs[];

// • From VBlanks.c file  
extern XVBLTask  gXVBLTask; 

// • ---------- Globals ----------------------
// • GWorld bounds 
Rect    gGlobBounds, // • Global coords 
 gBounds; // • Local coords

// •----------- Static Variables -------------
static Rect sDrawArea,  // • Frame bounds
 sTarget; // • Target square
static PointsOrigin, // • Window center
 sCenter; // • Frame center
static shortsRectSize;  // • Target width
static Boolean sIncrSize; // • T=grow,F=shrink

// • ---------- Static Functions -------------
static void PlaceTargets( const short );
static void SetDrawCoords( void );

// • ---------- Find Drawing Origin ----------
// • Find center of display window.
void FindOrigin( void )
 short  v, h;    // • Size of display window 
 h = gMainWindow->portRect.right - 
 v = gMainWindow->portRect.bottom - 
 SetPt( &sOrigin, h / 2, v / 2 );

// • ---------- Set Drawing Coordinates ------
// • Set up drawing bounds for GWorlds and display window 
// • in local and global coordinates.
static void SetDrawCoords( void )
 short  radius;
 Point  topLeft, botRight;

 radius = gSettings.frameSize / 2;  // • Half-width of frame   
 // • Set up drawing area and center point inGWorlds     
 SetRect( &gBounds, 0, 0, gSettings.frameSize, 
 gSettings.frameSize );
 SetPt( &sCenter, gSettings.frameSize / 2, 
 gSettings.frameSize / 2 );

 // • Set local coordinates in window for GWorld copying
 SetRect( &sDrawArea, sOrigin.h - radius, sOrigin.v - radius, 
 sOrigin.h + radius, sOrigin.v + radius );

 // • Convert GWorld drawing area to global screen 
 // • coordinates (Used in NewGWorld for faster 
 // • CopyBits operations).
 SetPt( &topLeft, sDrawArea.left, );
 LocalToGlobal( &topLeft );
 SetPt( &botRight, sDrawArea.right, sDrawArea.bottom );
 LocalToGlobal( &botRight );
 SetRect( &gGlobBounds, topLeft.h, topLeft.v, 
 botRight.h, botRight.v );

// • ---------- Allocate Memory for Frames ---
// • Inits blocks of memory for the GWorlds to be stored.
void PrepareFilm( void )
 GDHandle currentDev = NullHandle;
 CGrafPtr currentPort = NullPointer;
 QDErr  qderror;
 short  i;
 Str255 errStr;
 // • Save cur port & device  
 GetGWorld( &currentPort, &currentDev ); 
 UpdateWindowColors();   // • Ensure palette is current  

 // • Free any previously-allocated animation frame GWorlds. 
 // • Set bounds for drawing into GWorlds and window.

 // • Allocate space on heap for new GWorlds. 
 qderror = NewGWorld( &gFrames[ 0 ], 0, &gGlobBounds, 
 NullHandle, NullHandle, (GWorldFlags) NoFlags );  
 if (qderror != noErr) {
 ErrNumToPstr( qderror, (char *) errStr );
 ErrorHandler( kNoGWorldMsg, (char *) "\pErrorCode = ", 
 (char *) errStr, (char *) NilString );
 if (gFrames[ 0 ] == NullPointer)
 ErrorHandler( kNoMemoryMsg, (char *) NilString, 
 (char *) NilString, (char *) NilString );
 // • Move GWorld PixMaps to top of heap 
 // • to maximize contiguous memory.
 MoveHHi( (Handle) GetGWorldPixMap( gFrames[ 0 ] ) );
 // • Prevent freeing of PixMap memory until we're ready.
 NoPurgePixels( GetGWorldPixMap( gFrames[ 0 ] ) );

 // • Get as much contiguous heap space 
 // • for the GWorlds as possible.
 MaximizeHeap( (GetPtrSize( gFrames[ 0 ] ) + 
 GetHandleSize( GetGWorldPixMap( gFrames[ 0 ] ) ) ) 
 * kMaxFrames );
 for (i = 1; i < gSettings.numFrames; i++) {       
 qderror = NewGWorld( &gFrames[ i ], 0, 
 &gGlobBounds, NullHandle, 
   GetGWorldDevice( gFrames[ 0 ] ), noNewDevice );             
 if (qderror != noErr) {
 ErrNumToPstr( qderror, (char *) errStr );
 ErrorHandler( kNoGWorldMsg, (char *) 
 "\pErrorCode = ", (char *) errStr, 
 (char *) NilString );
 if (gFrames[ i ] == NullPointer)
 ErrorHandler( kNoMemoryMsg, (char *) NilString, 
 (char *) NilString, (char *) NilString );
 MoveHHi( (Handle) GetGWorldPixMap( gFrames[ i ] ) );
 NoPurgePixels( GetGWorldPixMap( gFrames[ i ] ) );
 SetGWorld( currentPort, currentDev ); 
 // • Restore port & device 

// • ---------- Plot Targets in Frames -------
// • Draws rectangle target inside frame 
// • for demonstrating animation.
static void PlaceTargets( const short frm )
 RGBColor rgbTarget, rgbBkgnd;

 if (sIncrSize) {
 sRectSize += gSettings.sizeDiff;  
 if (sRectSize >= 
 gSettings.frameSize - 10) {
 sRectSize -= gSettings.sizeDiff * 2;
 sIncrSize = false;
 else {
 sRectSize -= gSettings.sizeDiff;  
 if (sRectSize <= 0) {
 sRectSize += gSettings.sizeDiff * 2;
 sIncrSize = true;
 SetRect( &sTarget, sCenter.h - 
 (sRectSize / 2), sCenter.v - 
 (sRectSize / 2), sCenter.h + 
 (sRectSize / 2), sCenter.v + 
 (sRectSize / 2) );
 PenSize( 2, 2 ); // • Set outline width of target rectangle.

 PmForeColor( kPalBkgnd );// • Clear bkgnd 
 PaintRect( &gBounds );

 PmForeColor( kPalTarget ); // • Draw outline
 FrameRect( &sTarget );   


 // • Get background and target colors
 GetEntryColor( gPalette, kPalBkgnd, &rgbBkgnd );
 GetEntryColor( gPalette, kPalTarget, &rgbTarget );

 // • Repeat drawing into Pict for saving to file. Use 
 // • Color Mgr routines instead of Palette Mgr routines.
 gFramePICTs[ frm ] = OpenPicture( &gBounds );

 PenSize( 2, 2 );
 RGBForeColor( &rgbBkgnd );
 PaintRect( &gBounds );
 RGBForeColor( &rgbTarget );
 PaintRect( &sTarget );



// • ---------- Record Frames in Film Sequence 
// • Records the frames which are later shown with PlayFilm.
void DoFilm( void )
 GDHandle currentDev = NullHandle;
 CGrafPtr currentPort = NullPointer;
 short  i;
 sRectSize = 0;  
 sIncrSize = true;

 PrepareFilm();  // • Allocate film storage. 
 GetGWorld( &currentPort, &currentDev );     
 // • Save current port & device   
 for (i = 0; i < gSettings.numFrames; i++) {
 // • Draw frame area directly into GWorld 
 LockPixels( GetGWorldPixMap( gFrames[ i ] ) );                
 SetGWorld( gFrames[ i ], NullPointer );
 PlaceTargets( i );
 SetGWorld( currentPort, currentDev ); 
 // • Reset port & device  
 UnlockPixels( GetGWorldPixMap( gFrames[ i ] ) );              
 SysBeep( 1 );

// • ------------ Show Recorded Sequence -----
// • Play a pre-recorded animation sequence.
void PlayFilm( void )
 long   finalTicks;
 short  i;

 HideCursor();   // • Get cursor out of the way 

 for (i = 0; i < gSettings.numFrames; i++)
 LockPixels( GetGWorldPixMap( gFrames[ i ] ) ); 

 // • Insert VBlank task into interrupt queue. 
 // • Allow VBlank task to execute.
 gXVBLTask.vbl.vblCount = 1;

 // • On each VBlank copy frame to the screen.
 while (gXVBLTask.frameIndex < gSettings.numFrames) {
  if (gXVBLTask.showFrame == true) {
 gXVBLTask.showFrame = false;
 CopyBits( &gFrames[ gXVBLTask.frameIndex ]->portPixMap,
 &gMainWindow->portPixMap, &gBounds, &sDrawArea, srcCopy, NullHandle 
 // • Remove VBlank task from interrupt queue. 
 for (i = 0; i < gSettings.numFrames; i++)
 UnlockPixels( GetGWorldPixMap( gFrames[ i ] ) ); 

 // • Clear the screen when finished. 
 Delay( 5, &finalTicks );
 PmForeColor( kPalBkgnd ); 
 PaintRect( &(gMainWindow->portRect) ); // • Clear win bkgd
 ShowCursor();  // • Put the cursor back out 

// • ---------- Update Frame GWorld Palettes 
// • Sets gray levels of GWorlds.
void UpdateGWorldColors( void )
 GDHandle currentDev = NullHandle;
 CGrafPtr currentPort = NullPointer;
 short  i;

 GetGWorld( &currentPort, &currentDev );           
 // • Save current port & device  
 for (i = 0; i < gSettings.numFrames; i++) { 
 if (gFrames[ i ] != NullPointer) {
 SetGWorld( gFrames[ i ], NullPointer );
 NSetPalette( (WindowPtr) gFrames[ i ], 
 gPalette, true );
 ActivatePalette( (WindowPtr) gFrames[ i ] );
 SetGWorld( currentPort, currentDev ); 
 // • Restore port & device 

// • ---------- Dispose of the Frame GWorlds 
// • Dispose of the animation frame GWorlds.
void DisposeFrames( void )
 short  i;
 for (i = 0; i < kMaxFrames; i++) {
 if (gFrames[ i ] != NullPointer) {
 DisposeGWorld( gFrames[ i ] );
 gFrames[ i ] = NullPointer;

// • ---------- Reset Foregnd & Bkgnd Colors 
void ResetForeAndBackColors( void )
 PmForeColor( kPalBlack );
 PmBackColor( kPalWhite );

// • ---------- Round Up a Number ------------
// • Rounds up a floating-point number
// • into next highest whole integer.
short RoundUp( const double inputNumber )
 short  wholeNumber, roundNumber;
 wholeNumber = (short) inputNumber;
 if (inputNumber - wholeNumber == 0.0) 
 return( wholeNumber );
 return( wholeNumber + 1 );
excerpts from Listing:  VBlank.c
// • ---------- Increment Frame Number -------
// • ---------- (VBlank Task Routine) -------- 
#pragma options(assign_registers,redundant_loads)

// • Increment frame number of pre-recorded animation sequence 
// • in sync with vertical blank interrupt.
void VideoProc( void )
 long   currA5;
 XVBLTaskPtr   theVBLTask;
 // • Get our task record from A0 register
 theVBLTask = (XVBLTaskPtr) GetVBLRec();
 currA5 = SetA5( theVBLTask->a5 );
 if (theVBLTask->frameIndex < theVBLTask->frames) {
 theVBLTask->frameIndex++;  // • Set next frame #
 theVBLTask->showFrame = true;// • Show this frame 

 // • Reset VBL countdown 
 theVBLTask->vbl.vblCount = theVBLTask->frameDelay;      
 else {
 theVBLTask->showFrame = false;  // • Don't display frame      
 theVBLTask->vbl.vblCount = 0;   // • Inhibit VBL task 

 currA5 = SetA5( currA5 );
Listing:  Files.c
// •••••••••••••••••••••••••••••••••••••••••••
// • Program:  FrameAnim  File:  Files.c
// • © 1993 by Dr. Scott Steinman. All Rights Reserved.
// •••••••••••••••••••••••••••••••••••••••••••
// • NOTES:  Animation files are saved in a PICS 
// •   file format that includes:
// •    (a) One 'INFO' resource for playback information.
// •    (b) One 'PICT' (PICT2) resource for each stored image.
// •••••••••••••••••••••••••••••••••••••••••••

#include "FrameAnim.h"

#include <Files.h>
#include <StandardFile.h>
#include <PictUtil.h>

// • ---------- External Globals -------------
// • From Main.c file
extern GWorldPtr gFrames[]; 
extern PaletteHandle gPalette;
extern CTabHandlegCTable;
extern Handle    gMenuBar;
extern Settings  gSettings; 
extern FlagsgFlags;

// • From Animation.c file
extern Rect gBounds; 

// • ---------- Internal Globals -------------
PicHandle gFramePICTs[ kMaxFrames ]; // • Frame Pictures

// •----------- Static Variables -------------
static PICSInfoHandlesPicInfo; // • Animation parameters 

// • ---------- Static Functions -------------
static void DrawPICTToGWorld( const short );
static void NewColorsFromPICT( void );
static void ReadFilm( const StandardFileReply * );
static void WriteFilm( const StandardFileReply * );

// • ---------- Save Frame Images in File ----
// • Save an animation sequence to animation file on disk.
void SaveFilm( void )
 StandardFileReply theReply;
 Str255 thePrompt, theDefaultName;

 strcpy( (char *) thePrompt, 
 (char *) "\pSave Animation File as..." );   
 strcpy( (char *) theDefaultName, (char *) "\p" );             
 StandardPutFile( &thePrompt, &theDefaultName, &theReply );    
 if (theReply.sfGood) {
 FSpCreateResFile( &theReply.sfFile, 'FRMA', 
 'PICS', theReply.sfScript );
 WriteFilm( &theReply );

// • --------- Write the Frames to File ------
// • Writes entire animation sequence 
// • to animation file on disk.
static void WriteFilm( const StandardFileReply *theReply )
 OSErr  resultCode;
 short  resFile, i;
 Str255 frameText;

 // • Transfer animation information to PicsInfo record
 sPicInfo = (PICSInfoHandle) 
 NewHandle( sizeof( PICSInfoRec ) );
 if (sPicInfo == NullHandle)
 ErrorHandler( kNoMemoryMsg, (char *) NilString, 
 (char *) NilString, (char *) NilString );

 HLock( (Handle) sPicInfo );
 (**sPicInfo).speed = RoundUp( 
 kRefreshRate / gSettings.frameDelay );
 (**sPicInfo).depth = kDepth;
 (**sPicInfo).version = 0;
 (**sPicInfo).creator = 'FRMA';
 (**sPicInfo).largest = 0;// • Not used.
 (**sPicInfo).bwColor = 1; 
 HUnlock( (Handle) sPicInfo );

 // • Write the animation info and frames 
 // • to resource fork of file 
 resFile = FSpOpenResFile( &theReply->sfFile, fsWrPerm );
 AddResource( sPicInfo, 'INFO', kPICSInfoID, 
 "\pAnimation Parameters" );
 for (i = 0; i < gSettings.numFrames; i++) {
 PtoCstr( (char *) frameText );
 sprintf( (char *) frameText, 
 "Animation Frame %d of %ld", i + 1,
 gSettings.numFrames );
 CtoPstr( (char *) frameText );
 AddResource( gFramePICTs[ i ], 'PICT', 
 kFirstPictID + i, frameText );
 UpdateResFile( resFile );
 // • Force write of resource to file.
 CloseResFile( resFile );
 // • Null handles to resources that 
 // • have been freed by CloseResFile 
 sPicInfo = NullHandle;
 for (i = 0; i < gSettings.numFrames; i++)
 gFramePICTs[ i ] = NullHandle;
 DisableItem( GetMHandle( kFileID ), kSaveFilmItem );    }

// • ---------- Open Animation Sequence File 
// • Retrieve entire animation sequence 
// • from animation file on disk.
void OpenFilm( void )
 SFTypeList typeList;
 StandardFileReply theReply;
 short  numTypes;
 numTypes = 1;   // • Only one type of file 
 typeList[ 0 ] = 'PICS';  // • Only Anim files 
 StandardGetFile( NullPointer, numTypes, 
 &typeList, &theReply );

 if (theReply.sfGood)
 ReadFilm( &theReply );
 gFlags.cancel = true;

// • ---------- Read the Frames from File ----
// • This does the opposite of WriteFilm.
static void ReadFilm( const StandardFileReply *theReply )
 PictInfo pictInfo;
 OSErr  err;
 short  resFile, i;

 // • Free previously-allocated color records 
 if (gCTable != NullHandle) {
 DisposCTable( gCTable );
 gCTable = NullHandle;
 if (gPalette != NullHandle) {
 DisposePalette( gPalette );
 gPalette = NullHandle;

 // • Read the animation information resource. 
 resFile = FSpOpenResFile( &theReply->sfFile, fsRdPerm );
 sPicInfo = (PICSInfoHandle) Get1Resource( 
 'INFO', kPICSInfoID );
 // • Pull out frame delay from PICS resource 
 // • (other fields ignored here) 
 HLock( (Handle) sPicInfo );
 gSettings.frameDelay = RoundUp( 
 kRefreshRate / (**sPicInfo).speed );
 HUnlock( (Handle) sPicInfo );

 // • Prepare new frame GWorlds.

 // • Find the # frame image PICT resources. 
 gSettings.numFrames = Count1Resources( 'PICT' );
 // • Copy contents of first PICT rsrc to first GWorld.
 gFramePICTs[ 0 ] = GetPicture( kFirstPictID );
 DetachResource( gFramePICTs[ 0 ] ); 
 // • Get frame size info from first PICT.
 err = GetPictInfo( gFramePICTs[ 0 ], &pictInfo, 
 kNumColors, systemMethod, 0 );
 gSettings.frameSize = pictInfo.sourceRect.right - 

 // • Get color info from PICT to reset 
 // • color table and palette.
 HLock( (Handle) pictInfo.theColorTable );
 gSettings.bkgndGray = (**pictInfo.theColorTable).ctTable[ 0 
 ] / 256;
 gSettings.targetGray = (**pictInfo.theColorTable).ctTable[ 1 
 ] / 256;
 HUnlock( (Handle) pictInfo.theColorTable );
 DisposCTable( pictInfo.theColorTable );
 NewColorsFromPICT(); // • Create & init color table/palette.
 DrawPICTToGWorld( 0 );  // • Transfer frame image to GWorld.

 for (i = 1; i < gSettings.numFrames; i++) { 
 gFramePICTs[ i ]= GetPicture( kFirstPictID + i );
 DetachResource( gFramePICTs[ i ] ); 
 DrawPICTToGWorld( i );   
 CloseResFile( resFile );
 sPicInfo = NullHandle;
 for (i = 1; i < gSettings.numFrames; i++)
 gFramePICTs[ i ] = NullHandle;

 SysBeep( 1 );

// • ---------- Create & Init CTable From PICT 
// • Create and initialize color table and palette using 
// • background and target colors found in animation file.
static void NewColorsFromPICT( void )
 RGBColor c;
 short  i;
 // • Create new CTable and palette.
 gCTable = GetCTable( kGrayCTableID );
 DetachResource( gCTable ); 
 (*gCTable)->ctFlags |= 0x4000; // • Speeds up later CopyBits 
 gPalette = NewPalette( kNumColors, gCTable, 
 pmTolerant+pmExplicit, 0 );

 // • Initialize palette entries. = = = 0;
 for (i = 0; i < kNumColors; i++) {
 SetEntryColor( gPalette, i, &c );
 SetEntryUsage( gPalette, i, pmTolerant+pmExplicit, 0 );
 } = = = 65535;
 SetEntryColor( gPalette, kPalWhite, &c );
 // • Force palette color entries into CTable.
 Palette2CTab( gPalette, gCTable );
// • ---------- Draw PICT into GWorld --------
// • Draw the contents of an animation file into the GWorlds.
static void DrawPICTToGWorld( const short frm )
 GDHandle currentDev = NullHandle;
 CGrafPtr currentPort = NullPointer;
 GetGWorld( &currentPort, &currentDev );     
 // • Save current port & device  
 if (gFramePICTs[ frm ] != NullHandle) {
 HLock( (Handle) gFramePICTs[ frm ]);
 LockPixels( GetGWorldPixMap( gFrames[ frm ] ) );              
 SetGWorld( gFrames[ frm ], NullPointer );
 DrawPicture( gFramePICTs[ frm ], &gBounds );
 SetGWorld( currentPort, currentDev ); 
 // • Reset port & device  
 UnlockPixels( GetGWorldPixMap( gFrames[ frm ] ) );            
 HUnlock( (Handle) gFramePICTs[ frm ]);

// • --------- Dispose of the Frame PICT -----
// • Dispose of the animation Picture record.
void DisposeFramePICTs( void )
 short  i;

 for (i = 0; i < kMaxFrames; i++) {
 if (gFramePICTs[ i ]!= NullHandle) { 
 KillPicture( gFramePICTs[ i ]);
 gFramePICTs[ i ]= NullHandle;


Community Search:
MacTech Search:

Software Updates via MacUpdate

GraphicConverter 11.2.2 - $39.95
GraphicConverter is an all-purpose image-editing program that can import 200 different graphic-based formats, edit the image, and export it to any of 80 available file formats. The high-end editing... Read more
VueScan 9.7.30 - Scanner software with a...
VueScan is a scanning program that works with most high-quality flatbed and film scanners to produce scans that have excellent color fidelity and color balance. VueScan is easy to use, and has... Read more
Things 3.12.6 - Elegant personal task ma...
Things is a task management solution that helps to organize your tasks in an elegant and intuitive way. Things combines powerful features with simplicity through the use of tags and its intelligent... Read more
Skim 1.5.11 - PDF reader and note-taker...
Skim is a PDF reader and note-taker for OS X. It is designed to help you read and annotate scientific papers in PDF, but is also great for viewing any PDF file. Skim includes many features and has a... Read more
Navicat Premium Essentials 15.0.20 - Pro...
Navicat Premium Essentials is a compact version of Navicat which provides basic and necessary features you will need to perform simple administration on a database. It supports the latest features... Read more
Affinity Photo 1.8.4 - Digital editing f...
Affinity Photo - redefines the boundaries for professional photo editing software for the Mac. With a meticulous focus on workflow it offers sophisticated tools for enhancing, editing and retouching... Read more
EtreCheck Pro 6.3 - For troubleshooting...
EtreCheck is an app that displays the important details of your system configuration and allow you to copy that information to the Clipboard. It is meant to be used with Apple Support Communities to... Read more
beaTunes 5.2.11 - Organize your music co...
beaTunes is a full-featured music player and organizational tool for music collections. How well organized is your music library? Are your artists always spelled the same way? Any R.E.M. vs REM?... Read more
Bookends 13.4.4 - Reference management a...
Bookends is a full-featured bibliography/reference and information-management system for students and professionals. Bookends uses the cloud to sync reference libraries on all the Macs you use.... Read more
Affinity Designer 1.8.4 - Vector graphic...
Affinity Designer is an incredibly accurate vector illustrator that feels fast and at home in the hands of creative professionals. It intuitively combines rock solid and crisp vector art with... Read more

Latest Forum Discussions

See All

Global Spy is an intriguing 2D spy sim f...
Developer Yuyosoft Innovations' Global Spy launched last month for iOS and Android, though if you missed it at the time, we're here to tell you why it's well worth a go. This one's all about international espionage, tracking down elusive spies,... | Read more »
Distract Yourself With These Great Mobil...
There’s a lot going on right now, and I don’t really feel like trying to write some kind of pithy intro for it. All I’ll say is lots of people have been coming together and helping each other in small ways, and I’m choosing to focus on that as I... | Read more »
Hyena Squad is sci-fi turn-based strateg...
Wave Light Games has just revealed its latest release, Hyena Squad, a turn-based RPG set in a space station infested by gross aliens and the living dead. The announcement was first reported on by Touch Arcade. [Read more] | Read more »
Idle Guardians: Never Die is a pixel art...
SuperPlanet has been fairly prolific with game releases so far this year with both Evil Hunter Tycoon and Lucid Adventure releasing earlier this year. Now, they've released another idle RPG called Idle Guardians: Never Die, which you can download... | Read more »
Ruinverse, Kemco's latest RPG, now...
Kemco's latest RPG endeavour, Ruinverse, initially launched for both iOS and Android earlier this month. It was released as a premium title that also had additional in-app purchases. Now, the developers have decided to release a freemium version... | Read more »
The 5 Best Mobile Platformers
Touch screens and action-oriented gameplay don't typically mix, but over the course of pondering the best platformers on mobile, I found myself having a really hard time picking just five. Quite a few developers have found really creative ways to... | Read more »
Clash Royale: The Road to Legendary Aren...
Supercell recently celebrated its 10th anniversary and their best title, Clash Royale, is as good as it's ever been. Even for lapsed players, returning to the game is as easy as can be. If you want to join us in picking the game back up, we've put... | Read more »
Endless runner Monster Dash will relaunc...
Remember Monster Dash? Well, it's set to return to a mobile device near you after having been pulled from stores in 2017 to meet GDPR compliance. This one first launched all the way back in August of 2010. Over ten years later, it's heading into... | Read more »
Auto Battle Chess is a colourful genre s...
Auto Battle Chess is an interesting hodgepodge of genres that aims to offer the ultimate auto chess experience. That's a pretty tall order, though with the game now out for Android, I suppose we don't have to wait to see if it lives up to its... | Read more »
Tom and Jerry: Chase hits 1 million pre-...
NetEase's 1v4 asymmetrical multiplayer game Tom and Jerry: Chase recently became available to pre-order for both iOS and Android in Southeast Asia. It's clearly proving to be a highly anticipated title too since it has now reached 1 million pre-... | Read more »

Price Scanner via

Expercom offers $320 discount on the 6-core 1...
Apple reseller Expercom has the Silver 16″ 6-core MacBook Pro on sale for a limited time for $2079 shipped. Their price is $320 off Apple’s MSRP for this model, and it’s the cheapest price currently... Read more
Apple announces Education pricing for new 202...
Purchase a new 2020 iMac or iMac Pro at Apple using Apple’s Education discount, and take up to $400 off MSRP. All teachers, students, and staff of any educational institution with a .edu email... Read more
Apple reseller Expercom offers $256 discount...
Expercom has Apple’s new 2020 10-core iMac Pro available for order and on sale for $4743 shipped. Their price is $256 off Apple’s MSRP for this new model, and it’s the cheapest price we’ve seen so... Read more
Apple releases refreshed 2020 27″ iMacs with...
Apple today released updated versions of their 27″ iMacs featuring 10th generation Intel processors, SSDs across the board, a better 5K display, and improvements to the camera, speakers, and mic.... Read more
Xfinity Mobile promo: Take $200-$350 off Appl...
New customers can take $200 off the purchase of any new Apple iPhone model at Xfinity Mobile through 8/17/20. Service plan required. Existing customers can purchase an iPhone and receive $200 back in... Read more
B&H now offering $100-$200 discount on Ap...
B&H Photo has new 2020 13″ 2.0GHz MacBook Pros on sale for $100-$200 off Apple’s MSRP, starting at $1649. These are the same MacBook Pros sold by Apple in their retail and online stores, and B... Read more
Apple’s 6-Core Mac mini is on sale at Amazon...
Amazon has Apple’s new 2020 6-Core Mac mini on sale for $926.25 for a limited time. Their price is $173 off Apple’s $1099 MSRP for this model and includes a $48.75 instant discount available on their... Read more
New 2020 11″ iPad Pros on sale for $50-$75 of...
Apple reseller Expercom has new 2020 11″ Apple iPad Pros on sale for $50-$75 off MSRP, with prices starting at $749. These are the same iPad Pros sold by Apple in their retail and online stores: – 11... Read more
Switch to US Cellular and get a new Apple iPh...
US Cellular has Apple’s 2020 iPhone SE on sale for $350 off for new lines of service and a US Cellular unlimited plan. Promotion comes via monthly bill credits over a 30 month period. Their deal... Read more
Apple AirPods with Wireless Charging Case on...
Amazon has Apple’s AirPods with Wireless Charging Case on sale today for only $139.98 shipped. That’s $60 off Apple’s MSRP and the lowest price we’ve ever seen for these AirPods. Sale valid for a... Read more

Jobs Board

Director, Product Management - Lead *Apple*...
…better business results. **Job Title** Director, Product Management - Lead Apple / Token Requestor Services Overview The Mastercard Digital Enablement Services Read more
*Apple* Computing Professional - Store 286 (...
**770445BR** **Job Title:** Apple Computing Professional - Store 286 (Canton) **Job Category:** Store Associates **Store Number or Department:** 000286-Canton-Store Read more
Department Manager- Tech Shop/ *Apple* Stor...
…their parents want, and our faculty needs. As a Department Manager in our Tech Shop/ Apple Store you will spend the majority of your time on the sales floor engaging Read more
*Apple* Graders/Inspectors (Seasonal/Hourly/...
Title: Apple Graders/Inspectors (Seasonal/Hourly/No Benefits) # APPLE Location: US-VA-Winchester Read more
Tier 2 Technical Support Analyst - ( *Apple*...
…Analystiless than/strong>who will analyze and determine user software needs on all Apple devices (first support contact), Windows devices, and support printers in Read more
All contents are Copyright 1984-2011 by Xplain Corporation. All rights reserved. Theme designed by Icreon.