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.

 
AAPL
$111.16
Apple Inc.
+1.75
MSFT
$47.05
Microsoft Corpora
+1.31
GOOG
$510.51
Google Inc.
+5.62

MacTech Search:
Community Search:

Software Updates via MacUpdate

BBEdit 11.0.2 - Powerful text and HTML e...
BBEdit is the leading professional HTML and text editor for the Mac. Specifically crafted in response to the needs of Web authors and software developers, this award-winning product provides a... Read more
ExpanDrive 4.2.1 - Access cloud storage...
ExpanDrive builds cloud storage in every application, acts just like a USB drive plugged into your Mac. With ExpanDrive, you can securely access any remote file server directly from the Finder or... Read more
Adobe After Effects CC 2014 13.2 - Creat...
After Effects CC 2014 is available as part of Adobe Creative Cloud for as little as $19.99/month (or $9.99/month if you're a previous After Effects customer). After Effects CS6 is still available... Read more
Command-C 1.1.7 - Clipboard sharing tool...
Command-C is a revolutionary app which makes easy to share your clipboard between iOS and OS X using your local WiFi network, even if the app is not currently opened. Copy anything (text, pictures,... Read more
Tidy Up 4.0.2 - Find duplicate files and...
Tidy Up is a complete duplicate finder and disk-tidiness utility. With Tidy Up you can search for duplicate files and packages by the owner application, content, type, creator, extension, time... Read more
Typinator 6.3 - Speedy and reliable text...
Typinator turbo-charges your typing productivity. Type a little. Typinator does the rest. We've all faced projects that require repetitive typing tasks. With Typinator, you can store commonly used... Read more
GraphicConverter 9.5 - Graphics editor w...
GraphicConverter is an all-purpose image-editing program that can import 200 different graphic-based formats, edit the image, and export it to any of 80 available file formats. The high-end editing... Read more
Toast Titanium 12.0.1 - The ultimate med...
Toast Titanium goes way beyond the very basic burning in the Mac OS and iLife software, and sets the standard for burning CDs, DVDs, and now Blu-ray discs on the Mac. Create superior sounding audio... Read more
QuickBooks 2015 16.0.2.1422 R3 - Financi...
Save 20% on QuickBooks Pro for Mac today through this special discount link QuickBooks Pro 2013 helps you manage your business easily and efficiently. Organize your finances all in one place, track... Read more
Remotix 3.0.6 - Access all your computer...
Remotix is a fast and powerful application to easily access multiple Macs (and PCs) from your own Mac. Features: Complete Apple Screen Sharing support - including Mac OS X login, clipboard... Read more

Latest Forum Discussions

See All

Brothers in Arms 3: Sons of War Review
Brothers in Arms 3: Sons of War Review By Jennifer Allen on December 18th, 2014 Our Rating: :: FUN BUT PUSHYUniversal App - Designed for iPhone and iPad Brothers in Arms 3: Sons of War could be great fun, but its plethora of... | Read more »
The Snow is Falling on the French Countr...
The Snow is Falling on the French Countryside of Carcassonne’s Newest Update Posted by Jessica Fisher on December 18th, 2014 [ permalink ] < | Read more »
Creatures Such As We Review
Creatures Such As We Review By Jennifer Allen on December 18th, 2014 Our Rating: :: INTERESTING DISCUSSIONSUniversal App - Designed for iPhone and iPad Taking a more sedate approach to interactive fiction, Creatures Such As We... | Read more »
Give It Up! (Games)
Give It Up! 1.0 Device: iOS Universal Category: Games Price: $.99, Version: 1.0 (iTunes) Description: GIVE IT UP is a rather hard game where you have to assist this cheerful, singing Blob in jumping through 9 different tracks.So far... | Read more »
The Drive : Devil's Run (Games)
The Drive : Devil's Run 1.0 Device: iOS Universal Category: Games Price: $.99, Version: 1.0 (iTunes) Description: ON THE ROAD AGAIN! The Drive - Devil’s Run is classic point to point style racing game that pays homage to the classics... | Read more »
Procreate Pocket (Entertainment)
Procreate Pocket 1.01 Device: iOS iPhone Category: Entertainment Price: $2.99, Version: 1.01 (iTunes) Description: Create - anytime, anywhere. Made by the developers of the award-winning Procreate® for iPad®, Procreate Pocket™ allows... | Read more »
IRON FINGER - Mini Games Championship (...
IRON FINGER - Mini Games Championship 1.0 Device: iOS Universal Category: Games Price: $1.99, Version: 1.0 (iTunes) Description: Quick to play, easy to learn yet hard to master.. TAP, SWIPE & TILT your way through mini games that... | Read more »
Pentaction: Medieval (Games)
Pentaction: Medieval 1.0 Device: iOS Universal Category: Games Price: $1.99, Version: 1.0 (iTunes) Description: Pentaction: Medieval is a turn-based strategy board-game about chance and skill on the battlefield. Take control of your... | Read more »
Hipstify Review
Hipstify Review By Jennifer Allen on December 17th, 2014 Our Rating: :: COOL FILTERSUniversal App - Designed for iPhone and iPad Add filters, quotes, and fancy frames to your images, thanks to Hipstify.   | Read more »
Mighty Smighties Gets Evolve Cards and N...
Mighty Smighties Gets Evolve Cards and New Worlds Posted by Jessica Fisher on December 17th, 2014 [ permalink ] Universal App - Designed for iPhone and iPad | Read more »

Price Scanner via MacPrices.net

KMI MIDI K-Board Great Gift for Amateur &...
The K-Board is a MIDI Nano keyboard for music creation for iPad, Android, And computers; the easiest way to make music with iPads & Android tablets, and Mac, Windows, or Linux computers. Ultra-... Read more
Amazon offers 15-inch 2.2GHz Retina MacBook P...
 Amazon.com has the 15″ 2.2GHz Retina MacBook Pro on sale for $1699 including free shipping. Their price is $300 off MSRP. Stock is limited, so act now if you’re interested. Read more
Holiday sales continue: MacBook Pros for up t...
 B&H Photo has new MacBook Pros on sale for up to $300 off MSRP as part of their Holiday pricing. Shipping is free, and B&H charges NY sales tax only: - 15″ 2.2GHz Retina MacBook Pro: $1699... Read more
Google Search App For iOS Gets A Major Makeov...
Google has given iOS users an early Christmas present with a substantial update of it’s not-very-often-upgraded Google Search app. Google Search has been my go-to tool for Web searches since it was... Read more
ShopKeep Apple Pay And Chip Card Reader Avail...
ShopKeep, a cloud-based technology provider to more than 10,000 small business owners to manage retail shops and restaurants with iPads, has released its new Apple Pay and chip card reader. This... Read more
Holiday sale! 27-inch 5K iMac for $2299, save...
 B&H Photo has the 27″ 3.5GHz 5K iMac in stock today and on sale for $2299 including free shipping plus NY sales tax only. Their price is $200 off MSRP, and it’s the lowest price available for... Read more
Holiday Sale! 3.7GHz Quad Core Mac Pro availa...
 B&H Photo has the 3.7GHz Quad Core Mac Pro on sale for $2599 including free shipping plus NY sales tax only. Their price is $400 off MSRP, and it’s the lowest price for this model from any... Read more
iPhone 6 Number 3 Canadian Google Search Of 2...
CTVNews.ca reports that Apple’s iPhone 6 was the third highest-trending Google Canada search topic of 2014, exceeded only by Robin Williams largely after his death by suicide in August, and the FIFA... Read more
New iPad mini 3 Counter-Top & Wall Mount...
newMacgadgets has announced new secure all-acrylic displays for the iPad mini 3 (also works fine with the mini 2, last year’s iPad mini With Retina Display, and the original iPad mini). The new iPad... Read more
Holiday sales continue, MacBook Airs for up t...
B&H Photo has 2014 MacBook Airs on sale for up to $120 off MSRP, for a limited time, for the Thanksgiving/Christmas Holiday shopping season. Shipping is free, and B&H charges NY sales tax... Read more

Jobs Board

*Apple* Retail - Multiple Positions (US) - A...
Sales Specialist - Retail Customer Service and Sales Transform Apple Store visitors into loyal Apple customers. When customers enter the store, you're also the Read more
*Apple* Retail - Multiple Positions (US) - A...
Sales Specialist - Retail Customer Service and Sales Transform Apple Store visitors into loyal Apple customers. When customers enter the store, you're also the Read more
*Apple* Retail - Multiple Positions (US) - A...
Job Description: Sales Specialist - Retail Customer Service and Sales Transform Apple Store visitors into loyal Apple customers. When customers enter the store, Read more
*Apple* Retail - Multiple Positions (US) - A...
Sales Specialist - Retail Customer Service and Sales Transform Apple Store visitors into loyal Apple customers. When customers enter the store, you're also the 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.