Metropoli BBS
VIEWER: images.cpp MODE: TEXT (ASCII)
//**************************************************************************
// IMAGES.CPP -source file for basic video operations in mode 0x13.        *
// Copyright 1991 by the Gamers Programming Workshop, a function of the    *
// GAMERS forum, Compuserve. For more info e-mail 76605,2346.              *
//                                                                         *
// License is granted for use or modification of this code as long as      *
// this notice remains intact, and all improvements are listed in the      *
// version history below, and uploaded to the GAMERS forum. This code      *
// may not be used for any commercial purpose.                             *
//                                                                         *
// PCX stuff adapted from "BitMapped Graphics"  by Steve Rimmer (1990      *
// Windcrest)                                                              *
//**************************************************************************

//**************************************************************************
// Version history:                                                        *
//                                                                         *
// Version 1.0                                                             *
// Developed: May 2, 1991                                                  *
// Author:    Mark Betz, 76605, 2346                                       *
// Last update: July 5, 1991                                               *
//**************************************************************************

// INCLUDES ****************************************************************

#include <stdio.h>
#include <conio.h>
#include <dos.h>
#include <mem.h>
#include <alloc.h>
#include <io.h>
#include <fcntl.h>
#include "images.hpp"
#include "keyboard.hpp"

// *************************************************************************

// local constants *********************************************************

const int DacWrite = 0x3C8;        // DAC write index register
const int DacRead  = 0x3C7;        // DAC read index register
const int DacData  = 0x3C9;        // DAC data register
const int input_status_1 = 0x3DA;  // Port addr. of VGA input status 1 reg.
const int vbi_mask = 0x8;          // test bit 3 of input_status_1

// *************************************************************************

// Variables ***************************************************************

static unsigned int GRAPH_SEG = 0xA000; // segment for video operations
static p_rec current;                   // palette record for fades

// *************************************************************************

// *************************************************************************
// *************************************************************************
// NOTE:
//        The assembler functions in this module do not save any of the
//        registers they use, with the exception of di and si, which are
//        handled by the standard stack frame. When calling them expect
//        register values to be altered.
//
// *************************************************************************
// *************************************************************************

// *************************************************************************
// reporterr() should be called anytime one of the routines in this module
// returns a code other that NoErr. Pass it the error code returned by the
// offending subroutine, and a max. 30 character string describing the loc-
// ation of the error.
// *************************************************************************

void reporterr(char type, char mess[30])
	{
	settextmode();
	sound(300);
	delay(200);
	sound(250);
	delay(150);
	nosound();
	switch (type)
		{
		case MemErr        : printf("An error occured allocating memory...");
							 break;
		case FileReadErr   : printf("An error occured reading a file...");
							 break;
		case FileWriteErr  : printf("An error occured writing a file...");
							 break;
		case FileMakeErr   : printf("An error occured creating a file...");
							 break;
		case FileOpenErr   : printf("An error occured opening a file...");
							 break;
		case FileFormatErr : printf("A file format error has occured...");
							 break;
		case SecondaryErr  : printf("An error has occured in a subroutine...");
							 break;
		case UnknownErr    : printf("An unknown error has occured...");
		}
	printf(" error occured in ");
	printf(mess);
	return;
	}

// *************************************************************************
// UnpackPcx() unpacks the RLE encoded PCX data from a file if pcx != NULL,
// or the source buffer if pcx==NULL, to the Dest buffer. Unpacks NumBytes
// bytes of decompressed data. (max 64000) This is a pretty dumb routine.
// It's fast, but it expects you to know the uncompressed size of the data
// (the NumBytes parameter), and you have to do all of the file and buffer
// set-up elsewhere. Basically it's a core decoding engine. The InitPcxFile()
// function above is provided for opening a PCX file and saving the header
// and palette in data. For buffering and decoding of pcx's with various
// fade types see the fadeinpcx() function below.
// *************************************************************************

// *************************************************************************
// NOTE: this function isn't called by the PVC class objects when they dis-
// play themselves. It is an earlier version of the algrotihm, and they have
// their own version of it. I left this in in case anyone wanted to use it.
// **************************************************************************

void  unpackpcx(FILE *pcx, const char far *source, char far *dest,
				unsigned int num_bytes)
	{
	unsigned int     bytes=0;       // counts unpacked bytes
	unsigned char    c;             // byte being processed
	unsigned int     src_cntr=0;    // pointer into source buffer
	unsigned int     runlen;        // length of packet

	do
		{
		// get a key byte
		if (pcx!=NULL)
		   c=fgetc(pcx);
		else
		   c=source[src_cntr++] & 0xff;
		// check if it's a packet
		if ((c & 0xc0) == 0xc0)
			{
			// and off the high bits
			runlen = (c & 0x3f);
			// get the run value
			if (pcx!=NULL)
				c=fgetc(pcx);
			else
				c = source[src_cntr++];
			// run it out
			while(runlen--) (dest[bytes++]=c);
			}
		else
			dest[bytes++]=c;
		} while (bytes<num_bytes);
	}

// *************************************************************************
// sets the segment address that will be used in all graphics operations
// within this module. Can be used to operate on a buffer offscreen. This
// is a fairly powerfull feature, as any of the functions provided which
// write or read video memory will act on the segment contained in GRAPH_
// SEG. If you allocate a 64k buffer, and call _setgraphseg() with it's
// segment you can then create the screen there, and block copy it to
// video ram when you're finished.
// *************************************************************************

void setgraphseg(unsigned int newseg)
	{
	GRAPH_SEG = newseg;
	}

// *************************************************************************
// call bios interrupt 0x10, function 13, subfunction 00 to set up mode
// 0x13 graphics.
// *************************************************************************

void setgraphmode()
	{
	asm	{
		xor ah, ah          // zero out ah, subfunction 00
		mov al, 0x13        // function 0x13
		int 0x10            // interrupt 0x10
		}
	}

// *************************************************************************
// call bios interrupt 0x10 in same manner as previous function to restore
// text mode
// *************************************************************************

void settextmode()
	{
	asm	{
		xor ah, ah
		mov al, 0x03
		int 0x10
		}
	}

// **************************************************************************
// wait_vbi() waits twice for the vertical blanking interval. Once to make
// sure any current vbi is completed, and once for the start of the next vbi
// **************************************************************************

void wait_vbi()
	{
	asm mov dx, input_status_1;
	test_1:
	asm	{
		in al, dx
		test al,0x8
		jnz test_1
		}
	test_2:
	asm	{
		in al, dx
		test al,0x8
		jz test_2
		}
	}


// *************************************************************************
// loads the vga dac by direct method via the DAC access ports. Disables
// interrupts during load of each triplet. Does not do snow checking. Call
// with start = first register to load, number = number of registers to
// load, and palette pointing to a structure of type PRec containing the
// palette data. If start or number out of range, or palette is null then
// the function returns.
// *************************************************************************

void loadpalette(int start, int number, const p_rec palette)
	{
	unsigned int i;
	if ((start>256) || (start<0) || ((start+number)>256))
		return;
	for (i=start;i<(start+number);i++)
		{
		asm cli;
		outportb(DacWrite,i);
		outportb(DacData,palette[i][Red]);
		outportb(DacData,palette[i][Green]);
		outportb(DacData,palette[i][Blue]);
		asm sti;
		}
	}

// *************************************************************************
// same as previous function only reads the dac data into the supplied
// palette structure.
// *************************************************************************

