QTEffects.c

//////////
//
//  File:       QTEffects.c
//
//  Contains:   QuickTime video effect support for QuickTime movies.
//              Based on existing samples QTShowEffect, MakeEffectMovie, and AddEffectSeg.
//
//  Written by: Tim Monroe
//
//  Copyright:  © 1997-2001 by Apple Computer, Inc., all rights reserved.
//
//  Change History (most recent first):
//
//     <1>      11/06/97    rtm     first file; parts from QTShowEffect, MakeEffectMovie, and AddEffectSeg
//     
//
//////////
 
//////////
//
// header files
//
//////////
 
#include "QTEffects.h"
 
 
//////////
//
// global variables
//
//////////
 
QTParameterDialog           gEffectsDialog = 0L;            // identifier for the standard parameter dialog box
int                         gNumberOfSteps = k30StepsCount;
 
QTAtomContainer             gEffectDesc = NULL;         // effects description
QTAtomContainer             gEffectList = NULL;
PicHandle                   gPosterA = NULL;
PicHandle                   gPosterB = NULL;
Movie                       gSrcMovies[kMaxNumSources] = {NULL, NULL, NULL};
Track                       gSrcTracks[kMaxNumSources] = {NULL, NULL, NULL};
UInt16                      gSpecCount = 0;     
Boolean                     gCopyMovieMedia = false;        // should we copy the movie media into the new effects movie?
Boolean                     gDoneWithDialog = false;        // are we done using the effects parameters dialog box?
 
 
///////////////////////////////////////////////////////////////////////////////////////////////////////////
//
// High-level functions.
//
// These functions are called by QTApp_HandleMenu.
//
///////////////////////////////////////////////////////////////////////////////////////////////////////////
 
//////////
//
// QTEffects_MakeFireMovie
// Make a movie containing the fire effect.
//
//////////
 
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 a parameter atom to the effects sample
    if (0) {
        long        myRate = 11;    // this one goes to 11....
        
        myRate = EndianS32_NtoB(myRate);
        myErr = QTInsertChild(myEffectDesc, kParentAtomIsContainer, FOUR_CHAR_CODE('decy'), 1, 0, sizeof(myRate), &myRate, NULL);
    }
    
    //////////
    //
    // 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;
}
 
 
//////////
//
// QTEffects_AddFilmNoiseToMovie
// Add the film noise effect to the (first video track in the) specified movie.
//
//////////
 
void QTEffects_AddFilmNoiseToMovie (Movie theMovie)
{
    ImageDescriptionHandle  mySampleDesc = NULL;
    Track                   mySrcTrack = NULL;
    Track                   myTrack = NULL;
    Media                   myMedia = NULL;
    QTAtomContainer         myInputMap = NULL;
    QTAtomContainer         myEffectDesc = NULL;
    TimeValue               mySampleTime;
    Fixed                   myWidth, myHeight;
    OSErr                   myErr = noErr;
 
    if (theMovie == NULL)
        goto bail;
 
    // get the first visual track in the movie
    mySrcTrack = GetMovieIndTrackType(theMovie, 1, kCharacteristicCanSendVideo, movieTrackCharacteristic | movieTrackEnabledOnly);
    if (mySrcTrack == NULL)
        goto bail;
 
    // get the width and height of that track
    GetTrackDimensions(mySrcTrack, &myWidth, &myHeight);    
    
    //////////
    //
    // create the effects track
    //
    //////////
    
    myTrack = NewMovieTrack(theMovie, myWidth, myHeight, kNoVolume);
    if (myTrack == NULL)
        goto bail;
        
    myMedia = NewTrackMedia(myTrack, VideoMediaType, kOneSecond, NULL, 0);
    if (myMedia == NULL)
        goto bail;
 
    //////////
    //
    // create the sample description
    //
    //////////
    
    mySampleDesc = EffectsUtils_MakeSampleDescription(kFilmNoiseImageFilterType, FixedToInt(myWidth), FixedToInt(myHeight));
    if (mySampleDesc == NULL)
        goto bail;
    
    //////////
    //
    // create the effect description
    //
    //////////
    
    myEffectDesc = EffectsUtils_CreateEffectDescription(kFilmNoiseImageFilterType, kSourceOneName, kSourceNoneName, kSourceNoneName);
    if (myEffectDesc == NULL)
        goto bail;
 
    //////////
    //
    // add the effect description as a sample to the effects track media
    //
    //////////
 
    myErr = BeginMediaEdits(myMedia);
    if (myErr != noErr)
        goto bail;
 
    myErr = AddMediaSample(myMedia, (Handle)myEffectDesc, 0, GetHandleSize((Handle)myEffectDesc), GetMediaDuration(GetTrackMedia(mySrcTrack)), (SampleDescriptionHandle)mySampleDesc, 1, 0, &mySampleTime);
    if (myErr != noErr)
        goto bail;
 
    myErr = EndMediaEdits(myMedia);
    if (myErr != noErr)
        goto bail;
    
    //////////
    //
    // 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);
    if (myErr != noErr)
        goto bail;
 
    //////////
    //
    // add the media to the track
    //
    //////////
 
    myErr = InsertMediaIntoTrack(myTrack, 0, mySampleTime, GetMediaDuration(myMedia), fixed1);
    
bail:       
    if (myEffectDesc != NULL)
        QTDisposeAtomContainer(myEffectDesc);
    
    if (mySampleDesc != NULL)
        DisposeHandle((Handle)mySampleDesc);
    
    if (myInputMap != NULL)
        QTDisposeAtomContainer(myInputMap);
    
    return;
}
 
 
//////////
//
// QTEffects_AddFilmNoiseToImage
// Add the film noise effect to the image associated with the specified window object.
//
//////////
 
void QTEffects_AddFilmNoiseToImage (WindowObject theWindowObject)
{
    ApplicationDataHdl          myAppData = NULL;
    GraphicsImportComponent     myImporter = NULL;
    Rect                        myRect;
    
    if (theWindowObject == NULL)
        return;
 
    myAppData = (ApplicationDataHdl)(**theWindowObject).fAppData;
    if (myAppData == NULL)
        return;
    
    myImporter = (**theWindowObject).fGraphicsImporter;
    if (myImporter == NULL)
        return;
 
    GraphicsImportGetBoundsRect(myImporter, &myRect);
 
    // set up the initial state
    (**myAppData).fSampleDescription = EffectsUtils_MakeSampleDescription(kImageEffectType, myRect.right - myRect.left, myRect.bottom - myRect.top);
    (**myAppData).fEffectDescription = EffectsUtils_CreateEffectDescription(kImageEffectType, kSourceOneName, kSourceNoneName, kSourceNoneName);
    (**myAppData).fEffectType        = kImageEffectType;
    (**myAppData).fEffectSequenceID  = 0L;
    (**myAppData).fTimeBase          = NULL;
    
    QTEffects_SetUpEffectSequence(theWindowObject);
}
 
 
//////////
//
// QTEffects_MakePenguinMovie
// Make a movie that cross-fades the penguin picture in from a white initial frame.
//
//////////
 
