/* $Revision Header * Header built automatically - do not edit! *************
 *
 *	(C) Copyright 1990 by MXM
 *
 *	Name .....: LoadImage.c
 *	Created ..: Saturday 23-Sep-90 12:30
 *	Revision .: 11
 *
 *	Date            Author          Comment
 *	=========       ========        ====================
 *	11-May-90       Olsen           New routines
 *	01-May-90       Olsen           Rework for Aztec 5.0 release
 *	23-Sep-89       Olsen           Created this file!
 *
 * $Revision Header ********************************************************/

	/* Arp user interface stuff. */

char *CLI_Template	= "INFO/S,NOMODE/S,LACE/S,CYCLE/S,Files,,,,,,";
char *CLI_Help		= "\nUsage: LoadImage [INFO] [NOMODE] [LACE] [CYCLE] Files1 ... FileN\n\n       NOMODE and LACE are mutually exclusive.\n";

	/* Argument vector offsets. */

#define ARG_INFO	1
#define ARG_NOMODE	2
#define ARG_LACE	3
#define ARG_CYCLE	4
#define ARG_FILES	5

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

CRange CycleRange[6];

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

struct TextAttr StandardFont[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}
};

	/* Menu text definitions. */

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

	/* A chain of menu items. */

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

	/* Our only menu. */

struct Menu Menu = { NULL,1,0,120,0,257,"LoadImage 1.11",&MenuItem[0] };

	/* Requester text definitions. */

struct IntuiText ReqIntTxt[] =
{
	{0,0,0, 76,  4,&StandardFont[0],(UBYTE *)"Understood",				NULL},

	{0,0,0, 94,  6,&StandardFont[1],(UBYTE *)"LoadImage 1.11",			&ReqIntTxt[2]},

	{0,0,0, 34, 18,&StandardFont[0],(UBYTE *)"Was  written  by Olaf 'Olsen'",	&ReqIntTxt[3]},
	{0,0,0, 34, 26,&StandardFont[0],(UBYTE *)"Barthel of MXM.  This version",	&ReqIntTxt[4]},
	{0,0,0, 34, 34,&StandardFont[0],(UBYTE *)"supports  EHB & HAM pictures,",	&ReqIntTxt[5]},
	{0,0,0, 34, 42,&StandardFont[0],(UBYTE *)"will   also   take   care  of",	&ReqIntTxt[6]},
	{0,0,0, 34, 50,&StandardFont[0],(UBYTE *)"oversized    pictures   which",	&ReqIntTxt[7]},
	{0,0,0, 34, 58,&StandardFont[0],(UBYTE *)"require  1M  of  chip ram and",	&ReqIntTxt[8]},
	{0,0,0, 46, 66,&StandardFont[0],(UBYTE *)"features an Arp interface.",		&ReqIntTxt[9]},
	{0,0,0, 34, 82,&StandardFont[0],(UBYTE *)"Press  the  left mouse button",	&ReqIntTxt[10]},
	{0,0,0, 34, 90,&StandardFont[0],(UBYTE *)"and move the pointer  towards",	&ReqIntTxt[11]},
	{0,0,0, 34, 98,&StandardFont[0],(UBYTE *)"the borders of the  screen to",	&ReqIntTxt[12]},
	{0,0,0, 34,106,&StandardFont[0],(UBYTE *)"scroll around in  overscanned",	&ReqIntTxt[13]},
	{0,0,0,122,114,&StandardFont[0],(UBYTE *)"images.",				&ReqIntTxt[14]},

	{0,0,0, 58,130,&StandardFont[2],(UBYTE *)"© Copyright 1990 by MXM",		NULL}
};

	/* Requester border coordinates. */

SHORT ReqBrdDat[] =
{
	-3,-2,234,-2,234, 17,-3, 17,-3,-2,
	-6,-4,237,-4,237, 19,-6, 19,-6,-4,
	 0, 0,293, 0,293,172, 0,172, 0, 0,
	 0, 0,295, 0,295,172, 0,172, 0, 0
};

	/* Requester border definitions. */

struct Border ReqBrd[] =
{
	{0,0,0,0,0,5,&ReqBrdDat[ 0],&ReqBrd[1]},
	{0,0,0,0,0,5,&ReqBrdDat[10],NULL},
	{3,1,0,0,0,5,&ReqBrdDat[20],&ReqBrd[3]},
	{2,1,0,0,0,5,&ReqBrdDat[30],NULL}
};

	/* The only gadget employed by the Requester. */

