/* dgraph.c

graphics dump for the RX/FX-80/100 printers

This program is a graphics dump utility for the Epson RX/FX-80/100
printer.  It is very similar to the program graphics.com which is used
to dump graphics to the Epson RX/FX-80/100 printers.  

	graphics dump for the RX/FX-80/100 printers
	Includes CGA, EGA, and AT&T hi-res dump modes.
	(c) Lantern Systems 1986, 1988, 1989

calling convention:

	dgraph ( row_min, col_min, row_max, col_max )

where row_min, row_max, col_min, col_max are integers describing the
screen co-ordinates to use as a window.  If max values are set to 0, 
the values default to the full screen size.

Returns 0 for normal, EOF for error, 1 for object locked or printer not ready
*/

#include	<stdio.h>
#include	<stdlib.h>
#include	<dos.h>

#define		CR		0x0D
#define		LF		0x0A
#define		VIDEO_INT	0x10
#define		ESC		0x1B
#define		GET_VIDEO	0x0F		/* fn. code for read video */
#define		READ_PIX	0x0D		/* fn. code for read pixel */
#define		MAX_COL_H	639		/* max # of high-res col.s */
#define		MAX_ROW_H	399		/* max # of high-res rows */
#define		MAX_ROW_M	199		/* max # of med-res rows */
#define		MAX_COL_M	639		/* max # of med-res col.s */
#define		MAX_ROW_L	199		/* max # of low-res rows */
#define		MAX_COL_L	319		/* max # of low res col.s */
#define		EGA_ROWMAX	349		/* max # of EGA rows */
#define		PRINTER		0x17		/* interrupt for printer i/o */

#define		MAX(x,y)	( (x) < (y) ) ? (y) : (x)
#define		MIN(x,y)	( (x) < (y) ) ? (x) : (y)

static unsigned far *mon_lock;
static int raster_rows, bits, err;
static unsigned char top, mid;
static union REGS regs;
static int row_min, row_max, col_min, col_max;

/*	top is the top of a pixel triplet, mid is the top of a
	merged pixel triplet. This code is very sensitive to the size of
	long int's and the allocation off char's.  In this program, the
	union of long and char is such that pixels [3] is the MSB of the
	long and pixels [0] is the LSB.  In this way it is possible to 
	rotate bits into the entire dword with a single instruction. */

int dgraph ( row_mn, col_mn, row_mx, col_mx )
	int col_mn, col_mx, row_mn, row_mx;
	{

	int col_last, row_idx, col_idx, mode;


	/*	raster_rows defines the number of rows that make up a
		printhead raster scan and col_last holds the pointer 
		to the last active pixel of the current printhead raster. */


	mon_lock = (unsigned far *) 0x5000000;	/* point to seg. 50, adr. 0 for monitor lock byte */

	if ( *mon_lock == 1) return (1);	/* monitor is already locked */
	regs.h.ah = GET_VIDEO;			/* return current video mode */
	int86 (VIDEO_INT, &regs, &regs);
	mode = regs.h.al;			/* returned video mode */
	if ((mode == 7) || (mode < 4)) return (EOF);
	*mon_lock = 1;				/* assert monitor lock */


/*---------------------------------------------------------
	This block of code tests for valid graphics modes to dump graphics.
	The ranges of the graphics dump window are also tested here.  No 
	attempt was made to check for min values > max values.  */

	set_lpi();		/* set-up new printer linefeed size */
	row_min = MAX (0, row_mn);
	col_min = MAX (0, col_mn);
	switch (mode)
		{
		case 4:				/* CGA low-res mode 320x200 */
		case 5:
			row_max = MIN (row_mx, MAX_ROW_L);
			col_max = MIN (col_mx, MAX_COL_L);
			if (row_max == 0) row_max = MAX_ROW_L;
			if (col_max == 0) col_max = MAX_COL_L;
			err = low_res ();
			*mon_lock = 0;		/* release monitor lock */
			return (err);
			break;
		case 6:				/* CGA med-res mode 640x200 */
			row_max = MIN (row_mx, MAX_ROW_M);
			col_max = MIN (col_mx, MAX_COL_H);
			if (row_max == 0) row_max = MAX_ROW_M;
			if (col_max == 0) col_max = MAX_COL_H;
			raster_rows = 4;
			bits = 2;
			err = mh_res ();
			*mon_lock = 0;		/* release monitor lock */
			return (err);
			break;
		case 0x0d:			/* EGA 320x200 */
			row_max = MIN (row_mx, MAX_ROW_L);
			col_max = MIN (col_mx, MAX_COL_L);
			if (row_max == 0) row_max = MAX_ROW_L;
			if (col_max == 0) col_max = MAX_COL_L;
			err = low_res ();
			*mon_lock = 0;		/* release monitor lock */
			return (err);
			break;
		case 0x0e:			/* EGA 640x200 */
			row_max = MIN (row_mx, MAX_ROW_M);
			col_max = MIN (col_mx, MAX_COL_H);
			if (row_max == 0) row_max = MAX_ROW_M;
			if (col_max == 0) col_max = MAX_COL_H;
			raster_rows = 4;
			bits = 2;
			err = mh_res ();
			*mon_lock = 0;		/* release monitor lock */
			return (err);
			break;
		case 0x10:			/* EGA 640x350 */
			row_max = MIN (row_mx, EGA_ROWMAX);
			col_max = MIN (col_mx, MAX_COL_H);
			if (row_max == 0) row_max = EGA_ROWMAX;
			if (col_max == 0) col_max = MAX_COL_H;
			raster_rows = 8;
			bits = 1;
			err = mh_res ();
			*mon_lock = 0;		/* release monitor lock */
			return (err);
			break;
		case 0x40:			/* ATT high-res mode 640x400 */
		case 0x48:
			row_max = MIN (row_mx, MAX_ROW_H);
			col_max = MIN (col_mx, MAX_COL_H);
			if (row_max == 0) row_max = MAX_ROW_H;
			if (col_max == 0) col_max = MAX_COL_H;
			raster_rows = 8;
			bits = 1;
			err = mh_res ();
			*mon_lock = 0;		/* release monitor lock */
			return (err);
			break;
		default:
			*mon_lock = 0;		/* release monitor lock */
			return (EOF);
			break;
		}
	}					/* end of main part of dump routine */