void QTEffects_MakePenguinMovie (void)
{
    ImageDescriptionHandle  mySampleDesc = NULL;
    short                   myMovieRefNum = kInvalidFileRefNum;
    short                   myResID = movieInDataForkResID;
    Movie                   myMovie = NULL;
    Track                   myTrack = NULL;
    Track                   mySrc1Track = NULL;
    Track                   mySrc2Track = NULL;
    Media                   myMedia;
    GWorldPtr               myGW1 = NULL;
    GWorldPtr               myGW2 = NULL;
    FSSpec                  myFile;
    Boolean                 myIsSelected = false;
    Boolean                 myIsReplacing = false;  
    QTAtomContainer         myInputMap = NULL;
    QTAtomContainer         myEffectDesc = NULL;
    TimeValue               mySampleTime;
    long                    myFlags = createMovieFileDeleteCurFile | createMovieFileDontCreateResFile;
    StringPtr               myPrompt = QTUtils_ConvertCToPascalString(kEffectsSaveMoviePrompt);
    StringPtr               myFileName = QTUtils_ConvertCToPascalString(kEffectsPenguinMovieFileName);
    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, smCurrentScript, myFlags, &myMovieRefNum, &myMovie);
    if (myErr != noErr)
        goto bail;
    
    //////////
    //
    // create the effects track
    //
    //////////
    
    myTrack = NewMovieTrack(myMovie, IntToFixed(kPenguinTrackWidth), IntToFixed(kPenguinTrackHeight), kNoVolume);
    if (myTrack == NULL)
        goto bail;
        
    myMedia = NewTrackMedia(myTrack, VideoMediaType, kOneSecond, NULL, 0);
    if (myMedia == NULL)
        goto bail;
 
    //////////
    //
    // create the sample description
    //
    //////////
    
    mySampleDesc = EffectsUtils_MakeSampleDescription(kCrossFadeTransitionType, kPenguinTrackWidth, kPenguinTrackHeight);
    if (mySampleDesc == NULL)
        goto bail;
 
    //////////
    //
    // create the effect description
    //
    //////////
    
    myEffectDesc = EffectsUtils_CreateEffectDescription(kCrossFadeTransitionType, kSourceOneName, kSourceTwoName, kSourceNoneName);
    if (myEffectDesc == NULL)
        goto bail;
 
    //////////
    //
    // add the video tracks of the source pictures to the effects movie
    //
    //////////
    
    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;
 
    // the video tracks used as sources for the effect should start at the same time as the effects track
    // and end at the same time as the effects track
    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);
    if (myErr != noErr)
        goto bail;
 
    //////////
    //
    // add the effect description as a sample to the effects track media
    //
    //////////
 
    myErr = BeginMediaEdits(myMedia);
    if (myErr != noErr)
        goto bail;
 
    myErr = AddMediaSample(myMedia, (Handle)myEffectDesc, 0, GetHandleSize((Handle)myEffectDesc), kEffectMovieDuration, (SampleDescriptionHandle)mySampleDesc, 1, 0, &mySampleTime);
    if (myErr != noErr)
        goto bail;
 
    myErr = EndMediaEdits(myMedia);
    if (myErr != noErr)
        goto bail;
    
    //////////
    //
    // 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, mySrc1Track, kSourceOneName);
    if (myErr != noErr)
        goto bail;
        
    myErr = EffectsUtils_AddTrackReferenceToInputMap(myInputMap, myTrack, mySrc2Track, kSourceTwoName);
    if (myErr != noErr)
        goto bail;
 
    // add the input map to the effects track
    myErr = SetMediaInputMap(myMedia, myInputMap);
    if (myErr != noErr)
        goto bail;
 
    //////////
    //
    // add the media to the track
    //
    //////////
    
    myErr = InsertMediaIntoTrack(myTrack, 0, mySampleTime, GetMediaDuration(myMedia), fixed1);
    if (myErr != noErr)
        goto bail;
        
    // put the movie resource into the file
    myErr = AddMovieResource(myMovie, myMovieRefNum, &myResID, NULL);
    
bail:       
    if (myGW1 != NULL)
        DisposeGWorld(myGW1);
        
    if (myGW2 != NULL)
        DisposeGWorld(myGW2);
        
    if (mySampleDesc != NULL)
        DisposeHandle((Handle)mySampleDesc);
    
    if (myMovieRefNum != kInvalidFileRefNum)
        CloseMovieFile(myMovieRefNum);
 
    if (myMovie != NULL)
        DisposeMovie(myMovie);
 
    if (myInputMap != NULL)
        QTDisposeAtomContainer(myInputMap);
    
    free(myPrompt);
    free(myFileName);
 
    return;
}
 
 
//////////
//
// QTEffects_AddEffectToMovieSegment
// Add the specified effect to occur at the specified time and duration.
//
//////////
 
