/*********************************************************/
/* This source code copyright (c) 1991-2001, Aaron Giles */
/* See the Read Me file for licensing information.       */
/* Contact email: mac@aarongiles.com                     */
/*********************************************************/

#if THINK_C
#include "THINK.Header"
#elif applec
#pragma load ":Headers:MPW.Header"
#elif __MWERKS__
//#include "MW.Header"
#else
#include "JPEGView.h"
#endif

static OSErr MakePreviewAndIcons(Handle theHandle, ImageHandle theImage, Boolean preview, 
			Boolean icons, NestedProgressPtr progProc, short cropWhat);
static OSErr AddQuickTimePreview(FSSpec *theFile, GWorldPtr thePreview, Rect *theRect);
static OSErr MakeIcons(FSSpec *theSpec, ImageHandle theImage);
static OSErr MakeOneIcon(short theFile, OSType theType, GWorldPtr src, GWorldPtr dst,
			Rect *srcRect, Rect *dstRect);
static void TrimIcon(Rect *theRect);
static OSErr PixMapToHandle(PixMapHandle theMap, Rect *theRect, Rect *maskRect, Handle *theHandle);

static Rect gPreviewRect, gIconRect;

/*
 * DoSaveDocument(theImage, theSpec, theType, preview, palette)
 *
 *     Purpose: Oversees the saving of a JPEG image file
 *      Inputs: theImage = the image to be saved
 *				theSpec = pointer to the destination file spec
 *				theType = the destination type
 *				preview = flag: true to create and save a preview image
 *				palette = flag: true to save the palette with the image
 *     Returns: an OSErr describing the result
 *
 */
 
OSErr DoSaveDocument(ImageHandle theImage, FSSpec *theSpec, OSType theType, Boolean preview, 
	Boolean palette, Boolean icons, short cropWhat)
{
	short total = (preview || icons) ? 1 : 0, i, theFile;
	NestedProgress theProc = { { nil, 0 }, 0, 0L, 0L };
	FSSpec tempSpec, oldSpec = (*theImage)->file;
	Handle finalData = (*theImage)->data;
	ImageFormatPtr theAction = nil;
	OSErr theErr = noErr;
	
	// see if we can find the destination type in the type list
	for (i = 0; i < kFileFormats; i++)
		if (theType == gFormat[i]->outType) {
			if (((*theImage)->compression == kJPEGCompression && !gFormat[i]->fix) ||
				((*theImage)->compression != kJPEGCompression && gFormat[i]->save != (*theImage)->format->save)) continue;
			theAction = gFormat[i];
			break;
		}
	if (!theAction) return errAETypeError;
	
	// ensure that we can actually save the current image in the requested format
	if (Banded(theImage) && !theAction->doesBanded) return errAETypeError;

	// adjust parameters to match the output format's limitations
	palette = palette && theAction->doesColors && (*theImage)->depth > 8;

	// initialize the progress proc, and open it right away
	theProc.prog.progressProc = gGenericProgress;
	theProc.prog.progressRefCon = (long)&theProc;
	UpdateParamText(gString[strPreparing], gNullString, gNullString, (*theImage)->file.name);
	CallICMProgressProc(theProc.prog.progressProc, codecProgressOpen, 0L, 
				theProc.prog.progressRefCon);

	// determine the temporary file name
	(*theImage)->file = *theSpec;
	FSMakeFSSpec(theSpec->vRefNum, theSpec->parID, gString[strJPEGViewTempFile], &tempSpec);

	// if we want to save a palette, and we haven't calculated it yet, do it now
	if (palette && !(*theImage)->qpalette) {
		short depth = (*(*theImage)->dmon)->depth;
		PaletteHandle thePalette;
		if (depth < 4 || depth > 8) depth = 8;
		total++;
		theProc.end = 0x0000F000 / total;
		theErr = DoQuantize(theImage, 1L << depth, &thePalette, &theProc);
		theProc.prog.progressRefCon = (long)&theProc;
		if (theErr == noErr) (*theImage)->qpalette = thePalette;
	}
	
	if (theErr == noErr) {
		// update the progress, and do any internal JPEG conversions that are necessary
		theProc.begin = theProc.end;
		theProc.end += total ? (0x0000F000 / total) : 0L;
		KeepSpinning();
		if ((*theImage)->compression == kJPEGCompression && theAction->fix) 
			theErr = theAction->fix(theImage, &finalData, palette);

		// if everything's ok, create previews and icons
		if (theErr == noErr) theErr = MakePreviewAndIcons((*theImage)->data, theImage,
					preview, icons, &theProc, cropWhat);
		theProc.prog.progressRefCon = (long)&theProc;
		KeepSpinning();

		// now, do the actual saving of the image
		if (theErr == noErr)
			UpdateParamText(gString[strSaving], gNullString, gNullString, (*theImage)->file.name);
		if (theErr == noErr) 
			if (theAction->save) theErr = theAction->save(&tempSpec, finalData, 
					preview ? gPreviewGWorld : nil, &gPreviewRect, (*theImage)->privateData);
			else theErr = DefaultSave(&tempSpec, finalData, nil, nil, theAction->outType);
		if (theErr == noErr && preview && !theAction->customPreviews)
			theErr = AddQuickTimePreview(&tempSpec, gPreviewGWorld, &gPreviewRect);
		KeepSpinning();
		
		// if everything's still ok, we actually add the icons to the new file
		if (theErr == noErr && icons) {
			FSpCreateResFile(&tempSpec, kCreator, theType, 0);
			theErr = MakeIcons(&tempSpec, theImage);
		}

		// if things are still going well, we save the cropping rectangle as well
		if (theErr == noErr && (Cropped(theImage) || cropWhat == kCropImage)) {
			Rect **theRect = (Rect **)NewHandle(sizeof(Rect));
			if (theRect) {
				if (cropWhat == kCropImage) SelRectToCropRect(theImage, *theRect);
				else **theRect = (*theImage)->crect;
				FSpCreateResFile(&tempSpec, kCreator, theType, 0);
				if (theFile = FSpOpenResFile(&tempSpec, fsRdWrPerm)) {
					AddResource((Handle)theRect, 'RECT', 0, gNullString);
					if ((theErr = ResError()) == noErr) WriteResource((Handle)theRect);
					else gIntError = errCantWriteFile;
					CloseResFile(theFile);
				} else theErr = ResError(), gIntError = errCantWriteFile;
				if (theErr != noErr) DisposeHandle((Handle)theRect);
			} else theErr = memFullErr, gIntError = errNoSaveMemory;
		}
		if (finalData && finalData != (*theImage)->data) DisposeHandle(finalData);
		KeepSpinning();
		
		// finally, if nothing bad happened, exchange the temp file for the real file
		if (theErr == noErr) {
			theErr = FSpExchangeFiles(theSpec, &tempSpec);
			if (theErr == fnfErr) theErr = FSpRename(&tempSpec, theSpec->name);
			if (theErr == noErr) {
				FInfo theInfo;
				theErr = FSpGetFInfo(theSpec, &theInfo);
				if (theErr == noErr) {
					if (icons) theInfo.fdFlags |= kHasCustomIcon;
					theInfo.fdFlags &= ~kHasBeenInited;
					theInfo.fdCreator = kCreator;
					theInfo.fdType = theType;
					theErr = FSpSetFInfo(theSpec, &theInfo);
				}
			}
		}
	}
	
	// clean up and get outta here
	FSpDelete(&tempSpec);
	KeepSpinning();
	ForceFolderUpdate(theSpec->parID, theSpec->vRefNum);
	theProc.end = 0x00010000;
	CallICMProgressProc(theProc.prog.progressProc, codecProgressClose, 0L, 
				theProc.prog.progressRefCon);
	(*theImage)->file = oldSpec;
	return theErr;
}

