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

//=====================================================================================
// Generic includes for Macintosh headers
//=====================================================================================

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

//=====================================================================================
// Includes specific to this module
//=====================================================================================

#include "JPEG.h"

//=====================================================================================
// Global variables local to this module
//=====================================================================================

static Handle gJPEGDestHandle;
static CTabHandle gJFIFColors = nil;
static StdPixUPP gUnwrapPixProc = nil;
static QDBitsUPP gDummyBitsProc;
static QDTextUPP gDummyTextProc;
static QDLineUPP gDummyLineProc;
static QDRectUPP gDummyRectProc;
static QDRRectUPP gDummyRRectProc;
static QDOvalUPP gDummyOvalProc;
static QDArcUPP gDummyArcProc;
static QDPolyUPP gDummyPolyProc;
static QDRgnUPP gDummyRgnProc;

// JPEG quantization tables for identifying IJG-compressed images
static ushort gIJGLumiTab[64] = {
	16,  11,  12,  14,  12,  10,  16,  14,
	13,  14,  18,  17,  16,  19,  24,  40,
	26,  24,  22,  22,  24,  49,  35,  37,
	29,  40,  58,  51,  61,  60,  57,  51,
	56,  55,  64,  72,  92,  78,  64,  68,
	87,  69,  55,  56,  80, 109,  81,  87,
	95,  98, 103, 104, 103,  62,  77, 113,
   121, 112, 100, 120,  92, 101, 103,  99
};

static ushort gIJGChromTab[64] = {
    17,  18,  18,  24,  21,  24,  47,  26,
    26,  47,  99,  66,  56,  66,  99,  99,
    99,  99,  99,  99,  99,  99,  99,  99,
    99,  99,  99,  99,  99,  99,  99,  99,
    99,  99,  99,  99,  99,  99,  99,  99,
    99,  99,  99,  99,  99,  99,  99,  99,
    99,  99,  99,  99,  99,  99,  99,  99,
    99,  99,  99,  99,  99,  99,  99,  99
};

//=====================================================================================
// Prototypes for functions local to this module
//=====================================================================================

static OSErr DrawJPEGQuickTime(Handle theHandle, JVDrawParamsHandle theParams);
static void InitJPEGUPPs(void);
static uchar *GetJPEGData(uchar *theAdr, long theLen, uchar theCode);
static void GetJPEGInfo(ImageHandle theImage);
static Boolean CalcIJGTable(ushort theID, uchar *theData, long *theQuality);
static Boolean CalcAppleTable(ushort theID, uchar *theData, long *theQuality);
static OSErr CompressJFIFPreview(PixMapHandle thePixMap, Rect *theRect, Handle *theHandle);
static OSErr WriteJFIFHeader(short theFile, GWorldPtr thePreview, Rect *theRect);
static OSErr MakeJPEGImageDesc(Handle theHandle, ImageDescriptionHandle theDesc);
static pascal void UnwrapPixProc(PixMap *src, Rect *srcRect, MatrixRecord *matrix,
			short mode, RgnHandle mask, PixMap *matte, Rect *matteRect, 
			short callOldBits);
static pascal void DummyBitsProc(PixMap *src, Rect *srcRect, Rect *dstRect, short mode, 
			RgnHandle mask);
static pascal void DummyTextProc(short count, const void *textAddr, Point numer,
			Point denom);
static pascal void DummyLineProc(Point newPt);
static pascal void DummyRectProc(GrafVerb verb, const Rect *r);
static pascal void DummyRRectProc(GrafVerb verb, const Rect *r, short ovalWidth, 
			short ovalHeight);
static pascal void DummyOvalProc(GrafVerb verb, const Rect *r);
static pascal void DummyArcProc(GrafVerb verb, const Rect *r, short startAngle,
			short arcAngle);
static pascal void DummyPolyProc(GrafVerb verb, PolyHandle poly);
static pascal void DummyRgnProc(GrafVerb verb, RgnHandle rgn);

//=====================================================================================
// Boolean VerifyJPEGData(uchar *theData, long theSize)
//=====================================================================================
// Does a quick sanity check on the JPEG data.
//=====================================================================================

extern Boolean VerifyJPEGData(uchar *theData, long theSize)
{
	return (GetJPEGData(theData, theSize, 0xd9) != nil);
}

//=====================================================================================
// Boolean idJPEG(uchar *theData, long theSize)
//=====================================================================================
// Examines the given data and attempts to identify it as a JPEG image.
//=====================================================================================

extern Boolean idJPEG(uchar *theData, long theSize, short refNum, FSSpec *theSpec)
{
#if applec
#pragma unused(refNum, theSpec)
#endif
	return (theSize > 4 && theData[0] == 0xff && theData[1] == 0xd8 &&
				theData[2] == 0xff);
}

//=====================================================================================
// OSErr OpenJPEG(ImageHandle theImage)
//=====================================================================================
// Initializes the image record for a JPEG image.
//=====================================================================================