OSErr QTEffects_AddEffectToMovieSegment (Movie theMovie, OSType theEffectType, long theNumSources, TimeValue theStartTime, TimeValue theDuration)
{
    Track                   myVidTrack1 = NULL;
    Track                   myVidTrack2 = NULL;
    Track                   mySrcTrack1 = NULL;
    Track                   mySrcTrack2 = NULL;
    Media                   mySrcMedia1 = NULL;
    Media                   mySrcMedia2 = NULL;
    Track                   myEffectTrack = NULL;
    Media                   myEffectMedia = NULL;
    Fixed                   myWidth, myHeight;
    TimeScale               myTimeScale;
    TimeValue               mySampleTime;
    Rect                    myRect;
    QTAtomContainer         myInputMap = NULL;
    QTAtomContainer         myEffectDesc = NULL;
    ImageDescriptionHandle  mySampleDesc = NULL;
    OSType                  myEffectName1 = kSourceNoneName;
    OSType                  myEffectName2 = kSourceNoneName;
    short                   myLayer;
    OSErr                   myErr = noErr;
    
    // make sure we were passed a valid movie
    if (theMovie == NULL)
        return(paramErr);
        
    //////////
    //
    // get some information about the movie
    //
    //////////
    
    myTimeScale = GetMovieTimeScale(theMovie);
    GetMovieBox(theMovie, &myRect);
    myLayer = EffectsUtils_GetFrontmostTrackLayer(theMovie, VideoMediaType);
    
    myWidth = IntToFixed((myRect.right - myRect.left));
    myHeight = IntToFixed((myRect.bottom - myRect.top));
 
    //////////
    //
    // retrieve the original video track(s), create the effect's source track(s) and media,
    // then set the new source track(s) to reference the data in the original video track(s)
    //
    //////////
    
    switch (theNumSources) {
        case 2:
            myVidTrack2 = GetMovieIndTrackType(theMovie, 2, VideoMediaType, movieTrackMediaType | movieTrackEnabledOnly);
            if (myVidTrack2 == NULL)
                return(paramErr);
            
            mySrcTrack2 = NewMovieTrack(theMovie, myWidth, myHeight, kNoVolume);
            if (mySrcTrack2 == NULL)
                return(paramErr);
                
            mySrcMedia2 = NewTrackMedia(mySrcTrack2, VideoMediaType, myTimeScale, NULL, 0);
            if (mySrcMedia2 == NULL)
                return(paramErr);
 
#if COPY_MOVIE_MEDIA
            myErr = BeginMediaEdits(mySrcMedia2);
            if (myErr != noErr)
                return(myErr);
#endif          
            myErr = CopyTrackSettings(myVidTrack2, mySrcTrack2);
            myErr = InsertTrackSegment(myVidTrack2, mySrcTrack2, theStartTime, theDuration, theStartTime);
            if (myErr != noErr)
                return(myErr);
 
#if COPY_MOVIE_MEDIA
            EndMediaEdits(mySrcMedia2);
#endif          
            myEffectName2 = kSourceTwoName;
            
            // note that we fall through here!
                
        case 1:
            myVidTrack1 = GetMovieIndTrackType(theMovie, 1, VideoMediaType, movieTrackMediaType | movieTrackEnabledOnly);
            if (myVidTrack1 == NULL)
                return(paramErr);
                
            mySrcTrack1 = NewMovieTrack(theMovie, myWidth, myHeight, kNoVolume);
            if (mySrcTrack1 == NULL)
                return(paramErr);
                
            mySrcMedia1 = NewTrackMedia(mySrcTrack1, VideoMediaType, myTimeScale, NULL, 0);
            if (mySrcMedia1 == NULL)
                return(paramErr);
 
#if COPY_MOVIE_MEDIA
            myErr = BeginMediaEdits(mySrcMedia1);
            if (myErr != noErr)
                return(myErr);
#endif          
            myErr = CopyTrackSettings(myVidTrack1, mySrcTrack1);
            myErr = InsertTrackSegment(myVidTrack1, mySrcTrack1, theStartTime, theDuration, theStartTime);
            if (myErr != noErr)
                return(myErr);
            
#if COPY_MOVIE_MEDIA
            EndMediaEdits(mySrcMedia1);
#endif          
            myEffectName1 = kSourceOneName;
            
            break;
            
        case 0:
            // for 0-source effects, we don't need to create a new source track
            break;
    
        default:
            return(paramErr);
    }
    
    //////////
    //
    // create the effects track and media
    //
    //////////
 
    myEffectTrack = NewMovieTrack(theMovie, myWidth, myHeight, kNoVolume);
    if (myEffectTrack == NULL)
        return(GetMoviesError());
        
    myEffectMedia = NewTrackMedia(myEffectTrack, VideoMediaType, myTimeScale, NULL, 0);
    if (myEffectMedia == NULL)
        return(GetMoviesError());
    
    // create an effect sample description
    mySampleDesc = EffectsUtils_MakeSampleDescription(theEffectType, FixedToInt(myWidth), FixedToInt(myHeight));
    if (mySampleDesc == NULL)
        goto bail;
 
    // create an effect description
    myEffectDesc = EffectsUtils_CreateEffectDescription(theEffectType, myEffectName1, myEffectName2, kSourceNoneName);
 
    // add the effect description as a sample to the effects track media
    BeginMediaEdits(myEffectMedia);
 
    myErr = AddMediaSample(myEffectMedia, (Handle)myEffectDesc, 0, GetHandleSize((Handle)myEffectDesc), theDuration, (SampleDescriptionHandle)mySampleDesc, 1, 0, &mySampleTime);
    if (myErr != noErr)
        goto bail;
 
    EndMediaEdits(myEffectMedia);
    
    // add the media sample to the effects track
    myErr = InsertMediaIntoTrack(myEffectTrack, theStartTime, mySampleTime, theDuration, fixed1);
    if (myErr != noErr)
        goto bail;
 
    //////////
    //
    // create the input map and add references for the source track(s)
    //
    //////////
 
    myErr = QTNewAtomContainer(&myInputMap);
    if (myErr != noErr)
        goto bail;
    
    if (mySrcTrack1 != NULL) {  
        myErr = EffectsUtils_AddTrackReferenceToInputMap(myInputMap, myEffectTrack, mySrcTrack1, kSourceOneName);
        if (myErr != noErr)
            goto bail;
    }
        
    if (mySrcTrack2 != NULL) {  
        myErr = EffectsUtils_AddTrackReferenceToInputMap(myInputMap, myEffectTrack, mySrcTrack2, kSourceTwoName);
        if (myErr != noErr)
            goto bail;
    }
        
    // add the input map to the effects track
    myErr = SetMediaInputMap(myEffectMedia, myInputMap);
    if (myErr != noErr)
        goto bail;
 
    //////////
    //
    // do any required positioning and graphics mode manipulation
    //
    //////////
 
    SetTrackLayer(myEffectTrack, myLayer - 1);  // in front of any existing video track
    
    switch (theNumSources) {
        case 2:
            break;
            
        case 1:
            break;
            
        case 0: {
            RGBColor    myColor;
            
            myColor.red = 0;        // (good for fire, not so good for clouds)
            myColor.green = 0;
            myColor.blue = 0;
            
            MediaSetGraphicsMode(GetMediaHandler(myEffectMedia), transparent, &myColor);
            break;
        }
    }
    
bail:
    if (mySampleDesc != NULL)
        DisposeHandle((Handle)mySampleDesc);
    
    if (myInputMap != NULL)
        QTDisposeAtomContainer(myInputMap);
    
    return(myErr);
}
 
 
//////////
//
// QTEffects_MakeSpriteEffectMovie
// Create a movie with a sprite that uses an effect for its image.
//
//////////
 
