/****************************************************************************
*
*	LoadImage.c 23/09/89 by:	Olaf 'Olsen' Barthel
*					Brabeckstrasse 35
*					D-3000 Hannover 71
*
*					Federal Republic of Germany
*
*	LoadImage 1.9 (C) Copyright 1989 by Olaf Barthel & ED Hannover
*
*	Not  even  a  single character of this wonderful piece of code
*	may  be  incorporated  into  any  other  program  without  the
*	author's  consent.   Yep,  still a splendid time is guaranteed
*	for all!
*
*****************************************************************************
*
*	LoadImage   is  a  kind  of  IFF-ILBM-reader  with  some  very
*	remarkable  features the authors of other readers did not even
*	dream of:
*
*	× LoadImage loads anything that looks, smells and tastes like
*	  an IFF-ILBM-file, even EHB & HAM are handled correctly.
*
*	× LoadImage will accept overscanned pictures which require
*	  1MByte of chip ram.
*
*	× You can scroll around in the bitmap if the picture is larger
*	  in size than the current display.
*
*	× LoadImage takes care of the machine it is running on, i.e.
*	  PAL images will remain PAL images on a PAL Amiga and will
*	  be transformed into NTSC-images on an NTSC Amiga.
*
*	× LoadImage supports colour-cycling (interrupt code, no sick
*	  subtask cycling).
*
*	× LoadImage can display multiple images, i.e. you can enter more
*	  than one file in the command line. If you are a bit lazy you
*	  are hereby granted permission to use UNIX style wildcards
*	  instead of a list of filenames.
*
*	× LoadImage also works from workbench, simply hold down the
*	  shift-key and select the icons of the images you wish to
*	  load, then double-click the LoadImage-icon.
*
*	× LoadImage includes graphicdump functions. Select the area
*	  you wish to print and off it goes!
*
*	LoadImage  itself  was  written over a long period of time.  I
*	started  this project when I suddenly came upon an idea how to
*	resize  IFF-ILBM-images.   After  a  week  of thought (back in
*	winter  1988)  I  rewrote the decompression routines and added
*	the LoadHeader() function.  I continued to work on the program
*	in  late  spring  1989,  sped up the bitmap reader and thought
*	about  a  colour-cycling  routine.   At last the scrolling was
*	added (with some help from John Hodgson) to support those very
*	l  a  r  g  e  pictures generated by various DTP-scanners.  In
*	version  1.5  I  rewrote  the  chunk  reading routine to speed
*	things  up.  So you won't have to shave your beard off anymore
*	when  the  picture  finally appears on the screen.  In version
*	1.6 soft colour fading was added, a call to SetTaskPri() tries
*	to make the bitmap scrolling a bit smoother.
*
*	Some  routines  included  in this program are part of a larger
*	collection of routines which I called ImageTools (please don't
*	mix   them   up  with  the  ImageTools  utilities  by  Stephen
*	Vermeulen).   So,  please  don't  be too greedy, some stuff is
*	copyrighted and almost sold to a german publisher.
*
*	Anyway, have fun with the code.  Even though I did not use the
*	original  EA  IFF  85  routines ("Why stop now when I'm hating
*	it?")  the  reader  works quite well and is a bit too tolerant
*	for the IFF-restrictions Commodore-Amiga, Inc.  demands.
*
*								Olsen
*
****************************************************************************/

	/* End of header, includes start here. */

#include <intuition/intuitionbase.h>
#include <libraries/dosextens.h>
#include <workbench/startup.h>
#include <graphics/gfxbase.h>
#include <devices/printer.h>
#include <exec/memory.h>
#include <stdio.h>

	/* Some useful macros. */

#define byte(n) (((n + 15) >> 4) << 1)	/* Word aligned width in bytes. */

	/* The real BitMapHeader... "I ain't 'fraid of no pic!" */

typedef struct
{
	UWORD w,h;		/* Raster width & height. */
	UWORD x,y;		/* Pixel position. */
	UBYTE nPlanes;		/* Number of source bitplanes. */
	UBYTE masking;		/* Masking... good for nothing maybe? */
	UBYTE compression;	/* Packed or unpacked? */
	UBYTE pad1;		/* We don't like odd length structures. */
	UWORD transparentColor;	/* Maybe good for... */
	UBYTE xAspect, yAspect;	/* Kind of quotient, width / height. */
	WORD pageWidth, pageHeight;/* Source page size. */
} BitMapHeader;

	/* This is a full featured DPaint ColourRange chunk! */

typedef struct
{
	WORD pad1;		/* Odd length? Nope. */
	WORD rate;		/* Speed! */
	WORD active;		/* What shall we do with a drunken... */
	UBYTE low,high;		/* That's where we start and stop. */
} CRange;

typedef struct
{
	LONG	IFF_Type;
	LONG	IFF_Length;
} IFF_Chunk;

	/* The cycling code needs this to be defined externally. */

CRange CycleRange[6];

	/* Some forward declarations. */

extern void 		*AllocMem();
extern struct Library 	*OpenLibrary();
extern struct Window 	*OpenWindow();
extern struct Screen 	*OpenScreen();
extern struct MenuItem	*ItemAddress();
extern struct Message 	*GetMsg();
extern struct MsgPort	*CreatePort();
extern struct Task	*FindTask();
extern PLANEPTR		 AllocRaster();
extern struct IORequest	*CreateExtIO();

extern char		*malloc();
extern char		*scdir();

	/* Some more fontdata for the AutoRequest soon to come. */

struct TextAttr AboutFont[3] =
{
	{(UBYTE *)"topaz.font",8,FS_NORMAL ,FPF_ROMFONT},
	{(UBYTE *)"topaz.font",8,FSF_BOLD  ,FPF_ROMFONT},
	{(UBYTE *)"topaz.font",8,FSF_ITALIC,FPF_ROMFONT}
};

	/* The menu text is to follow soon. */

struct IntuiText MenuIntTxt[9] =
{
	{0,1,JAM2,2,1,&AboutFont[0],(UBYTE *)"About LoadImage...      ",NULL},
	{0,1,JAM1,2,1,&AboutFont[1],(UBYTE *)"________________________",NULL},
	{0,1,JAM2,2,1,&AboutFont[0],(UBYTE *)"Title Bar               ",NULL},
	{0,1,JAM2,2,1,&AboutFont[0],(UBYTE *)"Cycling                 ",NULL},
	{0,1,JAM2,2,1,&AboutFont[0],(UBYTE *)"Print Standard          ",NULL},
	{0,1,JAM2,2,1,&AboutFont[0],(UBYTE *)"      Enlarged          ",NULL},
	{0,1,JAM1,2,1,&AboutFont[1],(UBYTE *)"________________________",NULL},
	{0,1,JAM2,2,1,&AboutFont[0],(UBYTE *)"Next Image              ",NULL},
	{0,1,JAM2,2,1,&AboutFont[0],(UBYTE *)"Quit LoadImage          ",NULL}
};

	/* Now transform it into a chain of MenuItems. */

struct MenuItem MenuItem[9] =
{
	{&MenuItem[1],4, 0,196,10, 86,0,(APTR)&MenuIntTxt[0],NULL,'?',NULL,NULL},
	{&MenuItem[2],4, 3,196,10,210,0,(APTR)&MenuIntTxt[1],NULL,  0,NULL,NULL},
	{&MenuItem[3],4,13,196,10, 86,0,(APTR)&MenuIntTxt[2],NULL,'T',NULL,NULL},
	{&MenuItem[4],4,23,196,10, 86,0,(APTR)&MenuIntTxt[3],NULL,'C',NULL,NULL},
	{&MenuItem[5],4,33,196,10, 86,0,(APTR)&MenuIntTxt[4],NULL,'S',NULL,NULL},
	{&MenuItem[6],4,43,196,10, 86,0,(APTR)&MenuIntTxt[5],NULL,'E',NULL,NULL},
	{&MenuItem[7],4,46,196,10,210,0,(APTR)&MenuIntTxt[6],NULL,  0,NULL,NULL},
	{&MenuItem[8],4,56,196,10, 86,0,(APTR)&MenuIntTxt[7],NULL,'N',NULL,NULL},
	{NULL,        4,66,196,10, 86,0,(APTR)&MenuIntTxt[8],NULL,'Q',NULL,NULL}
};

	/* "This is what you need!" ... a menu definition! */

struct Menu Menu = { NULL,0,0,112,0,257,"LoadImage 1.9",&MenuItem[0] };

	/* "Don't wait for me, I'm still typing." */