void readpalette(int start, int number, p_rec palette)
	{
	unsigned int i;
	if ((start>256) | (start<0) | ((start+number)>256))
		return;
	for (i=start;i<(start+number);i++)
		{
		asm cli;
		outportb(DacRead,i);
		palette[i][Red] = inportb(DacData);
		palette[i][Green] = inportb(DacData);
		palette[i][Blue] = inportb(DacData);
		asm sti;
		}
	}

// **************************************************************************
// clears a range of palette registers to zero
// **************************************************************************

void clrpalette(int start, int number)
	{
	unsigned int i;
	if ((start>256) | (start<0) | ((start+number)>256))
		return;
	for (i=start;i<(start+number);i++)
		{
		asm cli;
		outportb(DacWrite,i);
		outportb(DacData,0);
		outportb(DacData,0);
		outportb(DacData,0);
		asm sti;
		}
	}

// **************************************************************************
// fadepalettein() fades the dac palette from 0 (black) to the default pal-
// ette values passed in palette structure. Fade is done in 64 passes, and
// is very smooth. Operates on count registers starting with register start.
// NOTE: this funtion expect the dac palette registers to be zero'd before
// it is called. Call clrpalette() to clear the registers.
// **************************************************************************

void fadepalettein(int start, int count, const p_rec palette)
	{
	int i, j, cycle;
	memset((void *)current,0,sizeof(p_rec));
	for (cycle=0;cycle<64;cycle++)
		{
		i=start;
		wait_vbi();
		outportb(DacWrite,i);
		asm cli;
		for (;i<((start+count)/2);i++)
			{
			for (j=0;j<3;j++)
				{
				if ((64-cycle)<=palette[i][j])
					current[i][j]++;
				outportb(DacData,current[i][j]);
				}
			}
		asm sti;
		wait_vbi();
		asm cli;
		for (;i<(start+count);i++)
			{
			for (j=0;j<3;j++)
				{
				if ((64-cycle)<=palette[i][j])
					current[i][j]++;
				outportb(DacData,current[i][j]);
				}
			}
		asm sti;
		}
	}

// **************************************************************************
// fadepaletteout() fades the current palette to black. It operates on count
// registers starting with register start. NOTE: this function destroys the
// current contents of temp_pal (images.cpp internal palette structure).
// **************************************************************************

void fadepaletteout(int start, int count)
	{
	int i, j, cycle;
	readpalette(0,256,current);
	for (cycle=64;cycle>0;cycle--)
		{
		i=start;
		wait_vbi();
		outportb(DacWrite,i);
		asm cli;
		for (;i<((start+count)/2);i++)
			{
			for (j=0;j<3;j++)
				{
				if (current[i][j]!=0)
					current[i][j]--;
				outportb(DacData,current[i][j]);
				}
			}
		asm sti;
		wait_vbi();
		asm cli;
		for (;i<(start+count);i++)
			{
			for (j=0;j<3;j++)
				{
				if (current[i][j]!=0)
					current[i][j]--;
				outportb(DacData,current[i][j]);
				}
			}
		asm sti;
		}
	clearscr(0);
	}

// *************************************************************************
// clear the graphics screen to the color provided
// *************************************************************************

void clearscr(int color)
	{
	asm {
		mov es, GRAPH_SEG
		xor di, di
		mov ax, color
		mov cx, 32000
		rep stosw
		}
	}

// *************************************************************************
// fill the rectangular area bounded by (tlx,tly)(brx,bry) with color.
// tlx = top left x, tly = top left y, brx = bottom right x, bry = bottom
// right y.
//
// This function needs to be recoded in assembler
// *************************************************************************

void barfill(int tlx, int tly, int brx, int bry, int color)
	{
	int row;
	void far *line;
	for (row=tly;row<bry;row++)
		{
		line = MK_FP(GRAPH_SEG,((row*320)+tlx));
		_fmemset(line,color,(brx-tlx));
		}
	}

// *************************************************************************
// set the pixel at coordinate x,y to color
// This could be translated into assembler, but is probably fast enough
// *************************************************************************

void writepixel(int x, int y, int color)
	{
	*(char far *)MK_FP(GRAPH_SEG,(y*320)+x)=color;
	}

// *************************************************************************
// read the color of the pixel at coordinate x,y
// Could also be done in assembler, but may be fast enough as is
// *************************************************************************

char readpixel(int x, int y)
	{
	return(*(char far *)MK_FP(GRAPH_SEG,(y*320)+x));
	}

// *************************************************************************
// returns a void type pointer to video memory at pixel coordinate x,y
// *************************************************************************

void far *xy_to_ptr(int x, int y)
	{
	return((char *)MK_FP(GRAPH_SEG,(y*320)+x));
	}

// *************************************************************************
// The following three functions make up the line drawing routine
//
// Draws a line in vga mode 13h only from (x0,y0) to (x1,y1) in color color.
// Drawing code adapted from Power Graphics Programming, Michael Abrash
// *************************************************************************

// *************************************************************************
// Octant0() and Octant1() are support functions for line()
// Adapted from "Power Graphics Programming", Michael Abrash.
// *************************************************************************

void static near Octant0(unsigned int x0,unsigned int y0,unsigned int deltaX,
			 unsigned int deltaY, int Xdirection, int color)
	{
	int deltaYx2;
	int deltaYx2minusdeltaXx2;
	int errorterm;

	deltaYx2 = deltaY*2;
	deltaYx2minusdeltaXx2=deltaYx2-(int)(deltaX*2);
	errorterm=deltaYx2-(int)deltaX;

	writepixel(x0,y0,color);
	while (deltaX--)
		{
		if (errorterm>=0)
			{
			y0++;
			errorterm+=deltaYx2minusdeltaXx2;
			}
		else
			errorterm+=deltaYx2;
		x0+=Xdirection;
		writepixel(x0,y0,color);
		}
	}

void static near Octant1(unsigned int x0,unsigned int y0,unsigned int deltaX,
			 unsigned int deltaY, int Xdirection, int color)
	{

	int deltaXx2;
	int deltaXx2minusdeltaYx2;
	int errorterm;

	deltaXx2=deltaX*2;
	deltaXx2minusdeltaYx2=deltaXx2-(int)(deltaY*2);
	errorterm=deltaXx2-(int)deltaY;

	writepixel(x0,y0,color);
	while (deltaY--)
		{
		if (errorterm>=0)
			{
			x0+=Xdirection;
			errorterm+=deltaXx2minusdeltaYx2;
			}
		else
			errorterm+=deltaXx2;
		y0++;
		writepixel(x0,y0,color);
		}
	}

// **************************************************************************
// takes the initial call and manages the line drawing
// Adapted from "Power Graphics Programming", Michael Abrash
// **************************************************************************

void line(int x0, int y0, int x1, int y1, int color)
	{
	int deltaX, deltaY;
	int temp;

	if (y0>y1)
		{
		temp=y0;
		y0=y1;
		y1=temp;
		temp=x0;
		x0=x1;
		x1=temp;
		}

	deltaX=x1-x0;
	deltaY=y1-y0;

	if (deltaX > 0)
		{
		if (deltaX>deltaY)
			Octant0(x0,y0,deltaX,deltaY,1,color);
		else
			Octant1(x0,y0,deltaX,deltaY,1,color);
		}
	else
		{
		deltaX= -deltaX;
		if (deltaX > deltaY)
			Octant0(x0,y0,deltaX,deltaY,-1,color);
		else
			Octant1(x0,y0,deltaX,deltaY,-1,color);
		}
	}