void QTEffects_MakeSpriteEffectMovie (void)
{
    Movie                   myMovie = NULL;
    Track                   myTrack = NULL;
    Media                   myMedia = NULL;
    FSSpec                  myFile;
    Boolean                 myIsSelected = false;
    Boolean                 myIsReplacing = false;  
    StringPtr               myPrompt = QTUtils_ConvertCToPascalString(kEffectsSaveMoviePrompt);
    StringPtr               myFileName = QTUtils_ConvertCToPascalString(kEffectsPenguinMovieFileName);
    long                    myFlags = createMovieFileDeleteCurFile | createMovieFileDontCreateResFile;
    OSType                  myType = FOUR_CHAR_CODE('none');
    short                   myMovieRefNum = kInvalidFileRefNum;
    short                   myResID = movieInDataForkResID;
    OSErr                   myErr = noErr;
 
    //////////
    //
    // create a new movie file
    //
    //////////
 
    // prompt the user for the destination file name
    QTFrame_PutFile(myPrompt, myFileName, &myFile, &myIsSelected, &myIsReplacing);
    if (!myIsSelected)
        goto bail;
 
    // 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 sprite track and media
    //
    //////////
    
    myTrack = NewMovieTrack(myMovie, IntToFixed(kPenguinTrackWidth), IntToFixed(kPenguinTrackHeight), kFullVolume);
    if (myTrack == NULL)
        goto bail;
 
    myMedia = NewTrackMedia(myTrack, SpriteMediaType, kSpriteMediaTimeScale, NULL, 0);
    if (myMedia == NULL)
        goto bail;
 
    //////////
    //
    // add the appropriate samples to the sprite media
    //
    //////////
    
    myErr = BeginMediaEdits(myMedia);
    if (myErr != noErr)
        goto bail;
 
    QTEffects_AddPenguinMovieSamplesToMedia(myMedia);
    
    myErr = EndMediaEdits(myMedia);
    if (myErr != noErr)
        goto bail;
    
    // add the media to the track
    InsertMediaIntoTrack(myTrack, 0, 0, GetMediaDuration(myMedia), fixed1);
        
    //////////
    //
    // set the sprite track properties
    //
    //////////
    
    QTEffects_SetTrackProperties(myMedia);
    
    //////////
    //
    // add the movie resource to the movie file
    //
    //////////
    
    myErr = AddMovieResource(myMovie, myMovieRefNum, &myResID, myFile.name);
        
bail:
    if (myMovieRefNum != kInvalidFileRefNum)
        CloseMovieFile(myMovieRefNum);
 
    if (myMovie != NULL)
        DisposeMovie(myMovie);
        
    free(myPrompt);
    free(myFileName);
 
    return;
}
 
 
//////////
//
// QTEffects_PromptUserForFilesAndMakeEffectMovie
// Let the user select some movies, then apply the effect to them.
//
// If the user cancels the first file-open dialog box, there are zero sources.
// If the user cancels the second file-open dialog box, there is one source.
// 
//////////
 
void QTEffects_PromptUserForFilesAndMakeEffectMovie (void)
{
    QTFrameFileFilterUPP    myFileFilterUPP = NULL;
    FSSpec                  mySpecs[kMaxNumSources];
    int                     mySpecCount;
    OSType                  myTypeList[] = {kQTFileTypeMovie};
    short                   myNumTypes = 1;
    OSErr                   myErr = noErr;
    
#if TARGET_OS_MAC
    myNumTypes = 0;
#endif
 
    myFileFilterUPP = QTFrame_GetFileFilterUPP((ProcPtr)QTFrame_FilterFiles);
 
    // ask for up to kMaxNumSources movie files;
    // accept early cancels; they just mean there are fewer input movies
    mySpecCount = 0;
    while (mySpecCount < kMaxNumSources) {
        FSSpec  myFSSpec;
 
        myTypeList[0] = MovieFileType;
 
        myErr = QTFrame_GetOneFileWithPreview(myNumTypes, (QTFrameTypeListPtr)myTypeList, &myFSSpec, myFileFilterUPP);
        if (myErr != noErr)
            break;  // the user doesn't want any more source movies
    
        // save the FSSpec from the reply information
        mySpecs[mySpecCount] = myFSSpec;
        
        mySpecCount++;
    }
    
    QTEffects_DisplayDialogForSources(mySpecs, mySpecCount);
    
    if (myFileFilterUPP != NULL)
        DisposeNavObjectFilterUPP(myFileFilterUPP);
}
 
 
////////////////////
//
// QTEffects_AddRippleEffectAsSpriteImage
// Add the ripple effect as the image override of the specified sprite image ID.
//
////////////////////
 
void QTEffects_AddRippleEffectAsSpriteImage (QTAtomContainer theKeySample, QTAtomID theImageID)
{
    ImageDescriptionHandle      mySampleDesc = NULL;
    QTAtomContainer             myEffectDesc = NULL;
    OSType                      myType = kWaterRippleCodecType;
    OSErr                       myErr = noErr;
    
    // create a sample description
    mySampleDesc = EffectsUtils_MakeSampleDescription(myType, kPenguinTrackWidth, kPenguinTrackHeight);
    if (mySampleDesc == NULL)
        goto bail;
 
    // create an effect description
    myEffectDesc = EffectsUtils_CreateEffectDescription(myType, kSourceNoneName, kSourceNoneName, kSourceNoneName);
    if (myEffectDesc == NULL)
        goto bail;
 
    SpriteUtils_AddCompressedImageToKeyFrameSample(theKeySample, mySampleDesc, GetHandleSize(myEffectDesc), *myEffectDesc, theImageID, NULL, NULL);
 
bail:
    if (mySampleDesc != NULL)
        DisposeHandle((Handle)mySampleDesc);
 
    if (myEffectDesc != NULL)
        QTDisposeAtomContainer(myEffectDesc);
        
    return;
}
 
 
///////////////////////////////////////////////////////////////////////////////////////////////////////////
//
// Effects dialog utilities.
//
// Use these functions to work with the effects parameter dialog box.
//
///////////////////////////////////////////////////////////////////////////////////////////////////////////
 
//////////
//
// QTEffects_DisplayDialogForSources
// Display the standard effects parameters dialog box for the movies passed in.
//
//////////
 