extern OSErr DefaultSave(FSSpec *theSpec, Handle theHandle, GWorldPtr thePreview, 
			Rect *theRect, OSType theType)
{
#if applec
#pragma unused(thePreview, theRect)
#endif
	long theSize = GetHandleSize(theHandle);
	char hState = HGetState(theHandle);
	short theFile;
	OSErr theErr;

	KeepSpinning();
	theErr = FSpCreate(theSpec, kCreator, theType, 0);
	if (theErr == noErr) {
		KeepSpinning();
		theErr = FSpOpenDF(theSpec, fsWrPerm, &theFile);
		if (theErr == noErr) {
			HLock(theHandle);
			SpinIndef();
			theErr = FSWrite(theFile, &theSize, *theHandle);
			KeepSpinning();
			if (theErr == noErr) {
				HSetState(theHandle, hState);
				if (theErr == noErr) {
					FSClose(theFile);
					return theErr;
				}
			} else gIntError = errCantWriteFile;
			HSetState(theHandle, hState);
			FSClose(theFile);
		} else gIntError = errCantOpenFile;
		FSpDelete(theSpec);
	} else gIntError = errCantCreateFile;
	return theErr;
}

static OSErr MakePreviewAndIcons(Handle theHandle, ImageHandle theImage, Boolean preview, 
			Boolean icons, NestedProgressPtr progProc, short cropWhat) 
{
#if applec
#pragma unused(theHandle)
#endif
	Rect prevSrcRect, iconSrcRect, prevDstRect, iconDstRect = { 0, 0, 32, 32 }, tmpRect;
	PixMapHandle dstPixMap = GetGWorldPixMap(gPreviewGWorld);
	short prevPort = -1, iconPort = -1;
	JVDrawParamsHandle theParams;
	StringPtr progressString;
	OSErr theErr = noErr;

	if (!preview && !icons) return noErr;
	
	// calculate the appropriate source/destination rectangles
	if (cropWhat != kCropImage) prevSrcRect = (*theImage)->crect;
	else SelRectToCropRect(theImage, &prevSrcRect);
	if (gThePrefs.iconStyle == isProportional || gThePrefs.iconStyle == isProportionalDog) {
		if (cropWhat == kCropNothing) iconSrcRect = (*theImage)->crect;
		else SelRectToCropRect(theImage, &iconSrcRect);
		MaxRect(&iconSrcRect, &gIconGWorld->portRect, &iconDstRect);
	} else {
		if (cropWhat == kCropNothing) MaxSquare(&prevSrcRect, &iconSrcRect);
		else {
			SelRectToCropRect(theImage, &tmpRect);
			MaxSquare(&tmpRect, &iconSrcRect);
		}
	}
	MaxRect(&prevSrcRect, &gPreviewGWorld->portRect, &prevDstRect);
	InsetRect(&iconDstRect, 1, 1);
	
	// set up the progress procedure
	if (preview && icons) progressString = gString[strCreatingPreviewIcons];
	else if (preview) progressString = gString[strCreatingPreview];
	else progressString = gString[strCreatingIcons];
	UpdateParamText(progressString, gNullString, gNullString, (*theImage)->file.name);

	// allocate and initialize the drawing parameters
	tmpRect = (*theImage)->grect;
	if (theParams = NewDrawParams(&tmpRect, iqVHigh, false, progProc, (*theImage)->privateData)) {
		if (preview) SetUpDrawPort(theParams, kOffscreenPort1, gPreviewGWorld, 
					&prevSrcRect, &prevDstRect, true);
		if (icons) SetUpDrawPort(theParams, preview ? kOffscreenPort2 : kOffscreenPort1,
					gIconGWorld, &iconSrcRect, &iconDstRect, true);

		// create the icons and preview
		KeepSpinning();
		theErr = PreflightDrawing(theParams);
		PushPort();
		if (theErr == noErr) {
		
			// lock down the pixels and erase the GWorlds
			LockPixels(GetGWorldPixMap(gPreviewGWorld));
			MySetPort(gPreviewGWorld);
			ClipRect(&gPreviewGWorld->portRect);
			RGBForeColor(&gBlack);
			RGBBackColor(&gWhite);
			if (NeedsWhiteBG(theImage)) EraseRect(&gPreviewGWorld->portRect);
			else PaintRect(&gPreviewGWorld->portRect);
			LockPixels(GetGWorldPixMap(gIconGWorld));
			MySetPort(gIconGWorld);
			ClipRect(&gPreviewGWorld->portRect);
			RGBForeColor(&gBlack);
			RGBBackColor(&gWhite);
			if (NeedsWhiteBG(theImage)) EraseRect(&gIconGWorld->portRect);
			else PaintRect(&gIconGWorld->portRect);
			
			// now get the ball rolling
			MySetPort((*theParams)->dummy);
			CallICMProgressProc((*theParams)->progress.prog.progressProc,
						codecProgressOpen, 0L, 
						(*theParams)->progress.prog.progressRefCon);
			
			// Either from the offscreen GWorld ...
			if ((*theImage)->gworld && GWOrigSize(theImage))
				CopyGWorldToGWorld((*theImage)->gworld, (*theParams)->dummy,
							&(*theImage)->gworld->portRect, &(*theImage)->gworld->portRect,
							srcCopy, nil);
			
			// or from the image directly!
			else theErr = (*theImage)->format->draw((*theImage)->data, theParams);

			CallICMProgressProc((*theParams)->progress.prog.progressProc,
						codecProgressClose, 0L,
						(*theParams)->progress.prog.progressRefCon);
			UnlockPixels(GetGWorldPixMap(gPreviewGWorld));
			UnlockPixels(GetGWorldPixMap(gIconGWorld));
			KeepSpinning();
			if (theErr == noErr) theErr = PostflightDrawing(theParams);
			else PostflightDrawing(theParams);
		}
		PopPort();
		DisposeHandle((Handle)theParams);
	}
	gPreviewRect = prevDstRect;
	gIconRect = iconDstRect;
	return theErr;
}

