/***************************************************************************************/
/* mp2iff24 - By Grant R. Young.  (Tab Size = 2)
 * - Converts ILBM Multi-Palette pictures to ILBM 24bit format.
 * Compiled with SAS/C 6.2
 */
#include <stdio.h>
#include <string.h>
#include <intuition/intuition.h>
#include <intuition/intuitionbase.h>
#include <graphics/gfxbase.h>
#include <exec/exec.h>

#include <libraries/iffparse.h>
#include <proto/exec.h>
#include <proto/intuition.h>
#include <proto/graphics.h>
#include <proto/dos.h>
#include <proto/iffparse.h>
#include <ilbm.h>
#include <packer.h>
#include <pchg.h>

char	version[] = {"$VER: mp2iff24 1.0"};

struct DosLibrary				*DOSBase								= NULL;
struct Library					*IFFParseBase						= NULL;
void 										*PCHGDecomp							= NULL;
struct PCHGHeader				*PCHGData								= NULL;
void 										*PCHGLineData						= NULL;

int mp2iff24(char *infile, char *outfile);
int WriteOutFile(struct BitMapHeader *bmhd, UBYTE *cmap, struct PCHGHeader *pchg,
								 ULONG *camg, UBYTE *anno, UBYTE *body, char *outfile);
static BOOL WriteBMHDChunk( struct IFFHandle *iffhandle, struct BitMapHeader *srcbmh);
BOOL WriteBODYChunk(struct IFFHandle *iffhandle,
										struct BitMapHeader *srcbmh, UBYTE *srccmap, struct PCHGHeader *srcpchg, UBYTE *srcbody);
static BOOL WriteCAMGChunk( struct IFFHandle *iffhandle, ULONG *camg);
static BOOL WriteANNOChunk( struct IFFHandle *iffhandle, UBYTE *anno);
void ConvertLineTo24Bit(UBYTE *srclinebuffer, UBYTE *dstlinebuffer,
												UWORD evenbytewidth, UWORD pixwidth, UBYTE *srccmap, UWORD srcdepth);
UBYTE GetPixelColour(UBYTE *buffer, UWORD bufferwidth, UWORD x, UWORD depth);
UBYTE Get8BitValue(UBYTE *buffer, UWORD bufferwidth, UWORD x);
BOOL PCHG_ParsePCHG(struct PCHGHeader *PCHG, UBYTE *cmap);
void *PCHG_SetCMAP(struct PCHGHeader *PCHG, APTR LineMask, APTR LineData, UBYTE *cmap, WORD line);
void __asm Put24BitValue(	register __a0 UBYTE *buffer,
										register __d0 UWORD bufferwidth,
										register __d1 UWORD x,
										register __d2 UBYTE r,
										register __d3 UBYTE g,
										register __d4 UBYTE b);

int main(int argc,char *argv[])
{
	int ret = 10;

	if( DOSBase = (struct DosLibrary *)OpenLibrary("dos.library", 36L) )
	{
		printf("mp2iff24 : (c) 1995 Grant R. Young\n");
	  if(argc == 3)
		{
			if( IFFParseBase = OpenLibrary("iffparse.library", 36L) )
			{
				ret = mp2iff24(argv[1],argv[2]);
			}
			else
				printf("Can't Open 'iffparse.library', V36\n");
		}
		else
			printf("Usage: mp2iff24 <multi-palette file> <24-bit output>\n");
	}
	else
	{
		if( DOSBase = (struct DosLibrary *)OpenLibrary("dos.library", 0L) )
			printf("Can't Open 'dos.library', V36\n");
	}

	if(PCHGDecomp) FreeVec(PCHGDecomp);
	if(IFFParseBase) CloseLibrary(IFFParseBase);
	if(DOSBase) CloseLibrary((struct Library *)DOSBase);
	return(ret);
}