OSErr QTEffects_DisplayDialogForSources (FSSpec *theSpecList, UInt16 theSpecCount)
{
    UInt16                  myMovieIter;
    OSErr                   myErr = noErr;
    
    // make sure that there aren't too many sources
    if (theSpecCount > kMaxNumSources) {
        myErr = paramErr;
        goto bail;
    }
    
    // assign source count to a global, so QTEffects_RespondToDialogSelection has access to it
    gSpecCount = theSpecCount;      
    
    // get the first video track from each movie file
    for (myMovieIter = 0; myMovieIter < theSpecCount; myMovieIter++) {
        short   myRefNum;
        
        // open a movie file using the FSSpec and create a movie from that file
        myErr = OpenMovieFile(&theSpecList[myMovieIter], &myRefNum, 0);
        if (myErr != noErr)
            goto bail;
        
        myErr = NewMovieFromFile(&gSrcMovies[myMovieIter], myRefNum, NULL, NULL, newMovieActive, NULL);
        if (myErr != noErr)
            goto bail;
        
        SetMoviePlayHints(gSrcMovies[myMovieIter], hintsHighQuality, hintsHighQuality);
 
        // we're done with the movie file, so close it
        CloseMovieFile(myRefNum);
        
        // find the first video track in the source movie
        gSrcTracks[myMovieIter] = GetMovieIndTrackType(gSrcMovies[myMovieIter], 1, kCharacteristicCanSendVideo, movieTrackCharacteristic | movieTrackEnabledOnly);
        if (gSrcTracks[myMovieIter] == NULL)
            goto bail;
    }
    
    // ask the user to select an effect
 
    myErr = QTNewAtomContainer(&gEffectDesc);
    if (myErr != noErr)
        goto bail;
    
    myErr = QTGetEffectsList(&gEffectList, theSpecCount, theSpecCount, 0);
    if (myErr != noErr)
        goto bail;
    
    // create the effects parameter dialog box, so the user can select an effect
    myErr = QTCreateStandardParameterDialog(gEffectList, gEffectDesc, 0, &gEffectsDialog);
    if (myErr != noErr)
        goto bail;
    
    // insert poster frames into dialog
    if (gSrcTracks[0] != NULL) {
        gPosterA = GetTrackPict(gSrcTracks[0], GetMoviePosterTime(gSrcMovies[0]));
        if (gPosterA != NULL) {
            QTParamPreviewRecord            myPreviewRecord;
 
            myPreviewRecord.sourcePicture = gPosterA;
            myPreviewRecord.sourceID = 1;
            myErr = QTStandardParameterDialogDoAction(gEffectsDialog, pdActionSetPreviewPicture, &myPreviewRecord);
        }
    }
 
    if (gSrcTracks[1] != NULL) {
        gPosterB = GetTrackPict(gSrcTracks[1], GetMoviePosterTime(gSrcMovies[1]));
        if (gPosterB != NULL) {
            QTParamPreviewRecord            myPreviewRecord;
 
            myPreviewRecord.sourcePicture = gPosterB;
            myPreviewRecord.sourceID = 2;
            myErr = QTStandardParameterDialogDoAction(gEffectsDialog, pdActionSetPreviewPicture, &myPreviewRecord);
        }
    }
    
    // now, the frontmost window is the standard effects parameter dialog box;
    // on the Mac, we call QTEffects_HandleEffectsDialogEvents in our main event loop
    // to find and process events targeted at the effects parameter dialog box; on Windows,
    // we need to use a different strategy: we install a modeless dialog callback procedure
    // that is called internally by QTML
 
#if TARGET_OS_WIN32
    gDoneWithDialog = false;
    
    // force the dialog box to be drawn
    {
        EventRecord         myEvent = {0};
        
        QTEffects_EffectsDialogCallback(&myEvent, FrontWindow(), 0);
    }
    
    SetModelessDialogCallbackProc(FrontWindow(), (QTModelessCallbackUPP)QTEffects_EffectsDialogCallback);
    QTMLSetWindowWndProc(FrontWindow(), QTEffects_CustomDialogWndProc);
#endif
    
bail:
    return(myErr);
}
 
 
//////////
//
// QTEffects_RespondToDialogSelection
// If theErr is codecParameterDialogConfirm, make an effects movie.
// If theErr is userCanceledErr, do any necessary clean up.
//
//////////
 