struct Gadget ReqGad = {NULL,34,149,232,16,0,3,4097,(APTR)&ReqBrd[0],NULL,&ReqIntTxt[0],NULL,NULL,0,NULL};

	/* Our only Requester. */

struct Requester Req;

	/* 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;

	/* Empty colour information. */

UWORD BlackIsBlack[32] =
{
	0,0,0,0,0,0,0,0,
	0,0,0,0,0,0,0,0,
	0,0,0,0,0,0,0,0,
	0,0,0,0,0,0,0,0
};

	/* 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,
	&StandardFont[0],			/* Just insure that the menu looks cute. */
	(STRPTR)"LoadImage 1.11",		/* 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. */
	GADGETUP | 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. */

extern struct IntuitionBase	*IntuitionBase;
extern struct GfxBase		*GfxBase;
extern struct ExecBase		*SysBase;

struct Screen			*Screen;
struct Window			*Window;

	/* Check for ^C signal. */

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

	/* 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[(2 + 7) * 2] =
{
	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[(2 + 22) * 2] =
{
	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[(2 + 13) * 2] =
{
	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;

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

ULONG
MakeID(char *IDString)
{
	ULONG 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.
	 */

LONG
FindChunk(char *ChunkName,FILE *FilePointer)
{
	IFF_Chunk	 Chunk;
	LONG		 OldPosition,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(0);
		}

			/* 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(Chunk . IFF_Length);

			continue;
		}

			/* Is this what we want? */

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

			/* 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, not NULL.
	 */

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

		/* No such file? */

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

		/* No BMHD-Chunk? */

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

		/* Read the header. */

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

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

	if(BMHeader -> nPlanes < 1 || BMHeader -> nPlanes > 8)
	{
		fclose(ImageFile);
		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",ImageFile))
	{
		fclose(ImageFile);
		return(ViewModes);
	}

		/* Read it then. */

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

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

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

		/* Finish it. */

	fclose(ImageFile);

	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(char *FileName,UWORD *ColourMap,LONG MaxCol,BitMapHeader *BMHeader)
{
	LONG  i;
	FILE *ColFile;
	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.
	 */

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

	BYTE		*AuxBuff;	/* Decompress in memory buffer. */
	BYTE		*AuxBuff2;
	LONG		 AuxLength;

		/* Clear the planes. */

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

		/* Set up the working copies. */

	Width	= LineBytes(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(!(AuxLength = 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++;

		/* If we can allocate the memory buffer, we will
		 * decompress the image in memory rather than
		 * while reading it from disk.
		 */

	if(AuxBuff = (BYTE *)AllocMem(AuxLength,MEMF_PUBLIC))
	{
			/* Read the data. */

		fread(AuxBuff,AuxLength,1,PicFile);

			/* Remember the buffer address. */

		AuxBuff2 = AuxBuff;

			/* No compression? */

		if(Compr == 0)
		{
			for(k = 0 ; k < Height ; k++)
			{
				for(j = 0 ; j < Depth ; j++)
				{
					if(Planes[j])
					{
						CopyMem(AuxBuff,Planes[j],Width);
						Planes[j] += Width;
					}

					AuxBuff += Width;
				}
			}
		}

			/* ByteRun compression? */

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

						if(ChkVal > 0)
						{
							if(Planes[j])
							{
								CopyMem(AuxBuff,Planes[j],ChkVal + 1);

								Planes[j] += ChkVal + 1;
							}

							AuxBuff += ChkVal + 1;

							SoFar += ChkVal + 1;
						}
						else
						{
							if(ChkVal != -128)
							{
								Value = *AuxBuff;
								AuxBuff++;

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

									SoFar++;
								}
							}
						}
					}
				}
			}
		}

			/* Free the auxilary buffer. */

		FreeMem(AuxBuff2,AuxLength);

		goto Quit;
	}
		/* 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. */

Quit:	fclose(PicFile);

	return(TRUE);
}

	/* FadeTo():
	 *
	 *	Fades a palette into a different palette or colour.
	 */