struct IntuiText AboutTxt[15] =
{
	{3,1,JAM1,8,  4,&AboutFont[1],(UBYTE *)"LoadImage v1.9",		&AboutTxt[ 1]},

	{2,1,JAM1,8, 16,&AboutFont[0],(UBYTE *)"Was  written  by Olaf 'Olsen'",	&AboutTxt[ 2]},
	{2,1,JAM1,8, 24,&AboutFont[0],(UBYTE *)"Barthel   of   ED  Electronic",	&AboutTxt[ 3]},
	{2,1,JAM1,8, 32,&AboutFont[0],(UBYTE *)"Design     Hannover.     This",	&AboutTxt[ 4]},
	{2,1,JAM1,8, 40,&AboutFont[0],(UBYTE *)"version  supports  EHB  & HAM",	&AboutTxt[ 5]},
	{2,1,JAM1,8, 48,&AboutFont[0],(UBYTE *)"pictures and  will also  take",	&AboutTxt[ 6]},
	{2,1,JAM1,8, 56,&AboutFont[0],(UBYTE *)"care  of  oversized  pictures",	&AboutTxt[ 7]},
	{2,1,JAM1,8, 64,&AboutFont[0],(UBYTE *)"which require 1M of chip ram.",	&AboutTxt[ 8]},

	{2,1,JAM1,8, 76,&AboutFont[0],(UBYTE *)"Press  the  left mouse button",	&AboutTxt[ 9]},
	{2,1,JAM1,8, 84,&AboutFont[0],(UBYTE *)"and move the pointer  towards",	&AboutTxt[10]},
	{2,1,JAM1,8, 92,&AboutFont[0],(UBYTE *)"the borders of the  screen to",	&AboutTxt[11]},
	{2,1,JAM1,8,100,&AboutFont[0],(UBYTE *)"scroll around in  overscanned",	&AboutTxt[12]},
	{2,1,JAM1,8,108,&AboutFont[0],(UBYTE *)"images.",			&AboutTxt[13]},

	{0,1,JAM1,8,122,&AboutFont[2],(UBYTE *)"(C) Copyright 1989 by...",	&AboutTxt[14]},
	{0,1,JAM1,8,130,&AboutFont[2],(UBYTE *)"ED Electronic Design Hannover",	NULL}
};

	/* We've got a body text, let's see if there's a negative text
	 * around.
	 */

struct IntuiText Proceed =
{
	0,1,JAM1,5,3,
	&AboutFont[0],
	(UBYTE *)"Understood",
	NULL
};

	/* Yes... we will open a CUSTOMBITMAP screen. */

struct BitMap ScreenMap;

	/* If the picture is smaller than the screen. */

struct BitMap TinyBitMap;

	/* Must be defined externally since three routines need it.
	 * There could have been an easier way to do this, but it was
	 * me who programmed it and not a "reentrant-code-fanatic".
	 */

BitMapHeader InfoHeader;

	/* Wait and see what this will be! */

struct NewScreen NewScreen =
{
	0,0,0,0,0,				/* Gets filled in later. */
	0,1,					/* Pens. */
	NULL,					/* ViewModes, get filled in later. */
	CUSTOMSCREEN | SCREENBEHIND | CUSTOMBITMAP,
	&AboutFont[0],				/* Just insure that the menu looks cute. */
	(STRPTR)"LoadImage 1.9",		/* Some title text. */
	NULL,					/* No gagdets. */
	&ScreenMap				/* Custom bitmap! */
};

	/* What do we need this window for? We've got a menu and
	 * screens don't like menus very much...
	 */

struct NewWindow NewWindow =
{
	0,0,					/* These get filled in later. */
	0,0,
	0,1,					/* Pens. */
	MOUSEBUTTONS | MENUPICK | MENUVERIFY | MOUSEMOVE | RAWKEY,	/* We want to know about this. */
	BACKDROP | BORDERLESS | RMBTRAP,	/* This is how it should look like. */
	(struct Gadget *)NULL,			/* Don't need a gadget. */
	(struct Image *)NULL,			/* Don't need a checkmark either. */
	(STRPTR)NULL,				/* Don't need a title. */
	(struct Screen *)NULL,			/* This gets filled in later. */
	(struct BitMap *)NULL,			/* Don't need it, really. */
	0,0,					/* Minimum dimensions. */
	0,0,					/* Maximum dimensions. */
	CUSTOMSCREEN				/* No Workbench, this is MY window. */
};

	/* External symbols the linker wants. */

struct IntuitionBase	*IntuitionBase;
struct GfxBase		*GfxBase;
struct Screen		*Screen;
struct Window		*Window;
struct IntuiMessage	*Massage;

	/* Additional window message data. */

ULONG Class;
USHORT Code;

	/* Macro expressions to set the pointer for our window. */

#define SetPoint(wind)	SetPointer(wind,PointData,7,16,-4,-3)
#define SetSnooze(wind)	SetPointer(wind,Snooze,22,16,-9,-13)
#define SetSize(wind)	SetPointer(wind,SizeData,13,16,-4,-3)

	/* Sprite dump for the pointer image. */

USHORT PointData[] =
{
	0x0000,0x0000,

	0x0000,0x1000,
	0x1000,0x0000,
	0x1000,0x1000,
	0x6C00,0xAA00,
	0x1000,0x1000,
	0x1000,0x0000,
	0x0000,0x1000,

	0x0000,0x0000
};

	/* Sprite dump for the Workbench balloon. */

USHORT Snooze[] =
{
	0x0000,0x0000,

	0x0700,0x0000,
	0x0FA0,0x0700,
	0x3FF0,0x0FA0,
	0x70F8,0x3FF0,
	0x7DFC,0x3FF8,
	0xFBFC,0x7FF8,
	0x70FC,0x3FF8,
	0x7FFE,0x3FFC,
	0x7F0E,0x3FFC,
	0x3FDF,0x1FFE,
	0x7FBE,0x3FFC,
	0x3F0E,0x1FFC,
	0x1FFC,0x07F8,
	0x07F8,0x01E0,
	0x01E0,0x0080,
	0x07C0,0x0340,
	0x0FE0,0x07C0,
	0x0740,0x0200,
	0x0000,0x0000,
	0x0070,0x0020,
	0x0078,0x0038,
	0x0038,0x0010,

	0x0000,0x0000
};

USHORT SizeData[] =
{
	0x0000,0x0000,

	0x0000,0x1000,
	0x1000,0x0000,
	0x1000,0x1000,
	0x6C00,0xAA00,
	0x1000,0x1000,
	0x1000,0x0000,
	0x0000,0x1000,
	0x0000,0x0000,
	0x0D77,0x0D77,
	0x1114,0x1114,
	0x1D27,0x1D27,
	0x0544,0x0544,
	0x1977,0x1977,

	0x0000,0x0000
};

	/* This is where the colours go when the About item is
	 * selected.
	 */

UWORD			PrefColours[32];
struct Preferences	StandardPrefs;

	/* These little "quickies" disable the CTRL-C abort function. */

#define BreakCheck() (SetSignal(0,0) & SIGBREAKF_CTRL_C)
#define BreakReset() SetSignal(0,SIGBREAKF_CTRL_C)

	/* Chk_Abort() :
	 *
	 *	The system calls this during I/O. Returns 0 to tell
	 *	it that there is no CTRL-C around.
	 */

Chk_Abort() { return(0); }

	/* _wb_parse(pp,wbm) :
	 *
	 *	Modified Workbench tool parsing routine. This one
	 *	always opens a standard output window if the program
	 *	has been started from Workbench. Original version
	 *	Copyright (C) 1986,1987 by Manx Software Systems, Inc.
	 */

void
_wb_parse(pp,wbm)
register struct Process *pp;
struct WBStartup *wbm;
{
	register struct FileHandle *FileHandlePtr;	/* BCPL window pointer. */
	register LONG FileWindow;			/* DOS window pointer. */
	static char WindowTitle[40 + 18];		/* Title buffer. */
	register LONG i,j;				/* Counters. */

		/* Set up the window title. */

	strcpy(WindowTitle,"CON:0/11/640/80/");

	i = j = 0;

		/* Find the end of the window title string. */

	while(WindowTitle[i])
		i++;

		/* Append the tool name. */

	while((WindowTitle[i++] = wbm -> sm_ArgList -> wa_Name[j++]) && j != 40);

		/* Window title null termination. */

	WindowTitle[40 + 17] = 0;       

		/* Open the window. */