extern OSErr OpenJPEG(ImageHandle theImage)
{
	long theLen = GetHandleSize((*theImage)->data);
	FSSpec theSpec = (*theImage)->file;
	Handle theComments = nil;
	uchar *theData, theCode;
	OSErr theErr = noErr;
	short refNum;

//	if (!gQTVersion) gIntError = errNoQuickTime, theErr = codecBadDataErr;
	if (refNum = FSpOpenResFile(&theSpec, fsRdPerm)) {
		CTabHandle theColors;
		if (theColors = (CTabHandle)Get1Resource('clut', 0))
			(*theImage)->ipalette = NewPalette((*theColors)->ctSize + 1, theColors,
						pmTolerant, 0);
		CloseResFile(refNum);
	}
	if (!VerifyJPEGData((uchar *)*(*theImage)->data, theLen))
		gIntError = errCorruptImage, theErr = errBadJPEG;
	for (theCode = 0xc0; theCode < 0xd0; theCode++)
		if (theData = GetJPEGData((uchar *)*(*theImage)->data, theLen, theCode)) break;
	if (theCode == 0xd0) return gIntError = errCorruptImage, codecBadDataErr;
	GetJPEGInfo(theImage);
	(*theImage)->compression = kJPEGCompression;
	MySetRect(&(*theImage)->grect, 0, 0, 
				(theData[3] << 8) + theData[4], (theData[1] << 8) + theData[2]);
	if (theData[5] == 3) (*theImage)->depth = 32;
	else if (theData[5] == 1) {
		(*theImage)->depth = 8;
		(*theImage)->ipalette = NewPalette(256, nil, pmTolerant, 0);
		if ((*theImage)->ipalette)
			CopyPalette(GreyPalette(8), (*theImage)->ipalette, 0, 0, 256);
	} else gIntError = errCorruptImage, errBadJPEG;
	(*theImage)->crect = (*theImage)->qrect = (*theImage)->grect;
	HLock((*theImage)->data);
	ExtractComments(*(*theImage)->data, GetHandleSize((*theImage)->data), &theComments);
	HUnlock((*theImage)->data);
	(*theImage)->comments = theComments;
	if ((*theImage)->desc.spatialQuality) {
		Str63 theDesc;
		BlockMove(gString[strIJGJPEG], theDesc, *gString[strIJGJPEG] + 1);
		StuffNumber(theDesc, 1, (*theImage)->desc.spatialQuality);
		BlockMove(theDesc, (*theImage)->compressionDesc, *theDesc + 1);
	} else BlockMove((*theImage)->desc.name, (*theImage)->compressionDesc, *(*theImage)->desc.name + 1);
	return theErr;
}

//=====================================================================================
// OSErr DrawJPEG(Handle theHandle, JVDrawParamsHandle theParams)
//=====================================================================================
// Draws a JPEG image, whose data is in theHandle, to the destination described in the
// drawing parameters.
//=====================================================================================

extern OSErr DrawJPEG(Handle theHandle, JVDrawParamsHandle theParams)
{
	if (!gQTVersion || !gThePrefs.useQuickTime)
		return DrawJPEGNoQuickTime(theHandle, theParams);
	else return DrawJPEGQuickTime(theHandle, theParams);
}

//=====================================================================================
// OSErr DrawJPEG(Handle theHandle, JVDrawParamsHandle theParams)
//=====================================================================================
// Draws a JPEG using QuickTime.
//=====================================================================================

static OSErr DrawJPEGQuickTime(Handle theHandle, JVDrawParamsHandle theParams)
{
	PixMapHandle dPixMap = GetGWorldPixMap((CGrafPtr)qd.thePort);
	NestedProgress theProgress = (*theParams)->progress;
	long theSize = GetHandleSize(theHandle);
	Rect srcRect = (*theParams)->bounds;
	char hState = HGetState(theHandle);
	ImageDescriptionHandle theDesc;
	MatrixRecord theMatrix;
	OSErr theErr;

	KeepSpinning();
	if (theDesc = (ImageDescriptionHandle)AnyNewHandle(sizeof(ImageDescription))) {
		HLock((Handle)theDesc);
		theErr = MakeJPEGImageDesc(theHandle, theDesc);
		if (theErr == noErr) {
			SetIdentityMatrix(&theMatrix);
			HLock(theHandle);
			FixBits((CGrafPtr)qd.thePort, true);
			KeepSpinning();
			if (theProgress.prog.progressProc)
				CallICMProgressProc(theProgress.prog.progressProc,
							codecProgressUpdatePercent, 0x00000000L,
							theProgress.prog.progressRefCon);
			InstallQDxDispatchPatch(true);
			theErr = FDecompressImage(StripAddress(*theHandle), theDesc, dPixMap,
						&srcRect, &theMatrix, srcCopy, qd.thePort->visRgn, nil, nil,
						codecHighQuality, anyCodec, theSize, nil,
						(ICMProgressProcRecordPtr)&theProgress);
			RemoveQDxDispatchPatch();
			if (theProgress.prog.progressProc)
				CallICMProgressProc(theProgress.prog.progressProc,
							codecProgressUpdatePercent, 0x00010000L,
							theProgress.prog.progressRefCon);
			if ((*theParams)->progress.aborted) theErr = codecAbortErr;
			KeepSpinning();
			FixBits((CGrafPtr)qd.thePort, false);
			HSetState(theHandle, hState);
		}
		DisposeHandle((Handle)theDesc);
	} else gIntError = errNoDrawMemory, theErr = memFullErr;
	return theErr;
}

