QTEffects.c

/*
    File:       QTEffects.c
    
    Contains:   Code to generate a QuickTime movie with a QuickTime video effect in it.
    
    Written by: Scott Kuechle
                (based heavily on QuickTime SDK QTShowEffect sample code by Tim Monroe)
 
    Copyright:  © 1999 by Apple Computer, Inc. All rights reserved
    
    Change History (most recent first)
    
        <1>     9/25/99     srk     first file
        <2>     10/19/99    srk     changed cross fade effect to explode effect
 
 
This file defines functions that create a QuickTime movie with a video effect as a transition
from one picture to another. This project differs from the QTShowEffect sample code it is based 
on in that it allows the user to create the effect "by hand" e.g. without the aid of the standard 
QuickTime effects parameter dialog box. Instead, the program itself builds the correct effects 
parameter description atoms.
 
Here's a quick rundown of how the program works:
 
- First, a QuickTime movie with two video tracks is created - these two tracks will be used as 
sources for a explode video effect
 
- The effect description and sample description structures for the explode effect are created
       
- An effects track and media are created and added to our movie for the explode effect
 
- The input map for the effect is created, and references for the two video tracks are added to 
this input map
        
The resulting movie, when played, will show a smooth alpha blending between the two video tracks.
 
*/
 
 
#ifndef __IMAGECODEC__
#include <ImageCodec.h>
#endif
 
#ifndef __MOVIES__
#include <Movies.h>
#endif
 
#ifndef __MOVIESFORMAT__
#include <MoviesFormat.h>
#endif
 
#ifndef __MEDIAHANDLERS__
#include <MediaHandlers.h>
#endif
 
#ifndef __NUMBERFORMATTING__
#include <NumberFormatting.h>
#endif
 
#ifndef __QUICKTIMECOMPONENTS__
#include <QuickTimeComponents.h>
#endif
 
#ifndef __ENDIAN__
#include <Endian.h>
#endif
 
#ifndef __GESTALT__
#include <Gestalt.h>
#endif
 
#ifndef __FIXMATH__
#include <FixMath.h>
#endif
 
#ifndef __MACWINDOWS__
#include <MacWindows.h>
#endif
 
#ifndef __PRINTING__
#include <Printing.h>
#endif
 
#ifndef __SCRIPT__
#include <Script.h>
#endif
 
#ifndef __SOUND__
#include <Sound.h>
#endif
 
#ifndef _STRING_H
#include <string.h>
#endif
 
#ifndef _STDLIB_H
#include <stdlib.h>
#endif
 
#ifndef __TEXTUTILS__
#include <TextUtils.h>
#endif
 
#ifndef __TRAPS__
#include <Traps.h>
#endif
 
#ifndef __RESOURCES__
#include <Resources.h>
#endif
 
#ifndef __FILETYPESANDCREATORS__
#include <FileTypesAndCreators.h>
#endif
 
#if TARGET_OS_WIN32
        // Windows headers
    #define STRICT
    #include <windows.h>
        // QTML stuff
    #include "QTML.h"
#endif
 
 
#include "QTEffects.h"
 
 
// miscellaneous constants
 
#define kSourceOneName                  FOUR_CHAR_CODE('srcA')
#define kSourceTwoName                  FOUR_CHAR_CODE('srcB')
#define kSourceNoneName                 FOUR_CHAR_CODE('srcZ')
 
#define kPictGWorldDepth                32
 
#define kPict1ResID                     128
#define kPict2ResID                     129
 
#define kPrompt                         "Specify a new movie file name:"
#define kMovieFileName                  "Movie File.mov"
 
#define kOneSecond                      600
#define kEffectMovieDuration            (3 * kOneSecond)
 
#define kTrackDisplayWidth              200
#define kTrackDisplayHeight             240
 
#define FAIL_OSERR(y)                   if (y != noErr) {goto bail;}
 
 
 
////////////////////
//
// QTEffects_GetPictResourceAsGWorld
// Create a new GWorld of the specified size and bit depth; then draw the specified PICT resource into it.
// The new GWorld is returned through the theGW parameter.
//
////////////////////
 
