/*
 * $Source: s:/prj/tech/libsrc/dev2d/RCS/ipal.c $
 * $Revision: 1.1 $
 * $Author: KEVIN $
 * $Date: 1996/04/10 16:04:00 $
 *
 * RGB color manipulation routines.
 *
 * This file is part of the dev2d library.
 */

#include <grd.h>
#include <grmalloc.h>
#include <rgb.h>

/* Static Globals for his and her pleasure */
static int bcenter, gcenter, rcenter;
static long gdist, rdist, cdist;
static long cbinc, cginc, crinc;
static ulong *gdp, *rdp, *cdp;
static uchar *grgbp, *rrgbp, *crgbp;
static int gstride, rstride;
static long x, xsqr, colormax;
static int cindex;

void inv_cmap_2(int colors, uchar *colormap[3],int bits,ulong *dist_buf, uchar *rgbmap );
int _redloop();
int _greenloop(int restart);
int _blueloop(int restart);
void _maxfill(ulong *buffer);

/* The routines in this file operate on grs_rgb color values.  The color
   values are encoded so that each r,g, and b has 8 bits of integer, 2
   bits of fraction, and one bit of padding. */

/* This routine allocates a 15 bit inverse palette for the
   current screen palette (that 32768 bytes) and initializes
	it for the current palette.  Returns OUT_OF_MEMORY
	if it can't allocate anything.  If ipal is currently non-null
	it tries to delete it first, in the interests of robustness */

int gr_alloc_ipal(void)
{
	int err;

	if (grd_ipal != NULL) {
		if ((err = gr_free_ipal())<0) return err;
	}

	if ((grd_ipal = gr_malloc(32768))==NULL) return RGB_OUT_OF_MEMORY;

	if ((err = gr_init_ipal())<0) return err;
	return RGB_OK;
}

int gr_free_ipal(void)
{
	if (grd_ipal==NULL) return RGB_CANT_DEALLOCATE;
	gr_free(grd_ipal);
   grd_ipal=NULL;
	return RGB_OK;
}

/* Initializes the inverse palette to the current screen
   palette.  */

int gr_init_ipal(void)
{
	if (grd_ipal == NULL) return RGB_IPAL_NOT_ALLOCATED;
	return gr_calc_ipal(grd_bpal, grd_ipal);
}

/* Calculates an inverse palette from a given
   palette.  */

int gr_calc_ipal(grs_rgb *bpal, uchar *ipal)
{
	int i;
   uchar r,g,b;
	uchar *data,*colormap[3];
	ulong *dist_buf;

   if ((data = gr_malloc(3*256)) == NULL) return RGB_OUT_OF_MEMORY;
	/* needs to be split up into r,g,b planes */
	colormap[0] = data;
	colormap[1] = data + 256;
   colormap[2] = data + 2*256;

   for(i=0;i<256;++i) {
      gr_split_rgb(bpal[i],&r,&g,&b);
   	colormap[2][i] = r;
   	colormap[1][i] = g;
   	colormap[0][i] = b;
   }

   if ((dist_buf = gr_malloc(sizeof(ulong) * 32768))==NULL) return RGB_OUT_OF_MEMORY;

   inv_cmap_2(256,colormap,5,dist_buf,ipal);

   gr_free(dist_buf);
   gr_free(data);

	return RGB_OK;
}