	if(FileWindow = (LONG)Open(WindowTitle, MODE_OLDFILE)) 
	{
			/* Convert window pointer to BPTR. */

		FileHandlePtr = (struct FileHandle *)(FileWindow << 2);

		pp -> pr_ConsoleTask	= (APTR)FileHandlePtr -> fh_Type;

			/* Set up console IO streams. */

		pp -> pr_CIS		= (BPTR)FileWindow;
		pp -> pr_COS		= (BPTR)Open("*", MODE_OLDFILE);
	}
}

	/* MakeID(IDString) :
	 *
	 *	Transforms a string into a chunk ID. How can we do it
	 *	in a macro expression? Just don't know.
	 */

LONG
MakeID(IDString)
char *IDString;
{
	LONG LongID;

	LongID = (IDString[0] << 24) | (IDString[1] << 16) | (IDString[2] << 8) | (IDString[3]);

	return(LongID);
}

	/* FindChunk(ChunkName,FilePointer) :
	 *
	 *	Will try to find a chunk ID somewhere in the
	 *	file. If it doesn't find any it returns FALSE.
	 *	This routine was somewhat inspired by the
	 *	Chunk-reader to be found on the disk distributed
	 *	along with the FutureSound sound sampling hardware.
	 *	Some months later I through the 'crap' out. Instead
	 *	of years this will take you only some moments to
	 *	load now.
	 */

BOOL
FindChunk(ChunkName,FilePointer)
register char *ChunkName;
register FILE *FilePointer;
{
	IFF_Chunk Chunk;
	LONG OldPosition;
	LONG FormType,WeWant;

		/* Set up the chunk type. */

	WeWant = MakeID(ChunkName);

		/* Remember the initial file position. */

	OldPosition = ftell(FilePointer);

		/* Reset the form type. */

	FormType = 0;

	for(;;)
	{
			/* Try to read the chunk. */

		if(fread(&Chunk,sizeof(Chunk),1,FilePointer) != 1)
		{
				/* If it went wrong, reset the
				 * file position.
				 */

			fseek(FilePointer,OldPosition,0);
			return(FALSE);
		}

			/* If this is supposed to be a FORM chunk,
			 * try to figure out the form type.
			 */

		if(OldPosition == 0 && FormType == 0 && Chunk . IFF_Type == MakeID("FORM"))
		{
			fread(&FormType,sizeof(LONG),1,FilePointer);

				/* Is it the type we want? */

			if(FormType == WeWant)
				return(TRUE);

			continue;
		}

			/* Is this what we want? */

		if(Chunk . IFF_Type == WeWant)
			return(TRUE);

			/* Else, skip the length information. */

		fseek(FilePointer,Chunk . IFF_Length,1);
	}
}

	/* LoadHeader(FileName,BMHeader) :
	 *
	 *	Does two jobs for us: Initializes the BitMapHeader
	 *	and tries to figure out the Amiga ViewModes this
	 *	file needs. Returns -1 on failure, so be careful.
	 */

LONG
LoadHeader(FileName,BMHeader)
char *FileName;
BitMapHeader *BMHeader;
{
	register LONG i;
	FILE *ImgFile;
	LONG ViewModes = NULL;

		/* No such file? */

	if(!(ImgFile = fopen(FileName,"r")))
		return(-1);

		/* No BMHD-Chunk? */

	if(!FindChunk("BMHD",ImgFile))
	{
		fclose(ImgFile);
		return(-1);
	}

		/* Read the header. */

	fread(BMHeader,sizeof(BitMapHeader),1,ImgFile);

		/* Strange values, probably not a picture but a
		 * "mistake" or a CMAP.
		 */

	if(BMHeader -> nPlanes < 1 || BMHeader -> nPlanes > 8)
	{
		fclose(ImgFile);
		return(-1);
	}

		/* If we don't find a CAMG chunk in the file
		 * we will have to guess the right
		 * ViewModes. This line takes care of the
		 * interlaced display mode.
		 */

	if(BMHeader -> pageHeight > GfxBase -> NormalDisplayRows)
		ViewModes |= LACE;

		/* Could it be HIRES? */

	if(BMHeader -> pageWidth >= 640)
		ViewModes |= HIRES;

		/* It is still much more likely to encounter a
		 * HAM picture than an EHB picture. If we are
		 * wrong with this assumption, the CAMG chunk
		 * will tell us (hope so).
		 */

	if(BMHeader -> nPlanes == 6)
		ViewModes |= HAM;

		/* Hello out there, got any CAMG chunk? */

	if(!FindChunk("CAMG",ImgFile))
	{
		fclose(ImgFile);
		return(ViewModes);
	}

		/* Read it then. */

	fread(&ViewModes,sizeof(LONG),1,ImgFile);

		/* Mask out all unwanted bits (thanks, Carolyn!). */

	ViewModes &= (~(SPRITES | VP_HIDE | GENLOCK_AUDIO | GENLOCK_VIDEO) | 0xFFFF);

		/* Finish it. */

	fclose(ImgFile);

	return(ViewModes);
}

	/* LoadCMAP(FileName,ColourMap,MaxCol,BMHeader) :
	 *
	 *	Will load an unsigned word array with the
	 *	colours to be found in the CMAP chunk.
	 */

LONG
LoadCMAP(FileName,ColourMap,MaxCol,BMHeader)
char *FileName;
UWORD ColourMap[];
LONG MaxCol;
BitMapHeader *BMHeader;
{
	register LONG i;
	register FILE *ColFile;
	register LONG R,G,B;

		/* Are you there? */

	if(!(ColFile = fopen(FileName,"r")))
		return(0);

		/* Black 'n white or colour TV? */

	if(!FindChunk("CMAP",ColFile))
	{
		fclose(ColFile);
		return(0);
	}

		/* Correct it before the reader believes it! */

	if(MaxCol < 2)
		MaxCol = 1 << BMHeader -> nPlanes;

		/* A bit too large, innit? */

	if(MaxCol > 32)
		MaxCol = 32;

		/* Read those colours. */

	for(i = 0 ; i < MaxCol ; i++)
	{
		R = fgetc(ColFile) >> 4;
		G = fgetc(ColFile) >> 4;
		B = fgetc(ColFile) >> 4;

			/* The transformation. */

		ColourMap[i] = (R << 8) | (G << 4) | (B);
	}

		/* Finish it. */

	fclose(ColFile);

	return(MaxCol);
}

	/* LoadRaster(FileName,BitPlanes,BMHeader) :
	 *
	 *	Will decompress the interleaved bitmap data
	 *	into a set of bitplanes.
	 */

BOOL
LoadRaster(FileName,BitPlanes,BMHeader)
char *FileName;
PLANEPTR BitPlanes[];
BitMapHeader *BMHeader;
{
	register LONG i,j,k;
	UBYTE Value,SoFar,Compr,Depth;
	BYTE ChkVal;
	LONG Height,Width;
	register PLANEPTR Planes[9];	/* 9 for possible bitmask. */
	FILE *PicFile;

		/* Clear the planes. */

	for(i = 0 ; i < 9 ; i++)
		Planes[i] = NULL;

		/* Set up the working copies. */

	Width	= byte(BMHeader -> w);
	Height	= BMHeader -> h;
	Depth	= BMHeader -> nPlanes;
	Compr	= BMHeader -> compression;

		/* Is there something wrong in paradise? */

	if(Compr > 1 || !BitPlanes)
		return(FALSE);

		/* Can we read it, please? */

	if(!(PicFile = fopen(FileName,"r")))
		return(FALSE);

		/* No BODY? What is this? */

	if(!FindChunk("BODY",PicFile))
	{
		fclose(PicFile);
		return(FALSE);
	}

		/* Copy the bitmap pointers since their
		 * contents will get changed.
		 */

	for(i = 0 ; i < Depth ; i++)
		Planes[i] = BitPlanes[i];

		/* Very well, nobody told me that DPaint and Aegis Images
		 * are allowed to save their own home-brewn BODY chunks
		 * if the transparent colour is nonzero or the
		 * stencil/behind function is used. In this case the
		 * interleaved plane data is immediately followed by
		 * a bitmask which is to clear out all unwanted pixels
		 * after the image is drawn. To support this attitude
		 * we increment the depth of the image to give the
		 * reader access to a blank pointer the bitmask will
		 * be sent to.
		 */

	if(BMHeader -> masking == 1)
		Depth++;

		/* No compression, take the data as is. */

	if(Compr == 0)
	{
		for(k = 0 ; k < Height ; k++)
		{
			for(j = 0 ; j < Depth ; j++)
			{
				if(Planes[j])
				{
					fread(Planes[j],Width,1,PicFile);
					Planes[j] += Width;
				}
				else
					fseek(PicFile,Width,1);
			}
		}
	}

		/* ByteRun1 compression, efficient but tricky. */

	if(Compr == 1)
	{
		for(k = 0 ; k < Height ; k++)
		{
			for(j = 0 ; j < Depth ; j++)
			{
				for(SoFar = 0 ; SoFar < Width ; )
				{
					ChkVal = fgetc(PicFile);

						/* Read the next bytes. */

					if(ChkVal > 0)
					{
						if(Planes[j])
						{
							fread(Planes[j],ChkVal + 1,1,PicFile);

							Planes[j] += ChkVal + 1;
						}
						else
							fseek(PicFile,ChkVal + 1,1);

						SoFar += ChkVal + 1;
					}
					else
					{
							/* Set the memory to this
							 * value.
							 */

						if(ChkVal != 128)
						{
							Value = fgetc(PicFile);

							for(i = 0 ; i <= -ChkVal ; i++)
							{
								if(Planes[j])
								{
									*Planes[j] = Value;
									Planes[j]++;
								}

								SoFar++;
							}
						}
					}
				}
			}
		}
	}

		/* Finish it up. */

	fclose(PicFile);

	return(TRUE);
}

	/* InitTinyBitMap() :
	 *
	 *	If the picture to be loaded is smaller than the
	 *	screen it is to appear on, a custom bitmap is
	 *	initialized to receive the image data.
	 */

