/*
	dt2macpict

	Reads a picture (by the datatypes system) and 
	writes a 256 colors picture in Macintosh Pict2 format.

	Made by Erik Engdahl 1998. The code is FREEWARE.

	This program is distributed in the hope that it will be useful,
	but WITHOUT ANY WARRANTY; without even the implied warranty of
	MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.

	v1.0 Initial version, 1998-02-28
*/

#include <exec/types.h>
#include <exec/memory.h>
#include <dos/dos.h>
#include <dos/rdargs.h>
#include <graphics/gfx.h>
#include <datatypes/datatypes.h>
#include <datatypes/datatypesclass.h>
#include <datatypes/pictureclass.h>
#include <proto/exec.h>
#include <proto/dos.h>
#include <proto/graphics.h>
#include <proto/datatypes.h>
#include <string.h>
#include <stdio.h>
#include <stdlib.h>

#define HEADER_SIZE			512
#define PICT_clipRgn		0x01
#define PICT_picVersion		0x11
#define PICT_PackBitsRect	0x98
#define PICT_EndOfPicture	0xFF
#define	PICT_headerOp		0x0C00

int  openLibs(void);
void closeLibs(void);

struct Library* DosBase = NULL;
struct GfxBase *GfxBase = NULL;
struct Library *DataTypesBase = NULL;

static char *verstr="$VER: dt2macpict 1.0 by Erik Engdahl\0";

struct {
	char *from;
	char *to;
	BOOL nopack;
} args;

#define NUM_OPTS 3

#define TEMPLATE "INFILE/A,OUTFILE/A,NOPACK/S"

UWORD headdata[26] = {
	0, 0, 0, 2, 16, PICT_picVersion, 0x02ff, PICT_headerOp,
	0xffff, 0xffff, 0, 0, 0, 0, 16, 0, 
	2, 0, 0, 0, PICT_clipRgn, 10, 0, 0, 
	16, 2
};

UWORD pbdata[27] = {
	PICT_PackBitsRect, 0x8016, 0, 0, 2, 16, 0, 0, 
	0, 0, 72, 0, 72, 0, 0, 8,
	1, 8, 0, 0, 0, 0, 0, 0,
	0, 0, 0
};


void putWord(BPTR fh, UWORD data, UWORD num)
{
	UWORD i;

	for(i = 0; i < num; i++)
	{
		FWrite(fh, &data, sizeof(UWORD), 1);
	}
} /* putWord */

UWORD packrow(UBYTE *inbuf, ULONG width, UBYTE *outbuf, BOOL nopack)
{
	UWORD outlen, inpos;
	UWORD i, imax;
	UBYTE n, len;
	
	outlen = 0;
	inpos = 0;

	/* Should we pack the row? */
	if(!nopack)
	{
		while(inpos < width)
		{
			if((imax = width - inpos)> 0x7f)
			{
				imax = 0x7f;
			}

			/* Possible to pack? */
			len = 0;
			for(i = 1; (i < imax); i++)
			{
				if(inbuf[inpos] == inbuf[inpos + i])
				{
					len = i;
				}
				else break;
			}

			/* OK pack */
			if(len > 0x2)
			{
				len++;
				n = (len^255) + 2;
				outbuf[outlen++] = n;
				outbuf[outlen++] = inbuf[inpos];
				inpos += len;
			}
			/* NO packing */
			else
			{
				len = 0;
				for(i = 1; (i < imax); i++)
				{
					if(inbuf[inpos + i] != inbuf[inpos + i + 1]) 
					{
						len = i;
					}
					else 
					{
						/* if there are just 2 identical bytes breaking
							a series of different bytes then ignore it 
							ex "a b c c d a c b"
						*/
						if(inbuf[inpos + i] == inbuf[inpos + i + 2]) 
						{
							break;
						}
					}
				}
				outbuf[outlen++] = len;
				imax = (UWORD) (len + 1);
				for(i = 0; i < imax; i++)
				{
					outbuf[outlen++] = inbuf[inpos++];
				}
			}
		}
	}
	/* No packing requested, simple */
	else
	{
		while(inpos < width)
		{
			n = width - inpos;
			if(n >= 0x80) 
			{
				n = 0x7f;
			}
			outbuf[outlen++] = n;

			for(i = 0; i <= n; i++)
			{
				outbuf[outlen++] = inbuf[inpos++];
			}
		}
	}
	return outlen;
} /* packrow */