VOID
FadeTo(struct ViewPort *VPort,UWORD *From,UWORD *To,LONG NumColours,LONG ToCol,LONG FromCol)
{
	UWORD FromTemp[32];
	UWORD ToTemp[32];

	SHORT i,j,R1,G1,B1,R2,G2,B2;

		/* A bit too large? */

	if(NumColours > 32)
		NumColours = 32;

		/* Do we have a source palette? */

	if(From)
		CopyMem(&From[0],&FromTemp[0],sizeof(UWORD) * 32);
	else
		memset(&FromTemp[0],FromCol,sizeof(UWORD) * 32);

		/* Do we have a destination palette? */

	if(To)
		CopyMem(&To[0],&ToTemp[0],sizeof(UWORD) * 32);
	else
		memset(&ToTemp[0],ToCol,sizeof(UWORD) * 32);

		/* Let's get into action. */

	for(j = 0 ; j < 16 ; j++)
	{
		for(i = 0 ; i < NumColours ; i++)
		{
				/* Split the source colour. */

			R1 = (FromTemp[i] >> 8)	& 0xF;
			G1 = (FromTemp[i] >> 4)	& 0xF;
			B1 = (FromTemp[i])	& 0xF;

				/* Split the destination colour. */

			R2 = (ToTemp[i] >> 8)	& 0xF;
			G2 = (ToTemp[i] >> 4)	& 0xF;
			B2 = (ToTemp[i])	& 0xF;

				/* Adjust the colours. */

			if(R1 > R2)
				R1--;

			if(G1 > G2)
				G1--;

			if(B1 > B2)
				B1--;

				/* Adjust the colours. */

			if(R1 < R2)
				R1++;

			if(G1 < G2)
				G1++;

			if(B1 < B2)
				B1++;

				/* Put it together again. */

			FromTemp[i] = (R1 << 8) | (G1 << 4) | B1;
		}

			/* Wait a bit. */

		Delay(2);

			/* Load the new map. */

		LoadRGB4(VPort,FromTemp,NumColours);
	}
}

	/* 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.
	 */

UBYTE
InitTinyBitMap()
{
	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);
}

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

VOID
CleanExit(LONG ExitCode)
{
		/* Take care of external signals. */

	SetSignal(0,SIGBREAKF_CTRL_C);

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

	exit(ExitCode);
}

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

VOID
CloseDisplay()
{
	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;
		}
	}
}

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

VOID
PrintScreen(UBYTE Large)
{
	struct IODRPReq		*PrinterDump;
	struct MsgPort		*PrinterPort;

	struct IntuiMessage	*Massage;

	ULONG			 Class;
	USHORT			 Code;

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

	LONG			 StartX,StartY,Width,Height;

	volatile LONG		 OldX = -1,OldY = -1,TempX,TempY,X,Y;

	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) || Class == RAWKEY)
					{
						DisplayBeep(Screen);

						goto HitAndQuit;
					}

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

						break;
					}
				}

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

				ReportMouse(TRUE,Window);

				FOREVER
				{
					USHORT Qualifier;

					TempX = Window -> MouseX;
					TempY = Window -> MouseY;

					if(TempX < 0)
						TempX = 0;

					if(TempY < 0)
						TempY = 0;

					if(TempX > Window -> Width - 1)
						TempX = Window -> Width - 1;

					if(OldX >= 0 && OldY >= 0)
					{
						Move(Window -> RPort,X,Y);
						Draw(Window -> RPort,OldX,Y);
						Draw(Window -> RPort,OldX,OldY);
						Draw(Window -> RPort,X,OldY);
						Draw(Window -> RPort,X,(OldY >= Y) ? Y + 1 : Y - 1);
					}
					else
						OldX = OldY = 0;

					Move(Window -> RPort,X,Y);
					Draw(Window -> RPort,TempX,Y);
					Draw(Window -> RPort,TempX,TempY);
					Draw(Window -> RPort,X,TempY);
					Draw(Window -> RPort,X,(TempY >= Y) ? Y + 1 : Y - 1);

					OldX = TempX;
					OldY = TempY;

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,X,Y);
						Draw(Window -> RPort,TempX,Y);
						Draw(Window -> RPort,TempX,TempY);
						Draw(Window -> RPort,X,TempY);
						Draw(Window -> RPort,X,(TempY >= Y) ? Y + 1 : Y - 1);

						goto HitAndQuit;
					}

					if(Class == MOUSEBUTTONS && Code == SELECTUP)
					{
						Move(Window -> RPort,X,Y);
						Draw(Window -> RPort,TempX,Y);
						Draw(Window -> RPort,TempX,TempY);
						Draw(Window -> RPort,X,TempY);
						Draw(Window -> RPort,X,(TempY >= Y) ? Y + 1 : Y - 1);

						StartX	= (X < TempX) ? X : TempX;
						StartY	= (Y < TempY) ? Y : TempY;

						Width	= ABS(TempX - X) + 1;
						Height	= ABS(TempY - Y) + 1;

						if(Width < 2 || Height < 2)
							goto HitAndQuit;

						break;
					}
				}

					/* 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	= Width;
				PrinterDump -> io_SrcHeight	= Height;
				PrinterDump -> io_SrcX		= StartX;
				PrinterDump -> io_SrcY		= StartY;

					/* Print it as large as possible? */

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

				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);
		}
		else
			DisplayBeep(Screen);

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

	SetPoint(Window);
}

	/* GetRealName():
	 *
	 *	Looks for a file and returns its real name.
	 */

