Mar 01 QTToolkit
Volume Number: 17
Issue Number: 3
Column Tag: QuickTime Toolkit
A Goofy Movie
By Tim Monroe
Working with Sprites in QuickTime Movies
Introduction
The major new technology introduced in QuickTime version 2.1 (released in 1995)
was support for sprites and sprite animation. A sprite is a graphical object that has a
number of properties, including its current image, location, size, layer, graphics
mode, and visibility state. These properties determine the appearance of the sprite at
any instant in time. By varying one or more of these properties over time, we can
animate the sprite. For instance, Figure 1 shows the first frame of a QuickTime movie
that contains one sprite, whose image is the original icon for the QuickTime system
extension.
Figure 1. A movie with a sprite
We can move the icon to the right by gradually changing the horizontal location of the
sprite. In addition, we can change the image associated with the sprite at any time. In
this case, we'll change the icon from the old QuickTime extension icon to the new
QuickTime extension icon when the sprite gets to the halfway point, as shown in Figure
2. The new icon then continues moving at the same rate until it reaches the right side
of the movie box.
Figure 2. The sprite with a new image
Figure 3 shows another sprite movie. In this case, there is only one image associated
with the sprite for the entire duration of the movie, and the sprite location remains
constant. We'll animate this sprite by changing the graphics mode, from totally
transparent to totally opaque. What we've done here is recreate, using sprite
animation, the appearing-penguin movie that was the very first QuickTime movie we
built in this series of articles (see "Making Movies" in MacTech, June 2000).
Figure 3. The penguin movie using sprite animation
Recall how we went about creating the first version of our penguin movie: we opened a
picture resource and drew that picture into an offscreen graphics world; we
compressed the data in that graphics world and added it as a frame to the movie file.
Then we repeated the drawing and compressing 99 times, each time with gradually
more opacity, to create 99 additional movie frames. The resulting movie file contained
100 compressed images, for a total size of about 472 kilobytes.
Using sprite animation, we can reduce that size dramatically. Our new penguin movie
file contains only a single compressed image and 100 sets of "instructions" that
indicate the desired level of opacity in each movie frame. The 100 movie frames are
generated at playback time by the sprite media handler from that image and those 100
sets of instructions. The size of the sprite version of the penguin movie is only about
36 kilobytes. (No, that's no typo; the exact same visual output can be achieved with a
file less than one tenth the size of our original movie file.)
Already you can see that sprite animation differs significantly from what's often called
cel animation, where each frame of the animation is a fully-rendered picture of some
characters superimposed on a background image. If cel animation is like a recorded
symphony, then sprite animation is more like a set of sounds together with
instructions for playing back those sounds in the right order and at the right time. If
the instructions in a sprite movie aren't too complicated, they can be executed at
runtime just as smoothly as decompressing and playing back the fully-rendered
version of the movie. And, as we've seen, the images and instructions can take up a lot
less space.
QuickTime 3.0 extended the capabilities of the sprite media handler by adding support
for wired sprites, or sprites that react to mouse events (and other kinds of events)
and that have various actions attached (or "wired") to them. For instance, we can use a
wired sprite to control the properties of the sprites in the movie (so that clicking on
one sprite causes another sprite to disappear or to change location). Or, we can control
various aspects of movie playback, such as the volume and balance of a sound track, or
the graphics mode of a video track. By using wired sprites in a movie, we can add a
level of interactivity previously unavailable in QuickTime.
In this article, we're going to learn how to create movies that contain sprite tracks.
We'll see how to create both the icon movie shown in Figure 1 and the penguin movie
shown in Figure 3. Our sample application this month is called QTSprites, and its Test
menu is shown in Figure 4.
Figure 4. The Test menu of QTSprites
(The "space movie" is a more complex movie that does sprite animation using location
changes, layer changes, and image changes. We won't learn how to build it in this
article, but the code for doing so is contained in the file QTSprites.c.)
We'll begin by looking in more detail at sprites and their properties. Then we'll take a
look at the structure of sprite tracks and see how to build the icon and penguin sprite
movies. Toward the end, we'll see how to add some simple interactivity to our sprite
movies without using wired sprites. We'll postpone our investigation of wired sprites
to an upcoming QuickTime Toolkit article.
Sprite Properties
In a QuickTime movie, a sprite is a graphical object that belongs to a sprite track (of
type SpriteMediaType). In the simplest case, the basic appearance of a sprite is set by
selecting one out of an array of images associated with the sprite. The current sprite
image index is one of the five main sprite properties, defined using these constants:
enum {
kSpritePropertyMatrix
= 1,
kSpritePropertyVisible
= 4,
kSpritePropertyLayer
= 5,
kSpritePropertyGraphicsMode = 6,
kSpritePropertyImageIndex
= 100
};
The sprite matrix is a 3-by-3 matrix that controls the location, size, and rotation of
the sprite image within the sprite track. (A sprite's matrix is added to the track
matrix of the sprite track.) The sprite visibility state is an integer value that controls
whether the sprite is currently visible. (This value is interpreted as a Boolean value
but is stored in the movie file as a 16-bit short integer.) The sprite layer is an
integer value that determines, when two or more sprites have locations that overlap,
which sprite is drawn on top of the other sprite(s). Sprites with lower layer values
are drawn on top of sprites with higher layer values; if we want to ensure that some
sprite is drawn behind all other overlapping sprites, we can set its layer to the special
value kBackgroundSpriteLayerNum (appropriately defined in the file Movies.h as
32767). Finally, the sprite graphics mode determines how the sprite is drawn into
the sprite track. We specify a sprite graphics mode using a structure of type
ModifierTrackGraphicsModeRecord, defined like this:
struct ModifierTrackGraphicsModeRecord {
long graphicsMode;
RGBColor opColor;
};
This structure contains the QuickDraw graphics mode and the color used by some of
those graphics modes. For instance, if graphicsMode is blend, then opColor specifies
the weight color (which determines the amount of blending of the source and
destination pixels).
A sprite's current image does not have to come from a sprite image array. Instead, the
sprite media handler can use a video track in the same QuickTime movie as the source
for the sprite's image. This allows us to create even more intricate animations than
are possible by simply varying the five basic sprite properties. As a very simple
example, we could create a sprite track containing two sprites, one whose image looks
like a television set and a superimposed sprite whose images are derived from a video
track. The net effect would be a sprite track containing a television playing the video.
Sprite Tracks
A sprite track consists of one or more sprite media samples. There are two basic kinds
of sprite media samples: (1) those that define the sprite image array and set the
initial properties of a frame, and (2) those that animate the sprites in the track by
specifying changes to the sprites' properties. The sprite media handler relies on the
distinction between key frames and difference frames, which we encountered in the
previous article ("Honey, I Shrunk the Kids" in MacTech, February 2001). The image
arrays and initial properties are stored in key frames, and the sprite property
changes are stored in difference frames. The only departure here is purely
terminological: when we're working with sprite data, the difference frames are called
override frames (because the data in those frames overrides the data in the key
frames).
The Format of Key Frame Samples
The data in both key frames and override frames is stored in atom containers. (Indeed,
atom containers were introduced in QuickTime version 2.1 primarily for the purpose
of organizing sprite media data.) A key frame atom container contains a child atom of
type kSpriteAtomType for each sprite in the key frame. This atom contains leaf atoms
that define the initial properties of the sprite. The atom IDs of the sprite atoms are
numbered sequentially, starting at 1; these atom IDs are also called sprite IDs. Figure
5 shows the basic structure of a key frame sample.
Figure 5. The structure of a key frame sample
A sprite atom contains child atoms that define the initial properties of the sprite. It
can contain a child for each of the five basic sprite properties, as well as an atom (of
type kSpriteNameAtomType) that defines the sprite's name. Figure 6 shows the
structure of a sprite atom.
Figure 6. The structure of a sprite atom
As Figure 5 indicates, a key frame atom container also contains a single child atom of
type kSpriteSharedDataAtomType (with atom ID 1) which contains the image data for
all the sprites. This atom contains one sprite images container atom, of type
kSpriteImagesContainerAtomType (also with atom ID 1). This atom, in turn, contains
one atom of type kSpriteImageAtomType for each individual image in the key frame.
Note that all the images for all the sprites are contained in the single images container
atom. Figure 7 shows the structure of a sprite shared data atom.
Figure 7. The structure of the shared sprite data
The Format of Override Samples
The structure of an override sample is somewhat simpler than that of a key frame
sample, largely because an override sample does not contain any image data. An
override sample is an atom container that contains a sprite atom (of type
kSpriteAtomType) for each sprite that is being animated by that override sample. The
sprite atoms contain child atoms for each of the properties that are changing from the
previous key frame or override sample. Figure 8 shows the structure of a typical
override sample.
Figure 8. The structure of an override sample
The ID of a sprite atom in the override sample should be the same as the ID of the
sprite atom in the key frame atom whose data that the override atom is overriding.
Creating Sprite Tracks
As we've just learned, a sprite track consists of key frame samples that contain the
images for the sprites in a track and the initial properties of those sprites, and
override samples that change one or more of the properties of those sprites. In both
cases, the sample data is contained in an atom container. Building a sprite track is
therefore largely a matter of creating the appropriate atom containers and inserting
them at the desired times in the sprite track media.
Creating Sprite Tracks and Media
When the user selects an item in the Test menu, QTSprites calls the
QTApp_HandleMenu function, which is shown in Listing 1.
Listing 1: Handling items in the Test menu
QTApp_HandleMenu
Boolean QTApp_HandleMenu (UInt16 theMenuItem)
{
Boolean myIsHandled = false;
switch (theMenuItem) {
case IDM_MAKE_ICONS_MOVIE:
case IDM_MAKE_PENGUIN_MOVIE:
case IDM_MAKE_SPACE_MOVIE:
QTSprites_CreateSpritesMovie(theMenuItem);
myIsHandled = true;
break;
case IDM_USE_BACKGROUND_IMAGE:
gUseBackgroundPicture = !gUseBackgroundPicture;
myIsHandled = true;
break;
} // switch (theMenuItem)
return(myIsHandled);
}
As you can see, we call the function QTSprites_CreateSpritesMovie to create each of
the three sample movies, passing in the menu item so that we know which movie to
create. QTSprites_CreateSpritesMovie is defined in Listing 2.
Listing 2: Creating a sprite movie
QTSprites_CreateSpritesMovie
OSErr QTSprites_CreateSpritesMovie (UInt16 theMenuItem)
{
Movie myMovie = NULL;
Track myTrack = NULL;
Media myMedia = NULL;
FSSpec [TOKEN:28025]File;
Boolean myIsSelected = false;
Boolean myIsReplacing = false;
Fixed myHeight = 0;
Fixed myWidth = 0;
StringPtr myPrompt =
QTUtils_ConvertCToPascalString(kSpriteSavePrompt);
StringPtr myFileName =
QTUtils_ConvertCToPascalString(kSpriteSaveMovieFileName);
long [TOKEN:28025]Flags =
createMovieFileDeleteCurFile |
createMovieFileDontCreateResFile;
short myResRefNum = 0;
short myResID = movieInDataForkResID;
OSErr myErr = noErr;
// prompt the user for the destination file name
QTFrame_PutFile(myPrompt, myFileName, &myFile,
&myIsSelected, &myIsReplacing);
myErr = myIsSelected ? noErr : userCanceledErr;
if (!myIsSelected)
goto bail;
// create a movie file for the destination movie
myErr = CreateMovieFile(&myFile, FOUR_CHAR_CODE('TVOD'),
smSystemScript, myFlags, &myResRefNum,
&myMovie);
if (myErr != noErr)
goto bail;