static void inv_cmap_2(int colors, uchar *colormap[3],int bits,ulong *dist_buf, uchar *rgbmap )
{
	int nbits = 8 - bits;

	colormax = 1 << bits;
	x = 1 << nbits;
	xsqr = 1 << (2 * nbits);

	/* Compute strides for accessing the arrays. */

	gstride = colormax;
	rstride = colormax * colormax ;

	_maxfill(dist_buf);

	for(cindex = 0;cindex<colors; cindex++) {
		/* The initial position is the cell containing the colormap
		 * entry.  We get this by quantiziing the colormap values.
		 */
		rcenter = colormap[0][cindex] >> nbits;
		gcenter = colormap[1][cindex] >> nbits;
		bcenter = colormap[2][cindex] >> nbits;

		rdist = colormap[0][cindex] - (rcenter * x + x/2);
		gdist = colormap[1][cindex] - (gcenter * x + x/2);
		cdist = colormap[2][cindex] - (bcenter * x + x/2);
		cdist = rdist*rdist + gdist*gdist + cdist*cdist;

		crinc = 2 * ((rcenter+1) * xsqr - (colormap[0][cindex] * x));
		cginc = 2 * ((gcenter+1) * xsqr - (colormap[1][cindex] * x));
		cbinc = 2 * ((bcenter+1) * xsqr - (colormap[2][cindex] * x));

		/* Array starting points. */
		cdp = dist_buf + rcenter*rstride + gcenter*gstride + bcenter;
		crgbp = rgbmap + rcenter*rstride + gcenter*gstride + bcenter;

		_redloop();
	}
}

/* redloop -- loop up and down from red center. */
static int _redloop()
{
	int detect, r, first;
	long txsqr = xsqr + xsqr;
	static long rxx;

	detect = 0;

	/* Basic loop up */
	for (r = rcenter, rdist = cdist, rxx = crinc,
			rdp = cdp, rrgbp = crgbp, first = 1;
		r<colormax;
		r++, rdp += rstride, rrgbp += rstride,
			rdist += rxx, rxx += txsqr, first = 0) {
		if (_greenloop(first))
			detect = 1;
		else if (detect)
			break;
	}

	/* Basic loop down */
	for (r = rcenter - 1, rxx = crinc - txsqr, rdist = cdist - rxx,
			rdp = cdp - rstride, rrgbp = crgbp - rstride, first = 1;
		r >= 0;
		r--, rdp -= rstride, rrgbp -= rstride,
			rxx -= txsqr, rdist -= rxx, first = 0) {
		if (_greenloop(first))
			detect = 1;
		else if (detect)
			break;
	}

	return detect;
}


/* greenloop -- loop up and down from green center. */
static int _greenloop(int restart)
{
	int detect, g, first;
	long txsqr = xsqr + xsqr;
	static int here, min, max;
	static int prevmax, prevmin;
	int thismax, thismin;
	static long ginc, gxx, gcdist;
	static ulong *gcdp;
	static uchar *gcrgbp;

	/* Red loop restarted, reset variables to "center" position */
	if (restart) {
		here = gcenter;
		min = 0;
		max = colormax - 1;
		ginc = cginc;
		prevmax = 0;
		prevmin = colormax;
	}

	/* finding actual min and max on this line. */
	thismin = min;
	thismax = max;
	detect = 0;

	/* Basic loop up. */
	for (g=here, gcdist = gdist = rdist, gxx = ginc,
			gcdp = gdp = rdp, gcrgbp = grgbp = rrgbp, first = 1;
		g <= max;
		g++, gdp += gstride, gcdp += gstride,
			grgbp += gstride, gcrgbp += gstride,
			gdist += gxx, gcdist += gxx, gxx += txsqr, first = 0) {
		if (_blueloop(first)) {
			if (!detect) {
				/* remember here and associated data! */
				if (g>here) {
					here = g;
					rdp = gcdp;
					rrgbp = gcrgbp;
					rdist = gcdist;
					ginc = gxx;
					thismin = here;
				}
				detect = 1;
			}
		}
		else if (detect) {
			thismax = g - 1;
			break;
		}
	}

	/* Basic loop down */
	for (g=here - 1, gxx = ginc - txsqr, gcdist = gdist = rdist - gxx,
			gcdp = gdp = rdp - gstride, gcrgbp = grgbp = rrgbp - gstride,
			first = 1;
		g >= min;
		g--, gdp -= gstride, gcdp -= gstride,
			grgbp -= gstride, gcrgbp -= gstride,
			gxx -= txsqr, gdist -= gxx, gcdist -= gxx, first = 0) {
		if (_blueloop(first)) {
			if (!detect) {
				/* remember here! */
				here = g;
				rdp = gcdp;
				rrgbp = gcrgbp;
				rdist = gcdist;
				ginc = gxx;
				thismax = here;
				detect = 1;
			}
		}
		else if (detect) {
			thismin = g + 1;
			break;
		}
	}

	/* If we saw something, update the edge tracers.  Only
	 * tracks edges that are "shrinking" (min increating, max
	 * decreasing.
  	 */

	if (detect) {
		if (thismax < prevmax)
			max = thismax;
		prevmax = thismax;

		if (thismin > prevmin )
				min = thismin;
		prevmin = thismin;
	}
		
	return detect;
}

