/* Copyright (c) 1992 Regents of the University of California */

#ifndef lint
static char SCCSid[] = "@(#)vgaimage.c 2.2 10/14/92 LBL";
#endif

/*
 *  vgaimage.c - driver for VGA board under DOS
 */

#include  "standard.h"
#include  <graph.h>
#include  "color.h"
#include  "random.h"
#include  "resolu.h"
#include <dos.h>
#include <i86.h>

#define  M_RDOWN        0x8
#define  M_RUP          0x10
#define  M_LDOWN        0x2
#define  M_LUP          0x4
#define  M_MOTION       0x1

int crad;
int mouse_event = 0;
int mouse_xpos = -1;
int mouse_ypos = -1;

#define  hide_cursor()  move_cursor(-1,-1)
#define  show_cursor()  move_cursor(mouse_xpos,mouse_ypos)

#define  CTRL(c)        ((c)-'@')

#define  MAXWIDTH       1024
#define  MAXHEIGHT      768

short  ourblack = 0; ourwhite = 1;

double  gamcor = 2.2;                   /* gamma correction */

int  dither = 1;                        /* dither colors? */

int  maxcolors = 0;                     /* maximum colors */
int  minpix = 0;                        /* minimum pixel value */
int  greyscale = 0;                     /* in grey */

int  scale = 0;                         /* scalefactor; power of two */

COLR  scanline[MAXWIDTH];               /* scan line buffer */

int  xmax, ymax;                        /* picture dimensions */
FILE  *fin = stdin;                     /* input file */
long  scanpos[MAXHEIGHT];               /* scan line positions in file */
int  cury = 0;                          /* current scan location */

double  exposure = 1.0;                 /* exposure compensation used */

int  wrongformat = 0;                   /* input in another format? */

struct {
	int  xmin, ymin, xsiz, ysiz;
}  box = {0, 0, 0, 0};                  /* current box */

int  initialized = 0;
int  cheight, cwidth;

#define postext(x,y)    _settextposition(1+(y)/cheight,1+(x)/cwidth)

char  *progname;

char  errmsg[128];

extern BYTE  clrtab[256][3];            /* global color map */

extern long  ftell();


main(argc, argv)
int  argc;
char  *argv[];
{
	extern char  *getenv(), *fixargv0();
	char  *gv;
	int  headline();
	int  i;
	
	progname = argv[0] = fixargv0(argv[0]);
	if ((gv = getenv("GAMMA")) != NULL)
		gamcor = atof(gv);

	for (i = 1; i < argc; i++)
		if (argv[i][0] == '-')
			switch (argv[i][1]) {
			case 'c':
				maxcolors = atoi(argv[++i]);
				break;
			case 'b':
				greyscale = !greyscale;
				break;
			case 'm':
				maxcolors = 2;
				break;
			case 'd':
				dither = !dither;
				break;
			case 'e':
				if (argv[i+1][0] != '+' && argv[i+1][0] != '-')
					goto userr;
				scale = atoi(argv[++i]);
				break;
			case 'g':
				gamcor = atof(argv[++i]);
				break;
			default:
				goto userr;
			}
		else
			break;

	if (i == argc-1) {              /* open picture */
		fin = fopen(argv[i], "r");
		if (fin == NULL) {
			sprintf(errmsg, "cannot open file \"%s\"", argv[i]);
			quiterr(errmsg);
		}
	} else if (i != argc)
		goto userr;
	setmode(fileno(fin), O_BINARY);
				/* get header */
	getheader(fin, headline, NULL);
				/* get picture dimensions */
	if (wrongformat || fgetresolu(&xmax, &ymax, fin) < 0)
		quiterr("bad picture format");
	if (xmax > MAXWIDTH | ymax > MAXHEIGHT)
		quiterr("input picture too large for VGA");

	init();                 /* initialize and display */

	while (docommand())     /* loop on command */
		;
	quiterr(NULL);
userr:
	fprintf(stderr,
	"Usage: %s [-b][-m][-d][-c ncolors][-e +/-stops] file\n", progname);
	exit(1);
}


headline(s)             /* get relevant info from header */
char  *s;
{
	char  fmt[32];

	if (isexpos(s))
		exposure *= exposval(s);
	else if (isformat(s)) {
		formatval(fmt, s);
		wrongformat = strcmp(fmt, COLRFMT);
	}
}


