/* tile.c */
#define VERSION "fractile 1.1, placed in public domain by Steve Kirkendall, 8 April 1996"

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <math.h>
#include <ctype.h>
#include "tile.h"
extern char *optarg;
extern int optind;
extern char **readopt(FILE *fp);

/* The following two macros allow rand() to be used instead of drand48().
 * This was done because Microsoft Visual C++ doesn't have drand48(), and
 * apparently even some UNIX systems don't have it.  The rand() function
 * isn't quite as random as drand48(), but it is good enough for this program.
 */
#define drand48()	((double)(rand() & 0x7fff) / 32768.0)
#define srand48(seed)	srand(seed)

#define QTY(array)	(sizeof(array) / sizeof(*(array)))

/* This is the entire color table */
rgb_t	palette[256];
int	palettesize;

/* This is a list of known color names, and their RGB values */
static struct
{
	char	*name;
	rgb_t	rgb;
} colors[] =
{
	{"black",	{   0,   0,   0	}},
	{"white",	{ 255, 255, 255	}},
	{"light",	{ 255, 255, 255	}},
	{"pale",	{ 170, 170, 170	}},
	{"dull",	{  85,  85,  85	}},
	{"dark",	{   0,   0,   0	}},
	{"red",		{ 255,   0,   0	}},
	{"green",	{   0, 255,   0	}},
	{"blue",	{   0,   0, 255	}},
	{"yellow",	{ 255, 255,   0	}},
	{"brown",	{ 128,  85,   0	}},
	{"orange",	{ 170,  85,   0	}},
	{"gray",	{ 128, 128, 128	}},
	{"grey",	{ 128, 128, 128	}},
	{"magenta",	{ 255,   0, 255	}},
	{"cyan",	{   0, 255, 255	}},
	{"random",	{   0,   0,   0 }},
	{"same",	{   0,   0,   0 }}
};


/* convert a color to an rgb value */
rgb_t name_to_RGB(char *name)
{
	int	weight = 0;
	int	i;
	rgb_t	rgb;

#ifdef RGB_TXT
	FILE	*fp;
	char	rgbname[50];
	char	linebuf[80];
	int	red, green, blue;

	/* maybe it is an X11 color name? */
	fp = fopen(RGB_TXT, "r");
	if (fp)
	{
		rgbname[0] = '\0';
		while (fgets(linebuf, sizeof linebuf, fp))
		{
			sscanf(linebuf, "%d %d %d %[a-zA-Z0-9 ]", &red, &green, &blue, rgbname);
			if (!strcmp(name, rgbname))
			{
				rgb.red = red;
				rgb.green = green;
				rgb.blue = blue;
				fclose(fp);
				return rgb;
			}
		}
		fclose(fp);
	}
#endif /* RGB_TXT */

	while (*name)
	{
		/* allow whitespace between color names */
		if (isspace(*name))
		{
			name++;
			continue;
		}

		/* look up the next color name */
		for (i = 0;
		     i < QTY(colors)
			&& strncmp(colors[i].name, name, strlen(colors[i].name));
		     i++)
		{
		}

		/* if unknown color, then complain */
		if (i >= QTY(colors))
		{
			fprintf(stderr, "unknown color %s\n", name);
			exit(1);
		}

		/* if "random", then generate random Red-Green-Blue values */
		if (!strcmp(colors[i].name, "random"))
		{
			colors[i].rgb.red = drand48() * 256;
			colors[i].rgb.green = drand48() * 256;
			colors[i].rgb.blue = drand48() * 256;

			/* also use this same random color as name "same" */
			colors[i + 1].rgb = colors[i].rgb;
		}

		/* blend this color with any previously mentioned colors */
		rgb.red = (rgb.red * weight + colors[i].rgb.red) / (weight + 1);
		rgb.green = (rgb.green * weight + colors[i].rgb.green) / (weight + 1);
		rgb.blue = (rgb.blue * weight + colors[i].rgb.blue) / (weight + 1);
		name += strlen(colors[i].name);
		weight++;
	}
	return rgb;
}

