//////////
//
//	File:		QTGraphImp.c
//
//	Contains:	Sample code for using QuickTime's graphics import routines.
//
//	Written by:	Tim Monroe
//				Based loosely on the SimpleGIExample.c code written by Apple DTS;
//				exporting code based heavily on Dispatch 14 from the Ice Floe by Sam Bushell.
//
//	Copyright:	© 1998 by Apple Computer, Inc., all rights reserved.
//
//	Change History (most recent first):
//
//	   <3>	 	03/21/00	rtm		reworked functions to avoid calling Standard File Package
//	   <2>	 	06/02/98	rtm		added QTGraphImp_ExportGWorldToFile and _GetAvailableExportTypes,
//									from Dispatch 14 from the Ice Floe; added QTGraphImp_ExportImageFile
//	   <1>	 	04/14/98	rtm		first file
//	   
//	This sample code illustrates how to use QuickTime's graphics importer routines
//	to open and display image files. The graphics importer routines were introduced
//	in QuickTime version 2.5 as a new way to draw still images. The graphics import
//	routines (for example, GetGraphicsImporterForFile) use graphics import components
//	(of component type 'grip') to open and perform other operations on image files.
//	Essentially, you can use the graphics import routines to insulate your application
//	from the nitty gritty details of the image file format and compression used in the
//	image.
//
//	This code also shows how to export images as picture files, and how to determine which
//	formats an image can be exported in. The QT functions that support these operations
//	were introduced in QuickTime version 3.
//
//	In this sample code, we allow the user to open an image file; then we draw it into
//	a window on the screen. Your application, of course, will probably want to do more
//	interesting things with the image. We also allow the user to save an image using JPEG
//	compression.
//
//////////

//////////
//
// header files
//
//////////

#include "QTGraphImp.h"


//////////
//
// global variables
//
//////////

WindowPtr						gImageWindow = NULL;	// the window we display the image in
GraphicsImportComponent			gImporter = NULL;


//////////
//
// QTGraphImp_OpenImageFileAndDisplay
// Open an image file, then display the image in a window.
//
//////////

void QTGraphImp_OpenImageFileAndDisplay (FSSpecPtr theFSSpecPtr)
{
	Rect						myRect;
	OSErr						myErr = noErr;

	if (theFSSpecPtr == NULL)
		goto bail;
	
	//////////
	//
	// get a graphics importer for the image file and determine the natural size of the image
	//
	//////////

	myErr = GetGraphicsImporterForFile(theFSSpecPtr, &gImporter);
	if (myErr != noErr)
		goto bail;
	
	myErr = GraphicsImportGetNaturalBounds(gImporter, &myRect);
	if (myErr != noErr)
		goto bail;
	
	//////////
	//
	// create a window to display the image in; then draw into that window
	//
	//////////
	
	MacOffsetRect(&myRect, 50, 50);
	gImageWindow = NewCWindow(NULL, &myRect, theFSSpecPtr->name, true, movableDBoxProc, (WindowPtr)-1L, true, 0);
	if (gImageWindow == NULL)
		goto bail;
	
	// set the current port and draw
	GraphicsImportSetGWorld(gImporter, GetWindowPort(gImageWindow), NULL);
	GraphicsImportDraw(gImporter);
	
bail:
	if (myErr != noErr)
		if (gImporter != NULL)
			CloseComponent(gImporter);
}


//////////
//
// QTGraphImp_SaveCompressedImageIntoDiskFile
// Compress the current picture into the specified file.
//
//////////

void QTGraphImp_SaveCompressedImageIntoDiskFile (FSSpecPtr theFSSpecPtr)
{
	Rect						myRect;
	GWorldPtr					myGWorld;
	OSErr						myErr = noErr;

	if (theFSSpecPtr == NULL)
		goto bail;

	// get the natural size of the image
	myErr = GraphicsImportGetNaturalBounds(gImporter, &myRect);
	if (myErr != noErr)
		goto bail;

	// create an offscreen graphics world to draw the image into
	myErr = NewGWorld(&myGWorld, 0, &myRect, NULL, NULL, 0);
	if (myErr == noErr) {
		GraphicsImportSetGWorld(gImporter, (CGrafPtr)myGWorld, NULL);
		GraphicsImportDraw(gImporter);
		
		// save the compressed image
		QTGraphImp_SaveCompressedImage(myGWorld, theFSSpecPtr);
		if (myGWorld != NULL)
			DisposeGWorld(myGWorld);
	}

bail:
	;
}


//////////
//
// QTGraphImp_SaveCompressedImage
// Save the current image as a compressed file.
//
//////////

