//////////
//
//	File:		EffectsUtilities.c
//
//	Contains:	Some utilities for working with QuickTime video effects.
//
//	Written by:	Tim Monroe
//
//	Copyright:	© 2001 by Apple Computer, Inc., all rights reserved.
//
//	Change History (most recent first):
//
//	   <1>	 	07/10/01	rtm		first file
//	   
//
//////////

#ifndef __EFFECTSUTILITIES__
#include "EffectsUtilities.h"
#endif


///////////////////////////////////////////////////////////////////////////////////////////////////////////
//
// Effects utilities.
//
// Use these functions to set up and run QuickTime video effects.
//
///////////////////////////////////////////////////////////////////////////////////////////////////////////

//////////
//
// EffectsUtils_CreateEffectDescription
// Create an effect description for zero, one, two, or three 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.
//
//////////

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

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

	// create the effect ID atom: the atom type is kParameterWhatName, and the atom ID is kParameterWhatID
	myErr = QTInsertChild(myEffectDesc, kParentAtomIsContainer, kParameterWhatName, kParameterWhatID, 0, sizeof(myType), &myType, NULL);
	if (myErr != noErr)
		goto bail;
		
	// add the first source
	if (theSourceName1 != kSourceNoneName) {
		myType = EndianU32_NtoB(theSourceName1);
		myErr = QTInsertChild(myEffectDesc, kParentAtomIsContainer, kEffectSourceName, 1, 0, sizeof(myType), &myType, NULL);
		if (myErr != noErr)
			goto bail;
	}
							
	// add the second source
	if (theSourceName2 != kSourceNoneName) {
		myType = EndianU32_NtoB(theSourceName2);
		myErr = QTInsertChild(myEffectDesc, kParentAtomIsContainer, kEffectSourceName, 2, 0, sizeof(myType), &myType, NULL);
		if (myErr != noErr)
			goto bail;
	}

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

bail:
	return(myEffectDesc);
}


//////////
//
// EffectsUtils_GetTypeFromEffectDescription
// Given an effect description, return the type of the effect it describes.
// 
//////////

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

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

	myEffectAtom = QTFindChildByIndex(theEffectDesc, kParentAtomIsContainer, kParameterWhatName, kParameterWhatID, NULL);
	if (myEffectAtom != 0) {
	
		myErr = QTLockContainer(theEffectDesc);
		if (myErr != noErr)
			goto bail;

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

		if (myEffectTypeSize != sizeof(OSType)) {
			myErr = paramErr;
			goto bail;
		}
		
		*theEffectType = *(OSType *)myEffectTypePtr;
		*theEffectType = EndianU32_BtoN(*theEffectType);	// because the data is read from an atom container
		
		myErr = QTUnlockContainer(theEffectDesc);
	}
	
bail:
	return(myErr);
}


//////////
//
// EffectsUtils_AddTrackReferenceToInputMap
// Add a track reference to the specified input map.
// 
//////////

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

	myErr = AddTrackReference(theTrack, theSrcTrack, kTrackReferenceModifier, &myRefIndex);
	if (myErr != noErr)
		goto bail;
			
	// add a reference atom to the input map
	myErr = QTInsertChild(theInputMap, kParentAtomIsContainer, kTrackModifierInput, myRefIndex, 0, 0, NULL, &myInputAtom);
	if (myErr != noErr)
		goto bail;
	
	// add two child atoms to the parent reference atom
	myType = EndianU32_NtoB(kTrackModifierTypeImage);
	myErr = QTInsertChild(theInputMap, myInputAtom, kTrackModifierType, 1, 0, sizeof(myType), &myType, NULL);
	if (myErr != noErr)
		goto bail;
	
	myType = EndianU32_NtoB(theSrcName);
	myErr = QTInsertChild(theInputMap, myInputAtom, kEffectDataSourceType, 1, 0, sizeof(myType), &myType, NULL);
		
bail:
	return(myErr);
}
 
 
//////////
//
// EffectsUtils_MakeSampleDescription
// Return a new image description with default and specified values.
// 
//////////

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

