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

Introduction

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).

Performance

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.

Footnotes

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.

Bibliography

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 );
 else 
 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) 
 return;
 
 // • 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 - 
 gMainWindow->portRect.left;
 v = gMainWindow->portRect.bottom - 
 gMainWindow->portRect.top;
 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, sDrawArea.top );
 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. 
 DisposeFrames();
 
 // • Set bounds for drawing into GWorlds and window.
 SetDrawCoords();

 // • 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 
 UpdateGWorldColors();
}

// • ---------- 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 );   

 ResetForeAndBackColors();

 // • 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 );

 ClosePicture();

 ResetForeAndBackColors();
}

// • ---------- 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 ] ) );              
 
 ResetForeAndBackColors();
 }
 SysBeep( 1 );
}

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

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

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

 // • Insert VBlank task into interrupt queue. 
 SetUpVBlankAnimation();
 
 // • 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. 
 ShutDownVBlankAnimation();
 
 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
 ResetForeAndBackColors();
 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 );
 else   
 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 );
 else
 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.
 PrepareFilm();  

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

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

 for (i = 1; i < gSettings.numFrames; i++) { 
 gFramePICTs[ i ]= GetPicture( kFirstPictID + i );
 DetachResource( gFramePICTs[ i ] ); 
 DrawPICTToGWorld( i );   
 } 
 ResetForeAndBackColors();
 
 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.

 c.red = c.green = c.blue = 0;
 for (i = 0; i < kNumColors; i++) {
 SetEntryColor( gPalette, i, &c );
 SetEntryUsage( gPalette, i, pmTolerant+pmExplicit, 0 );
 }
 c.red = c.green = c.blue = 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

Latest Forum Discussions

See All

Challenge those pesky wyverns to a dance...
After recently having you do battle against your foes by wildly flailing Hello Kitty and friends at them, GungHo Online has whipped out another surprising collaboration for Puzzle & Dragons. It is now time to beat your opponents by cha-cha... | Read more »
Pack a magnifying glass and practice you...
Somehow it has already been a year since Torchlight: Infinite launched, and XD Games is celebrating by blending in what sounds like a truly fantastic new update. Fans of Cthulhu rejoice, as Whispering Mist brings some horror elements, and tests... | Read more »
Summon your guild and prepare for war in...
Netmarble is making some pretty big moves with their latest update for Seven Knights Idle Adventure, with a bunch of interesting additions. Two new heroes enter the battle, there are events and bosses abound, and perhaps most interesting, a huge... | Read more »
Make the passage of time your plaything...
While some of us are still waiting for a chance to get our hands on Ash Prime - yes, don’t remind me I could currently buy him this month I’m barely hanging on - Digital Extremes has announced its next anticipated Prime Form for Warframe. Starting... | Read more »
If you can find it and fit through the d...
The holy trinity of amazing company names have come together, to release their equally amazing and adorable mobile game, Hamster Inn. Published by HyperBeard Games, and co-developed by Mum Not Proud and Little Sasquatch Studios, it's time to... | Read more »
Amikin Survival opens for pre-orders on...
Join me on the wonderful trip down the inspiration rabbit hole; much as Palworld seemingly “borrowed” many aspects from the hit Pokemon franchise, it is time for the heavily armed animal survival to also spawn some illegitimate children as Helio... | Read more »
PUBG Mobile teams up with global phenome...
Since launching in 2019, SpyxFamily has exploded to damn near catastrophic popularity, so it was only a matter of time before a mobile game snapped up a collaboration. Enter PUBG Mobile. Until May 12th, players will be able to collect a host of... | Read more »
Embark into the frozen tundra of certain...
Chucklefish, developers of hit action-adventure sandbox game Starbound and owner of one of the cutest logos in gaming, has released their roguelike deck-builder Wildfrost. Created alongside developers Gaziter and Deadpan Games, Wildfrost will... | Read more »
MoreFun Studios has announced Season 4,...
Tension has escalated in the ever-volatile world of Arena Breakout, as your old pal Randall Fisher and bosses Fred and Perrero continue to lob insults and explosives at each other, bringing us to a new phase of warfare. Season 4, Into The Fog of... | Read more »
Top Mobile Game Discounts
Every day, we pick out a curated list of the best mobile discounts on the App Store and post them here. This list won't be comprehensive, but it every game on it is recommended. Feel free to check out the coverage we did on them in the links below... | Read more »