Movie QTEffects_CreateEffectsMovie()
{
    Movie                   theMovie = nil;
    short                   resRefNum = 0;
    short                   resId = movieInDataForkResID;   
    OSErr                   myErr = noErr;
    Track                   sourceTrack1, sourceTrack2;
    ImageDescriptionHandle  mySampleDesc = NULL;
    QTAtomContainer         theEffectDesc;
    Track                   myTrack;
    Media                   myMedia;
    FSSpec                  movieFSSpec;
 
    /* Let's first create a movie file with two video tracks. Each
        video track will be used as a source for our explode effect */
    QTEffects_CreateTwoTrackMovie(  &theMovie,
                                    &resRefNum,
                                    &resId,
                                    &movieFSSpec,
                                    &sourceTrack1,
                                    &sourceTrack2);
 
    /* Setup the effect description and sample description for our explode
    effect */
    QTEffects_SetupEffectsDescription(  kExplodeTransitionType,
                                        &mySampleDesc,
                                        &theEffectDesc);
 
    /* Create and add an effects track and media to our movie for the explode
    effect */
    QTEffects_CreateEffectsTrackAndMedia(   theMovie,
                                            mySampleDesc,
                                            theEffectDesc,
                                            &myTrack,
                                            &myMedia);
 
    /* Create the input map for the effect, and add references for the two
        video tracks */
    QTEffects_CreateInputMapAndAddTrackReferences(  myTrack,
                                                    myMedia,
                                                    sourceTrack1,
                                                    sourceTrack2);
 
    /* save our changes to the movie */
    myErr = AddMovieResource (theMovie,
                                resRefNum,
                                &resId,
                                nil);
    DisposeMovie(theMovie);
 
    if (mySampleDesc != NULL)
        DisposeHandle((Handle)mySampleDesc);
        
    return theMovie;
}
 
////////////////////
//
// QTEffects_CreateTwoTrackMovie
// 
// Create a movie with two video tracks. Each track is based on our stored picture files.
// The two tracks will be used as sources for our two-source explode effect.
//
////////////////////
 
static void QTEffects_CreateTwoTrackMovie( Movie *theMovie,
                                            short   *resRefNum,
                                            short   *resID,
                                            FSSpec  *movieFSSpec,
                                            Track   *videoTrack1,
                                            Track   *videoTrack2)
{
    Point       where = {100,100};
    SFReply     theSFReply;
    OSErr       myErr = noErr;
    GWorldPtr   offscreenGWorld1,offscreenGWorld2;
    
 
        /* Let's first create a movie file */
 
    SFPutFile (where, c2pstr(kPrompt), 
                c2pstr(kMovieFileName), nil, &theSFReply);
    if (!theSFReply.good) return; 
 
    FSMakeFSSpec(theSFReply.vRefNum, 0, theSFReply.fName, movieFSSpec);
 
    myErr = CreateMovieFile (movieFSSpec, 
                            sigMoviePlayer,
                            smCurrentScript, 
                            createMovieFileDeleteCurFile | createMovieFileDontCreateResFile,
                            resRefNum, 
                            theMovie);
    FAIL_OSERR(myErr);
    
        /* Now we'll retrieve some stored pictures which will be used in the creation of our video tracks */ 
 
    myErr =  QTEffects_GetPictResourceAsGWorld (kPict1ResID, kTrackDisplayWidth, kTrackDisplayHeight, kPictGWorldDepth, &offscreenGWorld1);
    FAIL_OSERR(myErr);
    
    myErr =  QTEffects_GetPictResourceAsGWorld (kPict2ResID,  kTrackDisplayWidth, kTrackDisplayHeight, kPictGWorldDepth, &offscreenGWorld2);
    FAIL_OSERR(myErr);
 
 
        /* Now add two video tracks to our movie, based on the source pictures just retrieved. 
        
           Note: video tracks used as sources for an effect should start at the same time as the effect
           track and end at the same time as the effect track
           
        */
       
    myErr = QTEffects_AddVideoTrackFromGWorld(theMovie, offscreenGWorld1, videoTrack1, 0, kTrackDisplayWidth, kTrackDisplayHeight);
    FAIL_OSERR(myErr);
 
    myErr = QTEffects_AddVideoTrackFromGWorld(theMovie, offscreenGWorld2, videoTrack2, 0, kTrackDisplayWidth, kTrackDisplayHeight);
    FAIL_OSERR(myErr);
 
    bail:
    ;
    
}
 
                                        
 
////////////////////
//
// QTEffects_GetPictResourceAsGWorld
// Create a new GWorld of the specified size and bit depth; then draw the specified PICT resource into it.
// The new GWorld is returned through the theGW parameter.
//
////////////////////
 
static OSErr QTEffects_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);
    FAIL_OSERR(myErr);
    
    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);
}
 
 
////////////////////
//
// QTEffects_AddVideoTrackFromGWorld
// Add to the specified movie a video track for the specified picture resource.
//
////////////////////
 