int savebody(BPTR fh, struct BitMap *bm, 
			ULONG width, ULONG height, ULONG depth)
{
	struct BitMap *tbm;
	struct RastPort __aligned trp;
	struct RastPort __aligned rp;
	UBYTE *buf, *outbuf;
	LONG count;
	ULONG row;
	UWORD rowlen;
	UBYTE shortrow;
	int res;
	
  
	buf = (UBYTE *) AllocVec(width + 0x80UL, (MEMF_CHIP | MEMF_CLEAR));
	outbuf = (UBYTE *) AllocVec(width + 0x200UL, MEMF_CLEAR);

	tbm = AllocBitMap(	width + 16UL, 1UL, depth, BMF_CLEAR, NULL);
	
	if(tbm && buf && outbuf)
	{
		InitRastPort (&trp);
		trp.BitMap = tbm;
		InitRastPort (&rp);
		rp.BitMap = bm;

		/* Source and Destination Rectangle + Mode */
		putWord(fh, 0, 2); 
		putWord(fh, (UWORD)height, 1);
		putWord(fh, (UWORD)width, 1);
		putWord(fh, 0, 2); 
		putWord(fh, (UWORD)height, 1);
		putWord(fh, (UWORD)width, 1);
		putWord(fh, 0, 1); 

		for(row = 0; row < height; row++)
		{
			count = ReadPixelLine8(&rp, 0, row, width, buf, &trp);
			rowlen = packrow(buf, count, outbuf, args.nopack);

			/* Write number of (packed) bytes in this row */
			if(width > 250)
			{
				putWord(fh, rowlen, 1);
			}
			else
			{
				shortrow = (UBYTE) rowlen;
				FWrite(fh, &shortrow, sizeof(UBYTE), 1);
			}

			/* Write the bytes */
			FWrite(fh, outbuf, sizeof(UBYTE), (ULONG)(rowlen));

		}

		/* Word align */
		count = Seek(fh, 0, OFFSET_CURRENT);
		if(count & 1)
		{
			FWrite(fh, buf, sizeof(UBYTE), 1);
		}

		res = (int) row;
	}
	else
	{
		printf("Couldn't allocate memory and/or bitmap\n");
		res = -1;
	}

	/* Free the memory */
	if(tbm)	FreeBitMap(tbm);
	if(buf)	FreeVec(buf);
	if(outbuf)	FreeVec(outbuf);
	
	return res;
} /* savebody */

/* Open the libraries and return TRUE if ok */
int openLibs(void)
{
	if((DosBase = OpenLibrary("dos.library", 39)) == NULL)
	{
		fprintf(stderr, "Sorry, couldn't open dos.library\n");
		return FALSE;
	}
	if((GfxBase = (struct GfxBase *) 
		OpenLibrary("graphics.library", 39)) == NULL)
	{
		fprintf(stderr, "Sorry, couldn't open graphics.library\n");
		return FALSE;
	}
	if((DataTypesBase = OpenLibrary("datatypes.library", 39)) == NULL)
	{
		fprintf(stderr, "Sorry, couldn't open datatypes.library\n");
		return FALSE;
	}
	return TRUE;
} /* openLibs */

/* Close libraries */
void closeLibs(void)
{
	CloseLibrary(DosBase);
	CloseLibrary((struct Library *)GfxBase);
	CloseLibrary(DataTypesBase);
} /* closeLibs */


