/* :bk=0 */

/****************************************************************************/
/*																			*/
/*						Dissolve.c											*/
/*																			*/
/* Author: Lee M. Robertson													*/
/*	Based on an article in the Nov '86 Doctor Dobb's Journal 				*/
/*	by Mike Morton															*/
/****************************************************************************/

#include "fcntl.h"
#include "exec/types.h"
#include "exec/memory.h"
#include "graphics/copper.h"
#include "graphics/gfx.h"
#include "graphics/view.h"
#include "graphics/rastport.h"
#include "hardware/blit.h"
#include "hardware/custom.h"
#include "intuition/intuition.h"

/*---------------------------------------------------------------------------
 * following are definitions from the IFF specification
 *-------------------------------------------------------------------------*/
struct BitMapHeader
{
	UWORD w, h;
	WORD  x, y;
	UBYTE nPlanes;
	UBYTE masking;
	UBYTE compression;
	UBYTE pad1;
	UWORD transparentColor;
	UBYTE xAspect, yAspect;
	WORD  pageWidth, pageHeight;
};

typedef unsigned long ID;

struct Chunk
{
	ID		ckID;
	long	ckSize;
};

struct ColorRegister
{
	UBYTE red, green, blue;
};

#define CSIZE sizeof(chunk)
#define BMHDSIZE sizeof( struct BitMapHeader)

#define mskNone 0
#define mskHasMask 1
#define mskHasTransparentColor 2
#define mskLasso 3
#define cmpNone 0					/* file is not compressed */
#define cmpByteRun1 1				/* file is Run Length Encoded */

#define MakeID(a,b,c,d) ( (long)(a)<<24 | (long)(b)<<16 | (long)(c)<<8 | (d) )
#define FORM MakeID('F','O','R','M')
#define ILBM MakeID('I','L','B','M')
#define LIST MakeID('L','I','S','T')
#define BODY MakeID('B','O','D','Y')
#define BMHD MakeID('B','M','H','D')
#define CMAP MakeID('C','M','A','P')

/*---------------------------------------------------------------------------
 * Picture structure - this contains all the revalent data for a picture file
 *-------------------------------------------------------------------------*/
/* define for the # of bytes in the color register map */
#define CMSIZE 3*32

struct picture
{
	int numcolors;					/* # of valid colors */
	int fd;							/* the file # for this file */
	struct BitMap *bm;				/* a bitmap to store the picture */
	long bodysize;					/* # of bytes in the body data */
	struct BitMapHeader bmhd;
	UBYTE cmap[CMSIZE];				/* the color values for this picture */
	/* don't use the ColorRegister structure if using Aztec 'C' */
};

struct picture pic1,pic2;			/* some picture sturcts */
struct BitMap BitMap;				/* bitmap to read pictue into */
struct GfxBase *GfxBase;
struct IntuitionBase *IntuitionBase;
struct Window *Window;
struct Screen *Screen;
struct ViewPort *vp,*ViewPortAddress();

void *OpenLibrary();
void *GetMsg();
void *AllocMem();
void *OpenScreen();
void *OpenWindow();

#define INTUITION_REV 29L
#define GRAPHICS_REV 29L

struct NewWindow NewWindow = 
{
	0,							/* left edge */
	0,							/* top edge  */
	320,						/* width     */
	200,						/* heigth    */
	0,1,						/* pen's     */

	/* IDCmpFlags */
	RAWKEY | MOUSEBUTTONS | CLOSEWINDOW,

	/* flags */
	RMBTRAP | WINDOWCLOSE | BACKDROP | NOCAREREFRESH | BORDERLESS | ACTIVATE,

	0,							/* gadget list */
	0,							/* check mark */
	0,							/* title pointer */
	0,							/* screen pointer */
	0,							/* bitmap pointer */
	0,0,						/* minimum sizes */
	0,0,						/* maximum sizes */
	CUSTOMSCREEN,
};