/* Convert a color from RGB format to HSV format */
hsv_t RGB_to_HSV(rgb_t rgb)
{
	hsv_t	hsv;	/* the resulting HSV value */
	double	r, g, b;/* the RGB components, in range 0.0 - 1.0 */
	double	max, min;/* extremes from r, g, b */
	double	delta;	/* difference between max and min */

	/* extract the RGB components from rgb */
	r = (double)rgb.red / 256;
	g = (double)rgb.green / 256;
	b = (double)rgb.blue / 256;

	/* find max and min */
	if (r > g)
	{
		max = (b > r) ? b : r;
		min = (g > b) ? b : g;
	}
	else
	{
		max = (b > g) ? b : g;
		min = (r > b) ? b : r;
	}

	/* compute "value" */
	hsv.value = max;

	/* compute "saturation" */
	hsv.saturation = (max > 0.0) ? (max - min) / max : 0;

	/* compute "hue".  This is the hard one */
	delta = max - min;
	if (delta <= 0.001)
	{
		/* gray - any hue will work */
		hsv.hue = 0.0;
	}
	else
	{
		/* divide hexagonal color wheel into three sectors */
		if (max == r)
			/* color is between yellow and magenta */
			hsv.hue = (g - b) / delta;
		else if (max == g)
			/* color is between cyan and yellow */
			hsv.hue = 2.0 + (b - r) / delta;
		else /* max == b */
			/* color is between magenta and cyan */
			hsv.hue = 4.0 + (r - g) / delta;

		/* convert hue to degrees */
		hsv.hue *= 60.0;

		/* make sure hue is not negative */
		if (hsv.hue < 0.0)
			hsv.hue += 360.0;
	}

	/* return the computed color */
	return hsv;
}


/* convert a color from HSV format to RGB format */
rgb_t HSV_to_RGB(hsv_t hsv)
{
	rgb_t	rgb;	/* the returned value */
	double	h;	/* copy of the "hsv.hue" */
	double	i, f;	/* integer and fractional parts of "h" */
	int	p, q, t;/* permuted RGB values, in integer form */
	int	v;	/* "hsv.value", in integer form */

	if (hsv.saturation < 0.01)
	{
		/* simple gray conversion */
		rgb.red = rgb.green = rgb.blue = (int)(hsv.value * 256.0);
	}
	else
	{
		/* convert hue to range [0,6) */
		h = hsv.hue / 60.0;
		if (h >= 6.0)
			h -= 6.0;

		/* break "h" down into integer and fractional parts. */
		i = floor(h);
		f = h - i;

		/* compute the permuted RGB values */
		v = (int)(hsv.value * 256);
		p = (int)((hsv.value * (1.0 - hsv.saturation)) * 256.0);
		q = (int)((hsv.value * (1.0 - (hsv.saturation * f))) * 256.0);
		t = (int)((hsv.value * (1.0 - (hsv.saturation * (1.0 - f)))) * 256.0);

		/* map v, p, q, and t into red, green, and blue values */
		switch ((int)i)
		{
		  case 0:   rgb.red = v, rgb.green = t, rgb.blue = p;	break;
		  case 1:   rgb.red = q, rgb.green = v, rgb.blue = p;	break;
		  case 2:   rgb.red = p, rgb.green = v, rgb.blue = t;	break;
		  case 3:   rgb.red = p, rgb.green = q, rgb.blue = v;	break;
		  case 4:   rgb.red = t, rgb.green = p, rgb.blue = v;	break;
		  case 5:   rgb.red = v, rgb.green = p, rgb.blue = q;	break;
		}
	}

	/* return the RGB value */
	return rgb;
}