/*---------------------------------------------------------
	This routine scans the low-res (320x200) graphics screen for active
	pixels.  If no active pixels are found, only a <cr><lf> sequence
	is sent to the printer.  This saves printhead motion and time. */

static int low_res ()
	{
	int row, last_col;
	raster_rows = 4;					/* screen rows per printer raster */
	for (row = row_min; row <= row_max; row +=raster_rows)	/*starting row for printer dump */
		{
		if ((last_col = scan (row) ) >= col_min)
			err = xmit_low (row, last_col);
		if (err == EOF) return (EOF);
		}
	return (0);
	}
/*---------------------------------------------------------
	This routine transmits 4 rows of data per printhead raster.  It sends 
	3 bytes per column.  Color contrast is acheived by controling vertical
	and horizontal dot density. Each pixel is 3 printer pixels wide and two
	pixels tall to provide the correct aspect ratio for RX/FX-80/100 printers
	and color representation in grey scale. */

static int xmit_low (row, last_col)
	int row, last_col;
	{
	int row_idx, col, row_stop;
	set_bit_image ( (last_col - col_min + 1) * 3);		/* set byte count */
	row_stop = MIN ( row_max, row + raster_rows - 1);
	for (col = col_min; col <= last_col; ++col)		/* start at col_min and stop at last col. */
		{
		top = 0;	/* zero pixel data buffers */
		mid = 0;
		for (row_idx = row_stop; row_idx >= row; --row_idx)
			{
			top = (char) _rotr ((unsigned int) top, 2);
			mid = (char) _rotr ((unsigned int) mid, 2);
			regs.x.dx = row_idx;	/* setup row # */
			regs.x.cx = col;		/* setup column # */
			regs.h.ah = READ_PIX;
			int86 (VIDEO_INT, &regs, &regs);	/* read pixel */
			switch (regs.h.al & 3)			/* only low byte used */
				{
				case 0:			/* background color */
					break;		/* do nothing */
				case 1:			/* color 1 */
					top |= 0x0c0; 
					break;
				case 2:			/* color 2 */
					mid |= 0x0c0;
					break;
				case 3:			/* color 3 */
					top |= 0x0c0;
					mid |= 0x0c0;
					break;
				}
			}
		if (fputc ( top, stdprn ) == EOF) return (EOF);
		if (fputc ( mid, stdprn ) == EOF) return (EOF);
		if (fputc ( top, stdprn ) == EOF) return (EOF);
		}
	if (fputc('\n', stdprn) == EOF) return (EOF);
	return (0);
	}
/*--------------------------------------------------------
	This routine scans the med-res (640x200) graphics screen and the
	high-res (640x400) graphics screen for active pixels.  If no active
	pixels are found, only a <cr><lf> sequence is sent to the printer.
	This saves printhead motion and time. raster_rows = 4 for med-res, and
	8 for high-res.  Similarly, bits = 6 for med-res, and 3 for high-res */