static OSErr QTEffects_AddVideoTrackFromGWorld (Movie *theMovie, GWorldPtr theGW, Track *theSourceTrack, long theStartTime, short theWidth, short theHeight)
{
    Media                       myMedia;
    ImageDescriptionHandle      myDesc = NULL;
    Rect                        myRect;
    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, FixRatio(theWidth, 1), FixRatio(theHeight, 1), kNoVolume);
    myMedia = NewTrackMedia(*theSourceTrack, VideoMediaType, kOneSecond, NULL, 0);
    
    // get the rectangle for the movie
    GetMovieBox(*theMovie, &myRect);
    
    // begin editing the new track
    BeginMediaEdits(myMedia);
        
    // 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, kPictGWorldDepth, &myRect, NULL, NULL, 0L);
    FAIL_OSERR(myErr);
    
    mySrcPixMap = GetGWorldPixMap(theGW);
    // LockPixels(mySrcPixMap);
    
    myDstPixMap = GetGWorldPixMap(myGWorld);
    LockPixels(myDstPixMap);
    
    // create a new image description; CompressImage will fill in the fields of this structure
    myDesc = (ImageDescriptionHandle)NewHandle(4);
    
    SetGWorld(myGWorld, NULL);
    
    // copy the image from the specified GWorld into the new GWorld
    CopyBits((BitMap *)(*mySrcPixMap), (BitMap *)(*myDstPixMap), &(theGW->portRect), &(myGWorld->portRect), srcCopy, 0L);
 
    // restore the original port and device
    SetGWorld(mySavedPort, mySavedGDevice);
    
    myErr = GetMaxCompressionSize(myDstPixMap, &myRect, 0, codecNormalQuality, kAnimationCodecType, anyCodec, &mySize);
    FAIL_OSERR(myErr);
        
    myData = NewHandle(mySize);
    FAIL_OSERR(myErr);
        
    HLockHi(myData);
    myDataPtr = StripAddress(*myData);
    
    myErr = CompressImage(myDstPixMap, &myRect, codecNormalQuality, kAnimationCodecType, myDesc, myDataPtr);
    FAIL_OSERR(myErr);
        
    myErr = AddMediaSample(myMedia, myData, 0, (**myDesc).dataSize, kEffectMovieDuration, (SampleDescriptionHandle)myDesc, 1, 0, NULL);
    FAIL_OSERR(myErr);
 
    myErr = EndMediaEdits(myMedia);
    FAIL_OSERR(myErr);
 
    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 (myDesc != NULL)
        DisposeHandle((Handle)myDesc);
        
    // if (mySrcPixMap != NULL)
    //  UnlockPixels(mySrcPixMap);
        
    if (myDstPixMap != NULL)
        UnlockPixels(myDstPixMap);
        
    if (myGWorld != NULL)
        DisposeGWorld(myGWorld);
    
    return(myErr);
}
 
 
////////////////////
//
// QTEffects_SetupEffectsDescription
// Create the effect description and sample description for the effect.
//
////////////////////
 
static void QTEffects_SetupEffectsDescription( OSType                   theEffectType,
                                                ImageDescriptionHandle  *mySampleDesc,
                                                QTAtomContainer         *theEffectDesc)
{
    // set up a new effect description
    QTEffects_CreateEffectDescription(theEffectType, kSourceOneName, kSourceTwoName, theEffectDesc);
 
    // set up the effects parameters for the explode effect 
    QTEffects_CreateEffectParameterForExplode(*theEffectDesc);
 
    // create an effect sample description
    *mySampleDesc = QTEffects_MakeSampleDescription(theEffectType, kTrackDisplayWidth, kTrackDisplayHeight);
 
}
 
 
////////////////////
//
// QTEffects_CreateEffectsTrackAndMedia
// Create an effects track and associated media for our movie.
//
////////////////////
 
 
static void QTEffects_CreateEffectsTrackAndMedia(Movie myMovie, 
                                            ImageDescriptionHandle  mySampleDesc,
                                            QTAtomContainer         theEffectDesc,
                                            Track *myTrack,
                                            Media *myMedia)
{
    OSErr       myErr;
    TimeValue   mySampleTime;
 
 
    *myTrack = NewMovieTrack(myMovie, FixRatio(kTrackDisplayWidth, 1), FixRatio(kTrackDisplayHeight, 1), kNoVolume);
    myErr = GetMoviesError();
    FAIL_OSERR(myErr);
    
    *myMedia = NewTrackMedia(*myTrack, VideoMediaType, kOneSecond, NULL, 0);
    myErr = GetMoviesError();
    FAIL_OSERR(myErr);
 
    // add the effect description as a sample to the effect track media
    myErr = BeginMediaEdits(*myMedia);
    FAIL_OSERR(myErr);
 
    myErr = AddMediaSample(*myMedia,
                            (Handle)theEffectDesc,
                            0,
                            GetHandleSize((Handle)theEffectDesc),
                            kEffectMovieDuration,
                            (SampleDescriptionHandle)mySampleDesc,
                            1,
                            0,
                            &mySampleTime);
    FAIL_OSERR(myErr);
 
 
    myErr = EndMediaEdits(*myMedia);
    FAIL_OSERR(myErr);
 
    // add the media to the track
    myErr = InsertMediaIntoTrack(*myTrack, 0, mySampleTime, GetMediaDuration(*myMedia), fixed1);
    FAIL_OSERR(myErr);
 
bail:
;
 
 
}
 
 
////////////////////
//
// QTEffects_CreateInputMapAndAddTrackReferences
// Create an input map, and add references to the input map for both
// video tracks.
//
////////////////////
 
