/*****************************************************************************
*   "Gif-Lib" - Yet another gif library.				     *
*									     *
* Written by:  Gershon Elber				Ver 0.1, Jul. 1989   *
******************************************************************************
* Program to display GIF file using the BGI device indepedent routines       *
* Options:								     *
* -q : quite printing mode.						     *
* -d BGI path : specify the directory where to look for bgi drivers.	     *
* -u BGIUserDriverName.Mode : use user driver instead of auto detection.     *
* -z factor : zoom the pixels by the given factor.			     *
* -b : beeps disabled.							     *
* -h : on line help.							     *
*									     *
*   In this file Screen refers to GIF file screen, while Device to BGI size. *
******************************************************************************
* History:								     *
* 31 Jul 90 - Version 1.0 by Gershon Elber.				     *
*****************************************************************************/

#include <graphics.h>
#include <stdio.h>
#include <stdlib.h>
#include <conio.h>
#include <ctype.h>
#include <alloc.h>
#include <string.h>
#include <io.h>
#include <dos.h>
#include <bios.h>
#include <fcntl.h>
#include "gif_lib.h"
#include "getarg.h"

#define PROGRAM_NAME	"Gif2BGI"

#define KEY_LEFT	256	  /* Key Codes returned for operational keys */
#define KEY_RIGHT	257	  /* as return by the GetKey routine.	     */
#define KEY_UP		258
#define KEY_DOWN	259
#define KEY_RETURN	260
#define KEY_DELETE	261
#define KEY_INSERT	262
#define KEY_BSPACE	263
#define KEY_ESC		264
#define KEY_HOME	265
#define KEY_END		266
#define KEY_PGUP	267
#define KEY_PGDN	268

#define SET_POSITION_RESET	0	    /* Situations need positionings: */
#define SET_POSITION_ZOOM_U	1
#define SET_POSITION_ZOOM_D	2
#define SET_POSITION_PAN	3

#define CURSOR_TEXT_X	120

#define BGI_USER_INSTALL	999	/* BGI User installed driver device. */

#define MIN(x, y)	((x) < (y) ? (x) : (y))

extern unsigned int
    _stklen = 16384;			     /* Increase default stack size. */

static char
    *VersionStr =
	PROGRAM_NAME
	GIF_LIB_VERSION
	"	Gershon Elber,	"
	__DATE__ ",   " __TIME__ "\n"
	"(C) Copyright 1989 Gershon Elber, Non commercial use only.\n";
static char
    *CtrlStr =
	PROGRAM_NAME
	" q%- d%-BGI|Directory!s u%-UserBGIDrv.Mode!s z%-ZoomFactor!d b%- h%- GifFile!*s";
static char
    *GifFileName,
    *BGIPath = "",
    *BGIUserDriverName = NULL;

/* Make some variables global, so we could access them faster: */
static int
    ImageNum = 0,
    BackGround = 0,
    ForeGround = 1,			   /* As close to white as possible. */
    BeepsDisabled = FALSE,
    ZoomFactor = 1,
    MaximumScreenHeight = 1,
    BGIUserDriverMode = -1,
    DeviceMaxX = 640, DeviceMaxY = 400,	      /* Physical device dimensions. */
    ScreenWidth = 320, ScreenHeight = 200,         /* Gif image screen size. */
    InterlacedOffset[] = { 0, 4, 2, 1 }, /* The way Interlaced image should. */
    InterlacedJumps[] = { 8, 8, 4, 2 };    /* be read - offsets and jumps... */
static GifColorType
    *ColorMap;

static int huge detectVGA(void);
static void BGIInstallUserDriver(char *BGIUserDriverNameMode);
static void DisplayScreen(GifRowType *ScreenBuffer, GifFileType *GifFile);
static void PrintSettingStatus(GifFileType *GifFile);
static void CPrintStr(char *Str, int y);
static void SetPosition(int Why,
		        int *ScreenLeft, int *ScreenTop,
		        int *DeviceLeft, int *DeviceTop,
		        int MoveX,       int MoveY);
static void DrawScreen(GifRowType *ScreenBuffer,
		int ScreenLeft, int ScreenTop, int DeviceLeft, int DeviceTop);
