TweetFollow Us on Twitter

Mar 01 QTToolkit Volume Number: 17 (2001)
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               myFile;
   Boolean            myIsSelected = false;
   Boolean            myIsReplacing = false;
   Fixed               myHeight = 0;
   Fixed               myWidth = 0;
   StringPtr          myPrompt = 
      QTUtils_ConvertCToPascalString(kSpriteSavePrompt);
   StringPtr          myFileName = 
      QTUtils_ConvertCToPascalString(kSpriteSaveMovieFileName);
   long                  myFlags = 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;

   // create the sprite track and media
   QTSprites_GetMovieSize(theMenuItem, &myHeight, &myWidth);

   myTrack = NewMovieTrack(myMovie, myWidth, myHeight,
                                       kNoVolume);
   myMedia = NewTrackMedia(myTrack, SpriteMediaType, 
                                       kSpriteMediaTimeScale, NULL, 0);

   BeginMediaEdits(myMedia);

   // add the appropriate samples to the sprite media
   switch (theMenuItem) {
      case IDM_MAKE_ICONS_MOVIE:
         QTSprites_AddIconMovieSamplesToMedia(myMedia);
         break;
      case IDM_MAKE_PENGUIN_MOVIE:
         QTSprites_AddPenguinMovieSamplesToMedia(myMedia);
         break;
      case IDM_MAKE_SPACE_MOVIE:
         QTSprites_AddSpaceMovieSamplesToMedia(myMedia);
         break;
      default:
         goto bail;
   }

   EndMediaEdits(myMedia);

   // add the media to the track
   InsertMediaIntoTrack(myTrack, 0, 0, 
                           GetMediaDuration(myMedia), fixed1);

   // set the sprite track properties
   QTSprites_SetTrackProperties(myMedia, theMenuItem);

   // add the movie resource to the movie file
   myErr = AddMovieResource(myMovie, myResRefNum, &myResID, 
                     myFile.name);

bail:
   if (myResRefNum != 0)
      CloseMovieFile(myResRefNum);

   if (myMovie != NULL)
      DisposeMovie(myMovie);

   free(myPrompt);
   free(myFileName);

   return(myErr);
}

The QTSprites_CreateSpritesMovie function is remarkably similar to each of the other movie-creation functions we've used earlier in this series of articles (compare, for instance, the QTMM_CreateVideoMovie function in "Making Movies" in MacTech, June 2000). There are only three main additions for QTSprites. First, since we want to be able to create any one of three different sprite movies, we call the function QTSprites_GetMovieSize to get the desired size for each of those movies. QTSprites_GetMovieSize is defined in Listing 3.

Listing 3: Getting the size of a sprite movie

QTSprites_GetMovieSize
void QTSprites_GetMovieSize (UInt16 theMenuItem, 
                                    Fixed *theHeight, Fixed *theWidth)
{
   if ((theHeight == NULL) || (theWidth == NULL))
      return;

   switch (theMenuItem) {
      case IDM_MAKE_ICONS_MOVIE:
         *theWidth = (long)kIconSpriteTrackWidth << 16;
         *theHeight = (long)kIconSpriteTrackHeight << 16;
         break;
      case IDM_MAKE_PENGUIN_MOVIE:
         *theWidth = (long)kPenguinSpriteTrackWidth << 16;
         *theHeight = (long)kPenguinSpriteTrackHeight << 16;
         break;
      case IDM_MAKE_SPACE_MOVIE:
         *theWidth = (long)kSpaceSpriteTrackWidth << 16;
         *theHeight = (long)kSpaceSpriteTrackHeight << 16;
         break;
   }
}

This is pretty simple stuff; we just convert some long integer constants to the Fixed data type and return them to the caller.

The second difference between QTSprites_CreateSpritesMovie and our earlier movie-creation functions is that we use the menu item number passed in to select the appropriate function for adding samples to the sprite media. And, third, once we've added those samples to the media, we call the QTSprites_SetTrackProperties function to set some sprite track properties. We'll consider sprite track properties in more detail.

Setting Sprite Properties

A key frame sample is an atom container that contains an atom of type kSpriteAtomType for each sprite in the frame (which contains the initial properties of the sprite) and an atom of type kSpriteSharedDataAtomType (which contains atoms that hold the sprite images). So the first thing we need to do is create an atom container, like this:

myErr = QTNewAtomContainer(&mySample);

Let's begin by adding the sprite atoms to the key frame sample. A sprite atom is itself an atom container, because it contains child atoms for each of the initial sprite properties we want to assign it. (Any properties we don't explicitly define in a key frame sample are set to default values.) So we need to create another atom container, like so:

myErr = QTNewAtomContainer(&mySpriteData);

Now we want to add one or more property atoms to the sprite atom. For the icon movie, we want to set the initial location, visibility state, layer, and image index. We can set the image index, for instance, like this:

short      myIndex = 1;

myIndex = EndianS16_NtoB(myIndex);
myErr = QTInsertChild(mySpriteData, kParentAtomIsContainer, 
               kSpritePropertyImageIndex, 1, 0, sizeof(short), 
               &myIndex, NULL);

This code inserts a child of type kSpritePropertyImageIndex into the sprite atom, making sure that the atom data (in this case, a short integer whose value is 1) is in big-endian format. Similarly, we can set the initial visibility state of the icon sprite using these lines of code:

short      isVisible = true;

isVisible = EndianS16_NtoB(isVisible);
myErr = QTInsertChild(mySpriteData, kParentAtomIsContainer, 
               kSpritePropertyVisible, 1, 0, sizeof(short), 
               &isVisible, NULL);

(You might have thought that the default value for the visibility state of a sprite would be true, but sadly that's not so. So we need to explicitly configure our sprites to be visible or they won't be drawn.)

To increase the readability of our code, we'll define a utility function called SpriteUtils_SetSpriteData that allows us to set the main sprite properties in one fell swoop. Then we can define the initial state of the icon sprite like this:

myLocation.h = 32;
myLocation.v = 32;
isVisible = true;
myLayer = -1;
myIndex = 1;

SpriteUtils_SetSpriteData(mySpriteData, &myLocation, 
               &isVisible, &myLayer, &myIndex, NULL, NULL, NULL);

The SpriteUtils_SetSpriteData function is defined in the file SpriteUtilities.c; its definition is shown in Listing 4.

Listing 4: Setting properties of a sprite