Price Scanner via MacPrices.net

Free iPhone 15 plus Unlimited service for $60...
Boost Infinite, part of MVNO Boost Mobile using AT&T and T-Mobile’s networks, is offering a free 128GB iPhone 15 for $60 per month including their Unlimited service plan (30GB of premium data).... Read more
$300 off any new iPhone with service at Red P...
Red Pocket Mobile has new Apple iPhones on sale for $300 off MSRP when you switch and open up a new line of service. Red Pocket Mobile is a nationwide MVNO using all the major wireless carrier... Read more
Clearance 13-inch M1 MacBook Airs available a...
Apple has clearance 13″ M1 MacBook Airs, Certified Refurbished, available for $759 for 8-Core CPU/7-Core GPU/256GB models and $929 for 8-Core CPU/8-Core GPU/512GB models. Apple’s one-year warranty is... Read more
Updated Apple MacBook Price Trackers
Our Apple award-winning MacBook Price Trackers are continually updated with the latest information on prices, bundles, and availability for 16″ and 14″ MacBook Pros along with 13″ and 15″ MacBook... Read more
Every model of Apple’s 13-inch M3 MacBook Air...
Best Buy has Apple 13″ MacBook Airs with M3 CPUs in stock and on sale today for $100 off MSRP. Prices start at $999. Their prices are the lowest currently available for new 13″ M3 MacBook Airs among... Read more
Sunday Sale: Apple iPad Magic Keyboards for 1...
Walmart has Apple Magic Keyboards for 12.9″ iPad Pros, in Black, on sale for $150 off MSRP on their online store. Sale price for online orders only, in-store price may vary. Order online and choose... Read more
Apple Watch Ultra 2 now available at Apple fo...
Apple has, for the first time, begun offering Certified Refurbished Apple Watch Ultra 2 models in their online store for $679, or $120 off MSRP. Each Watch includes Apple’s standard one-year warranty... Read more
AT&T has the iPhone 14 on sale for only $...
AT&T has the 128GB Apple iPhone 14 available for only $5.99 per month for new and existing customers when you activate unlimited service and use AT&T’s 36 month installment plan. The fine... Read more
Amazon is offering a $100 discount on every M...
Amazon is offering a $100 instant discount on each configuration of Apple’s new 13″ M3 MacBook Air, in Midnight, this weekend. These are the lowest prices currently available for new 13″ M3 MacBook... Read more
You can save $300-$480 on a 14-inch M3 Pro/Ma...
Apple has 14″ M3 Pro and M3 Max MacBook Pros in stock today and available, Certified Refurbished, starting at $1699 and ranging up to $480 off MSRP. Each model features a new outer case, shipping is... Read more

Jobs Board

Omnichannel Associate - *Apple* Blossom Mal...
Omnichannel Associate - Apple Blossom Mall Location:Winchester, VA, United States (https://jobs.jcp.com/jobs/location/191170/winchester-va-united-states) - Apple Read more
Operations Associate - *Apple* Blossom Mall...
Operations Associate - Apple Blossom Mall Location:Winchester, VA, United States (https://jobs.jcp.com/jobs/location/191170/winchester-va-united-states) - Apple Read more
Cashier - *Apple* Blossom Mall - JCPenney (...
Cashier - Apple Blossom Mall Location:Winchester, VA, United States (https://jobs.jcp.com/jobs/location/191170/winchester-va-united-states) - Apple Blossom Mall Read more
IT Systems Engineer ( *Apple* Platforms) - S...
IT Systems Engineer ( Apple Platforms) at SpaceX Hawthorne, CA SpaceX was founded under the belief that a future where humanity is out exploring the stars is Read more
*Apple* Systems Administrator - JAMF - Activ...
…**Public Trust/Other Required:** None **Job Family:** Systems Administration **Skills:** Apple Platforms,Computer Servers,Jamf Pro **Experience:** 3 + years of Read more
All contents are Copyright 1984-2011 by Xplain Corporation. All rights reserved. Theme designed by Icreon.