static void DoCursorMode(GifRowType *ScreenBuffer,
		int ScreenLeft, int ScreenTop, int DeviceLeft, int DeviceTop);
static int MyKbHit(void);
static int MyGetCh(void);
static int GetKey(void);
static void Tone(int Frequency, int Time);

/******************************************************************************
* Interpret the command line and scan the given GIF file.		      *
******************************************************************************/
void main(int argc, char **argv)
{
    int	i, j, k, Error, NumFiles, Size, Row, Col, Width, Height, ExtCode,
	Count, ColorMapSize, GraphDriver, GraphMode, Sum,
	HelpFlag = FALSE,
	BGIPathFlag = FALSE,
	BGIUserDriverFlag = FALSE,
	ZoomFlag = FALSE;
    GifRecordType RecordType;
    GifByteType *Extension;
    char Str[80], *BGIUserDriverNameMode,
	**FileName = NULL;
    GifRowType *ScreenBuffer;
    GifFileType *GifFile;
    struct text_info TextInfo;      /* So we can restore starting text mode. */

    if ((Error = GAGetArgs(argc, argv, CtrlStr,
		&GifQuitePrint, &BGIPathFlag, &BGIPath,
		&BGIUserDriverFlag, &BGIUserDriverNameMode,
		&ZoomFlag, &ZoomFactor,
		&BeepsDisabled, &HelpFlag,
		&NumFiles, &FileName)) != FALSE ||
		(NumFiles > 1 && !HelpFlag)) {
	if (Error)
	    GAPrintErrMsg(Error);
	else if (NumFiles > 1)
	    GIF_MESSAGE("Error in command line parsing - one GIF file please.");
	GAPrintHowTo(CtrlStr);
	exit(1);
    }

    if (HelpFlag) {
	fprintf(stderr, VersionStr);
	GAPrintHowTo(CtrlStr);
	exit(0);
    }

    if (BGIUserDriverFlag) {
	/* Use the driver supplied by the user! */
        BGIInstallUserDriver(BGIUserDriverNameMode);
        installuserdriver(BGIUserDriverName, detectVGA);
	GraphDriver = BGI_USER_INSTALL;
    }
    else {
        /* Sense type of display we have and attempt to load right driver. */
        detectgraph(&GraphDriver, &GraphMode);
        if (GraphDriver < 0)
	    GIF_EXIT("BGI Auto detect: No graphics device detected.");
    }

    /* Put in the following any graphic driver specific setup: */
    switch (GraphDriver) {
	case CGA:
	    GraphMode = CGAHI;
	    break;
	case EGA:
	    GraphMode = EGAHI;
	    break;
	case EGA64:
	    GraphMode = EGA64HI;
	    break;
	case EGAMONO:
	    GraphMode = EGAMONOHI;
	    break;
	case HERCMONO:
	    GraphMode = HERCMONOHI;
	    break;
	case VGA:
	    GraphMode = VGAHI;
	    break;
	case BGI_USER_INSTALL:
	    GraphDriver = DETECT;
	    GraphMode = BGIUserDriverMode;
	    break;
	default:
	    GIF_EXIT("Requested graphic device is not supported.");
	    break;
    }

    if (NumFiles == 1) {
	GifFileName = *FileName;
	if ((GifFile = DGifOpenFileName(*FileName)) == NULL) {
	    PrintGifError();
	    exit(-1);
	}
    }
    else {
	/* Use the stdin instead: */
	GifFileName = "Stdin";
	setmode(0, O_BINARY);
	if ((GifFile = DGifOpenFileHandle(0)) == NULL) {
	    PrintGifError();
	    exit(-1);
	}
    }

    /* Allocate the screen as vector of column of rows. We cannt allocate    */
    /* the all screen at once, as this broken minded CPU can allocate up to  */
    /* 64k at a time and our image can be bigger than that:		     */
    /* Note this screen is device independent - its the screen as defined by */
    /* the GIF file parameters itself.					     */
    if ((ScreenBuffer = (GifRowType *)
	malloc(GifFile -> SHeight * sizeof(GifRowType *))) == NULL)
	    GIF_EXIT("Failed to allocate memory required, aborted.");

    Size = GifFile -> SWidth * sizeof(GifPixelType);/* Size in bytes of one row.*/
    if ((ScreenBuffer[0] = (GifRowType) malloc(Size)) == NULL)    /* First row. */
	GIF_EXIT("Failed to allocate memory required, aborted.");

    for (i = 0; i < GifFile -> SWidth; i++)  /* Set its color to BackGround. */
	ScreenBuffer[0][i] = GifFile -> SBackGroundColor;
    MaximumScreenHeight = GifFile -> SHeight - 1;
    for (i = 1; i < GifFile -> SHeight; i++) {
	/* Allocate the other rows, and set their color to background too: */
	if ((ScreenBuffer[i] = (GifRowType) malloc(Size)) == NULL) {
	    if (i > 30) {
		/* Free some memory for the BGI driver and auxilary. */
		for (j = 1; j < 28; j++)
	    	    free((char *) ScreenBuffer[i - j]);
	    	MaximumScreenHeight = i - 28;
	    	fprintf(stderr, "\n%s: Failed to allocate all memory required, last line %d.\n",
			PROGRAM_NAME, MaximumScreenHeight);
	    	break;
	    }
	    else
		GIF_EXIT("Failed to allocate memory required, aborted.");
	}

	memcpy(ScreenBuffer[i], ScreenBuffer[0], Size);
    }

    /* Scan the content of the GIF file and load the image(s) in: */
    do {
	if (DGifGetRecordType(GifFile, &RecordType) == GIF_ERROR) {
	    PrintGifError();
	    break;
	}
	switch (RecordType) {
	    case IMAGE_DESC_RECORD_TYPE:
		if (DGifGetImageDesc(GifFile) == GIF_ERROR) {
		    PrintGifError();
		    exit(-1);
		}

		Row = GifFile -> ITop; /* Image Position relative to Screen. */
		Col = GifFile -> ILeft;
		Width = GifFile -> IWidth;
		Height = GifFile -> IHeight;
		GifQprintf("\n%s: Image %d at (%d, %d) [%dx%d]:     ",
		    PROGRAM_NAME, ++ImageNum, Col, Row, Width, Height);
		if (GifFile -> ILeft + GifFile -> IWidth > GifFile -> SWidth ||
		   GifFile -> ITop + GifFile -> IHeight > GifFile -> SHeight) {
		    fprintf(stderr, "Image %d is not confined to screen dimension, aborted.\n");
		    exit(-2);
		}
		if (GifFile -> IInterlace) {
		    /* Need to perform 4 passes on the images: */
		    for (Count = i = 0; i < 4; i++)
			for (j = Row + InterlacedOffset[i]; j < Row + Height;
						 j += InterlacedJumps[i]) {
			    GifQprintf("\b\b\b\b%-4d", Count++);
			    if (DGifGetLine(GifFile,
				&ScreenBuffer[MIN(j, MaximumScreenHeight)][Col],
				Width) == GIF_ERROR) {
				PrintGifError();
				exit(-1);
			    }
			}
		}
		else {
		    for (i = 0; i < Height; i++, Row++) {
			GifQprintf("\b\b\b\b%-4d", i);
			if (DGifGetLine(GifFile, &ScreenBuffer[MIN(Row, MaximumScreenHeight)][Col],
				Width) == GIF_ERROR) {
			    PrintGifError();
			    MaximumScreenHeight = MIN(i - 1, MaximumScreenHeight);
			}
		    }
		}
		break;
	    case EXTENSION_RECORD_TYPE:
		/* Skip any extension blocks in file: */
		if (DGifGetExtension(GifFile, &ExtCode, &Extension) == GIF_ERROR) {
		    PrintGifError();
		    exit(-1);
		}
		while (Extension != NULL) {
		    if (DGifGetExtensionNext(GifFile, &Extension) == GIF_ERROR) {
			PrintGifError();
			exit(-1);
		    }
		}
		break;
	    case TERMINATE_RECORD_TYPE:
		break;
	    default:		    /* Should be traps by DGifGetRecordType. */
		break;
	}
    }
    while (RecordType != TERMINATE_RECORD_TYPE);

    /* Lets display it - set the global variables required and do it: */
    BackGround = GifFile -> SBackGroundColor;
    ColorMap = (GifFile -> IColorMap ? GifFile -> IColorMap :
				       GifFile -> SColorMap);
    ColorMapSize = 1 << (GifFile -> IColorMap ? GifFile -> IBitsPerPixel :
						GifFile -> SBitsPerPixel);

    gettextinfo(&TextInfo);	     /* Save current mode so we can recover. */

    initgraph(&GraphDriver, &GraphMode, BGIPath);
    if (graphresult() != grOk)	 	       /* Error occured during init. */
	GIF_EXIT("Graphics System Error, failed to initialize driver.");

    if (getmaxcolor() + 1 < ColorMapSize) {
	sprintf(Str, "GIF Image color map (%d) is too big for device (%d).\n",
					      ColorMapSize, getmaxcolor() + 1);
	closegraph();
	GIF_EXIT(Str);
    }

    /* Initialize hardware pallete and also select fore/background color.    */
    BackGround = ForeGround = 1;
    Sum = ((int) ColorMap[1].Red) +
	  ((int) ColorMap[1].Green) +
	  ((int) ColorMap[1].Blue);
    j = k = Sum;
    for (i = 0; i < ColorMapSize; i++) {
	setrgbpalette(i, ColorMap[i].Red >> 2,
			 ColorMap[i].Green >> 2,
			 ColorMap[i].Blue >> 2);

	Sum = ((int) ColorMap[i].Red) +
	      ((int) ColorMap[i].Green) +
	      ((int) ColorMap[i].Blue);
	if (i != 0 && Sum > j) {			/* Dont use color 0. */
	    ForeGround = i;
	    j = Sum;
	}
	if (i != 0 && Sum <= k) {			/* Dont use color 0. */
	    BackGround = i;
	    k = Sum;
	}
    }

    DeviceMaxX = getmaxx();		    /* Read size of physical screen. */
    DeviceMaxY = getmaxy();
    ScreenWidth = GifFile -> SWidth;
    ScreenHeight = MIN(GifFile -> SHeight, MaximumScreenHeight);

    Tone(500, 10);
    DisplayScreen(ScreenBuffer, GifFile);

    if (DGifCloseFile(GifFile) == GIF_ERROR) {
	PrintGifError();
	closegraph();
	exit(-1);
    }

    closegraph();

    textmode(TextInfo.currmode);
}

