mirror of
https://github.com/aaronsgiles/JPEGView.git
synced 2024-06-06 21:29:27 +00:00
92bdb55672
These are the sources for the final official release of JPEGView for the Mac, back in 1994.
1 line
18 KiB
C
1 line
18 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"
|
|
#include <stddef.h>
|
|
#elif applec
|
|
#pragma load ":Headers:MPW.Header"
|
|
#elif __MWERKS__
|
|
//#include "MW.Header"
|
|
#else
|
|
#include "JPEGView.h"
|
|
#endif
|
|
|
|
//=====================================================================================
|
|
// Includes specific to this module
|
|
//=====================================================================================
|
|
|
|
#include "Quantize.h"
|
|
|
|
//=====================================================================================
|
|
// Constants local to this module
|
|
//=====================================================================================
|
|
|
|
enum {
|
|
kRedScale = 4,
|
|
kGreenScale = 5,
|
|
kBlueScale = 3
|
|
};
|
|
|
|
//=====================================================================================
|
|
// Box: structure for holding information about color reduction boxes
|
|
//=====================================================================================
|
|
|
|
typedef struct Box {
|
|
ushort c0min, c0max, c1min, c1max, c2min, c2max;
|
|
ulong colorCount;
|
|
} Box, *BoxPtr, **BoxHandle;
|
|
|
|
//=====================================================================================
|
|
// FillData: structure for passing information between C and assembly portions of the
|
|
// histogram filling routine
|
|
//=====================================================================================
|
|
|
|
typedef struct FillData {
|
|
ulong height, width;
|
|
ulong rowBytes;
|
|
ushort *histogram;
|
|
uchar *baseAdr;
|
|
} FillData, *FillDataPtr;
|
|
|
|
//=====================================================================================
|
|
// Global variables local to this module
|
|
//=====================================================================================
|
|
|
|
static ushort gNumBoxes, *gHistogram;
|
|
static Box gBoxList[256];
|
|
|
|
//=====================================================================================
|
|
// Prototypes for functions local to this module
|
|
//=====================================================================================
|
|
|
|
static BoxPtr FindBiggestPixelPop(void);
|
|
static BoxPtr FindBiggestVolume(void);
|
|
static void UpdateBox(BoxPtr boxp);
|
|
static void MedianCut(short desiredColors);
|
|
static void ComputeColor(BoxPtr boxp, int icolor, PaletteHandle thePalette);
|
|
|
|
#if THINK_C || !USE_ASM
|
|
static void DoFillHistogram32(FillDataPtr theData);
|
|
static void DoFillHistogram16(FillDataPtr theData);
|
|
#else
|
|
extern void DoFillHistogram32(FillDataPtr theData);
|
|
extern void DoFillHistogram16(FillDataPtr theData);
|
|
#endif
|
|
|
|
//=====================================================================================
|
|
// OSErr DoQuantize(ImageHandle theImage, long numColors, PaletteHandle *thePalette)
|
|
//
|
|
// Calculates two-pass color quantization for a given image, reducing it to the
|
|
// specified number of colors and returning the result as a palette in the specified
|
|
// handle.
|
|
//
|
|
// External calls: KeepSpinning, CheckedNewHandle, ClearMem, DrawImage, KillGWorld
|
|
//=====================================================================================
|
|
|
|
extern OSErr DoQuantize(ImageHandle theImage, long numColors, PaletteHandle *thePalette,
|
|
NestedProgressPtr progProc)
|
|
{
|
|
Boolean hadGWorld = ((*theImage)->gworld != nil);
|
|
OSErr theErr = noErr;
|
|
Handle theHandle;
|
|
Str32 theNum;
|
|
|
|
*thePalette = nil;
|
|
KeepSpinning();
|
|
if (theHandle = CheckedNewHandle(32768L * sizeof(short), true)) {
|
|
NumToString(numColors, theNum);
|
|
SetSlideControlsText(gString[strSlideReducing], (*theImage)->file.name, gNullString, gNullString);
|
|
UpdateParamText(gString[strReducingTo], theNum, gNullString, (*theImage)->file.name);
|
|
HLockHi(theHandle);
|
|
ClearMem(*theHandle, 32768L * sizeof(short));
|
|
gHistogram = (ushort *)StripAddress(*theHandle);
|
|
if (!GWOrigSize(theImage)) KillGWorld(theImage);
|
|
KeepSpinning();
|
|
theErr = DrawImage(theImage, bfQuantize + (gSlideShow ? bfUseSlideProgress : 0),
|
|
progProc);
|
|
HUnlock(theHandle);
|
|
if (theErr == noErr)
|
|
theErr = QuantizeFromHistogram(theHandle, numColors, thePalette);
|
|
DisposeHandle(theHandle);
|
|
} else gIntError = errNoQuantMemory, theErr = memFullErr;
|
|
if (theErr != noErr) {
|
|
if (!hadGWorld && (*theImage)->gworld) KillGWorld(theImage);
|
|
}
|
|
return theErr;
|
|
}
|
|
|
|
//=====================================================================================
|
|
// QuantizeFromHistogram(Handle theHistogram, short numColors,
|
|
// PaletteHandle *thePalette)
|
|
//
|
|
// Performs two-pass color reduction, given a histogram of color counts.
|
|
//
|
|
// External calls: KeepSpinning
|
|
//=====================================================================================
|
|
|
|
extern OSErr QuantizeFromHistogram(Handle theHistogram, long numColors,
|
|
PaletteHandle *thePalette)
|
|
{
|
|
char hState = HGetState(theHistogram);
|
|
OSErr theErr = noErr;
|
|
RGBColor theColor;
|
|
ushort i;
|
|
|
|
HLock(theHistogram);
|
|
gHistogram = (ushort *)StripAddress(*theHistogram);
|
|
*thePalette = nil;
|
|
if (*thePalette = NewPalette(numColors, nil, pmTolerant, 0)) {
|
|
KeepSpinning();
|
|
gNumBoxes = 1;
|
|
gBoxList[0].c0min = gBoxList[0].c1min = gBoxList[0].c2min = 0;
|
|
gBoxList[0].c0max = gBoxList[0].c1max = gBoxList[0].c2max = 31;
|
|
UpdateBox(&gBoxList[0]);
|
|
KeepSpinning();
|
|
MedianCut(numColors - 2);
|
|
KeepSpinning();
|
|
for (i = 0; i < gNumBoxes; i++) ComputeColor(&gBoxList[i], i, *thePalette);
|
|
KeepSpinning();
|
|
for ( ; i < numColors; i++) {
|
|
GetEntryColor(gGreyPalette[8], i, &theColor);
|
|
SetEntryColor(*thePalette, i, &theColor);
|
|
}
|
|
KeepSpinning();
|
|
} else gIntError = errNoQuantMemory, theErr = memFullErr;
|
|
HSetState(theHistogram, hState);
|
|
return theErr;
|
|
}
|
|
|
|
//=====================================================================================
|
|
// void ShrinkGWorld(ImageHandle theImage)
|
|
//
|
|
// Attempts to create a properly-sized GWorld from an overly-deep GWorld.
|
|
//
|
|
// External calls: PushPort, SlideProgress, MySetPort, MakeImageGWorld, KillGWorld,
|
|
// PopPort, NewBits, KeepSpinning, OldBits
|
|
//=====================================================================================
|
|
|
|
extern OSErr ShrinkGWorld(ImageHandle theImage)
|
|
{
|
|
GWorldPtr oldGWorld = (*theImage)->gworld, newGWorld;
|
|
JVDrawParamsHandle theParams;
|
|
Rect srcRect, dstRect;
|
|
OSErr theErr = noErr;
|
|
|
|
// first we try to allocate a proper-sized GWorld
|
|
KeepSpinning();
|
|
(*theImage)->gworld = nil;
|
|
(*theImage)->flags &= ~ifGWOrigSize;
|
|
if (!MakeImageGWorld(theImage, false)) {
|
|
|
|
// if it didn't work, restore things and kill the oversized GWorld
|
|
(*theImage)->gworld = oldGWorld;
|
|
KillGWorld(theImage);
|
|
return noErr;
|
|
}
|
|
|
|
// calculate source and destination rectangles
|
|
srcRect = (GWOrigSize(theImage)) ? (*theImage)->crect : oldGWorld->portRect;
|
|
dstRect = (*theImage)->gworld->portRect;
|
|
|
|
// allocate the drawing parameters
|
|
if (theParams = NewDrawParams(&(*theImage)->gworld->portRect, (*theImage)->quality,
|
|
false, nil, (*theImage)->privateData)) {
|
|
|
|
// set up the destination port and preflight our drawing
|
|
SetUpDrawPort(theParams, kOffscreenPort1, (*theImage)->gworld, &srcRect, &dstRect, true);
|
|
theErr = PreflightDrawing(theParams);
|
|
|
|
// if everything worked, copy from GWorld to GWorld, and postflight when done
|
|
if (theErr == noErr) {
|
|
SpinIndef();
|
|
CopyGWorldToGWorld(oldGWorld, (*theParams)->dummy, &srcRect, &srcRect, srcCopy, nil);
|
|
theErr = PostflightDrawing(theParams);
|
|
}
|
|
|
|
// now kill the old GWorld through the proper channels
|
|
KeepSpinning();
|
|
newGWorld = (*theImage)->gworld;
|
|
(*theImage)->gworld = oldGWorld;
|
|
KillGWorld(theImage);
|
|
(*theImage)->gworld = newGWorld;
|
|
|
|
// dispose of everything and clean up
|
|
DisposeHandle((Handle)theParams);
|
|
} else theErr = MemError();
|
|
return theErr;
|
|
}
|
|
|
|
//=====================================================================================
|
|
// void FillHistogram(PixMapPtr thePixMap)
|
|
//
|
|
// Fills the local histogram according to the data in the specified PixMap. Works
|
|
// for 16-bit and 32-bit deep PixMaps. Both C and 68020 assembly versions are
|
|
// provided.
|
|
//
|
|
// External calls: none
|
|
//=====================================================================================
|
|
|
|
extern void FillHistogram(PixMapPtr thePixMap)
|
|
{
|
|
register short theDepth = thePixMap->pixelSize;
|
|
char mmuMode = true32b;
|
|
FillData theData;
|
|
|
|
theData.height = Height(&thePixMap->bounds);
|
|
theData.width = Width(&thePixMap->bounds);
|
|
theData.rowBytes = thePixMap->rowBytes & 0x3fff;
|
|
theData.histogram = gHistogram;
|
|
if (thePixMap->pmVersion == 4) theData.baseAdr = (uchar *)thePixMap->baseAddr;
|
|
else theData.baseAdr = (uchar *)StripAddress(thePixMap->baseAddr);
|
|
SwapMMUMode(&mmuMode);
|
|
switch (theDepth) {
|
|
case 32:
|
|
DoFillHistogram32(&theData);
|
|
break;
|
|
case 16:
|
|
DoFillHistogram16(&theData);
|
|
break;
|
|
}
|
|
SwapMMUMode(&mmuMode);
|
|
}
|
|
|
|
//=====================================================================================
|
|
// BoxPtr FindBiggestPixelPop(void)
|
|
//
|
|
// Searches the box list for the box with the largest number of pixels.
|
|
//
|
|
// External calls: none
|
|
//=====================================================================================
|
|
|
|
static BoxPtr FindBiggestPixelPop(void)
|
|
{
|
|
register ulong max = 0;
|
|
register BoxPtr boxp;
|
|
BoxPtr which = nil;
|
|
register ushort i;
|
|
|
|
for (i = 0, boxp = gBoxList; i < gNumBoxes; i++, boxp++)
|
|
if (boxp->colorCount > max)
|
|
if ((boxp->c0max > boxp->c0min) ||
|
|
(boxp->c1max > boxp->c1min) ||
|
|
(boxp->c2max > boxp->c2min)) {
|
|
which = boxp;
|
|
max = boxp->colorCount;
|
|
}
|
|
return which;
|
|
}
|
|
|
|
//=====================================================================================
|
|
// BoxPtr FindBiggestVolume(void)
|
|
//
|
|
// Searches the box list for the box with the largest volume (actually largest norm).
|
|
//
|
|
// External calls: none
|
|
//=====================================================================================
|
|
|
|
static BoxPtr FindBiggestVolume(void)
|
|
{
|
|
register ulong norm, c0, c1, c2;
|
|
register ulong max = 0;
|
|
register BoxPtr boxp;
|
|
BoxPtr which = nil;
|
|
register ushort i;
|
|
|
|
for (i = 0, boxp = gBoxList; i < gNumBoxes; i++, boxp++) {
|
|
c0 = (boxp->c0max - boxp->c0min) * kRedScale;
|
|
c1 = (boxp->c1max - boxp->c1min) * kGreenScale;
|
|
c2 = (boxp->c2max - boxp->c2min) * kBlueScale;
|
|
norm = c0*c0 + c1*c1 + c2*c2;
|
|
if (norm > max) {
|
|
which = boxp;
|
|
max = norm;
|
|
}
|
|
}
|
|
return which;
|
|
}
|
|
|
|
//=====================================================================================
|
|
// void UpdateBox(BoxPtr boxp)
|
|
//
|
|
// Shrinks a given box to its minimum size and recalculates the number of pixels
|
|
// contained therein.
|
|
//
|
|
// External calls: none
|
|
//=====================================================================================
|
|
|
|
static void UpdateBox(BoxPtr boxp)
|
|
{
|
|
short c0min, c0max, c1min, c1max, c2min, c2max;
|
|
register short c0, c1, c2;
|
|
register ushort *hist;
|
|
ulong ccount;
|
|
|
|
c0min = boxp->c0min; c0max = boxp->c0max;
|
|
c1min = boxp->c1min; c1max = boxp->c1max;
|
|
c2min = boxp->c2min; c2max = boxp->c2max;
|
|
|
|
if (c0max > c0min)
|
|
for (c0 = c0min; c0 <= c0max; c0++)
|
|
for (c1 = c1min; c1 <= c1max; c1++) {
|
|
hist = gHistogram + (((c0 << 5) + c1) << 5) + c2min;
|
|
for (c2 = c2min; c2 <= c2max; c2++)
|
|
if (*hist++) {
|
|
boxp->c0min = c0min = c0;
|
|
goto haveC0Min;
|
|
}
|
|
}
|
|
haveC0Min:
|
|
if (c0max > c0min)
|
|
for (c0 = c0max; c0 >= c0min; c0--)
|
|
for (c1 = c1min; c1 <= c1max; c1++) {
|
|
hist = gHistogram + (((c0 << 5) + c1) << 5) + c2min;
|
|
for (c2 = c2min; c2 <= c2max; c2++)
|
|
if (*hist++) {
|
|
boxp->c0max = c0max = c0;
|
|
goto haveC0Max;
|
|
}
|
|
}
|
|
haveC0Max:
|
|
if (c1max > c1min)
|
|
for (c1 = c1min; c1 <= c1max; c1++)
|
|
for (c0 = c0min; c0 <= c0max; c0++) {
|
|
hist = gHistogram + (((c0 << 5) + c1) << 5) + c2min;
|
|
for (c2 = c2min; c2 <= c2max; c2++)
|
|
if (*hist++) {
|
|
boxp->c1min = c1min = c1;
|
|
goto haveC1Min;
|
|
}
|
|
}
|
|
haveC1Min:
|
|
if (c1max > c1min)
|
|
for (c1 = c1max; c1 >= c1min; c1--)
|
|
for (c0 = c0min; c0 <= c0max; c0++) {
|
|
hist = gHistogram + (((c0 << 5) + c1) << 5) + c2min;
|
|
for (c2 = c2min; c2 <= c2max; c2++)
|
|
if (*hist++) {
|
|
boxp->c1max = c1max = c1;
|
|
goto haveC1Max;
|
|
}
|
|
}
|
|
haveC1Max:
|
|
if (c2max > c2min)
|
|
for (c2 = c2min; c2 <= c2max; c2++)
|
|
for (c0 = c0min; c0 <= c0max; c0++) {
|
|
hist = gHistogram + (((c0 << 5) + c1min) << 5) + c2;
|
|
for (c1 = c1min; c1 <= c1max; c1++, hist += 32)
|
|
if (*hist) {
|
|
boxp->c2min = c2min = c2;
|
|
goto haveC2Min;
|
|
}
|
|
}
|
|
haveC2Min:
|
|
if (c2max > c2min)
|
|
for (c2 = c2max; c2 >= c2min; c2--)
|
|
for (c0 = c0min; c0 <= c0max; c0++) {
|
|
hist = gHistogram + (((c0 << 5) + c1min) << 5) + c2;
|
|
for (c1 = c1min; c1 <= c1max; c1++, hist += 32)
|
|
if (*hist) {
|
|
boxp->c2max = c2max = c2;
|
|
goto haveC2Max;
|
|
}
|
|
}
|
|
haveC2Max:
|
|
ccount = 0;
|
|
for (c0 = c0min; c0 <= c0max; c0++)
|
|
for (c1 = c1min; c1 <= c1max; c1++) {
|
|
hist = gHistogram + (((c0 << 5) + c1) << 5) + c2min;
|
|
for (c2 = c2min; c2 <= c2max; c2++)
|
|
ccount += *hist++;
|
|
}
|
|
boxp->colorCount = ccount;
|
|
}
|
|
|
|
//=====================================================================================
|
|
// void MedianCut(short desiredColors)
|
|
//
|
|
// Repeatedly divides the original box into smaller boxes until we have as many boxes
|
|
// as there are desired colors.
|
|
//
|
|
// External calls: KeepSpinning
|
|
//=====================================================================================
|
|
|
|
static void MedianCut(short desiredColors)
|
|
{
|
|
long c0min, c0max, c1min, c1max, c2min, c2max;
|
|
long c0, c1, c2, cmax;
|
|
register BoxPtr b1, b2;
|
|
ushort n, lb;
|
|
|
|
while (gNumBoxes < desiredColors) {
|
|
KeepSpinning();
|
|
if ((gNumBoxes << 1) <= desiredColors) b1 = FindBiggestPixelPop();
|
|
else b1 = FindBiggestVolume();
|
|
if (!b1) break;
|
|
c0min = b1->c0min; c0max = b1->c0max;
|
|
c1min = b1->c1min; c1max = b1->c1max;
|
|
c2min = b1->c2min; c2max = b1->c2max;
|
|
b2 = &gBoxList[gNumBoxes];
|
|
b2->c0max = c0max; b2->c1max = c1max; b2->c2max = c2max;
|
|
b2->c0min = c0min; b2->c1min = c1min; b2->c2min = c2min;
|
|
c0 = (c0max - c0min) * kRedScale;
|
|
c1 = (c1max - c1min) * kGreenScale;
|
|
c2 = (c2max - c2min) * kBlueScale;
|
|
cmax = c0; n = 0;
|
|
if (c1 > cmax) { cmax = c1; n = 1; }
|
|
if (c2 > cmax) n = 2;
|
|
switch (n) {
|
|
case 0:
|
|
lb = (c0max + c0min) >> 1;
|
|
b1->c0max = lb;
|
|
b2->c0min = lb + 1;
|
|
break;
|
|
case 1:
|
|
lb = (c1max + c1min) >> 1;
|
|
b1->c1max = lb;
|
|
b2->c1min = lb + 1;
|
|
break;
|
|
case 2:
|
|
lb = (c2max + c2min) >> 1;
|
|
b1->c2max = lb;
|
|
b2->c2min = lb + 1;
|
|
break;
|
|
}
|
|
UpdateBox(b1);
|
|
UpdateBox(b2);
|
|
gNumBoxes++;
|
|
}
|
|
}
|
|
|
|
//=====================================================================================
|
|
// void ComputeColor(BoxPtr boxp, int icolor, PaletteHandle thePalette)
|
|
//
|
|
// Calculates the final RGB color by doing a weighted average over pixels in the
|
|
// given box.
|
|
//
|
|
// External calls: none
|
|
//=====================================================================================
|
|
|
|
static void ComputeColor(BoxPtr boxp, int icolor, PaletteHandle thePalette)
|
|
{
|
|
ulong count, total = 0, c0total = 0, c1total = 0, c2total = 0;
|
|
short c0min, c0max, c1min, c1max, c2min, c2max;
|
|
short c0, c1, c2, r, g, b;
|
|
register ushort *hist;
|
|
RGBColor theColor;
|
|
|
|
if (!icolor) {
|
|
theColor.red = theColor.blue = theColor.green = 0xffff;
|
|
SetEntryColor(thePalette, 0, &theColor);
|
|
theColor.red = theColor.blue = theColor.green = 0;
|
|
SetEntryColor(thePalette, 1, &theColor);
|
|
}
|
|
|
|
c0min = boxp->c0min; c0max = boxp->c0max;
|
|
c1min = boxp->c1min; c1max = boxp->c1max;
|
|
c2min = boxp->c2min; c2max = boxp->c2max;
|
|
|
|
for (c0 = c0min; c0 <= c0max; c0++)
|
|
for (c1 = c1min; c1 <= c1max; c1++) {
|
|
hist = gHistogram + (((c0 << 5) + c1) << 5) + c2min;
|
|
for (c2 = c2min; c2 <= c2max; c2++)
|
|
if (count = *hist++) {
|
|
total += count;
|
|
c0total += ((c0 << 3) + 4) * count;
|
|
c1total += ((c1 << 3) + 4) * count;
|
|
c2total += ((c2 << 3) + 4) * count;
|
|
}
|
|
}
|
|
|
|
if (total) {
|
|
r = (c0total + (total >> 1)) / total;
|
|
g = (c1total + (total >> 1)) / total;
|
|
b = (c2total + (total >> 1)) / total;
|
|
theColor.red = ((ushort)r << 8) | (ushort)r;
|
|
theColor.green = ((ushort)g << 8) | (ushort)g;
|
|
theColor.blue = ((ushort)b << 8) | (ushort)b;
|
|
} else theColor.red = theColor.green = theColor.blue = 0;
|
|
|
|
SetEntryColor(thePalette, icolor + 2, &theColor);
|
|
}
|
|
|
|
#if !USE_ASM
|
|
|
|
//=====================================================================================
|
|
// Generic C substitutes for the assembly functions defined below.
|
|
//=====================================================================================
|
|
|
|
static void DoFillHistogram32(FillDataPtr theData)
|
|
{
|
|
#include "FillHistogram32.c"
|
|
}
|
|
|
|
static void DoFillHistogram16(FillDataPtr theData)
|
|
{
|
|
#include "FillHistogram16.c"
|
|
}
|
|
|
|
#elif THINK_C
|
|
|
|
//=====================================================================================
|
|
// Assembly language interfaces for THINK C; here we define the offsets of all fields
|
|
// within the CopyData structure, and then provide C function wrappers around asm { }
|
|
// statements that #include the actual assembly code. In MPW, we just need to compile
|
|
// Quantize.a to get the assembly routines.
|
|
//=====================================================================================
|
|
|
|
#define height offsetof(FillData, height) /* 0 */
|
|
#define width offsetof(FillData, width) /* 4 */
|
|
#define rowBytes offsetof(FillData, rowBytes) /* 8 */
|
|
#define histogram offsetof(FillData, histogram) /* 12 */
|
|
#define baseAdr offsetof(FillData, baseAdr) /* 16 */
|
|
|
|
static void DoFillHistogram32(FillDataPtr theData)
|
|
{
|
|
asm {
|
|
#include "FillHistogram32.a"
|
|
}
|
|
}
|
|
|
|
static void DoFillHistogram16(FillDataPtr theData)
|
|
{
|
|
asm {
|
|
#include "FillHistogram16.a"
|
|
}
|
|
}
|
|
|
|
#endif
|