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.
CMovieMaker.cp
/* |
A set of static functions for creating QTVR movies from a source PICT file. |
Created 29 Jan 1996 by EGH |
Copyright © 1996, Apple Computer, Inc. |
*/ |
#include <ImageCompression.h> |
#include <String_Utils.h> |
#include "CBeachBall.h" |
#include "CUtils.h" |
#include "QTVRPanoAuthoring.h" |
#include "CMovieMaker.h" |
/* CMovieMaker::MakeAMovie |
What we are here for. Takes the FSSpec for a source PICT file, a tile movie and |
a destination QTVR movie file and creates them. |
Return whether or not the operation completed. Note this function returns even |
if an error occurs - exceptions are handled here. |
*/ |
Boolean CMovieMaker::MakeAMovie( |
Boolean inReplaceFiles, |
const FSSpec &inSrcSpec, |
const FSSpec &inTileSpec, |
const FSSpec &inDestSpec, |
Int16 inWidth, |
Int16 inHeight, |
Fixed inPan, |
Fixed inTilt, |
Fixed inZoom, |
CodecType inCodec, |
CodecQ inSpatialQuality, |
Int16 inDepth, |
ProgressProc inProgressProc) |
{ |
Boolean completed = true; |
Int16 errStrIndex; |
PicHandle srcPicH = nil; |
FSSpec |
tileSpec = inTileSpec, |
destSpec = inDestSpec; |
try |
{ |
if (inProgressProc("\pCreating tile movie")) |
throw(Exception_UserCancelled); |
// find unique file names if necessary |
if (!inReplaceFiles) |
{ |
FSSpec tempSpec; // a place for FSMakeFSSpec to do whatever it might |
Int32 increment = 1; // just a number to place after the file name |
unsigned char added = 0; // chars added to the name in case they need to be removed |
OSErr result; |
Str255 |
suffixStr, |
numStr; |
do |
{ |
result = ::FSMakeFSSpec(tileSpec.vRefNum, tileSpec.parID, tileSpec.name, |
&tempSpec); |
if (result == noErr) |
{ |
// the file exists, so try another name |
tileSpec.name[0] -= added; |
CopyPStr("\p_", suffixStr); |
::NumToString(increment++, numStr); |
// if necessary, chop off characters so the suffix will fit |
// the changing part of the file name must be used |
unsigned char charspace = 31 - suffixStr[0]; |
if (tileSpec.name[0] > charspace) |
tileSpec.name[0] = charspace; |
ConcatPStr(suffixStr, numStr); |
added = suffixStr[0]; |
ConcatPStr(tileSpec.name, suffixStr); |
} |
} while (result == noErr); |
added = 0; |
increment = 1; |
do |
{ |
result = ::FSMakeFSSpec(destSpec.vRefNum, destSpec.parID, destSpec.name, |
&tempSpec); |
if (result == noErr) |
{ |
// the file exists, so try another name |
destSpec.name[0] -= added; |
CopyPStr("\p_", suffixStr); |
::NumToString(increment++, numStr); |
// if necessary, chop off characters so the suffix will fit |
// the changing part of the file name must be used |
unsigned char charspace = 31 - suffixStr[0]; |
if (destSpec.name[0] > charspace) |
destSpec.name[0] = charspace; |
ConcatPStr(suffixStr, numStr); |
added = suffixStr[0]; |
ConcatPStr(destSpec.name, suffixStr); |
} |
} while (result == noErr); |
} |
// open the pict file and read the picture into a handle |
errStrIndex = err_OpenPicture; |
LFile pictFile(inSrcSpec); |
Int16 fileRef = pictFile.OpenDataFork(fsRdPerm); |
Int32 logEOF; |
ThrowIfOSErr_(::GetEOF(fileRef, &logEOF)); |
logEOF -= 512; // dont need the header |
ThrowIfOSErr_(::SetFPos(fileRef, fsFromStart, 512)); |
srcPicH = (PicHandle)::NewHandle(logEOF); |
ThrowIfMemError_(); |
ThrowIfOSErr_(::FSRead(fileRef, &logEOF, *srcPicH)); |
pictFile.CloseDataFork(); |
// examine the PICT's rectangle |
Int16 |
picHeight = (*srcPicH)->picFrame.bottom - (*srcPicH)->picFrame.top, |
picWidth = (*srcPicH)->picFrame.right - (*srcPicH)->picFrame.left; |
// check for correct orientation |
if (picHeight < picWidth) |
{ |
CBeachBall::StopSpinningTask(); |
::SetCursor(&qd.arrow); |
Str255 |
cwstr, chstr; |
::NumToString(picWidth, cwstr); |
::NumToString(picHeight, chstr); |
gApp->BugUserTilSwitchedIn(); |
::ParamText(cwstr, chstr, "\p", "\p"); |
if (::CautionAlert(ALRT_ConfirmOrientation, nil) != ok) |
{ |
errStrIndex = err_BadPictHeight; |
throw(Exception_UserCancelled); |
} |
CBeachBall::StartSpinningTask(5); |
} |
// check for % 96 height and % 4 width |
if ((picHeight % 96) != 0 || (picWidth % 4) != 0) |
{ |
CBeachBall::StopSpinningTask(); |
::SetCursor(&qd.arrow); |
Str255 |
hstr, wstr; |
if ((picHeight % 96) != 0) |
{ |
Int16 newPicHeight = picHeight - (picHeight % 96); |
double scale = (double)newPicHeight / (double)picHeight; |
Int16 newPicWidth = (Int16)((double)picWidth * scale); |
newPicWidth = newPicWidth - (newPicWidth % 4); |
::NumToString(newPicHeight, hstr); |
::NumToString(newPicWidth, wstr); |
} |
else |
::NumToString(picHeight, hstr); |
if ((picWidth % 4) != 0) |
{ |
Int16 newPicWidth = picWidth - (picWidth % 4); |
::NumToString(newPicWidth, wstr); |
} |
Str255 |
cwstr, chstr; |
::NumToString(picWidth, cwstr); |
::NumToString(picHeight, chstr); |
gApp->BugUserTilSwitchedIn(); |
::ParamText(cwstr, chstr, wstr, hstr); |
if (::CautionAlert(ALRT_ConfirmSize, nil) != ok) |
{ |
errStrIndex = err_BadPictHeight; |
throw(Exception_UserCancelled); |
} |
CBeachBall::StartSpinningTask(5); |
} |
// create the tile movie file |
errStrIndex = err_CreateTileMovie; |
CreateTileMovie(srcPicH, tileSpec, inCodec, inSpatialQuality, inDepth, inProgressProc); |
::DisposeHandle((Handle)srcPicH); |
srcPicH = nil; |
// create the single node movie |
errStrIndex = err_CreatePanoMovie; |
if (inProgressProc("\pCreating panorama movie")) |
throw(Exception_UserCancelled); |
CreateSingleNodeMovie(tileSpec, destSpec, inWidth, inHeight, inPan, inTilt, inZoom); |
} |
catch (ExceptionCode err) |
{ |
if (srcPicH != nil) |
::DisposeHandle((Handle)srcPicH); |
CBeachBall::StopSpinningTask(); |
if (err != Exception_UserCancelled) |
ReportError(err, errStrIndex); |
completed = false; |
} |
return completed; |
} |
/* CMovieMaker::QTProgress |
A progress function passed to QuickTime and called during |
compression. This was implemented to provide the user |
finer control over the UI during processing. |
It simply calls the app's progress proc and returns an error if the |
user aborted the operation. |
*/ |
//static |
pascal OSErr CMovieMaker::QTProgress( |
short inMessage, |
Fixed inCompleteness, |
long inRefcon) |
{ |
if (((ProgressProc)inRefcon)(nil)) |
return codecAbortErr; |
return noErr; |
} |
/* CMovieMaker::CreateSingleNodeMovie |
Create a QuickTime VR movie from the passed movie containg tiles. |
*/ |
void CMovieMaker::CreateSingleNodeMovie( |
const FSSpec &inTileSpec, |
const FSSpec &inMovieSpec, |
Int16 inWidth, |
Int16 inHeight, |
Fixed inPan, |
Fixed inTilt, |
Fixed inZoom) |
{ |
OSErr result; |
short tileResRefNum = 0; |
Movie tileMovie = nil; |
short resID = 0; |
Track sceneTrack; |
Fixed |
sceneWidth, sceneHeight; |
long sceneTrackID = 0; |
Movie panoMovie = nil; |
short panoResRefNum = 0; |
PanoramaDescriptionHandle panoDesc = nil; |
PanoSampleHeaderAtomHandle panoHeader = nil; |
try |
{ |
result = ::OpenMovieFile(&inTileSpec, &tileResRefNum, fsRdPerm); |
ThrowIfOSErr_(result); |
result = ::NewMovieFromFile(&tileMovie, tileResRefNum, &resID, 0, 0, 0); |
ThrowIfOSErr_(result); |
sceneTrack = ::GetMovieIndTrack(tileMovie, 1); |
ThrowIfNil_(sceneTrack); |
::SetTrackEnabled(sceneTrack, false); |
::GetTrackDimensions(sceneTrack, &sceneWidth, &sceneHeight); |
sceneWidth = sceneWidth >> 16; |
sceneHeight = sceneHeight >> 16; |
sceneTrackID = ::GetTrackID(sceneTrack); |
UserData userData; |
OSType controllerSubType; |
userData = ::GetMovieUserData(tileMovie); |
controllerSubType = kPanoMediaType; |
::SetUserDataItem( |
userData, &controllerSubType, sizeof(controllerSubType), 'ctyp', 0); |
panoMovie = ::FlattenMovieData( |
tileMovie, flattenDontInterleaveFlatten, &inMovieSpec, |
'vrod', 0, createMovieFileDeleteCurFile); |
result = ::OpenMovieFile(&inMovieSpec, &panoResRefNum, fsRdWrPerm); |
ThrowIfOSErr_(result); |
::AddMovieResource(panoMovie, panoResRefNum, &resID, 0); |
Track panoTrack; |
panoTrack = ::NewMovieTrack( |
panoMovie, ((long)inWidth) << 16, ((long)inHeight) << 16, 0); |
TimeScale panoTimeScale; |
panoTimeScale = ::GetMovieTimeScale(panoMovie); |
Media panoMedia; |
panoMedia = ::NewTrackMedia(panoTrack, kPanoMediaType, panoTimeScale, 0, 0); |
result = ::GetMoviesError(); |
ThrowIfOSErr_(result); |
TimeValue panoDuration; |
panoDuration = ::GetMovieDuration(panoMovie); |
panoDesc = (PanoramaDescriptionHandle)::NewHandleClear( |
sizeof (PanoramaDescription)); |
ThrowIfOSErr_(::MemError()); |
(*panoDesc)->size = sizeof (PanoramaDescription); |
(*panoDesc)->type = kPanDescType; |
(*panoDesc)->sceneTrackID = sceneTrackID; |
(*panoDesc)->hotSpotTrackID = 0; // no hot sport tracks |
for (int i = 1; i < 6; i++) |
{ |
(*panoDesc)->reserved3[i] = 0; |
(*panoDesc)->reserved4[i] = 0; |
} |
(*panoDesc)->sceneNumFramesX = 1; |
(*panoDesc)->sceneNumFramesY = 24; |
(*panoDesc)->numFrames = 24; |
(*panoDesc)->sceneSizeX = sceneWidth; |
(*panoDesc)->sceneSizeY = sceneHeight * 24; |
(*panoDesc)->hPanStart = 0; |
(*panoDesc)->hPanEnd = 360 << 16; |
float theta = |
180.0 * (atan((*panoDesc)->sceneSizeX * 3.14159 / |
(*panoDesc)->sceneSizeY)) / 3.14159; |
(*panoDesc)->vPanTop = (Fixed)(theta * 65536); |
(*panoDesc)->vPanBottom = (Fixed)(-theta * 65536); |
(*panoDesc)->minimumZoom = 0; |
(*panoDesc)->maximumZoom = 0; |
(*panoDesc)->sceneColorDepth = 32; |
// no hot spots |
(*panoDesc)->hotSpotNumFramesX = 0; |
(*panoDesc)->hotSpotNumFramesY = 0; |
(*panoDesc)->hotSpotSizeX = 0; |
(*panoDesc)->hotSpotSizeY = 0; |
(*panoDesc)->hotSpotColorDepth = 8; |
// fill in the panorama header atom data |
panoHeader = (PanoSampleHeaderAtomHandle)::NewHandleClear( |
sizeof(PanoSampleHeaderAtom)); |
(*panoHeader)->size = sizeof(PanoSampleHeaderAtom); |
(*panoHeader)->type = kPanHeaderType; |
(*panoHeader)->nodeID = 0; |
// check pan for obvious errors |
if (inPan < 0) |
inPan = 0; |
if (inPan > 360 << 16) |
inPan = 360 << 16; |
// set the default pan, tilt & zoom |
(*panoHeader)->defHPan = inPan; |
(*panoHeader)->defVPan = inTilt; |
if (inZoom == 0) // default: use some reasonable value |
(*panoHeader)->defZoom = (Fixed)(1.5 * theta * 65536); |
else |
(*panoHeader)->defZoom = inZoom; |
(*panoHeader)->commentStrOffset = 0; |
result = ::BeginMediaEdits(panoMedia); |
ThrowIfOSErr_(result); |
result = ::AddMediaSample(panoMedia, (Handle)panoHeader, 0, |
(*panoHeader)->size, panoDuration, |
(SampleDescriptionHandle)panoDesc, 1, 0, nil); |
if (result != noErr) |
{ |
::EndMediaEdits(panoMedia); |
ThrowIfOSErr_(result); |
} |
result = ::EndMediaEdits(panoMedia); |
ThrowIfOSErr_(result); |
::InsertMediaIntoTrack(panoTrack, 0, 0, |
::GetMediaDuration(panoMedia), 1 << 16); |
ThrowIfOSErr_(::GetMoviesError()); |
result = ::UpdateMovieResource(panoMovie, panoResRefNum, resID, 0); |
ThrowIfOSErr_(result); |
::CloseMovieFile(tileResRefNum); |
::DisposeMovie(tileMovie); |
::CloseMovieFile(panoResRefNum); |
::DisposeMovie(panoMovie); |
::DisposeHandle((Handle)panoDesc); |
::DisposeHandle((Handle)panoHeader); |
} |
catch(ExceptionCode inErr) |
{ |
if (tileResRefNum != 0) |
::CloseMovieFile(tileResRefNum); |
if (tileMovie != nil) |
::DisposeMovie(tileMovie); |
if (panoResRefNum != 0) |
::CloseMovieFile(panoResRefNum); |
if (panoMovie != nil) |
::DisposeMovie(panoMovie); |
if (panoDesc != nil) |
::DisposeHandle((Handle)panoDesc); |
if (panoHeader != nil) |
::DisposeHandle((Handle)panoHeader); |
throw(inErr); |
} |
} |
/* CMovieMaker::CreateTileMovie |
Create a QuickTime movie containing 24 tiles created from the passed PICT. |
*/ |
void CMovieMaker::CreateTileMovie( |
PicHandle inPicH, |
const FSSpec &inTileSpec, |
CodecType inCodec, |
CodecQ inSpatialQuality, |
Int16 inDepth, |
ProgressProc inProgressProc) |
{ |
Rect |
pictRect, tileRect; |
LGWorld *tileWorld = nil; |
OSErr result; |
Movie tileMovie = nil; |
short movieResRefNum = 0; |
Track tileTrack; |
Media tileMedia; |
Handle compressedData = nil; |
ImageDescriptionHandle imageDesc = nil; |
Ptr compressedDataPtr; |
try |
{ |
// create a gworld to draw the tiles into |
pictRect = tileRect = (*inPicH)->picFrame; |
tileRect.bottom = tileRect.top + ((tileRect.bottom - tileRect.top) / 24); |
tileWorld = new LGWorld(tileRect, 32, 0, nil, nil); |
// create the tile movie file |
result = ::CreateMovieFile( |
&inTileSpec, |
'TVOD', 0, |
createMovieFileDeleteCurFile, |
&movieResRefNum, |
&tileMovie); |
ThrowIfOSErr_(result); |
// create a track & media to contain the tiles |
tileTrack = ::NewMovieTrack( |
tileMovie, |
((long)(tileRect.right - tileRect.left)) << 16, |
((long)(tileRect.bottom - tileRect.top)) << 16, |
0); |
ThrowIfNil_(tileTrack); |
tileMedia = ::NewTrackMedia( |
tileTrack, |
VideoMediaType, 600, 0, 0); |
ThrowIfNil_(tileMedia); |
result = ::BeginMediaEdits(tileMedia); |
ThrowIfOSErr_(result); |
// prepare an image description handle |
imageDesc = (ImageDescriptionHandle)::NewHandle(4); |
ThrowIfOSErr_(::MemError()); |
// set up the progress record |
ICMProgressProcRecord qtproc; |
qtproc.progressProc = NewICMProgressProc(QTProgress); |
qtproc.progressRefCon = (long)inProgressProc; |
long compressedFrameSize; |
TimeValue |
duration = 60, currentTime; |
for (Int16 i = 0; i < 24; i++) |
{ |
// let the user know what is going on |
Str255 |
s1, s2; |
CopyPStr("\pCompressing tile ", s1); |
::NumToString(i + 1, s2); |
ConcatPStr(s1, s2); |
ConcatPStr(s1, "\p of 24"); |
if (inProgressProc(s1)) |
throw(Exception_UserCancelled); |
// draw the picture into the tile gworld |
tileWorld->BeginDrawing(); |
::DrawPicture(inPicH, &pictRect); |
tileWorld->EndDrawing(); |
// offset the pict rect for next time |
::OffsetRect(&pictRect, 0, -(tileRect.bottom - tileRect.top)); |
// prepare a handle for compression |
::LockPixels(::GetGWorldPixMap(tileWorld->GetMacGWorld())); |
result = GetMaxCompressionSize( |
::GetGWorldPixMap(tileWorld->GetMacGWorld()), |
&tileRect, inDepth, inSpatialQuality, |
inCodec, anyCodec, &compressedFrameSize); |
::UnlockPixels(::GetGWorldPixMap(tileWorld->GetMacGWorld())); |
ThrowIfOSErr_(result); |
compressedData = ::NewHandle(compressedFrameSize); |
ThrowIfOSErr_(::MemError()); |
::HLock(compressedData); |
compressedDataPtr = StripAddress((*compressedData)); |
// compress the tile |
::LockPixels(::GetGWorldPixMap(tileWorld->GetMacGWorld())); |
result = ::FCompressImage( |
::GetGWorldPixMap(tileWorld->GetMacGWorld()), &tileRect, |
0, inSpatialQuality, inCodec, anyCodec, |
nil, 0, 0, nil, &qtproc, |
imageDesc, compressedDataPtr); |
::UnlockPixels(::GetGWorldPixMap(tileWorld->GetMacGWorld())); |
if (result == codecAbortErr) |
throw(Exception_UserCancelled); // the user did it |
ThrowIfOSErr_(result); |
// add the tile to the movie |
result = ::AddMediaSample(tileMedia, compressedData,0, |
(*imageDesc)->dataSize, duration, |
(SampleDescriptionHandle)imageDesc, 1, 0, ¤tTime); |
ThrowIfOSErr_(result); |
::DisposeHandle(compressedData); |
compressedData = nil; |
} |
delete tileWorld; |
tileWorld = nil; |
result = ::EndMediaEdits(tileMedia); |
ThrowIfOSErr_(result); |
::InsertMediaIntoTrack( |
tileTrack, |
0, 0, |
GetMediaDuration(tileMedia), 1L<<16); |
ThrowIfOSErr_(::GetMoviesError()); |
short resID = 128; |
result = ::AddMovieResource( |
tileMovie, movieResRefNum, &resID, "\pMovie 1"); |
ThrowIfOSErr_(result); |
::DisposeHandle((Handle)imageDesc); |
imageDesc = nil; |
::DisposeHandle(compressedData); |
compressedData = nil; |
::CloseMovieFile(movieResRefNum); |
::DisposeMovie(tileMovie); |
} |
catch(ExceptionCode inErr) |
{ |
if (tileWorld != nil) |
delete tileWorld; |
// at least try to delete the tile movie file |
if (movieResRefNum != 0) |
{ |
::CloseMovieFile(movieResRefNum); |
::DeleteMovieFile(&inTileSpec); |
} |
if (compressedData != nil) |
::DisposeHandle(compressedData); |
if (imageDesc != nil) |
::DisposeHandle((Handle)imageDesc); |
if (tileMovie != nil) |
::DisposeMovie(tileMovie); |
throw(inErr); |
} |
} |
Copyright © 2003 Apple Computer, Inc. All Rights Reserved. Terms of Use | Privacy Policy | Updated: 2003-01-14