/****************************************************************************
* Routine to be called for the user installed driver:			    *
****************************************************************************/
static int huge detectVGA(void)
{
    return BGIUserDriverMode;
}

/****************************************************************************
* Routine to install the user BGI driver information.			    *
****************************************************************************/
static void BGIInstallUserDriver(char *BGIUserDriverNameMode)
{
    char *p;

    if ((p = strrchr(BGIUserDriverNameMode, '/')) != NULL ||
	(p = strrchr(BGIUserDriverNameMode, '\\')) != NULL) {
	p[0] = 0;
	BGIPath = strdup(BGIUserDriverNameMode);
	p++;
    }

    p = strtok(p, ".");
    BGIUserDriverName = strdup(p);

    p = strtok(NULL, ".");
    if (sscanf(p, "%d", &BGIUserDriverMode) != 1 || BGIUserDriverName == NULL)
	GIF_EXIT("User [-u] BGI specification has wrong format.");
}

/******************************************************************************
* Given the screen buffer, display it:					      *
* The following commands are available (case insensitive).		      *
* 1. Four arrow to move along the screen (only if ScreenBuffer > physical     *
*    screen in that direction.						      *
* 2. C - goto cursor mode - print current color & position in GIF screen      *
*        of the current pixel cursor is on.				      *
* 3. D - zoom out by factor of 2.					      *
* 4. R - redraw current image.						      *
* 5. S - Print Current status/options.					      *
* 6. U - zoom in by factor of 2.					      *
* 7. ' ' - stop drawing current image.					      *
* 8. ESC - to quit.							      *
******************************************************************************/
static void DisplayScreen(GifRowType *ScreenBuffer, GifFileType *GifFile)
{
    int DeviceTop, DeviceLeft,   /* Where ScreenBuffer is to mapped to ours. */
	ScreenTop, ScreenLeft,  /* Porsion of ScreenBuffer to start display. */
	XPanning, YPanning,		 /* Amount to move using the arrows. */
	GetK, DrawIt = TRUE;

    XPanning = DeviceMaxX / 2;
    YPanning = DeviceMaxY / 2;

    SetPosition(SET_POSITION_RESET,
		&ScreenLeft, &ScreenTop,
		&DeviceLeft, &DeviceTop,
		0, 0);

    do {
	if (DrawIt && !MyKbHit()) {
	    DrawScreen(ScreenBuffer, ScreenLeft, ScreenTop,
				     DeviceLeft, DeviceTop);
	    Tone(2000, 200);
	}
	DrawIt = TRUE;
	switch (GetK = GetKey()) {
	    case 'C':
		DoCursorMode(ScreenBuffer, ScreenLeft, ScreenTop,
					   DeviceLeft, DeviceTop);
		DrawIt = TRUE;
		break;
	    case 'D':
		if (ZoomFactor > 1) {
		    ZoomFactor >>= 1;
		    SetPosition(SET_POSITION_ZOOM_D,
			&ScreenLeft, &ScreenTop,
			&DeviceLeft, &DeviceTop,
			0, 0);
		}
		else {
		    Tone(1000, 100);
		    DrawIt = FALSE;
		}
		break;
	    case 'R':
		break;
	    case 'S':
		PrintSettingStatus(GifFile);
		break;
	    case 'U':
		if (ZoomFactor < 256) {
		    ZoomFactor <<= 1;
		    SetPosition(SET_POSITION_ZOOM_U,
			&ScreenLeft, &ScreenTop,
			&DeviceLeft, &DeviceTop,
			0, 0);
		}
		else {
		    Tone(1000, 100);
		    DrawIt = FALSE;
		}
		break;
	    case KEY_ESC:
		break;
	    case KEY_LEFT:
		SetPosition(SET_POSITION_PAN,
			&ScreenLeft, &ScreenTop,
			&DeviceLeft, &DeviceTop,
			-XPanning, 0);
		break;
	    case KEY_RIGHT:
		SetPosition(SET_POSITION_PAN,
			&ScreenLeft, &ScreenTop,
			&DeviceLeft, &DeviceTop,
			XPanning, 0);
		break;
	    case KEY_UP:
		SetPosition(SET_POSITION_PAN,
			&ScreenLeft, &ScreenTop,
			&DeviceLeft, &DeviceTop,
			0, -YPanning);
		break;
	    case KEY_DOWN:
		SetPosition(SET_POSITION_PAN,
			&ScreenLeft, &ScreenTop,
			&DeviceLeft, &DeviceTop,
			0, YPanning);
		break;
	    default:
		DrawIt = FALSE;
		Tone(800, 100);
		Tone(300, 200);
		break;
	}
    }
    while (GetK != KEY_ESC);
}