static void QTEffects_RespondToDialogSelection (OSErr theErr)
{
    short                   myMovieRefNum = 0;
    short                   myResID = movieInDataForkResID;
    OSType                  myEffectCode;
    OSType                  mySourceName;
    Fixed                   myTrackWidth, myTrackHeight;
    TimeScale               myMovieTimeScale = 600; 
    TimeValue               myEffectDuration = 0;
    TimeValue               mySampleTime = 0;
    FSSpec                  myFile;
    Boolean                 myIsSelected = false;
    Boolean                 myIsReplacing = false;  
    StringPtr               myPrompt = QTUtils_ConvertCToPascalString(kEffectsSaveMoviePrompt);
    StringPtr               myFileName = QTUtils_ConvertCToPascalString(kEffectsSaveMovieFileName);
    Movie                   myDestMovie = NULL;
    Track                   myVideoTracks[kMaxNumSources];
    Track                   myEffectTrack = NULL;
    Media                   myEffectMedia = NULL;
    UInt16                  myMovieIter;
    ImageDescriptionHandle  mySampleDesc = NULL;
    QTAtomContainer         myInputMap = NULL;
    long                    myFlags = createMovieFileDeleteCurFile | createMovieFileDontCreateResFile;
    OSErr                   myErr = noErr;
    
    //////////
    //
    // 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);
    
    // if the user cancelled, bail
    if (theErr == userCanceledErr)
        goto bail;
    
    //////////
    //
    // add to gEffectDesc some atoms naming the sources 
    //
    //////////
        
    if (gSpecCount >= 1) {
        mySourceName = EndianU32_NtoB(kSourceOneName);
        QTInsertChild(gEffectDesc, kParentAtomIsContainer, kEffectSourceName, 1, 0, sizeof(mySourceName), &mySourceName, NULL);
    }
    
    if (gSpecCount >= 2) {
        mySourceName = EndianU32_NtoB(kSourceTwoName);
        QTInsertChild(gEffectDesc, kParentAtomIsContainer, kEffectSourceName, 2, 0, sizeof(mySourceName), &mySourceName, NULL);
    }
    
    if (gSpecCount >= 3) {
        mySourceName = EndianU32_NtoB(kSourceThreeName);
        QTInsertChild(gEffectDesc, kParentAtomIsContainer, kEffectSourceName, 3, 0, sizeof(mySourceName), &mySourceName, NULL);
    }
    
    //////////
    //
    // find out what kind of effect it is
    //
    //////////
        
    myErr = EffectsUtils_GetTypeFromEffectDescription(gEffectDesc, &myEffectCode);
    if (myErr != noErr)
        goto bail;
 
    //////////
    //
    // create the effects movie
    //
    //////////
    
    // 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, &myDestMovie);
    if (myErr != noErr)
        goto bail;
    
    // copy the user data and settings from the source to the destination movie;
    // these settings include information like user data
    if (gSpecCount > 0)
        CopyMovieSettings(gSrcMovies[0], myDestMovie);
    
    // convert all the movies to have a common time scale;
    // we pick the largest time scale out of all the source movies, with a minimum of 600
    myMovieTimeScale = 600;
    for (myMovieIter = 0; myMovieIter < gSpecCount; myMovieIter++) {
        if (myMovieTimeScale < GetMovieTimeScale(gSrcMovies[myMovieIter]))
            myMovieTimeScale = GetMovieTimeScale(gSrcMovies[myMovieIter]);
    }
    
    for (myMovieIter = 0; myMovieIter < gSpecCount; myMovieIter++) {
        if (myMovieTimeScale != GetMovieTimeScale(gSrcMovies[myMovieIter]))
            SetMovieTimeScale(gSrcMovies[myMovieIter], myMovieTimeScale);
    }
    
    // the effect duration is the minimum of the length of the tracks
    if (gSpecCount == 0)
        myEffectDuration = myMovieTimeScale * 10;
    else
        myEffectDuration = GetTrackDuration(gSrcTracks[0]);
    
    for (myMovieIter = 1; myMovieIter < gSpecCount; myMovieIter++) {
        if (myEffectDuration > GetTrackDuration(gSrcTracks[myMovieIter]))
            myEffectDuration = GetTrackDuration(gSrcTracks[myMovieIter]);
    }
    
    // default size when there are no video tracks
    myTrackWidth = IntToFixed(kDefaultTrackWidth);
    myTrackHeight = IntToFixed(kDefaultTrackHeight);
 
    for (myMovieIter = 0; myMovieIter < kMaxNumSources; myMovieIter++) {
        myVideoTracks[myMovieIter] = NULL;
    }
    
    // make the video tracks
    for (myMovieIter = 0; myMovieIter < gSpecCount; myMovieIter++) {
        Fixed   mySrcTrackWidth, mySrcTrackHeight;
        OSType  mySrcMediaType = 0;
        Media   myVideoMedia;
 
        // myVideoTracks[n] is a clone of gSrcTracks[n]
        GetTrackDimensions(gSrcTracks[myMovieIter], &mySrcTrackWidth, &mySrcTrackHeight);
        
        if ((myMovieIter == 0) || (myTrackWidth < mySrcTrackWidth))
                myTrackWidth = mySrcTrackWidth;
                
        if ((myMovieIter == 0) || (myTrackHeight < mySrcTrackHeight))
                myTrackHeight = mySrcTrackHeight;
        
        GetMediaHandlerDescription(GetTrackMedia(gSrcTracks[myMovieIter]), &mySrcMediaType, NULL, NULL);
 
        myVideoTracks[myMovieIter] = NewMovieTrack(myDestMovie, mySrcTrackWidth, mySrcTrackHeight, kNoVolume);
        if (myVideoTracks[myMovieIter] == NULL)
            goto bail;
 
        myVideoMedia = NewTrackMedia(myVideoTracks[myMovieIter], mySrcMediaType, myMovieTimeScale, NULL, 0);
        if (myVideoMedia == NULL)
            goto bail;
        
        if (gCopyMovieMedia) {
            myErr = BeginMediaEdits(myVideoMedia);
            if (myErr != noErr)
                goto bail;
        }
 
        myErr = CopyTrackSettings(gSrcTracks[myMovieIter], myVideoTracks[myMovieIter]);
        if (myErr != noErr)
            goto bail;
 
        myErr = InsertTrackSegment(gSrcTracks[myMovieIter], myVideoTracks[myMovieIter], 0, myEffectDuration, 0);
        if (myErr != noErr)
            goto bail;
        
        if (gCopyMovieMedia) {
            myErr = EndMediaEdits(myVideoMedia);
            if (myErr != noErr)
                goto bail;
        }
    }
    
    //////////
    //
    // create the effects track
    //
    //////////
    
    // myEffectTrack is the special track that implements the effect
    myEffectTrack = NewMovieTrack(myDestMovie, myTrackWidth, myTrackHeight, kNoVolume);
    if (myEffectTrack == NULL)
        goto bail;
 
    myEffectMedia = NewTrackMedia(myEffectTrack, VideoMediaType, myMovieTimeScale, NULL, 0);
    if (myEffectMedia == NULL)
        goto bail;
 
    // create the sample description
    mySampleDesc = EffectsUtils_MakeSampleDescription(myEffectCode, FixedToInt(myTrackWidth), FixedToInt(myTrackHeight));
    if (mySampleDesc == NULL)
        goto bail;
    
    //////////
    //
    // add the effects sample to the movie
    //
    //////////
    
    myErr = BeginMediaEdits(myEffectMedia);
    if (myErr != noErr)
        goto bail;
 
    myErr = AddMediaSample(myEffectMedia, gEffectDesc, 0, GetHandleSize(gEffectDesc), myEffectDuration, (SampleDescriptionHandle)mySampleDesc, 1, 0, &mySampleTime);
    if (myErr != noErr)
        goto bail;
 
    myErr = EndMediaEdits(myEffectMedia);
    if (myErr != noErr)
        goto bail;
 
    QTDisposeAtomContainer(gEffectDesc);
    DisposeHandle((Handle)mySampleDesc);
 
    myErr = InsertMediaIntoTrack(myEffectTrack, 0, mySampleTime, myEffectDuration, fixed1);
    if (myErr != noErr)
        goto bail;
 
    //////////
    //
    // create and add the input map
    //
    //////////
 
    myErr = QTNewAtomContainer(&myInputMap);
    if (myErr != noErr)
        goto bail;
 
    // first input
    if (myVideoTracks[0] != NULL)
        EffectsUtils_AddTrackReferenceToInputMap(myInputMap, myEffectTrack, myVideoTracks[0], kSourceOneName);
 
    if (myVideoTracks[1] != NULL)
        EffectsUtils_AddTrackReferenceToInputMap(myInputMap, myEffectTrack, myVideoTracks[1], kSourceTwoName);
 
    if (myVideoTracks[2] != NULL)
        EffectsUtils_AddTrackReferenceToInputMap(myInputMap, myEffectTrack, myVideoTracks[2], kSourceThreeName);
 
    // set the input map
    if (gSpecCount > 0)
        SetMediaInputMap(GetTrackMedia(myEffectTrack), myInputMap);
 
    //////////
    //
    // finish up
    //
    //////////
 
    myErr = AddMovieResource(myDestMovie, myMovieRefNum, &myResID, NULL);
    if (myErr != noErr)
        goto bail;
    
    CloseMovieFile(myMovieRefNum);
    
    for (myMovieIter = 0; myMovieIter < gSpecCount; myMovieIter++)
        DisposeMovie(gSrcMovies[myMovieIter]);
        
    DisposeMovie(myDestMovie);
    
bail:
    free(myPrompt);
    free(myFileName);
 
    if (myInputMap != NULL)
        QTDisposeAtomContainer(myInputMap);
 
    return;
}
 
 
#if TARGET_OS_WIN32
//////////
//
// QTEffects_EffectsDialogCallback
// This function is called by QTML when it processes events for the standard or custom effects parameter 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 parameter dialog box
        QTEffects_HandleEffectsDialogEvents(theEvent, theItemHit);
    }
}
 
 
//////////
//
// QTEffects_CustomDialogWndProc
// Handle messages for the custom 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));
}
#endif
 
 
//////////
//
// QTEffects_HandleEffectsDialogEvents
// Process events that might be targeted at the standard effects parameter dialog box.
// Return true if the event was completely handled.
// 
//////////
 
