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

#define SystemSevenOrLater 1

#include <Aliases.h>
#include <Components.h>
#include <Errors.h>
#include <ImageCompression.h>
#include <QuickTimeComponents.h>
#include <Memory.h>
#include <MixedMode.h>
#include <QuickDraw.h>

/*
 * Macros and typedefs
 *
 */

#define JFIFPreviewVersion 0x00010001

#define kBufferSize 20
#define Height(r) ((r)->bottom - (r)->top)
#define Width(r) ((r)->right - (r)->left)

#define pnotDisplayPreview 1

typedef unsigned char uchar;
typedef unsigned short ushort;
typedef unsigned long ulong;

enum {
	uppPreviewOpenProcInfo = kPascalStackBased
		 | RESULT_SIZE(SIZE_CODE(sizeof(ComponentResult)))
		 | STACK_ROUTINE_PARAMETER(1, SIZE_CODE(sizeof(ComponentInstance))),
	uppPreviewCloseProcInfo = kPascalStackBased
		 | RESULT_SIZE(SIZE_CODE(sizeof(ComponentResult)))
		 | STACK_ROUTINE_PARAMETER(1, SIZE_CODE(sizeof(Handle)))
		 | STACK_ROUTINE_PARAMETER(2, SIZE_CODE(sizeof(ComponentInstance))),
	uppPreviewCanDoProcInfo = kPascalStackBased
		 | RESULT_SIZE(SIZE_CODE(sizeof(ComponentResult)))
		 | STACK_ROUTINE_PARAMETER(1, SIZE_CODE(sizeof(short))),
	uppPreviewDisplayProcInfo = kPascalStackBased
		 | RESULT_SIZE(SIZE_CODE(sizeof(ComponentResult)))
		 | STACK_ROUTINE_PARAMETER(1, SIZE_CODE(sizeof(Handle)))
		 | STACK_ROUTINE_PARAMETER(2, SIZE_CODE(sizeof(OSType)))
		 | STACK_ROUTINE_PARAMETER(3, SIZE_CODE(sizeof(Handle)))
		 | STACK_ROUTINE_PARAMETER(4, SIZE_CODE(sizeof(const Rect *)))
};

/*
 * Function prototypes
 *
 */

extern pascal ComponentResult EntryPoint(ComponentParameters *params, Handle storage);
pascal ComponentResult JFIFPreviewOpen(ComponentInstance self);
pascal ComponentResult JFIFPreviewClose(Handle theBuffer, ComponentInstance self);
pascal ComponentResult JFIFPreviewCanDo(short selector);
pascal ComponentResult JFIFPreviewShow(Handle theBuffer, OSType dataType, Handle data, 
	const Rect *inHere);
OSErr LoadPreview(Handle *theData, short theFile, uchar *buffer, short width, short height);
OSErr LoadPreviewExtension(Handle *theData, short theFile, long theSize);
OSErr MakePreviewGWorld(GWorldPtr *theGWorld, const Rect *inHere, short width, short height);
OSErr DrawPreview(Handle previewData, const Rect *inHere, short width, short height);
uchar *ParseJPEG(uchar *theAdr, long theLen, uchar theCode);
OSErr MakeImageDescription(ImageDescriptionHandle *theDesc, Handle previewData);
OSErr DrawJPEGPreview(Handle previewData, const Rect *inHere);
OSErr Draw1CompPreview(Handle previewData, const Rect *inHere);
OSErr Draw3CompPreview(Handle previewData, const Rect *inHere);

#if defined(powerc) || defined(__powerc)
enum {
	uppMainProcInfo = kPascalStackBased
		 | RESULT_SIZE(SIZE_CODE(sizeof(ComponentResult)))
		 | STACK_ROUTINE_PARAMETER(1, SIZE_CODE(sizeof(ComponentParameters *)))
		 | STACK_ROUTINE_PARAMETER(2, SIZE_CODE(sizeof(Handle)))
};
RoutineDescriptor EntryPointRD = BUILD_ROUTINE_DESCRIPTOR(uppMainProcInfo, EntryPoint); 
#endif