init()                  /* initialize and load display */
{
	static struct {
		short   mode;
		short   xsiz, ysiz;
	} video[] = {    
		{_MRES256COLOR, 320, 200},
		{_VRES256COLOR, 640, 400},
		{_SVRES256COLOR, 800, 600},
		{_XRES256COLOR, 1024, 768},
		-1
	};
	struct videoconfig      config;
	register int    i;
				/* pick a card... */
	for (i = 0; video[i].mode != -1; i++)
		if (video[i].xsiz >= xmax && video[i].ysiz >= ymax)
			break;
	if (video[i].mode == -1)
		quiterr("input picture too large");
	if (_setvideomode(video[i].mode) == 0)
		quiterr("inadequate display card for picture");
	ms_init();
	initialized = 1;
	_getvideoconfig(&config);
	if (maxcolors == 0)
		maxcolors = config.numcolors;
	if (maxcolors > 4) {
		maxcolors -= minpix = 2;
		_settextcolor(ourwhite);
	}
	cheight = config.numypixels/config.numtextrows;
	cwidth = config.numxpixels/config.numtextcols;
				/* clear scan position array */
	for (i = 0; i < ymax; i++)
		scanpos[i] = -1;
				/* display image */
	if (greyscale)
		greyimage();
	else
		mappedimage();
}


quiterr(err)            /* print message and exit */
char  *err;
{
	if (initialized) {
		ms_done();
		_setvideomode(_DEFAULTMODE);
	}
	if (err != NULL) {
		fprintf(stderr, "%s: %s\n", progname, err);
		exit(1);
	}
	exit(0);
}


int
docommand()                     /* execute command */
{
	char  buf[64];
	COLOR  cval;
	int  com;
	double  comp;

	while (!kbhit())
		watch_mouse();
	com = getch();
	switch (com) {                  /* interpret command */
	case 'q':
	case CTRL('Z'):                         /* quit */
		return(0);
	case '\n':
	case '\r':
	case 'l':
	case 'c':                               /* value */
		if (avgbox(cval) == -1)
			return(-1);
		switch (com) {
		case '\n':
		case '\r':                              /* radiance */
			sprintf(buf, "%.3f", intens(cval)/exposure);
			break;
		case 'l':                               /* luminance */
			sprintf(buf, "%.0fL", luminance(cval)/exposure);
			break;
		case 'c':                               /* color */
			comp = pow(2.0, (double)scale);
			sprintf(buf, "(%.2f,%.2f,%.2f)",
					colval(cval,RED)*comp,
					colval(cval,GRN)*comp,
					colval(cval,BLU)*comp);
			break;
		}
		postext(box.xmin+box.xsiz/2, box.ymin+box.ysiz/2);
		hide_cursor();
		_outtext(buf);
		show_cursor();
		return(1);
	default:
		return(-1);
	}
}


watch_mouse()                   /* look after mousie */
{
	int     a_x, a_y, l_x, l_y;

	if (mouse_event & M_MOTION)
		move_cursor(mouse_xpos, mouse_ypos);
	if (!(mouse_event & M_LDOWN))
		return;
	l_x = a_x = mouse_xpos; l_y = a_y = mouse_ypos;
	hide_cursor();
	revbox(a_x, a_y, l_x, l_y);             /* show box */
	do {
		mouse_event = 0;
		while (!mouse_event)
			;
		if (mouse_event & M_MOTION) {
			revbox(a_x, a_y, l_x, l_y);
			revbox(a_x, a_y, l_x=mouse_xpos, l_y=mouse_ypos);
		}
	} while (!(mouse_event & M_LUP));
	revbox(a_x, a_y, l_x, l_y);             /* hide box */
	show_cursor();
	box.xmin = mouse_xpos;
	box.ymin = mouse_ypos;
	if (box.xmin > a_x) {
		box.xsiz = box.xmin - a_x + 1;
		box.xmin = a_x;
	} else {
		box.xsiz = a_x - box.xmin + 1;
	}
	if (box.ymin > a_y) {
		box.ysiz = box.ymin - a_y + 1;
		box.ymin = a_y;
	} else {
		box.ysiz = a_y - box.ymin + 1;
	}
	mouse_event = 0;
}