SpriteUtils_SetSpriteData
OSErr SpriteUtils_SetSpriteData (
         QTAtomContainer theSprite, 
         Point *theLocation,
         short *theVisible,
         short *theLayer, 
         short *theImageIndex,
         ModifierTrackGraphicsModeRecord *theGraphicsMode, 
         StringPtr theSpriteName, 
         QTAtomContainer theActionAtoms)
{
   QTAtom                  myPropertyAtom;
   OSErr                  myErr = noErr;

   // set the sprite location data
   if (theLocation != NULL) {
      MatrixRecord      myMatrix;

      SetIdentityMatrix(&myMatrix);
      myMatrix.matrix[2][0] = ((long)theLocation->h << 16);
      myMatrix.matrix[2][1] = ((long)theLocation->v << 16);
      EndianUtils_MatrixRecord_NtoB(&myMatrix);

      myPropertyAtom = QTFindChildByIndex(theSprite,                                       kParentAtomIsContainer, 
                                 kSpritePropertyMatrix, 1, NULL);
      if (myPropertyAtom == 0)
         myErr = QTInsertChild(theSprite, 
                              kParentAtomIsContainer, 
                              kSpritePropertyMatrix, 1, 0, 
                              sizeof(MatrixRecord), &myMatrix, NULL);
      else
         myErr = QTSetAtomData(theSprite, myPropertyAtom, 
                              sizeof(MatrixRecord), &myMatrix);

      if (myErr != noErr)
         goto bail;
   }

   // set the sprite visibility state
   if (theVisible != NULL) {
      short          myVisible = *theVisible;

      myVisible = EndianS16_NtoB(myVisible);

      myPropertyAtom = QTFindChildByIndex(theSprite, 
                              kParentAtomIsContainer, 
                              kSpritePropertyVisible, 1, NULL);
      if (myPropertyAtom == 0)
         myErr = QTInsertChild(theSprite, 
                              kParentAtomIsContainer, 
                              kSpritePropertyVisible, 1, 0, 
                              sizeof(short), &myVisible, NULL);
      else
         myErr = QTSetAtomData(theSprite, myPropertyAtom, 
                              sizeof(short), &myVisible);

      if (myErr != noErr)
         goto bail;
   }

   // set the sprite layer
   if (theLayer != NULL) {
      short          myLayer = *theLayer;

      myLayer = EndianS16_NtoB(myLayer);

      myPropertyAtom = QTFindChildByIndex(theSprite, 
                              kParentAtomIsContainer, 
                              kSpritePropertyLayer, 1, NULL);
      if (myPropertyAtom == 0)
         myErr = QTInsertChild(theSprite, 
                              kParentAtomIsContainer, 
                              kSpritePropertyLayer, 1, 0, 
                              sizeof(short), &myLayer, NULL);
      else
         myErr = QTSetAtomData(theSprite, myPropertyAtom, 
                              sizeof(short), &myLayer);

      if (myErr != noErr)
         goto bail;
   }

   // set the sprite image index
   if (theImageIndex != NULL) {
      short          myImageIndex = *theImageIndex;

      myImageIndex = EndianS16_NtoB(myImageIndex);

      myPropertyAtom = QTFindChildByIndex(theSprite, 
                              kParentAtomIsContainer, 
                              kSpritePropertyImageIndex, 1, NULL);
      if (myPropertyAtom == 0)
         myErr = QTInsertChild(theSprite, 
                              kParentAtomIsContainer, 
                              kSpritePropertyImageIndex, 1, 0, 
                              sizeof(short), &myImageIndex, NULL);
      else
         myErr = QTSetAtomData(theSprite, myPropertyAtom, 
                              sizeof(short), &myImageIndex);

      if (myErr != noErr)
         goto bail;
   }

   // set the sprite graphics mode
   if (theGraphicsMode != NULL) {
      ModifierTrackGraphicsModeRecord      myGraphicsMode;

      myGraphicsMode.graphicsMode = 
                  EndianU32_NtoB(theGraphicsMode->graphicsMode);
      myGraphicsMode.opColor.red = 
                  EndianU16_NtoB(theGraphicsMode->opColor.red);
      myGraphicsMode.opColor.green = 
                  EndianU16_NtoB(theGraphicsMode->opColor.green);
      myGraphicsMode.opColor.blue = 
                  EndianU16_NtoB(theGraphicsMode->opColor.blue);

      myPropertyAtom = QTFindChildByIndex(theSprite, 
                           kParentAtomIsContainer, 
                           kSpritePropertyGraphicsMode, 1, NULL);
      if (myPropertyAtom == 0)
         myErr = QTInsertChild(theSprite, 
                           kParentAtomIsContainer, 
                           kSpritePropertyGraphicsMode, 1, 0, 
                           sizeof(myGraphicsMode), &myGraphicsMode, 
                           NULL);
      else
         myErr = QTSetAtomData(theSprite, myPropertyAtom, 
                           sizeof(myGraphicsMode), &myGraphicsMode);

      if (myErr != noErr)
         goto bail;
   }

   // set the sprite name
   if (theSpriteName != NULL) {
      QTAtom       mySpriteNameAtom;

      mySpriteNameAtom = QTFindChildByIndex(theSprite, 
                                 kParentAtomIsContainer, 
                                 kSpriteNameAtomType, 1, NULL);
      if (mySpriteNameAtom == 0)
         myErr = QTInsertChild(theSprite, 
                                 kParentAtomIsContainer, 
                                 kSpriteNameAtomType, 1, 0, 
                                 theSpriteName[0] + 1, theSpriteName, 
                                 NULL);
      else
         myErr = QTSetAtomData(theSprite, mySpriteNameAtom, 
                                 theSpriteName[0] + 1, theSpriteName);

      if (myErr != noErr)
         goto bail;
   }

   // set the action atoms
   if (theActionAtoms != NULL)
      myErr = QTInsertChildren(theSprite, 
                              kParentAtomIsContainer, theActionAtoms);

bail:
   if ((myErr != noErr) && (theSprite != NULL))
      QTRemoveChildren(theSprite, 0);

   return(myErr);
}

For each parameter that is not NULL, SpriteUtils_SetSpriteData looks to see whether the sprite atom container already contains an atom of that the corresponding type (by calling QTFindChildByIndex). If it does contain such an atom, then SpriteUtils_SetSpriteData calls QTSetAtomData to reset the data in that atom; otherwise, it calls QTInsertChild to add an atom of that type. In all cases, the data passed in is converted to big-endian format before being inserted into an atom.

Now that the mySpriteData atom container holds a child atom for each initial property we want to set, we need to add it to the key frame sample atom container, mySample. Once again, we'll define a utility function to help us out:

SpriteUtils_AddSpriteToSample(mySample, mySpriteData, 
kQTIconSpriteAtomID);