void QTGraphImp_SaveCompressedImage (GWorldPtr theWorld, FSSpec *theFile)
{
	ImageDescriptionHandle	 	myDesc;
	Handle						myData;
	Rect						myRect;
	PixMapHandle				myPixMap;	
	CTabHandle					myCTab = NULL;
	ICMFlushProcRecord			myFlushProc;
	short						myRefNum;
	short						myDepth;
	OSErr						myErr = noErr;

	if ((theWorld == NULL) || (theFile == NULL))
		goto bail;
		
	myDesc = (ImageDescriptionHandle)NewHandle(sizeof(ImageDescription));
	if (myDesc == NULL)
		goto bail;

#if TARGET_OS_MAC
	GetPortBounds(theWorld, &myRect);
#endif
#if TARGET_OS_WIN32
	myRect = theWorld->portRect;
#endif

	myPixMap = GetGWorldPixMap(theWorld);
	
	if (LockPixels(myPixMap)) {
	
		// if less than 16-bit, then get the color table of our GWorld
		myDepth = (**myPixMap).pixelSize;
		if (myDepth < 16)
			myCTab = (**myPixMap).pmTable;
		
		myData = NewHandle(kBufferSize);
		myErr = MemError();
		
		if ((myData != NULL) && (myErr == noErr)) {
			CodecType			myCodec = kJPEGCodecType;
			
			HLock(myData);
													 	
			myErr = FSpCreate(theFile, kImageFileCreator, kQTFileTypeJPEG, 0);
			
			if (myErr == noErr)
				myErr = FSpOpenDF(theFile, fsRdWrPerm, &myRefNum);
				
			if (myErr == noErr)
				myErr = SetFPos(myRefNum, fsFromStart, 0);
				
			if (myErr == noErr) {
				ICMFlushProcRecordPtr		myFlushProcPtr = NULL;
				
#if USE_FLUSH_PROC
				myFlushProc.flushProc = NewICMFlushProc(QTGraphImp_DataUnloadProc);
				myFlushProc.flushRefCon = myRefNum;
				myFlushProcPtr = &myFlushProc;
#else
				myFlushProcPtr = NULL;
#endif
				// compress the image
				myErr = FCompressImage(	myPixMap,
										&myRect,
										myDepth,
									 	codecNormalQuality,
									 	myCodec,
										anyCodec,
										myCTab,
									 	codecFlagWasCompressed,
									 	kBufferSize,
									 	myFlushProcPtr,
									 	NULL,
									 	myDesc,
									 	*myData);
			
#if !USE_FLUSH_PROC
				if (myErr == noErr)
					myErr = FSWrite(myRefNum, &(**myDesc).dataSize, *myData);
#endif
				
				if (myErr == noErr)
					myErr = SetFPos(myRefNum, fsFromStart, (**myDesc).dataSize);

				if (myErr == noErr)
					myErr = SetEOF(myRefNum, (**myDesc).dataSize);
							 
				if (myErr == noErr)		
					myErr = FSClose(myRefNum);
				
				HUnlock(myData);
				DisposeHandle(myData);
				
#if USE_FLUSH_PROC
				DisposeRoutineDescriptor(myFlushProc.flushProc);
#endif
			}
		}
	}
	
	UnlockPixels(myPixMap);

bail:	
	if (myDesc != NULL)
		DisposeHandle((Handle)myDesc);
}			


//////////
//
// QTGraphImp_DataUnloadProc
// A data unloading procedure: write the compressed data to disk.
//
// The theRefCon parameter is assumed to be a file reference number of an open file.
//
//////////

PASCAL_RTN OSErr QTGraphImp_DataUnloadProc (Ptr theData, long theBytesNeeded, long theRefCon)
{
	OSErr		myErr = noErr;
	
	if (theData == NULL) {
		// if data is NULL, set a new position in the file from the current mark, offset by bytesNeeded
		myErr = SetFPos(theRefCon, fsFromMark, theBytesNeeded);
	} else {
		// otherwise, write the specified data to disk
		myErr = FSWrite(theRefCon, &theBytesNeeded, theData);
	}
	
	return(myErr);
}


//////////
//
// QTGraphImp_ExportGWorldToFile
// Save the image in a graphics world to the specified file.
//
// Use this function when you've got an image that you want to export to a file
// but that image wasn't read in from a file (in which case you could just use
// the GetGraphicsImporterForFile function to open an appropriate graphics importer).
//
// Based on the function ExportGWorldToFile in Dispatch 14 from the Ice Floe.
//
//////////