/******************************************************************************
* Routine to print (in text mode), current program status.		      *
******************************************************************************/
static void PrintSettingStatus(GifFileType *GifFile)
{
    char s[80];

    setcolor(ForeGround);

    CPrintStr(PROGRAM_NAME, 1);

    sprintf(s, "GIF File - %s.", GifFileName);
    CPrintStr(s, 10);

    sprintf(s, "Gif Screen Size = [%d, %d]. Contains %d image(s).",
	GifFile -> SWidth, GifFile -> SHeight, ImageNum);
    CPrintStr(s, 20);

    if (GifFile -> SColorMap)
	sprintf(s,
	    "Has Screen Color map of %d bits. BackGround = [%d, %d, %d].",
	    GifFile -> SBitsPerPixel,
	    GifFile -> SColorMap[GifFile -> SBackGroundColor].Red,
	    GifFile -> SColorMap[GifFile -> SBackGroundColor].Green,
	    GifFile -> SColorMap[GifFile -> SBackGroundColor].Blue);
    else
	sprintf(s, "No Screen color map.");
    CPrintStr(s, 30);

    if (GifFile -> IColorMap)
	sprintf(s, "Has Image map of %d bits (last image). Image is %s.",
	    GifFile -> IBitsPerPixel,
	    (GifFile -> IInterlace ? "interlaced" : "non interlaced"));
    else
	sprintf(s, "No Image color map.");
    CPrintStr(s, 40);

    CPrintStr("Press anything to continue:", 60);
    MyGetCh();
}