// **************************************************************************
// getimage() grabs the pixel values in the rectangle marked by (x0,y0) on
// the top left, and (x1,y1) on the bottom right. The data is placed in buff.
//
// NOTE: This function needs translating into assembler
// **************************************************************************

void getimage(int x0, int y0, int x1, int y1, char far *buff)
	{
	int i;
	int deltaY=(y1-y0);
	int xdim=(x1-x0);

	for (i=0;i<=deltaY;i++)
		{
		_fmemcpy((void *)&buff[i*xdim],xy_to_ptr(x0,(y0+i)),xdim);
		}
	}

// **************************************************************************
// putimage() copies the data in *buff to a rectangular area of the screen
// marked by (x0,y0) on the top left, and (x1,y1) on the bottom right.
//
// NOTE: This function needs translating into assembler
// **************************************************************************

void putimage(int x0, int y0, int x1, int y1, char far *buff)
	{
	int i;
	int deltaY=(y1-y0);
	int xdim=(x1-x0);

	for (i=0;i<=deltaY;i++)
		{
		_fmemcpy(xy_to_ptr(x0,(y0+i)),(void *)&buff[i*xdim],xdim);
		}
	}

// *************************************************************************
// copyimage copies a rectangular image bounded by (x0, y0) at top left, and
// (x1, y1) at bottom right, to the video buffer at (putx, puty). The func-
// tion is very fast, suitable for copying blocks of a virtual screen into
// the physical screen for animation. It does no clipping, or checking for
// the vertical blanking interval. If using it to copy from system ram to
// the video buffer, make sure to pass a pointer to the buffer in src_buff,
// and set GRAPH_SEG = to the video buffer before the call.
// *************************************************************************

void copyimage(int x0, int y0, int x1, int y1, int putx, int puty,
			   void far *src_buf)
	{
	int deltaY=(y1-y0);
	int xdim=(x1-x0);
	asm	{
		mov es, GRAPH_SEG           // establish GRAPH_SEG as destination seg
		mov bx, 320                 // increment value to move down one line
		mov ax, puty                // calculate the start of the first line
		mul bx                      // for the destination
		add ax, putx
		mov di, ax
		mov ax, y0                  // caluclate the start of the first line
		mul bx                      // for the source
		add ax, x0
		push ds
		lds si, src_buf
		add si, ax
		mov dx, deltaY              // set dx up as loop counter
		cld                         // clear the direction flag
		}
	mainloop:                       // frame drawing loop
	asm	{
		mov cx, xdim                // cx == frame width in pixels
		push si                     // save the line pointers
		push di
		shr cx, 1                   // optimized copy
		rep movsw
		rcl cx, 1
		rep movsb
		pop di                      // restore the line pointers
		pop si
		add di, 320                 // point to next line in source, dest
		add si, 320
		dec dx                      // adjust the loop counter
		jnz mainloop
		pop ds
		}
	}

// *************************************************************************
// doSplitVerticalWipe causes the picture in pic_buf to be displayed by
// wiping from left and right to the center. If called with a null pointer
// it will clear the screen using palette register 0. Waits for the vertical
// blanking interval, producing a smooth motion. This function really doesn't
// need to be done in assembler, as the vbi test is the limiting factor (see
// below)
// *************************************************************************

void  doSplitVerticalWipe(void far *pic_buf)
	{
	unsigned leftx = 0;
	unsigned rightx = 319;
	unsigned run_y;                    // counts rows
	char far *lsrc_ptr;                // pointer to left column in source
	char far *ldst_ptr;                // pointer to left column in dest
	char far *rsrc_ptr;                // pointer to right column in source
	char far *rdst_ptr;                // pointer to right column in dest

	if (pic_buf != NULL)
		{
		while (rightx > leftx)
			{
			ldst_ptr = (char far *)xy_to_ptr(leftx, 0);
			lsrc_ptr = (char far *)pic_buf+leftx;
			rdst_ptr = (char far *)xy_to_ptr(rightx, 0);
			rsrc_ptr = (char far *)pic_buf+rightx;
			wait_vbi();
			for (run_y = 0; run_y < 200; run_y++)
				{
				*ldst_ptr = *lsrc_ptr;
				*rdst_ptr = *rsrc_ptr;
				ldst_ptr += 320;
				lsrc_ptr += 320;
				rdst_ptr += 320;
				rsrc_ptr += 320;
				}
			leftx++;
			rightx--;
			}
		}
	else
		{
		while (rightx > leftx)
			{
			ldst_ptr = (char far *)xy_to_ptr(leftx, 0);
			rdst_ptr = (char far *)xy_to_ptr(rightx, 0);
			wait_vbi();
			for (run_y = 0; run_y < 200; run_y++)
				{
				*ldst_ptr = 0;
				*rdst_ptr = 0;
				ldst_ptr += 320;
				rdst_ptr += 320;
				}
			leftx++;
			rightx--;
			}
		}
	}

// **************************************************************************
// doSplitHorizWipe displays the picture in pic_buf by wiping it down from
// top and bottom. If it's called with a null pointer it clears the screen
// with palette register 0. Waits for vertical blanking interval, producing
// a smooth wipe. This function really didn't need to be done in assembler.
// I realized after I did it that the test for the vertical blanking interval
// was the limiting factor, not the execution time. Lessons learned! <g>.
//
// This function moves the image data by copying lines to the top and bottom
// of the screen alternately. The key to this process is the value in the
// unsigned integer adjust. In the setup es:di and ds:si are set up to point
// to the first line of the dest and source memory areas respectively. The
// adjust variable is intialized to 64000, which, when added to the offset of
// the first line, yields the offset of the last. Then adjust is decremented,
// and subtracted from the offset of the last line to get the offset of the
// second, and so on. The value of adjust is not used directly, but is loaded
// into the bx register. Keep an eye on si and di, as they are saved and
// restored quite often in all of these dissolve functions.
// **************************************************************************

void  doSplitHorizWipe(void far *pic_buf)
	{
	unsigned adjust = 64000U;    // used to adjust di and si
	asm {
		mov dx, input_status_1   // used to test for vbi later
		mov cx, 200              // line counter
		mov bx, adjust           // bx stores adjust value
		mov es, GRAPH_SEG        // else get video segment into es
		xor di, di               // zero out the offset
		push ds
		lds si, pic_buf          // get pic_buf into es:di
		mov ax, ds               // check for a null pointer
		cmp ax,0
		je clearscr              // if it's null clear the screen
		cld                      // clear the direction flag
		}
	mainloop1:                   // outer line loop
	asm {
		push cx                  // save loop counter
		mov cx, 160              // move 160 words
		push si                  // save offsets
		push di
		}
	test_11:                     // these lines wait for vbi
	asm	{
		in al, dx                // first test, out of vbi
		test al,0x8
		jnz test_11
		}
	test_12:
	asm	{
		in al, dx                // second test, in vbi
		test al,0x8
		jz test_12

		rep movsw                // copy words to video segment
		pop di                   // restore the original offsets
		pop si
		sub bx, 320              // take one line off of bx
		add si, bx               // add it to the offsets
		add di, bx
		mov cx, 160              // move 160 more words
		push si                  // save the offsets
		push di
		rep movsw                // copy the data to video seg
		pop di                   // restore the offsets
		pop si
		sub bx, 320              // take 1 line off of bx
		sub si, bx               // subtract it from the offsets
		sub di, bx
		pop cx                   // restore the outer loop counter
		loop mainloop1           // do next two lines
		jmp done                 // skip the next part
		}
	clearscr:
	asm cld;                     // clear the direction flag

	mainloop2:
	asm {
		push cx                  // save the loop counter
		mov cx, 160              // move 160 words
		push di                  // save the offset
		}
	test_21:                     // these lines test for vbi
	asm	{
		in al, dx                // first test, out of vbi
		test al,0x8
		jnz test_21
		}
	test_22:
	asm	{
		in al, dx                // second test, in vbi
		test al,0x8
		jz test_22
		xor ax, ax               // clear ax, value to be stored==0
		rep stosw                // clear the line
		pop di                   // restore the offset
		sub bx, 320              // take one line from bx
		add di, bx               // add it to the offset
		mov cx, 160              // mov 160 more words
		push di                  // save the offset
		rep stosw                // copy the data to video buff
		pop di                   // restore the offset
		sub bx, 320              // take one line from bx
		sub di, bx               // subtract it from the offset
		pop cx                   // restore the loop counter
		loop mainloop2
		}
	done:
	asm pop ds;
	}