BOOL
InitTinyBitMap()
{
	register short i;

		/* So the Blitter won't be confused. */

	InitBitMap(&TinyBitMap,InfoHeader . nPlanes,InfoHeader . w,InfoHeader . h);

		/* Try to steal some memory for the tiny bitmap. */

	for(i = 0 ; i < InfoHeader . nPlanes ; i++)
		if(!(TinyBitMap . Planes[i] = (PLANEPTR)AllocRaster(InfoHeader . w,InfoHeader . h)))
			return(FALSE);

	InitBitMap(&ScreenMap,InfoHeader . nPlanes,NewScreen . Width,NewScreen . Height);

		/* Try to steal some memory for the bitplanes. */

	for(i = 0 ; i < InfoHeader . nPlanes ; i++)
		if(!(ScreenMap . Planes[i] = (PLANEPTR)AllocRaster(NewScreen . Width,NewScreen . Height)))
			return(FALSE);

	return(TRUE);
}

	/* CloseLibs() :
	 *
	 *	Closes the libraries and exits.
	 */

void
CloseLibs()
{
		/* Libraries? Who wanted the libraries? */

	if(GfxBase);
		CloseLibrary(GfxBase);

	if(IntuitionBase)
		CloseLibrary(IntuitionBase);

		/* Take care of external signals. */

	BreakReset();

		/* Return the control to DOS. See what it's doing with
		 * it.
		 */

	exit(0);
}

	/* CloseAll() :
	 *
	 *	Closes anything that we have opened but the
	 *	libraries.
	 */

void
CloseAll()
{
	register short i;

		/* Canst thou hear me? Finish what you have started. */

	ClearCycleCode();

		/* A window! */

	if(Window)
	{
			/* Hide the menu. */

		Window -> Flags |= RMBTRAP;

			/* We don't need the menu any more. */

		ClearMenuStrip(Window);

		CloseWindow(Window);

		Window = NULL;
	}

		/* Is there a screen anywhere? */

	if(Screen)
	{
		CloseScreen(Screen);
		Screen = NULL;
	}

		/* Get rid of the planes. */

	for(i = 0 ; i < ScreenMap . Depth ; i++)
	{
		if(TinyBitMap . Planes[i])
		{
			FreeRaster(TinyBitMap . Planes[i],InfoHeader . w,InfoHeader . h);
			TinyBitMap . Planes[i] = NULL;

			if(ScreenMap . Planes[i])
			{
				FreeRaster(ScreenMap . Planes[i],NewScreen . Width,NewScreen . Height);
				ScreenMap . Planes[i] = NULL;
			}

			continue;
		}

		if(ScreenMap . Planes[i])
		{
			FreeRaster(ScreenMap . Planes[i],InfoHeader . w,InfoHeader . h);
			ScreenMap . Planes[i] = NULL;
		}
	}
}

	/* GetPath(InitLock) :
	 *
	 *	Fills the PathString with the path name associated
	 *	with InitLock. This code fragment is based on the
	 *	do_cd() function in Matt Dillon's shell. I shortened
	 *	the code a bit, since we don't need to keep the
	 *	pathstring in the current pr_CLI -> cli_SetName.
	 *
	 *	Or do we?
	 */

char *
GetPath(InitLock)
struct FileLock *InitLock;
{
	struct FileLock		*TempLock;	/* "copy of Lock" */
	struct FileInfoBlock	*FileInfo;	/* We need the fib_FileName */

	register LONG		count,Length;	/* Loop counter, string length */
	register char		*FileName;	/* Temporary pointer */

	static char		PathString[256];/* Buffer to receive full
						 * path name. */
		/* Forward declarations. */

	struct FileLock 	*DupLock(),*ParentDir();

		/* Longword alignment... */

	if(!(FileInfo = (struct FileInfoBlock *)AllocMem(sizeof(struct FileInfoBlock),MEMF_PUBLIC | MEMF_CLEAR)))
		return(NULL);

		/* Attempt to duplicate initial lock; I don't expect
		 * this to go wrong, but let's be careful, you can
		 * never know what Commodore's doing with Tripos...
		 */

	if(!(TempLock = (struct FileLock *)DupLock(InitLock)))
	{
		FreeMem(FileInfo,sizeof(struct FileInfoBlock));
		return(NULL);
	}

	count = 255;

		/* As long as a parent directory can be found... */
      
	while(TempLock)
	{
			/* To avoid crashes we take a closer look at
			 * the DirEntryType; maybe we will try to
			 * get the parent directory to a file?
			 */

		Examine(TempLock,FileInfo);

		if(FileInfo -> fib_DirEntryType < 0)
		{
			UnLock(TempLock);
			FreeMem(FileInfo,sizeof(struct FileInfoBlock));

			return(NULL);
		}

			/* Take a look at the parent directory. */

		InitLock = (struct FileLock *)ParentDir(TempLock);

		FileName = FileInfo -> fib_FileName;

			/* Null string? Probably bug in 1.1/1.2
			 * Ram-Handler (should be fixed in 1.3, beta
			 * version crashes, though).
			 */

		if(!FileName[0])
			FileName = "RAM";

		Length = strlen(FileName);

			/* Parent directory available?. */

		if(InitLock)
		{
				/* Append name of parent directory. */

			if(count == 255)
			{
				count -= Length;
				CopyMem(FileName,PathString + count,Length);
			}
			else
			{
				count -= Length + 1;
				CopyMem(FileName,PathString + count,Length);
				PathString[count + Length] = '/';
			}
		}
		else
		{
			count -= Length + 1;
			CopyMem(FileName,PathString + count,Length);
			PathString[count + Length] = ':';
		}

			/* Continue with the next directory. */

		UnLock(TempLock);
		TempLock = InitLock;
	}

		/* Clean the things up... */

	FreeMem(FileInfo,sizeof(struct FileInfoBlock));
	CopyMem(PathString + count,PathString,256 - count);

		/* Let's be safe, append a slash. */

	if(PathString[strlen(PathString) - 1] != '/' && PathString[strlen(PathString) - 1] != ':')
		PathString[strlen(PathString)] = '/';

		/* Return a pointer to the path string. */

	return(PathString);
}

	/* QuickSort(av,n) :
	 *
	 *	Quicksort routine borrowed from Steve Drew's revision
	 *	of Matt Dillon's Shell. Please don't ask me how it
	 *	works, I forgot it some moments after my Pascal teacher
	 *	had explained it to me in detail.
	 */

QuickSort(av,n)
register char *av[];
register LONG n;
{
	register LONG b;

	if(n > 0)
	{
		b = QSplit(av,n);

		QuickSort(av,b);
		QuickSort(av + b + 1,n - b - 1);
	}
}

	/* QSplit(av,n) :
	 *
	 *	Quicksort subroutine to save stack space.
	 */

QSplit(av,n)
register char *av[];
register LONG n;
{
	register LONG i,b;
	register char *element,*scr;

	element = av[0];

	for(b = 0,i = 1 ; i < n ; ++i)
	{
		if(strcmp(av[i],element) < 0)
		{
			++b;

			scr = av[i];
			av[i] = av[b];
			av[b] = scr;
		}
	}

	scr = av[0];
	av[0] = av[b];
	av[b] = scr;

	return(b);
}

	/* StripPath(s):
	 *
	 *	The Lattice support library contains a function like
	 *	this one, the Aztec lib doesn't. StripPath does not
	 *	modify the contents of the file name it receives
	 *	but returns a pointer to the start of the real
	 *	file name without path identification.
	 */