int mp2iff24(char *infile, char *outfile)
{
	int ret = 10;
	struct IFFHandle *iffhandle		= NULL;
	UBYTE	*body										= NULL;
	UBYTE	*cmap										= NULL;
	struct PCHGHeader	*pchg				= NULL;
	struct BitMapHeader		*bmhd		= NULL;
	ULONG									*camg		= NULL;
	UBYTE									*anno		= NULL;
	struct StoredProperty	*sp;
	ULONG parseerr = 0;

	if( iffhandle = AllocIFF() )
	{
		/* open the DOS file */
		if( iffhandle->iff_Stream = Open( infile, MODE_OLDFILE ) )
		{
			InitIFFasDOS( iffhandle );
			if( !OpenIFF( iffhandle, IFFF_READ ) )
			{
				/* Read in everything that we want from the original source ilbm */
				PropChunk(iffhandle, ID_ILBM, ID_BMHD);
				PropChunk(iffhandle, ID_ILBM, ID_CMAP);
				PropChunk(iffhandle, ID_ILBM, ID_PCHG);
				PropChunk(iffhandle, ID_ILBM, ID_CAMG);
				PropChunk(iffhandle, ID_ILBM, ID_ANNO);
				PropChunk(iffhandle, ID_ILBM, ID_BODY);
				StopOnExit(iffhandle, ID_ILBM, ID_FORM);

				if( (parseerr = ParseIFF(iffhandle, IFFPARSE_SCAN)) == IFFERR_EOC )
				{
					if(sp = FindProp(iffhandle, ID_ILBM, ID_BMHD)) bmhd = (struct BitMapHeader *)sp->sp_Data;
					if(sp = FindProp(iffhandle, ID_ILBM, ID_CMAP)) cmap = (UBYTE *)sp->sp_Data;
					if(sp = FindProp(iffhandle, ID_ILBM, ID_PCHG)) pchg = (struct PCHGHeader *)sp->sp_Data;
					if(sp = FindProp(iffhandle, ID_ILBM, ID_CAMG)) camg = (ULONG *)sp->sp_Data;
					if(sp = FindProp(iffhandle, ID_ILBM, ID_ANNO)) anno = (UBYTE *)sp->sp_Data;
					if(sp = FindProp(iffhandle, ID_ILBM, ID_BODY)) body = (UBYTE *)sp->sp_Data;

					ret = 0;

					if(bmhd)
					{
						if(bmhd->bmh_Depth > 8)
						{
							printf("Error: Infile should be 8 bit ILBM\n");
							ret = 10;
						}
					}
					else
					{
						printf("Error: Infile missing BMHD chunk\n");
						ret = 10;
					}
					if(!cmap)
					{
						printf("Error: Infile missing CMAP chunk\n");
						ret = 10;
					}
					if(!body)
					{
						printf("Error: Infile missing BODY chunk\n");
						ret = 10;
					}
					if(!ret)
					{
						ret = WriteOutFile(bmhd, cmap, pchg, camg, anno, body, outfile);
						if(ret)
							printf("WriteOutFile Failed\n");
					}
				}
				else
					printf("Parsing Error. Code %ld\n",parseerr);

				CloseIFF( iffhandle );
			}
			else
				printf("Can't OpenIFF()\n");

			Close( iffhandle->iff_Stream );
		}
		else
			printf("Can't Open File '%s'\n",infile);

		FreeIFF( iffhandle );
	}
	else
		printf("Can't AllocIFF()\n");

	return(ret);
}

int WriteOutFile(struct BitMapHeader *bmhd, UBYTE *cmap, struct PCHGHeader *pchg,
								 ULONG *camg, UBYTE *anno, UBYTE *body, char *outfile)
{
	int ret = 10;
	struct IFFHandle *iffhandle;

	if( iffhandle = AllocIFF() )
	{
		/* open the DOS file */
		if( iffhandle->iff_Stream = Open( outfile, MODE_NEWFILE ) )
		{
			InitIFFasDOS( iffhandle );
			if( !OpenIFF( iffhandle, IFFF_WRITE ) )
			{
				if( !PushChunk( iffhandle, ID_ILBM, ID_FORM, IFFSIZE_UNKNOWN ) )
				{
					/* save out the map data */
					if( WriteBMHDChunk( iffhandle, bmhd ) )
					{
						if( !camg || (camg && WriteCAMGChunk(iffhandle, camg)) )
						{
							if( !anno || (anno && WriteANNOChunk(iffhandle, anno)) )
							{
								if( WriteBODYChunk( iffhandle, bmhd, cmap, pchg, body) )
								{
									ret = 0;
								}
							}
						}
					}
					PopChunk( iffhandle );
				}
				else
					printf("Error: PushChunk() Failed\n");
				CloseIFF( iffhandle );
			}
			else
				printf("Error: OpenIFF() Failed\n");
			Close( iffhandle->iff_Stream );
		}
		else
			printf("Error: Can't Open() Output File '%s'\n",outfile);
	}
	else
		printf("Error: AllocIFF() Failed\n");
	FreeIFF( iffhandle );

	return(ret);
}