/*
 * AddQuickTimePreview(theFile)
 *
 *     Purpose: Adds the preview PICT to the newly-created PICT file
 *      Inputs: theFile = pointer to a file spec
 *     Returns: an OSErr describing what went wrong
 *
 */
 
static OSErr AddQuickTimePreview(FSSpec *theFile, GWorldPtr thePreview, Rect *theRect) 
{
	PixMapHandle dstPixMap = GetGWorldPixMap(thePreview);
	PicHandle previewData;
	short resRef;
	OSErr theErr;

	if (previewData = (PicHandle)NewHandle(4)) {
		KeepSpinning();
		LockPixels(dstPixMap);
		theErr = MakeThumbnailFromPixMap(dstPixMap, theRect, 32, (PicHandle)previewData, 
					(ICMProgressProcRecordPtr)&gDummyProg);
		UnlockPixels(dstPixMap);
		KeepSpinning();
		if (theErr == noErr) {
			FSpCreateResFile(theFile, kCreator, kPICTType, 0);
			KeepSpinning();
			if (resRef = FSpOpenResFile(theFile, fsRdWrPerm)) {
				KeepSpinning();
				theErr = AddFilePreview(resRef, kPICTType, (Handle)previewData);
				KeepSpinning();
				CloseResFile(resRef);
			} else gIntError = errCantWriteFile, theErr = fnfErr;
		}
	} else gIntError = errCantMakePreview, theErr = memFullErr;
	return theErr;
}

