/*	PCX.c -  Graphic Routines that operate on PCX pictures
*/


#include <stdio.h>
#include "lib.h"
#include "pcx.h"


int pcx_init();
int pcx_clear();
int pcx_set_point();
int pcx_clr_point();
int pcx_xor_point();
int pcx_write_row();
int pcx_select_plane();
int pcx_set_palette();
int pcx_mode();
int movmem();
int pokeb();
int peekb();
int pcx_get_point();

static int (*pcx_func[])() = {
	pcx_init,      pcx_clear,     pcx_set_point, pcx_clr_point,
	pcx_xor_point, pcx_get_point, pcx_write_row, pcx_select_plane,
	pcx_set_palette,pcx_mode,     movmem, peekb, pokeb };

PCXPIC *pcx_cpic;      /* current picture   */
static int cplane;     /* current plane     */
int	pcx_md;        /* 1 if CGA-type picture, 0 for HERC/EGA */


int pcx_init()
{
	movmem( pcx_func, vgr_func, sizeof(vgr_func) );

	VGR_NBPL = VGR_HRES = VGR_VRES = VGR_NCOLORS = cplane = 0;
	if ( pcx_cpic )
	{  VGR_HRES    = pcx_cpic->hdr.hres;
	   VGR_VRES    = pcx_cpic->hdr.vres;
	   VGR_NBPL    = pcx_cpic->hdr.bpl;
	   VGR_NCOLORS = pcx_cpic->hdr.bpp > 1 ? 
	                 0x01 << pcx_cpic->hdr.bpp :
	                 0x01 << pcx_cpic->hdr.nplanes;
	};

	return OK;
}


int pcx_select_plane( plane )
int plane;
{
	return cplane = plane & 0x03;
}


int pcx_write_row( row, prow, nbytes )
int nbytes, row;
char *prow;
{
	int i;

	i = pcx_cpic->hdr.hres / 8;
	movmem( prow, (pcx_cpic->rows[cplane][row]), 
	               nbytes <= i ? nbytes : i );
}


int pcx_clear()
{
	int plane, row, nplanes, nrows, bytes;

	nplanes =  pcx_cpic->hdr.nplanes;
	nrows   =  pcx_cpic->hdr.y2 - pcx_cpic->hdr.y1 +1;
	bytes   =  pcx_cpic->hdr.bpl;

	for ( plane=0; plane < nplanes; plane ++ )
	   for ( row=0; row < bytes; row++ )
	      setmem( (pcx_cpic->rows[plane][row]), bytes, 0 );
}


int pcx_mode( m )
int m;
{
	switch ( m )
	{  case MODE_APA0:   /* 640x350x16 */
	   case MODE_APA1:   /* 720x348x02 */
	   case MODE_APA2:   /* 640x200x02 */
	      /* all these modes save color information
	         1 bit per plane.
	      */
	      pcx_md = 0;
	      break;
	   case MODE_APA3:   /* 320x200x04 */
	      /* this mode puts two bits per pixel into the
	         same plane.
	      */
	      pcx_md = 1;
	      break;
	   default:          /* invalid!   */
	      return ERROR;
	};

	return OK;
}


int pcx_xor_point( x, y, color )
int x, y, color;
{
	return pcx_set_point( x, y, pcx_get_point( x, y ) ^ color );
}


int pcx_clr_point( x, y )
int x, y;
{
	return pcx_set_point( x, y, 0 );
}


int pcx_set_point( x, y, color )
int x, y, color;
{
	register unsigned char *p;
	unsigned char plane, b, o, mask;

	if ( pcx_md )
	{  o = (x & 3) << 1;
	   p = (unsigned char *)pcx_cpic->rows[0][y] + (x>>2);
	   *p = (*p & ~(0xc0 >> o)) | ((color & 0x03) << (6-o));
	   return OK;
	};

	o = x >> 3;
	b = (unsigned int)0x80 >> (x & 0x07);

	for ( mask=1,plane=0; plane < pcx_cpic->hdr.nplanes; plane++, mask<<=1 )
	{  p = (unsigned char *)pcx_cpic->rows[plane][y] + o;
	   if ( color & mask )
	      *p |= b;
	   else *p &= ~b;
	};

	return OK;
}


int pcx_get_point( x, y )
int x, y;
{
	unsigned char plane, b, o, color;

	if ( pcx_md )
	{  b = (x & 3) << 1;
	   return ( pcx_cpic->rows[0][y][x>>2] & (0xc0 >> b) ) >> (6-b);
	};

	o = x >> 3;
	b = (unsigned int)0x80 >> (x & 0x07);

	for ( color=plane=0; plane < pcx_cpic->hdr.nplanes; plane++ )
	   color |= !!(pcx_cpic->rows[plane][y][o] & b) << plane;

	return color;
}


/*	PCX Palette Settings.
*/