static BOOL WriteBMHDChunk( struct IFFHandle *iffhandle, struct BitMapHeader *srcbmh)
{
	struct BitMapHeader bmh;
	BOOL success = FALSE;
	ULONG size;

	size = sizeof(bmh);

	if( !PushChunk( iffhandle, ID_ILBM, ID_BMHD, size ) )
	{
		printf("Width %ld, Height %ld, Depth %ld\n",
						srcbmh->bmh_Width, srcbmh->bmh_Height, srcbmh->bmh_Depth);

		bmh.bmh_Width = srcbmh->bmh_Width;
		bmh.bmh_Height = srcbmh->bmh_Height;
		bmh.bmh_Left = srcbmh->bmh_Left;
		bmh.bmh_Top = srcbmh->bmh_Top;
		bmh.bmh_Depth = 24;
		bmh.bmh_Masking = 0;
		bmh.bmh_Compression = cmpByteRun1;
		bmh.bmh_Flags = BMHDB_CMAPOK;
		bmh.bmh_Transparent = srcbmh->bmh_Transparent;
		bmh.bmh_XAspect = srcbmh->bmh_XAspect;
		bmh.bmh_YAspect = srcbmh->bmh_YAspect;
		bmh.bmh_PageWidth = srcbmh->bmh_PageWidth;
		bmh.bmh_PageHeight = srcbmh->bmh_PageHeight;

		if( WriteChunkBytes( iffhandle, &bmh, size ) == size )
			success = TRUE;
		else
			printf("Error: Write Error\n");
		/* tell IFFParse to clean up the ends */
		if( PopChunk( iffhandle ) )
		{
			printf("Error: Write Error\n");
			success = FALSE;
		}
	}
	else
		printf("Error: Write Error\n");
	return( success );
}

static BOOL WriteCAMGChunk( struct IFFHandle *iffhandle, ULONG *camg)
{
	BOOL success = FALSE;

	if( !PushChunk( iffhandle, ID_ILBM, ID_CAMG, 4 ) )
	{
		if( WriteChunkBytes( iffhandle, camg, 4 ) == 4 )
			success = TRUE;
		else
			printf("Error: Write Error\n");
		/* tell IFFParse to clean up the ends */
		if( PopChunk( iffhandle ) )
		{
			success = FALSE;
			printf("Error: Write Error\n");
		}
	}
	else
		printf("Error: Write Error\n");
	return( success );
}

static BOOL WriteANNOChunk( struct IFFHandle *iffhandle, UBYTE *anno)
{
	BOOL success = FALSE;
	ULONG size;

	size = strlen(anno)+1;

	if( !PushChunk( iffhandle, ID_ILBM, ID_ANNO, size ) )
	{
		if( WriteChunkBytes( iffhandle, anno, size ) == size )
			success = TRUE;
		else
			printf("Error: Write Error\n");
		/* tell IFFParse to clean up the ends */
		if( PopChunk( iffhandle ) )
		{
			printf("Error: Write Error\n");
			success = FALSE;
		}
	}
	else
		printf("Error: Write Error\n");
	return( success );
}

/**************************    WriteBODYChunk()    **************************
 * This is the main blood 'n guts routine. A general overview goes something
 * like this: 
 *  All 8 to 24 bit conversions are done on a line by line basis. When I say
 *   line by line I really mean on a line*depth basis, seeing as the planes
 *   are interleaved.
 * So we allocate a 1 line buffer for the source & destination, and another
 *  buffer to compress the destination buffer into.
 * Make any needed adjustments to the cmap, seeing as the PCHG data can
 *  start at a negative offset.
 * a) Loop Init
 * b) See if we have done the full height
 * c) Decompress line of source data.
 * d) convert it to 24 bit.
 * e) compress the 24 bit line.
 * f) write out the compressed line
 * g) increment line counter
 * h) goto (b)
 */
BOOL WriteBODYChunk(struct IFFHandle *iffhandle,
										struct BitMapHeader *srcbmh, UBYTE *srccmap, struct PCHGHeader *srcpchg, UBYTE *srcbody)
{
	BOOL abort = FALSE;
	UWORD evenbytewidth;
	BYTE		srccount;
	UBYTE		*srcbodyptr, *srcimageptr, srcrepeatbyte;
	UWORD srcplane,srcbytepos,line,plane24;
	UBYTE *srclinebase;
	UBYTE	*srclinebuffer=NULL, *dstlinebuffer=NULL, *dstpackedbuffer=NULL;
	UBYTE *dstlineptr,*dstpackedptr;
	ULONG packedbuffersize;

	evenbytewidth = ((srcbmh->bmh_Width+15)&0xFFF0)>>3;
	packedbuffersize = 	MaxPackedSize(evenbytewidth)*24;

