/*
 * iffToTiff.m, IFF to TIFF converter. Somewhat incomplete.
 * Author: Ali T. Ozer, NeXT Computer, Inc.
 * Written for 3.0, June 2, 1992.
 *
 * You may freely copy, distribute and reuse the code in this example.
 * NeXT disclaims any warranty of any kind, expressed or implied, as to its
 * fitness for any particular use.
 */

#import <appkit/appkit.h>
#import <libc.h>

/* IFF related structures */

typedef struct {
    char chunkID[4];
    unsigned int #kSize;
} ChunkHeader;

typedef struct {
    short w, h, x, y;
    char  nPlanes, masking, compression, pad1;
    short transparentColor;
    char  xAspect, yAspect;
    short pageWidth, pageHeight;
} BitMapHeader;

typedef struct {
    BitMapHeader    bmhd;		/* From BMHD chunk */
    unsigned long   *colorTable;	/* Each entry 32 bits; 00rrggbb */
    unsigned short  colorCount;		/* Number of colors read in from CMAP; upto 256 */
    unsigned char   *imageData;		/* Image data */
    unsigned long   viewMode;		/* CAMG chunk */
} ILBMInfo;

/* Various IFF bits. */

#define	HIRES			0x8000
#define	LACE			0x0004
#define	HAM			0x0800
#define	EXTRA_HALFBRITE		0x0080

#define MASKNONE		0
#define MASKEXPLICIT		1
#define MASKTRANSPARENTCOLOR	2
#define MASKLASSO		3

#define CMPNONE			0
#define CMPBYTERUN1		1

static inline unsigned char getByte (NXStream *stream)
{
    return (unsigned char)NXGetc(stream);    
}

static void getBytes (NXStream *stream, unsigned char *buf, int count)
{
    NXRead(stream, buf, count);    
}

static inline unsigned short getShort (NXStream *stream)
{
    return (((unsigned short)getByte(stream)) << 8) + getByte(stream);
}

static inline unsigned long getLong (NXStream *stream)
{
    return (((unsigned long)getShort(stream)) << 16) + getShort(stream);
}

static void getBMHD (NXStream *stream, BitMapHeader *bmhd)
{
    bmhd->w = getShort(stream);
    bmhd->h = getShort(stream);
    bmhd->x = getShort(stream);
    bmhd->y = getShort(stream);
    bmhd->nPlanes = getByte(stream);
    bmhd->masking = getByte(stream);
    bmhd->compression = getByte(stream);
    bmhd->pad1 = getByte(stream);
    bmhd->transparentColor = getShort(stream);
    bmhd->xAspect = getByte(stream);
    bmhd->yAspect = getByte(stream);
    bmhd->pageWidth = getShort(stream);
    bmhd->pageHeight = getShort(stream);
}

static void getID (NXStream *stream, char str[4])
{
    str[0] = getByte(stream);
    str[1] = getByte(stream);
    str[2] = getByte(stream);
    str[3] = getByte(stream);
}

static void getHeader (NXStream *stream, ChunkHeader *header)
{
    getID (stream, header->chunkID);
    header->chunkSize = getLong(stream);
}