//=====================================================================================
// OSErr FixJPEG(ImageHandle theImage, Handle *finalData, Boolean palette)
//=====================================================================================
// "Fixes" an image so that it can be saved as a JFIF.  Basically, we convert any PICTs
// to JFIF format, and save the quantized palette out to a global handle for later use
// by the saving routine.
//=====================================================================================

extern OSErr FixJPEG(ImageHandle theImage, Handle *finalData, Boolean palette)
{
	OSErr theErr = noErr;

	if ((*theImage)->format->inType != kPICTType && 
				(*theImage)->format->inType != kSCRNType &&
				(*theImage)->format->inType != kJPEGType) return errAETypeError;
	if ((*theImage)->format->inType == kPICTType ||
				(*theImage)->format->inType == kSCRNType) {
		KeepSpinning();
		if (!(*finalData = UnwrapJPEG((*theImage)->data))) return memFullErr;
	} else *finalData = (*theImage)->data;
	if (palette) {
		if (gJFIFColors = (CTabHandle)NewHandle(4)) 
			Palette2CTab((*theImage)->qpalette, gJFIFColors);
		else return memFullErr;
	} else gJFIFColors = nil;
	return theErr;
}

//=====================================================================================
// OSErr SaveJPEG(FSSpec *theSpec, Handle theHandle, GWorldPtr thePreview,
//			Rect *theRect, Handle privates)
//=====================================================================================
// Saves a JFIF image out to disk, including the preview image and palette resource,
// if specified.
//=====================================================================================

extern OSErr SaveJPEG(FSSpec *theSpec, Handle theHandle, GWorldPtr thePreview,
			Rect *theRect, Handle privates)
{
#if applec
#pragma unused(privates)
#endif
	long theSize = GetHandleSize(theHandle);
	uchar *headerStart, *headerEnd, *theAdr;
	char hState = HGetState(theHandle);
	short theFile;
	OSErr theErr;
	long theLen;

	KeepSpinning();
	theAdr = (uchar *)(*theHandle);
	if ((headerStart = GetJPEGData(theAdr, theSize, 0xe0)) && headerStart[0] == 'J' &&
		headerStart[1] == 'F' && headerStart[2] == 'I' && headerStart[3] == 'F' &&
		headerStart[4] == 0) {
		headerStart -= 4;
		headerEnd = headerStart + (headerStart[2] << 8) + headerStart[3] + 2;
		while (headerEnd[1] == 0xe0 && headerEnd[4] == 'J' && headerEnd[5] == 'F' &&
					headerEnd[6] == 'X' && headerEnd[7] == 'X' && headerEnd[8] == 0)
			headerEnd += (headerEnd[2] << 8) + headerEnd[3] + 2;
	} else if (headerStart = GetJPEGData(theAdr, theSize, 0xd8)) {
		headerStart -= 2;
		headerEnd = headerStart;
	} else return gIntError = errCorruptImage, codecBadDataErr;
	HLock(theHandle);
	KeepSpinning();
	theErr = FSpCreate(theSpec, kCreator, kJPEGType, 0);
	if (theErr == noErr) {
		KeepSpinning();
		theErr = FSpOpenDF(theSpec, fsWrPerm, &theFile);
		if (theErr == noErr) {
			KeepSpinning();
			theLen = (long)(headerStart - theAdr);
			theErr = FSWrite(theFile, &theLen, theAdr);
			if (theErr == noErr) {
				KeepSpinning();
				theSize -= theLen;
				theErr = WriteJFIFHeader(theFile, thePreview, theRect);
				if (theErr == noErr) {
					SpinIndef();
					theSize -= (long)(headerEnd - headerStart);
					theErr = FSWrite(theFile, &theSize, headerEnd);
					KeepSpinning();
					if (theErr == noErr) {
						FSClose(theFile);
						HSetState(theHandle, hState);
						if (gJFIFColors) {
							FSpCreateResFile(theSpec, kCreator, kJPEGType, 0);
							if (theFile = FSpOpenResFile(theSpec, fsRdWrPerm)) {
								AddResource((Handle)gJFIFColors, 'clut', 0, gNullString);
								if ((theErr = ResError()) == noErr) {
									WriteResource((Handle)gJFIFColors);
									ReleaseResource((Handle)gJFIFColors);
								} else theErr = ResError(), gIntError = errCantWriteFile;
								CloseResFile(theFile);
							} else theErr = ResError(), gIntError = errCantWriteFile;
							if (theErr != noErr) DisposeHandle((Handle)gJFIFColors);
						}
						return theErr;
					} else gIntError = errCantWriteFile;
				}
			} else gIntError = errCantWriteFile;
			FSClose(theFile);
		} else gIntError = errCantOpenFile;
		FSpDelete(theSpec);
	} else gIntError = errCantCreateFile;
	HSetState(theHandle, hState);
	if (theErr != noErr && gJFIFColors) DisposeHandle((Handle)gJFIFColors);
	return theErr;
}

