TweetFollow Us on Twitter

Sep 01 Cover

Volume Number: 17 (2001)
Issue Number: 09
Column Tag: QuickTime Toolkit

F/X

by Tim Monroe

Using Video Effects in QuickTime Movies

Introduction

The QuickTime video effects architecture, introduced in QuickTime 3, is an extensible system for applying video effects to single images or video tracks (called filters), and to pairs of images or video tracks (called transitions). QuickTime includes an implementation of the 133 standard transitions defined by the Society of Motion Picture and Television Engineers (SMPTE), as well as some additional effects developed by the QuickTime team. The SMPTE effects include various forms of wipe effects, iris effects, radial effects, and matrix effects. Of all of these, my personal favorite is a wipe effect called the horizontal barn zig-zag, shown in Figure 1.


Figure 1: The horizontal barn zig-zag wipe effect applied to two video tracks

The additional QuickTime effects include transitions like a simple explode (where the first image is exploded outward to reveal the second image) and a push (where the first image is pushed aside by the second image). Figures 2 and 3 show these effects applied to two penguin images.


Figure 2: The explode effect applied to two images


Figure 3: The push effect applied to two images

QuickTime also includes a very nice cross-fade or dissolve transition (which produces a smooth alpha blending from the first image to the second) and a nifty film noise filter that makes a video track look like old, faded, dusty, and scratched film. Figure 4 shows a frame of a movie with the film noise effect.


Figure 4: The film noise effect applied to a movie frame