SpriteUtils_AddSpriteToSample is defined in Listing 5.
Listing 5: Adding a sprite data atom to a sample container
SpriteUtils_AddSpriteToSample
OSErr SpriteUtils_AddSpriteToSample 
      (QTAtomContainer theSample, QTAtomContainer theSprite, 
         QTAtomID theSpriteID)
{
   QTAtom            mySpriteAtom = 0;
   OSErr            myErr = paramErr;

   // see if the sample already contains a sprite atom of the specified ID
   mySpriteAtom = QTFindChildByID(theSample, 
                           kParentAtomIsContainer, 
                           kSpriteAtomType, theSpriteID, NULL);
   if (mySpriteAtom != 0)
      goto bail;

   // here, the index 0 means to append the sprite to the sample
   myErr = QTInsertChild(theSample, kParentAtomIsContainer, 
                           kSpriteAtomType, theSpriteID, 0, 0, NULL, 
                           &mySpriteAtom);
   if (myErr != noErr)
      goto bail;

   myErr = QTInsertChildren(theSample, mySpriteAtom, 
                           theSprite);

bail:
   return(myErr);
}

As you can see, SpriteUtils_AddSpriteToSample first calls QTFindChildByID to determine whether the specified atom container already contains a sprite atom with the specified sprite ID. If there is an atom of that ID already in the sample, SpriteUtils_AddSpriteToSample returns an error to the caller. Otherwise, it calls QTInsertChild to create a sprite atom of that ID in the sample atom container. Then it calls QTInsertChildren to copy the children from the sprite atom container passed as a parameter into the newly inserted atom in the sample.

Setting Sprite Images

So far, then, we've created a sprite atom that contains the desired initial properties of the icon sprite and added it to the key frame atom container mySample. Now we need to create an atom of type kSpriteSharedDataAtomType and add the required subatoms to it that contain the sprite image data. In the case of the icon sprite movie, we need to add two images to that atom, one for the old QuickTime extension icon and one for the new icon. The function QTSprites_AddIconMovieSamplesToMedia does this by executing these lines of code:

myKeyColor.red = myKeyColor.green = myKeyColor.blue = 0xffff;

SpriteUtils_AddPICTImageToKeyFrameSample(mySample, 
                     kOldQTIconID, &myKeyColor, 1, NULL, NULL);
SpriteUtils_AddPICTImageToKeyFrameSample(mySample, 
                     kNewQTIconID, &myKeyColor, 2, NULL, NULL);

Once again, we're relying on a function defined in the file SpriteUtilities.c to hide the nitty-gritty details of building the required atom from our main application. SpriteUtils_AddPICTImageToKeyFrameSample (defined in Listing 6) reads a picture from our application's resource fork, recompresses the picture with the specified color as a transparency color, and then adds it to the key frame sample atom container.

Listing 6: Adding compressed image data to a key frame sample

SpriteUtils_AddPICTImageToKeyFrameSample
OSErr SpriteUtils_AddPICTImageToKeyFrameSample 
         (QTAtomContainer theKeySample, short thePictID, 
            RGBColor *theKeyColor, QTAtomID theID, 
            FixedPoint *theRegistrationPoint, 
            StringPtr theImageName)
{
   PicHandle            myPicture = NULL;
   Handle                  myCompressedPicture = NULL;
   ImageDescriptionHandle   myImageDesc = NULL;
   OSErr                  myErr = noErr;
   
   // get picture from resource
   myPicture = (PicHandle)GetPicture(thePictID);
   if (myPicture == NULL)
      myErr = resNotFound;

   if (myErr != noErr)
      goto bail;
   
   DetachResource((Handle)myPicture);
   
   // convert it to image data compressed by the animation compressor
   myErr = ICUtils_RecompressPictureWithTransparency
               (myPicture, theKeyColor, NULL, &myImageDesc, 
               &myCompressedPicture);
   if (myErr != noErr)
      goto bail;

   // add it to the key sample
   HLock(myCompressedPicture);
   myErr = SpriteUtils_AddCompressedImageToKeyFrameSample
               (theKeySample, myImageDesc, 
               GetHandleSize(myCompressedPicture), 
               *myCompressedPicture, theID, theRegistrationPoint, 
               theImageName);

bail:
   if (myPicture != NULL)
      KillPicture(myPicture);

   if (myCompressedPicture != NULL)
      DisposeHandle(myCompressedPicture);

   if (myImageDesc != NULL)
      DisposeHandle((Handle)myImageDesc);

   return(myErr);
}

SpriteUtils_AddPICTImageToKeyFrameSample does most of its work by calling two other utility functions, ICUtils_RecompressPictureWithTransparency and SpriteUtils_AddCompressedImageToKeyFrameSample. We won't dissect either of these functions in detail here, as that would take us too far afield. SpriteUtils_AddCompressedImageToKeyFrameSample really just does what you'd imagine to build the atom container illustrated in Figure 7.

Adding the Key Frame Sample to the Sprite Media

So, we've managed to build the key frame sample. Now we just need to add it to the sprite media. To do this, we first need to create a handle to a sprite sample description, which we'll pass to AddMediaSample. A sprite sample description is defined by the SpriteDescription data type, declared like this:

struct SpriteDescription {
   long                      descSize;
   long                      dataFormat;
   long                      resvd1;
   short                      resvd2;
   short                      dataRefIndex;
   long                      version;
   OSType                   decompressorType;
   long                      sampleFlags;
};

We allocate a handle to a sprite sample description by calling NewHandleClear:

mySampleDesc = (SampleDescriptionHandle)
                           NewHandleClear(sizeof(SpriteDescription));

The decompressorType field of the sprite sample description specifies the type of compressor used to compress the sample data (or 0 if no compression is used). The sprite media handler supports compressed sample data; the only restriction is that the compressor used to compress the data must be lossless. (A compressor is lossless if the result of compressing some data and then decompressing it yields the original data unchanged; otherwise, the compressor is lossy.) For the moment, we'll just use the uncompressed data that we've created. So we can just go right ahead and call AddMediaSample:

myErr = AddMediaSample(theMedia, (Handle)mySample, 0, 
         GetHandleSize(mySample), kSpriteMediaFrameDurationIcon, 
         mySampleDesc, 1, 0, NULL);

We can use yet another utility function defined in SpriteUtilities.c:

SpriteUtils_AddSpriteSampleToMedia(theMedia, mySample, 
                        kSpriteMediaFrameDurationIcon, true, NULL);

Listing 7 shows our definition of SpriteUtils_AddSpriteSampleToMedia.

Listing 7: Adding a sprite sample to the sprite media