/*
 * MakeIcons(theSpec, theImage)
 *
 *     Purpose: Handles the creation of icons for theImage
 *      Inputs: theSpec = pointer to the destination file spec
 *				theImage = handle to the image to make icons for
 *     Returns: an OSErr describing the result
 *
 */
 
static OSErr MakeIcons(FSSpec *theSpec, ImageHandle theImage)
{
#if applec
#pragma unused(theImage)
#endif
	Rect bigRect = { 0, 0, 32, 32 }, smallRect = { 0, 0, 16, 16 };
	OSErr theErr = noErr;
	GWorldPtr theGWorld;
	short theFile;
	
	if (!(theFile = FSpOpenResFile(theSpec, fsRdWrPerm))) 
		return gIntError = errCantCreateIcons, ResError();
	while (true) {
		if (!(theGWorld = MyNewGWorld(&bigRect, 8, nil, nil, true, false)))
			if (!(theGWorld = MyNewGWorld(&bigRect, 8, nil, nil, true, true))) {
				theErr = memFullErr;
				break;
			}
		theErr = MakeOneIcon(theFile, large8BitData, gIconGWorld, theGWorld, &gIconRect, &bigRect);
		if (theErr != noErr) break;
		theErr = MakeOneIcon(theFile, small8BitData, gIconGWorld, theGWorld, &gIconRect, &smallRect);
		if (theErr != noErr) break;
		DisposeGWorld(theGWorld);
		theGWorld = nil;
		if (!(theGWorld = MyNewGWorld(&bigRect, 4, nil, nil, true, false)))
			if (!(theGWorld = MyNewGWorld(&bigRect, 4, nil, nil, true, true))) {
				theErr = memFullErr;
				break;
			}
		theErr = MakeOneIcon(theFile, large4BitData, gIconGWorld, theGWorld, &gIconRect, &bigRect);
		if (theErr != noErr) break;
		theErr = MakeOneIcon(theFile, small4BitData, gIconGWorld, theGWorld, &gIconRect, &smallRect);
		if (theErr != noErr) break;
		DisposeGWorld(theGWorld);
		theGWorld = nil;
		if (!(theGWorld = MyNewGWorld(&bigRect, 1, nil, nil, true, false)))
			if (!(theGWorld = MyNewGWorld(&bigRect, 1, nil, nil, true, true))) {
				theErr = memFullErr;
				break;
			}
		theErr = MakeOneIcon(theFile, large1BitMask, gIconGWorld, theGWorld, &gIconRect, &bigRect);
		if (theErr != noErr) break;
		theErr = MakeOneIcon(theFile, small1BitMask, gIconGWorld, theGWorld, &gIconRect, &smallRect);
		break;
	}
	if (theGWorld) DisposeGWorld(theGWorld);
	if (theFile) CloseResFile(theFile);
	if (theErr != noErr) gIntError = errCantCreateIcons;
	return theErr;
}

/*
 * MakeOneIcon(theFile, theType, src, dst, srcRect, dstRect)
 *
 *     Purpose: Creates and adds a single icon resource to theFile
 *      Inputs: theFile = path number of the destination file's resource fork
 *				theType = the type of icon to create
 *				src = pointer to the source GWorld
 *				dst = pointer to a properly-sized destination GWorld
 *				srcRect = pointer to the source image's rectangle
 *				dstRect = pointer to the destination icon's rectangle
 *     Returns: an OSErr describing the result
 *
 */
 
static OSErr MakeOneIcon(short theFile, OSType theType, GWorldPtr src, GWorldPtr dst,
			Rect *srcRect, Rect *dstRect)
{
#if applec
#pragma unused(theFile)
#endif
	PixMapHandle srcMap = GetGWorldPixMap(src), dstMap = GetGWorldPixMap(dst);
	Rect realDstRect = *srcRect;
	Handle theHandle;
	short offset;
	OSErr theErr;
	
	MapRect(&realDstRect, &src->portRect, dstRect);
	if (Width(&realDstRect) < Height(&realDstRect)) {
		offset = (Height(&realDstRect) - Width(&realDstRect)) >> 1;
		realDstRect.left += offset;
		realDstRect.right += offset;
	} else {
		offset = (Width(&realDstRect) - Height(&realDstRect)) >> 1;
		realDstRect.top += offset;
		realDstRect.bottom += offset;
	}
	PushPort();
	MySetPort(dst);
	HLock((Handle)srcMap);
	HLock((Handle)dstMap);
	LockPixels(srcMap); 
	LockPixels(dstMap);
	EraseRect(dstRect);
	CopyBits((BitMap *)*srcMap, (BitMap *)*dstMap, srcRect, &realDstRect, srcCopy + ditherCopy, nil);
	UnlockPixels(srcMap);
	HUnlock((Handle)srcMap);
	TrimIcon(&realDstRect);
	if ((theErr = PixMapToHandle(dstMap, dstRect, &realDstRect, &theHandle)) != noErr) {
		PopPort();
		return theErr;
	}
	UnlockPixels(dstMap);
	HUnlock((Handle)dstMap);
	PopPort();
	AddResource(theHandle, theType, kCustomIconResource, gNullString);
	if ((theErr = ResError()) == noErr) {
		WriteResource(theHandle);
		KeepSpinning();
		ReleaseResource(theHandle);
		return noErr;
	};
	DisposeHandle(theHandle);
	return theErr;
}

