Retired Document
Important: This sample code may not represent best practices for current development. The project may use deprecated symbols and illustrate technologies and techniques that are no longer recommended.
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); |
} |
Copyright © 2003 Apple Computer, Inc. All Rights Reserved. Terms of Use | Privacy Policy | Updated: 2003-02-25