SpriteUtils_AddSpriteSampleToMedia
OSErr SpriteUtils_AddSpriteSampleToMedia (Media theMedia, 
               QTAtomContainer theSample, TimeValue theDuration, 
               Boolean isKeyFrame, TimeValue *theSampleTime)
{
   SampleDescriptionHandle    mySampleDesc = NULL;
   OSErr                              myErr = noErr;
   
   mySampleDesc = (SampleDescriptionHandle)
                        NewHandleClear(sizeof(SpriteDescription));
   if (mySampleDesc == NULL) {
      myErr = MemError();
      goto bail;
   }

   myErr = AddMediaSample(theMedia,
                     (Handle)theSample,
                     0,
                     GetHandleSize(theSample),
                     theDuration,
                     mySampleDesc,
                     1,
                     (short)(isKeyFrame ? 0 : mediaSampleNotSync),
                     theSampleTime);

bail:
   if (mySampleDesc != NULL)
      DisposeHandle((Handle) mySampleDesc);
   return(myErr);
}

Note that SpriteUtils_AddSpriteSampleToMedia adds the specified sample data as a key frame sample or an override frame sample, depending on the value of the isKeyFrame parameter.

Creating Override Samples

With these sprite utility functions at our disposal, it's now quite easy to create some override samples and add them to the sprite media. For the icon movie, we want to create 99 override samples. Each override sample will move the icon 2 pixels to the right; furthermore, when the icon reaches the halfway point, we'll change the image index from 1 to 2. Listing 8 shows the code we use to add those override samples to the sprite media.

Listing 8: Adding some override samples

QTSprites_AddIconMovieSamplesToMedia
for (myCount = 1; myCount <= kNumOverrideSamples; myCount++) {
   QTRemoveChildren(mySample, kParentAtomIsContainer);
   QTRemoveChildren(mySpriteData, kParentAtomIsContainer);

   // every frame, bump the icon's location
   myLocation.h += 2;

   // change icon half way thru
   if (myCount == kNumOverrideSamples / 2)
      myIndex = 2;

   SpriteUtils_SetSpriteData(mySpriteData, &myLocation, NULL, 
                        NULL, &myIndex, NULL, NULL, NULL);
   SpriteUtils_AddSpriteToSample(mySample, mySpriteData, 
                        kQTIconSpriteAtomID);
   SpriteUtils_AddSpriteSampleToMedia(theMedia, mySample, 
                     kSpriteMediaFrameDurationIcon, false, NULL);   
}

Setting Sprite Track Properties

We saw earlier that we can use a sprite image as the background of a sprite track by setting the layer of the image to kBackgroundSpriteLayerNum. But what if we want to have a solid background color for the entire sprite track? We could of course create a sprite image of the proper size and color and then put it into the first key frame sample of the sprite track. The sprite media handler, however, provides a better method to set a solid background color by allowing us to set the sprite track's background color property. The background color property is one of several sprite track properties that control global aspects of the sprite track. The currently defined sprite track properties are accessed using these constants:

enum {
   kSpriteTrackPropertyBackgroundColor         = 101,
   kSpriteTrackPropertyOffscreenBitDepth      = 102,
   kSpriteTrackPropertySampleFormat               = 103,
   kSpriteTrackPropertyScaleSpritesToScaleWorld
                                                            = 104,
   kSpriteTrackPropertyHasActions                  = 105,
   kSpriteTrackPropertyVisible                     = 106,
   kSpriteTrackPropertyQTIdleEventsFrequency   = 107
};

To set one or more sprite track properties, we need to create a media property atom, an atom container that contains a child atom for each of the properties we want to set. The atom type of the child atom should be set to one of these sprite property constants, and the atom ID should be set to 1. The type of the atom data varies from property to property. For the background color sprite track property, the atom data is an RGBColor structure. Once we've constructed the media property atom, we attach it to the sprite track by calling the SetMediaPropertyAtom function. Listing 9 shows how we set a solid white background for the penguin sprite movie. (If we don't specify a background sprite image and we don't set the background color property, then we'll get the default sprite track background color, which is black.)

Listing 9: Setting the background color of a sprite track

QTSprites_SetTrackProperties
void QTSprites_SetTrackProperties (Media theMedia, 
                                                   UInt16 theMenuItem)
{
   QTAtomContainer         myTrackProperties;
   RGBColor                  myBackgroundColor;
   OSErr                     myErr = noErr;

   if (!gUseBackgroundPicture) {
      // add a background color to the sprite track
      QTSprites_GetBackgroundColor(theMenuItem, 
                              &myBackgroundColor);

      myErr = QTNewAtomContainer(&myTrackProperties);
      if (myErr == noErr) {
         QTInsertChild(myTrackProperties, 0, 
                        kSpriteTrackPropertyBackgroundColor, 1, 1, 
                        sizeof(RGBColor), &myBackgroundColor, NULL);

         SetMediaPropertyAtom(theMedia, myTrackProperties);

         QTDisposeAtomContainer(myTrackProperties);
      }
   }
}

The kSpriteTrackPropertyOffscreenBitDepth property specifies the desired bit depth of the offscreen graphics world where the sprite data is drawn before it is copied to the screen. The atom data is of type short, and the default value is 0 (which means to use the bit depth of the deepest monitor that intersects the onscreen sprite window). Setting this property can save memory if your sprite graphics are drawn at a lower bit depth than the user's monitor. The kSpriteTrackPropertySampleFormat property specifies the override sample interpretation mode, which indicates how the sprite media handler interprets override samples. Currently there are two possible modes:

enum {
   kKeyFrameAndSingleOverride         = 1L << 1,
   kKeyFrameAndAllOverrides            = 1L << 2
};

If the override sample interpretation mode is kKeyFrameAndSingleOverride, then the sprite media handler generates the sprite data for a particular override sample by applying the changes in that override sample directly to the key frame sample, ignoring any previous override samples. (This is the default mode.) On the other hand, if the mode is kKeyFrameAndAllOverrides, then the sprite media handler generates sprite data by applying the changes in a particular override sample to the data generated by applying the changes in all previous override samples to the key frame sample data. In other words, kKeyFrameAndSingleOverride specifies that a particular override frame contains absolute changes to key frame data, while kKeyFrameAndAllOverrides specifies relative changes.

The kSpriteTrackPropertyScaleSpritesToScaleWorld property specifies whether the sprites in the sprite track are rescaled whenever the sprite track is resized. This is useful mostly when the sprite images have been compressed using a resolution-independent codec (for example, the Curve codec). The atom data is of type Boolean, and the default value is false.