// **************************************************************************
// doSlideVerticalWipe causes the image in pic_buf to be displayed by sliding
// it in from both sides toward the center. Does not check pic_buff for a non
// null address. Waits for vertical blanking interval between writes, produc-
// ing a smooth image motion.
//
// This function basically captures frames from the left and right sides of
// the source image and displays them in the video buffer so that the image
// appears to be sliding onto the screen. This is done by starting with very
// narrow slices, which are grabbed from the center of the source, and disp-
// layed at the outside edges of the screen. As the slices get wider they re-
// main rooted to the edges of the screen, so that they appear to slide into
// the center. The outer loop iterates 160 times, or half the screen width.
// On each iteration the width of the frame (i_wid) is increased by one, and
// es:di, ds:si point to the top left corner of the source frame.
// **************************************************************************

void doSlideVerticalWipe(void far *pic_buf)
	{
	int i_wid, x0, putx;
	char ctrl;                   // controls branching

	asm	{
		mov i_wid, 0             // zero out the outer loop counter
		push ds                  // save the data segment
		mov es, GRAPH_SEG        // get the video segment into es
		}
	lefthalf:
	asm {
		mov ax, 160              // get center line into ax
		sub ax, i_wid            // i_wid is the offset from center
		mov x0, ax               // x0 is the top left of the source block
		mov putx, 0              // putx is the top left of the dest block
		mov ctrl, 0
		jmp doframe              // display the frame
		}

	righthalf:
	asm {
		mov ax, 160              // mostly same as above except for
		mov x0, ax               // right side
		mov ax, 319
		sub ax, i_wid
		mov putx, ax
		mov ctrl, 1
		jmp doframe
		}
	checkloop:
	asm {
		inc i_wid
		cmp i_wid, 160           // have 160 iterations
		jna lefthalf
		pop ds                   // restore data segment
		}
	return;                      // ** exit point **

	doframe:
	asm {
		mov di, putx             // di == top left corner of destination
		mov dx, input_status_1   // used to test for vbi below
		lds si, pic_buf          // get picture pointe into ds:si
		add si, x0               // set si == top left corner of source block
		}
	test_1:                      // these lines test for the vbi
	asm	{
		in al, dx                // first test, out of vbi
		test al,0x8
		jnz test_1
		}
	test_2:
	asm	{
		in al, dx                // second test, in vbi
		test al,0x8
		jz test_2
		mov dx, 200              // set dx up as loop counter
		cld                      // clear the direction flag
		}
	mainloop:                    // frame drawing loop
	asm	{
		mov cx, i_wid            // i_wid is equal to the block width
		push si                  // save the offsets
		push di
		shr cx, 1                // optimized copy technique, cx==number of
		rep movsw                // number of bytes/2
		rcl cx, 1                // now, if cx was odd, cx == 1, else cx ==0
		rep movsb                // catches any odd byte  (thanks to Gus S.)
		pop di                   // restore the offsets
		pop si
		add di, 320              // point to the next line
		add si, 320
		dec dx                   // adjust the outer loop counter
		jnz mainloop

		cmp ctrl, 0              // if last copy was lefthalf do righthalf
		je righthalf
		jmp checkloop
		}
	}

// **************************************************************************
// doSlideHorizWipe operates almost exactly like doSlideVerticalWipe above,
// except that the image slides in from top and bottom instead of from left
// and right. Like the other wipes it uses the wipe to clear the screen if
// called with pic_buf == NULL.
//
// This function operates almost exactly like the vertical version directly
// above.
// **************************************************************************

void  doSlideHorizWipe(void far *pic_buf)
	{
	int i_wid, y0, puty;
	char ctrl;                   // controls branching

	asm	{
		mov i_wid, 1             // zero out the outer loop counter
		push ds                  // save the data segment
		mov es, GRAPH_SEG        // get the video segment into es
		}
	tophalf:
	asm {
		mov ax, 100              // get center line into ax
		sub ax, i_wid            // i_wid is the offset from center
		mov bx, 320
		mul bx
		mov y0, ax               // x0 is the top left of the source block
		mov puty, 0              // putx is the top left of the dest block
		mov ctrl, 0
		jmp doframe              // display the frame
		}

	bottomhalf:
	asm {
		mov ax, 99               // mostly same as above except for
		mov bx, 320
		mul bx
		mov y0, ax               // right side
		mov ax, 199
		sub ax, i_wid
		mul bx
		mov puty, ax
		mov ctrl, 1
		jmp doframe
		}
	checkloop:
	asm {
		inc i_wid
		cmp i_wid, 100           // have 160 iterations
		jna tophalf
		pop ds                   // restore data segment
		}
	return;                      // ** exit point **

	doframe:
	asm {
		mov di, puty             // di == top left corner of destination
		mov dx, input_status_1   // used to test for vbi below
		lds si, pic_buf          // get picture pointe into ds:si
		add si, y0               // set si == top left corner of source block
		}
	test_1:                      // these lines test for the vbi
	asm	{
		in al, dx                // first test, out of vbi
		test al,0x8
		jnz test_1
		}
	test_2:
	asm	{
		in al, dx                // second test, in vbi
		test al,0x8
		jz test_2
		mov dx, i_wid            // set dx up as loop counter
		cld                      // clear the direction flag
		}
	mainloop:                    // frame drawing loop
	asm	{
		mov cx, 160              // i_wid is equal to the block width
		push si                  // save the offsets
		push di
		rep movsw                // number of bytes/2
		pop di                   // restore the offsets
		pop si
		add di, 320              // point to the next line
		add si, 320
		dec dx                   // adjust the outer loop counter
		jnz mainloop

		cmp ctrl, 0              // if last copy was lefthalf do righthalf
		je bottomhalf
		jmp checkloop
		}
	}

// *************************************************************************
// doVerticalDissolve causes the image stored in pic_buf to be displayed by
// fading in multiple vertical slices. It is the vertical equivalent of the
// doHorizDissolve function. When called with a null pointer it clears the
// screen.
//
// The function moves the image data to the screen by iterating through
// 16 vertical slices. On each iteration two lines are drawn, at an equal
// distance on either side of the center mark for that section. The outer-
// most loop counts the lines; it takes 10 iterations to fill one slice. The
// innermost loop cycles through the 16 slices. So, the first slice gets two
// lines, the second gets two, on up to the 16th, and then the first gets
// two more, and so on. Keep an eye on si and di.
// *************************************************************************

