/****************************************************************************
*
*	LoadImage.c 24/05/89 by:	Olaf 'Olsen' Barthel
*					Brabeckstrasse 35
*					D-3000 Hannover 71
*
*					Federal Republic of Germany
*
*	LoadImage 1.3 (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. If you prefer to overlook this rule the evil curse of
*	Toot hbr ush will rest upon you!
*
*****************************************************************************
*
*	LoadImage is a kind of IFF-ILBM-reader with some very remarkable
*	features the authors of other readers did not even dream of
*	(no I am _n_o_t_ conceited!):
*
*	o LoadImage loads anything that looks, smells and tastes like
*	  an IFF-ILBM-file, even EHB & HAM are handled correctly.
*
*	o LoadImage will accept overscanned pictures which require
*	  1MByte of chip ram.
*
*	o You can scroll around in the bitmap if the picture is larger
*	  in size than the current display.
*
*	o 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 to NTSC-images on an NTSC Amiga.
*
*	o LoadImage supports colour-cycling.
*
*	That's for the good news, now the bad news...
*
*	o LoadImage accepts only pictures whose width is greater than
*	  319 columns and whose height is greater than 199 rows.
*
*	o LoadImage works from CLI only, but a Workbench controlled
*	  version will follow soon.
*
*	o LoadImage can only display one picture at a time (similar to
*	  Display by the honoured Carolyn Scheppner).
*
*	o The cycling code will not be distributed along with the
*	  rest of the code (until my producer gives me permission
*	  to do so).
*
*	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.
*
*	This program has still some weaknesses, e.g. very large
*	pictures take very long to load if they do not contain
*	a CAMG or CRNG chunk. This happens because of the chunk
*	reading routine which tries to filter the whole file.
*	But it's still better than a recursive descent parser,
*	isn't it?
*
*	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 Steve 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
*
****************************************************************************/

#include <intuition/intuitionbase.h>
#include <graphics/gfxbase.h>
#include <exec/memory.h>
#include <stdio.h>

	/* Some useful macros. */

#define byte(n) (((n + 15) >> 4) << 1)		/* Width in words. */
#define Skip(Bytes,File) fseek(File,Bytes,1)	/* Skip some 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;		/* Speeeeeeed! */
	WORD active;		/* What shall we do with a drunken... */
	UBYTE low,high;		/* That's where we start and stop. */
} CRange;

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

	/* Whoa! What do we need this font for? Guess what, its the
	 * menu font!
	 */

struct TextAttr MenuTxtAttr = {(UBYTE *)"topaz.font",8,FS_NORMAL,FPF_ROMFONT};

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

struct IntuiText MenuIntTxt[4] =
{
	{0,1,1,0,0,(struct TextAttr *)&MenuTxtAttr,(UBYTE *)"About Loadimage...      ",(struct IntuiText *)NULL},
	{0,1,1,0,0,(struct TextAttr *)&MenuTxtAttr,(UBYTE *)"------------------------",(struct IntuiText *)NULL},
	{0,1,1,0,0,(struct TextAttr *)&MenuTxtAttr,(UBYTE *)"Toggle cycling          ",(struct IntuiText *)NULL},
	{0,1,1,0,0,(struct TextAttr *)&MenuTxtAttr,(UBYTE *)"Quit Loadimage...       ",(struct IntuiText *)NULL}
};

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

struct MenuItem MenuItem[4] =
{
	{(struct MenuItem *)&MenuItem[1],0, 0,192,8, 86,0,(APTR)&MenuIntTxt[0],0,63,(struct MenuItem *)NULL,65535},
	{(struct MenuItem *)&MenuItem[2],0, 8,192,8,210,0,(APTR)&MenuIntTxt[1],0, 0,(struct MenuItem *)NULL,65535},
	{(struct MenuItem *)&MenuItem[3],0,15,192,8, 86,0,(APTR)&MenuIntTxt[2],0,67,(struct MenuItem *)NULL,65535},
	{(struct MenuItem *)NULL,        0,24,192,8, 86,0,(APTR)&MenuIntTxt[3],0,81,(struct MenuItem *)NULL,65535}
};

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

struct Menu Menu = {(struct Menu *)NULL,0,0,120,0,257,"LoadImage v1.3",(struct MenuItem *)&MenuItem[0]};

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

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

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

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

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

	{0,1,JAM1,8,122,(struct TextAttr *)&AboutFont[2],(UBYTE *)"(C) Copyright 1989 by...",		(struct IntuiText *)&AboutTxt[14]},
	{0,1,JAM1,8,130,(struct TextAttr *)&AboutFont[2],(UBYTE *)"ED Electronic Design Hannover",	(struct IntuiText *)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,
	(struct TextAttr *)&AboutFont[0],
	(UBYTE *)"Understood",
	(struct IntuiText *)NULL
};

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