The kSpriteTrackPropertyVisible property specifies whether the sprite track is visible. The atom data is of type Boolean, and the default value is true. It might occasionally be useful to set the sprite track to be invisible if there are other tracks in the movie and you want to allow the user to click on items in those tracks (by putting an invisible sprite track in front that intercepts those clicks).

The remaining two sprite track properties apply only to sprite tracks that contain wired actions. The kSpriteTrackPropertyHasActions property specifies whether the sprite track contains any wired actions; the atom data is of type Boolean and the default value is false. The kSpriteTrackPropertyQTIdleEventsFrequency property indicates the desired frequency at which the sprite media handler should send idle events (events of type kQTIdleEvent) to the sprite track. In this case, the atom data is of type UInt32 and the default value is kNoQTIdleEvents (which means not to issue any idle events). We'll consider these two sprite track properties in more detail in our article on wired sprites.

Putting it All Together

Let's see what the two functions QTSprites_AddIconMovieSamplesToMedia and QTSprites_AddPenguinMovieSamplesToMedia look like in their entirety. Listing 10 shows the definition of QTSprites_AddIconMovieSamplesToMedia.

Listing 10: Adding samples to the icon sprite movie

QTSprites_AddIconMovieSamplesToMedia
void QTSprites_AddIconMovieSamplesToMedia (Media theMedia)
{
   QTAtomContainer         mySample = NULL;
   QTAtomContainer         mySpriteData = NULL;
   RGBColor                  myKeyColor;
   Point                     myLocation;
   short                     isVisible;
   short                     myLayer, myIndex, myCount;
   OSErr                     myErr = noErr;

   // create a new, empty key frame sample
   myErr = QTNewAtomContainer(&mySample);
   if (myErr != noErr)
      goto bail;

   myKeyColor.red = myKeyColor.green = myKeyColor.blue = 
                                          0xffff;      // white

   // add images to the key frame sample
   SpriteUtils_AddPICTImageToKeyFrameSample(mySample, 
                     kOldQTIconID, &myKeyColor, 1, NULL, NULL);
   SpriteUtils_AddPICTImageToKeyFrameSample(mySample, 
                     kNewQTIconID, &myKeyColor, 2, NULL, NULL);

   // add the initial sprite properties to the key frame sample
   myErr = QTNewAtomContainer(&mySpriteData);
   if (myErr != noErr)
      goto bail;

   // the QT icon sprite
   myLocation.h   = 32;
   myLocation.v   = 32;
   isVisible      = true;
   myLayer         = -1;
   myIndex         = 1;
   
   SpriteUtils_SetSpriteData(mySpriteData, &myLocation, 
            &isVisible, &myLayer, &myIndex, NULL, NULL, NULL);
   SpriteUtils_AddSpriteToSample(mySample, mySpriteData, 
            kQTIconSpriteAtomID);
   SpriteUtils_AddSpriteSampleToMedia(theMedia, mySample, 
            kSpriteMediaFrameDurationIcon, true, NULL);

   // add a few override samples to change the icon's location and image
   for (myCount = 1; myCount <= kNumOverrideSamples; 
                        myCount++) {
      QTRemoveChildren(mySample, kParentAtomIsContainer);
      QTRemoveChildren(mySpriteData, kParentAtomIsContainer);

      // every frame, bump the icon's location
      myLocation.h += 2;

      // change icon half way thru
      if (myCount == kNumOverrideSamples / 2)
         myIndex = 2;

      SpriteUtils_SetSpriteData(mySpriteData, &myLocation, 
                     NULL, NULL, &myIndex, NULL, NULL, NULL);
      SpriteUtils_AddSpriteToSample(mySample, mySpriteData, 
                     kQTIconSpriteAtomID);
      SpriteUtils_AddSpriteSampleToMedia(theMedia, mySample, 
                     kSpriteMediaFrameDurationIcon, false, NULL);
   }

bail:
   if (mySample != NULL)
      QTDisposeAtomContainer(mySample);

   if (mySpriteData != NULL)
      QTDisposeAtomContainer(mySpriteData);   
}

Listing 11 shows our complete definition of QTSprites_AddPenguinMovieSamplesToMedia.

Listing 11: Adding samples to the penguin sprite movie

QTSprites_AddPenguinMovieSamplesToMedia
void QTSprites_AddPenguinMovieSamplesToMedia (Media theMedia)
{
   QTAtomContainer         mySample = NULL;
   QTAtomContainer         mySpriteData = NULL;
   RGBColor                  myKeyColor;
   Point                     myLocation;
   short                     isVisible;
   short                     myLayer, myIndex, myCount;
   ModifierTrackGraphicsModeRecord 
                              myGraphicsMode;
   OSErr                     myErr = noErr;

   // create a new, empty key frame sample
   myErr = QTNewAtomContainer(&mySample);
   if (myErr != noErr)
      goto bail;

   myKeyColor.red = myKeyColor.green = myKeyColor.blue = 
                                          0xffff;      // white

   // add images to the key frame sample
   SpriteUtils_AddPICTImageToKeyFrameSample(mySample, 
                     kPenguinPictID, &myKeyColor, 
                     kPenguinImageIndex, NULL, NULL);

   // add the initial sprite properties to the key frame sample
   myErr = QTNewAtomContainer(&mySpriteData);
   if (myErr != noErr)
      goto bail;

   // the penguin sprite
   myLocation.h   = 0;
   myLocation.v   = 0;
   isVisible      = true;
   myLayer         = -1;
   myIndex         = 1;

   // set the initial blend amount (0 = fully transparent; 0xffff = fully opaque)
   myGraphicsMode.graphicsMode = blend;
   myGraphicsMode.opColor.red = 0;
   myGraphicsMode.opColor.green = 0;
   myGraphicsMode.opColor.blue = 0;
   
   SpriteUtils_SetSpriteData(mySpriteData, &myLocation, 
            &isVisible, &myLayer, &myIndex, &myGraphicsMode, 
            NULL, NULL);
   SpriteUtils_AddSpriteToSample(mySample, mySpriteData, 
            kPenguinSpriteAtomID);
   SpriteUtils_AddSpriteSampleToMedia(theMedia, mySample, 
            kSpriteMediaFrameDurationPenguin, true, NULL);

   // add a few override samples to change the penguin's opacity
   for (myCount = 1; myCount <= kNumOverrideSamples; 
                              myCount++) {
      QTRemoveChildren(mySample, kParentAtomIsContainer);
      QTRemoveChildren(mySpriteData, kParentAtomIsContainer);

      // every frame, bump the penguin's opacity
      myGraphicsMode.graphicsMode = blend;
      myGraphicsMode.opColor.red = (myCount - 1) * 
                                 (0xffff / kNumOverrideSamples - 1);
      myGraphicsMode.opColor.green = (myCount - 1) * 
                                 (0xffff / kNumOverrideSamples - 1);
      myGraphicsMode.opColor.blue = (myCount - 1) * 
                                 (0xffff / kNumOverrideSamples - 1);

      SpriteUtils_SetSpriteData(mySpriteData, NULL, NULL, NULL, 
                  NULL, &myGraphicsMode, NULL, NULL);
      SpriteUtils_AddSpriteToSample(mySample, mySpriteData, 
                  kPenguinSpriteAtomID);
      SpriteUtils_AddSpriteSampleToMedia(theMedia, mySample, 
                  kSpriteMediaFrameDurationPenguin, false, NULL);
   }

bail:   
   if (mySample != NULL)
      QTDisposeAtomContainer(mySample);

   if (mySpriteData != NULL)
      QTDisposeAtomContainer(mySpriteData);
}