struct NewScreen NewScreen =
{
	0,							/* left edge */
	0,							/* top edge  */
	320,						/* width     */
	200,						/* heigth    */
	5,							/* depth     */
	0,1,						/* pen's     */
	0,							/* view modes */
	CUSTOMSCREEN,				/* type      */
	0,							/* font      */
	(UBYTE *)"Screen",			/* title     */
	0,							/* gagets    */
	0,							/* bitmap    */
};


/*---------------------------------------------------------------------------
 * main()
 *-------------------------------------------------------------------------*/
main(argc,argv)
int argc;
char *argv[];
{
	char *open_pic();
	char *rd_bmap();
	char *errmsg;
	int speed;
	register int i;
	register long psize;		/* the plane size in bytes */
	register char *arg;
	register char *filename;

	if(argc < 2)
	{
		printf("Usage: Dissolve filename [ -# ]\n");
		exit(0);
	}

	for( i=1; i < argc; i++ )
	{
		argv++;
		arg = *argv;
		if( *arg == '-' )
			speed = atoi( arg + 1 );
		else
			filename = arg;
	}

	if( speed < 100 )
		speed = 1000;

	open_libs();			/* open the libraries */

	/* open the picture files */
	if( errmsg = open_pic( filename, &pic1 ))
	{
		printf("%s: %s\n",errmsg,filename );
		end( 10 );
	}
	/* setup the temp bitmap for the data */
	BitMap.BytesPerRow = (pic1.bmhd.w >> 4) << 1;
	BitMap.Rows = pic1.bmhd.h;
	BitMap.Depth = pic1.bmhd.nPlanes;

	psize = BitMap.BytesPerRow * BitMap.Rows;	/* calculate the plane size */

	if( psize == 0 || pic1.bmhd.nPlanes > 6 )
	{
		printf("Ivalid parameters: %s\n",filename);
		end( 10 );
	}

	NewScreen.Width = pic1.bmhd.w;
	NewScreen.Height = pic1.bmhd.h;
	NewScreen.Depth = pic1.bmhd.nPlanes;

	/* set bits for display modes */
	if( NewScreen.Depth == 6 && NewScreen.Width <= 320 )
		NewScreen.ViewModes |= HAM;

	if( NewScreen.Height > 200 )
		NewScreen.ViewModes |= LACE;

	if( NewScreen.Width > 320 )
		NewScreen.ViewModes |= HIRES;

	Screen = OpenScreen( &NewScreen);
	if( Screen == 0 )
	{
		printf("Can't open Screen\n");
		end(10);
	}

	NewWindow.Screen = Screen;
	NewWindow.Width = pic1.bmhd.w;
	NewWindow.Height = pic1.bmhd.h;
	Window = OpenWindow( &NewWindow );
	if( Window == 0 )
	{
		printf("Can't open Window\n");
		end( 10 );
	}
	ShowTitle( Screen, 0L );			/* hide the screen title */
	vp = ViewPortAddress( Window );		/* get the windows viewport */

	SetRast( Window->RPort, 0L );		/* clear the screen */
	setcolors( vp, &pic1.cmap[0], pic1.numcolors );

	errmsg = "No memory for BitMap";
	/* Allocate memory for plane data */
	for( i=0; i<BitMap.Depth; i++ )
	{
		if( (BitMap.Planes[i] = AllocMem( psize, MEMF_CHIP)) == 0 )
			goto nomem;
	}

	/* Now read the picture into the temp bitmap */
	errmsg = rd_bmap( &BitMap, &pic1 );

	if( errmsg == 0 )
	{
		if( (dissolve( &BitMap, Window->RPort->BitMap, speed) ) == 0 )
		{
			while( get_message() == 0 )
				WaitPort( Window->UserPort);
		}
	}

nomem:
	for( i=0; i<8; i++)
	{
		if( BitMap.Planes[i] )
			FreeMem( BitMap.Planes[i],psize );
	}
	if( errmsg )
		printf("%s: %s\n",errmsg,filename);
	end( 0 );

}

/*---------------------------------------------------------------------------
 * open_pic() -- open a picture file and initialize the picture data struct
 *-------------------------------------------------------------------------*/