int main(int argc, char *argv[])
{
	Object *pic;
	struct BitMapHeader *bmhd;
	struct BitMap *bm;
	struct gpLayout dtlayout;
	ULONG *cregs, tlong;
	UWORD i, numcolors;
	BPTR fh;
	int exitstatus = EXIT_SUCCESS;
	struct RDArgs *rdargs = NULL;
	LONG options[NUM_OPTS];

	if(argc == 0)
	{
		fprintf(stderr, "Must be started from Shell\n");
		return EXIT_FAILURE;
	}

	if(!openLibs())
	{
		closeLibs();
		return EXIT_FAILURE;
	}

	memset(options, 0, sizeof(options));

	if(rdargs = ReadArgs(TEMPLATE, options, NULL))
	{
		if(options[0]) args.from = (char *)options[0];
		if(options[1]) args.to = (char *)options[1];
		if(options[2]) args.nopack = TRUE;

		if(pic = NewDTObject(	args.from, 
								DTA_SourceType, DTST_FILE, 
								DTA_GroupID, GID_PICTURE, 
								PDTA_Remap, FALSE, 
								TAG_END))
		{
			/* Make sure the object is rendered */
			dtlayout.MethodID = DTM_PROCLAYOUT;
			dtlayout.gpl_GInfo = NULL;
			dtlayout.gpl_Initial = TRUE;

			if (DoDTMethodA(pic, NULL, NULL, (Msg)&dtlayout))
			{
				/* Get bitmap and colors */
				if (GetDTAttrs( pic,
								PDTA_BitMapHeader, &bmhd,
								PDTA_BitMap, &bm,
								PDTA_CRegs, &cregs,
								PDTA_NumColors, &tlong,
								TAG_DONE) == 4)
				{
					numcolors = (UWORD) tlong;

					printf("Colors: %d\n",numcolors);
					printf("Size: %d x %d\n", bmhd->bmh_Width, bmhd->bmh_Height);
					printf("Wait a while, this can take some time...\n");

					fh = Open(args.to, MODE_NEWFILE);
					if(fh)
					{
						/* Save file header */
						putWord(fh, 0, (HEADER_SIZE/2));

						/* Fill in dimensions and write picture header */
						headdata[4] = headdata[14] = headdata[24] = 
							bmhd->bmh_Width;
						headdata[3] = headdata[16] = headdata[25] = 
							bmhd->bmh_Height;

						FWrite(fh, headdata, sizeof(UWORD), 26);

						/* Save bitmap packbits header */
						pbdata[1] = bmhd->bmh_Width | 0x8000;
						pbdata[4] = bmhd->bmh_Height;
						pbdata[5] = bmhd->bmh_Width;
						FWrite(fh, pbdata, sizeof(UWORD), 27);

						/* Save colormap */
						putWord(fh, (numcolors - 1), 1);
						for(i = 0; i < numcolors; i++)
						{
							putWord(fh, i, 1);
							tlong = cregs[3 * i    ] >> 16;
							putWord(fh, (UWORD)(tlong), 1);
							tlong = cregs[3 * i + 1] >> 16;
							putWord(fh, (UWORD)(tlong), 1);
							tlong = cregs[3 * i + 2] >> 16;
							putWord(fh, (UWORD)(tlong), 1);
						}

						/* Save the bitmap */
						savebody(fh, bm, (ULONG) bmhd->bmh_Width,
									(ULONG) bmhd->bmh_Height,
									(ULONG) bmhd->bmh_Depth);

						/* Put end-mark in file */
						putWord(fh, PICT_EndOfPicture, 1);

						/* Write size (excluding empty header) */
						tlong = Seek(fh, 0, OFFSET_CURRENT);
						i = (UWORD)(tlong - HEADER_SIZE);
						Seek(fh, HEADER_SIZE, OFFSET_BEGINNING);
						putWord(fh, i, 1);

						/* Done! */
						Close(fh);
					}
					else
					{
						printf("Couldn't open file %s for writing\n", 
							args.to);
						exitstatus = EXIT_FAILURE;
					}
				}
				else 
				{
					printf("Couldn't get information about the picture\n");
					exitstatus = EXIT_FAILURE;
				}
			}
			DisposeDTObject(pic);
		}
		FreeArgs (rdargs);
		rdargs = NULL;
	}
	else {
		printf("%s\n", &verstr[6]);
		printf("Usage: %s " TEMPLATE "\n", argv[0]);
	}

	closeLibs();
	return exitstatus;
}