/*
 * TrimIcon(theRect)
 *
 *     Purpose: Trims the appropriate icon by folding over the upper-right corner
 *      Inputs: theRect = pointer to the icon's rectangle
 *     Returns: nothing
 *
 */
 
static void TrimIcon(Rect *theRect)
{
	short offset = (Height(theRect) > 16 || Width(theRect) > 16) ? 6 : 4;
	Rect cornerRect;

	RGBForeColor(&gBlack);
	RGBBackColor(&gWhite);
	FrameRect(theRect);
	if (gThePrefs.iconStyle != isSquareDog && gThePrefs.iconStyle != isProportionalDog) return;
	MySetRect(&cornerRect, theRect->right - offset - 1, theRect->top, theRect->right, theRect->top + offset);
	EraseRect(&cornerRect);
	cornerRect.right--;
	MoveTo(cornerRect.left, cornerRect.top);
	LineTo(cornerRect.right, cornerRect.bottom);
	LineTo(cornerRect.left, cornerRect.bottom);
	LineTo(cornerRect.left, cornerRect.top);
}

/*
 * PixMapToHandle(theMap, theRect, theHandle)
 *
 *     Purpose: Takes the icon image in theMap and makes an icon handle from it
 *      Inputs: theMap = handle to the icon's pixmap
 *				theRect = pointer to the icon's rectangle
 *				theHandle = pointer to a place to receive the new handle
 *     Returns: an OSErr describing the result
 *
 */
 
static OSErr PixMapToHandle(PixMapHandle theMap, Rect *theRect, Rect *maskRect, Handle *theHandle)
{
	long theSize = (Height(theRect) * Width(theRect) * (*theMap)->pixelSize) >> 3;
	long r, c, words, rowBytes = (*theMap)->rowBytes & 0x3fff;
	char mode = true32b;
	ushort *s, *d;

	if ((*theMap)->pixelSize == 1) theSize <<= 1;
	if (*theHandle = NewHandle(theSize)) {
		words = (Width(theRect) * (*theMap)->pixelSize) >> 4;
		s = (ushort *)GetPixBaseAddr(theMap);
		d = (ushort *)StripAddress(**theHandle);
		SwapMMUMode(&mode);
		for (r = 0; r < Height(theRect); r++) {
			for (c = 0; c < words; c++) *d++ = s[c];
			s += rowBytes >> 1;
		}
		SwapMMUMode(&mode);
	} else return memFullErr;
	if ((*theMap)->pixelSize == 1) {
		short offset = (Height(theRect) > 16 || Width(theRect) > 16) ? 6 : 4;
		Rect cornerRect;
		EraseRect(theRect);
		PaintRect(maskRect);
		if (gThePrefs.iconStyle == isSquareDog || gThePrefs.iconStyle == isProportionalDog) {
			MySetRect(&cornerRect, maskRect->right - offset - 1, maskRect->top, maskRect->right, maskRect->top + offset);
			EraseRect(&cornerRect);
			cornerRect.right--;
			MoveTo(cornerRect.left, cornerRect.top);
			LineTo(cornerRect.right, cornerRect.bottom);
			while (++cornerRect.top != cornerRect.bottom) {
				MoveTo(cornerRect.left, cornerRect.top);
				LineTo(cornerRect.left + cornerRect.top - maskRect->top, cornerRect.top);
			}
		}
		s = (ushort *)GetPixBaseAddr(theMap);
		SwapMMUMode(&mode);
		for (r = 0; r < Height(theRect); r++) {
			for (c = 0; c < words; c++) *d++ = s[c];
			s += rowBytes >> 1;
		}
		SwapMMUMode(&mode);
	}
	return noErr;
}

/*
 * DoOpenDocument(theSpec, first)
 *
 *     Purpose: Calls the appropriate routines to open an new image
 *      Inputs: theSpec = the file specification for the new input file
 *				first = flag: true if this is the first image in a series
 *     Returns: an OSErr describing the result
 *
 */
 