void  doVerticalDissolve(void far *pic_buf)
	{
	int base_row;                //  base_row stores the line offset from x
	asm {
		mov es, GRAPH_SEG        // get the video segment into es
		mov base_row, 0          // start out drawing on the x pixel
		mov cx, 11               // outer loop, 11 pixels to each side of
		cld                      // the x pixel (in si and di) will be drawn
		push ds                  // save the ds register value
		lds si, pic_buf          // get the picture pointer into ds:si
		mov ax, ds               // check for a NULL pointer and jmp to
		cmp ax, 0                // clearscr if found
		je clearscr
		}
	mainloop:
	asm {
		push cx                  // save the outer loop counter
		push si                  // save original offset to byte 0 of pic
		mov cx, 16               // process 16 vertical slices
		add si, 9                // first x mark is on 9,0
		mov di, 9
		}
	drawloop:
	asm {
		push cx                  // save the inner loop counter
		push si                  // and the x mark, source and dest
		push di
		add di, base_row         // add in the offset from x for first line
		add si, base_row
		mov cx, 200              // plot 200 pixels
		}
	lineloop1:                   // this loop draws a vertical line
	asm {
		movsb                    // move one byte
		add si, 319              // move to next row
		add di, 319
		loop lineloop1           // do another

		pop di                   // restore the starting points
		pop si
		push si                  // save them again
		push di
		sub di, base_row         // drawing on the other side this time
		sub si, base_row
		mov cx, 200              // plot 200 pixels
		}
	lineloop2:
	asm {
		movsb                    // same as above
		add si, 319
		add di, 319
		loop lineloop2

		pop di                   // restore the starting point
		pop si
		pop cx                   // restore the inner loop counter
		add si, 20               // adjust pointer to the next slice
		add di, 20
		loop drawloop            // process it
		inc base_row             // increment the drawing offset
		pop si                   // restore the offset to byte 0 of pic
		pop cx                   // restore the outer loop counter
		loop mainloop            // do it all again
		jmp done
		}
	clearscr:                    // this block functions exactly as the
	asm {                        // previous one but clears the screen using
		push cx                  // palette register 0
		mov cx, 16
		mov di, 9
		}
	drawloop2:
	asm {
		push cx
		push di
		add di, base_row
		xor ax, ax
		mov cx, 200
		}
	lineloop21:
	asm {
		stosb
		add di, 319
		loop lineloop21

		pop di
		push di
		sub di, base_row
		xor ax, ax
		mov cx, 200
		}
	lineloop22:
	asm {
		stosb
		add di, 319
		loop lineloop22
		pop di
		pop cx
		add di, 20
		loop drawloop2
		inc base_row
		pop cx
		loop clearscr
		}
	done:
	asm pop ds;                  // restore the data segment
	}

// **************************************************************************
// doHorizDissolve causes the image in pic_buf to be displayed by dissolving
// it in in 10 horizontal bands. If called with pic_buf==NULL it clears the
// screen by the same method.
//
// This function processes the data much like the Vertcal Dissolve funtion
// above. The screen in this case is divided into 10 horizontal slices. The
// center line starting points of these slices begin at 2880 bytes into the
// video buffer, and every 6400 bytes after that. Like the function above it
// cycles through the slices drawing two lines at each, progressively further
// from the center line with each iteration of the outer loop. Like the func-
// tion above, you have to keep a carefull eye on di and si to understand it.
// **************************************************************************

void  doHorizDissolve(void far *pic_buf)
	{
	int base_line;
	asm {
		mov base_line, 0          // base_line is an offset from each line
		mov es, GRAPH_SEG         // get video segment into es
		mov bx, 320               // bx * base_line == adjust value for si,di
		mov cx, 20                // outer loop 20 iterations
		cld                       // clear the direction flag
		push ds                   // save the ds register value
		lds si, pic_buf           // get pic_buf into ds:si
		mov ax, ds                // if pic_buf == NULL jump to clearscr
		cmp ax, 0
		je clearscr
		}
	mainloop1:                    // main drawing loop, 20 iterations...
	asm {                         // the number of offset lines to draw
		push cx                   // save the counter
		mov cx, 10                // inner loop, 10 iterations, draws lines
		push si                   // save the offset of the pic_buf pointer
		add si, 60480             // 60480, address of first line processed
		mov di, 60480
		mov dx, input_status_1    // used below to test for vbi
		}
	test_11:                      // these lines test for the vbi
	asm	{
		in al, dx                 // first test, out of vbi
		test al,0x8
		jnz test_11
		}
	test_12:
	asm	{
		in al, dx                 // second test, in vbi
		test al,0x8
		jz test_12
		}
	drawloop1:                    // drawloop processes the 10 base lines,
	asm {                         // drawing 2 lines at offset ax from base
		push cx                   // save the counter
		push si                   // save the offset to the current base
		push di
		mov ax, base_line         // calculate the pointer offset from base
		mul bx                    // by multiplying base_line * 320
		sub si, ax                // subtract the offset from source and
		sub di, ax                // dest base pointers, and
		mov cx, 160               // draw the first line
		rep movsw
		pop di                    // restore the base pointers
		pop si
		push si                   // save em again
		push di
		add si, ax                // add the offset to the source and dest
		add di, ax                // base pointers and
		mov cx, 160               // draw the second line
		rep movsw
		pop di                    // restore the base pointers
		pop si
		pop cx                    // restore the inner loop counter
		sub si, 6400              // adjust base pointers to point to the
		sub di, 6400              // next base, 20 lines up the screen
		loop drawloop1            // process the next section
		inc base_line             // increment the base offset factor
		pop si                    // restore the original pic_buf offset
		pop cx                    // restore the outer loop counter
		loop mainloop1            // do it all over again
		jmp done                  // finished
		}
	clearscr:                     // this section executes if pic_buf==NULL
	asm {
		push cx                   // save the outer loop counter
		mov cx, 10
		mov di, 60480             // we can ignore ds:si since no source
		mov dx, input_status_1    // used to test for vbi below
		}
	test_21:                      // these lines test for the vbi
	asm	{
		in al, dx                 // first test, out of vbi
		test al,0x8
		jnz test_21
		}
	test_22:
	asm	{
		in al, dx                 // second test, in vbi
		test al,0x8
		jz test_22
		}
	drawloop2:
	asm {
		push cx                  // save the inner loop counter
		push di                  // save the base offset
		mov ax, base_line        // calculate the drawing offset from base
		mul bx
		sub di, ax               // subtract the offset from the base pointer
		push ax                  // save the offset
		xor ax, ax               // clear ax for use in stosw instruction
		mov cx, 160
		rep stosw                // clear the first line
		pop ax                   // restore the offset
		pop di                   // restore the base pointer
		push di                  // save it again
		add di, ax               // add the offset to the base pointer
		xor ax, ax               // clear ax for use in stosw instruction
		mov cx, 160
		rep stosw                // clear the second line
		pop di                   // restore the base pointer
		pop cx                   // restore inner loop counter
		sub di, 6400             // adjust base pointer to 20 lines up screen
		loop drawloop2           // process the next section
		inc base_line            // increment the offset factor
		pop cx                   // restore the outer loop counter
		loop clearscr
		}
	done:                        // finished
	asm pop ds;                  // restore ds value
	}



void  doSparkleDissolve(void far *pic_buf)
	{
	}



// *************************************************************************
// member function definitions for class font, declared in images.hpp
// *************************************************************************