/*
 * This is the entry point where we dispatch the request to the appropriate function
 *
 */

extern pascal ComponentResult EntryPoint(ComponentParameters *params, Handle storage)
{
	ComponentFunctionUPP theRoutine = nil;
	ComponentResult theResult = noErr;

	switch (params->what) {
		case kComponentOpenSelect:
			if (theRoutine = NewRoutineDescriptor((ProcPtr)JFIFPreviewOpen,
						uppPreviewOpenProcInfo, GetCurrentISA()))
				theResult = CallComponentFunction(params, theRoutine);
			break;
		case kComponentCloseSelect:
			if (theRoutine = NewRoutineDescriptor((ProcPtr)JFIFPreviewClose,
						uppPreviewCloseProcInfo, GetCurrentISA()))
				theResult = CallComponentFunctionWithStorage(storage, params, theRoutine);
			break;
		case kComponentCanDoSelect:
			if (theRoutine = NewRoutineDescriptor((ProcPtr)JFIFPreviewCanDo,
						uppPreviewCanDoProcInfo, GetCurrentISA()))
				theResult = CallComponentFunction(params, theRoutine);
			break;
		case kComponentVersionSelect:
			return JFIFPreviewVersion;
		case pnotDisplayPreview:
			if (theRoutine = NewRoutineDescriptor((ProcPtr)JFIFPreviewShow,
						uppPreviewDisplayProcInfo, GetCurrentISA()))
				theResult = CallComponentFunctionWithStorage(storage, params, theRoutine);
			break;
		default:
			theResult = badComponentSelector;
			break;
	}
	if (theRoutine) DisposeRoutineDescriptor(theRoutine);
	return theResult;
}

/*
 * Open Component function: Allocate our global storage
 *
 */

pascal ComponentResult JFIFPreviewOpen(ComponentInstance self)
{
	Handle theBuffer;
	
	if (!(theBuffer = NewHandle(kBufferSize))) return MemError();
	SetComponentInstanceStorage(self, theBuffer);
	return noErr;
}

/*
 * Close Component function: Dispose of our global storage
 *
 */

pascal ComponentResult JFIFPreviewClose(Handle theBuffer, ComponentInstance self)
{
#if applec
#pragma unused(self)
#endif
	if (theBuffer) DisposeHandle(theBuffer);
	return noErr;
}

/*
 * Can Do function: return 1 for any selector we can handle
 *
 */

pascal ComponentResult JFIFPreviewCanDo(short selector)
{
	switch (selector) {
		case kComponentOpenSelect:
		case kComponentCloseSelect:
		case kComponentCanDoSelect:
		case kComponentVersionSelect:
		case pnotDisplayPreview:
			return 1;
		default:
			return 0;
	}
}

/*
 * Show preview function: accept an alias and extract a preview from it
 *
 * INTERESTING FACT:
 *
 * If we return an error, QuickTime will go ahead and use any custom icon as the preview,
 * so we return an error if there is no JFIF preview, in order to preserve this
 * functionality
 *
 */