//=====================================================================================
// void InitJPEGUPPs(void)
//=====================================================================================
// Initializes all the local UPPs for the JPEG callbacks.
//=====================================================================================

static void InitJPEGUPPs(void)
{
	gUnwrapPixProc = NewStdPixProc((ProcPtr)UnwrapPixProc);
	gDummyBitsProc = NewQDBitsProc((ProcPtr)DummyBitsProc);
	gDummyTextProc = NewQDTextProc((ProcPtr)DummyTextProc);
	gDummyLineProc = NewQDLineProc((ProcPtr)DummyLineProc);
	gDummyRectProc = NewQDRectProc((ProcPtr)DummyRectProc);
	gDummyRRectProc = NewQDRRectProc((ProcPtr)DummyRRectProc);
	gDummyOvalProc = NewQDOvalProc((ProcPtr)DummyOvalProc);
	gDummyArcProc = NewQDArcProc((ProcPtr)DummyArcProc);
	gDummyPolyProc = NewQDPolyProc((ProcPtr)DummyPolyProc);
	gDummyRgnProc = NewQDRgnProc((ProcPtr)DummyRgnProc);
}

//=====================================================================================
// uchar *GetJPEGData(uchar *theAdr, long theLen, uchar theCode)
//=====================================================================================
// Returns the address of the specified marker within the JPEG data stream.
//=====================================================================================

static uchar *GetJPEGData(uchar *theAdr, long theLen, uchar theCode) 
{
	uchar *theEnd = theAdr + theLen;
	long theSize;
	
	while (true) {
		while (*theAdr++ != 0xff && theAdr < theEnd);
		if (theAdr >= theEnd) return nil;
		while (*theAdr == 0xff) theAdr++;
		if (theAdr >= theEnd) return nil;
		if (*theAdr == theCode) break;
		else if (*theAdr == 0xd9) return nil;
		else if (*theAdr <= 0x01 || (*theAdr >= 0xd0 && *theAdr <= 0xd8)) {
			theAdr++;
			continue;
		}
		theAdr++;
		if ((theSize = (*theAdr << 8) + *(theAdr + 1)) < 0) return nil;
		if ((theAdr + theSize) >= theEnd) {
			*(theAdr - 1) = 0xd9;
			return nil;
		} else theAdr += theSize;
	}
	theAdr += 3;
	return theAdr;
}

//=====================================================================================
// void GetJPEGInfo(ImageHandle theImage)
//=====================================================================================
// Examines the quantization tables, counts the number of components, and estimates a
// quality factor for IJG-compatible JPEGs.
//=====================================================================================

static void GetJPEGInfo(ImageHandle theImage)
{
	Boolean isApple = false, isAdobe = false, isIJG = true;
	Handle theHandle = (*theImage)->data;
	long theLen = GetHandleSize(theHandle), theQuality = 0, len;
	short components = 0;
	uchar *theData;
	ushort theID;
	
	if (theData = GetJPEGData((uchar *)*theHandle, theLen, 0xfe)) {
		isApple = (theData[0] == 'A' && theData[1] == 'p' && theData[2] == 'p' &&
					theData[3] == 'l' && theData[4] == 'e' && theData[5] == 'M');
	} else if (theData = GetJPEGData((uchar *)*theHandle, theLen, 0xee)) {
		if (theData[0] == 'A' && theData[1] == 'd' && theData[2] == 'o' &&
					theData[3] == 'b' && theData[4] == 'e' && theData[5] == 0)
			isAdobe = true;
	}
	(*theImage)->desc.spatialQuality = 0;
	BlockMove(gString[strUnknownJPEG], (*theImage)->desc.name, *gString[strUnknownJPEG] + 1);
	if (!(theData = GetJPEGData((uchar *)*theHandle, theLen, 0xdb))) return;
	do {
		len = ((long)theData[-2] << 8) + theData[-1] - 2;
		while (len > 0) {
			theID = *theData++;
			len--;
			if (isApple) CalcAppleTable(theID, theData, &theQuality);
			else if (isAdobe || !CalcIJGTable(theID, theData, &theQuality)) isIJG = false;
			if (theID >> 4) {
				theData += 128;
				len -= 128;
			} else {
				theData += 64;
				len -= 64;
			}
			components++;
		}
		theLen = StripAddress(*theHandle) + GetHandleSize(theHandle) - StripAddress(theData);
	} while (theData = GetJPEGData(theData, theLen, 0xdb));
	if (isApple) {
		BlockMove(gString[strQuickTimeJPEG], (*theImage)->desc.name, *gString[strQuickTimeJPEG] + 1);
	} else if (isAdobe) {
		BlockMove(gString[strAdobeJPEG], (*theImage)->desc.name, *gString[strAdobeJPEG] + 1);
	} else if (isIJG) {
		(*theImage)->desc.spatialQuality = ((theQuality + 0x7fff) / components) >> 16;
		BlockMove(gString[strIJGJPEG], (*theImage)->desc.name, *gString[strIJGJPEG] + 1);
	}
}