// *************************************************************************
// constructor takes a char * filepath. It opens the fontfile, gets the
// font header into the member structure font_hdr, then allocates memory
// and reads the font data into it. Errors cause the constructor set the
// font status to 0, then return.  The status can be checked via the
// font::getstatus() method.
// *************************************************************************

font::font(char *f_spec)
	{
	int handle;
	int readsize;
	int bytes_read=0;
	char cursor_size;

	if ((handle=open(f_spec,O_RDONLY|O_BINARY))==-1)
		{
		status = FileOpenErr;
		return;
		}
	bytes_read+=(read(handle,&f_hdr,sizeof(f_hdr)));
	bytes_read+=(read(handle,&width[0],sizeof(width)));
	cursor_size=(f_hdr.fram_wid*f_hdr.fram_hit);
	if (((void far *)cursor_mask=malloc(cursor_size)) ==NULL)
		{
		status = MemErr;
		return;
		}
	bytes_read+=(read(handle,(void far*)cursor_mask,cursor_size));
	readsize=(filelength(handle)-bytes_read);
	if (((void far *)font_ptr=malloc(readsize)) ==NULL)
		{
		free(cursor_mask);
		status = MemErr;
		return;
		}
	read(handle,(void far *)font_ptr,readsize);
	close(handle);
	fore_color=0;
	setstyle(0,15,1,opaque,4);
	status=1;
}

// *************************************************************************
// destructor disposes of the memory allocated to the font
// *************************************************************************

font::~font()
	{
	free(font_ptr);
	font_ptr=NULL;
	free(cursor_mask);
	cursor_mask=NULL;
	}

// *************************************************************************
// get_width() returns the proportional width in pixels of char c
// *************************************************************************

char font::get_width(char c)
	{
	if ((c>=f_hdr.ascii_first)&&(c<=f_hdr.ascii_last))
		return(width[c-f_hdr.ascii_first]);
	else
		return(0);
	}

// *************************************************************************
// put_char displays one bitmapped character at the passed x,y location
// *************************************************************************

void font::put_char(int x, int y, char c)
	{
	int j;
	int i;
	unsigned char src,dst;
	char far *p;
	int char_ofs;
	char_ofs=(c-f_hdr.ascii_first)*((f_hdr.fram_wid*f_hdr.fram_hit));
	(void far *)p=MK_FP(FP_SEG(font_ptr),FP_OFF(font_ptr)+char_ofs);
	for(j=0;j<f_hdr.fram_hit;j++)
		{
		for (i=0;i<f_hdr.fram_wid;i++)
			{
			src=p[(j*f_hdr.fram_wid)+i];
			if (src==f_hdr.f_color)
				{
				dst=fore_color;
				writepixel(x+i,y+j,dst);
				}
			else
				{
				if (see_thru==opaque)
					{
					dst=back_color;
					writepixel(x+i,y+j,dst);
					}
				}
			}
		}
	}

// *************************************************************************
// returns the current foreground color setting for text output
// *************************************************************************

int font::getforecolor()
	{
	return(fore_color);
	}

// *************************************************************************
// returns the current background color setting for text output
// *************************************************************************

int font::getbackcolor()
	{
	return(back_color);
	}

// *************************************************************************
// call this function to set the style parameters for text output. f_grnd=
// foreground color, b_grnd=background color, char_tab=pixels between
// characters, opacity={trans,opaque}, when opacity=trans the background
// pixels in the font are ingored, when opacity=opaque the background pixels
// are set to the color value passed in b_grnd, space_wid=width in pixels
// of ascii character 32 (20h), the space character.
// *************************************************************************

void font::setstyle(int f_grnd,int b_grnd,short char_tab,
					opacity o, char space_wid)
	{
	spacing=char_tab;
	see_thru=o;
	fore_color=f_grnd;
	back_color=b_grnd;
	width[32-f_hdr.ascii_first]=space_wid;
	}

// *************************************************************************
// ouput the string pointed to by str at the given x,y coordinate.
// *************************************************************************

void font::show(int x, int y, char *str)
	{
	int i;
	int j;
	for (j=0;str[j]!=0;j++);   // one line gets length of string
	for (i=0;i<j;i++)
		{
		put_char(x,y,str[i]);
		x += get_width(str[i])+spacing;
		}
	}

// *************************************************************************
// read a string from the keyboard, echo to screen at x,y. Reads until it
// encouters a carriage return <ascii 13>, or the string reaches width
// bytes in length, whichever occurs first. Displays a blinking cursor using
// the cursor mask loaded with the font. Backspace enabled.
// *************************************************************************

void font::readstr(int x, int y, char *str, char length, char mask)
	{
	char c='\0';                   // used in call to getfilteredkey()
	extnd scan=NO_EXT;             // ditto
	int cursor_x=x;                // cursor position/next letter position
	int cursor_y=y;                // ditto
	char cur_toggle=0;             // cursor display state
	unsigned int tick;             // last clock reading
	volatile unsigned int *clock=(unsigned int *)MK_FP(0x0040,0x006C);
	int str_len=0;                 // input string length
	int i,j;                       // generic counters

	str[str_len]='\0';
	while((c!=13)&&(str_len<length))
		{
		do
			{
			tick= *clock;
			while ((*clock<(tick+3))&&(!kbhit()));
			cur_toggle = !cur_toggle;
			for(j=0;j<f_hdr.fram_hit;j++)
				{
				for (i=0;i<(f_hdr.fram_wid-1);i++)
					{
					if ((cur_toggle==0)||(cursor_mask[(j*f_hdr.fram_wid+i)]==0))
						writepixel(cursor_x+i,cursor_y+j,back_color);
					else
						writepixel(cursor_x+i,cursor_y+j,fore_color);
					}
				}
			} while ((!kbhit())||(cur_toggle!=0));
		if (getfilteredkey(mask,&c,&scan))
			{
			if ((c!=13)&&(c!=8)&&(c!=9)&&(c!=27))
				{
				str[str_len++]=c;
				str[str_len]='\0';
				put_char(cursor_x,cursor_y,c);
				cursor_x+=(get_width(c)+spacing);
				}
			else
				{
				if ((c==8)&&(str_len!=0))
					{
					cursor_x-=(get_width(str[str_len-1])+spacing);
					str[--str_len]='\0';
					}
				}
			}
		}
	}

// **************************************************************************
// call installed() immediately after initializing an instance of class font.
// if it returns non-zero (true) the installation was successful. 0 indicates
// a file access or memory error during installation.
// **************************************************************************

int font::installed()
	{
	return(status);
	}

// **************************************************************************
// member function definitions for i_stack pointer stack class
//
// these don't have any specific place in a graphics module, except that they
// were going to be used in a window manager, and I was too lazy to make them
// a module to live in.
// **************************************************************************

// **************************************************************************
// constructor clears the pointer stack to NULL
// **************************************************************************

i_stack::i_stack()
	{
	int i;

	ptr_index=0;
	for (i=0;i<20;i++)
		{
		ptr_array[i]=NULL;
		}
	}

// **************************************************************************
// push a pointer on to the stack. Returns 0 if stack full, 1 if success
// **************************************************************************

char i_stack::push_ptr(void far *ptr)
	{
	if (ptr_index!=20)
		{
		ptr_array[ptr_index++]=ptr;
		return(1);
		}
	else
		{
		return(0);
		}
	}

// **************************************************************************
// pop a pointer from the stack. Returns NULL if stack empty.
// **************************************************************************

void far *i_stack::pop_ptr()
	{
	if (ptr_index!=0)
		return(ptr_array[--ptr_index]);
	else
		return(NULL);
	}