static void QTEffects_CreateInputMapAndAddTrackReferences(Track effectsTrack,
                                                    Media   effectsTrackMedia,
                                                    Track   sourceTrack1,
                                                    Track   sourceTrack2)
{
    QTAtomContainer myInputMap;
    OSErr           myErr;
 
 
    // create the input map
    myErr = QTNewAtomContainer(&myInputMap);
    FAIL_OSERR(myErr);
 
    // add references for the two video tracks
    myErr = QTEffects_AddTrackReferenceToInputMap(myInputMap, effectsTrack, sourceTrack1, kSourceOneName);
    FAIL_OSERR(myErr);
 
    // try using existing video track as source
    myErr = QTEffects_AddTrackReferenceToInputMap(myInputMap, effectsTrack, sourceTrack2, kSourceTwoName);
    FAIL_OSERR(myErr);
 
    // add the input map to the effects track
    myErr = SetMediaInputMap(effectsTrackMedia, myInputMap);
    FAIL_OSERR(myErr);
    
 
bail:
;
    if (myInputMap != NULL)
    QTDisposeAtomContainer(myInputMap);
 
}
 
 
 
////////////////////
//
// QTEffects_CreateEffectDescription
// Create an effect description for zero, one, or two sources.
// 
// The effect description specifies which video effect is desired and the parameters for that effect.
// It also describes the source(s) for the effect. An effect description is simply an atom container
// that holds atoms with the appropriate information.
//
// Note that because we are creating an atom container, we must pass big-endian data (hence the calls
// to EndianU32_NtoB).
//
// The caller is responsible for disposing of the returned atom container, by calling QTDisposeAtomContainer.
//
////////////////////
 
static void QTEffects_CreateEffectDescription (OSType           theEffectName,
                                              OSType            theSourceName1,
                                              OSType            theSourceName2,
                                              QTAtomContainer   *theEffectDesc)
{
    OSType              myType;
    OSErr               myErr = noErr;
 
 
    // create a new, empty effect description
    myErr = QTNewAtomContainer(theEffectDesc);
    FAIL_OSERR(myErr);
 
    // create the effect ID atom: the atom type is kParameterWhatName, and the atom ID is kParameterWhatID
    myType = EndianU32_NtoB(theEffectName);
    myErr = QTInsertChild(*theEffectDesc, kParentAtomIsContainer, kParameterWhatName, kParameterWhatID, 0, sizeof(myType), &myType, NULL);
    FAIL_OSERR(myErr);
        
    // add the first source, if it's not kSourceNoneName
    if (theSourceName1 != kSourceNoneName) {
        myType = EndianU32_NtoB(theSourceName1);
        myErr = QTInsertChild(*theEffectDesc, kParentAtomIsContainer, kEffectSourceName, 1, 0, sizeof(myType), &myType, NULL);
        FAIL_OSERR(myErr);
    }
                            
    // add the second source, if it's not kSourceNoneName
    if (theSourceName2 != kSourceNoneName) {
        myType = EndianU32_NtoB(theSourceName2);
        myErr = QTInsertChild(*theEffectDesc, kParentAtomIsContainer, kEffectSourceName, 2, 0, sizeof(myType), &myType, NULL);
    }
 
 
bail:
;
}
 
////////////////////
//
// QTEffects_CreateEffectParameterForExplode
// Create the parameter atoms for the explode effect. This will include a
// percentage atom, along with a tween atom to tween the effect
//
////////////////////
 