int pcx_set_palette( reg, red, green, blue )
unsigned char reg, red, green, blue;
{
	if ( pcx_md )
	{  pcx_cpic->hdr.triple[reg].red = red;
	   return OK;
	};

	/* why do we multiply by 85? */
	pcx_cpic->hdr.triple[reg].red   = red   * 85;
	pcx_cpic->hdr.triple[reg].green = green * 85;
	pcx_cpic->hdr.triple[reg].blue  = blue  * 85;
	return OK;
}


PCXPIC *pcx_init_pic( hres, vres, nplanes )
unsigned int hres, vres, nplanes;
{
	int y, p, bpl;
	PCXPIC *pic;
	char *calloc();
	static TRIPLET ega_pal[] = {  /* copied from a        */
	      0x00, 0x00, 0x00,       /* PBRUSH picture file  */
	      0x00, 0x00, 0xaa,
	      0x00, 0xaa, 0x00,
	      0x00, 0xaa, 0xaa,
	      0xaa, 0x00, 0x00,
	      0xaa, 0x00, 0xaa,
	      0xaa, 0xaa, 0x00,
	      0xaa, 0xaa, 0xaa,
	      0x55, 0x55, 0x55,
	      0x55, 0x55, 0xff,
	      0x55, 0xff, 0x55,
	      0x55, 0xff, 0xff,
	      0xff, 0x55, 0x55,
	      0xff, 0x55, 0xff,
	      0xff, 0xff, 0x55,
	      0xff, 0xff, 0xff        };

	static TRIPLET cga_pal[] = {
	        0,0,0, 255,0,0,
	        0,0,0, 255,0,0,
	        0,0,0, 255,0,0,
	        0,0,0, 255,0,0,
	        0,0,0, 255,0,0,
	        0,0,0, 255,0,0,
	        0,0,0, 255,0,0,
	        0,0,0, 255,0,0        };

	if ( !(pic = (PCXPIC *) calloc( 1, sizeof(PCXPIC) )) )
	   return NULL; /* out of memory */

	pic->hdr.x2 = hres -1;
	pic->hdr.y2 = vres -1;

	pic->hdr.maker     = 10;
	pic->hdr.version   =  5;
	pic->hdr.code      =  1;
	pic->hdr.bpp       =  1 + pcx_md;
	pic->hdr.bpl       = bpl = (hres * (1+pcx_md)) / 8;

	VGR_HRES    = pic->hdr.hres    = hres;
	VGR_VRES    = pic->hdr.vres    = vres;
	VGR_NBPL    = bpl;
	VGR_NCOLORS = 1 << (pic->hdr.nplanes = nplanes);

	movmem( (pcx_md ? cga_pal : ega_pal), 
	         pic->hdr.triple, sizeof(pic->hdr.triple) );

	for ( p=0; p < nplanes; p++ )
	{  if ( ! (pic->rows[p] = (uchar **)calloc( 1, sizeof(char *) * vres)) )
	      return pcx_free_pic( pic ), (PCXPIC *)0;
	   for ( y=0; y < vres; y++ )
	      if ( !(pic->rows[p][y] = (uchar *)calloc( 1, bpl )) )
	         return pcx_free_pic( pic ), (PCXPIC *)0;
	};

	return pic;
}


int pcx_free_pic( pic )
PCXPIC *pic;
{
	int row, plane, nrows, nplan;

	nrows = pic->hdr.y2 - pic->hdr.y1 +1;
	nplan = pic->hdr.nplanes;

	VGR_MODE( MODE_TEXT0 );
	printf("At pcx_free_pic, probably out of memory...\n");

	for ( plane = 0; plane < nplan; plane++ )
	{  for ( row = 0; row < nrows; row++ )
	      allocf( pic->rows[plane][row] );
	   allocf( pic->rows[plane] );
	};

	allocf( pic );
	return NULL;
}


void map_not( map, len )
register unsigned int *map, len;
{
	for ( ; len--; map++ )
	   *map = ~ *map;
}


void pcx_invert_pic( pic )
PCXPIC *pic;
{
	int p, r, i;

	for ( p=0; p < pic->hdr.nplanes; p++ )
	   for ( r=0; r < (pic->hdr.y2 - pic->hdr.y1); r++ )
	      map_not( pic->rows[p][r], pic->hdr.bpl / 2 );
}


allocf( p )
char *p;
{
	if ( !p )
	   return OK;

	if ( free(p) == -1 )
	   CRASH( "heap munged" );
}

#ifdef MAIN

/*	Manx 'C stack/heap size adjustment -
	The vgr_fill() function uses an enourmous amount of stack,
	and we're going to need enough heap space to allocate
	4 bit maps for the EGA board.
*/
uint	__STKLOW = 0,
    	__STKSIZ = 4096,
    	__HEAPSIZ = 8192;

int 	cga_init(), ega_init(), herc_init();
int	board, (*board_init)(), md;

/*	This structure is used here to facilitate drawing the
	simple X and complex box that are used to test the
	dot, line and fill functions.
*/
typedef struct {
   int x,y;
} OBJ;