//=====================================================================================
// Boolean CalcIJGTable(ushort theID, uchar *theData, long *theQuality)
//=====================================================================================
// Calculates the image quality for an IJG image.
//=====================================================================================

static Boolean CalcIJGTable(ushort theID, uchar *theData, long *theQuality)
{
	long scaleFactor = 0, stdDev = 0;
	ushort *theTable, entry;
	register long val;

	if ((theID & 0xf) == 0) theTable = gIJGLumiTab;
	else if ((theID & 0xf) == 1) theTable = gIJGChromTab;
	else return false;
	for (entry = 0; entry < 64; entry++) {
		if (theID >> 4) {
			val = (theData[0] << 8) + theData[1];
			theData += 2;
		} else val = *theData++;
		val *= 0x6400L / (long)*theTable++;
		scaleFactor += val;
		stdDev += (val >> 4) * (val >> 4);
	}
	scaleFactor >>= 6;
	stdDev >>= 6;
	stdDev -= (scaleFactor >> 4) * (scaleFactor >> 4);
	if (stdDev > 0x400) return false;
	if (scaleFactor <= 0x6400) *theQuality += ((0xc800 - scaleFactor) >> 1) << 8;
	else *theQuality += ((5000 << 16) / scaleFactor) << 8;
	return true;
}

//=====================================================================================
// Boolean CalcAppleTable(ushort theID, uchar *theData, long *theQuality)
//=====================================================================================
// Calculates the image quality for an Apple JPEG image (not really, but in theory).
//=====================================================================================

static Boolean CalcAppleTable(ushort theID, uchar *theData, long *theQuality)
{
#if applec
#pragma unused(theID, theData, theQuality)
#endif
	return true;
}

//=====================================================================================
// void ExtractComments(Ptr theJPEG, long origLen, Handle *theComments)
//=====================================================================================
// Finds any comments records in the JPEG data stream.
//=====================================================================================

extern void ExtractComments(Ptr theJPEG, long origLen, Handle *theComments)
{
	long len, i, weird, theLen = origLen;
	uchar *theData;
	Boolean bad;
	
	theData = (uchar *)theJPEG;
	while (true) {
		if (!(theData = GetJPEGData(theData, theLen, 0xfe))) break;
		len = ((long)theData[-2] << 8) + theData[-1] - 2;
		bad = false;
		if (theData[0] == 'A' && theData[1] == 'p' && theData[2] == 'p' &&
					theData[3] == 'l' && theData[4] == 'e' && theData[5] == 'M')
			bad = true;
		for (weird = i = 0; i < len && !bad; i++) {
			if (theData[i] >= 0x20 && theData[i] <= 0x7f) continue;
			else if (theData[i] == 0x0a) theData[i] = 0x0d;
			else if (theData[i] == 0x0d && theData[i + 1] == 0x0a) theData[++i] = 0x00;
			else weird++;
		}
		if (!bad) bad = (weird > (len >> 4));
		if (bad) break;
		if (!*theComments) PtrToHand(theData, theComments, len);
		else PtrAndHand(theData, *theComments, len);
		theData += len;
		theLen = StripAddress(theJPEG) + origLen - StripAddress(theData);
	}
}

//=====================================================================================
// OSErr CompressJFIFPreview(PixMapHandle thePixMap, Rect *theRect,
//			Handle *theHandle)
//=====================================================================================
// Compresses the JFIF preview using high quality JPEG.
//=====================================================================================

