/*	ShowILBM by Russell Wallace 
	Usage: ShowILBM filename [filename]...
	or use Workbench icons to provide the program with a list of files
	or just run the program and it will ask for a filename.
	ShowILBM takes an IFF format picture in hires or lores, interlace or
	noninterlace, normal or HAM mode and displays it, waiting for a keypress
	or a click of the left mouse button before going on to display the next
	picture in its list of arguments. Pictures in either PAL or NTSC
	resolutions can be displayed, and if a PAL picture is to be displayed on
	an NTSC Amiga, ShowILBM will omit every fifth line to get all of the
	picture onto the screen. Compressed or uncompressed pictures can be
	displayed.
	Compile with 16-bit integers - I used Aztec C's precompiled INCLUDE files.
	Source and executable code for this program are in the public domain

	Version 1.1 - fixed a couple of bugs in the display and put in code
	to handle files that have a mask bitplane */

#define	mskHasMask	1		/* Possible value of BMHD Masking field */
#define	BUFLEN	64L
#define MAXSIZE	0x7FFFFFFF	/* Define maximum file size as 2^31-1 */
#define	MAX_CMAP	100		/* Biggest color map (CMAP) which we can use */

	/* Types of IFF chunk */

#define FORM	0x464F524D
#define LIST	0x4C495354
#define CAT		0x43415420
#define PROP	0x50524F50
#define	ILBM	0x494C424D
#define	BODY	0x424F4459
#define	BMHD	0x424D4844
#define	CMAP	0x434D4150

typedef	char	boolean;
typedef	unsigned long	ulong;

ulong readlong ();

extern struct WBStartup *WBenchMsg;

struct IntuiMessage *message;
struct GfxBase *GfxBase;
struct IntuitionBase *IntuitionBase;
struct FileHandle *output,*fh;
char *mem;	/* Pointer to dynamically allocated memory */
ulong ckID,ckSize;
struct Screen *screen;
struct Window *window;

struct BitMapHeader
{
	UWORD w,h;	/* raster width and height */
	WORD x,y;	/* image offset */
	UBYTE nPlanes;	/* # source bitplanes */
	UBYTE Masking;
	UBYTE Compression;
	UBYTE pad1;	/* unused */
	UWORD transparentColor;
	UBYTE xAspect,yAspect;
	WORD pageWidth,pageHeight;
} bmhd;

struct NewScreen newscreen;
struct NewWindow newwindow;

char buffer[BUFLEN];
unsigned char cmap[MAX_CMAP];

boolean close_output,ierror,in_prop;
char found_chunks;

main (argc,argv)
int argc;
char **argv;
{
	int arg;
	output=Output ();
	GfxBase=(struct GfxBase *)OpenLibrary ("graphics.library",0L);
	IntuitionBase=(struct IntuitionBase *)OpenLibrary ("intuition.library",0L);
		/* Come on, when will either of these be unavailable? */
	if (argc==0)
	{
		for (arg=1;arg<WBenchMsg->sm_NumArgs;arg++)
		{
			CurrentDir (WBenchMsg->sm_ArgList[arg].wa_Lock);
			showilbm (WBenchMsg->sm_ArgList[arg].wa_Name);
		}
		if (WBenchMsg->sm_NumArgs<2)
			ask_filename ();
		finish ();
		return;
	}
	if (argc==2 && *argv[1]=='?')
	{
		print ("ShowILBM v1.1 by Russell Wallace.\n\
This program is in the public domain.\n\
Usage: ShowILBM filename [filename]... or use icons from Workbench.\n");
		finish ();
		return;
	}
	for (arg=1;arg<argc;arg++)
		showilbm (argv[arg]);
	if (argc==1)
		ask_filename ();
	finish ();
}

print (text)
char *text;
{
	if (!output)
	{
		if (!(output=Open\
			("CON:0/0/640/200/ShowILBM v1.1 by Russell Wallace 1988",\
			MODE_NEWFILE)))
			exit_error ();
		close_output=TRUE;
	}
	Write (output,text,(long)strlen (text));
}

exit_error ()
{
	DisplayBeep (0L);
	finish ();
	exit (20);
}

finish ()
{
	if (close_output)
		Close (output);
	CloseLibrary (IntuitionBase);
	CloseLibrary (GfxBase);
}