revbox(x0, y0, x1, y1)                  /* draw box with reversed lines */
int  x0, y0, x1, y1;
{
	_setplotaction(_GXOR);
	_setcolor(255);
	_moveto(x0, y0);
	_lineto(x1, y0);
	_lineto(x1, y1);
	_lineto(x0, y1);
	_lineto(x0, y0);
}


int
avgbox(clr)                             /* average color over current box */
COLOR  clr;
{
	static COLOR  lc;
	static int  ll, lr, lt, lb;
	int  left, right, top, bottom;
	int  y;
	double  d;
	COLOR  ctmp;
	register int  x;

	setcolor(clr, 0.0, 0.0, 0.0);
	left = box.xmin;
	right = left + box.xsiz;
	if (left < 0)
		left = 0;
	if (right > xmax)
		right = xmax;
	if (left >= right)
		return(-1);
	top = box.ymin;
	bottom = top + box.ysiz;
	if (top < 0)
		top = 0;
	if (bottom > ymax)
		bottom = ymax;
	if (top >= bottom)
		return(-1);
	if (left == ll && right == lr && top == lt && bottom == lb) {
		copycolor(clr, lc);
		return(0);
	}
	for (y = top; y < bottom; y++) {
		if (getscan(y) == -1)
			return(-1);
		for (x = left; x < right; x++) {
			colr_color(ctmp, scanline[x]);
			addcolor(clr, ctmp);
		}
	}
	d = 1.0/((right-left)*(bottom-top));
	scalecolor(clr, d);
	ll = left; lr = right; lt = top; lb = bottom;
	copycolor(lc, clr);
	return(0);
}


setpalette()                    /* set our palette using clrtab */
{
	long    cvals[256];
	register int    i;

	if (minpix >= 2) {
		cvals[ourblack] = _BLACK; cvals[ourwhite] = _BRIGHTWHITE;
	}
	for (i = 0; i < maxcolors; i++)
		cvals[i+minpix] = clrtab[i][BLU]<<14 & 0x3f0000L |
				  clrtab[i][GRN]<<6 & 0x3f00 |
				  clrtab[i][RED]>>2;
	_remapallpalette(cvals);
}


greyimage()                     /* display greyscale image */
{
	short   thiscolor, lastcolor = -1;
	int     y;
	register int    x;
					/* set gamma correction */
	setcolrgam(gamcor);
					/* set up color map */
	for (x = 0; x < maxcolors; x++)
		clrtab[x][RED] = clrtab[x][GRN] =
			clrtab[x][BLU] = ((long)x*256+maxcolors/2)/maxcolors;
	setpalette();
	_setplotaction(_GPSET);
					/* read and display file */
	for (y = 0; y < ymax; y++) {
		getscan(y);
		if (scale)
			shiftcolrs(scanline, xmax, scale);
		colrs_gambs(scanline, xmax);
		if (maxcolors < 256)
			for (x = 0; x < xmax; x++) { 
				thiscolor = ((long)normbright(scanline[x]) *
					maxcolors + 128) >> 8;
				if (thiscolor != lastcolor)
					_setcolor((lastcolor=thiscolor)+minpix);
				_setpixel(x, y);
			}
		else
			for (x = 0; x < xmax; x++) {
				thiscolor = normbright(scanline[x]);
				if (thiscolor != lastcolor)
					_setcolor((lastcolor=thiscolor)+minpix);
				_setpixel(x, y);
			}
	}
}


mappedimage()                   /* display color-mapped image */
{
	BYTE    bscan[MAXWIDTH];
	int     y;
	register int    x;
					/* set gamma correction */
	setcolrgam(gamcor);
					/* make histogram */
	_outtext("Quantizing image -- Please wait...");
	new_histo();
	for (y = 0; y < ymax; y++) {
		if (getscan(y) < 0)
			quiterr("seek error in getmapped");
		if (scale)
			shiftcolrs(scanline, xmax, scale);
		colrs_gambs(scanline, xmax);
		cnt_colrs(scanline, xmax);
	}
					/* map pixels */
	if (!new_clrtab(maxcolors))
		quiterr("cannot create color map");
	setpalette();
	_setplotaction(_GPSET);
					/* display image */
	for (y = 0; y < ymax; y++) {
		if (getscan(y) < 0)
			quiterr("seek error in getmapped");
		if (scale)
			shiftcolrs(scanline, xmax, scale);
		colrs_gambs(scanline, xmax);
		if (dither)
			dith_colrs(bscan, scanline, xmax);
		else
			map_colrs(bscan, scanline, xmax);
		for (x = 0; x < xmax; x++) {
			if (x==0 || bscan[x] != bscan[x-1])
				_setcolor(bscan[x]+minpix);
			_setpixel(x, y);
		}
	}
}