char * open_pic( filename, pic )
char *filename;						/* the picture file name */
register struct picture *pic;		/* the picture data */
{
	register int fd;				/* the picture file descriptor */
	struct Chunk chunk;				/* temp for read data */
	register int cmsize;

	fd = open( filename, O_RDONLY);	 	 	 /* open the file */
	if( fd == -1)
		return "Can't open";

	if( read(fd, &chunk, CSIZE) != CSIZE )
		goto readerr;

	if( chunk.ckID != FORM)
		return "Not a picture file";

	/* check for ILBM file */
	if( read(fd, &chunk, sizeof(long) ) != sizeof(long) )
		goto readerr;
	if( chunk.ckID != ILBM )
		return "Not an ILBM file";

	if( read(fd, &chunk, CSIZE) != CSIZE )
		goto readerr;
	
	if( chunk.ckID != BMHD)
		return "bit map header expected";

	if( chunk.ckSize != BMHDSIZE )
		return "Incorrect BMHD size";

	/* read the BMHD data */
	if( read( fd, &pic->bmhd, BMHDSIZE ) != BMHDSIZE )
		goto readerr;

	pic->numcolors = 1;			/* set default color map */

	/* read chunks untill body is found */
	while( read(fd, &chunk, CSIZE) == CSIZE )
	{

		if( chunk.ckID == CMAP)
		{
			/* read the color map */

			/* get size of the color map */
			if( chunk.ckSize > CMSIZE )
				cmsize = CMSIZE;
			else
				cmsize = chunk.ckSize;

			if( read(fd, &pic->cmap[0], cmsize ) != cmsize )
				goto readerr;
			pic->numcolors = cmsize / 3;		/* save # of colors in picture */

			chunk.ckSize++;
			chunk.ckSize &= ~1;

			if( chunk.ckSize -= cmsize )
				lseek( fd, chunk.ckSize, 1 );	/* skip rest of colormap */
		}

		if( chunk.ckID == BODY )
		{
			pic->bodysize = chunk.ckSize;
			pic->fd = fd;
			return 0;					/* no errs */
		}

		/* else ignore this chunks data */
		lseek(fd,chunk.ckSize,1);
	}

readerr:
	return "Read error";
}

/*---------------------------------------------------------------------------
 * open_libs() -- open the required libraries
 *-------------------------------------------------------------------------*/
open_libs()
{
	if( !(IntuitionBase = OpenLibrary("intuition.library",INTUITION_REV)) )
	{
		printf("Can't open Intuition library\n");
		exit(5);
	}

	if( !(GfxBase = OpenLibrary("graphics.library",GRAPHICS_REV)) )
	{
		printf("Can't open Graphics library\n");
		CloseLibrary(IntuitionBase);
		exit(5);
	}
}

/*---------------------------------------------------------------------------
 * end() -- cleanup and exit
 *-------------------------------------------------------------------------*/
end( err )
int err;
{
	/* cleanup stuff */
	if( Window )
		CloseWindow( Window );
	if( Screen )
		CloseScreen( Screen );
	if( GfxBase )
		CloseLibrary(GfxBase);
	if( IntuitionBase )
		CloseLibrary(IntuitionBase);

	exit( err );
}

/*---------------------------------------------------------------------------
 * setcolors() -- set the color registers
 *-------------------------------------------------------------------------*/
setcolors( vp, creg, numcolors )
struct ViewPort *vp;
register UBYTE *creg;			/* the color values */
int numcolors;					/* the # of colors to set */
{
	register ULONG r,g,b;		/* the RGB color values (temporaries) */
	register int i;

	for(i=0; i<numcolors; i++)
	{
		r = *creg++ >> 4;
		g = *creg++ >> 4;
		b = *creg++ >> 4;
		SetRGB4( vp, (long)i, (long)r, (long)g, (long)b );
	}
}

/*---------------------------------------------------------------------------
 *
 *-------------------------------------------------------------------------*/
get_message()
{
	register ULONG class;
	register USHORT code;
	register struct IntuiMessage *Message;
	static int retval = 0;

	while( Message = GetMsg(Window->UserPort) )
	{
		class = Message->Class;
		code  = Message->Code;
		ReplyMsg(Message);

		/* allow any key press to end program */
		if( class == CLOSEWINDOW || class == RAWKEY )
			retval = 1;
		/* allow user to hold program with right mouse button */
		else if( class == MOUSEBUTTONS && code == MENUDOWN )
			WaitPort( Window->UserPort);
	}
	return retval;
}