ask_filename ()
{
	int i;
	print ("Enter name of file to display: ");
	if (Read (output,buffer,BUFLEN))
	{
		for (i=0;buffer[i];i++)
			if (buffer[i]=='\n')
				buffer[i]=0;	/* Remove \n from end of filename */
		showilbm (buffer);
	}
}

showilbm (filename)
char *filename;
{
	if (!(fh=Open (filename,MODE_OLDFILE)))
	{
		print ("ERROR - Can't open file ");
		print (filename);
		print (".\n");
		return;
	}
	do	/* Loop until found FORM ILBM and read it, then will break */
	{
		ckID=readlong ();
		ckSize=readlong ();
		if (ckID==PROP)
		{
			readlong ();	/* Ignore type */
			in_prop=TRUE;
			continue;
		}
		in_prop=FALSE;
		if (ckID==CAT || ckID==LIST)
		{
			readlong ();	/* Ignore type */
			continue;
		}
		if (ckID!=FORM)
		{
			if (ckID==BMHD && in_prop)	/* Try to get BMHD property */
			{
				Read (fh,&bmhd,20L);
				if (bmhd.Compression>1)	/* Unknown compression algorithm */
					goto READ_ERROR;
				found_chunks|=1;
				continue;
			}
			else
				if (ckID==CMAP && in_prop)	/* or CMAP property */
				{
					if (ckSize>MAX_CMAP)
						goto READ_ERROR;
					Read (fh,cmap,ckSize);
					found_chunks|=2;
					continue;
				}
				else
					forward (ckSize);	/* else just skip the chunk */
			continue;
		}
		if (readlong ()!=ILBM)
			continue;
				/* Found FORM ILBM, so read BMHD, CMAP and BODY */
		do
		{
			ckID=readlong ();
			ckSize=readlong ();
			if (ckID==BMHD)
			{
				Read (fh,&bmhd,20L);
				if (bmhd.Compression>1)	/* Unknown compression algorithm */
					goto READ_ERROR;
				found_chunks|=1;
				continue;
			}
			if (ckID==CMAP)
			{
				if (ckSize>MAX_CMAP)
					goto READ_ERROR;
				Read (fh,cmap,ckSize);
				found_chunks|=2;
				continue;
			}
			if (ckID==BODY)
			{
				if (found_chunks!=3)
					goto READ_ERROR;	/* Must have BMHD and CMAP */
				display_picture ();		/* OK, so read and display bitplanes */
				goto CLOSE_FILE;
			}
			forward (ckSize);
		}
		while (!ierror);	/* Keep going until error or found BODY */
	}
	while (!ierror);	/* If read error occurs, stop and report it */
READ_ERROR:
	print ("Sorry, I can't read file ");
	print (filename);
	print (".\n");
CLOSE_FILE:		/* Jump to here to finish and return quietly */
	Close (fh);
	in_prop=ierror=FALSE;	/* Reset for next file if any */
	found_chunks=0;
}

forward (size)
ulong size;
{
	ulong oldposition;
	size=size+(size & 1);	/* Ensure word alignment of chunks */
	oldposition=Seek (fh,size,(long)OFFSET_CURRENT);
	if ((Seek (fh,0L,(long)OFFSET_CURRENT)-oldposition)!=size)
		ierror=TRUE;
}

ulong readlong ()
{
	ulong buffer;
	if (Read (fh,&buffer,4L)!=4)
	{
		ierror=TRUE;
		buffer=0;	/* Ensure don't return garbage whatever happens */
	}
	return (buffer);
}