static int mh_res ()
	{
	int last_col, row, err;
	for (row = row_min; row <= row_max; row +=raster_rows)	/*starting row for printer dump */
		{
		if ((last_col = scan (row) ) >= col_min)
			err = xmit_mh (row, last_col);
		if (err == EOF) return (EOF);
 		}
	return (0);
 	}
/*--------------------------------------------------------
	This routine transmits 4 rows of data per printhead raster.  It 
	sends 3 bytes/2 columns. The 2nd byte is transmitted as the 
	logical OR of the columns on either side of it.  In this way, the 
	aspect ratio is corrected without biasing the drawing in favor of 
	even or odd columns. */

static int xmit_mh (row, last_col)
	int row, last_col;
	{
	register int row_idx, row_stop, col;
	int idx, err;
	unsigned char pixel;
	idx = last_col - col_min + 1;				/* true column count */
	set_bit_image ( idx  + (idx >> 1) + (idx & 1) );	/* set byte count */
	row_stop = MIN ( row_max, row + raster_rows - 1);
	mid = 0;
	for (col = col_min; col <= last_col; ++col)		/* start at col_min and stop at last col. */
		{
		top = 0;
		for (row_idx = row_stop; row_idx >= row; --row_idx)
			{
			regs.x.dx = row_idx;
			regs.x.cx = col;		/* pixel at (row,col) = (dx,cx) */
			regs.h.ah = READ_PIX;
			int86 (VIDEO_INT, &regs, &regs);
			/* value returned in al reg. */
			pixel = 0;
			if (regs.h.al != 0) pixel = 0x80;	/* set top pixel: only 1 bit for BW modes */
			for (idx = 0; idx < bits; ++idx)	/* shift in the pixel data */
				{

				top = (unsigned char) _rotr ((unsigned int) top, 1);
				top |= pixel;

				}

			}
		mid |= top;	/* merge the column data */
		if (fputc ( top, stdprn ) == EOF) return (EOF);
		if (col & 1)
			{
			if (fputc ( mid, stdprn ) == EOF) return (EOF);
			mid = 0;
			}
		}
	if (col & 1)	/* need to print merged byte if last */
		{
		if (fputc ( mid, stdprn ) == EOF) return (EOF);
		}
	if (fputc ( '\n', stdprn ) == EOF) return (EOF);
	return (err);						/* column was an odd col. number */
	}

/*--------------------------------------------------------*/
static int pdump ( data ) union  { unsigned char pix[4]; unsigned long p;} data;
	{
	register int idx;
	/* send bytes of raster data to printer */

	for (idx = 3; idx > 0; --idx)
		{
		if (fputc ( data.pix[idx], stdprn ) == EOF) return (EOF);
		}
	return (0);
	}
/*---------------------------------------------------------
	This routine scans the columns and finds the last column where an active
	pixel exists, if any.  When it returns, (dx,cx) contain the (row,col)
	of the last location scanned.  If (dx,cx) >= (row_min,col_min) then there
	is an active pixel to transmit to the printer.
	*/
static int scan (row)
	int row;
	{
	register int col_idx, row_idx, row_stop;
	row_stop = MIN ( row_max + 1, row + raster_rows);
	for (col_idx = col_max; col_idx >= col_min; --col_idx)
		{
		for (row_idx = row; row_idx < row_stop; ++row_idx)
			{
			regs.h.ah = READ_PIX;
			regs.x.dx = row_idx;
			regs.x.cx = col_idx;
			int86 (VIDEO_INT, &regs, &regs);
			if (regs.h.al != 0) return (col_idx);
			}
		}
	return (col_idx);
	}
/*---------------------------------------------------------*/
static int set_lpi()
	{
	if (fputs ( "\015\012\033\063\030", stdprn ) == EOF) return (EOF);
	return (0);	/* <cr><lf><esc>'3 '<24> :  set printer to 24/2160 inch vert line spacing */
	}
/*---------------------------------------------------------*/
static int reset_lpi()
	{
	if (fputs ("\033\062", stdprn ) == EOF ) return (EOF);
	return (0);	/* <esc> '2' : set printer to 6 lpi vert spacing */
	}
/*---------------------------------------------------------*/
static int set_bit_image (count) int count;
	{
	/* set printer for bit image mode.
	and send the byte count */
	if (fputs ("\033L", stdprn)  == EOF) return (EOF);	/* <esc> L set double density graphics */
	if (fputc ( (char) count, stdprn) == EOF) return (EOF);
	if (fputc ( (char) (count >> 8), stdprn) == EOF) return (EOF);
	return (0);
				/* <esc>*<39h> <count % 256> <count/256> */
	}