VOID
GetRealName(char *FullName,char *RealName)
{
	struct FileInfoBlock	*FileInfo;
	BPTR			 FileLock;

	if(FileLock = Lock(FullName,ACCESS_READ))
	{
		if(FileInfo = (struct FileInfoBlock *)AllocMem(sizeof(struct FileInfoBlock),MEMF_PUBLIC))
		{
			if(Examine(FileLock,FileInfo))
				strcpy(RealName,FileInfo -> fib_FileName);
				
			FreeMem(FileInfo,sizeof(struct FileInfoBlock));
		}

		UnLock(FileLock);
	}
}

	/* LoadImage():
	 *
	 *	Will finally load and display an image file.
	 */

LONG
LoadImage(char *FileName,UBYTE ForceScroll,UBYTE ForceLace,UBYTE CycleOnStartup,UBYTE LastOne)
{
	UWORD			 Colours[32];		/* Colour buffer (yes I'm British!) */
	LONG			 ViewModes;
	WORD			 ColourNumber;		/* Number of colours. */

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

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

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

	UBYTE			 WeAreScrolling = FALSE;
	UBYTE			 RemakeTheView;

	USHORT			 MenuNum;
	struct MenuItem		*Item;

	UBYTE			 IsMouse;		/* Scrollmode. */

	UBYTE			 WasCycling;		/* What happened? */
	UBYTE			 ChangedColours = FALSE;

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

	struct IntuiMessage	*Massage;

	ULONG			 Class;
	USHORT			 Code;

	SHORT			 i;			/* Loop counters. */

	volatile UBYTE		*RightMouse = (UBYTE *)0xDFF016;

	char			 RealName[108];

	GetRealName(FileName,RealName);

		/* Is it the last picture? */

	if(LastOne)
		MenuItem[8] . Flags &= ~ITEMENABLED;
	else
		MenuItem[8] . Flags |= ITEMENABLED;

		/* Try to load the header. */

	if((ViewModes = LoadHeader(FileName,&InfoHeader)) == -1)
		return(ERR_NOIFF);

		/* Are there any colours out there? */

	if(!(ColourNumber = LoadCMAP(FileName,Colours,32,&InfoHeader)))
		return(ERR_NOCOLMAP);

		/* 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())
			return(ERR_NOMEM);

		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++)
		{
			if(!(ScreenMap . Planes[i] = (PLANEPTR)AllocRaster(InfoHeader . w,InfoHeader . h)))
				return(ERR_NOMEM);

			IsTiny = FALSE;
		}
	}

		/* Open the screen. */

	if(!(Screen = (struct Screen *)OpenScreen(&NewScreen)))
		return(ERR_NOMEM);

		/* 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)))
		return(ERR_NOMEM);

	SetSnooze(Window);

	SetWindowTitles(Window,(STRPTR)-1,(STRPTR)RealName);

		/* Set up the alternate colour palette. */

	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(FileName,TinyBitMap . Planes,&InfoHeader))
			return(ERR_DOS);
	}
	else
	{
		if(!LoadRaster(FileName,ScreenMap . Planes,&InfoHeader))
			return(ERR_DOS);
	}

		/* Do we allow colour cycling? */

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

		/* Install the menu and the pointer. */

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

		/* 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;

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

		/* 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))
		FadeTo(&Screen -> ViewPort,NULL,Colours,ColourNumber,0,0);

		/* Allow the menu to be selected. */

	Window -> Flags &= ~RMBTRAP;

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

	if(CycleOnStartup)
		ToggleCycleCode();

		/* "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();
		}

			/* The user picked a menu item. */

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

				if(WasCycling)
					ToggleCycleCode();

				ChangedColours	= FALSE;
				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);

								/* Reset the offsets. */

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

								/* Remake the copper list. */

							MakeScreen(Screen);
							RethinkDisplay();

							InitRequester(&Req);

							Req . Width	= 300;
							Req . Height	= 175;

							Req . LeftEdge	= (Window -> Width - Req . Width) / 2;
							Req . TopEdge	= (Window -> Height - Req . Height) / 2;

							Req . BackFill	= 1;
							Req . Flags	= NOISYREQ | SIMPLEREQ;

							Req . ReqGadget	= &ReqGad;
							Req . ReqBorder	= &ReqBrd[2];
							Req . ReqText	= &ReqIntTxt[1];

							if(Request(&Req,Window))
							{
								FOREVER
								{
									WaitPort(Window -> UserPort);

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

										ReplyMsg(Massage);

										if(Class == GADGETUP || Class == RAWKEY)
										{
											EndRequest(&Req,Window);
											goto EndReq;
										}
									}
								}
							}