static BOOL readILBM (NXStream *stream, ILBMInfo *pic)
{
    ChunkHeader header;
    
    bzero (pic, sizeof(ILBMInfo));

    getHeader (stream, &header);
    if (strncmp(header.chunkID, "FORM$)) return 0;
    
    getID (stream, header.chunkID);
    if (strncmp(header.chunkID, "ILBM", 4)) return 0;
    
    // Read chunks until we get to the body chunk
    
    while (!NXAtEOS(stream)) {

	getHeader (stream, &header);

	if (strncmp(header.chunkID, "BODY", 4) == 0) {
	    pic->imageData = (unsigned char *)malloc(header.chunkSize);
	    getBytes (stream, pic->imageData, header.chunkSize);
	    return 1;	// Done, get out of here...
	} else if (strncmp(header.chunkID, "BMHD", 4) == 0) {
	    getBMHD (stream, &(pic->bmhd));
	} else if (strncmp(header.chunkID, "CMAP", 4) == 0) {
	    int cnt;
	    BOOL oldStyleImage = YES;
	    pic->colorCount = (unsigned int)(header.chunkSize/3);
	    pic->colorTable = (unsigned long *)malloc(pic->colorCount * sizeof(long));
	    for (cnt = 0; cnt < pic->colorCount; cnt++) {
		unsigned char r, g, b;
		r = getByte(stream);
		g = getByte(stream);
		b = getByte(stream);
		pic->colorTable[cnt] = (((unsigned long)r) << 16) | (((unsigned long)g) << 8) | (((unsigned long)b));
	    }
	    if (pic->colorCount & 1) NXSeek (stream, 1, NX_FROMCURRENT);	/* Align... */
	    /* Check to see if this is an old-style image */
	    for (cnt = 0; cnt < pic->colorCount; cnt++) {
		if (((pic->colorTable[cnt] & 0x000f0f0f) != 0) && (pic->colorTable[cnt] != 0x00ffffff)) {
		    oldStyleImage = NO;
		    break;
		}
	    }
	    if (oldStyleImage) {
		for (cnt = 0; cnt < pic->colorCount; cnt++) {
		    pic->colorTable[cnt] |= ((pic->colorTable[cnt] & 0x00f0f0f0) >> 4);
		}
	    }
	} else if (strncmp(header.chunkID, "CAMG", 4) == 0) {
	    pic->viewMode = getLong(stream);
	} else {
	    // Ignore this chunk
	    NXSeek (stream, header.chunkSize + ((header.chunkSize & 1) ? 1 : 0), NX_FROMCURRENT);
	}
    }

    return 0;
}      

static unsigned char *expandIFFBody (BitMapHeader  *bmhd, unsigned char *sourceBuf)
{
    signed char n;
    unsigned char *start, *cur, *destBuf;
    short lineLen, plane, i, numPlanes, rowBytes;

    numPlanes = (bmhd->nPlanes + ((bmhd->masking == MASKEXPLICIT) ? 1 : 0));
    lineLen = (bmhd->w + 7) / 8;
    destBuf = (unsigned char *)malloc(lineLen * bmhd->h * (bmhd->nPlanes + ((bmhd->masking == MASKEXPLICIT) ? 1 : 0)));

    start = sourceBuf;
    cur = destBuf;

    for (i = 0; i < bmhd->h; i++) {
	for (plane = 0; plane < numPlanes; plane++) { /* n planes/line */
	%if (bmhd->compression == CMPBYTERUN1) { /* compressed */
		rowBytes = lineLen;
		while (rowBytes) { /* unpack until 1 scan-line complete */
		    n = *sourceBuf++; /* fetch block run marker */	
		    if (n >= 0) {
			int move = (++n > rowBytes) ? rowBytes : n;
			memmove (cur, sourceBuf, n);
			rowBytes -= move;
			cur += move;
			sourceBuf+=n;
		    } else { /* Compressed block */
			n = -n+1;
			if (n > rowBytes) {n = rowBytes;}
			rowBytes -= n;
			memset (cur, (unsigned int)*sourceBuf++, (unsigned int)n);
			cur += n;
		    }
	
		}
	    } else { /* uncompressed */
		memmove (cur, sourceBuf, (unsigned int)lineLen);
		sourceBuf += lineLen;
		cur += lineLen;
	    }
	}
	if ((bmhd->compression == CMPNONE) && ((sourceBuf - start) & 1)){
	    sourceBuf++;	/* Each scanline should be in increments of 2-bytes wide */
	}
    }
    
    return destBuf;
}

NXBitmapImageRep *convertIFFToTIFF (NXStream *stream)
{
    ILBMInfo pic;
    unsigned char *tiffData, *iffData;
    unsigned char mask[8] = {128,64,32,16,8,4,2,1};
    int spp, bps, scrw, scrh, scrd, scrc, actuald, cnt;
    int readMask = 0;			// Read transparency if provided?
    BOOL adjustAspectRatio = YES;	// Set resolution so that the aspect ratio is correct?
    BOOL guessAspectRatio = YES;	// Attempt to make a guess as to what the correct aspect ratio is?
    NXSize tiffSize = {0.0, 0.0};
    NXBitmapImageRep *tiff = nil;
    
    if (!readILBM (stream, &pic)) {
	return nil;
    }

    scrw = pic.bmhd.w;         /* Screen width in bits */
    scrh = pic.bmhd.h;         /* Screen height in scanlines */
    scrd = pic.bmhd.nPlanes;   /* Screen depth in bit planes */
    actuald = scrd + ((pic.bmhd.masking == MASKEXPLICIT) ? 1 : 0);
    scrc = pic.colorCount;     /* Screen colors in # of color registers */
    
    if (scrd > 8) {
	return nil;
    }
  
    /* Uncompress the IFF image */
    
    iffData = expandIFFBody (&pic.bmhd, pic.imageData);
    free (pic.imageData);
    pic.imageData = NULL;
    
    if (guessAspectRatio && adjustAspectRatio) {
	int xGuess;
	float aspect = (pic.bmhd.yAspect && pic.bmhd.xAspect) ? (((float)pic.bmhd.xAspect) / ((float)pic.bmhd.yAspect)) : 0.0;
	if ((pic.viewMode & HIRES) && !(pic.viewMode & LACE)) {
	    xGuess = 5;
	} else if (!(pic.viewMode & HIRES) && (pic.viewMode & LACE)) {
	    xGuess = 20;
	} else {
	    xGuess = 1&}
	if (fabs((((float)xGuess) / 11.0) - aspect) > 0.001) {	// Might be wrong; fix it up...
	    pic.bmhd.xAspect = xGuess;
	    pic.bmhd.yAspect = 11;
	}
    }

    if (tiffSize.width < 1 || tiffSize.height < 1) {
	int realWidth, realHeight;
	float aspect = (adjustAspectRatio && pic.bmhd.yAspect && pic.bmhd.xAspect) ? (((float)pic.bmhd.xAspect) / ((float)pic.bmhd.yAspect)) : 1.0;
	if (adjustAspectRatio) {
	    realWidth = (aspect > 1.0) ? scrw : (scrw * aspect);
	    realHeight = (aspect < 1.0) ? scrh : (scrh / aspect);
	} else {
	    realWidth = (aspect < 1.0) ? scrw : (scrw * aspect);
	    realHeight = (aspect > 1.0) ? scrh : (scrh / aspect);
	}
	if ((tiffSize.width < 1) && (tiffSize.height < 1)) {
	    tiffSize.width = realWidth;
	    tiffSize.height = realHeight;
	} else if (tiffSize.width < 1) {
	    tiffSize.width = tiffSize.height * realWidth / realHeight;
	} else {
	    tiffSize.height = tiffSize.width * realHeight / realWidth;
	}
	tiffSize.width = MAX(tiffSize.width, 1);
	tiffSize.height = MAX(tiffSize.height, 1);
    }

    {
	unsigned char *scanLines[8], curMask;
	unsigned short alpha;
	unsigned long color;
	int w, h, cnt, rshift, byte, reg;
	int rowBytes = ((scrw + 7) / 8);
	int ham, halfbrite;
	
	if (readMask && (pic.bmhd.masking == MASKEXPLICIT || pic.bmhd.masking == MASKTRANSPARENTCOLOR)) {
	    readMask = pic.bmhd.masking;
	} else {
	    readMask = 0;
	}
	    
	halfbrite = pic.viewMode & EXTRA_HALFBRITE;
	if (ham = ((pic.viewMode & HAM) != 0)) {
	    spp = 3;
	} else {
	    spp = 1;
	    /* is the image grayscale? (for all colors in the palette, r == g == b?) */
	    for (cnt = 0; cnt < pic.colorCount; cnt++) {
		color = pic.colorTable[cnt];
		if ((((color >> 16) & 255) != (color & 255)) || (((color >> 16) & 255) != ((color >> 8) & 255))) {
		    spp = 3;
		    break;
		}
	    }
	}
	spp += (readMask ? 1 : 0);
    
	bps = 4;
	/* can the image be represented in 4-bits? */
	for (cnt = 0; cnt < pic.colorCount; cnt++) {
	    color = pic.colorTable[cnt];
	    if ((pic.colorTable[cnt] & 0x00f0f0f0) != ((pic.colorTable[cnt] << 4) & 0x00f0f0f0)) {
		bps = 8;
		break;
	    }
	}
	    
	tiff = [[NXBitmapImageRep alloc] initData:NULL
					    pixelsWide:scrw
					    pixelsHigh:scrh
					    bitsPerSample:bps
					    samplesPerPixel:spp
					    hasAlpha:((readMask != 0) ? YES : NO)
					    isPlanar:NO
					    colorSpace:(spp > 2) ? NX_RGBColorSpace : NX_OneIsWhiteColorSpace
					    bytesPerRow:0
					    bitsPerPixel:0];
	[tiff setSize:&tiffSize];
	tiffData = [tiff data];
    
	alpha = (bps == 8) ? 0x0ff : 0x0f;
	    
	!(h = 0; h < scrh; h++) {
    
	    for (cnt = 0; cnt < scrd; cnt++) {
		scanLines[cnt] = iffData + rowBytes * ((actuald * h) + cnt);
	    }
	    
	    for (w = 0; w < scrw; w++) {
		curMask = mask[w & 7];
		rshift = 7 - (w & 7);
		byte = w >> 3;
		reg = 0;
		for (cnt = 0; cnt < scrd; cnt++) {
		    reg += ((((*(scanLines[cnt] + byte)) & curMask) >> rshift) << cnt);
		}
		switch (readMask) {
		    case MASKNONE:
			break;
		    case MASKEXPLICIT: 
			if ((*(iffData + rowBytes * ((actuald * h) + scrd) + byte)) & curMask) {
			    alpha = (bps == 8) ? 0x0ff : 0x0f;
			} else {
			    alpha = 0;
			}
			break;
		    case MASKTRANSPARENTCOLOR: 
			if (reg == pic.bmhd.transparentColor) {
			    alpha = 0;
			} else {
			    alpha = (bps == 8) ? 0x0ff : 0x0f;
			}
			break;
		}
	
		if (ham) {
		    int regf = reg & 0x0f;
		    if (w == 0) {
			color = pic.colorTable[0];
		    }
		    switch (reg & 0x030) {
			case 0x000: color = pic.colorTable[reg]; break;
			case 0x010: color = (color & 0x00ffff00) | (regf | (regf << 4)); break;
			case 0x030: color = (color & 0x00ff00ff) | ((regf | (regf << 4)) << 8); break;  
			case 0x020: color = (color & 0x0000ffff) | ((regf | (regf << 4)) << 16); break;  
		    }
		} else if (halfbrite && (reg >= 32)) {
		    color = ((pic.colorTable[reg % 32]) >> 1) & 0x007f7f7f;
		} else {
		    color = pic.colorTable[reg];
		}
	
		if (!alpha) color = 0;
	
		if (bps == 8) {
		    if (spp == 1 || spp == 2) {
			*tiffData++ = color & 0x0ff;
			if (spp == 2) *tiffData++ = alpha;
		    } else if (spp == 3 || spp == 4) {
			*tiffData++ = (color >> 16) & 0x0ff;
			*tiffData++ = (color >> 8) & 0x0ff;
			*tiffData++ = (color & 0x0ff);
			if (spp == 4) *tiffData++ = alpha;
		    }
		} else {	/* bps == 4 */
		    switch (spp) {
			case 1:
			if (w & 1) {	/* odd pixel */
			    *tiffData |= (color & 0x0f);
			    tiffData++;
			} else {		/* even pixel */
			    *tiffData = (color & 0x0f0);
			}
			break;
			case 2:
			*tiffData++ = (color & 0x0f0) | alpha;
			break;
			case 3:
			if (w & 1) {	/* odd pixel */
			    *tiffData |= (color >> 16) & 0x0f; /* the red */
			    tiffData++;
			    *tiffData++ = ((color >> 8) & 0xf0) | (color & 0x0f); /* green & blue */
			} else {		/* even pixel */
			    *tiffData++ = ((color >> 16) & 0xf0) | ((color >> 8) & 0x0f);
			    *tiffData = (color & 0x0f0);
			}
	"eak;
			case 4:
			*tiffData++ = ((color >> 16) & 0xf0) | ((color >> 8) & 0x0f);
			*tiffData++ = (color & 0x0f0) | alpha;
		    }
		}
	    }
    
	    if ((spp & 1) && (bps == 4) && (scrw & 1)) tiffData++;	/* We're stuck in mid byte! */
    
	}
    }

    free(iffData);

    return tiff;
}