struct BitMap ScreenMap;

	/* Must be defined externally since two routines need it. */

BitMapHeader InfoHeader;

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

struct NewScreen NewScreen =
{
	0,0,0,0,0,		/* Gets filled in later. */
	0,0,			/* Pens. */
	NULL,			/* ViewModes, gets filled in later. */
	CUSTOMSCREEN | SCREENBEHIND | SCREENQUIET | CUSTOMBITMAP,
	(struct TextAttr *)NULL,/* No private font? We don't have a title text either! */
	(STRPTR)NULL,		/* No title text. */
	(struct Gadget *)NULL,	/* No gagdets. */
	(struct BitMap *)&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,	/* We want to know about this. */
	BORDERLESS | ACTIVATE | 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;

	/* This is where we get the input from. */

ULONG	Class;
USHORT	Code;

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

#define SetPoint(wind) SetPointer(wind,PointData,16,16,-4,-3)

	/* Sprite dump for the tiny pointer image. */

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

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

	0x0000,0x0000
};

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

BOOL
FindChunk(ChunkName,FilePointer)
register char *ChunkName;
register FILE *FilePointer;
{
	register char NextByte;

		/* Read until EOF. */

	while(!feof(FilePointer))
	{
			/* Get the first byte. */

		NextByte = fgetc(FilePointer);

			/* Is this what we want? */

GetNext:	if(NextByte == ChunkName[0])
		{
				/* Is the next byte what we want? */

			if((NextByte = fgetc(FilePointer)) != ChunkName[1])
				goto GetNext;

				/* Is the next byte what we want? */

			if((NextByte = fgetc(FilePointer)) != ChunkName[2])
				goto GetNext;

				/* Is the next byte what we want? */

			if((NextByte = fgetc(FilePointer)) != ChunkName[3])
				goto GetNext;

				/* Yes it is. */

			return(TRUE);
		}
	}

		/* Sad news, no such chunk to be found. */

	return(FALSE);
}

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

		/* Skip length information. */

	Skip(sizeof(LONG),ImgFile);

		/* Read the header. */

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

		/* Strange values, probably not a picture but
		 * "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 much more likely to encounter a six plane
		 * HAM picture than a six plane EHB picture. If we
		 * are wrong with this assumption, the CAMG chunk
		 * will tell us.
		 */

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

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

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

		/* Skip length entry. */

	Skip(sizeof(LONG),ImgFile);

		/* 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 char R,G,B;

		/* Are you there? */

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

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

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

	Skip(sizeof(LONG),ColFile);

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

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[8];
	FILE *PicFile;

		/* Set up the working copies. */

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

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

	Skip(sizeof(LONG),PicFile);

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

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

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

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

		/* ByteRun1 compression, effective 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)
					{
						fread(Planes[j],ChkVal + 1,1,PicFile);

						Planes[j] += ChkVal + 1;
						SoFar += ChkVal + 1;
					}
					else
					{
							/* Set the memory to this
							 * number.
							 */

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

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

		/* Finish it up. */

	fclose(PicFile);

	return(TRUE);
}

	/* CloseAll(RetCode) :
	 *
	 *	Closes anything that we have opened and locks the
	 *	shop.
	 */