	if(srclinebuffer = AllocVec( evenbytewidth*srcbmh->bmh_Depth, MEMF_ANY|MEMF_CLEAR))
	{
		if(dstlinebuffer = AllocVec( evenbytewidth*24, MEMF_ANY|MEMF_CLEAR))
		{
			if(dstpackedbuffer = AllocVec( packedbuffersize, MEMF_ANY|MEMF_CLEAR))
			{
				if( !srcpchg || (srcpchg && PCHG_ParsePCHG(srcpchg, srccmap)) )
				{
					if( !PushChunk( iffhandle, ID_ILBM, ID_BODY, IFFSIZE_UNKNOWN ) )
					{
						srcbodyptr = srcbody;
	
						for(line = 0; (line < srcbmh->bmh_Height) && (!abort); line++)
						{
							if(srcpchg)
								PCHGLineData = PCHG_SetCMAP(PCHGData, (void *)&PCHGData[1], PCHGLineData, srccmap, line);
							srclinebase = srclinebuffer;
							switch(srcbmh->bmh_Compression)
							{
								case cmpByteRun1:
									for(srcplane = 0; srcplane < srcbmh->bmh_Depth; srcplane++)
									{
										srcimageptr = srclinebase;
										srcbytepos = 0;
										while(srcbytepos < evenbytewidth)
										{
											srccount = *srcbodyptr++;
											if(srccount != -128)
											{
												if(srccount >= 0)
												{
													/* copy the next count+1 bytes literally */
													srcbytepos += srccount+1;
													while(srccount-- >= 0)			/* include 0 so that makes it count+1 */
													{
														*srcimageptr++ = *srcbodyptr++;
													}
												}
												else
												{
													/* repeat the next byte count+1 times */
													srcrepeatbyte = *srcbodyptr++;
													srccount *= -1;									/* make positive */
													srcbytepos += srccount+1;
													while(srccount-- >= 0)			/* include 0 so that makes it count+1 */
													{
														*srcimageptr++ = srcrepeatbyte;
													}
												}
											}
										}
										srclinebase += evenbytewidth;
									}
									break;
								case cmpNone:
									for(srcplane = 0; srcplane < srcbmh->bmh_Depth; srcplane++)
									{
										srcimageptr = srclinebase;
										for(srcbytepos = 0; srcbytepos < evenbytewidth; srcbytepos++)
										{
											*srcimageptr++ = *srcbodyptr++;
										}
										srclinebase += evenbytewidth;
									}
									break;
								default:
									printf("Unknown Compression Type\n");
									abort = TRUE;
									break;
							}		/* switch statement */
							if(!abort)
							{
								ConvertLineTo24Bit(srclinebuffer, dstlinebuffer, evenbytewidth, srcbmh->bmh_Width, srccmap, srcbmh->bmh_Depth);
								dstlineptr = dstlinebuffer;
								dstpackedptr = dstpackedbuffer;
								packedbuffersize = 0;
								for(plane24 = 0; plane24 < 24; plane24++)
									packedbuffersize += PackRow(&dstlineptr, &dstpackedptr, evenbytewidth);
								if( WriteChunkBytes( iffhandle, dstpackedbuffer, packedbuffersize ) != packedbuffersize)
								{
									abort = TRUE;
									printf("Error: Write Error\n");
								}
							}
						}		/* line loop */
						PopChunk( iffhandle );
					}
					else
						printf("Error: Can't Push BODY Chunk\n");
				}
				else
					printf("Error: Can't Parse PCHG Chunk\n");
			}
			else
				printf("Error: Can't Alloc DestPackedBuffer\n");
		}
		else
			printf("Error: Can't Alloc DestLine Buffer\n");
	}
	else
		printf("Error: Can't Alloc Source Line Buffer\n");

	if(srclinebuffer) FreeVec(srclinebuffer);
	if(dstlinebuffer) FreeVec(dstlinebuffer);
	if(dstpackedbuffer) FreeVec(dstpackedbuffer);

	return((BOOL)!abort);
}

void ConvertLineTo24Bit(UBYTE *srclinebuffer, UBYTE *dstlinebuffer,
												UWORD evenbytewidth, UWORD pixwidth, UBYTE *srccmap, UWORD srcdepth)
{
	UBYTE col,*cmapptr;
	UWORD x;

	for(x = 0; x < pixwidth; x++)
	{
		col = GetPixelColour(srclinebuffer, evenbytewidth, x, srcdepth);
		cmapptr = srccmap+(col*3);
		Put24BitValue(dstlinebuffer, evenbytewidth, x, cmapptr[0], cmapptr[1], cmapptr[2]);
	}
}

UBYTE GetPixelColour(UBYTE *buffer, UWORD bufferwidth, UWORD x, UWORD depth)
{
	UBYTE mask,byte = 0;
	UWORD plane;

	mask = 1<<(7-( x &0x0007));
	buffer += x>>3;
	plane = 0;
	buffer += bufferwidth*(depth-1);
	while(plane < depth)
	{
		if( (*buffer)&mask) byte |= 0x01;
		plane++;
		if(plane < depth) byte = byte<<1;
		buffer -= bufferwidth;
	}
	return(byte);
}