// **************************************************************************
// member function definitions for window class
// **************************************************************************

// **************************************************************************
// Constructor creates the window. See WIN.DOC for more information.
// **************************************************************************

win::win(int tlx, int tly, int xwid, int ywid, char far *bmap,
			   char brdrwid, char shdwwid, char bgrnd, char border)
	{
	if (shdwwid==0)
		{
		shdwon=0;
		if ((backgrnd=new char[xwid*ywid])==NULL)
			{
			status = 0;
			return;
			}
		getimage(tlx,tly,tlx+xwid,tly+ywid,backgrnd);
		}
	else
		{
		shdwon=1;
		if ((backgrnd = new char[(xwid+shdwwid)*(ywid+shdwwid)])==NULL)
			{
			status=0;
			return;
			}
		getimage(tlx,tly,tlx+xwid+shdwwid,tly+ywid+shdwwid,backgrnd);
		barfill(tlx+xwid,tly+shdwwid,tlx+xwid+shdwwid,tly+ywid+shdwwid,0);
		barfill(tlx+shdwwid,tly+ywid,tlx+xwid,tly+ywid+shdwwid,0);
		}
	if (bmap==NULL)
		barfill(tlx,tly,tlx+xwid,tly+ywid,bgrnd);
	else
		putimage(tlx,tly,tlx+xwid,tly+ywid,bmap);
	if (brdrwid!=0)
		{
		if (brdrwid>1)
			{
			barfill(tlx,tly,tlx+xwid,tly+brdrwid,border);
			barfill(tlx+(xwid-brdrwid),tly,tlx+xwid,tly+ywid,border);
			barfill(tlx,tly+(ywid-brdrwid),tlx+xwid,tly+ywid,border);
			barfill(tlx,tly,tlx+brdrwid,tly+ywid,border);
			}
		else
			{
			line(tlx,tly,tlx+xwid,tly,border);
			line(tlx+xwid,tly,tlx+xwid,tly+ywid,border);
			line(tlx,tly+ywid,tlx+xwid,tly+ywid,border);
			line(tlx,tly,tlx,tly+ywid,border);
			}
		}
	origX=tlx;
	origY=tly;
	xsize=xwid;
	ysize=ywid;
	bdrwid=brdrwid;
	shdwid=shdwwid;
	b_grnd=bgrnd;
	brdr=border;
	status=1;
	return;
	}

win::~win()
	{
	if (shdwon)
		putimage(origX,origY,origX+xsize+shdwid,origY+ysize+shdwid,backgrnd);
	else
		putimage(origX,origY,origX+xsize,origY+ysize,backgrnd);
	delete(backgrnd);
	return;
	}

// **************************************************************************
// member function defintions for pcx class
// **************************************************************************

// **************************************************************************
// constructor opens the file as binary, readonly. It reads the dac palette
// into the member p_rec structure palette, corrects it for shift, sets sev-
// eral status variables, and closes the file. On error it sets pcx.status to
// Failed, and reports the error in the images module error record imag_err.
// **************************************************************************

pcx::pcx(char *fspec)
	{
	char check_pal;
	int i,j;
	FILE *filptr;

	status = NoErr;

	if ((filptr = fopen(fspec,"rb"))==NULL)
		{
		status = FileOpenErr;
		return;
		}
	if (fseek(filptr,-769L,SEEK_END) != 0)
		{
		status= FileReadErr;
		fclose(filptr);
		return;
		}
	fread(&check_pal,1,1,filptr);

	// check for 256 color PCX palette tag 0x0C at filelength-769

	if (check_pal!=0x0C)
		{
		status = FileFormatErr;
		fclose(filptr);
		return;
		}

	// read in the palette and store in class member data palette

	if (fread(palette,1,768,filptr) != 768)
		{
		status = FileReadErr;
		fclose(filptr);
		return;
		}

	// shift the palette values right by two bits (pcx specific detail)

	for (i=0;i<256;i++)
		{
		for (j=0;j<3;j++)
			{
			palette[i][j]=palette[i][j]>>2;
			}
		}

	// leave the file pointer on the first byte of image data

	if (fseek(filptr,128,SEEK_SET) != 0)
		{
		status = FileReadErr;
		fclose(filptr);
		return;
		}
	fclose(filptr);
	buffer=NULL;                       // pcx:image.buffer
	for (j=0;fspec[j]!=0;j++);         // these two lines copy the path to
	memcpy(fpath,fspec,j+1);           // to storage in the object data
	in_ram = 0;                        // pcx.in_ram
	packed = 0;                        // pcx.packed
	return;
	}

// **************************************************************************
// unpacktoram unpacks the rle encoded into the buffer pointed to by dest.
// No size checking is performed on the buffer. The unpacking process cont-
// inues until num_bytes have been moved to dest. The function checks the
// state of the in_ram member variable. If the image is not in memory it is
// unpacked from the disk file. If the image is in memory in compressed mode
// it is unpacked from memory. If the image is in memory, but is already
// uncompressed the function returns UnknownErr.
// **************************************************************************

char pcx::unpacktoram(char far *dest, unsigned int num_bytes)
	{
	unsigned int bytes=0;           // counts unpacked bytes
	char    c;                      // byte being processed
	int     runlen;                 // length of packet
	unsigned int src_cntr = 0;      // counts through ram buffer if any
	char far *streambuf;
	FILE *filptr;

	if (!in_ram)
		{
		if ((filptr=fopen(fpath,"rb"))==NULL)
			return(FileOpenErr);
		if ((streambuf=new char[5000]) != NULL)
			{
			setbuf(filptr,streambuf);
			}
		if (fseek(filptr,128L,SEEK_SET) != 0)
			{
			fclose(filptr);
			if (streambuf != NULL)
				delete(streambuf);
			return(FileReadErr);
			}
		do
			{
			c=fgetc(filptr);
			if ((c & 0xc0) == 0xc0)
				{
				runlen = (c & 0x3f);
				c=fgetc(filptr);
				while(runlen--) (dest[bytes++]=c);
				}
			else
				dest[bytes++]=c;
			} while (bytes<num_bytes);
		fclose(filptr);
		if (streambuf!=NULL)
			delete(streambuf);
		}
	else
		{
		if (!packed)
			return(UnknownErr);
		else
			{
			do
				{
				c=buffer[src_cntr++];
				if ((c & 0xc0) == 0xc0)
					{
					runlen = (c & 0x3f);
					c=buffer[src_cntr++];
					while(runlen--) (dest[bytes++]=c);
					}
				else
					dest[bytes++]=c;
				} while (bytes<num_bytes);
			}
		}
	return(NoErr);
	}


// **************************************************************************
// the load() member function reads the pcx file into a memory buffer. The
// pointer to the buffer and the buffer size are internal to the class, and
// do not need to be taken into account by the programmer. The data is loaded
// based on the value of method. A set of three constants is defined for this
// value; Packed, Unpacked, and Bestfit. If method = packed the data will be
// read into the memory buffer in compressed PCX format, but the palette and
// header data will be stripped. If method = unpacked the data is uncompress-
// to memory using the unpacktoram() function. In either case, if there is
// not sufficient memory, the function returns MemErr. If method = Bestfit
// the function will unpack the data is possible, or load it packed if not,
// and finally returns MemErr if neither method can be used. If successfull
// the function sets internal switches so that other pcx member functions
// will know that the image data is already loaded, and in what form it has
// been loaded.
// **************************************************************************

