/* +-------------------------------------------------------------------+ */
/* | Copyright (C) 1993, David Koblas (koblas@netcom.com)              | */
/* |                                                                   | */
/* | Permission to use, copy, modify, and to distribute this software  | */
/* | and its documentation for any purpose is hereby granted without   | */
/* | fee, provided that the above copyright notice appear in all       | */
/* | copies and that both that copyright notice and this permission    | */
/* | notice appear in supporting documentation.  There is no           | */
/* | representations about the suitability of this software for        | */
/* | any purpose.  this software is provided "as is" without express   | */
/* | or implied warranty.                                              | */
/* |                                                                   | */
/* +-------------------------------------------------------------------+ */

/* reduce.c:
 *
 * reduce an image's colormap usage to a set number of colors.  this also
 * translates a true color image to a TLA-style image of `n' colors.
 *
 * this uses an algorithm by Paul Heckbert discussed in `Color Image
 * Quantization for Frame Buffer Display,' _Computer Graphics_ 16(3),
 * pp 297-307.  this implementation is based on one discussed in
 * 'A Few Good Colors,' _Computer Language_, Aug. 1990, pp 32-41 by
 * Dave Pomerantz.
 *
 * this function cannot reduce to any number of colors larger than 32768.
 *
 * jim frost 04.18.91
 *
 */

/*
 * Copyright 1989, 1990, 1991 Jim Frost
 *
 * Permission to use, copy, modify, distribute, and sell this software
 * and its documentation for any purpose is hereby granted without fee,
 * provided that the above copyright notice appear in all copies and
 * that both that copyright notice and this permission notice appear
 * in supporting documentation.  The author makes no representations
 * about the suitability of this software for any purpose.  It is
 * provided "as is" without express or implied warranty.
 *
 * THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE,
 * INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS, IN
 * NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, INDIRECT OR
 * CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS
 * OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
 * OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE
 * USE OR PERFORMANCE OF THIS SOFTWARE.
 */

#include <X11/Intrinsic.h>
#include <stdio.h>

#ifndef NOSTDHDRS
#include <stdlib.h>
#include <unistd.h>
#endif

/*
**  swiped from X11/Xfuncproto.h
**   since qsort() may or may not be defined with a constant sub-function
*/
#ifndef _Xconst
#if __STDC__ || defined(__cplusplus) || defined(c_plusplus) || (FUNCPROTO&4)
#define _Xconst const
#else
#define _Xconst
#endif
#endif /* _Xconst */

#include "image.h"
#include "hash.h"

/*
** this converts a RGB pixel into a 15-bit true color value
*/

#define TO_15BITS(r,g,b)	((r & 0xf8) << 7) | ((g & 0xf8) << 2) | ((b & 0xf8) >> 3)

/* 
** these macros extract color intensities, in the compressed range.
*/

#define RED_INTENSITY(P)   (((P) & 0x7c00) >> 10)
#define GREEN_INTENSITY(P) (((P) & 0x03e0) >> 5)
#define BLUE_INTENSITY(P)   ((P) & 0x001f)

/*
**  These macros exctrat the 0..255 range value from the "true color" version
*/
#define RED_VALUE(P)   (((P) & 0x7c00) >> 7)
#define GREEN_VALUE(P) (((P) & 0x03e0) >> 2)
#define BLUE_VALUE(P)  (((P) & 0x001f) << 3)

/* this structure defines a color area which is made up of an array of pixel
 * values and a count of the total number of image pixels represented by
 * the area.  color areas are kept in a list sorted by the number of image
 * pixels they represent.
 */

typedef struct color_area {
    unsigned short    *pixels;    /* array of pixel values in this area */
    unsigned short     npixels;   /* size of above array */
				  /* predicate func to sort with before * splitting */
    int              (*func)(unsigned short *, unsigned short *);   
    unsigned long      count;     /* # of image pixels we represent */
    struct color_area *prev, *next;
} Area_t;

typedef struct {  
	unsigned short	idx; 
	unsigned char	r,g,b; 
} color_t;

/* 
** predicate functions for qsort
**   Generated code as you can see.
*/