char *
StripPath(s)
char *s;
{
	long i,start = -1;

		/* Start at the bottom, move up to the top. */

	for(i = strlen(s) - 1 ; i >= 0 ; i--)
	{
		if(s[i] == '/' || s[i] == ':')
			break;

		start = i;
	}

		/* Sorry kid... */

	if(start == -1)
		return(NULL);

		/* Here we go. */

	return(s + start);
}

	/* FadeOut(VPort,CMap,Count):
	 *
	 *	Fades the colours of a ViewPort out (colour -> black).
	 */

void
FadeOut(VPort,CMap,Count)
struct ViewPort *VPort;
UWORD *CMap;
long Count;
{
	UWORD TempMap[32];
	BYTE Red,Green,Blue;
	register long i,j;

	for(i = 1 ; i < 16 ; i++)
	{
		for(j = 0 ; j < Count ; j++)
		{
			Red	= ((CMap[j] >> 8) & 0x0F) - i;
			Green	= ((CMap[j] >> 4) & 0x0F) - i;
			Blue	= ((CMap[j]     ) & 0x0F) - i;

			if(Red < 0)
				Red = 0;

			if(Green < 0)
				Green = 0;

			if(Blue < 0)
				Blue = 0;

			TempMap[j] = (UWORD)Red << 8 | (UWORD)Green << 4 | (UWORD)Blue;
		}

		Delay(2);
		LoadRGB4(VPort,TempMap,Count);
	}
}

	/* FadeIn(VPort,CMap,Count):
	 *
	 *	Fades the colours of a ViewPort in (black -> colour).
	 */

void
FadeIn(VPort,CMap,Count)
struct ViewPort *VPort;
UWORD *CMap;
long Count;
{
	UWORD TempMap[32];
	BYTE Red,Green,Blue;
	register long i,j;

	for(i = 15 ; i >= 0 ; i--)
	{
		for(j = 0 ; j < Count ; j++)
		{
			Red	= ((CMap[j] >> 8) & 0x0F) - i;
			Green	= ((CMap[j] >> 4) & 0x0F) - i;
			Blue	= ((CMap[j]     ) & 0x0F) - i;

			if(Red < 0)
				Red = 0;

			if(Green < 0)
				Green = 0;

			if(Blue < 0)
				Blue = 0;

			TempMap[j] = (UWORD)Red << 8 | (UWORD)Green << 4 | (UWORD)Blue;
		}

		Delay(2);
		LoadRGB4(VPort,TempMap,Count);
	}
}

	/* PrintScreen(Large):
	 *
	 *	Sends the current LoadImage screen to the printer.
	 */

void
PrintScreen(Large)
BOOL Large;
{
	struct IODRPReq	*PrinterDump;
	struct MsgPort	*PrinterPort;

	BOOL HasTitle	= (Screen -> Flags & SHOWTITLE) ? TRUE : FALSE;
	BOOL Cycling	= IsCycling();

	SHORT TopEdge,LeftEdge,BottomEdge,RightEdge;

	SetSnooze(Window);

		/* IO Replyport. */

	if(PrinterPort = (struct MsgPort *)CreatePort(NULL,0))
	{
			/* Custom RastPort dump structure. */

		if(PrinterDump = (struct IODRPReq *)CreateExtIO(PrinterPort,sizeof(struct IODRPReq)))
		{
				/* Try to open the device. */

			if(!OpenDevice("printer.device",0,PrinterDump,0))
			{
				SetSize(Window);
				SetDrMd(Window -> RPort,COMPLEMENT);

					/* Don't let anyone disturb the
					 * LoadImage screen bitmap.
					 */

				Window -> Flags |= RMBTRAP;

				if(HasTitle)
					ShowTitle(Screen,FALSE);

				if(Cycling)
					ToggleCycleCode();

					/* Let the user set the initial
					 * point.
					 */

				FOREVER
				{
					WaitPort(Window -> UserPort);

					if(Massage = (struct IntuiMessage *)GetMsg(Window -> UserPort))
					{
						Class = Massage -> Class;
						Code = Massage -> Code;

						ReplyMsg(Massage);
					}

					if(Class == MOUSEBUTTONS && Code == MENUDOWN)
					{
						DisplayBeep(Screen);

						goto HitAndQuit;
					}

					if(Class == MOUSEBUTTONS && Code == SELECTDOWN)
					{
						LeftEdge = Window -> MouseX;
						TopEdge = Window -> MouseY;

						break;
					}
				}

				BottomEdge = TopEdge;
				RightEdge = LeftEdge;

					/* Now let him resize the rubber box
					 * to the right dimensions.
					 */

				ReportMouse(TRUE,Window);

				FOREVER
				{
					USHORT Qualifier;

					Move(Window -> RPort,LeftEdge,TopEdge);
					Draw(Window -> RPort,RightEdge,TopEdge);
					Draw(Window -> RPort,RightEdge,BottomEdge);
					Draw(Window -> RPort,LeftEdge,BottomEdge);
					Draw(Window -> RPort,LeftEdge,TopEdge);

WaitLoop:				WaitPort(Window -> UserPort);

					if(Massage = (struct IntuiMessage *)GetMsg(Window -> UserPort))
					{
						Class = Massage -> Class;
						Code = Massage -> Code;
						Qualifier = Massage -> Qualifier;

						ReplyMsg(Massage);
					}

					if((Class == MOUSEBUTTONS && Code == MENUDOWN) || Class == RAWKEY)
					{
						DisplayBeep(Screen);

						Move(Window -> RPort,LeftEdge,TopEdge);
						Draw(Window -> RPort,RightEdge,TopEdge);
						Draw(Window -> RPort,RightEdge,BottomEdge);
						Draw(Window -> RPort,LeftEdge,BottomEdge);
						Draw(Window -> RPort,LeftEdge,TopEdge);

						goto HitAndQuit;
					}

					if(Class == MOUSEBUTTONS && Code == SELECTUP)
					{
						if((RightEdge <= LeftEdge || BottomEdge <= TopEdge) && !(Qualifier & IEQUALIFIER_RBUTTON))
							continue;

						Move(Window -> RPort,LeftEdge,TopEdge);
						Draw(Window -> RPort,RightEdge,TopEdge);
						Draw(Window -> RPort,RightEdge,BottomEdge);
						Draw(Window -> RPort,LeftEdge,BottomEdge);
						Draw(Window -> RPort,LeftEdge,TopEdge);

						if(Qualifier & IEQUALIFIER_RBUTTON)
							goto HitAndQuit;

						break;
					}

					if(Class == MOUSEMOVE)
					{
						Move(Window -> RPort,LeftEdge,TopEdge);
						Draw(Window -> RPort,RightEdge,TopEdge);
						Draw(Window -> RPort,RightEdge,BottomEdge);
						Draw(Window -> RPort,LeftEdge,BottomEdge);
						Draw(Window -> RPort,LeftEdge,TopEdge);

						RightEdge = Window -> MouseX;
						BottomEdge = Window -> MouseY;

						continue;
					}

					goto WaitLoop;
				}

					/* Initialize the IO Request for
					 * a RastPort dump.
					 */

				PrinterDump -> io_Command	= PRD_DUMPRPORT;
				PrinterDump -> io_RastPort	= &Screen -> RastPort;
				PrinterDump -> io_ColorMap	= Screen -> ViewPort . ColorMap;
				PrinterDump -> io_Modes		= Screen -> ViewPort . Modes;
				PrinterDump -> io_SrcWidth	= RightEdge - LeftEdge;
				PrinterDump -> io_SrcHeight	= BottomEdge - TopEdge;
				PrinterDump -> io_SrcX		= LeftEdge;
				PrinterDump -> io_SrcY		= TopEdge;

					/* Print it as large as possible? */

				if(Large)
				{
					PrinterDump -> io_DestCols	= 0;
					PrinterDump -> io_Special	= SPECIAL_FULLCOLS | SPECIAL_ASPECT;
				}
				else
				{
					PrinterDump -> io_DestCols	= RightEdge - LeftEdge;
					PrinterDump -> io_Special	= SPECIAL_MILCOLS | SPECIAL_ASPECT;

						/* I sincerely hope that this aspect
						 * doctoring has any sensible effect.
						 */
/*
					if(!(PrinterDump -> io_Modes & HIRES) && !(PrinterDump -> io_Modes & LACE))
						PrinterDump -> io_DestCols *= 10;

					if(!(PrinterDump -> io_Modes & HIRES) && (PrinterDump -> io_Modes & LACE))
						PrinterDump -> io_DestCols *= 20;

					if((PrinterDump -> io_Modes & HIRES) && !(PrinterDump -> io_Modes & LACE))
						PrinterDump -> io_DestCols *= 5;

					if((PrinterDump -> io_Modes & HIRES) && (PrinterDump -> io_Modes & LACE))
						PrinterDump -> io_DestCols *= 10;
*/

					PrinterDump -> io_DestCols *= 11;
				}

				SetSnooze(Window);

					/* Try the dump. */

				if(DoIO(PrinterDump))
					DisplayBeep(Screen);

					/* Re-enable the window. */

HitAndQuit:			Window -> Flags &= ~RMBTRAP;
				SetPoint(Window);

					/* If set, re-enable the screen title. */

				if(HasTitle)
					ShowTitle(Screen,TRUE);

				if(Cycling)
					ToggleCycleCode();

				ReportMouse(FALSE,Window);

				CloseDevice(PrinterDump);
			}
			else
				DisplayBeep(Screen);

			DeleteExtIO(PrinterDump,sizeof(struct IODRPReq));
		}
		else
			DisplayBeep(Screen);

		DeletePort(PrinterPort);
	}
	else
		DisplayBeep(Screen);

	SetPoint(Window);
}

	/* main(argc,argv) :
	 *
	 *	This is where all the trouble starts.
	 */