static OSErr CompressJFIFPreview(PixMapHandle thePixMap, Rect *theRect,
			Handle *theHandle)
{
	char pixState = GetPixelsState(thePixMap);
	ImageDescriptionHandle theDesc;
	long theSize, removeSize = 0;
	OSErr theErr = noErr;
	uchar *theData;
	
	if (theDesc = (ImageDescriptionHandle)CheckedNewHandle(sizeof(ImageDescription), true)) {
		theErr = GetMaxCompressionSize(thePixMap, theRect, 32, codecHighQuality, 
					kJPEGCompression, anyCodec, &theSize);
		if (theErr == noErr) *theHandle = CheckedNewHandle(theSize, true);
		if (*theHandle) {
			LockPixels(thePixMap);
			HLock(*theHandle);
			theErr = CompressImage(thePixMap, theRect, codecHighQuality, kJPEGCompression,
						theDesc, StripAddress(**theHandle));
			HUnlock(*theHandle);
			SetPixelsState(thePixMap, pixState);
			if (theErr == noErr) {
				theSize = (*theDesc)->dataSize;
				theData = GetJPEGData((uchar *)**theHandle, theSize, 0xe0);
				if (theData && theData[0] == 'J' && theData[1] == 'F' &&
					theData[2] == 'I' && theData[3] == 'F' && theData[4] == 0) {
					theData -= 4;
					removeSize = (theData[2] << 8) + theData[3] + 2;
					BlockMove(theData + removeSize, theData, theSize - removeSize);
				}
				SetHandleSize(*theHandle, theSize - removeSize);
			} else DisposeHandle(*theHandle), *theHandle = nil;
		}
		DisposeHandle((Handle)theDesc);
	} else theErr = memFullErr;
	return theErr;
}

//=====================================================================================
// OSErr WriteJFIFHeader(short theFile, GWorldPtr thePreview, Rect *theRect)
//=====================================================================================
// Creates a new header for the active JPEG image and writes it to theFile.
//=====================================================================================

static OSErr WriteJFIFHeader(short theFile, GWorldPtr thePreview, Rect *theRect) 
{
	Handle theHandle, compressedPrev = nil;
	short width, height, h, w, rowSize;
	long theLen = 18, extLen = 0;
	uchar *bp, *pp, *rowStart;
	char mmuMode = true32b;
	OSErr theErr;

	if (thePreview) {
		if (gThePrefs.compressPrev) {
			PixMapHandle dstPixMap = GetGWorldPixMap(thePreview);
			LockPixels(dstPixMap);
			theErr = CompressJFIFPreview(dstPixMap, theRect, &compressedPrev);
			UnlockPixels(dstPixMap);
		}
		if (!compressedPrev) {
			width = Width(theRect);
			height = Height(theRect);
			theLen += 3 * width * height;
		} else extLen += GetHandleSize(compressedPrev) + 10;
	}
	if (!(theHandle = CheckedNewHandle(theLen + extLen, true))) 
		return gIntError = errNoSaveMemory, memFullErr;
	HLock(theHandle);
	bp = (uchar *)StripAddress(*theHandle);
	*bp++ = 0xff;	*bp++ = 0xe0;				// JFIF Header
	*bp++ = (theLen - 2) >> 8;					// Length of block
	*bp++ = (theLen - 2) & 0xff;
	*bp++ = 'J';	*bp++ = 'F';				// JFIF signature	
	*bp++ = 'I';	*bp++ = 'F';	
					*bp++ = 0x00;
	*bp++ = 0x01;	*bp++ = (compressedPrev) ? 0x02 : 0x01; // Version number = 1.01/02
	*bp++ = 0x00;								// Units = none
	*bp++ = 0x00;	*bp++ = 0x01;				// Horizontal resolution
	*bp++ = 0x00;	*bp++ = 0x01;				// Vertical resolution
	if (thePreview && !compressedPrev) {
		PixMapHandle thePixMap = GetGWorldPixMap(thePreview);
		*bp++ = width;							// Width of preview image
		*bp++ = height;							// Height of preview image
		LockPixels(thePixMap);
		rowStart = (uchar *)GetPixBaseAddr(thePixMap);
		rowSize = (*thePixMap)->rowBytes & 0x3fff;
		SwapMMUMode(&mmuMode);
		for (h = 0; h < height; h++) {
			pp = rowStart;
			for (w = 0; w < width; w++) {
				pp++;
				*bp++ = *pp++;
				*bp++ = *pp++;
				*bp++ = *pp++;
			}
			rowStart += rowSize;
		}
		SwapMMUMode(&mmuMode);
		UnlockPixels(thePixMap);
	} else {
		*bp++ = 0x00;	*bp++ = 0x00;			// Width and height of preview image = 0
	}
	if (compressedPrev) {
		*bp++ = 0xff;	*bp++ = 0xe0;			// APP0 marker
		*bp++ = (extLen - 2) >> 8;				// Length of block
		*bp++ = (extLen - 2) & 0xff;
		*bp++ = 'J';	*bp++ = 'F';			// JFXX signature
		*bp++ = 'X';	*bp++ = 'X';
						*bp++ = 0x00;
		*bp++ = 0x10;							// JPEG-coded preview extension
		BlockMove(*compressedPrev, bp, GetHandleSize(compressedPrev));
		DisposeHandle(compressedPrev);
	}
	KeepSpinning();
	theLen += extLen;
	theErr = FSWrite(theFile, &theLen, *theHandle);
	KeepSpinning();
	if (theErr != noErr) gIntError = errCantWriteFile;
	DisposeHandle(theHandle);
	return theErr;
}

