/*********************************************************/
/* 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 "BMP.h"

//=====================================================================================
// BMPPrivates: private data structure for BMP images, maintained for each image.
//=====================================================================================

typedef struct BMPPrivates BMPPrivates, *BMPPrivatesPtr, **BMPPrivatesHandle;
typedef (*ExtractProcPtr)(uchar **srcData, uchar *dstData, BMPPrivatesHandle privates);

struct BMPPrivates
{
	ushort compression;			// compression method used
	ushort depth;				// calculated depth of the image
	ulong height, width;		// size of the image
	ulong dataStart;			// offset of the start of data
	CTabHandle decompColors;	// color table to be used in decompression
	ExtractProcPtr extract;		// pointer to extract function
};

//=====================================================================================
// Constants specific to this module
//=====================================================================================

enum {
	compRGB = 0,
	compRLE8 = 1,
	compRLE4 = 2
};

enum {
	errAllDone = 1
};

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

static CTabHandle ExtractColorTable(uchar *dataStart, ulong offset, short depth,
			short colors, ulong *newOffset);
static OSErr SetUpCompression(BMPPrivatesHandle privates, ImageHandle theImage);
static OSErr DecompressBMPImage(uchar *dataStart, BMPPrivatesHandle privates,
			PixMapHandle dstPixMap, ICMProgressProcRecordPtr prog, Size length);
static void CopyPixMapToDestination(PixMapHandle srcPixMap, short startRow);
static OSErr ExtractUncompressed(uchar **srcData, uchar *dstData,
			BMPPrivatesHandle privates);
static OSErr ExtractUncompressedRGB(uchar **srcData, uchar *dstData,
			BMPPrivatesHandle privates);
static OSErr ExtractRLE4(uchar **srcData, uchar *dstData, BMPPrivatesHandle privates);
static OSErr ExtractRLE8(uchar **srcData, uchar *dstData, BMPPrivatesHandle privates);

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

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

//=====================================================================================
// OSErr OpenBMP(ImageHandle theImage)
//=====================================================================================
// Initializes the image record for a BMP image; note that since we are a simple
// baseline BMP reader, we only read the first set of tags.
//=====================================================================================

#define GetIntelLong(a, o) \
			((ulong)a[o] + \
			 (ulong)(a[(o) + 1] << 8) + \
			 (ulong)(a[(o) + 2] << 16) + \
			 (ulong)(a[(o) + 3] << 24))
#define GetIntelShort(a, o) \
			((ushort)a[o] + \
			 (ushort)(a[(o) + 1] << 8))

extern OSErr OpenBMP(ImageHandle theImage)
{
	long theLen = GetHandleSize((*theImage)->data);
	char hState = HGetState((*theImage)->data);
	BMPPrivatesHandle privates;
	ulong offset, headerSize;
	OSErr theErr = noErr;
	uchar *dataStart;

	if (!(privates = (BMPPrivatesHandle)NewHandleClear(sizeof(BMPPrivates))))
		return memFullErr;
	(*theImage)->privateData = (Handle)privates;
	HLock((*theImage)->data);
	dataStart = (uchar *)*(*theImage)->data;
	(*privates)->dataStart = GetIntelLong(dataStart, 10);
	offset = 14;
	headerSize = GetIntelLong(dataStart, offset);
	if (headerSize <= 12) {
		// BITMAPCOREHEADER
		(*privates)->width = (*theImage)->grect.right = GetIntelShort(dataStart, offset + 4);
		(*privates)->height = (*theImage)->grect.bottom = GetIntelShort(dataStart, offset + 6);
		if (GetIntelShort(dataStart, offset + 8) != 1) theErr = codecBadDataErr;
		(*privates)->depth = (*theImage)->depth = GetIntelShort(dataStart, offset + 10);
		(*privates)->decompColors = ExtractColorTable(dataStart, offset + headerSize,
					(*privates)->depth, -1, &offset);
	} else {
		// BITMAPINFOHEADER
		(*privates)->width = (*theImage)->grect.right = GetIntelLong(dataStart, offset + 4);
		(*privates)->height = (*theImage)->grect.bottom = GetIntelLong(dataStart, offset + 8);
		if (GetIntelShort(dataStart, offset + 12) != 1) theErr = codecBadDataErr;
		(*privates)->depth = (*theImage)->depth = GetIntelShort(dataStart, offset + 14);
		(*privates)->compression = GetIntelLong(dataStart, offset + 16);
		(*privates)->decompColors = ExtractColorTable(dataStart, offset + headerSize,
					(*privates)->depth, GetIntelLong(dataStart, offset + 32), &offset);
	}
	if ((*privates)->depth == 24) (*privates)->depth = 32;
	else if ((*privates)->depth < 24 && !(*privates)->decompColors) theErr = codecBadDataErr;
	if (theErr == noErr) theErr = SetUpCompression(privates, theImage);
	if ((*privates)->decompColors)
		(*theImage)->ipalette = NewPalette(256, (*privates)->decompColors, pmTolerant, 0);
	(*theImage)->qrect = (*theImage)->crect = (*theImage)->grect;
	HSetState((*theImage)->data, hState);
	return theErr;
}

//=====================================================================================
// OSErr DrawBMP(Handle theHandle, JVDrawParamsHandle theParams)
//=====================================================================================
// Draws a BMP image into the specified GWorld, from the srcRect into the dstRect,
// clipping as dstRgn specifies.
//=====================================================================================

extern OSErr DrawBMP(Handle theHandle, JVDrawParamsHandle theParams)
{
	BMPPrivatesHandle privates = (BMPPrivatesHandle)(*theParams)->privateData;
	NestedProgress theProgress = (*theParams)->progress;
	char hState = HGetState(theHandle);
	OSErr theErr = noErr;
	GWorldPtr theGWorld;
	uchar *dataStart;
	
	if (theProgress.prog.progressProc) CallICMProgressProc(theProgress.prog.progressProc, 
				codecProgressUpdatePercent, 0x00000000L, theProgress.prog.progressRefCon);
	HLockHi(theHandle);
	if (theErr == noErr) {
		dataStart = (uchar *)StripAddress(*theHandle);
		theGWorld = NewTempGWorld((*privates)->width, (*privates)->height, (*privates)->depth,
					(*privates)->decompColors);
		if (theGWorld) {
			theErr = DecompressBMPImage(dataStart, privates, GetGWorldPixMap(theGWorld), 
						(ICMProgressProcRecordPtr)&theProgress, GetHandleSize(theHandle));
			if ((*theParams)->progress.aborted) theErr = codecAbortErr;
			DisposeGWorld(theGWorld);
		} else gIntError = errNoDrawMemory, theErr = memFullErr;
	}
	HSetState(theHandle, hState);
	if (theProgress.prog.progressProc) CallICMProgressProc(theProgress.prog.progressProc, 
				codecProgressUpdatePercent, 0x00010000L, theProgress.prog.progressRefCon);
	return theErr;
}

//=====================================================================================
// OSErr CloneBMP(ImageHandle origImage, ImageHandle newImage)
//=====================================================================================
// Called when an image is cloned, to give us a chance to duplicate any secondary
// storage.
//=====================================================================================

extern OSErr CloneBMP(ImageHandle origImage, ImageHandle newImage)
{
	BMPPrivatesHandle origPrivates = (BMPPrivatesHandle)(*origImage)->privateData;
	BMPPrivatesHandle newPrivates = (BMPPrivatesHandle)(*newImage)->privateData;
	CTabHandle theColors;
	OSErr theErr;
	
	if (origPrivates && newPrivates) {
		if (theColors = (*origPrivates)->decompColors) {
			theErr = HandToHand((Handle *)&theColors);
			(*newPrivates)->decompColors = theColors;
		}
	}
	return theErr;
}

//=====================================================================================
// OSErr CloseBMP(ImageHandle theImage)
//=====================================================================================
// Called before the image is disposed, to get rid of any secondary storage we
// allocated on open.
//=====================================================================================

extern OSErr CloseBMP(ImageHandle theImage)
{
	BMPPrivatesHandle privates = (BMPPrivatesHandle)(*theImage)->privateData;
	
	if (privates)
		if ((*privates)->decompColors) DisposeHandle((Handle)(*privates)->decompColors);
	return noErr;
}

//=====================================================================================
// CTabHandle ExtractColorTable(uchar *dataStart, ulong offset, short depth,
//			short colors, ulong *newOffset)
//=====================================================================================
// Extracts the included BMP color table from the data at the given offset.
//=====================================================================================

static CTabHandle ExtractColorTable(uchar *dataStart, ulong offset, short depth,
			short colors, ulong *newOffset)
{
	ulong count = (colors <= 0) ? (1L << depth) : colors, size = (1L << depth), i;
	uchar *data = dataStart + offset;
	Boolean isRGB3 = (colors == -1);
	CTabHandle theColors;

	if (depth == 24 && colors <= 0) return nil;
	if (count > size || count > 256) {
		*newOffset = offset + count * ((isRGB3) ? 3 : 4);
		return nil;
	}
	theColors = (CTabHandle)NewHandleClear(sizeof(ColorTable) + (size - 1) * sizeof(ColorSpec));
	if (theColors) {
		(*theColors)->ctSeed = GetCTSeed();
		(*theColors)->ctFlags = 0x8000;
		(*theColors)->ctSize = size;
		for (i = 0; i < count; i++) {
			(*theColors)->ctTable[i].rgb.blue = ((ushort)*data << 8) + *data, data++;
			(*theColors)->ctTable[i].rgb.green = ((ushort)*data << 8) + *data, data++;
			(*theColors)->ctTable[i].rgb.red = ((ushort)*data << 8) + *data, data++;
			if (!isRGB3) data++;
		}
	}
	*newOffset = data - dataStart;
	return theColors;
}

//=====================================================================================
// OSErr SetUpCompression(BMPPrivatesHandle privates, ImageHandle theImage)
//=====================================================================================
// Sets up the compression parameters for the BMP image.
//=====================================================================================

static OSErr SetUpCompression(BMPPrivatesHandle privates, ImageHandle theImage)
{
	OSErr theErr = noErr;
	
	switch ((*privates)->compression) {
		case compRGB:
			(*theImage)->compression = 0;
			BlockMove(gString[strUncompressed], (*theImage)->compressionDesc, *gString[strUncompressed] + 1);
			(*privates)->extract = ((*privates)->depth < 24) ?
				(ExtractProcPtr)ExtractUncompressed : (ExtractProcPtr)ExtractUncompressedRGB;
			break;
			
		case compRLE8:
			(*theImage)->compression = kRLE8Compression;
			BlockMove(gString[strRLE8], (*theImage)->compressionDesc, *gString[strRLE8] + 1);
			(*privates)->extract = (ExtractProcPtr)ExtractRLE8;
			break;
			
		case compRLE4:
			(*theImage)->compression = kRLE4Compression;
			BlockMove(gString[strRLE4], (*theImage)->compressionDesc, *gString[strRLE4] + 1);
			(*privates)->extract = (ExtractProcPtr)ExtractRLE4;
			break;
			
		default:
			theErr = codecBadDataErr;
			break;
	}
	return theErr;
}

//=====================================================================================
// OSErr DecompressBMPImage(uchar *dataStart, BMPPrivatesHandle privates,
//			PixMapHandle dstPixMap, ICMProgressProcRecordPtr prog, Size length)
//=====================================================================================
// Oversees the decompression of a BMP image, handling banding, and calling the
// appropriate function to perform the actual decompression.
//=====================================================================================

static OSErr DecompressBMPImage(uchar *dataStart, BMPPrivatesHandle privates,
			PixMapHandle dstPixMap, ICMProgressProcRecordPtr prog, Size length)
{
	uchar *ptr, *rowStart, *dataEnd = dataStart + length;
	char pState = GetPixelsState(dstPixMap);
	RgnHandle dstRgn = qd.thePort->visRgn;
	ulong bandRow = 0, row;
	char mmuMode = true32b;
	OSErr theErr = noErr;
	
	LockPixels(dstPixMap);
	rowStart = (uchar *)GetPixBaseAddr(dstPixMap) +
				(Height(&(*dstPixMap)->bounds) - 1) * ((*dstPixMap)->rowBytes & 0x3fff);
	ptr = dataStart + (*privates)->dataStart;
	for (row = 0; row < (*privates)->height; row++) {
		if (ptr < dataStart || ptr > dataEnd) {
			theErr = codecBadDataErr;
			break;
		}
		SwapMMUMode(&mmuMode);
		theErr = (*privates)->extract(&ptr, rowStart, privates);
		SwapMMUMode(&mmuMode);
		rowStart -= ((*dstPixMap)->rowBytes & 0x3fff);
		if (!(row & 63) && prog->progressProc)
			theErr = (OSErr)CallICMProgressProc(prog->progressProc, 
						codecProgressUpdatePercent,
						FixRatio(row, (*privates)->height), prog->progressRefCon);
		if (++bandRow == Height(&(*dstPixMap)->bounds)) {
			CopyPixMapToDestination(dstPixMap, (*privates)->height - row - 1);
			bandRow = 0;
			rowStart = (uchar *)GetPixBaseAddr(dstPixMap) +
						(Height(&(*dstPixMap)->bounds) - 1) * ((*dstPixMap)->rowBytes & 0x3fff);
		}
		if (theErr != noErr) break;
	}
	if (theErr == errAllDone) theErr = noErr;
	if (theErr == noErr && bandRow) 
		CopyPixMapToDestination(dstPixMap,
					(*privates)->height - (row + Height(&(*dstPixMap)->bounds) - bandRow));
	SetPixelsState(dstPixMap, pState);
	return theErr;
}

//=====================================================================================
// void CopyPixMapToDestination(PixMapHandle srcPixMap, short startRow)
//=====================================================================================
// Copies the band contained in srcPixMap to the destination, offset from the top by
// startRow.
//=====================================================================================

static void CopyPixMapToDestination(PixMapHandle srcPixMap, short startRow)
{
	Rect srcRect = (*srcPixMap)->bounds, dstRect = (*srcPixMap)->bounds;
	PixMapHandle dstPixMap = GetGWorldPixMap((CGrafPtr)qd.thePort);
	char srcState = HGetState((Handle)srcPixMap);
	char dstState = HGetState((Handle)dstPixMap);
	
	OffsetRect(&dstRect, 0, startRow);
	HLock((Handle)srcPixMap);
	HLock((Handle)dstPixMap);
	CopyBits((BitMap *)*srcPixMap, (BitMap *)*dstPixMap, &srcRect, &dstRect,
				srcCopy + ditherCopy, qd.thePort->visRgn);
	HSetState((Handle)dstPixMap, dstState);
	HSetState((Handle)srcPixMap, srcState);
}

//=====================================================================================
// OSErr ExtractUncompressed(uchar **srcData, uchar *dstData,
//			BMPPrivatesHandle privates)
//=====================================================================================
// Pulls out a row of uncompressed 4 or 8 bit data.
//=====================================================================================

static OSErr ExtractUncompressed(uchar **srcData, uchar *dstData,
			BMPPrivatesHandle privates)
{
	ulong bytes = ((*privates)->width * (*privates)->depth + 7) / 8L;
	ulong offset = (*privates)->dataStart & 3;
	uchar *src = *srcData;

	while (bytes--) *dstData++ = *src++;
	*srcData = (uchar *)(((long)src - offset + 3) & 0xfffffffc) + offset;
	return noErr;
}

//=====================================================================================
// OSErr ExtractUncompressedRGB(uchar **srcData, uchar *dstData,
//			BMPPrivatesHandle privates)
//=====================================================================================
// Pulls out a row of uncompressed RGB data, skipping the alpha channel in the
// destination.
//=====================================================================================

static OSErr ExtractUncompressedRGB(uchar **srcData, uchar *dstData,
			BMPPrivatesHandle privates)
{
//	ulong offset = (*privates)->dataStart & 3;
	ulong col = (*privates)->width;
	uchar *src = *srcData;

	while (col--) {
		dstData++, *dstData++ = src[2], *dstData++ = src[1], *dstData++ = src[0];
		src += 3;
	}
//	*srcData = (uchar *)(((long)src - offset + 3) & 0xfffffffc) + offset;
	*srcData = src;
	return noErr;
}

//=====================================================================================
// OSErr ExtractRLE4(uchar **srcData, uchar *dstData, BMPPrivatesHandle privates)
//=====================================================================================
// Pulls out a row of RLE-compressed 4-bit data.
//=====================================================================================

static OSErr ExtractRLE4(uchar **srcData, uchar *dstData, BMPPrivatesHandle privates)
{
	uchar *src = *srcData, val, pixel = 0, shift = 4, sshift, spixel;
	short count, col = (*privates)->width;

	while (col) {
		if (count = *src++) {
			val = *src++;
			if ((col -= count) < 0) return codecBadDataErr;
			while (count--) {
				pixel |= (val & 0xf) << shift;
				if (shift = 4 - shift) *dstData++ = pixel, pixel = 0;
				val = (val << 4) | (val >> 4);
			}
		} else {
			count = *src++;
			if (count > 2) {
				if ((col -= count) < 0) return codecBadDataErr;
				sshift = 4, spixel = *src++;
				while (count--) {
					pixel |= ((spixel >> sshift) & 0xf) << shift;
					if (count && (sshift = 4 - sshift)) spixel = *src++;
					if (shift = 4 - shift) *dstData++ = pixel, pixel = 0;
				}
				src = (uchar *)(((long)src + 1) & 0xfffffffe);
			} else if (count == 0) break;
			else if (count == 1) return errAllDone;
			else {
				count = *src++;
				if ((col -= count) < 0) return codecBadDataErr;
				dstData += count >> 1;
				if (count & 1)
					if (shift = 4 - shift) dstData++;
				src++;			// we skip the vertical offset for now
			}
		}
	}
	if (!shift) *dstData = pixel;
	while (!src[0] && !src[1]) src += 2;
	*srcData = src;
	return noErr;
}

//=====================================================================================
// OSErr ExtractRLE8(uchar **srcData, uchar *dstData, BMPPrivatesHandle privates)
//=====================================================================================
// Pulls out a row of RLE-compressed 8-bit data.
//=====================================================================================

static OSErr ExtractRLE8(uchar **srcData, uchar *dstData, BMPPrivatesHandle privates)
{
	short count, col = (*privates)->width;
	uchar *src = *srcData, val;

	while (col) {
		if (count = *src++) {
			val = *src++;
			if ((col -= count) < 0) return codecBadDataErr;
			while (count--) *dstData++ = val;
		} else {
			count = *src++;
			if (count > 2) {
				if ((col -= count) < 0) return codecBadDataErr;
				while (count--) *dstData++ = *src++;
				src = (uchar *)(((long)src + 1) & 0xfffffffe);
			} else if (count == 0) break;
			else if (count == 1) return errAllDone;
			else {
				count = *src++;
				if ((col -= count) < 0) return codecBadDataErr;
				dstData += count;
				src++;			// we skip the vertical offset for now
			}
		}
	}
	while (!src[0] && !src[1]) src += 2;
	*srcData = src;
	return noErr;
}