void
main(argc,argv)
LONG argc;
char *argv[];
{
	UWORD Colours[32];		/* Colour buffer (yes I'm British!) */
	register short i,j;		/* Loop counters. */
	ULONG ViewModes;
	LONG ColourNumber;		/* Number of colours. */

	SHORT MaxOffsetX,MaxOffsetY;	/* Some scrolling stuff. */
	SHORT StartOffsetX,StartOffsetY;

	SHORT Width,Height;		/* Size of the ViewPort */

	SHORT JumpY = 1,JumpX = 1;	/* Scrolljump steps */

	BOOL WeAreScrolling = FALSE;	/* Are we? Really? */
	BOOL RemakeTheView;		/* Push it? */

	USHORT MenuNum;			/* Oh, we have a menu. */
	struct MenuItem *Item;

	BOOL ForceScroll = FALSE;	/* Are we to scroll? */
	BOOL ForceLace = FALSE;		/* Do we like jittering? */
	BOOL IsMouse;			/* Scrollmode. */
	char *PicName;			/* If we are to scroll around. */
	char *PrgName;			/* Our name. */

	short StartAt;			/* Item to load first. */
	short NumItems;			/* Number of items to load. */
	BOOL NextPic;			/* Proceed with the next picture. */
	BOOL MultipleItems = FALSE;	/* One or more pictures? */

	struct WBArg *WArg;		/* Workbench control. */
	char PathBuffer[256];		/* Temporary directory string. */

	BOOL CycleOnStartup = FALSE;	/* Are we to cycle at once? */
	BOOL WasCycling;		/* What happened? */
	BOOL ChangedColours = FALSE;	/* Did we change the colours? */

	UBYTE *RightMouse = (UBYTE *)0xDFF016;	/* The right mousebutton via CIA. */

	char *PicList[128];		/* Temporary filename buffer. */
	LONG PicsInList = 0;		/* Files in the buffer. */

	BOOL IsTiny = FALSE;		/* Image smaller than screen? */
	long TinyX,TinyY;		/* Image offsets for tiny bitmap. */

	struct Process *ThatsMe = (struct Process *)FindTask(NULL);	/* Process identifier. */
	long OldPri;			/* Old task priority. */

	UWORD BlackIsBlack[32];		/* Empty colour information. */

	for(i = 0 ; i < 32 ; i++)
		BlackIsBlack[i] = 0;

		/* Started via Workbench? */

	if(!ThatsMe -> pr_CLI)
	{
		extern struct WBStartup *WBenchMsg;

		WArg = WBenchMsg -> sm_ArgList;

		PrgName = WArg -> wa_Name;

		WArg++;

		if((NumItems = WBenchMsg -> sm_NumArgs - 1) < 1)
		{
			printf("\n [4m%s problem[0m:\n\n",PrgName);

			printf(" [33mHold down[31m the shift-key, [33mclick[31m the picture icons you wish to\n");
			printf(" load, then [33mdouble click[31m the LoadImage icon.\n\n");

			printf(" Sorry, [33mno[31m second icon was selected, please press [1mRETURN33[0m.");

			getchar();

			exit(0);
		}

		goto Skip;
	}
	else
	{
		StartAt = 1;
		NumItems = argc - 1;

			/* Some capital letters. */

		for(i = 0 ; i < strlen(argv[0]) ; i++)
			argv[0][i] = toupper(argv[0][i]);

		PrgName = argv[0];
	}

	if(argv[1][0] == '-')
	{
		switch(toupper(argv[1][1]))
		{
			case 'F':
			{
				/* If "-F" is specified, all HIRES and LACE flags
				 * will be ignored.
				 */

				ForceScroll = TRUE;

				break;
			}

			case 'I':
			{
				/* Forced interlace mode. */

				ForceLace = TRUE;

				break;
			}

			case 'C':
			{
				/* Colour cycling on startup. */

				CycleOnStartup = TRUE;

				break;
			}

			default:
			{
				printf("%s: Option \"%s\" not recognized.\n",PrgName,argv[1]);
				exit(20);
			}
		}

		if(strlen(argv[1]) > 2 && argv[1][0] == '-')
			if(toupper(argv[1][2]) == 'C')
				CycleOnStartup = TRUE;

		StartAt = 2;
		NumItems--;
	}
		/* "Help! I need somebody; Help! not just anybody..." */

	if((argv[1][0] == '?' || argc < 2 || (ForceScroll && argc < 3)) && argc)
	{
		printf("\n [1m[33mLoadImage[31m[0m © 1989 ED Electronic Design Hannover\n\n");
		printf(" Usage is: [1m%s[0m [-F or -I, -FC or -IC] Picture1 Picture2 ... PictureN\n",argv[0]);
		printf("           [33mUNIX[31m style wildcards are supported!\n\n");

		printf("           [1m-F[0m [33m»[31m Force scrollmode (ignore Hi-Res & Interlace flags).\n");
		printf("           [1m-I[0m [33m»[31m Force interlaced display mode.\n");
		printf("           [1m-C[0m [33m»[31m Force colour cycling on startup.\n\n");

		printf(" If you wish to come in contact with [1mme[0m, here's my address:\n\n");

		printf("\tOlaf 'Olsen' Barthel\n");
		printf("\tBrabeckstrasse 35\n");
		printf("\tD-3000 Hannover 71\n\n");

		printf("\t[1mFederal Republic of Germany[0m\n\n");

		printf(" Have [4mfun[0m with the code ... [3mOlsen[0m\n\n");

		exit(0);
	}

Skip:	if(NumItems > 1)
		MultipleItems = TRUE;
	else
	{
		if(argc)
		{
			while(PicsInList < 128 && (PicName = scdir(argv[StartAt])))
			{
				if(!(PicList[PicsInList] = malloc(strlen(PicName) + 1)))
					break;

				strcpy(PicList[PicsInList++],PicName);

				if(PicsInList == 2)
					printf("\n[3mScanning directory for [0m[1m%s[0m...\n",StripPath(argv[StartAt]));
			}

			if(!PicsInList)
			{
				printf("%s: No files matched with \"%s\".\n",PrgName,argv[StartAt]);
				exit(20);
			}

			StartAt = 0;

			if((NumItems = PicsInList) > 1)
			{
				MultipleItems = TRUE;

				printf("[3mSorting entries...[0m\n\n");

				QuickSort(PicList,PicsInList);
			}
		}
	}

	if(!argc)
		goto Lib;

		/* The libraries we need. */

Lib:	if(!(IntuitionBase = (struct IntuitionBase *)OpenLibrary("intuition.library",33)))
	{
		CloseAll();
		CloseLibs();
	}

	if(!(GfxBase = (struct GfxBase *)OpenLibrary("graphics.library",33)))
	{
		CloseAll();
		CloseLibs();
	}

		/* Setup the standard colours for later use. */

	GetPrefs(&StandardPrefs,sizeof(struct Preferences));

	for(j = StartAt ; j < (StartAt + NumItems) ; j++)
	{
		if(!argc)
		{
			PicName = GetPath(WArg -> wa_Lock);

			strcpy(PathBuffer,PicName);
			strcat(PathBuffer,WArg -> wa_Name);

			PicName = PathBuffer;

			WArg++;
		}
		else
		{
			if(PicsInList)
				PicName = PicList[j];
			else
				PicName = argv[j];
		}

			/* Finished so soon? */

		if(BreakCheck())
		{
			printf("[33m*** [1mAborting[31m[0m <RETURN> ");
			getchar();

			CloseAll();
			CloseLibs();
		}

			/* Got a whole list to display. */

		if(MultipleItems || !argc)
			printf("Picture [33mNº %03d[31m, [1m\"%s\"[0m loading...\n",j - StartAt + 1,PicName);

			/* Everything okay with the ViewModes? */

		if((ViewModes = LoadHeader(PicName,&InfoHeader)) == -1)
		{
			printf("%s: Couldn't load \"%s\"!\n",PrgName,PicName);
			CloseAll();

			continue;
		}

			/* Are there any colours out there? */

		if(!(ColourNumber = LoadCMAP(PicName,Colours,32,&InfoHeader)))
		{
			printf("%s: Couldn't load \"%s\"!\n",PrgName,PicName);
			CloseAll();

			continue;
		}

			/* Take care of the size of the ViewPort. */

		Width	= InfoHeader . pageWidth;
		Height	= InfoHeader . pageHeight;

			/* Forced scrolling? */

		if(ForceScroll)
			ViewModes &= ~(HIRES | LACE);

			/* Forced interlaced display mode? */

		if(ForceLace)
			ViewModes |= LACE;

			/* Take care of exotic screen sizes. */

		if(InfoHeader . h > Height)
			Height = InfoHeader . h;

		if(InfoHeader . w > Width)
			Width = InfoHeader . w;

			/* Hires or lores? */

		if(ViewModes & HIRES)
		{
			Width = 640;
			JumpX = 2;
		}
		else
			Width = 320;

			/* Interlaced or not? This may get funny since
			 * it is possible to scroll the image in steps
			 * of one pixel. In doing so one copper list
			 * (either shortframe or longframe) is ignored
			 * and only half of the image is displayed
			 * correctly. To avoid this the scroll jump
			 * is set to two pixels.
			 */

		if(ViewModes & LACE)
			JumpY = 2;

		if(Height > GfxBase -> NormalDisplayRows * JumpY)
			Height = GfxBase -> NormalDisplayRows * JumpY;

			/* Adjust the information. */

		NewScreen . Width 	= Width;
		NewScreen . Height	= Height;
		NewScreen . Depth	= InfoHeader . nPlanes;
		NewScreen . ViewModes	= ViewModes;

			/* And don't forget the window. */

		NewWindow . Width	= Width;
		NewWindow . Height	= Height;

			/* Anything wrong with the picture? */

		if(InfoHeader . w < InfoHeader . pageWidth || InfoHeader . h < InfoHeader . pageHeight)
		{
			if(!InitTinyBitMap())
			{
				printf("%s: Not enough memory for secondary bitmap!\n",PrgName);
				CloseAll();

				continue;
			}

			IsTiny = TRUE;
		}
		else
		{
				/* Initialize the bitmap for future use. */

			InitBitMap(&ScreenMap,InfoHeader . nPlanes,InfoHeader . w,InfoHeader . h);

				/* Try to steal some memory for the bitplanes. */

			for(i = 0 ; i < InfoHeader . nPlanes ; i++)
			{
				NextPic = FALSE;

				if(!(ScreenMap . Planes[i] = (PLANEPTR)AllocRaster(InfoHeader . w,InfoHeader . h)))
				{
					printf("%s: Not enough memory for screen raster!\n",PrgName);
					CloseAll();

					NextPic = TRUE;

					break;
				}
			}

			if(NextPic)
				continue;

			IsTiny = FALSE;
		}

			/* Open the screen. */

		if(!(Screen = (struct Screen *)OpenScreen(&NewScreen)))
		{
			printf("%s: Couldn't open screen!\n",PrgName);
			CloseAll();

			continue;
		}

			/* Hide the title bar and prepare the window. */

		ShowTitle(Screen,FALSE);

		NewWindow . Screen = Screen;

			/* Try to open the window. */

		if(!(Window = (struct Window *)OpenWindow(&NewWindow)))
		{
			printf("%s: Couldn't open window!\n",PrgName);
			CloseAll();

			continue;
		}

		SetSnooze(Window);

		SetWindowTitles(Window,-1,StripPath(PicName));

		for(i = 0 ; i < 32 ; i++)
		{
			PrefColours[i] = Colours[i];

			if(i >= 1 << NewScreen . Depth)
				PrefColours[i] = Colours[i] = GetRGB4(Screen -> ViewPort . ColorMap,i);
		}

		PrefColours[0] = StandardPrefs . color0;
		PrefColours[1] = StandardPrefs . color1;
		PrefColours[2] = StandardPrefs . color2;
		PrefColours[3] = StandardPrefs . color3;

			/* Load the colours. */

		LoadRGB4(&Screen -> ViewPort,Colours,ColourNumber);

			/* Just in case we will have to restore it. */

		StartOffsetX = Screen -> ViewPort . RasInfo -> RxOffset;
		StartOffsetY = Screen -> ViewPort . RasInfo -> RyOffset;

			/* Play it safe --- keep the maximum scroll offset
			 * in reasonable dimensions.
			 */

		if((MaxOffsetX = InfoHeader . w - Width) < 0)
			MaxOffsetX = 0;

		if((MaxOffsetY = InfoHeader . h - Height) < 0)
			MaxOffsetY = 0;

			/* Absolute maximum limit. */

		MaxOffsetX += StartOffsetX;
		MaxOffsetY += StartOffsetY;

			/* "Here comes the sun..." */

		if(IsTiny)
		{
			if(!LoadRaster(PicName,TinyBitMap . Planes,&InfoHeader))
			{
				printf("%s: Error while reading image data!\n",PrgName);
				CloseAll();

				continue;
			}
		}
		else
		{
			if(!LoadRaster(PicName,ScreenMap . Planes,&InfoHeader))
			{
				printf("%s: Error while reading image data!\n",PrgName);
				CloseAll();

				continue;
			}
		}

			/* Do we allow colour cycling? */

		if(LoadCycleRange(PicName,CycleRange,6))
			InitCycleCode(&Screen -> ViewPort,Colours,ColourNumber,CycleRange,6);

			/* Install the menu and the pointer. */

		SetMenuStrip(Window,&Menu);
		SetPoint(Window);

			/* This one disables the "Next Image" item. */

		if(!MultipleItems)
			OffMenu(Window,0xF8E0);

			/* And - if necessary - print the tiny image. */

		if(IsTiny)
		{
			if(InfoHeader . x + InfoHeader . w <= Screen -> Width)
				TinyX = InfoHeader . x;
			else
				TinyX = (NewScreen . Width - InfoHeader . w) >> 1;

			if(InfoHeader . y + InfoHeader . h <= Screen -> Height)
				TinyY = InfoHeader . y;
			else
				TinyY = (NewScreen . Height - InfoHeader . h) >> 1;

				/* Woop! what a long line! */

			BltBitMap(&TinyBitMap,0,0,Screen -> RastPort . BitMap,TinyX,TinyY,InfoHeader . w,InfoHeader . h,0xC0,0xFF);

			IsTiny = FALSE;
		}

			/* Paint the screen black (for the effect). */

		if(!(ViewModes & HAM))
			LoadRGB4(&Screen -> ViewPort,BlackIsBlack,ColourNumber);

			/* Bring the screen to the front. */

		ScreenToFront(Screen);
		ActivateWindow(Window);

		FreeSprite(0);

			/* Fade the colours in. */

		if(!(ViewModes & HAM))
			FadeIn(&Screen -> ViewPort,Colours,ColourNumber);

			/* Allow the menu to be selected. */

		Window -> Flags &= ~RMBTRAP;

			/* If we are to cycle... */

		if(CycleOnStartup)
			ToggleCycleCode();

			/* To make the scrolling a bit smoother, we
			 * raise our own priority.
			 */

		OldPri = SetTaskPri(ThatsMe,5);

			/* "Ewig währt am längsten." : Kurt Schwitters (1887 - 1948).
			 *
			 *                             Kurt Schwitters was a famous
			 *                             Hanoverian Dada artist.
			 */

		FOREVER
		{
				/* Let's be nice and wait for reactions. */

			WaitPort(Window -> UserPort);

				/* Massage the userport. */

			if(Massage = (struct IntuiMessage *)GetMsg(Window -> UserPort))
			{
				Class = Massage -> Class;
				Code = Massage -> Code;

					/* If the user presses the menu button
					 * the menu appears. There is nothing
					 * wrong with this, but it might be
					 * possible that the user does not
					 * see the menu because of the raster
					 * offset. So we reposition the ViewPort
					 * before Intuition renders the menu bar.
					 * To keep the menu visible we adjust
					 * the colours if the right mouse
					 * button is pressed. This is done
					 * because of the fact that the
					 * keyboard shortcuts generate the
					 * same MENUVERIFY event. If there is
					 * anyone who doesn't like it this
					 * way he can try to test the keyboard
					 * matrix by tickling the hardware.
					 * I have chosen the easier way.
					 */

				if(Class == MENUVERIFY && !(*RightMouse & 4))
				{
					if(WasCycling = IsCycling())
						ToggleCycleCode();

					LoadRGB4(&Screen -> ViewPort,PrefColours,32);

					ChangedColours = TRUE;

						/* Reset the offsets. */

					Screen -> ViewPort . RasInfo -> RxOffset = StartOffsetX;
					Screen -> ViewPort . RasInfo -> RyOffset = StartOffsetY;

						/* Remake the copper list. */

					MakeScreen(Screen);
					RethinkDisplay();
				}

				ReplyMsg(Massage);
			}

				/* If the user presses the select button
				 * we'll assume that he wants to scroll
				 * around in the picture.
				 */

			if(Class == MOUSEBUTTONS && Code == SELECTDOWN)
			{
				FreeSprite(0);

				if(ChangedColours)
				{
					LoadRGB4(&Screen -> ViewPort,Colours,ColourNumber);
					ChangedColours = FALSE;

					if(WasCycling)
						ToggleCycleCode();

					WasCycling = FALSE;
				}

				WeAreScrolling = TRUE;

				IsMouse = TRUE;
			}

				/* User pressed the cursor keys and wants
				 * to scroll around in the picture.
				 */

			if(Class == RAWKEY && (Code & ~IECODE_UP_PREFIX) >= CURSORUP && (Code & ~IECODE_UP_PREFIX) <= CURSORLEFT)
			{
				FreeSprite(0);

				WeAreScrolling = TRUE;

				IsMouse = FALSE;
			}

				/* User didn't like the menu shortcut
				 * and pressed the Tab key (is he a
				 * DPaint fanatic?).
				 */

			if(Class == RAWKEY && Code == 0x42)
			{
				FreeSprite(0);

				ToggleCycleCode();
			}

			NextPic = FALSE;

				/* The user picked a menu item. */

			if(Class == MENUPICK)
			{
				if(ChangedColours)
				{
					LoadRGB4(&Screen -> ViewPort,Colours,ColourNumber);
					ChangedColours = FALSE;

					if(WasCycling)
						ToggleCycleCode();

					WasCycling = FALSE;
				}

				MenuNum = Code;

					/* Until the last event is traced. */

				while(MenuNum != MENUNULL)
				{
					if(MENUNUM(MenuNum) == 0)
					{
						switch(ITEMNUM(MenuNum))
						{
								/* About... */

							case 0:	if(WasCycling = IsCycling())
									ToggleCycleCode();

								LoadRGB4(&Screen -> ViewPort,PrefColours,32);

								SetSnooze(Window);

									/* Reset the offsets. */

								Screen -> ViewPort . RasInfo -> RxOffset = StartOffsetX;
								Screen -> ViewPort . RasInfo -> RyOffset = StartOffsetY;

									/* Remake the copper list. */

								MakeScreen(Screen);
								RethinkDisplay();

								Window -> Flags |= RMBTRAP;
								AutoRequest(Window,&AboutTxt[0],NULL,&Proceed,NULL,NULL,274,176);
								Window -> Flags &= ~RMBTRAP;

								SetPoint(Window);

								LoadRGB4(&Screen -> ViewPort,Colours,ColourNumber);

								if(WasCycling)
									ToggleCycleCode();

								FreeSprite(0);
								break;

								/* Toggle the presence of the screen title. */

							case 2:	FreeSprite(0);

								if(Screen -> Flags & SHOWTITLE)
									ShowTitle(Screen,FALSE);
								else
									ShowTitle(Screen,TRUE);

								break;

								/* Toggle cycling. */

							case 3:	FreeSprite(0);

								ToggleCycleCode();
								break;

								/* Print screen (standard size). */

							case 4:	PrintScreen(FALSE);

								break;

								/* Print screen (enlarged). */

							case 5:	PrintScreen(TRUE);

								break;

								/* Next picture. */

							case 7:	if(IsCycling())
									ToggleCycleCode();

								WasCycling = FALSE;

								if(!(ViewModes & HAM))
									FadeOut(&Screen -> ViewPort,Colours,ColourNumber);

								ScreenToBack(Screen);

									/* Restore original priority. */

								SetTaskPri(ThatsMe,OldPri);

								NextPic = TRUE;
								CloseAll();

								break;

								/* Quit LoadImage. */

							case 8:	if(IsCycling())
									ToggleCycleCode();

								WasCycling = FALSE;

								if(!(ViewModes & HAM))
									FadeOut(&Screen -> ViewPort,Colours,ColourNumber);

								ScreenToBack(Screen);

									/* Restore original priority. */

								SetTaskPri(ThatsMe,OldPri);

								NextPic = TRUE;
								CloseAll();

								goto Bye;
						}
					}

						/* See if there is another
						 * menu item around.
						 */

					Item = (struct MenuItem *)ItemAddress(&Menu,MenuNum);

					MenuNum = Item -> NextSelect;
				}
			}

			if(NextPic)
				break;

				/* No chance to scroll anywhere, so we'll block
				 * it.
				 */

			if(MaxOffsetX == StartOffsetX && MaxOffsetY == StartOffsetY)
				WeAreScrolling = FALSE;

				/* This loop will run until the select button
				 * or the cursor key is released.
				 */

			while(WeAreScrolling)
			{
				if(Massage = (struct IntuiMessage *)GetMsg(Window -> UserPort))
				{
					Class = Massage -> Class;
					Code  = Massage -> Code;

					ReplyMsg(Massage);
				}

					/* User doesn't want to scroll anymore? */

				if(Code & IECODE_UP_PREFIX)
				{
					FreeSprite(0);
					WeAreScrolling = FALSE;
				}

					/* Nothing happened. */

				RemakeTheView = FALSE;

					/* Left border, scroll left. */

				if(((Window -> MouseX == 0 && IsMouse) || (Code == CURSORLEFT && !IsMouse)) && (Screen -> ViewPort . RasInfo -> RxOffset - JumpX >= StartOffsetX))
				{
					Screen -> ViewPort . RasInfo -> RxOffset -= JumpX;
					RemakeTheView = TRUE;
				}

					/* Top border, scroll up. */

				if(((Window -> MouseY == 0 && IsMouse) || (Code == CURSORUP && !IsMouse)) && (Screen -> ViewPort . RasInfo -> RyOffset - JumpY >= StartOffsetY))
				{
					Screen -> ViewPort . RasInfo -> RyOffset -= JumpY;
					RemakeTheView = TRUE;
				}

					/* Right border, scroll right. */

				if(((Window -> MouseX == Window -> Width - 1 && IsMouse) || (Code == CURSORRIGHT && !IsMouse)) && (Screen -> ViewPort . RasInfo -> RxOffset + JumpX <= MaxOffsetX))
				{
					Screen -> ViewPort . RasInfo -> RxOffset += JumpX;
					RemakeTheView = TRUE;
				}

					/* Bottom border, scroll down. */

				if(((Window -> MouseY == Window -> Height - 1 && IsMouse) || (Code == CURSORDOWN && !IsMouse)) && (Screen -> ViewPort . RasInfo -> RyOffset + JumpY <= MaxOffsetY))
				{
					Screen -> ViewPort . RasInfo -> RyOffset += JumpY;
					RemakeTheView = TRUE;
				}

					/* Are we to scroll the ViewPort? */

				if(RemakeTheView)
				{
					MakeScreen(Screen);
					RethinkDisplay();
				}
			}
		}

			/* Last image, disable menu item. */

		if(j == StartAt + NumItems - 1)
			OffMenu(Window,0xF8E0);
	}

		/* The goodbye message. */

Bye:	if(!argc)
	{
		printf("\nPlease press [1mRETURN[0m.");
		getchar();
	}

		/* Give it a newline. */

	if(MultipleItems)
		putchar('\n');

	CloseLibs();
}

	/* End of Code, end of file. */