OSErr DoOpenDocument(FSSpec *theFile, Boolean first, DescType openScreen, DescType openFull,
	Boolean autoExpand, Boolean openVisible, DescType openPalette, DescType drawQuality,
	Boolean openBitmaps, Boolean autoComments, Boolean fixTypes, Boolean fixCreator)
{
	OSErr theErr = noErr;
	ImageHandle theImage;

	LMSetSFSaveDisk(-theFile->vRefNum);
	LMSetCurDirStore(theFile->parID);
	if (theImage = NewImage()) {
		(*theImage)->file = *theFile;
		if ((theErr = ReadFile(theImage, fixTypes, fixCreator)) == noErr) {
			if ((theErr = InitImage(theImage, openScreen, openFull, drawQuality, openVisible)) == noErr) {
				if ((theErr = MakeNewWindow(theImage, first, autoExpand)) == noErr) {
					MySetPort((CGrafPtr)(*theImage)->window);
					if (gSlideShow) SlideProgress(codecProgressRealOpen, 0, 0);
					AddWindowItem(theImage);
					if ((theErr = PickFirstPalette(theImage, openPalette)) == noErr) {
						if ((theErr = RenderOffscreen(theImage, openBitmaps)) == noErr) {
							if (gSlideShow) SlideProgress(codecProgressRealClose, 0, 0);
							if (openVisible) {
								if (!gSlideShow && first) ChangeActive((*theImage)->window);
								else FWShowWindow((*theImage)->window);
								if (autoComments && (*theImage)->comments &&
									((*theImage)->window == FWFrontWindow())) {
									if (OpenComments() == noErr)
										ChangeActive(GetCommentsWindow());
								}
								SetStatistics();
								AdjustMenus();
							}
							return noErr;
						}
					}
					if (gSlideShow) SlideProgress(codecProgressRealClose, 0, 0);
				} else gIntError = errCantOpenWindow;
			}
		}
	} else gIntError = errNoLoadMemory, theErr = memFullErr;
	if ((theErr != codecAbortErr || gSlideShow) && theImage) {
		if ((*theImage)->window) DoCloseWindow((*theImage)->window, gThePrefs.restoreColors);
		else DisposeImage(theImage);
	}
	if (theErr == errBadJPEG) theErr = codecBadDataErr;
	return theErr;
}

/*
 * ReadFile(theImage)
 *
 *     Purpose: Opens the input file, figures its type, and dispatches the read operation
 *					to the appropriate routine
 *      Inputs: theImage = the destination image record for the new image
 *     Returns: an OSErr describing the problem
 *
 */
 
OSErr ReadFile(ImageHandle theImage, Boolean fixTypes, Boolean fixCreator)
{
	FSSpec theSpec = (*theImage)->file;
	short theFile, theType;
	OSErr theErr = noErr;
	
	if (gGetAbout) {
		(*theImage)->format = &gPICTFormat;
		(*theImage)->data = GetResource(kPICTType, rAboutPict);
		if (!(*theImage)->data) theErr = ResError();
		else DetachResource((*theImage)->data);
		(*theImage)->flags |= ifIsAboutBox;
		gGetAbout = false;
		return theErr;
	}
	if ((theErr = FSpOpenDF(&theSpec, fsRdPerm, &theFile)) != noErr)
		theFile = -1;
	if ((theType = IDImage(theFile, &theSpec)) != kInvalidType) {
		if (fixTypes) {
			FInfo theInfo;
			FSpGetFInfo(&theSpec, &theInfo);
			if (theInfo.fdType != gFormat[theType]->inType &&
						theInfo.fdType != gFormat[theType]->altInType) {
				theInfo.fdType = gFormat[theType]->inType;
				if (fixCreator) theInfo.fdCreator = kCreator;
				FSpSetFInfo(&theSpec, &theInfo);
				FSClose(theFile);
				theErr = FSpOpenDF(&theSpec, fsRdPerm, &theFile);
			}
		}
		if (theErr == noErr) {
			(*theImage)->format = gFormat[theType];
			if ((*theImage)->format->load)
				theErr = (*theImage)->format->load(theFile, theImage);
			else theErr = DefaultLoad(theFile, theImage);
		}
	} else {
		if (gIntError != errHSIFile) gIntError = errInvalidFile;
		theErr = errAETypeError;
	}
	if (theFile != -1) FSClose(theFile);
	return theErr;
}

//=====================================================================================
// OSErr DefaultLoad(short theFile, ImageHandle theImage) 
//=====================================================================================
// Performs a generic load of an image into memory, with no special processing.
//=====================================================================================

extern OSErr DefaultLoad(short theFile, ImageHandle theImage) 
{
	OSErr theErr = noErr;
	long theSize;
	
	KeepSpinning();
	GetEOF(theFile, &theSize);
	if (!MakeMemAvailable(theSize)) return gIntError = errNoLoadMemory, memFullErr;
	if ((*theImage)->data = NewHandle(theSize)) {
		HLock((*theImage)->data);
		SpinIndef();
		theErr = FSRead(theFile, &theSize, *(*theImage)->data);
		KeepSpinning();
		HUnlock((*theImage)->data);
		if (theErr == noErr) return theErr;
		else DisposeHandle((*theImage)->data);
		gIntError = errCantReadFile;
	} else gIntError = errNoLoadMemory, theErr = memFullErr;
	return theErr;
}

/*
 * IDImage(theFile, theSpec)
 *
 *     Purpose: Determines the image type by loading in a little and examining the header
 *      Inputs: theFile = a pointer to the file number
 *				theSpec = the original file specification
 *     Returns: the index of the file type, or -1 if not a valid type
 *
 */
 