char pcx::load(char method)
	{
	unsigned readsize;
	FILE *filptr;

	// make sure the image isn't already loaded

	if (!in_ram)
		{
		if (method == Bestfit)
			{

			// if enough ram for the image load unpacked

			if (coreleft() >= 64000U)
				{
				method = Unpacked;
				}
			else

			// if not enough ram open the file, compare it's size to the
			// available memory, and load it packed if there's enough room
			// or return a MemErr if there isn't.

				{
				if ((filptr=fopen(fpath,"rb"))==NULL)
					{
					return(FileOpenErr);
					}
				readsize = filelength(fileno(filptr))-(128+769);
				fclose(filptr);
				if (coreleft() >= readsize)
					method = Packed;
				else
					return(MemErr);
				}
			}

		// if method = packed then allocate a buffer the size of the image
		// file with the header and palette data stripped out. Zero the
		// buffer, then read the packed data into it. Finally, set in_ram
		// and packed = 1 (pcx class member data used by display() member
		// function)

		if (method == Packed)
			{
			if ((filptr=fopen(fpath,"rb"))==NULL)
				return(FileOpenErr);
			readsize = filelength(fileno(filptr))-(128+769);
			if ((buffer = new char[readsize]) == NULL)
				{
				fclose(filptr);
				return(MemErr);
				}
			_fmemset(buffer,0,readsize);
			if (fseek(filptr, 128L, SEEK_SET) != 0)
				{
				fclose(filptr);
				delete(buffer);
				return(FileReadErr);
				}
			if (fread(buffer, 1, readsize, filptr) != readsize)
				{
				fclose(filptr);
				delete(buffer);
				return(FileReadErr);
				}
			fclose(filptr);
			in_ram = 1;
			packed = 1;
			}
		else

		// if method = Unpacked then allocate a 64000 byte buffer, and
		// unpack the data into it.

			{
			if ((buffer = new char[64000U]) == NULL)
				return(MemErr);
			_fmemset(buffer,0,64000U);
			if(unpacktoram(buffer,64000U) != NoErr)
				{
				delete(buffer);
				in_ram = 0;
				return(SecondaryErr);
				}
			else
				{
				in_ram = 1;
				packed = 0;
				}
			}
		}
	return(NoErr);
	}

// **************************************************************************
// the unload() member function frees the memory allocated to the pcx data,
// and updates the internal switches so that other member functions will
// know that the image is no longer in memory.
// **************************************************************************

void pcx::unload()
	{
	if (in_ram)
		{
		delete(buffer);
		in_ram = 0;
		packed = 0;
		}
	}

// **************************************************************************
// pcx::display can be used to display a PCX class object to screen using
// various fade types, wipes, and dissolves. All fade types other than Soft-
// Fade and SnapWipe require a screen buffer of 64000 bytes. If that memory
// is not available the function returns MemErr. The PCX will be displayed
// whether it is: on disk, in ram packed, or in ram unpacked (in order of
// decreasing execution time).
//
// The following fade types are available:
//
//		SnapWipe           - snaps the pcx to screen, basic block mem move.
//		SplitVerticalWipe  - fades from the top and bottom toward center.
//		SplitHorizWipe     - fades from the right and left toward center.
//      SlideVerticalWipe  - slides pic in from left and right
//      SlideHorizWipe     - slides pic in from top and bottom
//		VerticalDissolve   - overlays multiple vertical slices
//		HorizDissolve      - overlays multiple horizontal slices
//		SparkleDissolve    - fades with random block placement
//		SoftFade           - dac palette intensity fade, 64 passes
// **************************************************************************

char pcx::display(char fadetype)
	{
	char far *do_buffer;
	char loaded_here = 0;

	// SoftFade (palette fade) requires the palette to be cleared.

	if (fadetype == SoftFade)
		{
		wait_vbi();
		clrpalette(0,256);
		}
	else

	// all other types require the new palette to be loaded

		{
		wait_vbi();
		loadpalette(0,256,palette);
		}

	// if the image is not in ram, or is in ram compressed, then it
	// needs to be unpacked, from either disk or memory, to either the
	// video buffer or a memory buffer depending on the fade type.

	if ((!in_ram) || ((in_ram) && (packed)))
		{

		// these two fade types simply require the data unpacked to vseg.

		if ((fadetype == SoftFade) || (fadetype == SnapWipe))
			{
			if (unpacktoram((char far *)MK_FP(0xa000,0), 64000U) != NoErr)
				return(SecondaryErr);
			}
		else

		// all the others require the data in a 64K memory buffer
		// so one is allocated, and the data is unpacked to it

			{
			if ((do_buffer = new char[64100U]) == NULL)
				return(MemErr);
			if (unpacktoram(do_buffer, 64000U) != NoErr)
				{
				delete(do_buffer);
				return(SecondaryErr);
				}
			loaded_here = 1;
			}
		}
	else

	// if the data is in ram uncompressed it's ready to go. The only
	// action necessary is to copy it to the video buffer for the two
	// simple fade types. The buffer identifier is member data of the
	// image class, and stores a pointer to the image buffer. In both
	// cases do_buffer is assigned the value of buffer, and then the
	// operations are performed on it. This simplifies the calls to
	// the fade functions below.

		{
		do_buffer = buffer;

		// for these two fade types copy the data over to vseg

		if ((fadetype == SoftFade) || (fadetype == SnapWipe))
			{
			wait_vbi();
			asm {
				push es
				push ds
				push cx
				push si
				push di
				mov es, GRAPH_SEG
				xor di, di
				lds si, do_buffer
				mov cx, 32000
				rep movsw
				pop di
				pop si
				pop cx
				pop ds
				pop es
				}
			}
		}
	switch (fadetype)
		{
		case SoftFade:           fadepalettein(0,256,palette); break;
		case SplitVerticalWipe:  doSplitVerticalWipe(do_buffer); break;
		case SplitHorizWipe:     doSplitHorizWipe(do_buffer); break;
		case SlideVerticalWipe:  doSlideVerticalWipe(do_buffer); break;
		case SlideHorizWipe:     doSlideHorizWipe(do_buffer); break;
		case VerticalDissolve:   doVerticalDissolve(do_buffer); break;
		case HorizDissolve:      doHorizDissolve(do_buffer); break;
		case SparkleDissolve:    doSparkleDissolve(do_buffer); break;
		}
	if (loaded_here)
		delete(do_buffer);
	return(NoErr);
	}

// **************************************************************************
// remove() clears the current image from the screen using the passed fade
// type. It will clear the screen regardless of what image is on it. No
// checking is performed to be certain this function is called after a call
// to display() has been made for the object.
// **************************************************************************

void pcx::remove(char fadetype)
	{
	switch (fadetype)
		{
		case SoftFade:            fadepaletteout(0,256); break;
		case SnapWipe:            clearscr(0); break;
		case SplitVerticalWipe:   doSplitVerticalWipe(NULL); break;
		case SplitHorizWipe:      doSplitHorizWipe(NULL); break;
		case SlideVerticalWipe:   break;
		case SlideHorizWipe:      break;
		case VerticalDissolve:    doVerticalDissolve(NULL); break;
		case HorizDissolve:       doHorizDissolve(NULL); break;
		case SparkleDissolve:     doSparkleDissolve(NULL); break;
		}
	return;
	}

// **************************************************************************
// destructor frees memory allocated to pcx file, if any
// **************************************************************************

pcx::~pcx()
	{
	if (in_ram)
		delete(buffer);
	}
[ RETURN TO DIRECTORY ]