getscan(y)
int  y;
{
	if (y != cury) {
		if (scanpos[y] == -1)
			return(-1);
		if (fseek(fin, scanpos[y], 0) == -1)
			quiterr("fseek error");
		cury = y;
	} else if (scanpos != NULL && scanpos[y] == -1)
		scanpos[y] = ftell(fin);

	if (freadcolrs(scanline, xmax, fin) < 0)
		quiterr("picture read error");

	cury++;
	return(0);
}


/*
 * Microsoft Mouse handling routines
 */


#pragma off (check_stack)
void _loadds far mouse_handler (int max, int mcx, int mdx)
{
#pragma aux mouse_handler parm [EAX] [ECX] [EDX]
	mouse_event = max;
	mouse_xpos = mcx;
	mouse_ypos = mdx;
}
#pragma on (check_stack)


void
move_cursor(newx, newy)         /* move cursor to new position */
int  newx, newy;
{
	static char  *imp = NULL;
	static int  curx = -1, cury = -1;
#define xcmin           (curx-crad<0 ? 0 : curx-crad)
#define ycmin           (cury-crad<0 ? 0 : cury-crad)
#define xcmax           (curx+crad>=xmax ? xmax-1 : curx+crad)
#define ycmax           (cury+crad>=ymax ? ymax-1 : cury+crad)

	if (newx == curx & newy == cury)
		return;
	if (imp == NULL && 
		(imp = bmalloc(_imagesize(0,0,2*crad+1,2*crad+1))) == NULL) {
		quiterr("out of memory in move_cursor");
	}
	if (curx >= 0 & cury >= 0)      /* clear old cursor */
		_putimage(xcmin, ycmin, imp, _GPSET);
					/* record new position */
	curx = newx; cury = newy;
	if (curx < 0 | cury < 0)
		return;         /* no cursor */
					/* save under new cursor */
	_getimage(xcmin, ycmin, xcmax, ycmax, imp);
					/* draw new cursor */
	_setplotaction(_GPSET);
	_setcolor(ourwhite);
	_rectangle(_GFILLINTERIOR, xcmin, cury-1, xcmax, cury+1);
	_rectangle(_GFILLINTERIOR, curx-1, ycmin, curx+1, ycmax);
	_setcolor(ourblack);
	_moveto(xcmin+1, cury);
	_lineto(xcmax-1, cury);
	_moveto(curx, ycmin+1);
	_lineto(curx, ycmax-1);
#undef xcmin
#undef ycmin
#undef xcmax
#undef ycmax
}


int
ms_init()
{
    struct SREGS sregs;
    union REGS inregs, outregs;
    int far *ptr;
    int (far *function_ptr)();

    segread(&sregs);

    /* check for mouse driver */

    inregs.w.ax = 0;
    int386 (0x33, &inregs, &outregs);
    if( outregs.w.ax != -1 ) {
	return(0);
    }

    crad = ymax/40;
    
    /* set screen limits */

    inregs.w.ax = 0x7;          /* horizontal resolution */
    inregs.w.cx = 0;
    inregs.w.dx = xmax-1;
    int386x( 0x33, &inregs, &outregs, &sregs );
    inregs.w.ax = 0x8;          /* vertical resolution */
    inregs.w.cx = 0;
    inregs.w.dx = ymax-1;
    int386x( 0x33, &inregs, &outregs, &sregs );

    /* install watcher */

    inregs.w.ax = 0xC;
    inregs.w.cx = M_LDOWN | M_LUP | M_MOTION;
    function_ptr = mouse_handler;
    inregs.x.edx = FP_OFF( function_ptr );
    sregs.es     = FP_SEG( function_ptr );
    int386x( 0x33, &inregs, &outregs, &sregs );
    
    return(1);
}

ms_done()
{
    union REGS inregs, outregs;

    /* uninstall watcher */

    inregs.w.ax = 0;
    int386 (0x33, &inregs, &outregs);
}