/******************************************************************************
* Routine to cprintf given string centered at given Y level, and attr:        *
******************************************************************************/
static void CPrintStr(char *Str, int y)
{
    setfillstyle(SOLID_FILL, BackGround);
    bar(0, y, textwidth(Str) + 2, y + textheight(Str) + 2);

    setcolor(ForeGround);
    outtextxy(1, y + 1, Str);
}

/******************************************************************************
* Routine to set the position of Screen in Device, and what porsion of the    *
* screen should be visible:						      *
* MoveX, MoveY are the panning factors (if both zero - initialize).	      *
******************************************************************************/
static void SetPosition(int Why,
		        int *ScreenLeft, int *ScreenTop,
		        int *DeviceLeft, int *DeviceTop,
		        int MoveX,       int MoveY)
{

    MoveX /= ZoomFactor;     /* Make sure move move same amount independent. */
    MoveY /= ZoomFactor;			   /* of what ZommFactor is. */

    /* Figure out position of GIF file in real device X axis: */
    if (ScreenWidth * ZoomFactor <= DeviceMaxX + 1) {
	/* Device is big enough to hold all the image X axis: */
	*ScreenLeft = 0;
	*DeviceLeft = (DeviceMaxX - ScreenWidth * ZoomFactor) / 2;
    }
    else {
	/* Device is too small to hold all the image X axis: */
	switch (Why) {
	    case SET_POSITION_RESET:
		*ScreenLeft = 0;
		break;
	    case SET_POSITION_ZOOM_U:
		*ScreenLeft += DeviceMaxX / (2 * ZoomFactor);
		break;
	    case SET_POSITION_ZOOM_D:
		*ScreenLeft -= DeviceMaxX / (4 * ZoomFactor);
		break;
	    case SET_POSITION_PAN:
		if (MoveX != 0) *ScreenLeft += MoveX;
		break;
	}
	if (*ScreenLeft < 0) *ScreenLeft = 0;
	if ((ScreenWidth - *ScreenLeft) * ZoomFactor < DeviceMaxX + 1)
	    *ScreenLeft = (ScreenWidth * ZoomFactor -
						DeviceMaxX + 1) / ZoomFactor;
	*DeviceLeft = 0;
    }

    /* Figure out position of GIF file in real device Y axis: */
    if (ScreenHeight * ZoomFactor <= DeviceMaxY + 1) {
	/* Device is big enough to hold all the image Y axis: */
	*ScreenTop = 0;
	*DeviceTop = (DeviceMaxY - ScreenHeight * ZoomFactor) / 2;
    }
    else {
	/* Device is too small to hold all the image Y axis: */
	switch (Why) {
	    case SET_POSITION_RESET:
		*ScreenTop = 0;
		break;
	    case SET_POSITION_ZOOM_U:
		*ScreenTop += DeviceMaxY / (2 * ZoomFactor);
		break;
	    case SET_POSITION_ZOOM_D:
		*ScreenTop -= DeviceMaxY / (4 * ZoomFactor);
		break;
	    case SET_POSITION_PAN:
		if (MoveY != 0) *ScreenTop += MoveY;
		break;
	}
	if (*ScreenTop < 0) *ScreenTop = 0;
	if ((ScreenHeight - *ScreenTop) * ZoomFactor < DeviceMaxY + 1)
	    *ScreenTop = (ScreenHeight * ZoomFactor -
						 DeviceMaxY - 1) / ZoomFactor;
	*DeviceTop = 0;
    }

    /* Make sure the position is on Byte boundary (8 pixels per byte): */
    *DeviceLeft &= 0xfff8;
}