Boolean QTEffects_HandleEffectsDialogEvents (EventRecord *theEvent, DialogItemIndex theItemHit)
{
#pragma unused(theItemHit)
    Boolean         isHandled = false;
    OSErr           myErr = noErr;
    
    // pass the event to the standard effects parameter 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);
}
 
 
///////////////////////////////////////////////////////////////////////////////////////////////////////////
//
// Effects-rendering utilities.
//
// Use these functions to render an effect into a GWorld.
//
///////////////////////////////////////////////////////////////////////////////////////////////////////////
 
//////////
//
// QTEffects_InitWindowData
// Do any effects-specific initialization for the specified window.
// 
//////////
 
Handle QTEffects_InitWindowData (WindowObject theWindowObject)
{
    ApplicationDataHdl          myAppData = NULL;
 
    // if we already have some window data, dump it
    myAppData = (ApplicationDataHdl)QTFrame_GetAppDataFromWindowObject(theWindowObject);
    if (myAppData != NULL)
        QTEffects_DumpWindowData(theWindowObject);
 
    // allocate and initialize our application data
    myAppData = (ApplicationDataHdl)NewHandleClear(sizeof(ApplicationDataRecord));
 
    return((Handle)myAppData);
}
 
 
//////////
//
// QTEffects_DumpWindowData
// Do any effects-specific tear-down for the specified window.
//
//////////
 
void QTEffects_DumpWindowData (WindowObject theWindowObject)
{
    ApplicationDataHdl      myAppData = NULL;
        
    myAppData = (ApplicationDataHdl)QTFrame_GetAppDataFromWindowObject(theWindowObject);
    if (myAppData != NULL) {
        if ((**myAppData).fGWDesc != NULL)
            DisposeHandle((Handle)(**myAppData).fGWDesc);
 
        if ((**myAppData).fGW != NULL)
            DisposeGWorld((**myAppData).fGW);
 
        if ((**myAppData).fSampleDescription != NULL)
            DisposeHandle((Handle)(**myAppData).fSampleDescription);
            
        if ((**myAppData).fEffectDescription != NULL)
            QTDisposeAtomContainer((**myAppData).fEffectDescription);
            
        if ((**myAppData).fEffectSequenceID != 0L)
            CDSequenceEnd((**myAppData).fEffectSequenceID);
            
        if ((**myAppData).fTimeBase != NULL)
            DisposeTimeBase((**myAppData).fTimeBase);
 
        DisposeHandle((Handle)myAppData);
        (**theWindowObject).fAppData = NULL;
    }
}
 
 
//////////
//
// QTEffects_SetUpEffectSequence
// Set up an effects sequence for a one-source effect.
// 
//////////
 
static OSErr QTEffects_SetUpEffectSequence (WindowObject theWindowObject)
{
    ApplicationDataHdl          myAppData = NULL;
    ImageSequenceDataSource     mySrc = 0;
    PixMapHandle                mySrcPixMap = NULL;
    GraphicsImportComponent     myImporter = NULL;
    Rect                        myRect;
    OSErr                       myErr = paramErr;
    
    myAppData = (ApplicationDataHdl)QTFrame_GetAppDataFromWindowObject(theWindowObject);
    if (myAppData == NULL)
        goto bail;
 
    // if an effect sequence is already set up, end it
    if ((**myAppData).fEffectSequenceID != 0L) {
        CDSequenceEnd((**myAppData).fEffectSequenceID);
        (**myAppData).fEffectSequenceID = 0L;
    }
    
    // if there is a timebase already set up, dispose of it
    if ((**myAppData).fTimeBase != NULL) {
        DisposeTimeBase((**myAppData).fTimeBase);
        (**myAppData).fTimeBase = NULL;
    }
        
    // make an effects sequence
    HLock((Handle)(**myAppData).fEffectDescription);
 
    // prepare the decompression sequence for playback
    myErr = DecompressSequenceBeginS(
                            &(**myAppData).fEffectSequenceID,
                            (**myAppData).fSampleDescription,
#if TARGET_CPU_68K
                            StripAddress(*(**myAppData).fEffectDescription),
#else
                            *(**myAppData).fEffectDescription,
#endif
                            GetHandleSize((**myAppData).fEffectDescription),
                            (CGrafPtr)QTFrame_GetPortFromWindowReference((**theWindowObject).fWindow),
                            NULL,
                            NULL,
                            NULL,
                            ditherCopy,
                            NULL,
                            0,
                            codecNormalQuality,
                            NULL);
 
    HUnlock((Handle)(**myAppData).fEffectDescription);
    if (myErr != noErr)
        goto bail;
 
    // create the offscreen GWorld holding the original image data
    
    myImporter = (**theWindowObject).fGraphicsImporter;
    if (myImporter == NULL)
        goto bail;
 
    // set the size of the GWorld
    GraphicsImportGetBoundsRect(myImporter, &myRect);
    
    HLock((Handle)myAppData);
 
    // allocate a new GWorld
    myErr = QTNewGWorld(&(**myAppData).fGW, 32, &myRect, NULL, NULL, kICMTempThenAppMemory);
    if (myErr != noErr)
        goto bail;
    
    // lock the pixmap
    LockPixels(GetGWorldPixMap((**myAppData).fGW));
 
    GraphicsImportSetGWorld(myImporter, (**myAppData).fGW, NULL);
    GraphicsImportDraw(myImporter);
    
    // get the pixel maps for the GWorlds
    mySrcPixMap = GetGWorldPixMap((**myAppData).fGW);
    if (mySrcPixMap == NULL)
        goto bail;
 
    // make the effect source
    if ((**myAppData).fGW == NULL)
        goto bail;
        
    myErr = MakeImageDescriptionForPixMap(mySrcPixMap, &(**myAppData).fGWDesc);
    if (myErr != noErr)
        goto bail;
 
    myErr = CDSequenceNewDataSource((**myAppData).fEffectSequenceID, &mySrc, kSourceOneName, 1, (Handle)(**myAppData).fGWDesc, NULL, 0);
    if (myErr != noErr)
        goto bail;
 
    CDSequenceSetSourceData(mySrc, GetPixBaseAddr(mySrcPixMap), (**(**myAppData).fGWDesc).dataSize);
 
    // create a new time base and associate it with the decompression sequence
    (**myAppData).fTimeBase = NewTimeBase();
    myErr = GetMoviesError();
    if (myErr != noErr)
        goto bail;
 
    SetTimeBaseRate((**myAppData).fTimeBase, 0);
 
    myErr = CDSequenceSetTimeBase((**myAppData).fEffectSequenceID, (**myAppData).fTimeBase);
 
bail:
    HUnlock((Handle)myAppData);
 
    return(myErr);
}
 
 
//////////
//
// QTEffects_RunEffect
// Run the effect: decompress a single step of the effect sequence.
// 
//////////
 