//=====================================================================================
// OSErr MakeJPEGImageDesc(Handle theHandle, ImageDescriptionHandle theDesc)
//=====================================================================================
// Creates a QuickTime image description record based on JPEG data.
//=====================================================================================

static OSErr MakeJPEGImageDesc(Handle theHandle, ImageDescriptionHandle theDesc) 
{
	long theLen = GetHandleSize(theHandle);
	uchar *theData;
	short theCode;
	
	for (theCode = 0xc0; theCode < 0xd0; theCode++)
		if (theData = GetJPEGData((uchar *)*theHandle, theLen, theCode)) break;
	if (theCode == 0xd0) return gIntError = errCorruptImage, codecBadDataErr;
	(*theDesc)->idSize = sizeof(ImageDescription);
	(*theDesc)->cType = kJPEGCompression;
	(*theDesc)->resvd1 = (*theDesc)->resvd2 = (*theDesc)->dataRefIndex = 0;
	BlockMove(gJPEGInfo.typeName, (*theDesc)->name, 32);
	(*theDesc)->version = gJPEGInfo.version;
	(*theDesc)->revisionLevel = gJPEGInfo.revisionLevel;
	(*theDesc)->vendor = gJPEGInfo.vendor;
	(*theDesc)->temporalQuality = 0;
	(*theDesc)->spatialQuality = 0x200L;
	(*theDesc)->width = (theData[3] << 8) + theData[4];
	(*theDesc)->height = (theData[1] << 8) + theData[2];
	(*theDesc)->hRes = (*theDesc)->vRes = 0x480000L;
	(*theDesc)->dataSize = theLen;
	(*theDesc)->frameCount = 1;
	(*theDesc)->depth = 32;
	(*theDesc)->clutID = -1;
	return noErr;
}

//=====================================================================================
// Handle UnwrapJPEG(Handle srcHandle)
//=====================================================================================
// Extracts (unwraps) the original JPEG data from a JPEG PICT image.
//=====================================================================================

extern Handle UnwrapJPEG(Handle srcHandle)
{
	long theSize = GetHandleSize(srcHandle);
	char hState = HGetState(srcHandle);
	CQDProcs theProcs, *oldProcs;

	KeepSpinning();
	if (MakeMemAvailable(theSize) && (gJPEGDestHandle = NewHandle(theSize))) {
		HLockHi(srcHandle);
		PushPort();
		MySetPort(gGenericGWorld);
		SetStdCProcs(&theProcs);
		if (!gUnwrapPixProc) InitJPEGUPPs();
		theProcs.newProc1 = (UniversalProcPtr)gUnwrapPixProc;
		theProcs.bitsProc = gDummyBitsProc;
		theProcs.textProc = gDummyTextProc;
		theProcs.lineProc = gDummyLineProc;
		theProcs.rectProc = gDummyRectProc;
		theProcs.rRectProc = gDummyRRectProc;
		theProcs.ovalProc = gDummyOvalProc;
		theProcs.arcProc = gDummyArcProc;
		theProcs.polyProc = gDummyPolyProc;
		theProcs.rgnProc = gDummyRgnProc;
		oldProcs = gGenericGWorld->grafProcs;
		gGenericGWorld->grafProcs = &theProcs;
		KeepSpinning();
		DrawTrimmedPicture((PicHandle)srcHandle, &(*(PicHandle)srcHandle)->picFrame, nil, 
					false, nil);
		gGenericGWorld->grafProcs = oldProcs;
		PopPort();
		HSetState(srcHandle, hState);
		return gJPEGDestHandle;
	} else gIntError = errNoUnwrapMemory;
	return nil;
}

//=====================================================================================
// Handle UnwrapJPEG(Handle srcHandle)
//=====================================================================================
// Wraps a JPEG image into a PICT.
//=====================================================================================