/******************************************************************************
* The real drawing of the image is performed here. Few things are taken into  *
* account:								      *
* 1. The zoom factor. If > 1 each pixel is multiplied this amount vertically  *
*    and horizontally.							      *
*   The image is drawn from ScreenBuffer ScreenTop/Left in the bottom/right   *
* directions, onto the Device DeviceTop/Left in the bottom/right direction    *
*   Pressing space during drawing will abort this routine.		      *
******************************************************************************/
static void DrawScreen(GifRowType *ScreenBuffer,
		int ScreenLeft, int ScreenTop, int DeviceLeft, int DeviceTop)
{
    int i, j, k, l, CountZoomJ, CountZoomI,
	DeviceWidth, DeviceRight, ScreenBottom;
    unsigned char *ImageBuffer, *p;
    GifPixelType *Line;

    /* Make sure we start from scratch. Note cleardevice() uses color 0 even */
    /* if it may be non black.						     */
    cleardevice();

    if (getmaxcolor() + 1 == 256) {
	/* Optimize this case - one byte per pixel. */
    	DeviceWidth = ScreenWidth * ZoomFactor;
	if (DeviceWidth + DeviceLeft > DeviceMaxX)
	    DeviceWidth = DeviceMaxX - DeviceLeft;
    	DeviceRight = DeviceLeft + DeviceWidth - 1;
    	ScreenBottom = ScreenTop + ScreenHeight - 1;

	if ((ImageBuffer = malloc(imagesize(DeviceLeft, DeviceTop,
					    DeviceRight, DeviceTop))) == NULL)
	    GIF_EXIT("Failed to allocate memory required, aborted.");
	getimage(DeviceLeft, DeviceTop, DeviceRight, DeviceTop, ImageBuffer);

	for (k = DeviceTop; k < DeviceMaxY && ScreenTop <= ScreenBottom;) {
	    Line = ScreenBuffer[ScreenTop++];
	    p = ImageBuffer + 4;	  /* point on first pixel in bitmap. */
	    if (ZoomFactor == 1)
		memcpy(p, &Line[ScreenLeft], DeviceWidth);
	    else {
		for (i = 0, j = ScreenLeft, CountZoomI = ZoomFactor;
		     i < DeviceWidth;
		     i++) {
		    *p++ = Line[j];
		    if (--CountZoomI == 0) {
			CountZoomI = ZoomFactor;
			j++;
		    }
		}
	    }

	    /* Abort drawing if space bar was pressed: */
	    if (MyKbHit() && GetKey() == ' ') break;

	    for (i = 0; i < ZoomFactor; i++)
		putimage(DeviceLeft, k++, ImageBuffer, COPY_PUT);
	}

	free((char *) ImageBuffer);
    }
    else {
        for (CountZoomJ = ZoomFactor, j = ScreenTop, l = DeviceTop;
	     j < ScreenHeight && l <= DeviceMaxY; l++) {
	    Line = ScreenBuffer[j];

	    /* Abort drawing if space bar was pressed: */
	    if (MyKbHit() && GetKey() == ' ') break;

	    for (CountZoomI = ZoomFactor, i = ScreenLeft, k = DeviceLeft;
	         i < ScreenWidth && k <= DeviceMaxX;) {
	        putpixel(k++, l, Line[i]);

	        if (!--CountZoomI) {
		    /* Go to next column: */
		    i++;
		    CountZoomI = ZoomFactor;
	        }
	    }

	    if (!--CountZoomJ) {
	        /* Go to next row: */
	        j++;
	        CountZoomJ = ZoomFactor;
	    }
        }
    }
}