short IDImage(short theFile, FSSpec *theSpec) 
{
	uchar theBuffer[kHeaderSize + 64];
	long theSize = kHeaderSize + 64;
	OSErr theErr = noErr;
	FInfo theInfo;
	short i;
	
	if (theFile != -1) {
		SetFPos(theFile, fsFromStart, 0);
		theErr = FSRead(theFile, &theSize, theBuffer);
		if (theErr != noErr && theErr != eofErr) theSize = 0;
		SetFPos(theFile, fsFromStart, 0);
	} else theSize = 0;
	if (FSpGetFInfo(theSpec, &theInfo) == noErr) {
		for (i = 0; i < kFileFormats; i++) 
			if (gFormat[i]->id) {
				Boolean valid = gFormat[i]->id(theBuffer, theSize, theFile, theSpec);
				if (valid && (gFormat[i]->positiveID ||
							gFormat[i]->inType == theInfo.fdType ||
							gFormat[i]->altInType == theInfo.fdType)) return i;
			}
	}
	if (theBuffer[0] == 'h' && theBuffer[1] == 's' && theBuffer[2] == 'i' && theBuffer[3] == '1') gIntError = errHSIFile;
/*	
#define errBinHexData 26
#define errUUEncodeData 27
*/
	return kInvalidType;
}

/*
 * InitImage(theImage, openScreen, openFull, drawQuality)
 *
 *     Purpose: Initializes the fields in the image record
 *      Inputs: theImage = the destination image record
 *				openScreen = AE enum describing which screen to open up on
 *				openFull = AE enum describing how to use full screen windows
 *				drawQuality = AE enum describing the default drawing quality
 *				visible = flag: true if the image is to be made visible
 *     Returns: an OSErr describing what went wrong
 *
 */
 
OSErr InitImage(ImageHandle theImage, DescType openScreen, DescType openFull, 
	DescType drawQuality, Boolean visible) 
{
	FSSpec theSpec = (*theImage)->file;
	short choice, refNum;
	OSErr theErr, AEErr;
	
	switch (openScreen) {
		case kAEOSDeepestColor: (*theImage)->dmon = GetDeepMonitor(true); break;
		case kAEOSDeepestGray: (*theImage)->dmon = GetDeepMonitor(false); break;
		case kAEOSLargestColor: (*theImage)->dmon = GetBigMonitor(true); break;
		case kAEOSLargestGray: (*theImage)->dmon = GetBigMonitor(false); break;
		case kAEOSMain: (*theImage)->dmon = gMainMonitor; break;
	}
	(*theImage)->smon = (*theImage)->dmon;
	theErr = (*theImage)->format->open(theImage);
	if (Height(&(*theImage)->grect) <= 0 || Width(&(*theImage)->grect) <= 0)
		gIntError = errCorruptImage, theErr = codecBadDataErr;
	if (theErr == errBadJPEG && (!gSlideShow || !(*gSlideOptions)->noErrors) &&
				(gThePrefs.useQuickTime && gQTVersion)) {
		if (gInAppleEvent) {
			AEErr = AEInteractWithUser(kAEDefaultTimeout, &gTheNotification, 
										gAEIdleProc);
			if (AEErr == errAENoUserInteraction) return codecBadDataErr;
		}
		StopSpinning(&qd.arrow);
		ParamText(gNullString, (*theImage)->file.name, gNullString, gNullString);
		choice = FWCautionAlert(CenterAlert(rBadJPEGAlert), gGenericFilter);
		if (choice == 3 || choice == kDialogCancel) return theErr;
		else gIntError = noErr, theErr = noErr;
		(*theImage)->flags |= ifCorrupt;
		StartSpinning();
	}
	if (UseFullScreen(theImage, openFull, visible)) (*theImage)->flags |= ifFull;
	if (gSlideShow && (*gSlideOptions)->fileNames) (*theImage)->flags |= ifFilename;
	switch (drawQuality) {
		case kAEDQVeryHigh: (*theImage)->quality = iqVHigh; break;
		case kAEDQHigh: (*theImage)->quality = iqHigh; break;
		case kAEDQNormal: (*theImage)->quality = iqMedium; break;
	}
	if ((*theImage)->comments) {
		Size oldLength = GetHandleSize((*theImage)->comments), length = 0, i;
		uchar *src = (uchar *)*(*theImage)->comments, *dst = src;
		
		for (i = 0; i < oldLength; i++)
			if (*src >= 0x20) break;
			else src++;
		for ( ; i < oldLength; i++)
			if ((*src == 0x0d) || (*src >= 0x20)) *dst++ = *src++, length++;
			else src++;
		while ((length > 0) && (*--dst < 0x20)) length--;
		if (length > 0) SetHandleSize((*theImage)->comments, length);
		else DisposeHandle((*theImage)->comments), (*theImage)->comments = nil;
	}
	if (refNum = FSpOpenResFile(&theSpec, fsRdPerm)) {
		Rect **theRect;
		if (theRect = (Rect **)Get1Resource('RECT', 0)) {
			(*theImage)->crect = **theRect;
			(*theImage)->flags |= ifCropped;
		}
		CloseResFile(refNum);
	}
	return theErr;
}

/*
 * UseFullScreen(theImage, openFull, visible)
 *
 *     Purpose: Determines whether the new image should be opened in full screen mode
 *      Inputs: theImage = the image record for the image in question
 *				openFull = AE enum describing how to use full screen windows
 *				visible = flag: true if the image is to be made visible
 *     Returns: nothing
 *
 */
 