static OBJ obj[] = { 
100, 100, 150, 200, 200, 100, 250, 150, 175, 250, 250, 350, 200, 400, 150, 300, 
100, 400,  50, 350, 125, 250,  50, 150, 100, 100 };

static OBJ obj2[] = {
  0,  0,280,  0,280,140,240,140,240, 20,200, 20,200,280,240,280,240,160, 
280,160,280,300,  0,300,  0,160, 40,160, 40,280, 80,280, 80, 20, 40, 20, 
 40,140,  0,140,  0,  0 };
	   
static OBJ obj3[] = {
	   120,20, 160,20, 160,280, 120,280, 120,20 };


main( argc, argv )
char **argv;
int argc;
{
	void pcx_showpic();
	FILE *fp, *fopen();
	int c, h, v, p;

	printf("PCX test routine.\n");
	if ( argc == 1 )
	   printf("Try \"PCX xx\" for help\n" );

	board = vgr_get_board();

	if ( argc >= 2 )
	{       if ( !strcmp(argv[1], "ega") )
	           board = TYPE_EGA;
	   else if ( !strcmp(argv[1], "cga") )
	           board = TYPE_CGA;
	   else if ( !strcmp(argv[1], "herc") )
	           board = TYPE_HERC;
	   else if ( !strcmp(argv[1], "cga2") )
	           board = 9;
	   else {  printf("\
usage: PCX\n\
or     PCX <board>\n\
or     PCX <board> <pcx_file_name>\n\n\
 func: Excercises the board & PCX routines.\n\
       <board> may be one of:\n\
       	   cga - 640x200x2 CGA\n\
       	   cga2- 320x200x4 CGA\n\
       	   herc- Hercules Hi-Res\n\
       	   ega - 640x350x16 EGA\n\
       <pcx_file_name> may be any file name,\n\
       although an extension of .PCX is\n\
       appropriate.\n" );
 	           exit(1);
 	        };
	};

	switch( board )
	{  case TYPE_UNKNOWN:
	   case TYPE_MDA:
	      CRASH("unable to detect a HERC, CGA or EGA board");
	   case TYPE_CGA:
	      printf("CGA board detected\n");
	      board_init = cga_init;
	      md = MODE_APA2, c = 1, h = 640, v=200, p=1;
	      break;
	   case 9:
	      printf("CGA/APA3 Selected\n");
	      board_init = cga_init;
	      md = MODE_APA3, c = 2, h = 320, v=200, p=1;
	      break;
	   case TYPE_EGA:
	      printf("EGA board detected\n");
	      board_init = ega_init;
	      md = MODE_APA0, c = 9, h = 640, v=350, p=4;
	      break;
	   case TYPE_HERC:
	      printf("HERC board detected\n");
	      board_init = herc_init;
	      md = MODE_APA1, c = 1, h = 720, v=348, p=1;
	      break;
	   default:
	      CRASH("bug: invalid board type number");
	};

	printf("Hit return to continue:");
	getchar();

	/* pcx_init_pic needs to know what mode it's in
	*/
	pcx_init();
	pcx_mode( md );   
	if ( !(pcx_cpic = pcx_init_pic( h, v, p )) )
	   CRASH("out of memory");

	pcx_init();
	if ( VGR_MODE( md ) )
	   CRASH( "error in mode selection" );

	printf("HRES=%d, VRES=%d, NCOLORS=%d, NPLANES=%d, pcx_md=%d\n",
	      VGR_HRES, VGR_VRES, VGR_NCOLORS, p, pcx_md );

	draw_it( (OBJ *)&obj,  NELEMS(obj) , 3, 3,   0, 0, c );
	printf(".");
	draw_it( (OBJ *)&obj2, NELEMS(obj2), 3, 3, 150, 0, c );
	printf(".");
	draw_it( (OBJ *)&obj3, NELEMS(obj3), 3, 3, 150, 0, c );
	printf(".");

	vgr_fill(  33, 40, c );
	printf(".");
	vgr_fill( 179, 19, c );

	printf("\nhit return to continue, then hit return again:");
	getchar();

	if ( (*board_init)() )
	   CRASH( "board init failed" );
	if ( VGR_MODE( md ) )
	   CRASH( "error in mode selection" );

	pcx_showpic( pcx_cpic, 0, 0, 0 );

	getchar();

	VGR_MODE( MODE_TEXT0 );

	if ( argc == 3 )
	{  if ( !(fp = fopen( argv[2], "w" )) )
	      CRASH( "error opening picture file" );
	   if ( pcx_write_pic( pcx_cpic, fp ) )
	      CRASH( "error writing picture file" );
	   fclose(fp);
	};

	exit(0);
}


draw_it( obj, n, divx, divy, ox, oy, c )
OBJ *obj;
int n, divx, divy;
{
	int x;

	vgr_point( obj[0].x / divx + ox, obj[0].y / divy + oy, -1 );
	for ( x=1; x < n; x++ )
	   vgr_point( obj[x].x / divx + ox, obj[x].y / divy + oy, c );
}


#endif