/******************************************************************************
* Walks along the current image, while printing pixel value and position.     *
* 4 arrows may be used, and any other key will abort this operation	      *
******************************************************************************/
static void DoCursorMode(GifRowType *ScreenBuffer,
		int ScreenLeft, int ScreenTop, int DeviceLeft, int DeviceTop)
{
    int GetK, DeviceRight, DeviceBottom, x, y, Step;
    GifPixelType Pixel;
    char s[80];

    DeviceRight = DeviceLeft + (ScreenWidth - ScreenLeft) * ZoomFactor;
    if (DeviceRight > DeviceMaxX) DeviceRight = DeviceMaxX;

    DeviceBottom = DeviceTop + (ScreenHeight - ScreenTop) * ZoomFactor;
    if (DeviceBottom > DeviceMaxY) DeviceBottom = DeviceMaxY;

    x = (DeviceLeft + DeviceRight) / 2;
    y = (DeviceTop + DeviceBottom) / 2;

    setwritemode(XOR_PUT);

    while (TRUE) {
	Pixel = ScreenBuffer[ScreenTop + (y - DeviceTop) / ZoomFactor]
			    [ScreenLeft + (x - DeviceLeft) / ZoomFactor];
	sprintf(s, "Color = %3d [%3d, %3d, %3d], X = %3d, Y = %3d",
		Pixel,
		ColorMap[Pixel].Red,
		ColorMap[Pixel].Green,
		ColorMap[Pixel].Blue,
		(x - DeviceLeft) / ZoomFactor,
		(y - DeviceTop) / ZoomFactor);

	setfillstyle(SOLID_FILL, BackGround);
	bar(0, 0, textwidth(s) + 2, textheight(s) + 2);

    	setcolor(ForeGround);
	outtextxy(1, 1, s);

	line(0, y, DeviceMaxX, y);
	line(x, 0, x, DeviceMaxY);
	GetK = GetKey();
	line(0, y, DeviceMaxX, y);
	line(x, 0, x, DeviceMaxY);

	Step = 10;
	switch (GetK) {
	    case '1':
		GetK = KEY_END;
		break;
	    case '2':
		GetK = KEY_DOWN;
		break;
	    case '3':
		GetK = KEY_PGDN;
		break;
	    case '4':
		GetK = KEY_LEFT;
		break;
	    case '6':
		GetK = KEY_RIGHT;
		break;
	    case '7':
		GetK = KEY_HOME;
		break;
	    case '8':
		GetK = KEY_UP;
		break;
	    case '9':
		GetK = KEY_PGUP;
		break;
	    default:
		Step = 1;
	}

	switch (GetK) {
	    case KEY_LEFT:
		x -= Step;
		break;
	    case KEY_RIGHT:
		x += Step;
		break;
	    case KEY_UP:
		y -= Step;
		break;
	    case KEY_DOWN:
		y += Step;
		break;
	    case KEY_PGUP:
		y -= Step;
		x += Step;
		break;
	    case KEY_PGDN:
		y += Step;
		x += Step;
		break;
	    case KEY_HOME:
		y -= Step;
		x -= Step;
		break;
	    case KEY_END:
		y += Step;
		x -= Step;
		break;
	    default:
		setwritemode(COPY_PUT);
		return;
	}
	if (x < DeviceLeft) x = DeviceLeft;
	if (x >= DeviceRight) x = DeviceRight;
	if (y < DeviceTop) y = DeviceTop;
	if (y >= DeviceBottom) y = DeviceBottom;
    }
}