/* This function generates the color palette, except for the flood/thaw color */
void genpalette(rgb_t firstrgb,	/* lighest color in spectrum */
		rgb_t lastrgb,	/* darkest color in spectrum */
		rgb_t otherrgb,	/* another color, outside of spectrum */
		int maxpalette,	/* number of colors in spectrum */
		int cw, int ccw, int bounce)/* how to interpolate colors */
{
	int	i, j, delta;
	hsv_t	lighthsv, darkhsv;
	hsv_t	hsv;

	/* convert endpoints to HSV, if necessary */
	if (cw || ccw)
	{
		lighthsv = RGB_to_HSV(firstrgb);
		darkhsv = RGB_to_HSV(lastrgb);

		/* tweak the "hue" values so we can wrap around the color wheel
		 * in the desired direction without bumping into endpoints.
		 */
		if (cw && lighthsv.hue <= darkhsv.hue)
		{
			lighthsv.hue += 360.0;
		}
		else if (ccw && lighthsv.hue >= darkhsv.hue)
		{
			darkhsv.hue += 360.0;
		}
	}

	/* generate the color palette */
	for (i = j = 0, delta = 1; i < maxpalette; i++)
	{
		/* set this palette entry to this color */
		if (cw || ccw)
		{
			hsv.hue = (darkhsv.hue * j + lighthsv.hue * (bounce - 1 - j)) / (bounce - 1);
			hsv.saturation = (darkhsv.saturation * j + lighthsv.saturation * (bounce - 1 - j)) / (bounce - 1);
			hsv.value = (darkhsv.value * j + lighthsv.value * (bounce - 1 - j)) / (bounce - 1);
			palette[i] = HSV_to_RGB(hsv);
		}
		else /* chord */
		{
			palette[i].red = (lastrgb.red * j + firstrgb.red * (bounce - 1 - j)) / (bounce - 1);
			palette[i].green = (lastrgb.green * j + firstrgb.green * (bounce - 1 - j)) / (bounce - 1);
			palette[i].blue = (lastrgb.blue * j + firstrgb.blue * (bounce - 1 - j)) / (bounce - 1);
		}
		
		/* choose next color */
		j += delta;
		if (j < 0 || j >= bounce)
		{
			delta = -delta;
			j += 2*delta;
		}
	}
	palette[i++] = otherrgb;
	palettesize = i;
}




/* This function reduces the palette in two ways: first by eliminating entries
 * which aren't used in the image, and then by combining any identical entries
 * in the palette.
 */
void reduce(unsigned char *imagedata, int width, int height)
{
	int	i, j, k;
	unsigned char	*scan;
	unsigned char	trans[256];
	
	/* see which colors aren't used */
	memset(trans, 0, sizeof trans);
	for (i = 0, scan = imagedata; i < height; i++)
		for (j = 0; j < width; j++)
			trans[*scan++] = 1;

	/* Look for any identical colors in the palette.  Throughout the body
	 * of this loop, "i" is the old palette index and "j" is the new palette
	 * index.
	 */
	for (i = j = 0; i < palettesize; i++)
	{
		/* if unused, then eliminate it from the palette */
		if (!trans[i])
		{
			trans[i] = 255;
			continue;
		}
		
		/* if duplicate, then use the first copy instead */
		for (k = 0; k < j; k++)
		{
			if (!memcmp(&palette[i], &palette[k], sizeof *palette))
			{
				trans[i] = k;
				break;
			}
		}
		if (k < j) continue;

		/* else keep it */
		trans[i] = j;
		palette[j] = palette[i];
		j++;
	}
	palettesize = j;

	/* convert image from old color numbers to new color numbers */
	for (i = 0, scan = imagedata; i < height; i++)
		for (j = 0; j < width; j++, scan++)
			*scan = trans[*scan];

	/* blank out the extra palette entries */
	memset(&palette[palettesize], 0, (256 - palettesize) * sizeof *palette);

	/* GIF can't be one color; must be at least two */
	if (palettesize == 1)
	{
		fprintf(stderr, "Solid color; hope that's okay\n");
		palettesize = 2;
	}
}