OSErr QTEffects_RunEffect (WindowObject theWindowObject, TimeValue theTime)
{
    ApplicationDataHdl          myAppData = NULL;
    ICMFrameTimeRecord          myFrameTime;
    OSErr                       myErr = paramErr;
    
    myAppData = (ApplicationDataHdl)QTFrame_GetAppDataFromWindowObject(theWindowObject);
    if (myAppData == NULL)
        goto bail;
 
    // assertions
    if (((**myAppData).fEffectDescription == NULL) || ((**myAppData).fEffectSequenceID == 0L))
        goto bail;
 
    // set the timebase time to the step of the sequence to be rendered
    SetTimeBaseValue((**myAppData).fTimeBase, theTime, gNumberOfSteps);
 
    myFrameTime.value.hi                = 0;
    myFrameTime.value.lo                = theTime;
    myFrameTime.scale                   = gNumberOfSteps;
    myFrameTime.base                    = 0;
    myFrameTime.duration                = gNumberOfSteps;
    myFrameTime.rate                    = 0;
    myFrameTime.recordSize              = sizeof(myFrameTime);
    myFrameTime.frameNumber             = 1;
    myFrameTime.flags                   = icmFrameTimeHasVirtualStartTimeAndDuration;
    myFrameTime.virtualStartTime.lo     = 0;
    myFrameTime.virtualStartTime.hi     = 0;
    myFrameTime.virtualDuration         = gNumberOfSteps;
    
    HLock((Handle)(**myAppData).fEffectDescription);
 
    myErr = DecompressSequenceFrameWhen(
                                        (**myAppData).fEffectSequenceID,
#if TARGET_CPU_68K
                                        StripAddress(*((Handle)(**myAppData).fEffectDescription)),
#else
                                        *((Handle)(**myAppData).fEffectDescription),
#endif
                                        GetHandleSize((Handle)(**myAppData).fEffectDescription),
                                        0,
                                        NULL,
                                        NULL,
                                        &myFrameTime);
                                        
    HUnlock((Handle)(**myAppData).fEffectDescription);
    
bail:
    return(myErr);
}
 
 
///////////////////////////////////////////////////////////////////////////////////////////////////////////
//
// Sprite utilities.
//
// Use these functions to create sprite tracks.
//
///////////////////////////////////////////////////////////////////////////////////////////////////////////
 
//////////
//
// QTEffects_AddPenguinMovieSamplesToMedia
// Build the key frame for the penguin sprite movie.
//
//////////
 
static void QTEffects_AddPenguinMovieSamplesToMedia (Media theMedia)
{
    QTAtomContainer         mySample = NULL;
    QTAtomContainer         mySpriteData = NULL;
    RGBColor                myKeyColor;
    Point                   myLocation;
    short                   isVisible, myIndex, myLayer;
    OSErr                   myErr = noErr;
    
    // create a new, empty key frame sample
    myErr = QTNewAtomContainer(&mySample);
    if (myErr != noErr)
        goto bail;
 
    myKeyColor.red = myKeyColor.green = myKeyColor.blue = 0xffff;       // white
    
    // add images to the key frame sample
    SpriteUtils_AddPICTImageToKeyFrameSample(mySample, kPenguinPictID, &myKeyColor, 1, NULL, NULL);
    QTEffects_AddRippleEffectAsSpriteImage(mySample, 2);
 
    myErr = QTNewAtomContainer(&mySpriteData);
    if (myErr != noErr)
        goto bail;
 
    // the penguin sprite
    myLocation.h    = 0;
    myLocation.v    = 0;
    isVisible       = true;
    myIndex         = 1;
    myLayer         = 0;
    
    SpriteUtils_SetSpriteData(mySpriteData, &myLocation, &isVisible, &myLayer, &myIndex, NULL, NULL, NULL);
    SpriteUtils_AddSpriteToSample(mySample, mySpriteData, 1);
    
    QTDisposeAtomContainer(mySpriteData);   
 
    myErr = QTNewAtomContainer(&mySpriteData);
    if (myErr != noErr)
        goto bail;
 
    // the ripple sprite
    myLocation.h    = 0;
    myLocation.v    = 0;
    isVisible       = true;
    myIndex         = 2;
    myLayer         = -1;
 
    SpriteUtils_SetSpriteData(mySpriteData, &myLocation, &isVisible, &myLayer, &myIndex, NULL, NULL, NULL); 
    WiredUtils_AddQTEventAndActionAtoms(mySpriteData, kParentAtomIsContainer, kQTEventMouseClick, kActionSpritePassMouseToCodec, NULL);
    SpriteUtils_AddSpriteToSample(mySample, mySpriteData, 2);
 
    SpriteUtils_AddSpriteSampleToMedia(theMedia, mySample, kSpriteMediaFrameDurationPenguin, true, NULL);   
    
bail:   
    if (mySample != NULL)
        QTDisposeAtomContainer(mySample);
 
    if (mySpriteData != NULL)
        QTDisposeAtomContainer(mySpriteData);   
}
 
 
//////////
//
// QTEffects_SetTrackProperties
// Set the track properties for the specified sample sprite movie.
//
//////////
 
void QTEffects_SetTrackProperties (Media theMedia)
{
    QTAtomContainer     myTrackProperties;
    RGBColor            myBackgroundColor;
    Boolean             hasActions;
    OSErr               myErr = noErr;
        
    // add a background color to the sprite track
    myBackgroundColor.red = EndianU16_NtoB(0xffff);
    myBackgroundColor.green = EndianU16_NtoB(0xffff);
    myBackgroundColor.blue = EndianU16_NtoB(0xffff);
    
    myErr = QTNewAtomContainer(&myTrackProperties);
    if (myErr == noErr) {
        QTInsertChild(myTrackProperties, 0, kSpriteTrackPropertyBackgroundColor, 1, 1, sizeof(myBackgroundColor), &myBackgroundColor, NULL);
 
        // tell the movie controller that this sprite track has actions
        hasActions = true;
        QTInsertChild(myTrackProperties, 0, kSpriteTrackPropertyHasActions, 1, 1, sizeof(hasActions), &hasActions, NULL);
    
        SetMediaPropertyAtom(theMedia, myTrackProperties);
        
        QTDisposeAtomContainer(myTrackProperties);
    }
}