For the definition of QTSprites_AddSpaceMovieSamplesToMedia, see the file QTSprites.h. The basic strategy is the same, but the increased complexity of the space movie gives a considerably longer function.

Hit Testing

As I mentioned earlier, QuickTime sprites can be interactive. That is to say, we can create movies with sprite tracks whose sprites respond to user actions like mouse movements and mouse button clicks. When we consider wired sprites in a future article, we'll investigate the full power of this interactivity. In the meantime, let's take a look at a more limited form of interactivity supported by non-wired sprites, the ability to determine whether the user has clicked on a sprite (also called hit testing).

Programmatically finding clicks on a sprite is a two-stage process. First, we need to determine when the user has clicked the mouse button inside of the movie rectangle. Then we need to determine whether that mouse click was on top of a sprite. For the first task, we can add a case to the switch statement in our movie controller action filter function QTApp_MCActionFilterProc, looking for the mcActionMouseDown movie controller action (as shown in Listing 12).

Listing 12: Detecting clicks in the movie window

QTApp_MCActionFilterProc
switch (theAction) {

   // handle window resizing
   case mcActionControllerSizeChanged:
      QTFrame_SizeWindowToMovie(myWindowObject);
      break;

   // handle idle events
   case mcActionIdle:
      QTApp_Idle((**myWindowObject).fWindow);
      break;

   // handle mouse-down events
   case mcActionMouseDown:
      isHandled = QTSprites_HitTestSprites(myWindowObject, 
                                          (EventRecord *)theParams);
      break;

   default:
      break;
}

The movie controller issues an mcActionMouseDown action whenever it receives a mouse-down event that's inside the movie rectangle. The parameter data passed to our action filter function (in theParams) is a pointer to the event record for that mouse-down event. As you can see, our filter function simply calls the application-defined function QTSprites_HitTestSprites to see whether the click is on a sprite and, if so, to react appropriately. (For more information about movie controller action filter functions, see "QuickTime 101" in MacTech, January 2000.)

The sprite media handler supplies the SpriteMediaHitTestOneSprite and SpriteMediaHitTestAllSprites functions, which we can use to determine whether the user has clicked on a sprite. For present purposes, we'll use SpriteMediaHitTestAllSprites, which looks at each one of the sprites in a sprite track to see whether it is currently at a specified location. SpriteMediaHitTestAllSprites is declared essentially like this:

ComponentResult SpriteMediaHitTestAllSprites 
               (MediaHandler    mh, long flags, Point loc, 
                     QTAtomID *spriteHitID);

If a sprite is situated at the specified location in the sprite track associated with the specified sprite media handler, then SpriteMediaHitTestAllSprites returns the ID of that sprite in the spriteHitID parameter. If more than one sprite is situated at that location, then spriteHitID is set to the ID of the frontmost sprite (that is, the sprite with the lowest layer number). If no sprite is situated at that location, then spriteHitID is set to 0.

By default, the loc parameter should be the location of the mouse click, in coordinates local to the sprite track. However, the event record passed to our movie controller action filter function contains the location of the mouse click in global coordinates. Rather than bother with converting the global location to a local position, we can add spriteHitTestLocInDisplayCoordinates to the flags parameter, to indicate that the loc parameter is in global coordinates. These flags are understood by SpriteMediaHitTestAllSprites:

enum {
   spriteHitTestBounds                              = 1L << 0,
   spriteHitTestImage                              = 1L << 1,
   spriteHitTestInvisibleSprites               = 1L << 2,
   spriteHitTestIsClick                              = 1L << 3,
   spriteHitTestLocInDisplayCoordinates      = 1L << 4
};

If you want to accept clicks anywhere within the rectangular bounding box of a sprite, add in the spriteHitTestBounds flag. If, conversely, you want to accept clicks only on a non-transparent part of a sprite, add in the spriteHitTestImage flag. You must specify one or the other of these two flags, or else no hit-testing will occur. Setting both of these flags is tantamount to setting only spriteHitTestImage. (Earlier versions of QuickTime required you to set both flags if you wanted image testing, but current versions do not.)

By default, SpriteMediaHitTestAllSprites and SpriteMediaHitTestOneSprite test only visible sprites for hits. If you want to test invisible sprites as well, set the spriteHitTestInvisibleSprites flag. If you want to pass the mouse click onto the codec that is rendering the sprite image, then specify the spriteHitTestIsClick flag; this is currently useful only with the Ripple codec.

In QTSprites_HitTestSprites, we want to test both visible and invisible sprites for hits, and we want to test only within the non-transparent portions of a sprite image. So we'll specify our flags parameter like this:

myFlags = spriteHitTestImage | 
                     spriteHitTestLocInDisplayCoordinates | 
                     spriteHitTestInvisibleSprites;

Now, what will we do when we detect a click on a sprite? In theory, we can do anything we want. We've got the entire QuickTime API at our disposal, so we could do some neat things like launch the user's web browser and navigate to a specific page, or download a file from a remote location, or even build a QuickTime movie. Even just within the context of our sprite track we could do some rather interesting stuff, like moving the sprite to a new location in the sprite track, changing its image index, and so forth. For simplicity, we'll limit ourselves to changing the sprite's visibility state: if the sprite is currently visible, we'll make it invisible (and vice versa).

SpriteMediaGetSpriteProperty(myHandler, myAtomID, 
                        kSpritePropertyVisible, (void *)&isVisible);
SpriteMediaSetSpriteProperty(myHandler, myAtomID, 
                        kSpritePropertyVisible, (void *)!isVisible);

Listing 13 shows our complete definition of QTSprites_HitTestSprites.