There are several video effects that operate on no source images or video tracks at all, called effects generators. For instance, we can use the fire effect to generate a real-looking fire (see Figure 5), and we can use the cloud effect to generate a wind-pushed, moving cloud. With generators, we will usually want to composite the effect onto some other image or video track. Figure 6 shows the fire effect composited onto the penguin image. (Ouch, that's gotta hurt!)


Figure 5: The fire effect in a movie


Figure 6: The fire effect composited onto an image

The data describing an effect is stored in a video track, and the actual effect itself is generated in real time as the movie is played. These effects use extremely little data to achieve the desired visual output. For instance, a video track that specifies the fire effect is only about 60 bytes in size; when the track is played, QuickTime generates a real-time, non-repeating, dynamic fire image.

Generators, filters, and transitions are implemented in the general QuickTime architecture as image decompressor components (of type decompressorComponentType). One thing this means is that we can reference a specific effect by providing a four-character code, which is an image decompressor component subtype. Here are a few of the available effects types:

enum {
   kWaterRippleCodecType               = FOUR_CHAR_CODE(‘ripl'),
   kFireCodecType                        = FOUR_CHAR_CODE(‘fire'),
   kFilmNoiseImageFilterType         = FOUR_CHAR_CODE(‘fmns'),
   kWipeTransitionType                  = FOUR_CHAR_CODE(‘smpt'),
   kIrisTransitionType                  = FOUR_CHAR_CODE(‘smp2'),
   kRadialTransitionType               = FOUR_CHAR_CODE(‘smp3'),
   kMatrixTransitionType               = FOUR_CHAR_CODE(‘smp4'),
   kCrossFadeTransitionType         = FOUR_CHAR_CODE(‘dslv'),
   kPushTransitionType                  = FOUR_CHAR_CODE(‘push')
};

Another thing this means is that we can use QuickTime video effects anywhere we might use a decompressor, not only in connection with QuickTime movies. We can just as easily apply a transition between two arbitrary images (perhaps contained in two offscreen graphics worlds). I've seen this capability used in applications that support QuickTime video effects as transitions between QuickTime VR nodes. The default behavior of QuickTime VR is simply to jump from one node to the next. It's much nicer to render some video effect, say a nice smooth dissolve, when moving from node to node.

In this article and the next, we're going to work with QuickTime video effects. We'll see how to create the fire movie shown in Figure 5 and how to apply a filter to a video track or image. We'll also see how to display and manage the effects parameters dialog box, which allows the user to select an effect and modify the parameters of that effect. Finally, we'll see how to apply an effect to only part of an existing movie and how to use effects as sources of sprite images.

Our sample application in these two articles is called QTEffects; its Test menu is shown in Figure 7.


Figure 7: The Test menu of QTEffects.

In this article, we'll see how to handle all these menu items except for the fourth (which happens to be grayed out) and the final two. We'll postpone consideration of those three items to our next article.

QuickTime Video Effects in Movies

It's extremely easy to add a video effect to a QuickTime movie. In the simplest case, where the effect lasts for the entire length of the movie, we just add an effects track to the movie. An effects track is a video track (of type VideoMediaType) whose media data is an effect description. An effect description is an atom container that indicates which effect to perform and which parameters, if any, to use when rendering the effect. The effect description also indicates which other tracks in the movie are to be used as the input sources for the effect. These are called the effect source tracks (or effect sources). A transition needs two source tracks; a filter needs one source track; a generator needs no source tracks. Figure 8 illustrates the general structure of the fire movie shown in Figure 5.


Figure 8: The structure of a zero-source effect movie

And Figure 9 illustrates the general structure of a movie that contains a two-source effect (perhaps the zig-zag transition shown in Figure 1).


Figure 9: The structure of a two-source effect movie

The source tracks for a video effect can be any tracks that have the visual media characteristic, including video tracks, sprite tracks, text tracks, and others. In particular, because an effects track is a video track, it too can be a source track for another effects track. This allows us to stack effects, so that the output of one effect is used as input for another effect. For example, we could set up a cross-fade transition from one video track to another, and then apply a film noise filter to the resulting images. Keep in mind, however, that some effects can use a significant amount of CPU power, so that stacking effects may result in movies that do not play smoothly in real time on slower machines.

As we'll see in greater detail later, we need to connect an effects track to its source tracks by setting up track references from the effects track to the source tracks. These references tell QuickTime where to get the data for the effects track. We also need to configure the effects track's input map, so that the effects track knows how to interpret the data it receives from the source tracks. The source tracks operate as modifier tracks, whose data is not presented directly to the user; rather, their data is used as input for the effects track. This is important, particularly when we want to apply an effect to only part of a source track. You might think that we could just construct an effects track with the appropriate start time and duration, as shown in Figure 10.


Figure 10: A filter applied to part of a video track (wrong)

But this won't work, since once we've created a track reference from the effects track to the video track and set the effects track's input map appropriately, the video track will send all of its data to the effects track, not just the data in the track segment that overlaps the effects track. To apply an effect to a part of a track, we can create another track that has the desired start time and duration and that references data in the video track. Then we use this new track segment as the source track for the effect, as shown in Figure 11. The new track segment doesn't contain a copy of the media data; instead, it contains references to the media data that already exists in the video track. So we don't increase the size of a movie file very much at all when we add effects to it.


Figure 11: A filter applied to part of a video track

All three of the tracks shown in Figure 11 are enabled; to prevent the original video track from covering up the effects track, we need to make sure that the effects track has a lower track layer than the video track. We'll see exactly how to do this in the next article, when we discuss applying effects to track segments.

It's worth mentioning that the QuickTime video effects architecture was originally designed to render effects in real time using software effects components (which, as we've seen, are image decompressor components). Recently, QuickTime 5 added support for hardware acceleration of effects rendering. This acceleration is used only when the user's machine has the appropriate hardware installed, and it occurs automatically (without any intervention by the effects movie creator or the playback application).

It's also worth mentioning that a video effect can have more than two sources. QuickTime 5 introduced a three-source effect, the traveling matte effect. In these articles, we'll always work with two or fewer sources, but our code can in fact handle up to three.

Effects Utilities

Before we begin creating effects movies, let's take a brief moment to define a couple of functions that will be useful throughout our effects code.

Creating a Sample Description

When we build an effects track, we need to pass AddMediaSample an image description that provides information about the effect. In the past, we've always created sample descriptions and image descriptions by calling NewHandleClear and then setting the fields of the structure appropriately. When we are working with effects, however, we should use the function MakeImageDescriptionForEffect, which allocates a handle to an image description and fills in some of its fields; it also attaches an image description extension to the end of the image description. This extension indicates that that image description applies to an effect. For most purposes this extension is ignored, but it's necessary when we want to create stacked effects.

MakeImageDescriptionForEffect was introduced in QuickTime 4.0; if we want our code to run also under versions 3.x, we can set the USES_MAKE_IMAGE_DESC_FOR_EFFECT compiler flag to 0. Listing 1 shows our definition of EffectsUtils_MakeSampleDescription, which we'll call quite a few times in QTEffects to create an image description for an effect.

Listing 1: Creating a sample description for an effect

ImageDescriptionHandle EffectsUtils_MakeSampleDescription 
         (OSType theEffectType, short theWidth, short theHeight)
{
   ImageDescriptionHandle      mySampleDesc = NULL;

#if USES_MAKE_IMAGE_DESC_FOR_EFFECT
   OSErr                              myErr = noErr;

   // create a new sample description
   myErr = MakeImageDescriptionForEffect(theEffectType, 
            &mySampleDesc);
   if (myErr != noErr)
      return(NULL);
#else
   // create a new sample description
   mySampleDesc = (ImageDescriptionHandle)
            NewHandleClear(sizeof(ImageDescription));
   if (mySampleDesc == NULL)
      return(NULL);

   // fill in the fields of the sample description
   (**mySampleDesc).cType = theEffectType;
   (**mySampleDesc).idSize = sizeof(ImageDescription);
   (**mySampleDesc).hRes = 72L << 16;
   (**mySampleDesc).vRes = 72L << 16;
   (**mySampleDesc).frameCount = 1;
   (**mySampleDesc).depth = 0;
   (**mySampleDesc).clutID = -1;
#endif

   (**mySampleDesc).vendor = kAppleManufacturer;
   (**mySampleDesc).temporalQuality = codecNormalQuality;
   (**mySampleDesc).spatialQuality = codecNormalQuality;
   (**mySampleDesc).width = theWidth;
   (**mySampleDesc).height = theHeight;

   return(mySampleDesc);
}

Notice that we need to set a few fields of the image description even if we call MakeImageDescriptionForEffect.

Creating an Effect Description

It's also useful to define a utility function to build an effect description. As we've learned, an effect description is an atom container that specifies an effect and its sources. Listing 2 shows the definition of our utility EffectsUtils_CreateEffectDescription. The essential step is to add an atom of type kParameterWhatName and ID kParameterWhatID whose data is the four-character code for the desired effect.

Listing 2: Creating an effect description

QTAtomContainer EffectsUtils_CreateEffectDescription 
            (OSType theEffectType, OSType theSourceName1, 
            OSType theSourceName2, OSType theSourceName3)
{
   QTAtomContainer      myEffectDesc = NULL;
   OSType                  myType = EndianU32_NtoB(theEffectType);
   OSErr                  myErr = noErr;

   // create a new, empty effect description
   myErr = QTNewAtomContainer(&myEffectDesc);
   if (myErr != noErr)
      goto bail;

   // create the effect ID atom
   myErr = QTInsertChild(myEffectDesc, kParentAtomIsContainer, 
            kParameterWhatName, kParameterWhatID, 0, 
            sizeof(myType), &myType, NULL);
   if (myErr != noErr)
      goto bail;

   // add the first source
   if (theSourceName1 != kSourceNoneName) {
      myType = EndianU32_NtoB(theSourceName1);
      myErr = QTInsertChild(myEffectDesc, 
            kParentAtomIsContainer, kEffectSourceName, 1, 0, 
            sizeof(myType), &myType, NULL);
      if (myErr != noErr)
         goto bail;
   }

   // add the second source
   if (theSourceName2 != kSourceNoneName) {
      myType = EndianU32_NtoB(theSourceName2);
      myErr = QTInsertChild(myEffectDesc, 
            kParentAtomIsContainer, kEffectSourceName, 2, 0, 
            sizeof(myType), &myType, NULL);
      if (myErr != noErr)
         goto bail;
   }

   // add the third source
   if (theSourceName3 != kSourceNoneName) {
      myType = EndianU32_NtoB(theSourceName3);
      myErr = QTInsertChild(myEffectDesc, 
            kParentAtomIsContainer, kEffectSourceName, 3, 0, 
            sizeof(myType), &myType, NULL);
   }

bail:
   return(myEffectDesc);
}

EffectsUtils_CreateEffectDescription builds an effect description with up to three source name atoms, of type kEffectSourceName. The data in these atoms is a source name, of type OSType. Source names are used to link the source tracks to the effects track. These names are arbitrary, but Apple recommends using names of the form ‘srcX', where X is an uppercase letter. In the file EffectsUtilities.h, we define these constants for our source names:

#define kSourceOneName                  FOUR_CHAR_CODE(‘srcA')
#define kSourceTwoName                  FOUR_CHAR_CODE(‘srcB')
#define kSourceThreeName               FOUR_CHAR_CODE(‘srcC')
#define kSourceNoneName                  FOUR_CHAR_CODE(‘srcZ')

When we call EffectsUtils_CreateEffectDescription, we'll pass the constant kSourceNoneName for any unused sources.

Getting an Effect Type

Sometimes we might get our hands on an effect description and need to know what kind of effect it describes. We can get this information by inspecting the data of the atom of type kParameterWhatName and ID kParameterWhatID that's inside that effect description. The function EffectsUtils_GetTypeFromEffectDescription defined in Listing 3 accomplishes this.

Listing 3: Getting the type of an effect

OSErr EffectsUtils_GetTypeFromEffectDescription 
         (QTAtomContainer theEffectDesc, OSType *theEffectType)
{
   QTAtom         myEffectAtom = 0;
   long            myEffectTypeSize = 0;
   Ptr            myEffectTypePtr = NULL;
   OSErr         myErr = noErr;

   if ((theEffectDesc == NULL) || (theEffectType == NULL))
      return(paramErr);

   myEffectAtom = QTFindChildByIndex(theEffectDesc, 
            kParentAtomIsContainer, kParameterWhatName, 
            kParameterWhatID, NULL);
   if (myEffectAtom != 0) {

      myErr = QTLockContainer(theEffectDesc);
      if (myErr != noErr)
         goto bail;

      myErr = QTGetAtomDataPtr(theEffectDesc, myEffectAtom, 
            &myEffectTypeSize, &myEffectTypePtr);
      if (myErr != noErr)
         goto bail;

      if (myEffectTypeSize != sizeof(OSType)) {
         myErr = paramErr;
         goto bail;
      }

      *theEffectType = *(OSType *)myEffectTypePtr;
      *theEffectType = EndianU32_BtoN(*theEffectType);

      myErr = QTUnlockContainer(theEffectDesc);
   }
   
bail:
   return(myErr);
}

Notice that we call QTLockContainer on the effect description, even though it isn't strictly necessary here. As we learned in a previous article, QTGetAtomDataPtr returns a pointer to the actual leaf atom data. We need to call QTLockContainer only when we make calls that might move memory; in this case, we're just reading a few bytes into a local variable, and this operation will not cause any memory movement. The calls to QTLockContainer and QTUnlockContainer are fairly lightweight, so we'll make them anyway.

Generators

Let's begin our hands-on work with QuickTime video effects by building a movie that uses a generator, or zero-source effect. In this case, we'll build the fire movie shown earlier in Figure 5. This movie has only one track, which is an effects track and which has only one media sample. We'll set the dimensions of the effects track and its duration using some hard-coded values:

#define kDefaultTrackWidth            160
#define kDefaultTrackHeight            120
#define kEffectMovieDuration            (10 * kOneSecond)

We create the new movie file by calling CreateMovieFile, and then we create a new effects track and media like this:

myEffectTrack = NewMovieTrack(myMovie, 
            IntToFixed(kDefaultTrackWidth), 
            IntToFixed(kDefaultTrackHeight), kNoVolume);

myEffectMedia = NewTrackMedia(myEffectTrack, VideoMediaType, 
            kOneSecond, NULL, 0);

Now we are ready to use the utility functions we defined in the previous section. We create the sample description and the effect description:

mySampleDesc = EffectsUtils_MakeSampleDescription
            (kFireCodecType, kDefaultTrackWidth, 
            kDefaultTrackHeight);

myEffectDesc = EffectsUtils_CreateEffectDescription
            (kFireCodecType, kSourceNoneName, kSourceNoneName, 
            kSourceNoneName);

The fire effect takes no sources, so we pass the constant kSourceNoneName for all three source name parameters.

Now we are essentially done; we add the effect description as a media sample using the usual media-editing song-and-dance (BeginMediaEdits, AddMediaSample, EndMediaEdits, and InsertMediaIntoTrack). The key step is the call to AddMediaSample:

myErr = AddMediaSample(myEffectMedia, myEffectDesc, 0, 
            GetHandleSize(myEffectDesc), kEffectMovieDuration, 
            (SampleDescriptionHandle)mySampleDesc, 1, 0, 
            &mySampleTime);

It's really just that easy to create a zero-source effects movie. Listing 4 shows the complete definition of QTEffects_MakeFireMovie, which we call in response to the "Make Fire Movie..." menu item.

Listing 4: Creating a zero-source effects movie

void QTEffects_MakeFireMovie (void)
{
   FSSpec                  myFile;
   Boolean               myIsSelected = false;
   Boolean               myIsReplacing = false;
   StringPtr             myPrompt = 
   QTUtils_ConvertCToPascalString(kEffectsSaveMoviePrompt);
   StringPtr             myFileName = 
   QTUtils_ConvertCToPascalString(kEffectsFireMovieFileName);
   Movie                  myMovie = NULL;
   short                  myMovieRefNum = kInvalidFileRefNum;
   short                  myResID = movieInDataForkResID;
   Track                  myEffectTrack = NULL;
   Media                  myEffectMedia = NULL;
   QTAtomContainer      myEffectDesc = NULL;
   ImageDescriptionHandle
                           mySampleDesc = NULL;
   TimeValue            mySampleTime = 0;
   long                     myFlags = createMovieFileDeleteCurFile
                                  | createMovieFileDontCreateResFile;
   OSType                  myType = FOUR_CHAR_CODE(‘none');
   OSErr                  myErr = noErr;

   // ask the user for the name of the new movie file
   QTFrame_PutFile(myPrompt, myFileName, &myFile, 
            &myIsSelected, &myIsReplacing);
   if (!myIsSelected)
      goto bail;            // deal with user cancelling

   // create a movie file for the destination movie
   myErr = CreateMovieFile(&myFile, sigMoviePlayer, 
            smSystemScript, myFlags, &myMovieRefNum, &myMovie);
   if (myErr != noErr)
      goto bail;

   // select the "no controller" movie controller
   myType = EndianU32_NtoB(myType);
   SetUserDataItem(GetMovieUserData(myMovie), &myType, 
            sizeof(myType), kUserDataMovieControllerType, 1);

   // create the effects track
   myEffectTrack = NewMovieTrack(myMovie, 
            IntToFixed(kDefaultTrackWidth), 
            IntToFixed(kDefaultTrackHeight), kNoVolume);
   if (myEffectTrack == NULL)
      goto bail;

   myEffectMedia = NewTrackMedia(myEffectTrack, 
            VideoMediaType, kOneSecond, NULL, 0);
   if (myEffectMedia == NULL)
      goto bail;

   // create the sample description
   mySampleDesc = EffectsUtils_MakeSampleDescription
            (kFireCodecType, kDefaultTrackWidth, 
            kDefaultTrackHeight);
   if (mySampleDesc == NULL)
      goto bail;

   // create the effect description
   myEffectDesc = EffectsUtils_CreateEffectDescription
            (kFireCodecType, kSourceNoneName, kSourceNoneName, 
            kSourceNoneName);
   if (myEffectDesc == NULL)
      goto bail;

   // add the effect description as a sample to the effects track media
   myErr = BeginMediaEdits(myEffectMedia);
   if (myErr != noErr)
      goto bail;

   myErr = AddMediaSample(myEffectMedia, myEffectDesc, 0, 
            GetHandleSize(myEffectDesc), kEffectMovieDuration, 
            (SampleDescriptionHandle)mySampleDesc, 1, 0, 
            &mySampleTime);
   if (myErr != noErr)
      goto bail;

   myErr = EndMediaEdits(myEffectMedia);
   if (myErr != noErr)
      goto bail;

   myErr = InsertMediaIntoTrack(myEffectTrack, 0, 
            mySampleTime, kEffectMovieDuration, fixed1);
   if (myErr != noErr)
      goto bail;

   AddMovieResource(myMovie, myMovieRefNum, &myResID, NULL);

bail:
   if (myMovieRefNum != kInvalidFileRefNum)
      CloseMovieFile(myMovieRefNum);

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

   if (myEffectDesc != NULL)
      QTDisposeAtomContainer(myEffectDesc);

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

   free(myPrompt);
   free(myFileName);

   return;
}

With the fire effect, the duration is fairly arbitrary. Any non-zero duration would produce the same visual output.

Filters

It's just about as easy to add a filter to a video track in an existing movie — for instance, to handle the "Add Film Noise To Movie" menu item. We add an effects track, whose media data consists of an effect description. This time, however, we need to specify a source name in the effect description. We'll call our utility EffectsUtils_CreateEffectDescription like this, passing kSourceOneName as the first source name parameter:

myEffectDesc = EffectsUtils_CreateEffectDescription
            (kFilmNoiseImageFilterType, kSourceOneName, 
            kSourceNoneName, kSourceNoneName);

We also need to create an input map for the effects track, which specifies which track is to be used as the effect source. Listing 5 shows the code we use to create, configure, and set the input map.

Listing 5: Creating an input map for an effects track

// create the input map and add references for the first effects track
myErr = QTNewAtomContainer(&myInputMap);
if (myErr != noErr)
   goto bail;

myErr = EffectsUtils_AddTrackReferenceToInputMap
            (myInputMap, myTrack, mySrcTrack, kSourceOneName);
if (myErr != noErr)
   goto bail;

// add the input map to the effects track
myErr = SetMediaInputMap(myMedia, myInputMap);

An input map for an effects track is an atom container that holds one atom of type kTrackModifierInput for each source track that is sending data to the effects track. The ID of each such atom must be set to the reference index returned by AddTrackReference when the track reference between the effects track and that source track is created. Each atom of type kTrackModifierInput must contain at least two child atoms. One of these children is of type kTrackModifierType and specifies the kind of data the target track is going to receive from the source track; in the case of an effects track, the type of the modifier track input is kTrackModifierTypeImage. The second child atom in an input map entry atom specifies the name of the source track and is of type kEffectDataSourceType; the data in this atom is of type OSType. Figure 12 shows the structure of the input map we'll use to add the film noise filter to a video track.


Figure 12: The structure of an input map for an effects track

Listing 6 shows our definition of the EffectsUtils_AddTrackReferenceToInputMap function, which we use to add the appropriate children to an existing input map for an effects track.

Listing 6: Adding track references to an input map

OSErr EffectsUtils_AddTrackReferenceToInputMap 
            (QTAtomContainer theInputMap, Track theTrack, 
            Track theSrcTrack, OSType theSrcName)
{
   QTAtom            myInputAtom;
   long               myRefIndex;
   OSType            myType;
   OSErr            myErr = noErr;

   myErr = AddTrackReference(theTrack, theSrcTrack, 
            kTrackReferenceModifier, &myRefIndex);
   if (myErr != noErr)
      goto bail;

   // add a reference atom to the input map
   myErr = QTInsertChild(theInputMap, kParentAtomIsContainer, 
            kTrackModifierInput, myRefIndex, 0, 0, NULL, 
            &myInputAtom);
   if (myErr != noErr)
      goto bail;

   // add two child atoms to the parent reference atom
   myType = EndianU32_NtoB(kTrackModifierTypeImage);
   myErr = QTInsertChild(theInputMap, myInputAtom, 
            kTrackModifierType, 1, 0, sizeof(myType), &myType, 
            NULL);
   if (myErr != noErr)
      goto bail;

   myType = EndianU32_NtoB(theSrcName);
   myErr = QTInsertChild(theInputMap, myInputAtom, 
            kEffectDataSourceType, 1, 0, sizeof(myType), &myType, 
            NULL);

bail:
   return(myErr);
}

If EffectsUtils_AddTrackReferenceToInputMap seems familiar, that's because we've already bumped into similar functions (for instance, when we worked with sprite image overrides in "An Extremely Goofy Movie" in MacTech, April 2001).

Transitions

Adding a transition to a movie with two video tracks is really no more complicated than adding a filter to a movie with one video track. We pass kSourceTwoName as the second source name parameter when calling EffectsUtils_CreateEffectDescription, and we call EffectsUtils_AddTrackReferenceToInputMap a second time, to create a track reference between the effects track and the second source video track. For fun, let's see how to recreate our appearing-penguin movie using QuickTime video effects. We'll also take this opportunity to play a little more with our favorite Image Compression Manager functions GetMaxCompressionSize and CompressImage.

Given what we've learned so far, all we really need to do is create two video tracks to serve as the source tracks for a cross-fade transition. The first video track is an all-white frame that lasts for the duration of the movie; the second video track is the fully-opaque penguin picture, also lasting for the duration of the movie. As always, we'll set the duration of the movie to 10 seconds, this time using the constant kEffectMovieDuration. Then we'll add the effects track to the movie, specifying a cross-fade from the first source track to the second.

The two images that we'll use to create our video tracks are stored in our application's resource fork, in two ‘PICT' resources with these IDs:

#define kWhiteRectID                  129
#define kPenguinPictID               128

So we need to read each image and create a video track of the desired length. We'll split our work into two parts. First we'll write a utility, EffectsUtils_GetPictResourceAsGWorld, that reads a ‘PICT' resource and draws it into an offscreen graphics world. Then we'll write another utility, EffectsUtils_AddVideoTrackFromGWorld, that creates a video track from the image in an offscreen graphics world. Once we've got these two utilities, we can create the two video tracks using the code shown in Listing 7.

Listing 7: Creating two video tracks from two ‘PICT' resources

myErr = EffectsUtils_GetPictResourceAsGWorld(kWhiteRectID, 
            kPenguinTrackWidth, kPenguinTrackHeight, 0, &myGW1);
if (myErr != noErr)
   goto bail;

myErr = EffectsUtils_GetPictResourceAsGWorld(kPenguinPictID, 
            kPenguinTrackWidth, kPenguinTrackHeight, 0, &myGW2);
if (myErr != noErr)
   goto bail;

myErr = EffectsUtils_AddVideoTrackFromGWorld(&myMovie, myGW1, 
            &mySrc1Track, 0, kEffectMovieDuration, 
            kPenguinTrackWidth, kPenguinTrackHeight);
if (myErr != noErr)
   goto bail;

myErr = EffectsUtils_AddVideoTrackFromGWorld(&myMovie, myGW2, 
            &mySrc2Track, 0, kEffectMovieDuration, 
            kPenguinTrackWidth, kPenguinTrackHeight);

To create an offscreen graphics world that holds the image stored in a ‘PICT' resource, we get the picture data from the resource (by calling GetPicture), create an offscreen graphics world of the required size (by calling QTNewGWorld), and then draw the picture data into that new graphics world (by calling DrawPicture). Before drawing into our graphics world, however, we need to call LockPixels to lock the offscreen pixel image. Listing 8 shows our definition of EffectsUtils_GetPictResourceAsGWorld.

Listing 8: Creating a graphics world from a ‘PICT' resource

OSErr EffectsUtils_GetPictResourceAsGWorld (short theResID, 
            short theWidth, short theHeight, short theDepth, 
            GWorldPtr *theGW)
{
   PicHandle         myHandle = NULL;
   PixMapHandle      myPixMap = NULL;
   CGrafPtr            mySavedPort;
   GDHandle            mySavedDevice;
   Rect                  myRect;
   OSErr               myErr = noErr;

   // get the current drawing environment
   GetGWorld(&mySavedPort, &mySavedDevice);

   // read the specified ‘PICT' resource from the application's resource file
   myHandle = GetPicture(theResID);
   if (myHandle == NULL) {
      myErr = ResError();
      if (myErr == noErr)
         myErr = resNotFound;
      goto bail;
   }

   // set the size of the GWorld
   MacSetRect(&myRect, 0, 0, theWidth, theHeight);

   // allocate a new GWorld
   myErr = QTNewGWorld(theGW, theDepth, &myRect, NULL, NULL, 
            kICMTempThenAppMemory);
   if (myErr != noErr)
      goto bail;

   SetGWorld(*theGW, NULL);

   // get a handle to the offscreen pixel image and lock it
   myPixMap = GetGWorldPixMap(*theGW);
   LockPixels(myPixMap);

   EraseRect(&myRect);
   DrawPicture(myHandle, &myRect);

   if (myPixMap != NULL)
      UnlockPixels(myPixMap);

bail:
   // restore the previous port and device
   SetGWorld(mySavedPort, mySavedDevice);

   if (myHandle != NULL)
      ReleaseResource((Handle)myHandle);

   return(myErr);
}

Now we want to create a video track in a movie that lasts for a specified duration and whose data is the image contained in an offscreen graphics world. Listing 9 shows the complete definition of EffectsUtils_AddVideoTrackFromGWorld. This function is a tad long, since we need to create a new track and add a media sample to it; we also need to call GetMaxCompressionSize and CompressImage to compress the data in the original graphics world to reduce the size of the resulting new movie track. Notice that we return the track identifier to the caller through the theSourceTrack parameter. (For more details on calling GetMaxCompressionSize and CompressImage, see "Making Movies" in MacTech, June 2000.)

Listing 9: Creating a video track from a graphics world

OSErr EffectsUtils_AddVideoTrackFromGWorld (Movie *theMovie, 
            GWorldPtr theGW, Track *theSourceTrack, 
            long theStartTime, TimeValue theDuration, 
            short theWidth, short theHeight)
{
   Media               myMedia;
   ImageDescriptionHandle
                        mySampleDesc = NULL;
   Rect                  myRect;
   Rect                  myRect2;
   Rect                  myRect3;
   long                  mySize;
   Handle               myData = NULL;
   Ptr                  myDataPtr = NULL;
   GWorldPtr         myGWorld = NULL;
   CGrafPtr          mySavedPort = NULL;
   GDHandle          mySavedGDevice = NULL;
   PicHandle         myHandle = NULL;
   PixMapHandle      mySrcPixMap = NULL;
   PixMapHandle      myDstPixMap = NULL;
   OSErr               myErr = noErr;

   // get the current port and device
   GetGWorld(&mySavedPort, &mySavedGDevice);

   // create a video track in the movie
   *theSourceTrack = NewMovieTrack(*theMovie, 
            IntToFixed(theWidth), IntToFixed(theHeight), 
            kNoVolume);
   if (theSourceTrack == NULL)
      goto bail;

   myMedia = NewTrackMedia(*theSourceTrack, VideoMediaType, 
            kVideoTrackTimeScale, NULL, 0);
   if (myMedia == NULL)
      goto bail;

   // get the rectangle for the movie
   GetMovieBox(*theMovie, &myRect);

   // begin editing the new track
   myErr = BeginMediaEdits(myMedia);
   if (myErr != noErr)
      goto bail;

   // create a new GWorld; we draw the picture into this GWorld and then compress it
   // (note that we are creating a picture with the maximum bit depth)
   myErr = NewGWorld(&myGWorld, 32, &myRect, NULL, NULL, 0L);
   if (myErr != noErr)
      goto bail;

   mySrcPixMap = GetGWorldPixMap(theGW);
   myDstPixMap = GetGWorldPixMap(myGWorld);
   LockPixels(myDstPixMap);

   // create a new image description; CompressImage will fill in the fields of this structure
   mySampleDesc = (ImageDescriptionHandle)NewHandle(4);

   SetGWorld(myGWorld, NULL);
#if TARGET_OS_MAC
   GetPortBounds(theGW, &myRect2);
   GetPortBounds(myGWorld, &myRect3);
#endif
#if TARGET_OS_WIN32
   myRect2 = theGW->portRect;
   myRect3 = myGWorld->portRect;
#endif

   // copy the image from the specified GWorld into the new GWorld
   CopyBits((BitMapPtr)*mySrcPixMap, (BitMapPtr)*myDstPixMap, 
            &myRect2, &myRect3, srcCopy, NULL);

   // restore the original port and device
   SetGWorld(mySavedPort, mySavedGDevice);

   myErr = GetMaxCompressionSize(myDstPixMap, &myRect, 0, 
            codecNormalQuality, kJPEGCodecType, anyCodec, 
            &mySize);
   if (myErr != noErr)
      goto bail;

   myData = NewHandle(mySize);
   if (myData == NULL)
      goto bail;

   HLockHi(myData);
#if TARGET_CPU_68K
   myDataPtr = StripAddress(*myData);
#else
   myDataPtr = *myData;
#endif
   myErr = CompressImage(myDstPixMap, &myRect, 
            codecNormalQuality, kJPEGCodecType, mySampleDesc, 
            myDataPtr);
   if (myErr != noErr)
      goto bail;

   myErr = AddMediaSample(myMedia, myData, 0, 
            (**mySampleDesc).dataSize, theDuration, 
            (SampleDescriptionHandle)mySampleDesc, 1, 0, NULL);
   if (myErr != noErr)
      goto bail;

   myErr = EndMediaEdits(myMedia);
   if (myErr != noErr)
      goto bail;

   myErr = InsertMediaIntoTrack(*theSourceTrack, theStartTime, 
            0, GetMediaDuration(myMedia), fixed1);

bail:
   // restore the original port and device
   SetGWorld(mySavedPort, mySavedGDevice);

   if (myData != NULL) {
      HUnlock(myData);
      DisposeHandle(myData);
   }

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

   if (myDstPixMap != NULL)
      UnlockPixels(myDstPixMap);

   if (myGWorld != NULL)
      DisposeGWorld(myGWorld);

   return(myErr);
}

See the file QTEffects.c for the complete listing of QTEffects_MakePenguinMovie, which we call in response to the "Make Fade-In Movie..." menu item. It's really just a longer version of QTEffects_MakeFireMovie (Listing 4) that incorporates the extra code in Listing 7.

Before we move on, it's worth reflecting on the fact that the movie created by QTEffects_MakePenguinMovie is now the fourth version of our penguin movie. We first created an appearing-penguin movie in "Making Movies" (cited earlier), where we built a video track with 100 frames, each frame having slightly more opacity than the previous. The total size of the movie file was about 470 kilobytes. In "A Goofy Movie" (March 2001), we created a second version of the penguin movie, using a sprite image in a key frame with zero opacity and 99 override frames that gradually increased the level of opacity of the sprite image. This version of the penguin movie file was only about 36 kilobytes. In the very next article ("An Extremely Goofy Movie", April 2001), we reworked that sprite version using a tween track to change the graphics mode of the penguin sprite image. The total size of that version was about 28 kilobytes. Finally, in this article, we've created a movie file that uses the cross-fade transition to blend from a totally white frame to the penguin image; this version is only about 10 kilobytes (most of which is occupied by the compressed penguin image).

One moral of this story is obvious: there is more than one way, using QuickTime, to skin a cat (or fade in a penguin). The first version uses a single video track. The second version uses a single sprite track. The third version uses a sprite track and a tween track. This fourth version uses two video tracks (the sources) and an effects track. None of these versions is inherently any better or worse than any of the others (though it's hard not to choke on the beefy size of the first version). Which of them we employ for a specific purpose depends on various factors. For instance, if we want the smallest file size, we would use the effects version; if we want to be able to add wiring to the movie, then a sprite version is preferable.

Effects Parameters

So far, our effect descriptions contain only a single kParameterWhatName atom and zero or more kEffectSourceName atoms. All of the built-in QuickTime video effects also support effects parameters, which specify additional information about the effect. For instance, the fire effect supports four parameters, which indicate the desired spread rate, sputter rate, water rate, and restart rate for the fire. The sputter rate (or decay rate) specifies how quickly the flames die down as they move upward. Larger values of the decay rate result in very low flames. (See Apple's effects documentation, cited at the end of this article, for descriptions of the other parameters.)

We specify a value for an effects parameter by inserting a parameter atom into the effect description. For instance, once we've created an effect description for the fire effect (by calling EffectsUtils_CreateEffectDescription), we can add a parameter atom to set the decay rate to 11, like this:

myRate = EndianS32_NtoB(11);
myErr = QTInsertChild(myEffectDesc, kParentAtomIsContainer, 
            FOUR_CHAR_CODE(‘decy'), 1, 0, sizeof(myRate), 
            &myRate, NULL);

The type of the parameter atom indicates the kind of parameter we are setting, and the data in the parameter atom is the desired value for that parameter.

Not all parameters are optional. With the SMPTE effects, the effect type indicates which of the four general classes of SMPTE effects (wipe, iris, radial, or matrix) the effect belongs to. To select a specific effect from those classes, we need to add a wipe ID parameter atom to the effect description. For instance, to specify the horizontal barn zig-zag effect (shown in Figure 1), we could execute this code:

myWipe = EndianS32_NtoB(kHorizontalBarnZigZagWipe);
myErr = QTInsertChild(myEffectDesc, kParentAtomIsContainer, 
            FOUR_CHAR_CODE(‘wpID'), 1, 0, sizeof(myWipe), 
            &myWipe, NULL);

The constant kHorizontalBarnZigZagWipe and others for the remaining SMPTE effects are defined in the file ImageCodec.h.

Using the Effects Parameters Dialog Box

When we build an effects movie, it would be nice to provide the user with an interactive way to set any of the optional effects parameters. To this end, the QuickTime video effects architecture includes support for displaying and managing the effects parameters dialog box, shown in Figure 13. (Sometimes this dialog box is also called the standard parameters dialog box.) As you can see, this dialog box includes a list of available effects (in this case, just the one-source effects) and some controls allowing the user to modify the parameters associated with the selected effect. It also includes a preview pane holding a poster image that is dynamically updated to reflect the current parameter settings.


Figure 13: The effect parameters dialog box/

QuickTime provides the QTCreateStandardParameterDialog function for displaying the effects parameters dialog box, which is declared essentially like this:

OSErr QTCreateStandardParameterDialog (
            QTAtomContainer effectList,
            QTAtomContainer parameters,
            QTParameterDialogOptions dialogOptions,
            QTParameterDialog *createdDialog);

The effectList parameter specifies which effects we want to appear in the list on the left side of the dialog box. QuickTime also provides a function that we can use to get a list of all effects that take a certain number of sources:

myErr = QTGetEffectsList(&gEffectList, theSpecCount, 
            theSpecCount, 0);

The second and third parameters to QTGetEffectsList specify the minimum and maximum number of sources; in this case, we set both of those parameters to the number of sources selected by the user. QTGetEffectsList returns, through its first parameter, an atom container that holds at least two atoms for every available effect that has the requisite number of sources. These two atoms specify the name and type of the effect. The atoms are sorted alphabetically by effect name. (That is, the atom of type kEffectNameAtom with ID 1 is the first name alphabetically; the atom of type kEffectNameAtom with ID 2 is next; and so forth.)

The second parameter to QTCreateStandardParameterDialog is an atom container in which information will be returned to us when the user finishes selecting an effect and its parameters. We need to allocate that atom container ourselves, like so:

myErr = QTNewAtomContainer(&gEffectDesc);

myErr = QTCreateStandardParameterDialog(gEffectList, 
            gEffectDesc, 0, &gEffectsDialog);

The third parameter specifies some flags (which we set to 0 here) and the fourth parameter is the location of a variable of type QTParameterDialog; if QTCreateStandardParameterDialog completes successfully, it returns in that location an identifier for the effects parameters dialog box. We'll use that identifier in subsequent operations on the dialog box.

Setting the Poster Images

After we call QTCreateStandardParameterDialog, the effects parameters dialog box is not actually displayed on the screen until the dialog box receives an event. (As we'll see shortly, we pass events to the dialog box by calling QTIsStandardParameterDialogEvent.) This delay gives us an opportunity to do any necessary configuration in the dialog box before the user actually sees it. The main thing we want to do is set the poster image or images displayed in the box.

We set a poster image by calling the QTStandardParameterDialogDoAction function, which is declared essentially like this:

OSErr QTStandardParameterDialogDoAction (
            QTParameterDialog createdDialog, long action,
            void *params);

The action parameter specifies which action we want to perform on the dialog box. In QTEffects, we will use these three actions:

enum {
   pdActionConfirmDialog                  = 1,
   pdActionSetPreviewPicture            = 6,
   pdActionModelessCallback            = 12
};

To set the preview image, we use the pdActionSetPreviewPicture action, in which case the params parameter is a pointer to a parameter dialog box preview record, declared like this:

struct QTParamPreviewRecord {
   long                           sourceID;
   PicHandle                  sourcePicture;
};

The sourcePicture field contains a picture handle for the preview image, which must not be disposed until the dialog box is dismissed. The sourceID field indicates the index of the image. A filter should have one preview image with this field set to 1, and a transition should have two preview images with this field set to 1 and 2. Listing 10 shows how we would set the preview image for a filter.

Listing 10: Setting a preview image

if (mySrcTrack != NULL) {
   gPosterA = GetTrackPict(mySrcTrack, 
            GetMoviePosterTime(mySrcTrack));
   if (gPosterA != NULL) {
      QTParamPreviewRecord         myPreviewRecord;

      myPreviewRecord.sourcePicture = gPosterA;
      myPreviewRecord.sourceID = 1;
      myErr = QTStandardParameterDialogDoAction(gEffectsDialog, 
            pdActionSetPreviewPicture, &myPreviewRecord);
   }
}

QuickTime provides a number of other selectors for customizing the effects parameters dialog box and its operation. For instance, to set a custom title on the dialog box, we can use the pdActionSetDialogTitle action selector, like this:

StringPtr myPtr = QTUtils_ConvertCToPascalString(kMyTitle);

myErr = QTStandardParameterDialogDoAction(gEffectsDialog, 
            pdActionSetDialogTitle, myPtr);
free(myPtr);

See Apple's effects documentation for a complete list of the action selectors supported by QTStandardParameterDialogDoAction.

Handling Events in the Effects Parameters Dialog Box

Once we've configured the effects parameters dialog box to our liking, we need to start sending events to it so that it is displayed on the screen and the user can interact with it. We use the QTIsStandardParameterDialogEvent function to send events to that dialog box, like this:

myErr = QTIsStandardParameterDialogEvent(theEvent, 
            gEffectsDialog);

QTIsStandardParameterDialogEvent determines whether the specified event is meant for the effects parameters dialog box (rather in the same way that IsDialogEvent determines whether an event is meant for a typical dialog box). If the event does apply to that dialog box, it's handled; in any case, QTIsStandardParameterDialogEvent returns a result code to its caller that indicates what action, if any, it took. We need to inspect that result code and react accordingly. Currently, QTIsStandardParameterDialogEvent returns one of four result codes:

  • If codecParameterDialogConfirm is returned, the user has clicked the OK button; in this case, we need to tell QuickTime to fill the effect description we earlier passed to QTCreateStandardParameterDialog with atoms that reflect the user's selections in the dialog box. Then we should close the dialog box and use the information in that effects description.
  • If userCanceledErr is returned, the user has clicked the Cancel button in the dialog box. In this case, we should close the dialog box and perform any necessary clean-up operations.
  • If noErr is returned, the event was completely handled by the effects parameters dialog box code; we should proceed with further event processing.
  • If featureUnsupported is returned, the event was not handled by the effects parameters dialog box code; we should allow the event to be processed by our application normally.

Listing 11 shows our definition of the QTEffects_HandleEffectsDialogEvents function, which we use to send events to the effect parameters dialog box and respond appropriately.

Listing 11: Handling dialog events

Boolean QTEffects_HandleEffectsDialogEvents 
            (EventRecord *theEvent, DialogItemIndex theItemHit)
{
#pragma unused(theItemHit)
   Boolean      isHandled = false;
   OSErr         myErr = noErr;

   // pass the event to the standard effects parameters dialog box handler
   myErr = QTIsStandardParameterDialogEvent(theEvent, 
            gEffectsDialog);

   // the result from QTIsStandardParameterDialogEvent tells us how to respond next
   switch (myErr) {

      case codecParameterDialogConfirm:
      case userCanceledErr:
         // the user clicked the OK or Cancel button; 
         // dismiss the dialog box and respond accordingly
         gDoneWithDialog = true;

         if (myErr == codecParameterDialogConfirm)
            QTStandardParameterDialogDoAction(gEffectsDialog, 
            pdActionConfirmDialog, NULL);
         QTDismissStandardParameterDialog(gEffectsDialog);
         gEffectsDialog = 0L;
         QTEffects_RespondToDialogSelection(myErr);
         isHandled = true;
         break;

      case noErr:
         // the event was completely handled by QTIsStandardParameterDialogEvent
         isHandled = true;
         break;

      case featureUnsupported:
         // the event was not handled by QTIsStandardParameterDialogEvent;
         // let the event be processed normally
         isHandled = false;
         break;

      default:
         // the event was not handled by QTIsStandardParameterDialogEvent;
         // do not let the event be processed normally
         isHandled = true;
         break;
   }

   return(isHandled);
}

Notice that the code for the codecParameterDialogConfirm result code calls QTStandardParameterDialogDoAction with the pdActionConfirmDialog action parameter; this fills in the effect description with the current values in the dialog box. That code also calls QTDismissStandardParameterDialog to close the dialog box and the application function QTEffects_RespondToDialogSelection to respond to the user's selection. We won't consider the QTEffects_RespondToDialogSelection function in this article, since it pretty much reprises code we've already seen to build an effects movie. That function does, however, contain some important clean-up code, shown in Listing 12.

Listing 12: Cleaning up after the dialog box is closed

// standard parameter box has been dismissed; first do any necessary clean-up
gEffectsDialog = 0L;

// we're finished with the effect list and movie posters
if (gEffectList != NULL)
   QTDisposeAtomContainer(gEffectList);

if (gPosterA != NULL)
   KillPicture(gPosterA);

if (gPosterB != NULL)
   KillPicture(gPosterB);

Sending Events to the Effects Parameters Dialog Box

Now we know how to send events to the effects parameter dialog box and how to respond to the result codes that are returned to us. But when should we call QTEffects_HandleEffectsDialogEvents in our application code? On Macintosh systems, this is pretty easy, since our basic Macintosh application framework calls the function QTApp_HandleEvent for every event it receives from WaitNextEvent. Our application can inspect the gEffectsDialog global variable to see whether the effects parameter dialog box is currently displayed; if it is, we'll just call QTEffects_HandleEffectsDialogEvents, as shown in Listing 13.

Listing 13: Looking for events for the effects parameters dialog box

Boolean QTApp_HandleEvent (EventRecord *theEvent)
{
   Boolean      isHandled = false;

   // see if the event is meant for the effects parameter dialog box
   if (gEffectsDialog != 0L)
      isHandled = QTEffects_HandleEffectsDialogEvents(theEvent, 
            0);

   return(isHandled);
}

On Windows, things are a bit trickier here. In our Windows application framework, QTApp_HandleEvent is called only when a movie window is open. So we can't rely on QTApp_HandleEvent to trigger the QTEffects_HandleEffectsDialogEvents function. Instead, we can use SetModelessDialogCallbackProc to install a callback function to handle Windows messages that apply to the effects parameters dialog box. (This function works equally well with modal dialog boxes, so don't worry about the name.) We'll call SetModelessDialogCallbackProc like this:

SetModelessDialogCallbackProc(FrontWindow(), 
      (QTModelessCallbackUPP)QTEffects_EffectsDialogCallback);

The specified callback procedure, QTEffects_EffectsDialogCallback, is called by QTML when it's handling events in dialog boxes. When our callback function is executed, QTML has already done any control tracking for controls in the dialog box. If a control has been selected, its ID is passed to us in the theItemHit parameter. Listing 14 shows our definition of QTEffects_EffectsDialogCallback.

Listing 14: Handling events for the effects parameters dialog box

static void QTEffects_EffectsDialogCallback 
            (EventRecord *theEvent, DialogRef theDialog, 
            DialogItemIndex theItemHit)
{
   QTParamDialogEventRecord   myRecord;

   myRecord.theEvent = theEvent;
   myRecord.whichDialog = theDialog;
   myRecord.itemHit = theItemHit;

   if (gEffectsDialog != 0L) {
      QTStandardParameterDialogDoAction(gEffectsDialog, 
            pdActionModelessCallback, &myRecord);

      // see if the event is meant for the effects parameters dialog box
      QTEffects_HandleEffectsDialogEvents(theEvent, 
            theItemHit);
   }
}

As you can see, we pass the event to QTEffects_HandleEffectsDialogEvents. (We also pass the index of the item hit, but it's ignored by that function.) We also call QTStandardParameterDialogDoAction, this time with the action pdActionModelessCallback. This is some magic that ensures that QTML properly updates the dialog box and its controls.

One last "gotcha" on Windows: we need to make sure that idle events are sent to the dialog box, so that it can run the effect in the preview pane. To accomplish this, we attach a custom window procedure to the dialog box by calling QTMLSetWindowWndProc, like this:

QTMLSetWindowWndProc(FrontWindow(), 
            QTEffects_CustomDialogWndProc);

QTEffects_CustomDialogWndProc, defined in Listing 15, is called whenever the dialog box receives a message

Listing 15: Handling messages for the effects parameters dialog box

LRESULT CALLBACK QTEffects_CustomDialogWndProc (HWND theWnd, 
            UINT theMessage, UINT wParam, LONG lParam)
{
   EventRecord         myEvent = {0};

   if (!gDoneWithDialog && (theMessage == 0x7FFF))
      QTEffects_EffectsDialogCallback(&myEvent, 
            GetNativeWindowPort(theWnd), 0);

   return(DefWindowProc(theWnd, theMessage, wParam, lParam));
}

As you can see, QTEffects_CustomDialogWndProc looks for messages of the type 0x7FFF (which is a special message produced by QTML to simulate Macintosh idle events); when it finds one, and if the dialog box is still active, it calls the function QTEffects_EffectsDialogCallback with an event record for an idle event.

Effects Parameter Files

Notice that the effects parameters dialog box in Figure 13 contains two buttons, labeled "Save..." and "Load...". These buttons allow the user to save the effects parameters currently displayed in the dialog box and to reload a saved set of parameters. For various purposes, it might be useful to perform these actions programmatically. For instance, once the user has selected a set of parameters for an effect, our application might want to save them into a file, whence we can retrieve them the next time the application is run. The format of these files is publicly defined and is indeed quite easy to read and write.

An effects parameter file is a file that specifies an effect and zero or more of its parameters; it may also specify the poster picture that appears in the effects parameters dialog box. An effects parameter file is organized as a series of "classic" atoms. Currently three kinds of atoms are included in one of these files:

  • An atom of type ‘qtfx' (required). The atom data is an atom container that holds information about the effect type and parameters. In other words, the atom data is an effect description.
  • An atom of type ‘pnot' (optional). The atom data is organized as a preview resource record (of type PreviewResourceRecord). This atom specifies the type and index of some other atom, which contains the actual poster data. Usually the other atom is of type ‘PICT'.
  • An atom of type ‘PICT' (optional). The atom data is a picture that's used as the poster image in the effects parameters dialog box.

Other atoms may be included in an effects parameter file; applications that aren't expecting other atoms should be smart enough to skip them. By convention, an effects parameter file has the file extension ‘.qfx'; on Macintosh systems, the file type is ‘qtfx'.

Currently, QuickTime does not provide any functions for reading or writing effects parameter files, but based on what we've learned hitherto (especially in "The Atomic Café" in MacTech, September 2000), we can easily write our own. Listing 16 shows a simple routine that we can use to open an effects parameter file and read the data of the ‘qtfx' atom it contains.

Listing 16: Getting an effect description from an effects parameter file

QTAtomContainer EffectsUtils_GetEffectDescFromQFXFile 
            (FSSpec *theFSSpec)
{
   Handle         myEffectDesc = NULL;
   short         myRefNum = 0;
   long            mySize = 0L;
   OSType         myType = 0L;
   long            myAtomHeader[2];
   OSErr         myErr = noErr;

   myErr = FSpOpenDF(theFSSpec, fsRdPerm, &myRefNum);
   if (myErr != noErr)
      goto bail;

   SetFPos(myRefNum, fsFromStart, 0);

   while ((myErr == noErr) && (myEffectDesc == NULL)) {
      // read the atom header at the current file position
      mySize = sizeof(myAtomHeader);
      myErr = FSRead(myRefNum, &mySize, myAtomHeader);
      if (myErr != noErr)
         goto bail;

      mySize = EndianU32_BtoN(myAtomHeader[0]) - 
                                                   sizeof(myAtomHeader);
      myType = EndianU32_BtoN(myAtomHeader[1]);

      if (myType == FOUR_CHAR_CODE(‘qtfx')) {
         myEffectDesc = NewHandleClear(mySize);
         if (myEffectDesc == NULL)
            goto bail;

         myErr = FSRead(myRefNum, &mySize, *myEffectDesc);

      } else {
         SetFPos(myRefNum, fsFromMark, mySize);
      }
   }

bail:
   return((QTAtomContainer)myEffectDesc);
}

The effect description returned by this function can be used anywhere we use an effect description.

Conclusion

In this article, we've seen how to create movies that contain QuickTime video effects. We've worked with generators, filters, and transitions, and we've seen how to display and manage the effects parameters dialog box. We've also seen how to read data from an effects parameter file. The QuickTime video effects architecture provides a rich source of new capabilities that we can tap into with some very simple programming. The only really new thing we've encountered in this article is building effect descriptions, and even that turns out to be just another exercise in building atom containers.

You already know what's in store for us in the next article: we're going to see how to apply effects to images (not just to movie tracks). We're also going to see how to apply an effect to part of a movie, and how to use an effect as the image for a sprite. At some point in the distant future, we'll even learn how to write our own custom effects.

Acknowledgements and References

Many thanks are due to Tom Dowdy for reviewing this article and suggesting a number of improvements. Tom also wrote the code snippet in the Letter from the Ice Floe, Dispatch 24
(found at http://developer.apple.com/quicktime/icefloe/dispatch024.html) on which Listing 16 is based. The Apple documentation for the QuickTime video effects architecture can be found at
http://developer.apple.com/techpubs/quicktime/qtdevdocs/RM/rmEffects.htm.


Tim Monroe's lizards have perfected the color-tint effect, changing from green to brown (and vice versa) at their whim. In his day job, Tim is a member of the QuickTime engineering team. You can contact him at monroe@apple.com.

 

Community Search:
MacTech Search:

Software Updates via MacUpdate

Latest Forum Discussions

See All

Tokkun Studio unveils alpha trailer for...
We are back on the MMORPG news train, and this time it comes from the sort of international developers Tokkun Studio. They are based in France and Japan, so it counts. Anyway, semantics aside, they have released an alpha trailer for the upcoming... | Read more »
Win a host of exclusive in-game Honor of...
To celebrate its latest Jujutsu Kaisen crossover event, Honor of Kings is offering a bounty of login and achievement rewards kicking off the holiday season early. [Read more] | Read more »
Miraibo GO comes out swinging hard as it...
Having just launched what feels like yesterday, Dreamcube Studio is wasting no time adding events to their open-world survival Miraibo GO. Abyssal Souls arrives relatively in time for the spooky season and brings with it horrifying new partners to... | Read more »
Ditch the heavy binders and high price t...
As fun as the real-world equivalent and the very old Game Boy version are, the Pokemon Trading Card games have historically been received poorly on mobile. It is a very strange and confusing trend, but one that The Pokemon Company is determined to... | Read more »
Peace amongst mobile gamers is now shatt...
Some of the crazy folk tales from gaming have undoubtedly come from the EVE universe. Stories of spying, betrayal, and epic battles have entered history, and now the franchise expands as CCP Games launches EVE Galaxy Conquest, a free-to-play 4x... | Read more »
Lord of Nazarick, the turn-based RPG bas...
Crunchyroll and A PLUS JAPAN have just confirmed that Lord of Nazarick, their turn-based RPG based on the popular OVERLORD anime, is now available for iOS and Android. Starting today at 2PM CET, fans can download the game from Google Play and the... | Read more »
Digital Extremes' recent Devstream...
If you are anything like me you are impatiently waiting for Warframe: 1999 whilst simultaneously cursing the fact Excalibur Prime is permanently Vault locked. To keep us fed during our wait, Digital Extremes hosted a Double Devstream to dish out a... | Read more »
The Frozen Canvas adds a splash of colou...
It is time to grab your gloves and layer up, as Torchlight: Infinite is diving into the frozen tundra in its sixth season. The Frozen Canvas is a colourful new update that brings a stylish flair to the Netherrealm and puts creativity in the... | Read more »
Back When AOL WAS the Internet – The Tou...
In Episode 606 of The TouchArcade Show we kick things off talking about my plans for this weekend, which has resulted in this week’s show being a bit shorter than normal. We also go over some more updates on our Patreon situation, which has been... | Read more »
Creative Assembly's latest mobile p...
The Total War series has been slowly trickling onto mobile, which is a fantastic thing because most, if not all, of them are incredibly great fun. Creative Assembly's latest to get the Feral Interactive treatment into portable form is Total War:... | Read more »

Price Scanner via MacPrices.net

Early Black Friday Deal: Apple’s newly upgrad...
Amazon has Apple 13″ MacBook Airs with M2 CPUs and 16GB of RAM on early Black Friday sale for $200 off MSRP, only $799. Their prices are the lowest currently available for these newly upgraded 13″ M2... Read more
13-inch 8GB M2 MacBook Airs for $749, $250 of...
Best Buy has Apple 13″ MacBook Airs with M2 CPUs and 8GB of RAM in stock and on sale on their online store for $250 off MSRP. Prices start at $749. Their prices are the lowest currently available for... Read more
Amazon is offering an early Black Friday $100...
Amazon is offering early Black Friday discounts on Apple’s new 2024 WiFi iPad minis ranging up to $100 off MSRP, each with free shipping. These are the lowest prices available for new minis anywhere... Read more
Price Drop! Clearance 14-inch M3 MacBook Pros...
Best Buy is offering a $500 discount on clearance 14″ M3 MacBook Pros on their online store this week with prices available starting at only $1099. Prices valid for online orders only, in-store... Read more
Apple AirPods Pro with USB-C on early Black F...
A couple of Apple retailers are offering $70 (28%) discounts on Apple’s AirPods Pro with USB-C (and hearing aid capabilities) this weekend. These are early AirPods Black Friday discounts if you’re... Read more
Price drop! 13-inch M3 MacBook Airs now avail...
With yesterday’s across-the-board MacBook Air upgrade to 16GB of RAM standard, Apple has dropped prices on clearance 13″ 8GB M3 MacBook Airs, Certified Refurbished, to a new low starting at only $829... Read more
Price drop! Apple 15-inch M3 MacBook Airs now...
With yesterday’s release of 15-inch M3 MacBook Airs with 16GB of RAM standard, Apple has dropped prices on clearance Certified Refurbished 15″ 8GB M3 MacBook Airs to a new low starting at only $999.... Read more
Apple has clearance 15-inch M2 MacBook Airs a...
Apple has clearance, Certified Refurbished, 15″ M2 MacBook Airs now available starting at $929 and ranging up to $410 off original MSRP. These are the cheapest 15″ MacBook Airs for sale today at... Read more
Apple drops prices on 13-inch M2 MacBook Airs...
Apple has dropped prices on 13″ M2 MacBook Airs to a new low of only $749 in their Certified Refurbished store. These are the cheapest M2-powered MacBooks for sale at Apple. Apple’s one-year warranty... Read more
Clearance 13-inch M1 MacBook Airs available a...
Apple has clearance 13″ M1 MacBook Airs, Certified Refurbished, now available for $679 for 8-Core CPU/7-Core GPU/256GB models. Apple’s one-year warranty is included, shipping is free, and each... Read more

Jobs Board

Seasonal Cashier - *Apple* Blossom Mall - J...
Seasonal Cashier - Apple Blossom Mall Location:Winchester, VA, United States (https://jobs.jcp.com/jobs/location/191170/winchester-va-united-states) - Apple Read more
Seasonal Fine Jewelry Commission Associate -...
…Fine Jewelry Commission Associate - Apple Blossom Mall Location:Winchester, VA, United States (https://jobs.jcp.com/jobs/location/191170/winchester-va-united-states) Read more
Seasonal Operations Associate - *Apple* Blo...
Seasonal Operations Associate - Apple Blossom Mall Location:Winchester, VA, United States (https://jobs.jcp.com/jobs/location/191170/winchester-va-united-states) - Read more
Hair Stylist - *Apple* Blossom Mall - JCPen...
Hair Stylist - Apple Blossom Mall Location:Winchester, VA, United States (https://jobs.jcp.com/jobs/location/191170/winchester-va-united-states) - Apple Blossom 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
All contents are Copyright 1984-2011 by Xplain Corporation. All rights reserved. Theme designed by Icreon.