void
CloseAll(RetCode)
long RetCode;
{
	register short i;

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

	ClearCycleTask();

		/* A window! */

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

		Window -> Flags |= RMBTRAP;

			/* Avoid the problem with dead messages lying
			 * around in memory.
			 */

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

			/* We don't need the menu anymore. */

		ClearMenuStrip(Window);

		CloseWindow(Window);
	}

		/* Is there a screen anywhere? */

	if(Screen)
		CloseScreen(Screen);

		/* Get rid of the planes. */

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

		/* Libraries? Who wanted the libraries? */

	if(GfxBase);
		CloseLibrary(GfxBase);

	if(IntuitionBase)
		CloseLibrary(IntuitionBase);

		/* Return the control to DOS. */

	exit(RetCode);
}

	/* 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;
	ULONG ViewModes;
	LONG ColourNumber;		/* Number of colours. */

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

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

	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? */
	char *PicName = argv[1];	/* If we are to scroll around. */

		/* Arrgh! Stupid user tries to run us from Workbench! */

	if(!argc)
		exit(20);

		/* If "-F" is specified, all HIRES and LACE flags
		 * will be ignored.
		 */

	if(!strcmp(argv[1],"-F") || !strcmp(argv[1],"-f"))
	{
		ForceScroll = TRUE;
		PicName = argv[2];
	}

		/* Some capital letters. */

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

		/* "Help! I need somebody; Help! not just anybody..." */

	if(argv[1][0] == '?' || argc < 2 || (ForceScroll && argc < 3))
	{
		printf("\33[1m\33[33mLoadImage\33[31m\33[0m © 1989 ED Hannover\n");
		printf("Usage is: \33[1m%s\33[0m [-F] <IFF-ILBM>\n",argv[0]);

		exit(0);
	}

		/* The libraries we need and do not like. */

	if(!(IntuitionBase = (struct IntuitionBase *)OpenLibrary("intuition.library",LIBRARY_VERSION)))
		CloseAll(20);

	if(!(GfxBase = (struct GfxBase *)OpenLibrary("graphics.library",LIBRARY_VERSION)))
		CloseAll(20);

		/* Everything okay with the ViewModes? */

	if((ViewModes = LoadHeader(PicName,&InfoHeader)) == -1)
	{
		printf("%s: Couldn't load \"%s\"!\n",argv[0],PicName);
		CloseAll(10);
	}

		/* Are there any colours out there? */

	if(!(ColourNumber = LoadCMAP(PicName,Colours,32,&InfoHeader)))
	{
		printf("%s: Couldn't load \"%s\"!\n",argv[0],PicName);
		CloseAll(10);
	}

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

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

		/* Forced scrolling? */

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

		/* Hires or lores? */

	if(ViewModes & HIRES)
		Width = 640;
	else
		Width = 320;

		/* Interlaced or not? */

	if(ViewModes & LACE)
		if(Height > (GfxBase -> NormalDisplayRows * 2))
			Height = GfxBase -> NormalDisplayRows * 2;

	if(!(ViewModes & LACE))
		if(Height > GfxBase -> NormalDisplayRows)
			Height = GfxBase -> NormalDisplayRows;

		/* 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)
	{
		printf("%s: Image smaller than screen!\n",argv[0]);
		CloseAll(10);
	}

		/* 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)))
		{
			printf("%s: Not enough memory for screen raster!\n",argv[0]);
			CloseAll(10);
		}
	}

		/* Open the screen. */

	if(!(Screen = (struct Screen *)OpenScreen(&NewScreen)))
	{
		printf("%s: Couldn't open screen!\n",argv[0]);
		CloseAll(10);
	}

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

	ShowTitle(Screen,FALSE);

	NewWindow . Screen = Screen;

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

		/* Try to open the window. */

	if(!(Window = (struct Window *)OpenWindow(&NewWindow)))
	{
		printf("%s: Couldn't open window!\n",argv[0]);
		CloseAll(10);
	}

		/* Install the menu and the pointer. */

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

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

	if(!LoadRaster(PicName,ScreenMap . Planes,&InfoHeader))
	{
		printf("%s: Error while reading image data!\n",argv[0]);
		CloseAll(10);
	}

		/* Do we allow colour cycling? */

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

		/* Bring the screen to the front. */

	ScreenToFront(Screen);

		/* Allow the menu to be selected. */

	Window -> Flags &= ~RMBTRAP;

		/* "Ewig währt am längsten." : Kurt Schwitters (1887 - 1948). */

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

			if(Class == MENUVERIFY)
			{
					/* 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 in
			 * the picture.
			 */

		if(Class == MOUSEBUTTONS && Code == SELECTDOWN)
			WeAreScrolling = TRUE;

			/* The user picked a menu item. */

		if(Class == MENUPICK)
		{
			MenuNum = Code;

				/* Until the last event is traced. */

			while(MenuNum != MENUNULL)
			{
				if(MENUNUM(MenuNum) == 0)
				{
					switch(ITEMNUM(MenuNum))
					{
						case 0:	AutoRequest(Window,&AboutTxt[0],NULL,&Proceed,NULL,NULL,274,176);
							break;

						case 2:	ToggleCycleTask();
							break;

						case 3:	ScreenToBack(Screen);
							CloseAll(0);
					}
				}

				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
			 * 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(Class == MOUSEBUTTONS && Code == SELECTUP)
				WeAreScrolling = FALSE;

				/* Nothing happened. */

			RemakeTheView = FALSE;

				/* Left border, scroll left. */

			if(Window -> MouseX == 0 && (Screen -> ViewPort . RasInfo -> RxOffset - 1 >= StartOffsetX))
			{
				Screen -> ViewPort . RasInfo -> RxOffset--;
				RemakeTheView = TRUE;
			}

				/* Top border, scroll up. */

			if(Window -> MouseY == 0 && (Screen -> ViewPort . RasInfo -> RyOffset - 1 >= StartOffsetY))
			{
				Screen -> ViewPort . RasInfo -> RyOffset--;
				RemakeTheView = TRUE;
			}

				/* Right border, scroll right. */

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

				/* Bottom border, scroll down. */

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

				/* Are we to scroll the ViewPort? */

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