pascal ComponentResult JFIFPreviewShow(Handle theBuffer, OSType dataType, Handle data, 
	const Rect *inHere)
{
	long theLength = kBufferSize;
	short fileRef, width, height;
	Handle previewData;
	Boolean isFolder;
	FSSpec theFile;
	OSErr theErr;
	uchar *buf;
	
	if (dataType != rAliasType) return paramErr;
	HLock(theBuffer);
	buf = (uchar *)*theBuffer;
	theErr = ResolveAlias(nil, (AliasHandle)data, &theFile, &isFolder);
	if (theErr == noErr) {
		theErr = FSpOpenDF(&theFile, fsRdPerm, &fileRef);
		if (theErr == noErr) {
			theErr = FSRead(fileRef, &theLength, buf);
			if (theErr == noErr && 
				buf[0] == 0xff && buf[1] == 0xd8 && buf[2] == 0xff &&
				buf[3] == 0xe0 && buf[6] == 'J'  && buf[7] == 'F' &&
				buf[8] == 'I'  && buf[9] == 'F'  && buf[10] == 0) {
				width = buf[18];
				height = buf[19];
				if (width && height) {
					theErr = LoadPreview(&previewData, fileRef, buf, width, height);
					if (theErr == noErr) {
						theErr = DrawPreview(previewData, inHere, width, height);
						DisposeHandle(previewData);
					}
				} else {
					SetFPos(fileRef, fsFromStart, 4 + (buf[4] << 8) + buf[5]);
					theLength = 10;
					theErr = FSRead(fileRef, &theLength, buf);
					if (theErr == noErr &&
						buf[0] == 0xff && buf[1] == 0xe0 && buf[4] == 'J' &&
						buf[5] == 'F' && buf[6] == 'X' && buf[7] == 'X' && buf[8] == 0) {
						theErr = LoadPreviewExtension(&previewData, fileRef, 
									(buf[2] << 8) + buf[3] - 8);
						if (theErr == noErr) {
							switch (buf[9]) {
								case 0x10: 
									theErr = DrawJPEGPreview(previewData, inHere);
									break;
								case 0x11:
									theErr = Draw1CompPreview(previewData, inHere);
									break;
								case 0x13:
									theErr = Draw3CompPreview(previewData, inHere);
									break;
							}
							DisposeHandle(previewData);
						}
					} else theErr = memFullErr; // fake an error if no preview around
				}
			}
			FSClose(fileRef);
		}
	}
	HUnlock(theBuffer);
	return theErr;
}

/*
 * LoadPreview: Allocate a new handle and read the preview data into it
 *
 */

OSErr LoadPreview(Handle *theData, short theFile, uchar *buffer, short width, short height)
{
#if applec
#pragma unused(buffer)
#endif
	long theSize = (long)width * (long)height * 3L;
	OSErr theErr = noErr;

	if (!(*theData = NewHandle(theSize))) {
		*theData = TempNewHandle(theSize, &theErr);
		if (theErr != noErr) return theErr;
	}
	HLock(*theData);
	theErr = FSRead(theFile, &theSize, **theData);
	HUnlock(*theData);
	return theErr;
}

/*
 * LoadPreviewExtension: Allocate a new handle and read the preview extension data into it
 *
 */

OSErr LoadPreviewExtension(Handle *theData, short theFile, long theSize)
{
	OSErr theErr = noErr;
	
	if (!(*theData = NewHandle(theSize))) {
		*theData = TempNewHandle(theSize, &theErr);
		if (theErr != noErr) return theErr;
	}
	HLock(*theData);
	theErr = FSRead(theFile, &theSize, **theData);
	HUnlock(*theData);
	return theErr;
}

/*
 * MakePreviewGWorld: Create a GWorld the proper size for the given preview width and
 *		height
 *
 */

OSErr MakePreviewGWorld(GWorldPtr *theGWorld, const Rect *inHere, short width, short height)
{
	Rect GWBounds;
	OSErr theErr;
	
	GWBounds.left = GWBounds.top = 0;
	GWBounds.bottom = (height > Height(inHere)) ? Height(inHere) : height;
	GWBounds.right = (width > Width(inHere)) ? Width(inHere) : width;
	theErr = NewGWorld(theGWorld, 32, &GWBounds, nil, nil, 0);
	if (theErr != noErr)
		theErr = NewGWorld(theGWorld, 32, &GWBounds, nil, nil, useTempMem);
	return theErr;
}

/*
 * DrawPreview: Make a GWorld and copy the preview data into it; then do a CopyBits into
 * the requested area
 *
 */