/*---------------------------------------------------------------------------
 * rd_bmap() -- read a picture into a bitmap
 *		This routine assumes the bitmap is the correct size to recieve
 *		the picture. No error checking is done.
 *-------------------------------------------------------------------------*/
char *rd_bmap( bm, pic)
struct BitMap *bm;
struct picture *pic;
{
	register UBYTE *buffer;		/* the picture file data */
	register UBYTE *data;
	register unsigned int i;
	register unsigned long size;
	register int nplanes;		/* # of data planes */
	UBYTE *planes[8];			/* array of pointers to plane data */
	void *lmalloc();
	char *error;

	if( !( buffer = lmalloc( pic->bodysize)) )
		return "No Memory for body buffer";

	error = 0;					/* clear for return */

	size = pic->bodysize;
	data = buffer;

	/* read the picture data */
	while( size )
	{
		if( size >= 0x10000 )
			i = 0xffff;
		else
			i = size;
		if( read( pic->fd, data, i ) != i )
		{
			error = "Read error";
			goto err;
		}
		data += i;
		size -= i;
	}

	/* setup the data to pass to the decode routines */

	nplanes = pic->bmhd.nPlanes;
	if( pic->bmhd.masking == mskHasMask )
		nplanes++;				/* add 1 for the mask plane */

	for( i=0; i < 8; i++ )
		planes[i] = bm->Planes[i];			/* copy plane pointers */
	
	i = (pic->bmhd.w >> 4) << 1;			/* # of bytes per row */

	if( pic->bmhd.compression == cmpNone )
		decode0( planes,buffer,i,pic->bmhd.h,nplanes);
	else if( pic->bmhd.compression == cmpByteRun1 )
		decode1( planes,buffer,i,pic->bmhd.h,nplanes);
	else
		error = "Can't decode";

err:
	free( buffer );				/* dealloc the file data buffer */
	return error;
}

/*---------------------------------------------------------------------------
 * decode0() -- decodes the body data with no compression
 *-------------------------------------------------------------------------*/
decode0( planes,data,w,h,nplanes)
UBYTE *planes[];
register UBYTE *data;
register int w,h,nplanes;
{
	register UBYTE **pp;			/* pointer to current plane data */
	register int pcnt;			/* plane counter */

	for( ; h; h--)				/* for each row */
	{
		for( pcnt = nplanes, pp = planes; pcnt; pcnt--, pp++ )
		{
			if( *pp )		/* don't move the data if no destination plane */
			{
				movmem( data, *pp, w);
				*pp += w;
			}
			data += w;
		}
	}
}

/*---------------------------------------------------------------------------
 * decode1() -- decodes the body data with ByteRun1 compression
 *		this should really be in assembly
 *-------------------------------------------------------------------------*/
decode1( planes,data,w,h,nplanes)
UBYTE *planes[];
register BYTE *data;
int w,h,nplanes;
{
	register UBYTE **pp;		/* pointer to current plane data */
	register int pcnt;			/* plane counter */
	register int len;
	register int n;

	for( ; h; h--)				/* for each row */
	{
		for( pcnt = nplanes, pp = planes; pcnt; pcnt--, pp++ )
		{
			for(n=w; n > 0; n -= len )
			{
				len = *data++;
				if( len >= 0 )
				{
					len++;
					if( *pp )		/* move the data if destination plane */
					{
						movmem( data, *pp, len);
						*pp += len;
					}
					data += len;
				}
				else if( len != -128 ) 		/* ignore nop's */
				{
					len = -len;
					len++;
					if( *pp )		/* move the data if destination plane */
					{
						setmem( *pp, len, *data );
						*pp += len;
					}
					data++;
				}
				else
					len = 0;		/* if nop */
			}
		}
	}
}

/*---------------------------------------------------------------------------
 * some global variables for the dissolve function
 *-------------------------------------------------------------------------*/