EndReq:							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 5:	Screen -> ViewPort . RasInfo -> RxOffset = StartOffsetX;
							Screen -> ViewPort . RasInfo -> RyOffset = StartOffsetY;

							/* Remake the copper list. */

							MakeScreen(Screen);
							RethinkDisplay();

							PrintScreen(FALSE);

							break;

							/* Print screen (enlarged). */

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

							/* Remake the copper list. */

							MakeScreen(Screen);
							RethinkDisplay();

							PrintScreen(TRUE);

							break;

							/* Next picture. */

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

							WasCycling = FALSE;

							if(!(ViewModes & HAM))
								FadeTo(&Screen -> ViewPort,Colours,NULL,ColourNumber,0,0);

							ScreenToBack(Screen);

							CloseDisplay();

							return(0);

							/* Quit LoadImage. */

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

							WasCycling = FALSE;

							if(!(ViewModes & HAM))
								FadeTo(&Screen -> ViewPort,Colours,NULL,ColourNumber,0,0);

							ScreenToBack(Screen);

							CloseDisplay();

							return(-1);
					}
				}

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

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

				MenuNum = Item -> NextSelect;
			}
		}

			/* 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();
			}
		}
	}
}

	/* 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.
	 */

VOID
QuickSort(char **av,LONG n)
{
	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.
	 */

LONG
QSplit(char **av,LONG n)
{
	LONG i,b;
	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);
}

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

LONG Chk_Abort(VOID) { return(0); }

	/* _wb_parse(CurrentProcess,WBMsg) :
	 *
	 *	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(struct Process *CurrentProcess,struct WBStartup *WBMsg)
{
	struct FileHandle	*FileHandlePtr;		/* Window pointer. */
	BPTR			 FileWindow;		/* BCPL window pointer. */
	static char		 WindowTitle[40 + 18];	/* Title buffer. */
	SHORT			 i = 0,j = 0;		/* Counters. */

	if(WBMsg -> sm_NumArgs == 1)
		return;

		/* Set up the window title. */

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

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

	while(WindowTitle[i])
		i++;

		/* Append the tool name. */

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

		/* Window title null termination. */

	WindowTitle[40 + 17] = 0;       

		/* Open the window. */

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

		FileHandlePtr = (struct FileHandle *)BADDR(FileWindow);

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

			/* Set up console IO streams. */

		CurrentProcess -> pr_CIS		= FileWindow;
		CurrentProcess -> pr_COS		= Open("*", MODE_OLDFILE);

			/* Setup for exit to close these if we succeeded. */

		_devtab[0] . mode	&= ~O_STDIO;
		_devtab[1] . mode	&= ~O_STDIO;
	}
}