OSErr DrawPreview(Handle previewData, const Rect *inHere, short width, short height)
{
	PixMapHandle thePixMap, dstPixMap;
	uchar *bufPtr, *pixPtr, *pixBase; 
	Rect GWBounds, dstRect = *inHere;
	short rowBytes, r, c, oldWidth;
	GWorldPtr theGWorld, curGWorld;
	char mmuMode = true32b;
	OSErr theErr = noErr;
	GDHandle curDevice;

	if (!(oldWidth = width)) {
		width = *(uchar *)*previewData;
		height = *((uchar *)*previewData + 1);
	}
	theErr = MakePreviewGWorld(&theGWorld, inHere, width, height);
	if (theErr == noErr) {
		GWBounds = theGWorld->portRect;
		LockPixels(thePixMap = GetGWorldPixMap(theGWorld));
		pixBase = (uchar *)GetPixBaseAddr(thePixMap);
		rowBytes = (*thePixMap)->rowBytes & 0x3fff;
		bufPtr = (uchar *)StripAddress(*previewData);
		if (!oldWidth) bufPtr++;
		SwapMMUMode(&mmuMode);
		for (r = 0; r < GWBounds.bottom; r++) {
			pixPtr = pixBase;
			for (c = 0; c < GWBounds.right; c++) {
				pixPtr++;
				*pixPtr++ = *bufPtr++;
				*pixPtr++ = *bufPtr++;
				*pixPtr++ = *bufPtr++;
			}
			bufPtr += 3L * (long)(width - GWBounds.right);
			pixBase += rowBytes;
		}
		SwapMMUMode(&mmuMode);
		dstRect.left += (Width(inHere) - GWBounds.right) >> 1;
		dstRect.top += (Height(inHere) - GWBounds.bottom) >> 1;
		dstRect.right = dstRect.left + GWBounds.right;
		dstRect.bottom = dstRect.top + GWBounds.bottom;
		GetGWorld(&curGWorld, &curDevice);
		dstPixMap = GetGWorldPixMap(curGWorld);
		CopyBits((BitMap *)*thePixMap, (BitMap *)*dstPixMap, &GWBounds, &dstRect,
				 srcCopy + ditherCopy, nil);
		DisposeGWorld(theGWorld);
	}
	return theErr;
}

/*
 * ParseJPEG: returns a pointer to the first instance of the specified code in the
 *		given JPEG stream
 *
 */
 
uchar *ParseJPEG(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;
}

/*
 * MakeImageDescription: create an image description record for the given JPEG image
 *
 */
 
OSErr MakeImageDescription(ImageDescriptionHandle *theDesc, Handle previewData)
{
	long theLen = GetHandleSize(previewData);
	CodecInfo theInfo;
	uchar *theData;
	short theCode;
	OSErr theErr;
	
	theErr = GetCodecInfo(&theInfo, 'jpeg', 0);
	if (theErr == noErr) {
		*theDesc = (ImageDescriptionHandle)NewHandle(sizeof(ImageDescription));
		if (!*theDesc) {
			*theDesc = (ImageDescriptionHandle)TempNewHandle(sizeof(ImageDescription), &theErr);
			if (theErr != noErr) return theErr;
		}
		for (theCode = 0xc0; theCode < 0xd0; theCode++)
			if (theData = ParseJPEG((uchar *)*previewData, theLen, theCode)) break;
		if (theCode != 0xd0) {
			(**theDesc)->idSize = sizeof(ImageDescription);
			(**theDesc)->cType = 'jpeg';
			(**theDesc)->resvd1 = (**theDesc)->resvd2 = (**theDesc)->dataRefIndex = 0;
			BlockMove(theInfo.typeName, (**theDesc)->name, 32);
			(**theDesc)->version = theInfo.version;
			(**theDesc)->revisionLevel = theInfo.revisionLevel;
			(**theDesc)->vendor = theInfo.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;
		} else theErr = memFullErr;
	}
	return theErr;
}

/*
 * DrawJPEGPreview: draws a preview image in JPEG format
 *
 */
 