#ifndef __STDC__
#define SORT(ca, cb, cc)								\
	static int sort/**/ca/**/cb/**/cc(unsigned short *p1, unsigned short *p2)	\
	{ 										\
		unsigned int	R1,R2,G1,G2,B1,B2;					\
		R1 = RED_INTENSITY(*p1);						\
		G1 = GREEN_INTENSITY(*p1);						\
		B1 = BLUE_INTENSITY(*p1);						\
		R2 = RED_INTENSITY(*p2);						\
		G2 = GREEN_INTENSITY(*p2);						\
		B2 = BLUE_INTENSITY(*p2);						\
		if (ca/**/1 == ca/**/2) 						\
			if (cb/**/1 == cb/**/2) 					\
				return (cc/**/1 < cc/**/2) ? -1 : 1;			\
			else								\
				return (cb/**/1 < cb/**/2) ? -1 : 1;			\
		else									\
			return (ca/**/1 < ca/**/2) ? -1 : 1;				\
	}
#else
#define SORT(ca, cb, cc)								\
	static int sort ## ca ## cb ## cc(unsigned short *p1, unsigned short *p2)	\
	{ 										\
		unsigned int	R1,R2,G1,G2,B1,B2;					\
		R1 = RED_INTENSITY(*p1);						\
		G1 = GREEN_INTENSITY(*p1);						\
		B1 = BLUE_INTENSITY(*p1);						\
		R2 = RED_INTENSITY(*p2);						\
		G2 = GREEN_INTENSITY(*p2);						\
		B2 = BLUE_INTENSITY(*p2);						\
		if (ca ## 1 == ca ## 2) 						\
			if (cb ## 1 == cb ## 2) 					\
				return (cc ## 1 < cc ## 2) ? -1 : 1;			\
			else								\
				return (cb ## 1 < cb ## 2) ? -1 : 1;			\
		else									\
			return (ca ## 1 < ca ## 2) ? -1 : 1;				\
	}
#endif

SORT(R,G,B)
SORT(R,B,G)
SORT(G,R,B)
SORT(G,B,R)
SORT(B,G,R)
SORT(B,R,G)

#undef SORT

/* this does calculations on a color area following a split and inserts
 * the color area in the list of color areas.
 */

static void insertColorArea(unsigned long *counts, Area_t **rlargest, Area_t **rsmallest, Area_t *area)
{
	int		i;
	unsigned int	red, green, blue;
	unsigned int	min_red, min_green, min_blue;
	unsigned int	max_red, max_green, max_blue= 0;
	Area_t		*largest, *smallest;

	/*
	** update pixel count for this area and find RGB intensity widths
	*/
	min_red   = max_red   = RED_INTENSITY(area->pixels[0]);
	min_green = max_green = GREEN_INTENSITY(area->pixels[0]);
	min_blue  = max_blue  = BLUE_INTENSITY(area->pixels[0]);

	area->count= 0;
	for (i = 1; i < area->npixels; i++) {
		area->count += counts[area->pixels[i]];
		red   = RED_INTENSITY(area->pixels[i]);
		green = GREEN_INTENSITY(area->pixels[i]);
		blue  = BLUE_INTENSITY(area->pixels[i]);

		if (red < min_red)
			min_red= red;
		if (red > max_red)
			max_red= red;
		if (green < min_green)
			min_green= green;
		if (green > max_green)
			max_green= green;
		if (blue < min_blue)
			min_blue= blue;
		if (blue > max_blue)
			max_blue= blue;
	}

	/*
	** calculate widths and determine which predicate function to use based
	** on the result
	*/

	red   = max_red   - min_red;
	green = max_green - min_green;
	blue  = max_blue  - min_blue;

	if (red > green)
		if (green > blue)
			area->func= sortRGB;
		else if (red > blue)
			area->func= sortRBG;
		else
			area->func= sortBRG;
	else if (green > blue)
		if (red > blue)
			area->func= sortGRB;
		else
			area->func= sortGBR;
	else
		area->func= sortBGR;

	/* insert color area in color area list sorted by number of pixels that
	** the area represents
	*/

	largest = *rlargest;
	smallest= *rsmallest;

	if (largest == NULL) {
		largest = smallest = area;
		area->prev = area->next = (Area_t *)NULL;
	} else if (area->npixels < 2) {
		/*
		**  if we only have one element, our pixel count is immaterial so we get
		**  stuck on the end of the list.
		*/
		smallest->next= area;
		area->prev= smallest;
		area->next= (Area_t *)NULL;
		smallest= area;
	} else {
		/*
		**  insert node into list
		*/
		Area_t	*cur;

		for (cur = largest; cur != NULL; cur= cur->next) {
			if ((area->count > cur->count) || (cur->npixels < 2)) {
				area->prev= cur->prev;
				area->next= cur;
				cur->prev = area;
				if (area->prev != NULL)
					area->prev->next= area;
				else
					largest= area;
				break;
			}
		}
		if (cur == NULL) {
			area->prev= smallest;
			area->next= (Area_t *)NULL;
			smallest->next= area;
			smallest= area;
		}
	}
	*rlargest = largest;
	*rsmallest= smallest;
}

/*
**  hash table support functions.
*/
static int cmpColor(void *a, void *b)
{
	color_t	*ca = (color_t *)a;
	color_t	*cb = (color_t *)b;

	if (ca->r != cb->r)
		return ca->r < cb->r ? -1 : 1;
	if (ca->g != cb->g)
		return ca->g < cb->g ? -1 : 1;
	if (ca->b != cb->b)
		return ca->b < cb->b ? -1 : 1;
	return 0;
}

static void freeColor(void *junk)
{
	/*
	**  Don't apply a free function to the hashtable items.
	*/
}

Image	*ImageCompress(Image *input, int ncolors)
{ 
	unsigned long	counts[32768];
	unsigned short	array[XtNumber(counts)];
	unsigned char	*ocp;
	unsigned short	*osp;
	int		x, y, i, count, nuniq;
	int		allocated;
	Area_t		*areas, *largest, *smallest;
	Image		*output;
	void		*htable;
	color_t		*ctable;
	
	/*
	**  make sure we have array space...
	*/
	if (ncolors > XtNumber(counts)) 
		ncolors = XtNumber(counts);

	/*
	**  Why are we trying to compress a b&w image?
	**    or an image that already fits
	*/
	if (input->isBW)
		return input;
	if (input->cmapSize != 0 && input->cmapSize < ncolors) 
		return input;

	/*
	**  Creata a histogram of 15-bit color values.
	**    also, save the "real" color values.
	**    or at least enough of them so that we know
	**    that compression is really necessary.
	*/
	htable = HashCreate(cmpColor, freeColor, 256);
	ctable = (color_t *)XtCalloc(sizeof(color_t), ncolors + 1);
	nuniq  = 0;

	for (i = 0; i < XtNumber(counts); i++)
		counts[i] = 0;
	
	for (y = 0; y < input->height; y++) {
		for (x = 0; x < input->width; x++) {
			unsigned char	*dp;

			dp = ImagePixel(input, x, y);

			if (nuniq <= ncolors && htable != NULL) {
				color_t		*cptr = &ctable[nuniq];
				cptr->r = dp[0];
				cptr->g = dp[1];
				cptr->b = dp[2];
				if (HashFind(htable, dp[0], cptr) == NULL) {
					cptr->idx = nuniq;
					HashAdd(htable, dp[0], cptr);
					nuniq++;
				}
			}

			counts[TO_15BITS(dp[0],dp[1],dp[2])]++;
		}

		if (y % 256 == 0)
			StateTimeStep();
	}

	if (nuniq <= ncolors) {
		/*
		**  Wow, this has enough colors to fit on the requested colormap
		**    This should be able to renumber (compress) an existing
		**    colormap, rather than creating a new image.
		*/
		unsigned short	*osp;
		unsigned char	*ocp;

		output = ImageNewCmap(input->width, input->height, nuniq);
		for (y = 0; y < nuniq; y++) 
			ImageSetCmap(output, y, 
					ctable[y].r, ctable[y].g, ctable[y].b);
		
		osp = (unsigned short *)output->data;
		ocp = output->data;

		for (y = 0; y < input->height; y++) {
			for (x = 0; x < input->width; x++, osp++, ocp++) {
				unsigned char	*dp;
				color_t		*cptr, col;

				dp = ImagePixel(input, x, y);

				col.r = dp[0];
				col.g = dp[1];
				col.b = dp[2];
				cptr = HashFind(htable, dp[0], &col);

				if (nuniq > 256)
					*osp = cptr->idx;
				else
					*ocp = cptr->idx;
			}

			if (y % 256 == 0)
				StateTimeStep();
		}

		output->cmapPacked = True;

		HashDestroy(htable);
		XtFree((XtPointer)ctable);
		output->maskData = input->maskData;
		input->maskData  = NULL;
		ImageDelete(input);
		return output;
	}
	HashDestroy(htable);
	XtFree((XtPointer)ctable);

	/*
	**  Create an array of 15-bit pixel values that actually occur 
	**   in the image.
	*/
	count = 0;
	for (i = 0; i < XtNumber(counts); i++) {
		if (counts[i] != 0)
			array[count++] = i;
	}

	/*
	**  Create the color areas an initalize the first element
	*/
	areas = (Area_t *)XtCalloc(sizeof(Area_t), ncolors);
	areas[0].pixels  = array;
	areas[0].npixels = count;

	largest = smallest = NULL;
	insertColorArea(counts, &largest, &smallest, areas);

	allocated = 1;

	/*
	**  While there are area's still to be broken down 
	**    or the largest cannot be subdivided.
	*/
	while (allocated < ncolors && largest->npixels >= 2) {
		Area_t	*newArea, *oldArea;
		int	i, midpoint;

		qsort(largest->pixels, largest->npixels, sizeof(short), 	
				(int (*)(_Xconst void *,_Xconst void *))largest->func);

		/*
		**  Find the midpoint of the largest area an split
		*/
		midpoint = largest->count / 2;
		for (i = 0, count = 0; i < largest->npixels && count < midpoint; i++) 
			count += counts[largest->pixels[i]];

		if (i == largest->npixels)
			i--;
		if (i == 0)
			i = 1;
		newArea = areas + allocated;
		newArea->pixels = largest->pixels + i;
		newArea->npixels = largest->npixels - i;
		largest->npixels = i;
		oldArea = largest;
		largest = largest->next;
		if (largest != NULL) 
			largest->prev = NULL;
		else
			smallest = NULL;

		/* 
		**  recalculate for each area of split and insert in the area list
		*/
		insertColorArea(counts, &largest, &smallest, newArea);
		insertColorArea(counts, &largest, &smallest, oldArea);

		allocated++;

		if (allocated % 64 == 0)
			StateTimeStep();
	}

	output = ImageNewCmap(input->width, input->height, allocated);
	output->maskData = input->maskData;
	input->maskData  = NULL;

	for (i = 0; i < allocated; i++) {
		long	red, green, blue;
		int	j;

		/* 
		** calculate RGB table from each color area.  this should really calculate
		** a new color by weighting the intensities by the number of pixels, but
		** it's a pain to scale so this just averages all the intensities.  it
		** works pretty well regardless.
		*/
		red = green = blue = 0;
		for (j = 0; j < areas[i].npixels; j++) {
			unsigned short	pixel = areas[i].pixels[j];

			red   += RED_VALUE(pixel)   & 0xff;
			green += GREEN_VALUE(pixel) & 0xff;
			blue  += BLUE_VALUE(pixel)  & 0xff;

			counts[pixel] = i;
		}
		red   /= areas[i].npixels;
		green /= areas[i].npixels;
		blue  /= areas[i].npixels;

		ImageSetCmap(output, i, red, green, blue);
	}

	/*
	**  Done with the areas information.
	*/
	XtFree((XtPointer)areas);

	/*
	**  Copy input to output
	*/

	ocp = output->data;
	osp = (unsigned short *)output->data;

	for (y = 0; y < input->height; y++) {
		for (x = 0; x < input->width; x++) {
			unsigned char	*dp;

			dp = ImagePixel(input, x, y);

			if (output->cmapSize > 256) {
				*osp++ = counts[TO_15BITS(dp[0],dp[1],dp[2])];
			} else {
				*ocp++ = counts[TO_15BITS(dp[0],dp[1],dp[2])];
			}
		}
	}

	output->cmapPacked = True;
	ImageDelete(input);
	return output;
}