extern Handle WrapJPEG(Handle srcHandle, short depth) 
{
	PixMapHandle thePixMap = GetGWorldPixMap(gGenericGWorld);
	long theSize = GetHandleSize(srcHandle);
	char hState = HGetState(srcHandle);
	ImageDescriptionHandle theDesc;
	OpenCPicParams theParams;
	Handle theHandle;
	OSErr theErr;

	KeepSpinning();
	HLock(srcHandle);
	if (MakeMemAvailable(theSize) && 
		(theDesc = (ImageDescriptionHandle)CheckedNewHandle(sizeof(ImageDescription), true))) {
		theErr = MakeJPEGImageDesc(srcHandle, theDesc);
		if (depth == 8 || depth == 40) (*theDesc)->depth = 40;
		if (theErr == noErr) {
			MySetRect(&theParams.srcRect, 0, 0, (*theDesc)->width, (*theDesc)->height);
			theParams.hRes = (*theDesc)->hRes;
			theParams.vRes = (*theDesc)->vRes;
			theParams.version = -2;
			theParams.reserved1 = theParams.reserved2 = 0;
			LockPixels(thePixMap);
			PushPort();
			MySetPort(gGenericGWorld);
			ClipRect(&theParams.srcRect);
			if (theHandle = (Handle)OpenCPicture(&theParams)) {
				KeepSpinning();
				theErr = FDecompressImage(StripAddress(*srcHandle), theDesc, 
							thePixMap, &theParams.srcRect, nil, ditherCopy, nil, nil, nil, 
							codecHighQuality, anyCodec, theSize, nil, nil);
				KeepSpinning();
				UnlockPixels(thePixMap);
				ClosePicture();
				if (theErr == noErr && !EmptyRect(&(*(PicHandle)theHandle)->picFrame)) {
					PopPort();
					DisposeHandle((Handle)theDesc);
					HSetState(srcHandle, hState);
					return theHandle;
				}
				DisposeHandle(theHandle);
			} else gIntError = errNoWrapMemory;
			PopPort();
		}
		DisposeHandle((Handle)theDesc);
	} else gIntError = errNoWrapMemory;
	HSetState(srcHandle, hState);
	return nil;
}

//=====================================================================================
// pascal void UnwrapPixProc(PixMap *src, Rect *srcRect, MatrixRecord *matrix,
//			short mode, RgnHandle mask, PixMap *matte, Rect *matteRect,
//			short callOldBits)
//=====================================================================================
// Bottleneck function called to extract raw JPEG data from an image.
//=====================================================================================

static pascal void UnwrapPixProc(PixMap *src, Rect *srcRect, MatrixRecord *matrix,
			short mode, RgnHandle mask, PixMap *matte, Rect *matteRect,
			short callOldBits)
{
#if applec
#pragma unused(srcRect, matrix, mode, mask, matte, matteRect, callOldBits)
#endif
	long bufSize, left, offset = 0;
	ImageDescriptionHandle theDesc;
	ICMProgressProcRecord progProc;
	ICMDataProcRecord dataProc;
	Ptr theData, destAdr;
	
	KeepSpinning();
	if (!GetCompressedPixMapInfo(src, &theDesc, &theData, &bufSize, &dataProc, &progProc)) {
		if ((*theDesc)->cType == kJPEGCompression) {
			HLock(gJPEGDestHandle);
			destAdr = (Ptr)*gJPEGDestHandle;
			left = (*theDesc)->dataSize;
			if (dataProc.dataProc) {
				while (left > bufSize) {
					BlockMove(theData, &destAdr[offset], bufSize);
					offset += bufSize;
					left -= bufSize;
					theData += bufSize;
					if (CallICMDataProc(dataProc.dataProc, &theData, 
								(left > bufSize) ? bufSize : left, 
								dataProc.dataRefCon)) return;
				}
			}
			BlockMove(theData, &destAdr[offset], left);
			HUnlock(gJPEGDestHandle);
		}
	}
}

//=====================================================================================
// Dummy QuickDraw bottlenecks to filter out non-PixMaps within PICTs
//=====================================================================================

static pascal void DummyBitsProc(PixMap *src, Rect *srcRect, Rect *dstRect, short mode, 
			RgnHandle mask) 
{
#if applec
#pragma unused(src, srcRect, dstRect, mode, mask)
#endif
}

static pascal void DummyTextProc(short count, const void *textAddr, Point numer,
			Point denom) 
{
#if applec
#pragma unused(count, textAddr, numer, denom)
#endif
}

static pascal void DummyLineProc(Point newPt)
{
#if applec
#pragma unused(newPt)
#endif
}

static pascal void DummyRectProc(GrafVerb verb, const Rect *r)
{
#if applec
#pragma unused(verb, r)
#endif
}

static pascal void DummyRRectProc(GrafVerb verb, const Rect *r, short ovalWidth, 
	short ovalHeight)
{
#if applec
#pragma unused(verb, r, ovalWidth, ovalHeight)
#endif
}

static pascal void DummyOvalProc(GrafVerb verb, const Rect *r)
{
#if applec
#pragma unused(verb, r)
#endif
}

static pascal void DummyArcProc(GrafVerb verb, const Rect *r, short startAngle,
			short arcAngle)
{
#if applec
#pragma unused(verb, r, startAngle, arcAngle)
#endif
}

static pascal void DummyPolyProc(GrafVerb verb, PolyHandle poly)
{
#if applec
#pragma unused(verb, poly)
#endif
}

static pascal void DummyRgnProc(GrafVerb verb, RgnHandle rgn)
{
#if applec
#pragma unused(verb, rgn)
#endif
}