/* blueloop -- loop up and down from blue center. */
static int _blueloop(int restart)
{
	int detect;
	/* These are all registers on a Sun 3. Your mileage may differ. */
	ulong *dp;
	uchar *rgbp;
	long bdist, bxx;
	int b, i=cindex;
	long txsqr = xsqr + xsqr;
	int lim; 	/* for min and max, avoid extra registers. */
	static int here, min, max;
	static int prevmin, prevmax;	/* For tracking min and max. */
	int thismin, thismax;
	static long binc;

	if (restart) {
		here = bcenter;
		min = 0;
		max = colormax - 1;
		binc = cbinc;
		prevmin = colormax;
		prevmax = 0;
	}

	detect = 0;
	thismin = min;
	thismax = max;

	/* Basic loop up. */
	/* First loop just finds first applicable cell. */
	for (b = here, bdist = gdist, bxx = binc, dp = gdp, rgbp = grgbp,
			lim = max;
		b <= lim;
		b++, dp++, rgbp++, bdist += bxx, bxx += txsqr) {
		if (*dp > bdist) {
			/* Remember new here and associated data! */
			if (b>here) {
				here = b;
				gdp = dp;
				grgbp = rgbp;
				gdist = bdist;
				binc = bxx;
				thismin = here;
			}
			detect = 1;
			break;
		}
	}
	/* Second loop fills in a run of closer cells. */
	for (;
		b <= lim;
		b++, dp++, rgbp++, bdist += bxx, bxx += txsqr) {
		if (*dp > bdist ) {
			*dp = bdist;
			*rgbp = i;
		} else {
			thismax = b - 1;
			break;
		}
	}

	/* Basic loop down */
	/* Do initializations here, since the 'find' loop might not get
	 * executed.
	 */
	lim = min;
	b = here - 1;
	bxx = binc - txsqr;
	bdist = gdist - bxx;
	dp = gdp - 1;
	rgbp = grgbp - 1;
	/* The 'find' loop ios executed on ly if we didn't already find
	 * something.
	 */
	if (!detect)
		for(;
			b >= lim;
			b--, dp--, rgbp--, bxx -= txsqr, bdist -= bxx) {
			if ( *dp > bdist) {
				/* Remember here! */
				/* No test for b against here necessary because b <
				 * here by definition.
				 */
				here = b;
				gdp = dp;
				grgbp = rgbp;
				gdist = bdist;
				binc = bxx;
				thismax = here;
				detect = 1;
				break;
			}
		}
	/* the 'update' loop */
	for (;
			b>= lim;
			b--, dp--, rgbp--, bxx -= txsqr, bdist -= bxx) {
		if ( *dp > bdist) {
			*dp = bdist;
			*rgbp = i;
		} else {
			thismin = b + 1;
			break;
		}
	}

	/* If we saw something, update the edge trackers. */
	if (detect) {
		/* Only tracks edges that are 'shrinking' (*min increasin, max
		 * decreasing).
	    */
		if (thismax < prevmax)
		 	max = thismax;
		if (thismin > prevmin )
			min = thismin;

		/* Remember the min and max values. */
		prevmax = thismax;
		prevmin = thismin;
	}

	return detect;
}

/* Fill a buffer with the largest unsigned long. */
static void _maxfill(ulong *buffer)
{
	ulong maxv = (long)-1;
	long i;
	ulong *bp;

	for(i=colormax * colormax * colormax, bp = buffer;
			i > 0;
			i--, bp++ )
		*bp = maxv;
}