OSErr QTGraphImp_ExportGWorldToFile (GWorldPtr theWorld, FSSpec *theFile, OSType theType)
{
	PicHandle					myPicture = NULL;
	PixMapHandle				myPixMap = NULL;
	GraphicsImportComponent		myImporter = NULL;
	Handle						myHandle = NULL;
	CGrafPtr					mySavedGW;
	GDHandle					mySavedGD;
	Rect						myRect;
	OSErr						myErr = noErr;

	// save the current graphics world, and set the specified graphics world as current
	GetGWorld(&mySavedGW, &mySavedGD);
	SetGWorld(theWorld, NULL);
	
	mySrcPixMap = GetGWorldPixMap(theWorld);

#if TARGET_OS_MAC
	GetPortBounds(theWorld, &myRect);
#endif
#if TARGET_OS_WIN32
	myRect = theWorld->portRect;
#endif

	// capture the contents of the specified graphics world in a picture handle
	myPicture = OpenPicture(&((GrafPtr)theWorld)->portRect);
	CopyBits(	(BitMapPtr)*myPixMap,
				(BitMapPtr)*myPixMap,
				&myRect,
				&myRect,
				srcCopy,
				NULL);
                  
	ClosePicture();

	// convert the picture handle into a handle-based PICT file by adding a 512-byte header to the start;
	// picture files are just like picture handles, except that they have an extra 512-byte header at the
	// front; any data in this header is usually ignored but it must exist, for historical reasons
	myHandle = NewHandleClear(512);
	myErr = MemError();
	if (myErr != noErr)
		goto bail;

	myErr = HandAndHand((Handle)myPicture, myHandle);
	if (myErr != noErr)
		goto bail;

	// open an instance of the picture file graphics importer
	myErr = OpenADefaultComponent(GraphicsImporterComponentType, kQTFileTypePicture, &myImporter);
	if (myErr != noErr)
		goto bail;

	myErr = GraphicsImportSetDataHandle(myImporter, myHandle);
	if (myErr != noErr)
		goto bail;

	// export the image to a file
	myErr = GraphicsImportExportImageFile(myImporter, theType, kImageFileCreator, theFile, smSystemScript);

bail:
	// restore the original graphics world
	SetGWorld(mySavedGW, mySavedGD);
	
	if (myPicture != NULL)
		KillPicture(myPicture);
	
	if (myImporter != NULL)
		CloseComponent(myImporter);
	
	if (myHandle != NULL)
		DisposeHandle(myHandle);
	
	return(myErr);
}


//////////
//
// QTGraphImp_GetAvailableExportTypes
// Get a list of the available image export file types.
//
// Based on a function in Dispatch 14 from the Ice Floe.
//
//////////

OSErr QTGraphImp_GetAvailableExportTypes (GraphicsImportComponent theImporter)
{
    QTAtomContainer				myContainer = NULL;
	short						myCount, myIndex;
	OSErr						myErr = noErr;

	// get an atom container that contains one or more kGraphicsExportGroup atoms;
	// each export group atom represents a single export format and has child atoms that indicate
	// the file type (kGraphicsExportFileType), a human-readable name (kGraphicsExportDescription),
	// a file extension (kGraphicsExportExtension) and a MIME type (kGraphicsExportMIMEType)
	myErr = GraphicsImportGetExportImageTypeList(theImporter, &myContainer);
	if (myErr != noErr)
		goto bail;

	myCount = QTCountChildrenOfType(myContainer, kParentAtomIsContainer, kGraphicsExportGroup);
	for (myIndex = 1; myIndex <= myCount; myIndex++) {
		QTAtom					myGroupAtom;

		myGroupAtom = QTFindChildByIndex(myContainer, kParentAtomIsContainer, kGraphicsExportGroup, myIndex, NULL);
		if (myGroupAtom != 0) {
			QTAtom				myTypeAtom;
			
			myTypeAtom = QTFindChildByIndex(myContainer, myGroupAtom, kGraphicsExportFileType, 1, NULL);
			if (myTypeAtom != 0) {
				OSType			myType;
				
				myErr = QTCopyAtomDataToPtr(myContainer, myTypeAtom, false, sizeof(myType), &myType, NULL);
				// the data in QT atoms is always big-endian, so convert the file type to native format
    		  	myType = EndianU32_BtoN(myType);
				
				// at this point, you probably want to do something interesting with the file type, eh?
				// this is left as an exercise for the reader
				
			} else {
				myErr = cannotFindAtomErr;
			}
		}
	}

bail:
	if (myContainer != NULL)
		QTDisposeAtomContainer(myContainer);
	return(myErr);
}


//////////
//
// QTGraphImp_ExportImageFile
// Let the user select a disk file, then export the current picture into that file.
//
//////////

OSErr QTGraphImp_ExportImageFile (GraphicsImportComponent theImporter)
{
	OSType					myType;
	FSSpec					myFSSpec;
	OSErr					myErr = noErr;

	myErr = GraphicsImportDoExportImageFileDialog(
							theImporter, 
							NULL, 			// no default name for the file
							NULL, 			// use the default prompt in the dialog box
							NULL, 			// no modal dialog filter procedure
							&myType, 		// return the type of the selected file
							&myFSSpec, 		// return the name (etc.) of the selected file
							NULL);			// don't return the script code
	return(myErr);
}