Listing 13: Hit testing sprites

QTSprites_HitTestSprites
Boolean QTSprites_HitTestSprites 
            (WindowObject theWindowObject, EventRecord *theEvent)
{
   ApplicationDataHdl      myAppData = NULL;
   MediaHandler               myHandler = NULL;
   Boolean                     isHandled = false;
   long                           myFlags = 0L;
   QTAtomID                     myAtomID = 0;
   Point                        myPoint;
   ComponentResult            myErr = noErr;

   myAppData = (ApplicationDataHdl)
         QTFrame_GetAppDataFromWindowObject(theWindowObject);
   if (myAppData == NULL)
      goto bail;

   if (theEvent == NULL)
      goto bail;

   // make sure that the click is in our window
   if ((**theWindowObject).fWindow != 
                     QTFrame_GetFrontMovieWindow())
      goto bail;

   myHandler = (**myAppData).fSpriteHandler;
   myFlags = spriteHitTestImage | 
                     spriteHitTestLocInDisplayCoordinates | 
                     spriteHitTestInvisibleSprites;
   myPoint = theEvent->where;
   
   myErr = SpriteMediaHitTestAllSprites(myHandler, myFlags, 
                                       myPoint, &myAtomID);
   if ((myErr == noErr) && (myAtomID != 0)) {
      Boolean            isVisible;

      // the user has clicked on a sprite;
      // for now, we'll just toggle the visibility state of the sprite
      SpriteMediaGetSpriteProperty(myHandler, myAtomID, 
                        kSpritePropertyVisible, (void *)&isVisible);
      SpriteMediaSetSpriteProperty(myHandler, myAtomID, 
                        kSpritePropertyVisible, (void *)!isVisible);

      isHandled = true;
   }

bail:
   return(isHandled);
}

You'll notice that we need to make sure that the window associated with the window object passed to QTSprites_HitTestSprites is the frontmost movie window, by executing these lines of code:

if ((**theWindowObject).fWindow != 
                  QTFrame_GetFrontMovieWindow())
   goto bail;

This is because, when we are handling an event on Macintosh computers, we call MCIsPlayerEvent for each open movie controller, until we find one that handles the event. It's possible to have two or more overlapping sprite movies such that a mouse click does not hit a sprite in the frontmost movie window but does hit one in an overlapped window. We can avoid unexpected behaviors by limiting our hit testing to the frontmost movie window. (On Windows, this additional check is unnecessary but harmless.)

Conclusion

In this article, we've seen how to create and work with sprite tracks in QuickTime movies. Sprite tracks are remarkably easy to create. It's really just an exercise in creating atom containers and adding them as samples to a sprite media. Keep in mind that sprite media data isn't just a collection of pixels that are copied from the movie file onto the screen. Rather, sprites are distinct objects, with properties that can be changed dynamically to animate the sprites. This is what accounts for the drastic size differences between sprite and non-sprite versions of a movie. This is also what accounts for our ability to interact with individual sprites (for instance, our ability to hit test mouse clicks on sprites).

In the next article, we'll continue our investigation of sprites. Among other things, we'll see how to use a video track as the source of a sprite's image data, and we'll see how to use modifier tracks to animate sprites. Believe it or not, using modifier tracks instead of override samples will result in even further size reductions for our sprite movie files. Stay tuned!

Acknowledgements and References

The utilities in the file SpriteUtilities.c were originally written by Sean Allen; I have taken the liberty of reworking them to bring the general programming style into conformance with the rest of the sample code we've encountered in this series of articles. The code for building the space movie is based on an existing sample code package, MakeSpriteMovie.c, also by Sean Allen.