Boolean UseFullScreen(ImageHandle theImage, DescType openFull, Boolean visible) 
{
	Rect screenRect, theRect;
	Boolean result;
	short choice;
	OSErr AEErr;

	if (openFull == kAEOFAlways) return true;
	else if (openFull == kAEOFNever) return false;
	GetActiveRect((*theImage)->dmon, &screenRect);
	screenRect.top += gTitleBarHeight;
	theRect = (*theImage)->crect;
	OffsetRect(&theRect, -theRect.left, -theRect.top);
	if (Width(&theRect) > Width(&screenRect))
		theRect.bottom = (long)theRect.bottom * (long)Width(&screenRect) / (long)Width(&theRect);
	if (Height(&theRect) <= Height(&screenRect)) return false;
	if (openFull == kAEOFAlwaysIfLarge) return true;
	if (!visible) return false;
	if (gInAppleEvent) {
		AEErr = AEInteractWithUser(kAEDefaultTimeout, &gTheNotification, 
									gAEIdleProc);
		if (AEErr == errAENoUserInteraction) return false;
	}
	StopSpinning(&qd.arrow);
	ParamText(gNullString, (*theImage)->file.name, gNullString, gNullString);
	choice = FWAlert(CenterAlert(rTooBigAlert), gGenericFilter);
	result = (choice == 1 || choice == kDialogOK);
	StartSpinning();
	return result;
}

/*
 * PickFirstPalette(theImage, openPalette)
 *
 *     Purpose: Determines which palette we should use for the given image
 *      Inputs: theImage = the image record for the image in question
 *				openFull = AE enum describing palette preferences
 *     Returns: an OSErr describing what went wrong
 *
 */
 
OSErr PickFirstPalette(ImageHandle theImage, DescType openPalette)
{
	short depth = (*(*theImage)->dmon)->depth;
	Boolean image = (*theImage)->ipalette && ((*(*theImage)->ipalette)->pmEntries <= (1L << depth));
	Boolean quantize = ((*theImage)->depth >= 16);
	short palette = plNone;
	OSErr theErr = noErr;
	DescType thePalette;

	while (true) {
		if (!(*(*theImage)->dmon)->color && (depth <= 8)) {
			palette = plGrey;
			break;
		} else if (depth <= 2) {
			palette = plGrey;
			break;
		} else if (depth > 8) {
			palette = plSys;
			break;
		}
		switch (openPalette) {
			case kAEOPQuantImageScrn:
			case kAEOPQuantScrn:
				if (quantize) palette = plQuant;
				if ((openPalette != kAEOPQuantScrn) && image && (palette == plNone)) 
					palette = plImage;
				if (palette == plNone) palette = plSys;
				break;
			case kAEOPImageQuantScrn:
			case kAEOPImageScrn:
				if (image) palette = plImage;
				if ((openPalette != kAEOPImageScrn) && quantize && (palette == plNone))
					palette = plQuant;
				if (palette == plNone) palette = plSys;
				break;
			case kAEOPScrn:
				palette = plSys;
				break;
		}
		break;
	}
	switch (palette) {
		case plGrey: thePalette = kAEPGrayscale; break;
		case plImage: thePalette = kAEPImage; break;
		case plQuant: thePalette = kAEPQuantized; break;
		case plSys:
		default: thePalette = kAEPSystem; break;
	}
	if (!gThePrefs.ditherQuant && (palette == plQuant ||
		 (palette == plImage && (*theImage)->depth >= 16))) 
		(*theImage)->flags &= ~ifDithered;
	else (*theImage)->flags |= ifDithered;
	if (theErr == noErr) theErr = DoSetPalette((*theImage)->window, thePalette);
	if (palette == plQuant && (*theImage)->npalette != plQuant) (*theImage)->flags |= ifDithered;
	if (theErr == codecAbortErr && !gSlideShow) {
		theErr = noErr;
		gIntError = 0;
	}
	return theErr;
}

/*
 * RenderOffscreen(theImage, openBitmaps)
 *
 *     Purpose: Renders the given image into an offscreen buffer, if appropriate
 *      Inputs: theImage = a pointer to the image in question
 *				openBitmaps = flag: true if we should force an offscreen bitmap
 *     Returns: an OSErr describing the problem, if any
 *
 */
 
OSErr RenderOffscreen(ImageHandle theImage, Boolean openBitmaps)
{
	short flags = bfOffscreen + bfForceFull;
	OSErr theErr = noErr;
	ImageHandle image;
	Boolean itWorked;
	
	if ((*theImage)->gworld) return noErr;
	if (openBitmaps) {
		itWorked = MakeImageGWorld(theImage, false);
		if (!itWorked && gSlideShow) 
			for (image = gImageRoot; image; image = (*image)->next) KillGWorld(image);
		if (itWorked || MakeImageGWorld(theImage, false)) {
			if (!gSlideShow) {
				ChangeActive((*theImage)->window);
				flags |= bfOnscreen;
			}
			if (!(flags & bfOnscreen) && gSlideShow) flags |= bfUseSlideProgress;
			SetSlideControlsText(gString[strSlideRendering], (*theImage)->file.name, gNullString, gNullString);
			theErr = DrawImage(theImage, flags, nil);
		} else if (!gSlideShow && ((*theImage)->compression || !gThePrefs.noUncomp))
			gIntError = errNoOffscreenMemory, theErr = memFullErr;
	}
	return theErr;
}
