JPEGView/JFIF Preview/JFIFPreview.c

1 line
16 KiB
C

/*********************************************************/
/* 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);
}