OSErr DrawJPEGPreview(Handle previewData, const Rect *inHere)
{
	Rect srcRect, dstRect = *inHere;
	ImageDescriptionHandle theDesc;
	PixMapHandle thePixMap;
	MatrixRecord theMatrix;	
	OSErr theErr = noErr;
	GWorldPtr curGWorld;
	short width, height;
	GDHandle curDevice;
	
	theErr = MakeImageDescription(&theDesc, previewData);
	if (theErr == noErr) {
		width = (*theDesc)->width;
		height = (*theDesc)->height;
		srcRect.left = srcRect.top = 0;
		srcRect.bottom = (height > Height(inHere)) ? Height(inHere) : height;
		srcRect.right = (width > Width(inHere)) ? Width(inHere) : width;
		dstRect.left += (Width(inHere) - srcRect.right) >> 1;
		dstRect.top += (Height(inHere) - srcRect.bottom) >> 1;
		dstRect.right = dstRect.left + srcRect.right;
		dstRect.bottom = dstRect.top + srcRect.bottom;
		GetGWorld(&curGWorld, &curDevice);
		thePixMap = GetGWorldPixMap(curGWorld);
		RectMatrix(&theMatrix, &srcRect, &dstRect);
		HLock(previewData);
		theErr = FDecompressImage(StripAddress(*previewData), theDesc, thePixMap, &srcRect,
			&theMatrix, srcCopy + ditherCopy, nil, nil, nil, codecHighQuality, anyCodec,
			GetHandleSize(previewData), nil, nil);
		HUnlock(previewData);
		DisposeHandle((Handle)theDesc);
	}
	return theErr;
}

/*
 * Draw1CompPreview: draws a preview image 256-color palette format
 *
 */
 
OSErr Draw1CompPreview(Handle previewData, const Rect *inHere)
{
	short rowBytes, r, c, width, height;
	ulong *pixPtr, *pixBase, *theColor;
	PixMapHandle thePixMap, dstPixMap;
	Rect GWBounds, dstRect = *inHere;
	GWorldPtr theGWorld, curGWorld;
	uchar *bufPtr, *colPtr;
	char mmuMode = true32b;
	OSErr theErr = noErr;
	Handle colorsHandle;
	GDHandle curDevice;

	colorsHandle = NewHandle(256 * sizeof(long));
	if (!colorsHandle) {
		colorsHandle = TempNewHandle(256 * sizeof(long), &theErr);
		if (theErr != noErr) return theErr;
	}
	width = *(uchar *)*previewData;
	height = *((uchar *)*previewData + 1);
	theErr = MakePreviewGWorld(&theGWorld, inHere, width, height);
	if (theErr == noErr) {
		GWBounds = theGWorld->portRect;
		LockPixels(thePixMap = GetGWorldPixMap(theGWorld));
		pixBase = (ulong *)GetPixBaseAddr(thePixMap);
		rowBytes = (*thePixMap)->rowBytes & 0x3fff;
		bufPtr = (uchar *)StripAddress(*previewData) + 2;
		theColor = (ulong *)(colPtr = (uchar *)StripAddress(*colorsHandle));
		for (c = 0; c < 256; c++) {
			*colPtr++ = 0;
			*colPtr++ = *bufPtr++;
			*colPtr++ = *bufPtr++;
			*colPtr++ = *bufPtr++;
		}
		SwapMMUMode(&mmuMode);
		for (r = 0; r < GWBounds.bottom; r++) {
			pixPtr = pixBase;
			for (c = 0; c < GWBounds.right; c++) *pixPtr++ = theColor[*bufPtr++];
			bufPtr += 3L * (long)(width - GWBounds.right);
			pixBase += rowBytes >> 2;
		}
		SwapMMUMode(&mmuMode);
		dstRect.left += (Width(inHere) - GWBounds.right) >> 1;
		dstRect.top += (Height(inHere) - GWBounds.bottom) >> 1;
		dstRect.right = dstRect.left + GWBounds.right;
		dstRect.bottom = dstRect.top + GWBounds.bottom;
		GetGWorld(&curGWorld, &curDevice);
		dstPixMap = GetGWorldPixMap(curGWorld);
		CopyBits((BitMap *)*thePixMap, (BitMap *)*dstPixMap, &GWBounds, &dstRect,
				 srcCopy + ditherCopy, nil);
		DisposeGWorld(theGWorld);
	}
	return theErr;
}

/*
 * Draw3CompPreview: draws a preview image 24-bit color format
 *
 */
 
OSErr Draw3CompPreview(Handle previewData, const Rect *inHere)
{
	return DrawPreview(previewData, inHere, 0, 0);
}