VOID
main(int argc,char **argv)
{
	struct FileRequester	*Request;
	SHORT			 i,NumItems,Error;
	char			 TempName[DSIZE * 10 + FCHARS];

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

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

		/* Workbench startup? */

	if(!argc)
	{
		extern struct WBStartup	*WBenchMsg;
		struct WBArg		*WArg;
		char			 FullPath[DSIZE * 10];

			/* Didn't select any project icons? */

		if((NumItems = WBenchMsg -> sm_NumArgs - 1) < 1)
		{
			if(Request = ArpAllocFreq())
			{
				Request -> fr_Hail = "Select file to display";

				FOREVER
				{
					if(FileRequest(Request))
					{
						strcpy(TempName,Request -> fr_Dir);

						TackOn(TempName,Request -> fr_File);

						if(LoadImage(TempName,FALSE,FALSE,FALSE,FALSE) > 0)
							DisplayBeep(NULL);
					}
					else
						break;
				}
			}

			CleanExit(RETURN_OK);
		}

			/* Take the first argument. */

		WArg = WBenchMsg -> sm_ArgList;
		WArg++;

			/* Scan the list... */

		for(i = 0 ; i < NumItems ; i++)
		{
				/* Build image file name. */

			PathName(WArg -> wa_Lock,FullPath,DSIZE * 10);

			TackOn(FullPath,WArg -> wa_Name);

			WArg++;

			Printf("Picture \33[33mNº %03ld\33[31m, \33[1m\"%s\"\33[0m loading... ",i + 1,FullPath);

			if((Error = LoadImage(FullPath,FALSE,FALSE,TRUE,(i == NumItems - 1))) < 0 || BreakCheck())
			{
				if(BreakCheck())
					Puts("*** aborted.\a");
				else
					Puts("");

				break;
			}

			switch(Error)
			{
				case ERR_NOIFF:		Printf("\33[33mNot an IFF-ILBM file.\33[31m\a");
							break;

				case ERR_NOCOLMAP:	Printf("\33[33mNo colour map.\33[31m\a");
							break;

				case ERR_NOMEM:		Printf("\33[33mNot enough memory.\33[31m\a");
							break;

				case ERR_DOS:		Printf("\33[33mError while reading.\33[31m\a");
							break;

				default:		Printf("Done.");
							break;
			}

			Puts("");
		}

		Printf("\nPlease press \33[1mRETURN\33[0m.");

		Read(Input(),FullPath,1);

		CleanExit(RETURN_OK);
	}
	else
	{
		UBYTE	 ForceScroll = FALSE,ForceLace = FALSE,Cycle = FALSE;
		SHORT	 Offset = 1,NumFiles,j;
		char	*FileArray[256],*Pat;

			/* User wants information? */

		if(argv[ARG_INFO])
		{
			Printf("\n \33[1m\33[33mLoadImage\33[31m\33[0m © Copyright 1990 by MXM\n\n");
			Printf(" Usage is: \33[1mLoadImage\33[0m [INFO] [NOMODE]/[LACE] [CYCLE] Files1 ... FileN\n");
			Printf("           \33[33mAmigaDOS\33[31m wildcards are supported!\n\n");

			Printf("           \33[1mINFO\33[0m   \33[33m»\33[31m Displays this page.\n");
			Printf("           \33[1mNOMODE\33[0m \33[33m»\33[31m Force scrollmode (ignore Hi-Res & Interlace flags).\n");
			Printf("           \33[1mLACE\33[0m   \33[33m»\33[31m Force interlaced display mode.\n");
			Printf("           \33[1mCYCLE\33[0m  \33[33m»\33[31m Force colour cycling on startup.\n\n");

			Printf("                    Note that \33[1mNOMODE\33[0m and \33[1mLACE\33[0m are \33[4mmutually exclusive\33[0m.\n\n");

			Printf(" If you wish to come in contact with \33[1mme\33[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\33[1mFederal Republic of Germany\33[0m\n\n");

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

			CleanExit(0);
		}

			/* Both modes selected? */

		if(argv[ARG_NOMODE] && argv[ARG_LACE])
		{
			Puts(CLI_Help);
			CleanExit(RETURN_WARN);
		}

			/* Force scrolling? */

		if(argv[ARG_NOMODE])
		{
			ForceScroll = TRUE;
			Offset++;
		}

			/* Interlaced display mode? */

		if(argv[ARG_LACE])
		{
			ForceLace = TRUE;
			Offset++;
		}

			/* Cycle on startup? */

		if(argv[ARG_CYCLE])
		{
			Cycle = TRUE;
			Offset++;
		}

			/* No file names given? Show the file requester. */

		if(!argv[ARG_FILES])
		{
			if(Request = ArpAllocFreq())
			{
				Request -> fr_Hail = "Select file to display";

				FOREVER
				{
					if(FileRequest(Request))
					{
						strcpy(TempName,Request -> fr_Dir);

						TackOn(TempName,Request -> fr_File);

						if((Error = LoadImage(TempName,ForceScroll,ForceLace,Cycle,FALSE)) < 0 || BreakCheck())
						{
							if(BreakCheck())
								Puts("*** aborted.\a");
							else
								Puts("");

							break;
						}

						switch(Error)
						{
							case ERR_NOIFF:		Printf("\33[33mNot an IFF-ILBM file.\33[31m\a");
										break;

							case ERR_NOCOLMAP:	Printf("\33[33mNo colour map.\33[31m\a");
										break;

							case ERR_NOMEM:		Printf("\33[33mNot enough memory.\33[31m\a");
										break;

							case ERR_DOS:		Printf("\33[33mError while reading.\33[31m\a");
										break;

							default:		Printf("Done.");
										break;
						}

						Puts("");
					}
				}
			}

			CleanExit(RETURN_OK);
		}

		NumItems = argc - Offset;

			/* Scan the argument list. */

		for(i = 0 ; i < NumItems ; i++)
		{
			if(PreParse(argv[ARG_FILES + i],TempName))
			{
				NumFiles = 0;

				while((Pat = scdir(argv[ARG_FILES + i])) && NumFiles < 255)
				{
					if(!(FileArray[NumFiles] = (char *)malloc(strlen(Pat) + 1)))
						break;

					strcpy(FileArray[NumFiles++],Pat);

					if(NumFiles == 2)
						Printf("\n\33[3mScanning directory for \33[0m\33[1m%s\33[0m...\n\n",BaseName(argv[ARG_FILES + i]));
				}

				if(NumFiles > 1)
					QuickSort(FileArray,NumFiles);

				for(j = 0 ; j < NumFiles ; j++)
				{
					if(!FileArray[j] || !FileArray[j][0])
						continue;

					Printf("Picture \33[33mNº %03ld\33[31m, \33[1m\"%s\"\33[0m loading... ",j + 1,BaseName(FileArray[j]));

					if((Error = LoadImage(FileArray[j],ForceScroll,ForceLace,Cycle,(i == NumFiles - 1))) < 0 || BreakCheck())
					{
						if(BreakCheck())
							Puts("*** aborted.\a");
						else
							Puts("");

						break;
					}

					switch(Error)
					{
						case ERR_NOIFF:		Printf("\33[33mNot an IFF-ILBM file.\33[31m\a");
									break;

						case ERR_NOCOLMAP:	Printf("\33[33mNo colour map.\33[31m\a");
									break;

						case ERR_NOMEM:		Printf("\33[33mNot enough memory.\33[31m\a");
									break;

						case ERR_DOS:		Printf("\33[33mError while reading.\33[31m\a");
									break;

						default:		Printf("Done.");
									break;
					}

					Puts("");
				}

				for(j = 0 ; j < NumFiles ; j++)
					free(FileArray[j]);
			}
			else
			{
				Printf("Picture \33[33mNº %03ld\33[31m, \33[1m\"%s\"\33[0m loading... ",i + 1,argv[ARG_FILES + i]);

				if((Error = LoadImage(argv[ARG_FILES + i],ForceScroll,ForceLace,Cycle,(i == NumItems - 1))) < 0 || BreakCheck())
				{
					if(BreakCheck())
						Puts("*** aborted.\a");
					else
						Puts("");

					break;
				}

				switch(Error)
				{
					case ERR_NOIFF:		Printf("\33[33mNot an IFF-ILBM file.\33[31m\a");
								break;

					case ERR_NOCOLMAP:	Printf("\33[33mNo colour map.\33[31m\a");
								break;

					case ERR_NOMEM:		Printf("\33[33mNot enough memory.\33[31m\a");
								break;

					case ERR_DOS:		Printf("\33[33mError while reading.\33[31m\a");
								break;

					default:		Printf("Done.");
								break;
				}

				Puts("");
			}
		}
	}

	CleanExit(RETURN_OK);
}