/******************************************************************************
* Return non zero value if at list one character exists in keyboard queue.    *
* This routine emulates kbhit() which do uses stdin and useless for us.       *
******************************************************************************/
static int MyKbHit(void)
{
    return bioskey(1);
}

/******************************************************************************
* Get a key from keyboard directly (bypass stdin as we might redirect it).    *
* This routine emulates getch() which do uses stdin and useless for us.       *
******************************************************************************/
static int MyGetCh(void)
{
    static int Extended = 0;
    int c;

    if (Extended) {
	c = Extended;
	Extended = 0;
	return c;
    }
    else {
	c = bioskey(0);
	if (c & 0x0ff)
	    return c;
	else {
	    Extended = c >> 8;
	    return 0;
	}
    }
}

/******************************************************************************
* Get a key from keyboard, and translating operational keys into special      *
* codes (>255).	Lower case characters are upercased.			      *
******************************************************************************/
static int GetKey(void)
{
    char c;

    while (TRUE) switch (c = MyGetCh()) {
	case 0:		      /* Extended code - get the next extended char. */
	    switch (MyGetCh()) {
		case 75: return KEY_LEFT;
		case 77: return KEY_RIGHT;
		case 72: return KEY_UP;
		case 80: return KEY_DOWN;
		case 71: return KEY_HOME;
		case 79: return KEY_END;
		case 73: return KEY_PGUP;
		case 81: return KEY_PGDN;
		case 83: return KEY_DELETE;
		case 82: return KEY_INSERT;
	    }
	    break;
	case 8:
	    return KEY_BSPACE;
	case 10:
	case 13:
	    return KEY_RETURN;
	case 27:
	    return KEY_ESC;
	default:
	    if (isprint(c)) {
		if (islower(c))
		    return toupper(c);
		else
		    return c;
	    }
	    else {
		Tone(800, 100);
		Tone(300, 200);
	    }
    }
}

/******************************************************************************
* Routine to make some sound with given Frequency, Time milliseconds:	      *
******************************************************************************/
static void Tone(int Frequency, int Time)
{
    if (BeepsDisabled) return;

    sound(Frequency);
    delay(Time);
    nosound();
}