#if USES_MAKE_IMAGE_DESC_FOR_EFFECT
	OSErr						myErr = noErr;
	
	// create a new sample description
	myErr = MakeImageDescriptionForEffect(theEffectType, &mySampleDesc);
	if (myErr != noErr)
		return(NULL);
#else
	// create a new sample description
	mySampleDesc = (ImageDescriptionHandle)NewHandleClear(sizeof(ImageDescription));
	if (mySampleDesc == NULL)
		return(NULL);
		
	// fill in the fields of the sample description
	(**mySampleDesc).cType = theEffectType;
	(**mySampleDesc).idSize = sizeof(ImageDescription);
	(**mySampleDesc).hRes = 72L << 16;
	(**mySampleDesc).vRes = 72L << 16;
	(**mySampleDesc).frameCount = 1;
	(**mySampleDesc).depth = 0;
	(**mySampleDesc).clutID = -1;
#endif
	
	(**mySampleDesc).vendor = kAppleManufacturer;
	(**mySampleDesc).temporalQuality = codecNormalQuality;
	(**mySampleDesc).spatialQuality = codecNormalQuality;
	(**mySampleDesc).width = theWidth;
	(**mySampleDesc).height = theHeight;
	
	return(mySampleDesc);
}


//////////
//
// EffectsUtils_GetEffectDescFromQFXFile
// Open the specified effects parameter file and get the effect description in it.
// 
//////////

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

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

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

		if (myType == FOUR_CHAR_CODE('qtfx')) {
			myEffectDesc = NewHandleClear(mySize);
			if (myEffectDesc == NULL)
				goto bail;
				
			myErr = FSRead(myRefNum, &mySize, *myEffectDesc);

		} else {
			SetFPos(myRefNum, fsFromMark, mySize);
		}
	}	
	
bail:
	return((QTAtomContainer)myEffectDesc);
}


///////////////////////////////////////////////////////////////////////////////////////////////////////////
//
// General imaging utilities.
//
// Use these functions to draw pictures into GWorlds and do other imaging operations.
//
///////////////////////////////////////////////////////////////////////////////////////////////////////////

//////////
//
// EffectsUtils_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.
//
//////////

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

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

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

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

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

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

	EraseRect(&myRect);
	DrawPicture(myHandle, &myRect);
	
	if (myPixMap != NULL)
		UnlockPixels(myPixMap);
	
bail:
	// restore the previous port and device
	SetGWorld(mySavedPort, mySavedDevice);

	if (myHandle != NULL)
		ReleaseResource((Handle)myHandle);
	
	return(myErr);
}


//////////
//
// EffectsUtils_AddVideoTrackFromGWorld
// Add to the specified movie a video track for the specified picture resource.
//
//////////