static void QTEffects_CreateEffectParameterForExplode(QTAtomContainer myEffectDesc)
{
    Fixed   percentage[2];
    Fixed   explodeCentreX,explodeCentreY;
    OSErr   myErr = noErr;
    QTAtom  newAtom;
    OSType  tweenType;
 
 
 
    /* add the percentage ('pcnt') atom for the effect */
    myErr = QTInsertChild(myEffectDesc, kParentAtomIsContainer, kParameterUsagePercent, 1, 0, 0, 0, &newAtom);
    FAIL_OSERR(myErr);
 
    /* this atom specifies the format of the tween data */
    tweenType = EndianU32_NtoB(kParameterTypeDataFixed);
    myErr = QTInsertChild(myEffectDesc, newAtom, kTweenType, 1 , 0, sizeof(OSType), &tweenType, NULL);
    FAIL_OSERR(myErr);
 
    /* here's the data for the tween  */
    percentage[0] =  EndianS32_NtoB( X2Fix(0.90) ); /* 90% */
    percentage[1] =  EndianS32_NtoB( X2Fix(0.10) ); /* 10% */
    myErr = QTInsertChild(myEffectDesc, newAtom, kTweenData, 1 , 0, sizeof(long) * 2, &percentage, NULL);
 
    /* Add Explode Centre X atom */
    explodeCentreX = EndianS32_NtoB( X2Fix(0.0) );
    myErr = QTInsertChild(myEffectDesc, kParentAtomIsContainer, 'xcnt', 1 , 0, sizeof(long), &explodeCentreX, NULL);
 
    /* Add Explode Centre Y atom */
    explodeCentreY = EndianS32_NtoB( X2Fix(0.0) );
    myErr = QTInsertChild(myEffectDesc, kParentAtomIsContainer, 'ycnt', 1 , 0, sizeof(long), &explodeCentreY, NULL);
 
    bail:
    ;
}
 
 
////////////////////
//
// QTEffects_MakeSampleDescription
// Return a new image description with default and specified values.
// 
////////////////////
 
static ImageDescriptionHandle QTEffects_MakeSampleDescription (OSType theEffectType, short theWidth, short theHeight)
{
    ImageDescriptionHandle      mySampleDesc = NULL;
 
    // create a new sample description
    mySampleDesc = (ImageDescriptionHandle)NewHandleClear(sizeof(ImageDescription));
    if (mySampleDesc == NULL)
        return(NULL);
    
    // fill in the fields of the sample description
    (**mySampleDesc).idSize = sizeof(ImageDescription);
    (**mySampleDesc).cType = theEffectType;
    (**mySampleDesc).vendor = kAppleManufacturer;
    (**mySampleDesc).temporalQuality = codecNormalQuality;
    (**mySampleDesc).spatialQuality = codecNormalQuality;
    (**mySampleDesc).width = theWidth;
    (**mySampleDesc).height = theHeight;
    (**mySampleDesc).hRes = 72L << 16;
    (**mySampleDesc).vRes = 72L << 16;
    (**mySampleDesc).frameCount = 1;
    (**mySampleDesc).depth = 0;
    (**mySampleDesc).clutID = -1;
    
    return(mySampleDesc);
}
 
 
 
////////////////////
//
// QTEffects_AddTrackReferenceToInputMap
// Add a track reference to the specified input map.
// 
////////////////////
 
static OSErr QTEffects_AddTrackReferenceToInputMap (QTAtomContainer theInputMap, Track theTrack, Track theSrcTrack, OSType theSrcName)
{
    OSErr               myErr = noErr;
    QTAtom              myInputAtom;
    long                myRefIndex;
    OSType              myType;
 
    myErr = AddTrackReference(theTrack, theSrcTrack, kTrackModifierReference, &myRefIndex);
    FAIL_OSERR(myErr);
            
    // add a reference atom to the input map
    myErr = QTInsertChild(theInputMap, kParentAtomIsContainer, kTrackModifierInput, myRefIndex, 0, 0, NULL, &myInputAtom);
    FAIL_OSERR(myErr);
    
    // add two child atoms to the parent reference atom
    myType = EndianU32_NtoB(kTrackModifierTypeImage);
    myErr = QTInsertChild(theInputMap, myInputAtom, kTrackModifierType, 1, 0, sizeof(myType), &myType, NULL);
    FAIL_OSERR(myErr);
    
    myType = EndianU32_NtoB(theSrcName);
    myErr = QTInsertChild(theInputMap, myInputAtom, kEffectDataSourceType, 1, 0, sizeof(myType), &myType, NULL);
        
bail:
    return(myErr);
}