unsigned long rand_mask;		/* the mask value for the random # generator */
unsigned long rand_val;			/* the current random # value */
unsigned long rand_tbl[] =		/* the table of random mask values */
{								/* from Nov '86 DDJ p. 48 */
	0,0,						/* first 2 values don't work */
	3,6,
	0xc,0x14,
	0x30,0x60,
	0xb8,0x110,
	0x240,0x500,
	0xca0,0x1b00,
	0x3500,0x6000,
	0xb400,0x12000,
	0x20400,0x72000,
	0x90000,0x140000,
	0x300000,0x420000,
	0xd80000,0x1200000,
	0x3880000,0x7200000,
	0x9000000,0x14000000,
	0x32800000,0x48000000,
	0xa3000000				/* for 32 bits wide */
};

/*---------------------------------------------------------------------------
 * dissolve() -- dissolve one bitmap into another
 *		this only works for full width bit maps
 *-------------------------------------------------------------------------*/
dissolve( src, dst, speed )
struct BitMap *src,*dst;
int speed;
{
	register UBYTE *mplane;			/* the dissolve mask plane */
	register long psize;				/* the size of a plane */
	register long nbits;				/* # of bits in a plane */
	register int done;

	psize = src->BytesPerRow * src->Rows;

	if( (mplane = AllocMem( psize, MEMF_CHIP | MEMF_CLEAR) ) == 0 )
		return -1;

	nbits = psize << 3;
	nbits--;				/* max bit is 1 less */

	/* initialize the random number generator */
	rand_val = 1;
	rand_mask = rand_tbl[ bitwidth(nbits) ];
	mplane[0] |= 1;			/* set the first bit in the mask plane */

	done = 0;		/* clear done flag */
	while( done == 0 )
	{
		done = setbits( mplane, nbits, speed );
		BlitBitMapMask( src, dst, mplane );
		if( get_message() )
			break;
	}

	FreeMem( mplane, psize );
	return 0;
}

/*---------------------------------------------------------------------------
 * setbits() -- set random bits in a plane
 *-------------------------------------------------------------------------*/
setbits( plane, maxbit, numbits )
UBYTE *plane;
unsigned long maxbit;
unsigned int numbits;
{
#asm
	;register useage: d5 - max bit # to set, d4 - random generator mask
	; d3 - current random value, d2 - byte offset in array for bit
	; d2 - bit # to set, d1 - loop counter
regs	reg		d2-d5

	movem.l	regs,-(a7)
	move.l	8(a5),a0		;get base address of array
	move.l	12(a5),d5		;get maxbit number
	move	16(a5),d1		;get # of bits to set
	move.l	_rand_mask,d4
	move.l	_rand_val,d3
	move.l	#0,d0			;clear ret val for not finished
	subq	#1,d1
loop
	lsr.l	#1,d3			;rand_val >>= 1;
	bhi		ok				;bif not 0 || no carry
xor
	eor.l	d4,d3			;rand_val ^= rand_mask;
ok
	cmp.l	d5,d3			;if( rand_val > maxbit )
	bls		setbit			;go set the bit if ok
	bra		loop
setloop
	lsr.l	#1,d3			;do the shift
	bls		xor				;bif need to do the exculsive or
setbit
	move.l	d3,d2			;copy to temp reg
	lsr.l	#3,d2			;make it a byte offset
	bset	d3,0(a0,d2.l)	;set the bit in the array
	dbne	d1,setloop		;end if bit is already set || # of bits done
	beq		ret
	move.l	#1,d0			;set ret val for finished
ret
	move.l	d3,_rand_val	;save new random value
	movem.l	(a7)+,regs
#endasm

}

/*---------------------------------------------------------------------------
 * bitwidth() -- get the # of bits in a long number
 *-------------------------------------------------------------------------*/
bitwidth( n )
register unsigned long n;
{
	register int w;

	w = 0;

	while( n )
	{
		w++;
		n >>= 1;
	}
	return w;
}

/*---------------------------------------------------------------------------
 *
 *-------------------------------------------------------------------------*/