OSErr EffectsUtils_AddVideoTrackFromGWorld (Movie *theMovie, GWorldPtr theGW, Track *theSourceTrack, long theStartTime, TimeValue theDuration, short theWidth, short theHeight)
{
	Media						myMedia;
	ImageDescriptionHandle		mySampleDesc = NULL;
	Rect						myRect;
	Rect						myRect2;
	Rect						myRect3;
	long						mySize;
	Handle						myData = NULL;
	Ptr							myDataPtr = NULL;
	GWorldPtr					myGWorld = NULL;
	CGrafPtr 					mySavedPort = NULL;
	GDHandle 					mySavedGDevice = NULL;
	PicHandle					myHandle = NULL;
	PixMapHandle				mySrcPixMap = NULL;
	PixMapHandle				myDstPixMap = NULL;
	OSErr						myErr = noErr;
	
	// get the current port and device
	GetGWorld(&mySavedPort, &mySavedGDevice);
	
	// create a video track in the movie
	*theSourceTrack = NewMovieTrack(*theMovie, IntToFixed(theWidth), IntToFixed(theHeight), kNoVolume);
	if (theSourceTrack == NULL)
		goto bail;
		
	myMedia = NewTrackMedia(*theSourceTrack, VideoMediaType, kVideoTrackTimeScale, NULL, 0);
	if (myMedia == NULL)
		goto bail;

	// get the rectangle for the movie
	GetMovieBox(*theMovie, &myRect);
	
	// begin editing the new track
	myErr = BeginMediaEdits(myMedia);
	if (myErr != noErr)
		goto bail;
		
	// create a new GWorld; we draw the picture into this GWorld and then compress it
	// (note that we are creating a picture with the maximum bit depth)
	myErr = NewGWorld(&myGWorld, 32, &myRect, NULL, NULL, 0L);
	if (myErr != noErr)
		goto bail;
	
	mySrcPixMap = GetGWorldPixMap(theGW);
	// LockPixels(mySrcPixMap);
	
	myDstPixMap = GetGWorldPixMap(myGWorld);
	LockPixels(myDstPixMap);
	
	// create a new image description; CompressImage will fill in the fields of this structure
	mySampleDesc = (ImageDescriptionHandle)NewHandle(4);
	
	SetGWorld(myGWorld, NULL);
#if TARGET_OS_MAC
	GetPortBounds(theGW, &myRect2);
	GetPortBounds(myGWorld, &myRect3);
#endif
#if TARGET_OS_WIN32
	myRect2 = theGW->portRect;
	myRect3 = myGWorld->portRect;
#endif

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

	// restore the original port and device
	SetGWorld(mySavedPort, mySavedGDevice);
	
	myErr = GetMaxCompressionSize(myDstPixMap, &myRect, 0, codecNormalQuality, kJPEGCodecType, anyCodec, &mySize);
	if (myErr != noErr)
		goto bail;
		
	myData = NewHandle(mySize);
	if (myData == NULL)
		goto bail;
		
	HLockHi(myData);
#if TARGET_CPU_68K
	myDataPtr = StripAddress(*myData);
#else
	myDataPtr = *myData;
#endif
	myErr = CompressImage(myDstPixMap, &myRect, codecNormalQuality, kJPEGCodecType, mySampleDesc, myDataPtr);
	if (myErr != noErr)
		goto bail;
		
	myErr = AddMediaSample(myMedia, myData, 0, (**mySampleDesc).dataSize, theDuration, (SampleDescriptionHandle)mySampleDesc, 1, 0, NULL);
	if (myErr != noErr)
		goto bail;

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

	myErr = InsertMediaIntoTrack(*theSourceTrack, theStartTime, 0, GetMediaDuration(myMedia), fixed1);
	
bail:
	// restore the original port and device
	SetGWorld(mySavedPort, mySavedGDevice);
	
	if (myData != NULL) {
		HUnlock(myData);
		DisposeHandle(myData);
	}

	if (mySampleDesc != NULL)
		DisposeHandle((Handle)mySampleDesc);
		
	// if (mySrcPixMap != NULL)
	// 	UnlockPixels(mySrcPixMap);
		
	if (myDstPixMap != NULL)
		UnlockPixels(myDstPixMap);
		
	if (myGWorld != NULL)
		DisposeGWorld(myGWorld);
	
	return(myErr);
}


//////////
//
// EffectsUtils_GetFrontmostTrackLayer
// Return the layer number of the frontmost track of the specified kind in a movie.
// 
//////////

short EffectsUtils_GetFrontmostTrackLayer (Movie theMovie, OSType theTrackType)
{
	short		myLayer = 0;
	short		myIndex = 1;
	Track		myTrack = NULL;
	
	// get the layer number of the first track of the specified kind;
	// if no track of that kind exists in the movie, return 0
	myTrack = GetMovieIndTrackType(theMovie, 1, theTrackType, movieTrackMediaType | movieTrackEnabledOnly);
	if (myTrack == NULL)
		return(myLayer);
		
	myLayer = GetTrackLayer(myTrack);
	
	// see if any of the remaining tracks have lower layer numbers
	while (myTrack != NULL) {
		if (myLayer > GetTrackLayer(myTrack))
			myLayer = GetTrackLayer(myTrack);
		myIndex++;
		myTrack = GetMovieIndTrackType(theMovie, myIndex, theTrackType, movieTrackMediaType | movieTrackEnabledOnly);
	}
	
	return(myLayer);
}


