mirror of
https://github.com/aaronsgiles/JPEGView.git
synced 2024-06-13 21:29:28 +00:00
92bdb55672
These are the sources for the final official release of JPEGView for the Mac, back in 1994.
1 line
34 KiB
C
1 line
34 KiB
C
/*********************************************************/
|
|
/* 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
|
|
}
|