The definitive API reference for QuickTime sprites and wired actions is the book QuickTime Wired Movies And Sprite Animation by Tom Maremaa (downloadable from http://developer.apple.com/techpubs/quicktime/qtdevdocs/RM/pdfframe.htm).


Tim Monroe is amazed at how fast his new lizard Libra is growing. Maybe he'll try feeding insects to his own children to see if they grow any faster. In the meantime, you can contact him at monroe@apple.com.

 
AAPL
$97.67
Apple Inc.
+0.64
MSFT
$44.50
Microsoft Corpora
+0.10
GOOG
$589.02
Google Inc.
-4.33

MacTech Search:
Community Search:

Software Updates via MacUpdate

TotalFinder 1.6.2 - Adds tabs, hotkeys,...
TotalFinder is a universally acclaimed navigational companion for your Mac. Enhance your Mac's Finder with features so smart and convenient, you won't believe you ever lived without them. Tab-based... Read more
Vienna 3.0.0 RC 2 :be5265e: - RSS and At...
Vienna is a freeware and Open-Source RSS/Atom newsreader with article storage and management via a SQLite database, written in Objective-C and Cocoa, for the OS X operating system. It provides... Read more
VLC Media Player 2.1.5 - Popular multime...
VLC Media Player is a highly portable multimedia player for various audio and video formats (MPEG-1, MPEG-2, MPEG-4, DivX, MP3, OGG, ...) as well as DVDs, VCDs, and various streaming protocols. It... Read more
Default Folder X 4.6.7 - Enhances Open a...
Default Folder X attaches a toolbar to the right side of the Open and Save dialogs in any OS X-native application. The toolbar gives you fast access to various folders and commands. You just click... Read more
TinkerTool 5.3 - Expanded preference set...
TinkerTool is an application that gives you access to additional preference settings Apple has built into Mac OS X. This allows to activate hidden features in the operating system and in some of the... Read more
Audio Hijack Pro 2.11.0 - Record and enh...
Audio Hijack Pro drastically changes the way you use audio on your computer, giving you the freedom to listen to audio when you want and how you want. Record and enhance any audio with Audio Hijack... Read more
Intermission 1.1.1 - Pause and rewind li...
Intermission allows you to pause and rewind live audio from any application on your Mac. Intermission will buffer up to 3 hours of audio, allowing users to skip through any assortment of audio... Read more
Autopano Giga 3.6 - Stitch multiple imag...
Autopano Giga allows you to stitch 2, 20, or 2,000 images. Version 3.0 integrates impressive new features that will definitely make you adopt Autopano Pro or Autopano Giga: Choose between 9... Read more
Airfoil 4.8.7 - Send audio from any app...
Airfoil allows you to send any audio to AirPort Express units, Apple TVs, and even other Macs and PCs, all in sync! It's your audio - everywhere. With Airfoil you can take audio from any... Read more
Microsoft Remote Desktop 8.0.8 - Connect...
With Microsoft Remote Desktop, you can connect to a remote PC and your work resources from almost anywhere. Experience the power of Windows with RemoteFX in a Remote Desktop client designed to help... Read more

Latest Forum Discussions

See All

This Week at 148Apps: July 21-25, 2014
Another Week of Expert App Reviews   At 148Apps, we help you sort through the great ocean of apps to find the ones we think you’ll like and the ones you’ll need. Our top picks become Editor’s Choice, our stamp of approval for apps with that little... | Read more »
Reddme for iPhone - The Reddit Client (...
Reddme for iPhone - The Reddit Client 1.0 Device: iOS iPhone Category: News Price: $.99, Version: 1.0 (iTunes) Description: Reddme for iPhone is an iOS 7-optimized Reddit client that offers a refreshing new way to experience Reddit... | Read more »
Jacob Jones and the Bigfoot Mystery : Ep...
Jacob Jones and the Bigfoot Mystery : Episode 2 1.0 Device: iOS Universal Category: Games Price: $1.99, Version: 1.0 (iTunes) Description: Jacob Jones is back in Episode 2 of one of Apples 'Best of 2013' games and an App Store... | Read more »
New Trailer For Outcast Odyssey, A New K...
New Trailer For Outcast Odyssey, A New Kind of Card Battler Posted by Jennifer Allen on July 25th, 2014 [ permalink ] Out this Fall is a new kind of card battle game: Outcast Odyssey. | Read more »
Hay Day – Tip, Tricks, Strategies, and C...
Recently got into Supercell’s other huge hit, Hay Day and could do with some advice on what to do? We’ve got you covered with some helpful trips and tricks to bear in mind! Ticking Along One of the key things to keep in mind while building up that... | Read more »
Monster Head Review
Monster Head Review By Nadia Oxford on July 25th, 2014 Our Rating: :: FEEDING TIMEUniversal App - Designed for iPhone and iPad Racking up a high score with Monster Head is trickier than it first appears. The appeal wears out fairly... | Read more »
Garfield: Survival of the Fattest Coming...
Garfield: Survival of the Fattest Coming to iOS this Fall Posted by Jennifer Allen on July 25th, 2014 [ permalink ] Who loves lasagna? Me. Also everyone’s favorite grumpy fat cat, Garfield. | Read more »
Happy Flock Review
Happy Flock Review By Andrew Fisher on July 25th, 2014 Our Rating: :: HERD IT ALL BEFOREUniversal App - Designed for iPhone and iPad Underneath the gloss of Happy Flock’s visuals is a game of very little substance. It’s cute, but... | Read more »
Square Register Updates Adds Offline Pay...
Square Register Updates Adds Offline Payments Posted by Ellis Spice on July 25th, 2014 [ permalink ] Universal App - Designed for iPhone and iPad | Read more »
Looking For Group – Hearthstone’s Curse...
For the first time since its release (which has thankfully been a much shorter window for iPad players than their PC counterparts), Blizzard’s wildly successful Hearthstone: Heroes of Warcraft CCG is sporting some brand new content: the single... | Read more »

Price Scanner via MacPrices.net

Record Mac Shipments In Q2/14 Confound Analys...
A Seeking Alpha Trefis commentary notes that Apple’s fiscal Q3 2014 results released July 22, beat market predictions on earnings, although revenues were slightly lower than anticipated. Apple’s Mac’... Read more
Intel To Launch Core M Silicon For Use In Not...
Digitimes’ Monica Chen and Joseph Tsai, report that Intel will launch 14nm-based Core M series processors specifically for use in fanless notebook/tablet 2-in-1 models in Q4 2014, with many models to... Read more
Apple’s 2014 Back to School promotion: $100 g...
 Apple’s 2014 Back to School promotion includes a free $100 App Store Gift Card with the purchase of any new Mac (Mac mini excluded), or a $50 Gift Card with the purchase of an iPad or iPhone,... Read more
iMacs on sale for $150 off MSRP, $250 off for...
Best Buy has iMacs on sale for up to $160 off MSRP for a limited time. Choose free home shipping or free instant local store pickup (if available). Prices are valid for online orders only, in-store... Read more
Mac minis on sale for $100 off MSRP, starting...
Best Buy has Mac minis on sale for $100 off MSRP. Choose free shipping or free instant local store pickup. Prices are for online orders only, in-store prices may vary: 2.5GHz Mac mini: $499.99 2.3GHz... Read more
Global Tablet Market Grows 11% in Q2/14 Notwi...
Worldwide tablet sales grew 11.0 percent year over year in the second quarter of 2014, with shipments reaching 49.3 million units according to preliminary data from the International Data Corporation... Read more
New iPhone 6 Models to Have Staggered Release...
Digitimes’ Cage Chao and Steve Shen report that according to unnamed sources in Apple’s upstream iPhone supply chain, the new 5.5-inch iPhone will be released several months later than the new 4.7-... Read more
New iOS App Helps People Feel Good About thei...
Mobile shoppers looking for big savings at their favorite stores can turn to the Goodshop app, a new iOS app with the latest coupons and deals at more than 5,000 online stores. In addition to being a... Read more
Save on 5th generation refurbished iPod touch...
The Apple Store has Apple Certified Refurbished 5th generation iPod touches available starting at $149. Apple’s one-year warranty is included with each model, and shipping is free. Many, but not all... Read more
What Should Apple’s Next MacBook Priority Be;...
Stabley Times’ Phil Moore says that after expanding its iMac lineup with a new low end model, Apple’s next Mac hardware decision will be how it wants to approach expanding its MacBook lineup as well... Read more

Jobs Board

*Apple* Solutions Consultant (ASC) - Apple (...
**Job Summary** The ASC is an Apple employee who serves as an Apple brand ambassador and influencer in a Reseller's store. The ASC's role is to grow Apple Read more
WW Sales Program Manager, *Apple* Online St...
**Job Summary** Imagine what you could do here. At Apple , great ideas have a way of becoming great products, services, and customer experiences very quickly. Bring Read more
Lead Software Engineer, *Apple* Online Stor...
**Job Summary** Imagine what you could do here. At Apple , great ideas have a way of becoming great products, services, and customer experiences very quickly. Bring Read more
Manager, *Apple* Fullfillment Operation (AF...
…cross-functional teams to drive the highest level of program management quality for Apple . You will help plan launch strategy and demand generation programs with Read more
Project Manager / Business Analyst, WW *Appl...
…a senior project manager / business analyst to work within our Worldwide Apple Fulfillment Operations and the Business Process Re-engineering team. This role will work Read more
All contents are Copyright 1984-2011 by Xplain Corporation. All rights reserved. Theme designed by Icreon.