display_picture ()
{
	int i,j,l;
	register int k;
	long rowlen;	/* Number of bytes per row */
	long row;		/* Current row for decompressing */
	ulong red,green,blue;
	struct BitMap *bitmap;	/* Pointer to our screen's bitmap */
	char *rowstart;	/* Temporary pointer to data in BODY chunk in memory */
	register char *p;
	boolean pal2ntsc;	/* Are we displaying a PAL picture on an NTSC Amiga? */
	register char c,c1;		/* Temporary storage for BODY bytes */

	pal2ntsc=FALSE;
	if (!(mem=AllocMem (ckSize,0L)))	/* Grab memory for file workspace */
		goto NO_MEM;
	Read (fh,mem,ckSize);	/* and read it in */
	rowlen=(newscreen.Width=bmhd.w)>>3;
	newscreen.Height=bmhd.h;
	newscreen.Depth=bmhd.nPlanes;
	newscreen.Type=CUSTOMSCREEN;
	if (GfxBase->NormalDisplayRows==200)
			/* Check for displaying PAL picture on NTSC Amiga */
	{
		if (bmhd.h==256)
		{
			newscreen.Height=200;
			pal2ntsc=TRUE;
		}
		if (bmhd.h==512)
		{
			newscreen.Height=400;
			pal2ntsc=TRUE;
		}
	}
	newscreen.ViewModes=0;
	if (bmhd.h>256)
		newscreen.ViewModes=LACE;
	if (bmhd.w==640)
		newscreen.ViewModes|=HIRES;
	if (bmhd.nPlanes==6)
		newscreen.ViewModes|=HAM;
	if (!(screen=OpenScreen (&newscreen)))	/* Try to open Intuition screen */
		goto NO_SCREEN;
	newwindow.Width=newscreen.Width;
	newwindow.Height=newscreen.Height;
	newwindow.Screen=screen;
	newwindow.IDCMPFlags=MOUSEBUTTONS|RAWKEY;
	newwindow.Flags=BACKDROP|BORDERLESS|ACTIVATE;
	newwindow.Type=CUSTOMSCREEN;
	if (!(window=OpenWindow (&newwindow)))
		goto NO_WINDOW;		/* Open a window for IntuiMessages */
	ShowTitle (screen,0L);		/* Hide title bar */
	j=0;
	for (i=0;i< (1<<bmhd.nPlanes) ;i++)		/* Use color map information */
	{
		red=cmap[j++]>>4;
		green=cmap[j++]>>4;
		blue=cmap[j++]>>4;
		SetRGB4 (&screen->ViewPort,(long)i,red,green,blue);
	}
				/* Now put actual bitmap data on screen */
	p=mem;
	bitmap=screen->RastPort.BitMap;
	row=-1;
	for (i=0;i<bmhd.h;i++)	/* Do for each row */
	{
		row++;
		if (pal2ntsc)	/* Omit every fifth row if appropriate */
		{
			if (!((i+1) % 5))
				row--;
			if (row>=GfxBase->NormalDisplayRows)
				break;
		}
		for (j=0;j<bmhd.nPlanes;j++)	/* Do for each bitplane of this row */
		{
			k=0;
			rowstart=(char *)(bitmap->Planes[j])+rowlen*row;
			if (!bmhd.Compression)		/* If uncompressed ... */
				do
					rowstart[k++]=*p++;	/* copy data directly into bitmap */
				while (k<rowlen);
			else						/* otherwise, decompress it */
				do
				{
					c=*p++;			/* get a byte and depending on its value */
					if (c<0 && c!=-128)	/* either replicate following byte,
										   skip the byte (if -128), */
					{
						c1=*p++;
						for (l=0;l<-c+1;l++)
							rowstart[k++]=c1;
					}
					else			/* or just copy the next group of bytes */
						for (l=0;l<c+1;l++)
							rowstart[k++]=*p++;
				}	/* do for this row */
				while (k<rowlen);	/* While more bytes to put on this row */
		}	/* loop thru bitplanes */
		if (bmhd.Masking==mskHasMask)	/* If must ignore a mask bitplane */
		{
			if (!bmhd.Compression)
				p+=rowlen;
			else
			{
				k=0;
				do
				{
					c=*p++;
					if (c>=0)
					{
						p+=(c+1);
						k+=(c+1);
					}
					else
						if (c!=-128)
						{
							p++;
							k+=(-c+1);
						}
				}
				while (k<rowlen);
			}
		}
	}	/* loop thru rows */
	WaitPort (window->UserPort);	/* Wait for mouse click or keypress */
		/* and finish everything up */
	CloseWindow (window);
NO_WINDOW:
	CloseScreen (screen);
NO_SCREEN:
	FreeMem (mem,ckSize);
	return;
NO_MEM:
	print ("ERROR - Not enough memory!\n");
}
