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.
pictmovier.c
/* |
File: PictMovier.c |
Contains: Application to make a movie from PICTs with various processes |
Written by: Mark Krueger |
Copyright: © 1990-1994 by Apple Computer, Inc., all rights reserved. |
Change History (most recent first): |
12/06/94 khs Fixes for MW |
06/05/94 PM/MHK New Today |
This example shows how you can make movies by doing processing on pictures. |
This allows a process that takes a long time to be shown at much faster rates |
as a QuickTime movie. Any type of processing can be done, such as filtering, |
transitions, 3-D transformations. This example shows two trivial examples. |
The first simple does a linear cross fade between two images. The second creates |
a random dot stereogram ( an image that if you stare at it long enough you can |
see three dimensional depth in ( without special glasses ). This works from a single |
image and shows how the effect can be varied from frame to frame. |
Note that the code is not necessarily well commented, but should prove useful as |
a jumping off point for making your own movie creation applications. |
*/ |
// INCLUDES |
#include <Types.h> |
#include <Files.h> |
#include <Quickdraw.h> |
#include <Packages.h> |
#include <Memory.h> |
#include <Fonts.h> |
#include <Events.h> |
#include <OSUtils.h> |
#include <ToolUtils.h> |
#include <Menus.h> |
#include <Dialogs.h> |
#include <stdio.h> |
#include <Errors.h> |
#include <Scrap.h> |
#include <Desk.h> |
#include <string.h> |
#include <GestaltEqu.h> |
#include <Resources.h> |
#include <Finder.h> |
#include <PictUtil.h> |
#include <palettes.h> |
#include <OSEvents.h> |
#include <Math.h> |
#include <SegLoad.h> |
#include <Windows.h> |
#ifndef THINK_C |
#include <Strings.h> |
#endif |
#include <ImageCompression.h> |
#include <QuickTimeComponents.h> |
#include <Components.h> |
#include <Movies.h> |
// DEFINES |
#define kNumberSteps 30 |
#define kFrameRate 10 /* frames per second */ |
#define kCodecID anyCodec |
#define kCodecType (CodecType) 'rpza' |
#define kCodecDepth 32 |
#define kCodecQuality codecNormalQuality |
// FUNCTION PROTOTYPES |
void FixMenus(void); |
void SetupMenus(void); |
void Error(char* msg, OSErr code); |
void ShowAboutBox(void); |
void Initialize(void); |
long DoClose(WindowPtr wind); |
void SetCompression(void); |
void SetStages(void); |
void DoHLock(Handle h); |
void DoHUnlock(Handle h); |
void DoDisposHandle(Handle h); |
void DoPreview(void); |
void DoMovie(void); |
void DoCopy(void); |
void DoCut(void); |
void DoPaste(void); |
void DoClear(void); |
void DoUndo(void); |
Boolean HandleEvents(EventRecord* myEvent); |
OSErr DoOpen(FSSpec* fsp); |
long BetterRand(long reseed); |
unsigned char DecodeDepth(unsigned char pix); |
void DoCrossFade(long stageNum, |
long totalStages, |
Boolean reverse); |
void DoRandomDotStereogram(long stageNum, |
long totalStages, |
Boolean reverse); |
void DoStage(long stageNum, |
long totalStages, |
Boolean reverse); |
void DoClear(); |
void DoUpdate(CWindowPtr wind); |
short DoCommand(long mResult); |
OSErr AllocateBuffers(void); |
CWindowPtr MakeSWindow(Rect* frame, |
StringPtr name, |
GWorldPtr buffer); |
// GLOBALS |
CWindowPtr gSrcWindow = nil; |
CWindowPtr gAltWindow = nil; |
CWindowPtr gDstWindow = nil; |
Rect gOriginalPicFrame; |
Boolean gZoomed = false; |
Boolean gFitToWindow = true; |
Boolean gHasNewStdFile = false; |
PicHandle gOriginalPicture = nil; |
Boolean gCompressed = false; |
GWorldPtr gWorld = nil; |
GWorldPtr gAltWorld = nil; |
GWorldPtr gDstWorld = nil; |
GWorldPtr gBufferWorld = nil; |
long gFrameNumber = -1; |
MenuHandle gMenus[4]; // our menus |
Boolean gExitFlag = false; |
WindowPtr gActiveWindow = nil; |
Rect gRect; |
short gStripWidth = 128; |
short gDepthFactor = 16; |
Boolean gDepthDecodeTabInted = false; |
CodecType gCodecType = kCodecType; |
CodecQ gCodecQuality = kCodecQuality; |
CodecQ gCodecTemporalQuality = 0; |
short gCodecDepth = kCodecDepth; |
long gFrameRate = kFrameRate; |
long gKeyFrameRate = kFrameRate; |
CodecComponent gCodecID = kCodecID; |
long gNumberSteps = kNumberSteps; |
Boolean gOversample = false; |
Boolean gBackwards = false; |
#define APPLE_MENU 0 |
#define APPLE_MENU_ID 128 |
#define FILE_MENU 1 |
#define FILE_MENU_ID 129 |
#define FILE_M_OPEN 1 |
#define FILE_M_CLOSE 2 |
#define FILE_M_SAVE 3 |
#define FILE_M_QUIT 5 |
#define EDIT_MENU 2 |
#define EDIT_MENU_ID 130 |
#define EDIT_M_UNDO 1 |
#define EDIT_M_CUT 3 |
#define EDIT_M_COPY 4 |
#define EDIT_M_PASTE 5 |
#define EDIT_M_CLEAR 6 |
#define MOVIE_MENU 3 |
#define MOVIE_MENU_ID 131 |
#define MOVIE_M_COMPRESS 1 |
#define MOVIE_M_STAGES 2 |
#define MOVIE_M_OVERSAMPLE 3 |
#define MOVIE_M_BACKWARDS 4 |
#define MOVIE_M_PREVIEW 6 |
#define MOVIE_M_MOVIE 8 |
Boolean gDoCrossFade = true; // type of processing to do ( set for crossfade, set to false to do random dot stereograms ) |
Boolean gRequiresAlternate = true; // if true means requires second picture for processing |
short gDepth = 32; // pixel depth of allocated image buffers |
/********************************************* |
Funtion prototypes. |
*/ |
/********************************************* |
Clean up menu hiliting |
*********************************************/ |
void FixMenus(void) |
{ |
Boolean gotAllWindows = gSrcWindow != nil && (!gRequiresAlternate || gAltWindow != nil); |
Boolean gotAnyWindows = gSrcWindow != nil || gAltWindow != nil; |
DisableItem(gMenus[FILE_MENU], FILE_M_SAVE); |
if (gotAnyWindows) |
EnableItem(gMenus[FILE_MENU], FILE_M_CLOSE); |
else |
DisableItem(gMenus[FILE_MENU], FILE_M_CLOSE); |
if (gotAllWindows) |
DisableItem(gMenus[FILE_MENU], FILE_M_OPEN); |
else |
EnableItem(gMenus[FILE_MENU], FILE_M_OPEN); |
if (!gotAllWindows) |
{ |
DisableItem(gMenus[MOVIE_MENU], MOVIE_M_MOVIE); |
DisableItem(gMenus[MOVIE_MENU], MOVIE_M_PREVIEW); |
DisableItem(gMenus[MOVIE_MENU], MOVIE_M_OVERSAMPLE); |
} |
else |
{ |
EnableItem(gMenus[MOVIE_MENU], MOVIE_M_MOVIE); |
EnableItem(gMenus[MOVIE_MENU], MOVIE_M_PREVIEW); |
EnableItem(gMenus[MOVIE_MENU], MOVIE_M_OVERSAMPLE); |
} |
DisableItem(gMenus[EDIT_MENU], EDIT_M_CUT); |
DisableItem(gMenus[EDIT_MENU], EDIT_M_COPY); |
DisableItem(gMenus[EDIT_MENU], EDIT_M_PASTE); |
DisableItem(gMenus[EDIT_MENU], EDIT_M_CLEAR); |
DisableItem(gMenus[EDIT_MENU], EDIT_M_UNDO); |
CheckItem(gMenus[MOVIE_MENU], MOVIE_M_OVERSAMPLE, gOversample); |
CheckItem(gMenus[MOVIE_MENU], MOVIE_M_BACKWARDS, gBackwards); |
} |
/********************************************* |
Initialize menu bar. |
*********************************************/ |
void SetupMenus(void) |
{ |
gMenus[APPLE_MENU] = GetMenu(APPLE_MENU_ID); |
AddResMenu(gMenus[APPLE_MENU], (ResType)'DRVR'); |
InsertMenu(gMenus[APPLE_MENU], 0); |
gMenus[FILE_MENU] = GetMenu(FILE_MENU_ID); |
InsertMenu(gMenus[FILE_MENU], 0); |
gMenus[EDIT_MENU] = GetMenu(EDIT_MENU_ID); |
InsertMenu(gMenus[EDIT_MENU], 0); |
gMenus[MOVIE_MENU] = GetMenu(MOVIE_MENU_ID); |
InsertMenu(gMenus[MOVIE_MENU], 0); |
DrawMenuBar(); |
FixMenus(); |
} |
/********************************************* |
Prepare for work. |
*********************************************/ |
void Initialize(void) |
{ |
Ptr size; |
long resp; |
size = GetApplLimit(); |
SetApplLimit(size - 32 * 1024); /* make room on stack so Quickdraw can do big pictures */ |
MaxApplZone(); |
/* initialize managers */ |
InitGraf(&qd.thePort); |
InitFonts(); |
InitWindows(); |
InitMenus(); |
InitDialogs(nil); |
InitCursor(); |
FlushEvents(everyEvent, 0); |
SetupMenus(); |
CouldAlert(140); |
if (NGetTrapAddress(0xab1d, ToolTrap) == NGetTrapAddress(0x9f, ToolTrap)) |
{ |
ExitToShell(); |
} |
if (Gestalt(gestaltQuickTime, &resp) != 0) |
{ |
ExitToShell(); |
} |
} |
/********************************************* |
If some kind of fatal error happened come here. |
*********************************************/ |
void Error(char* msg, |
OSErr code) |
{ |
char buf[256]; |
GWorldPtr saveWorld; |
GDHandle saveGD; |
CGrafPtr wmgrPort; |
GetGWorld(&saveWorld, &saveGD); |
GetCWMgrPort(&wmgrPort); |
SetGWorld(wmgrPort, nil); |
if (code) |
{ |
sprintf(buf, "%s %d", msg, code); |
msg = buf; |
} |
c2pstr(msg); |
ParamText((StringPtr)msg, "\p", 0, 0); |
if (code == 0) |
Alert(140, nil); |
else |
StopAlert(140, nil); |
SetGWorld(saveWorld, saveGD); |
} |
void ShowAboutBox(void) |
{ |
ParamText("\pPictMovier", "\pQuickTime!", 0, 0); |
Alert(140, nil); |
} |
/* |
Close a window |
*/ |
long DoClose(WindowPtr wind) |
{ |
if (wind == nil) |
return (0); |
if (gActiveWindow == wind) |
gActiveWindow = nil; |
if (wind == (WindowPtr)gSrcWindow) |
{ |
DoClear(); |
gSrcWindow = nil; |
DisposeGWorld(gWorld); |
gWorld = nil; |
} |
else if (wind == (WindowPtr)gAltWindow) |
{ |
DoClear(); |
gAltWindow = nil; |
DisposeGWorld(gAltWorld); |
gAltWorld = nil; |
} |
else if (wind == (WindowPtr)gDstWindow) |
{ |
gDstWindow = nil; |
} |
CloseWindow(wind); |
return (0); |
} |
/********************************************* |
Allow the user to set the compression parameters with the standard compression dialog. |
*********************************************/ |
void SetCompression(void) |
{ |
ComponentInstance ci; |
Point where; |
SCParams p; |
where.h = where.v = -2; |
ci = OpenDefaultComponent('scdi', 0); |
if (ci == nil) |
{ |
Error("Couldn't open StdCompression", 0); |
return; |
} |
p.flags = scShowMotionSettings; |
p.theCodecType = gCodecType; |
p.theCodec = gCodecID; |
p.spatialQuality = gCodecQuality; |
p.temporalQuality = gCodecTemporalQuality; |
p.depth = gCodecDepth; |
p.frameRate = gFrameRate << 16; |
p.keyFrameRate = gKeyFrameRate; |
SCGetCompressionExtended(ci, &p, where, 0,0,0,0); |
gCodecType = p.theCodecType; |
gCodecQuality = p.spatialQuality; |
gCodecTemporalQuality = p.temporalQuality; |
gCodecDepth = p.depth; |
gFrameRate = p.frameRate >> 16; |
gKeyFrameRate = p.keyFrameRate; |
gCodecID = p.theCodec; |
} |
/* |
Allow the user to set the number of stages she wants the processing to take. |
*/ |
void SetStages(void) |
{ |
#define STAGE_DLOG 128 |
#define IT_STAGES 3 |
#define IT_LINEAR 4 |
#define IT_OK 1 |
#define IT_CANCEL 2 |
CGrafPtr savePort; |
DialogPtr theDialog; |
short itemType; |
Handle itemHdl; |
Rect itemRect; |
short itemHit, |
lastItem = -1; |
Boolean done = false; |
Point tp; |
GDHandle saveGD; |
Str255 text; |
long num; |
long stageCount = gNumberSteps; |
Boolean linear = true; |
if ((theDialog = GetNewDialog(STAGE_DLOG, nil, (WindowPtr) - 1)) == nil) |
return; |
GetMouse(&tp); |
GetGWorld(&savePort, &saveGD); |
SetPort(theDialog); |
GetDItem(theDialog, IT_OK, &itemType, &itemHdl, &itemRect); |
PenSize(3, 3); |
InsetRect(&itemRect, -4, -4); |
FrameRoundRect(&itemRect, 16, 16); |
GetDItem(theDialog, IT_STAGES, &itemType, &itemHdl, &itemRect); |
sprintf((char*)text, "%ld", stageCount); |
c2pstr((char*)text); |
SetIText(itemHdl, text); |
SelIText(theDialog, IT_STAGES, 0, 32767); |
GetDItem(theDialog, IT_LINEAR, &itemType, &itemHdl, &itemRect); |
SetCtlValue((ControlHandle)itemHdl, linear); |
HiliteControl((ControlHandle)itemHdl, 255); |
while (!done) |
{ |
ModalDialog(nil, &itemHit); |
if (itemHit != lastItem) |
{ |
switch (lastItem) |
{ |
case IT_STAGES: |
GetDItem(theDialog, IT_STAGES, &itemType, &itemHdl, &itemRect); |
GetIText(itemHdl, text); |
p2cstr(text); |
if (sscanf((char*)text, "%ld", &num) == 1 && num > 0) |
stageCount = num; |
else |
SysBeep(1); |
sprintf((char*)text, "%ld", stageCount); |
c2pstr((char*)text); |
SetIText(itemHdl, text); |
break; |
} |
lastItem = itemHit; |
} |
switch (itemHit) |
{ |
case IT_OK: |
gNumberSteps = stageCount; |
done = true; |
break; |
case IT_CANCEL: |
done = true; |
break; |
case IT_LINEAR: |
linear =!linear; |
GetDItem(theDialog, IT_LINEAR, &itemType, &itemHdl, &itemRect); |
SetCtlValue((ControlHandle)itemHdl, linear); |
break; |
default: |
break; |
} |
} |
CloseDialog(theDialog); |
SetGWorld(savePort, saveGD); |
} |
void DoStage(long stageNum, |
long totalStages, |
Boolean reverse) |
{ |
if (gDoCrossFade) |
DoCrossFade(stageNum, totalStages, reverse); |
else |
DoRandomDotStereogram(stageNum, totalStages, reverse); |
} |
/********************************************* |
This processing routine creates a random dot stereogram of the input picture. |
The second picture is not used. |
********************************************/ |
void DoRandomDotStereogram(long stageNum, |
long totalStages, |
Boolean reverse) |
{ |
/* StdFile stuff */ |
CGrafPtr savePort; |
GDHandle saveGD; |
char* ip, |
* rp, |
* pp, |
* opp, |
* qp; |
char* sip, |
* srp, |
* spp; |
short rowBytes; |
short sRowBytes; |
char mode = 1; |
/* for sepecifying compression */ |
short i, |
j, |
x, |
y, |
height, |
width; |
unsigned char pix, |
spix, |
ppix, |
qpix; |
char* lineBuf = nil; |
char* newLineBuf = nil; |
char* tLineBuf = nil; |
#ifdef RDS_NEW_SEED_EACH_FRAME |
unsigned long timeseed; |
#endif |
//#define RDS_DEPTH_FACTOR_VARIES // set this to increase depth as movie progresses |
//#define RDS_STRIP_WIDTH_VARIES // set this to increase repeat strip as movie progresses |
//#define RDS_NEW_SEED_EACH_FRAME // set this to have a new random background for each frame |
#ifdef RDS_NEW_SEED_EACH_FRAME |
GetDateTime(×eed); |
BetterRand(time); |
#else |
BetterRand(1); |
#endif |
#ifdef RDS_DEPTH_FACTOR_VARIES |
gDepthFactor = 1 + (stageNum * 64) / totalStages; |
gDepthDecodeTabInted = false; |
#else |
gDepthFactor = 16; |
#endif |
#ifdef RDS_STRIP_WIDTH_VARIES |
gStripWidth = 32 + (stageNum * 256) / totalStages; |
#else |
gStripWidth = 128; |
#endif |
GetGWorld(&savePort, &saveGD); |
SetGWorld(gDstWorld, nil); |
EraseRect(&gDstWorld->portRect); |
width = gWorld->portRect.right - gWorld->portRect.left; |
height = gWorld->portRect.bottom - gWorld->portRect.top; |
LockPixels(gWorld->portPixMap); |
sRowBytes = (*gWorld->portPixMap)->rowBytes & 0x7fff; |
sip = GetPixBaseAddr(gWorld->portPixMap); |
SetGWorld(gDstWorld, nil); |
LockPixels(gDstWorld->portPixMap); |
rowBytes = (*gDstWorld->portPixMap)->rowBytes & 0x7fff; |
ip = GetPixBaseAddr(gDstWorld->portPixMap); |
lineBuf = NewPtr(sRowBytes); |
if (lineBuf == nil) |
{ |
Error("allocating line buffer", 0); |
goto done; |
} |
newLineBuf = NewPtr(rowBytes); |
if (newLineBuf == nil) |
{ |
Error("allocating nline buffer", 0); |
goto done; |
} |
tLineBuf = NewPtr(sRowBytes); |
if (tLineBuf == nil) |
{ |
Error("allocating tline buffer", 0); |
goto done; |
} |
SwapMMUMode(&mode); |
if (gDepth == 1) |
{ |
rp = ip; |
for (y = 0; y < height; y++) |
{ |
pp = rp; |
for (x = 0; x < gStripWidth / 8; x++, pp++) |
{ |
pix = 0; |
for (i = 0; i < 8; i++) |
{ |
pix <<= 1; |
if (BetterRand(0) % 5 == 0) |
pix |= 1; |
} |
qp = pp; |
for (j = 0; j < width / gStripWidth; j++) |
{ |
*qp = pix; |
qp += gStripWidth / 8; |
} |
} |
rp += rowBytes; |
} |
rp = ip; |
srp = sip; |
for (y = 0; y < height; y++) |
{ |
pp = rp; |
spp = srp; |
spp += gDepthFactor / 8; |
qp = pp; |
BlockMove(pp, tLineBuf, sRowBytes); |
qp = tLineBuf; |
pp = qp; |
qp += gDepthFactor / 8; |
opp = newLineBuf; |
for (x = 0; x < width / 8 - gStripWidth / 8; x++, pp++, qp++, opp++) |
{ |
spix = *spp++; |
qpix = *qp; |
ppix = *pp; |
pix = 0; |
for (i = 0; i < 8; i++) |
{ |
pix <<= 1; |
if ((spix & 0x80)) |
pix |= (qpix & 0x80) != 0 ? 1 : 0; |
else |
pix |= (ppix & 0x80) != 0 ? 1 : 0; |
spix <<= 1; |
ppix <<= 1; |
qpix <<= 1; |
} |
*opp = pix; |
*(pp + gStripWidth / 8) = pix; |
} |
BlockMove(newLineBuf, rp + gStripWidth / 8, rowBytes - gStripWidth / 8); |
rp += rowBytes; |
srp += sRowBytes; |
} |
} |
else if (gDepth == 8 || gDepth == 40) |
{ |
rp = ip; |
for (y = 0; y < height; y++) |
{ |
pp = rp; |
for (x = 0; x < gStripWidth; x++) |
{ |
*pp++ = BetterRand(0); |
} |
rp += rowBytes; |
} |
rp = ip; |
srp = sip; |
for (y = 0; y < height; y++) |
{ |
pp = rp; |
spp = srp; |
spp += gStripWidth; |
qp = pp; |
BlockMove(pp, tLineBuf, sRowBytes); |
qp = tLineBuf; |
pp = qp; |
opp = newLineBuf; |
for (x = 0; x < width - gStripWidth; x++, pp++, qp++, opp++) |
{ |
spix = *spp++; |
ppix = *pp; |
pix = *(qp + DecodeDepth(spix)); |
*opp = pix; |
*(pp + gStripWidth) = pix; |
} |
BlockMove(newLineBuf, rp + gStripWidth, rowBytes - gStripWidth); |
rp += rowBytes; |
srp += sRowBytes; |
} |
} |
SwapMMUMode(&mode); |
UnlockPixels(gDstWorld->portPixMap); |
SetGWorld(savePort, saveGD); |
done:if (newLineBuf) |
DisposPtr(newLineBuf); |
if (lineBuf) |
DisposPtr(lineBuf); |
if (tLineBuf) |
DisposPtr(tLineBuf); |
} |
/********************************************* |
a better random function than the one in the ROM |
*********************************************/ |
long BetterRand(long reseed) |
{ |
#define A 16807 |
#define M 2147483647 |
#define Q 127773 |
#define R 2836 |
static long seed = 1; |
long lo, |
hi, |
test; |
if (reseed != 0) |
seed = reseed; |
hi = seed / Q; |
lo = seed % Q; |
test = A * lo - R * hi; |
if (test > 0) |
seed = test; |
else |
seed = test + M; |
return (seed / (M >> 16)); |
} |
unsigned char DecodeDepth(unsigned char pix) |
{ |
static unsigned char table[256]; |
short i; |
if (!gDepthDecodeTabInted) |
{ |
gDepthDecodeTabInted = 1; |
for (i = 0; i < 255; i++) |
{ |
table[i] = (i * gDepthFactor) / 255; |
} |
} |
return (table[pix]); |
} |
/********************************************* |
This processing routine does a linear cross dissolve between the two images, |
using CopyBits with blend mode. It shows how to do a process which involves |
both images. Any type of process could be done here, using the three parameters |
to determine how much of the processing has progressed. |
Note: See the Develop article by Konstantin Othmar ( sorry I forget the issue number ) |
about CopyBits to learn how to do other cool effects just using CopyBits. |
*********************************************/ |
void DoCrossFade(long stageNum, |
long totalStages, |
Boolean reverse) |
{ |
GWorldPtr saveWorld; |
GDHandle saveGD; |
RGBColor opColor; |
if (reverse) |
opColor.red = opColor.green = opColor.blue = (stageNum * 0xffff) / totalStages; |
else |
opColor.red = opColor.green = opColor.blue = ((totalStages - stageNum) * 0xffff) / totalStages; |
GetGWorld(&saveWorld, &saveGD); |
SetGWorld(gDstWorld, nil); |
CopyBits((BitMap *) * gAltWorld->portPixMap, (BitMap *) * gDstWorld->portPixMap, &gAltWorld->portRect, &gDstWorld->portRect, ditherCopy, nil); |
OpColor(&opColor); |
CopyBits((BitMap *) * gWorld->portPixMap, (BitMap *) * gDstWorld->portPixMap, &gWorld->portRect, &gDstWorld->portRect, blend, nil); |
SetGWorld(saveWorld, saveGD); |
} |
/* |
The following three routines are here because the MPW 3.2 c compiler |
screw up compiling the DoMovie routine if you use the "straight" calls. |
Try it and see and complain to DTS. |
*/ |
void DoHLock(Handle h) |
{ |
HLock(h); |
} |
void DoHUnlock(Handle h) |
{ |
HUnlock(h); |
} |
void DoDisposHandle(Handle h) |
{ |
DisposHandle(h); |
} |
/********************************************* |
Show a preview of some stage in the process. |
********************************************/ |
void DoPreview(void) |
{ |
#define PV_DLOG 129 |
#define IT_STAGE 4 |
#define IT_OK 1 |
#define IT_CANCEL 2 |
CGrafPtr savePort; |
DialogPtr theDialog; |
short itemType; |
Handle itemHdl; |
Rect itemRect; |
short itemHit, |
lastItem = -1; |
Boolean done = false; |
Point tp; |
GDHandle saveGD; |
Str255 text; |
long num; |
long whichStage = gNumberSteps / 2; |
if ((theDialog = GetNewDialog(PV_DLOG, nil, (WindowPtr) - 1)) == nil) |
return; |
GetMouse(&tp); |
GetGWorld(&savePort, &saveGD); |
SetPort(theDialog); |
GetDItem(theDialog, IT_OK, &itemType, &itemHdl, &itemRect); |
PenSize(3, 3); |
InsetRect(&itemRect, -4, -4); |
FrameRoundRect(&itemRect, 16, 16); |
GetDItem(theDialog, IT_STAGE, &itemType, &itemHdl, &itemRect); |
sprintf((char*)text, "%ld", whichStage); |
c2pstr((char*)text); |
SetIText(itemHdl, text); |
SelIText(theDialog, IT_STAGE, 0, 32767); |
while (!done) |
{ |
ModalDialog(nil, &itemHit); |
if (itemHit != lastItem) |
{ |
switch (lastItem) |
{ |
case IT_STAGE: |
GetDItem(theDialog, IT_STAGE, &itemType, &itemHdl, &itemRect); |
GetIText(itemHdl, text); |
p2cstr(text); |
if (sscanf((char*)text, "%ld", &num) == 1 && num > 0 && num <= gNumberSteps) |
whichStage = num; |
else |
SysBeep(1); |
sprintf((char*)text, "%ld", whichStage); |
c2pstr((char*)text); |
SetIText(itemHdl, text); |
break; |
} |
lastItem = itemHit; |
} |
switch (itemHit) |
{ |
case IT_OK: |
done = true; |
break; |
case IT_CANCEL: |
whichStage = 0; |
done = true; |
break; |
default: |
break; |
} |
} |
CloseDialog(theDialog); |
if (whichStage) |
{ |
Rect rect; |
sprintf((char*)text, "Preview %d of %d", whichStage, gNumberSteps); |
c2pstr((char*)text); |
rect = gSrcWindow->portRect; |
SetGWorld(gSrcWindow, nil); |
LocalToGlobal((Point *) & rect.top); |
LocalToGlobal((Point *) & rect.bottom); |
OffsetRect(&rect, 20, 20); |
if (gOversample) |
{ |
rect.right = rect.left + (rect.right - rect.left) / 2; |
rect.bottom = rect.top + (rect.bottom - rect.top) / 2; |
} |
SetGWorld(savePort, saveGD); |
if (gDstWindow) |
{ |
CloseWindow((WindowPtr)gDstWindow); |
gDstWindow = nil; |
} |
if ((gDstWindow = (CWindowPtr)NewCWindow(nil, &rect, text, true, zoomDocProc, (WindowPtr) - 1, true, 0)) == nil) |
{ |
Error("NewCWindow Failed", 0); |
goto done; |
} |
BringToFront((WindowPtr)gDstWindow); |
HiliteWindow((WindowPtr)gDstWindow, true); |
SetGWorld((CGrafPtr)gDstWorld, nil); |
PaintRect(&gDstWorld->portRect); |
if (gDstWorld && gWorld && gAltWorld) |
DoStage(whichStage, gNumberSteps, gBackwards); |
SetGWorld((CGrafPtr)gDstWindow, nil); |
CopyBits((BitMap *) * gDstWorld->portPixMap, (BitMap *) * gDstWindow->portPixMap, &gDstWorld->portRect, &gDstWindow->portRect, ditherCopy, nil); |
} |
done:SetGWorld(savePort, saveGD); |
} |
/********************************************* |
Create the movie by processing all the frames. |
********************************************/ |
void DoMovie(void) |
{ |
GWorldPtr saveWorld; |
GDHandle saveGD; |
short resRefNum; |
OSErr result; |
Rect rect; |
/* Stuff for creating the file */ |
Point dlgPos = |
{ |
100, 100 |
} |
; /* Position the dialog box */ |
SFReply sfr; /* StdFile reply */ |
FSSpec mySpec; /* Data structure with filename, etc. */ |
Movie gMovie = 0; /* Our movie, track and media */ |
Track gTrack; |
Media gMedia; |
long maxCompressedFrameSize; /* Max size of compressed frame */ |
long compressedFrameSize; /* Size of current compressed frame */ |
Handle compressedFrameBitsH = nil; /* Buffer for the compressed data */ |
ImageDescription * *imageDescriptionH = nil;/* Contains info about the sample */ |
Ptr data; |
EventRecord myEvent; |
ImageSequence seq; |
static Str255 name = "\pMy Movie"; |
Rect dstRect; |
short width, |
height; |
TimeValue timeVal; |
if (gDstWindow) |
{ |
CloseWindow((WindowPtr)gDstWindow); |
gDstWindow = nil; |
} |
/* Prompt the user for a file name and create it */ |
SFPutFile(dlgPos, (StringPtr)"\pMovie file to create:", (StringPtr)name, nil, &sfr); |
if (!sfr.good) |
return; |
BlockMove(sfr.fName, name, sfr.fName[0] + 1); |
GetGWorld(&saveWorld, &saveGD); |
width = gRect.right - gRect.left; |
height = gRect.bottom - gRect.top; |
if (gOversample) |
{ |
width /= 2; |
height /= 2; |
} |
SetRect(&rect, 80, 80, width + 80, height + 80); |
if ((gDstWindow = (CWindowPtr)NewCWindow(nil, &rect, sfr.fName, true, zoomDocProc, (WindowPtr) - 1, true, 0)) == nil) |
{ |
Error("NewCWindow Failed", 0); |
goto done; |
} |
BringToFront((WindowPtr)gDstWindow); |
HiliteWindow((WindowPtr)gDstWindow, true); |
imageDescriptionH = (ImageDescription * *)NewHandle(sizeof(ImageDescription));/* handle for image descriptor */ |
if (imageDescriptionH == nil) |
{ |
Error("Out of Memory", MemError()); |
goto done; |
} |
ClearMoviesStickyError(); /* Clear any old errors */ |
result = FSMakeFSSpec(sfr.vRefNum, 0, (unsigned char*)sfr.fName, &mySpec); |
if (result == fnfErr) |
result = 0; |
CreateMovieFile(&mySpec, 'TVOD', 0, createMovieFileDeleteCurFile, &resRefNum, &gMovie); |
gTrack = NewMovieTrack(gMovie, (long)width << 16, (long)height << 16, 0); |
gMedia = NewTrackMedia(gTrack, VideoMediaType, gFrameRate, nil, (OSType)nil); |
BeginMediaEdits(gMedia); /* We do this since we are adding samples to the media */ |
GetMaxCompressionSize(gAltWorld->portPixMap, &gAltWorld->portRect, gCodecDepth, gCodecQuality, gCodecType, gCodecID, &maxCompressedFrameSize); |
compressedFrameBitsH = NewHandle(maxCompressedFrameSize); |
if (compressedFrameBitsH == nil) |
{ |
EndMediaEdits(gMedia); |
Error("Out of Memory", MemError()); |
goto bail; |
} |
GetGWorld(&saveWorld, &saveGD); |
SetGWorld((CGrafPtr)gDstWorld, nil); |
PaintRect(&gDstWorld->portRect); |
if ((result = CompressSequenceBegin(&seq, gDstWorld->portPixMap, nil, &gDstWorld->portRect, nil, gCodecDepth, gCodecType, gCodecID, gCodecQuality, gCodecTemporalQuality, gKeyFrameRate, nil, codecFlagUpdatePreviousComp, imageDescriptionH)) != 0) |
{ |
Error("CompressSequenceBegin Failed", result); |
goto bail2; |
} |
SetGWorld(saveWorld, saveGD); |
while (WaitNextEvent(everyEvent, &myEvent, 0, nil) != 0) |
{ |
if (HandleEvents(&myEvent)) |
{ |
CDSequenceEnd(seq); |
EndMediaEdits(gMedia); |
return; |
} |
} |
SetRect(&dstRect, 0, 0, gRect.right / 2, gRect.bottom / 2); |
for (gFrameNumber = 0; gFrameNumber <= gNumberSteps; gFrameNumber++) |
{ |
SetGWorld(saveWorld, saveGD); |
if (WaitNextEvent(everyEvent, &myEvent, 0, nil) != 0) |
{ |
if (HandleEvents(&myEvent)) |
{ |
break; |
} |
} |
SetGWorld((CGrafPtr)gDstWorld, nil); |
PaintRect(&gDstWorld->portRect); |
if (gDstWorld && gWorld && gAltWorld) |
DoStage(gFrameNumber, gNumberSteps, gBackwards); |
SetGWorld((CGrafPtr)gDstWindow, nil); |
InvalRect(&gDstWindow->portRect); |
DoHLock(compressedFrameBitsH); |
data = StripAddress(*compressedFrameBitsH); |
if (gOversample) |
{ |
SetGWorld(gBufferWorld, nil); |
CopyBits((BitMap *) * gDstWorld->portPixMap, (BitMap *) * gBufferWorld->portPixMap, &gDstWorld->portRect, &dstRect, ditherCopy, nil); |
result = CompressSequenceFrame(seq, gBufferWorld->portPixMap, &dstRect, codecFlagUpdatePreviousComp, data, &compressedFrameSize, nil, nil); |
} |
else |
{ |
SetGWorld((CGrafPtr)gDstWorld, nil); |
result = CompressSequenceFrame(seq, gDstWorld->portPixMap, &gDstWorld->portRect, codecFlagUpdatePreviousComp, data, &compressedFrameSize, nil, nil); |
} |
if (result) |
{ |
Error("Compress Sequence Frame Failed", result); |
break; |
} |
DoHUnlock(compressedFrameBitsH); |
result = AddMediaSample(gMedia, compressedFrameBitsH, 0L, compressedFrameSize, (TimeValue)1, (SampleDescriptionHandle)imageDescriptionH, 1L, 0, nil); |
if (result) |
{ |
Error("AddMediaSample Failed", result); |
break; |
} |
} |
bail2:CDSequenceEnd(seq); |
bail:EndMediaEdits(gMedia); /* We're done adding samples */ |
timeVal = GetMediaDuration(gMedia); |
result = InsertMediaIntoTrack(gTrack, 0L, 0L, timeVal, 1L << 16); |
if (result) |
{ |
Error("InsertMediaIntoTrack Failed", result); |
} |
result = AddMovieResource(gMovie, resRefNum, nil, nil); |
if (result) |
{ |
Error("AddMovieResource Failed", result); |
} |
CloseMovieFile(resRefNum); |
done:SetGWorld(saveWorld, saveGD); |
if (imageDescriptionH) |
DoDisposHandle((Handle)imageDescriptionH); |
if (compressedFrameBitsH) |
DoDisposHandle((Handle)compressedFrameBitsH); |
if (gMovie) |
DisposeMovie(gMovie); |
if (gDstWindow) |
{ |
CloseWindow((WindowPtr)gDstWindow); |
gDstWindow = nil; |
} |
SetGWorld(saveWorld, saveGD); |
} |
/********************************************* |
Stubs |
********************************************/ |
void DoCopy(void) |
{ |
} |
void DoCut(void) |
{ |
} |
void DoPaste(void) |
{ |
} |
void DoClear(void) |
{ |
} |
void DoUndo(void) |
{ |
} |
/********************************************* |
Process menu command. |
********************************************/ |
short DoCommand(long mResult) |
{ |
short theMenu, |
theItem; |
Str255 daName; |
GDHandle saveGD; |
CGrafPtr savePort; |
OSErr res = 0; |
#define HIshort(aLong) (((aLong) >> 16) & 0xFFFF) |
#define LOshort(aLong) ((aLong) & 0xFFFF) |
theItem = LOshort(mResult); |
theMenu = HIshort(mResult); /* This is the resource ID */ |
switch (theMenu) |
{ |
case APPLE_MENU_ID: |
if (theItem == 1) |
{ |
ShowAboutBox(); |
} |
else |
{ |
GetItem(gMenus[0], theItem, daName); |
GetGWorld(&savePort, &saveGD); |
(void)OpenDeskAcc(daName); |
SetGWorld(savePort, saveGD); |
} |
break; |
case FILE_MENU_ID: |
{ |
switch (theItem) |
{ |
case FILE_M_OPEN: |
DoOpen(nil); |
FixMenus(); |
break; |
case FILE_M_CLOSE: |
DoClose(gActiveWindow); |
FixMenus(); |
res = 1; |
break; |
case FILE_M_SAVE: |
FixMenus(); |
break; |
case FILE_M_QUIT: |
{ |
Boolean abortion = false; |
while (gActiveWindow) |
{ |
if (DoClose(gActiveWindow) < 0) |
{ |
abortion = true; |
break; |
} |
} |
if (!abortion) |
gExitFlag = true;/* Request exit */ |
res = 1; |
break; |
} |
} |
} |
break; |
case EDIT_MENU_ID: |
if (!SystemEdit(theItem - 1)) |
{ |
switch (theItem) |
{ |
case EDIT_M_UNDO: |
DoUndo(); |
break; |
case EDIT_M_CUT: |
DoCut(); |
break; |
case EDIT_M_COPY: |
DoCopy(); |
break; |
case EDIT_M_PASTE: |
DoPaste(); |
break; |
case EDIT_M_CLEAR: |
DoClear(); |
break; |
} |
} |
FixMenus(); |
break; |
case MOVIE_MENU_ID: |
switch (theItem) |
{ |
case MOVIE_M_COMPRESS: |
SetCompression(); |
break; |
case MOVIE_M_STAGES: |
DoClose((WindowPtr)gDstWindow); |
SetStages(); |
break; |
case MOVIE_M_OVERSAMPLE: |
gOversample =!gOversample; |
CheckItem(gMenus[MOVIE_MENU], theItem, gOversample); |
break; |
case MOVIE_M_BACKWARDS: |
DoClose((WindowPtr)gDstWindow); |
gBackwards =!gBackwards; |
CheckItem(gMenus[MOVIE_MENU], theItem, gBackwards); |
break; |
case MOVIE_M_PREVIEW: |
DoPreview(); |
break; |
case MOVIE_M_MOVIE: |
DoMovie(); |
break; |
} |
break; |
default: |
break; |
} /*endsw theMenu*/ |
HiliteMenu(0); |
return (res); |
} |
/******************************************** |
Process events. |
********************************************/ |
Boolean HandleEvents(EventRecord* myEvent) |
{ |
Rect dragRect; |
WindowPtr whichWindow; |
short res = 0; |
GWorldPtr saveWorld; |
GDHandle saveGD; |
GetGWorld(&saveWorld, &saveGD); |
switch (myEvent->what) |
{ |
case mouseDown: |
switch ((short)FindWindow(myEvent->where, &whichWindow)) |
{ |
case inSysWindow: |
SystemClick(myEvent, whichWindow); |
break; |
case inMenuBar: |
res = DoCommand(MenuSelect(myEvent->where)); |
break; |
case inDrag: |
SetPort((GrafPtr)whichWindow); |
SetRect(&dragRect, 4, 20 + 4, qd.screenBits.bounds.right - 4, qd.screenBits.bounds.bottom - 4); |
DragWindow(whichWindow, myEvent->where, &dragRect); |
break; |
case inGrow: |
break; |
case inGoAway: |
if (TrackGoAway(whichWindow, myEvent->where)) |
{ |
DoClose(whichWindow); |
res = true; |
} |
break; |
case inZoomIn: |
break; |
case inZoomOut: |
break; |
case inContent: |
if (whichWindow != FrontWindow()) |
{ |
SelectWindow(whichWindow); |
gActiveWindow = whichWindow; |
} |
FixMenus(); |
break; |
default: |
break; |
} |
break; |
case keyDown: |
if (((myEvent->modifiers & cmdKey) != 0)) |
{ |
res = DoCommand(MenuKey(myEvent->message & charCodeMask)); |
} |
break; |
case updateEvt: |
DoUpdate((CWindowPtr)myEvent->message); |
break; |
case activateEvt: |
whichWindow = (WindowPtr)myEvent->message; |
if ((myEvent->modifiers & activeFlag)) |
{ |
gActiveWindow = whichWindow; |
} |
break; |
default: |
break; |
} |
SetGWorld(saveWorld, saveGD); |
return (res); |
} |
/******************************************** |
Allocate the image buffers for the processing |
********************************************/ |
OSErr AllocateBuffers(void) |
{ |
Rect rect = gRect; |
OSErr result = 0; |
short depth = gDepth > 32 ? (gDepth - 32) : gDepth; |
CTabHandle clut = nil; |
OffsetRect(&rect, -rect.left, -rect.top); |
if (gDepth > 32) |
clut = GetCTable(gDepth); |
if (gDstWorld) |
{ |
DisposeGWorld(gDstWorld); |
gDstWorld = nil; |
} |
if (gBufferWorld) |
{ |
DisposeGWorld(gBufferWorld); |
gBufferWorld = nil; |
} |
if ((result = NewGWorld(&gWorld, depth, &rect, clut, nil, 0)) != 0) |
{ |
if ((result = NewGWorld(&gWorld, depth, &rect, clut, nil, useTempMem)) != 0) |
{ |
Error("NewGWorld Failed", result); |
goto done; |
} |
} |
if ((result = NewGWorld(&gAltWorld, depth, &rect, clut, nil, 0)) != 0) |
{ |
if ((result = NewGWorld(&gAltWorld, depth, &rect, clut, nil, useTempMem)) != 0) |
{ |
Error("NewGWorld Failed", result); |
goto done; |
} |
} |
if ((result = NewGWorld(&gBufferWorld, depth, &rect, clut, nil, 0)) != 0) |
{ |
if ((result = NewGWorld(&gBufferWorld, depth, &rect, clut, nil, useTempMem)) != 0) |
{ |
Error("NewGWorld Failed", result); |
goto done; |
} |
} |
if ((result = NewGWorld(&gDstWorld, depth, &rect, clut, nil, 0)) != 0) |
{ |
if ((result = NewGWorld(&gDstWorld, depth, &rect, clut, nil, useTempMem)) != 0) |
{ |
Error("NewGWorld Failed", result); |
goto done; |
} |
} |
done:if (result) |
{ |
if (gWorld) |
DisposeGWorld(gWorld); |
gWorld = nil; |
if (gAltWorld) |
DisposeGWorld(gAltWorld); |
gAltWorld = nil; |
if (gDstWorld) |
DisposeGWorld(gDstWorld); |
gDstWorld = nil; |
if (gBufferWorld) |
DisposeGWorld(gBufferWorld); |
gBufferWorld = nil; |
} |
if (clut) |
DisposeCTable(clut); |
return (result); |
} |
/******************************************** |
Make a source window |
********************************************/ |
CWindowPtr MakeSWindow(Rect* frame, |
StringPtr name, |
GWorldPtr buffer) |
{ |
CWindowPtr wind; |
if ((wind = (CWindowPtr)NewCWindow(nil, frame, name, false, zoomDocProc, (WindowPtr) - 1, true, 0)) == nil) |
{ |
Error("NewCWindow Failed", 0); |
return (nil); |
} |
ShowWindow((WindowPtr)wind); |
BringToFront((WindowPtr)wind); |
HiliteWindow((WindowPtr)wind, true); |
SetWRefCon((WindowPtr)wind, (long)buffer); |
return (wind); |
} |
/******************************************** |
Open a file. |
********************************************/ |
OSErr DoOpen(FSSpec* fsp) |
{ |
long result = noErr; |
Rect zpFrame; |
GWorldPtr saveWorld; |
GDHandle saveGD; |
short oFile = -1; |
StandardFileReply theSFR; |
GetGWorld(&saveWorld, &saveGD); |
if (fsp == nil) |
{ |
SFTypeList types = |
{ |
'PICT', 0 |
} |
; |
if (gHasNewStdFile) |
StandardGetFile(nil, 1, types, &theSFR); |
else |
{ |
SFReply osfr; |
Point pt = |
{ |
100, 100 |
} |
; |
SFGetFile(pt, (ConstStr255Param)"", nil, 1, types, nil, &osfr); |
theSFR.sfGood = osfr.good; |
theSFR.sfReplacing = osfr.copy; |
theSFR.sfType = osfr.fType; |
if (osfr.good) |
FSMakeFSSpec(osfr.vRefNum, 0L, osfr.fName, &theSFR.sfFile); |
} |
if (!theSFR.sfGood) |
{ |
return (1); |
} |
} |
else |
{ |
theSFR.sfFile = *fsp; |
} |
if (FSpOpenDF(&theSFR.sfFile, fsRdPerm, &oFile)) |
{ |
result = -1; |
goto done; |
} |
/************************************************ |
* |
* Get the picture frame, to see how big of a window to make. |
* |
************************************************/ |
if (GetPictureFileHeader(oFile, &gOriginalPicFrame, nil)) |
{ |
result = -1; |
goto done; |
} |
if (gSrcWindow == nil) |
{ |
OffsetRect(&gOriginalPicFrame, -gOriginalPicFrame.left, -gOriginalPicFrame.top); |
if (gOversample) |
{ |
gOriginalPicFrame.right *= 2; |
gOriginalPicFrame.bottom *= 2; |
} |
gRect = gOriginalPicFrame; |
zpFrame = gRect; |
if ((result = AllocateBuffers()) != 0) |
goto done; |
OffsetRect(&zpFrame, 50, 50); |
gSrcWindow = MakeSWindow(&zpFrame, theSFR.sfFile.name, gWorld); |
SetGWorld((CGrafPtr)gWorld, nil); |
if ((result = DrawPictureFile(oFile, &gWorld->portRect, nil)) != 0) |
{ |
Error("DrawPictureFile Failed", result); |
goto done; |
} |
} |
else |
{ |
zpFrame = gRect; |
OffsetRect(&zpFrame, 70, 70); |
gAltWindow = MakeSWindow(&zpFrame, theSFR.sfFile.name, gAltWorld); |
SetGWorld((CGrafPtr)gAltWorld, nil); |
if ((result = DrawPictureFile(oFile, &gAltWorld->portRect, nil)) != 0) |
{ |
Error("DrawPictureFile Failed", result); |
goto done; |
} |
} |
done:if (oFile != -1) |
FSClose(oFile); |
SetGWorld(saveWorld, saveGD); |
return (result); |
} |
/******************************************** |
Update a window |
********************************************/ |
void DoUpdate(CWindowPtr wind) |
{ |
GWorldPtr saveWorld, gw; |
GDHandle saveGD; |
RGBColor gray = |
{ |
0xc000, 0xc000, 0xc000 |
} |
; |
char c[32]; |
GetGWorld(&saveWorld, &saveGD); |
if (wind == gDstWindow) |
{ |
BeginUpdate((WindowPtr)wind); |
SetGWorld((CGrafPtr)wind, nil); |
CopyBits((BitMap *) * gDstWorld->portPixMap, (BitMap *) * wind->portPixMap, &gDstWorld->portRect, &wind->portRect, ditherCopy, nil); |
if (gFrameNumber >= 0) |
{ |
MoveTo(20, 20); |
TextSize(12); |
TextFace(bold); |
TextMode(patBic); |
NumToString(gFrameNumber + 1, (StringPtr) & c); |
DrawString((StringPtr) & c); |
DrawString((StringPtr)"\p/"); |
NumToString(gNumberSteps + 1, (StringPtr) & c); |
DrawString((StringPtr) & c); |
MoveTo(21, 21); |
TextMode(srcOr); |
NumToString(gFrameNumber + 1, (StringPtr) & c); |
DrawString((StringPtr) & c); |
DrawString((StringPtr)"\p/"); |
NumToString(gNumberSteps + 1, (StringPtr) & c); |
DrawString((StringPtr) & c); |
} |
EndUpdate((WindowPtr)wind); |
SetGWorld(saveWorld, saveGD); |
return; |
} |
SetGWorld(wind, nil); |
gw = (GWorldPtr)GetWRefCon((WindowPtr)wind); |
BeginUpdate((WindowPtr)wind); |
CopyBits((BitMap *) * gw->portPixMap, (BitMap *) * wind->portPixMap, &gw->portRect, &wind->portRect, ditherCopy, nil); |
EndUpdate((WindowPtr)wind); |
SetGWorld(saveWorld, saveGD); |
} |
/******************************************** |
The main program |
********************************************/ |
void main(void) |
{ |
EventRecord myEvent; |
Initialize(); |
EnterMovies(); |
if (!gDoCrossFade) |
{ |
gDepth = 40; // randomdotstereogram needs grayscale buffers |
gCodecType = 'smc '; // graphics compressor is good choice |
gCodecDepth = gDepth; // prime depth for std compression |
gRequiresAlternate = false; // only needs one pict to do processing |
} |
else |
{ |
gDepth = 32; // full resolution for best effect |
gRequiresAlternate = true; // needs two picts to do processing |
} |
while (!gExitFlag) |
{ |
if (WaitNextEvent(everyEvent, &myEvent, 1, nil) != 0) |
{ |
HandleEvents(&myEvent); |
} |
} |
done:ExitMovies(); |
ExitToShell(); |
} |
Copyright © 2003 Apple Computer, Inc. All Rights Reserved. Terms of Use | Privacy Policy | Updated: 2003-01-14