/******************************************************************************

    palette.c

    Palette handling functions.

****************************************************************************

    Copyright Aaron Giles
    All rights reserved.

    Redistribution and use in source and binary forms, with or without
    modification, are permitted provided that the following conditions are
    met:

        * Redistributions of source code must retain the above copyright
          notice, this list of conditions and the following disclaimer.
        * Redistributions in binary form must reproduce the above copyright
          notice, this list of conditions and the following disclaimer in
          the documentation and/or other materials provided with the
          distribution.
        * Neither the name 'MAME' nor the names of its contributors may be
          used to endorse or promote products derived from this software
          without specific prior written permission.

    THIS SOFTWARE IS PROVIDED BY AARON GILES ''AS IS'' AND ANY EXPRESS OR
    IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
    WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
    DISCLAIMED. IN NO EVENT SHALL AARON GILES BE LIABLE FOR ANY DIRECT,
    INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
    (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
    SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
    HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
    STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING
    IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
    POSSIBILITY OF SUCH DAMAGE.

******************************************************************************/

#include "palette.h"
#include <stdlib.h>
#include <math.h>



/***************************************************************************
    TYPE DEFINITIONS
***************************************************************************/

/* object to track dirty states */
typedef struct _dirty_state dirty_state;
struct _dirty_state
{
	UINT32 *		dirty;						/* bitmap of dirty entries */
	UINT32			mindirty;					/* minimum dirty entry */
	UINT32			maxdirty;					/* minimum dirty entry */
};


/* a single palette client */
struct _palette_client
{
	palette_client *next;						/* pointer to next client */
	palette_t *		palette;					/* reference to the palette */
	dirty_state		live;						/* live dirty state */
	dirty_state		previous;					/* previous dirty state */
};


/* a palette object */
struct _palette_t
{
	UINT32			refcount;					/* reference count on the palette */
	UINT32			numcolors;					/* number of colors in the palette */
	UINT32			numgroups;					/* number of groups in the palette */

	float			brightness;					/* overall brightness value */
	float			contrast;					/* overall contrast value */
	float			gamma;						/* overall gamma value */
	UINT8			gamma_map[256];				/* gamma map */

	rgb_t *			entry_color;				/* array of raw colors */
	float *			entry_contrast;				/* contrast value for each entry */
	rgb_t *			adjusted_color;				/* array of adjusted colors */
	rgb_t *			adjusted_rgb15;				/* array of adjusted colors as RGB15 */

	float *			group_bright;				/* brightness value for each group */
	float *			group_contrast;				/* contrast value for each group */

	palette_client *client_list;				/* list of clients for this palette */
};



/***************************************************************************
    FUNCTION PROTOTYPES
***************************************************************************/

static void internal_palette_free(palette_t *palette);
static void update_adjusted_color(palette_t *palette, UINT32 group, UINT32 index);



/***************************************************************************
    INLINE FUNCTIONS
***************************************************************************/

/*-------------------------------------------------
    adjust_palette_entry - adjust a palette
    entry for brightness
-------------------------------------------------*/

INLINE rgb_t adjust_palette_entry(rgb_t entry, float brightness, float contrast, const UINT8 *gamma_map)
{
	int r = rgb_clamp((float)gamma_map[RGB_RED(entry)] * contrast + brightness);
	int g = rgb_clamp((float)gamma_map[RGB_GREEN(entry)] * contrast + brightness);
	int b = rgb_clamp((float)gamma_map[RGB_BLUE(entry)] * contrast + brightness);
	int a = RGB_ALPHA(entry);
	return MAKE_ARGB(a,r,g,b);
}



/***************************************************************************
    PALETTE ALLOCATION
***************************************************************************/

/*-------------------------------------------------
    palette_alloc - allocate a new palette object
    and take a single reference on it
-------------------------------------------------*/

palette_t *palette_alloc(UINT32 numcolors, UINT32 numgroups)
{
	palette_t *palette;
	UINT32 index;

	/* allocate memory */
	palette = (palette_t *)malloc(sizeof(*palette));
	if (palette == NULL)
		goto error;
	memset(palette, 0, sizeof(*palette));

	/* initialize overall controls */
	palette->brightness = 0.0f;
	palette->contrast = 1.0f;
	palette->gamma = 1.0f;
	for (index = 0; index < 256; index++)
		palette->gamma_map[index] = index;

	/* allocate an array of palette entries and individual contrasts for each */
	palette->entry_color = (rgb_t *)malloc(sizeof(*palette->entry_color) * numcolors);
	palette->entry_contrast = (float *)malloc(sizeof(*palette->entry_contrast) * numcolors);
	if (palette->entry_color == NULL || palette->entry_contrast == NULL)
		goto error;

	/* initialize the entries */
	for (index = 0; index < numcolors; index++)
	{
		palette->entry_color[index] = RGB_BLACK;
		palette->entry_contrast[index] = 1.0f;
	}

	/* allocate an array of brightness and contrast for each group */
	palette->group_bright = (float *)malloc(sizeof(*palette->group_bright) * numgroups);
	palette->group_contrast = (float *)malloc(sizeof(*palette->group_contrast) * numgroups);
	if (palette->group_bright == NULL || palette->group_contrast == NULL)
		goto error;

	/* initialize the entries */
	for (index = 0; index < numgroups; index++)
	{
		palette->group_bright[index] = 0.0f;
		palette->group_contrast[index] = 1.0f;
	}

	/* allocate arrays for the adjusted colors */
	palette->adjusted_color = (rgb_t *)malloc(sizeof(*palette->adjusted_color) * (numcolors * numgroups + 2));
	palette->adjusted_rgb15 = (rgb_t *)malloc(sizeof(*palette->adjusted_rgb15) * (numcolors * numgroups + 2));
	if (palette->adjusted_color == NULL || palette->adjusted_rgb15 == NULL)
		goto error;

	/* initialize the arrays */
	for (index = 0; index < numcolors * numgroups; index++)
	{
		palette->adjusted_color[index] = RGB_BLACK;
		palette->adjusted_rgb15[index] = rgb_to_rgb15(RGB_BLACK);
	}

	/* add black and white as the last two colors */
	palette->adjusted_color[index] = RGB_BLACK;
	palette->adjusted_rgb15[index++] = rgb_to_rgb15(RGB_BLACK);
	palette->adjusted_color[index] = RGB_WHITE;
	palette->adjusted_rgb15[index++] = rgb_to_rgb15(RGB_WHITE);

	/* initialize the remainder of the structure */
	palette->refcount = 1;
	palette->numcolors = numcolors;
	palette->numgroups = numgroups;
	return palette;

error:
	if (palette != NULL)
		internal_palette_free(palette);

	return NULL;
}


/*-------------------------------------------------
    palette_ref - reference a palette object,
    incrementing its reference count
-------------------------------------------------*/

void palette_ref(palette_t *palette)
{
	palette->refcount++;
}


/*-------------------------------------------------
    palette_deref - dereference a palette object;
    if the reference count goes to 0, it is freed
-------------------------------------------------*/

void palette_deref(palette_t *palette)
{
	/* if the reference count goes to 0, free */
	if (--palette->refcount == 0)
		internal_palette_free(palette);
}



/***************************************************************************
    PALETTE INFORMATION
***************************************************************************/

/*-------------------------------------------------
    palette_get_num_colors - return the number of
    colors allocated in the palette
-------------------------------------------------*/

int palette_get_num_colors(palette_t *palette)
{
	return palette->numcolors;
}


/*-------------------------------------------------
    palette_get_num_groups - return the number of
    groups managed by the palette
-------------------------------------------------*/

int palette_get_num_groups(palette_t *palette)
{
	return palette->numgroups;
}


/*-------------------------------------------------
    palette_get_max_index - return the maximum
    allowed index (i.e., length of arrays returned
    by palette_entry_list*)
-------------------------------------------------*/

int palette_get_max_index(palette_t *palette)
{
	return palette->numcolors * palette->numgroups + 2;
}


/*-------------------------------------------------
    palette_get_black_entry - return the index of
    the black entry
-------------------------------------------------*/

UINT32 palette_get_black_entry(palette_t *palette)
{
	return palette->numcolors * palette->numgroups + 0;
}


/*-------------------------------------------------
    palette_get_white_entry - return the index of
    the white entry
-------------------------------------------------*/

UINT32 palette_get_white_entry(palette_t *palette)
{
	return palette->numcolors * palette->numgroups + 1;
}



/***************************************************************************
    PALETTE CLIENTS
***************************************************************************/

/*-------------------------------------------------
    palette_client_alloc - add a new client to a
    palette
-------------------------------------------------*/

palette_client *palette_client_alloc(palette_t *palette)
{
	UINT32 total_colors = palette->numcolors * palette->numgroups;
	UINT32 dirty_dwords = (total_colors + 31) / 32;
	palette_client *client;

	/* allocate memory for the client */
	client = (palette_client *)malloc(sizeof(*client));
	if (client == NULL)
		goto error;
	memset(client, 0, sizeof(*client));

	/* allocate dirty lists */
	client->live.dirty = (UINT32 *)malloc(dirty_dwords * sizeof(UINT32));
	client->previous.dirty = (UINT32 *)malloc(dirty_dwords * sizeof(UINT32));
	if (client->live.dirty == NULL || client->previous.dirty == NULL)
		goto error;

	/* mark everything dirty to start with */
	memset(client->live.dirty, 0xff, dirty_dwords * sizeof(UINT32));
	memset(client->previous.dirty, 0xff, dirty_dwords * sizeof(UINT32));
	client->live.dirty[dirty_dwords - 1] &= (1 << (total_colors % 32)) - 1;
	client->previous.dirty[dirty_dwords - 1] &= (1 << (total_colors % 32)) - 1;

	/* initialize the rest of the structure and add a reference to a palette */
	client->palette = palette;
	palette_ref(palette);
	client->live.mindirty = 0;
	client->live.maxdirty = total_colors - 1;

	/* now add us to the list of clients */
	client->next = palette->client_list;
	palette->client_list = client;
	return client;

error:
	if (client != NULL)
	{
		if (client->live.dirty != NULL)
			free(client->live.dirty);
		if (client->previous.dirty != NULL)
			free(client->previous.dirty);
		free(client);
	}
	return NULL;
}


/*-------------------------------------------------
    palette_client_free - remove a client from a
    palette
-------------------------------------------------*/

void palette_client_free(palette_client *client)
{
	palette_client **curptr;

	/* first locate and remove ourself from the palette's list */
	for (curptr = &client->palette->client_list; *curptr != NULL; curptr = &(*curptr)->next)
		if (*curptr == client)
		{
			*curptr = client->next;
			break;
		}

	/* now deref the palette */
	palette_deref(client->palette);

	/* free our data */
	if (client->live.dirty != NULL)
		free(client->live.dirty);
	if (client->previous.dirty != NULL)
		free(client->previous.dirty);
	free(client);
}


/*-------------------------------------------------
    palette_client_get_palette - return a pointer
    to the palette for this client
-------------------------------------------------*/

palette_t *palette_client_get_palette(palette_client *client)
{
	return client->palette;
}


/*-------------------------------------------------
    palette_client_get_dirty_list - atomically get
    the current dirty list for a client
-------------------------------------------------*/

const UINT32 *palette_client_get_dirty_list(palette_client *client, UINT32 *mindirty, UINT32 *maxdirty)
{
	dirty_state temp;

	/* fill in the mindirty/maxdirty */
	if (mindirty != NULL)
		*mindirty = client->live.mindirty;
	if (maxdirty != NULL)
		*maxdirty = client->live.maxdirty;

	/* if nothing to report, report nothing and don't swap */
	if (client->live.mindirty > client->live.maxdirty)
		return NULL;

	/* swap the live and previous lists */
	temp = client->live;
	client->live = client->previous;
	client->previous = temp;

	/* erase relevant entries in the new live one */
	if (client->live.mindirty <= client->live.maxdirty)
		memset(client->live.dirty, client->live.mindirty / 8, (client->live.maxdirty / 8) + 1 - (client->live.mindirty / 8));
	client->live.mindirty = client->palette->numcolors * client->palette->numgroups;
	client->live.maxdirty = 0;

	/* return a pointer to the previous table */
	return client->previous.dirty;
}



/***************************************************************************
    PALETTE COLOR MANAGEMENT
***************************************************************************/

/*-------------------------------------------------
    palette_entry_set_color - set the raw RGB
    color for a given palette index
-------------------------------------------------*/

void palette_entry_set_color(palette_t *palette, UINT32 index, rgb_t rgb)
{
	int groupnum;

	/* if out of range, or unchanged, ignore */
	if (index >= palette->numcolors || palette->entry_color[index] == rgb)
		return;

	/* set the color */
	palette->entry_color[index] = rgb;

	/* update across all groups */
	for (groupnum = 0; groupnum < palette->numgroups; groupnum++)
		update_adjusted_color(palette, groupnum, index);
}


/*-------------------------------------------------
    palette_entry_get_color - return the raw RGB
    color for a given palette index
-------------------------------------------------*/

rgb_t palette_entry_get_color(palette_t *palette, UINT32 index)
{
	return (index < palette->numcolors) ? palette->entry_color[index] : RGB_BLACK;
}


/*-------------------------------------------------
    palette_entry_get_adjusted_color - return the
    adjusted RGB color (after all adjustments) for
    a given palette index
-------------------------------------------------*/

rgb_t palette_entry_get_adjusted_color(palette_t *palette, UINT32 index)
{
	return (index < palette->numcolors * palette->numgroups) ? palette->adjusted_color[index] : RGB_BLACK;
}


/*-------------------------------------------------
    palette_entry_list_raw - return the entire
    palette as an array of raw RGB values
-------------------------------------------------*/

const rgb_t *palette_entry_list_raw(palette_t *palette)
{
	return palette->entry_color;
}


/*-------------------------------------------------
    palette_entry_list_adjusted - return the
    entire palette as an array of adjusted RGB
    values
-------------------------------------------------*/

const rgb_t *palette_entry_list_adjusted(palette_t *palette)
{
	return palette->adjusted_color;
}


/*-------------------------------------------------
    palette_entry_list_adjusted_rgb15 - return the
    entire palette as an array of adjusted RGB-15
    values
-------------------------------------------------*/

const rgb_t *palette_entry_list_adjusted_rgb15(palette_t *palette)
{
	return palette->adjusted_rgb15;
}



/***************************************************************************
    PALETTE ADJUSTMENTS
***************************************************************************/

/*-------------------------------------------------
    palette_set_brightness - set the overall
    brightness for the palette
-------------------------------------------------*/

void palette_set_brightness(palette_t *palette, float brightness)
{
	int groupnum, index;

	/* convert incoming value to normalized result */
	brightness = (brightness - 1.0f) * 256.0f;

	/* set the global brightness if changed */
	if (palette->brightness == brightness)
		return;
	palette->brightness = brightness;

	/* update across all indices in all groups */
	for (groupnum = 0; groupnum < palette->numgroups; groupnum++)
		for (index = 0; index < palette->numcolors; index++)
			update_adjusted_color(palette, groupnum, index);
}


/*-------------------------------------------------
    palette_set_contrast - set the overall
    contrast for the palette
-------------------------------------------------*/

void palette_set_contrast(palette_t *palette, float contrast)
{
	int groupnum, index;

	/* set the global contrast if changed */
	if (palette->contrast == contrast)
		return;
	palette->contrast = contrast;

	/* update across all indices in all groups */
	for (groupnum = 0; groupnum < palette->numgroups; groupnum++)
		for (index = 0; index < palette->numcolors; index++)
			update_adjusted_color(palette, groupnum, index);
}


/*-------------------------------------------------
    palette_set_gamma - set the overall
    gamma for the palette
-------------------------------------------------*/

void palette_set_gamma(palette_t *palette, float gamma)
{
	int groupnum, index;

	/* set the global gamma if changed */
	if (palette->gamma == gamma)
		return;
	palette->gamma = gamma;

	/* recompute the gamma map */
	gamma = 1.0f / gamma;
	for (index = 0; index < 256; index++)
	{
		float fval = (float)index * (1.0f / 255.0f);
		float fresult = pow(fval, gamma);
		palette->gamma_map[index] = rgb_clamp(255.0f * fresult);
	}

	/* update across all indices in all groups */
	for (groupnum = 0; groupnum < palette->numgroups; groupnum++)
		for (index = 0; index < palette->numcolors; index++)
			update_adjusted_color(palette, groupnum, index);
}


/*-------------------------------------------------
    palette_entry_set_contrast - set the contrast
    adjustment for a single palette index
-------------------------------------------------*/

void palette_entry_set_contrast(palette_t *palette, UINT32 index, float contrast)
{
	int groupnum;

	/* if out of range, or unchanged, ignore */
	if (index >= palette->numcolors || palette->entry_contrast[index] == contrast)
		return;

	/* set the contrast */
	palette->entry_contrast[index] = contrast;

	/* update across all groups */
	for (groupnum = 0; groupnum < palette->numgroups; groupnum++)
		update_adjusted_color(palette, groupnum, index);
}


/*-------------------------------------------------
    palette_entry_get_contrast - return the
    contrast adjustment for a single palette index
-------------------------------------------------*/

float palette_entry_get_contrast(palette_t *palette, UINT32 index)
{
	return (index < palette->numcolors) ? palette->entry_contrast[index] : 1.0f;
}


/*-------------------------------------------------
    palette_group_set_brightness - configure
    overall brightness for a palette group
-------------------------------------------------*/

void palette_group_set_brightness(palette_t *palette, UINT32 group, float brightness)
{
	int index;

	/* convert incoming value to normalized result */
	brightness = (brightness - 1.0f) * 256.0f;

	/* if out of range, or unchanged, ignore */
	if (group >= palette->numgroups || palette->group_bright[group] == brightness)
		return;

	/* set the contrast */
	palette->group_bright[group] = brightness;

	/* update across all colors */
	for (index = 0; index < palette->numcolors; index++)
		update_adjusted_color(palette, group, index);
}


/*-------------------------------------------------
    palette_group_set_contrast - configure
    overall contrast for a palette group
-------------------------------------------------*/

void palette_group_set_contrast(palette_t *palette, UINT32 group, float contrast)
{
	int index;

	/* if out of range, or unchanged, ignore */
	if (group >= palette->numgroups || palette->group_contrast[group] == contrast)
		return;

	/* set the contrast */
	palette->group_contrast[group] = contrast;

	/* update across all colors */
	for (index = 0; index < palette->numcolors; index++)
		update_adjusted_color(palette, group, index);
}



/***************************************************************************
    PALETTE UTILITIES
***************************************************************************/

/*-------------------------------------------------
    palette_normalize_range - normalize a range
    of palette entries
-------------------------------------------------*/

void palette_normalize_range(palette_t *palette, UINT32 start, UINT32 end, int lum_min, int lum_max)
{
	INT32 ymin = 1000 * 255, ymax = 0;
	INT32 tmin, tmax;
	UINT32 index;

	/* clamp within range */
	start = MAX(start, 0);
	end = MIN(end, palette->numcolors - 1);

	/* find the minimum and maximum brightness of all the colors in the range */
	for (index = start; index <= end; index++)
	{
		rgb_t rgb = palette->entry_color[index];
		UINT32 y = 299 * RGB_RED(rgb) + 587 * RGB_GREEN(rgb) + 114 * RGB_BLUE(rgb);
		ymin = MIN(ymin, y);
		ymax = MAX(ymax, y);
	}

	/* determine target minimum/maximum */
	tmin = (lum_min < 0) ? ((ymin + 500) / 1000) : lum_min;
	tmax = (lum_max < 0) ? ((ymax + 500) / 1000) : lum_max;

	/* now normalize the palette */
	for (index = start; index <= end; index++)
	{
		rgb_t rgb = palette->entry_color[index];
		INT32 y = 299 * RGB_RED(rgb) + 587 * RGB_GREEN(rgb) + 114 * RGB_BLUE(rgb);
		INT32 u = ((INT32)RGB_BLUE(rgb)-y /1000)*492 / 1000;
		INT32 v = ((INT32)RGB_RED(rgb)-y / 1000)*877 / 1000;
		INT32 target = tmin + ((y - ymin) * (tmax - tmin + 1)) / (ymax - ymin);
		UINT8 r = rgb_clamp(target + 1140 * v / 1000);
		UINT8 g = rgb_clamp(target -  395 * u / 1000 - 581 * v / 1000);
		UINT8 b = rgb_clamp(target + 2032 * u / 1000);
		palette_entry_set_color(palette, index, MAKE_RGB(r, g, b));
	}
}



/***************************************************************************
    INTERNAL ROUTINES
***************************************************************************/

/*-------------------------------------------------
    internal_palette_free - free all allocations
    from a palette
-------------------------------------------------*/

static void internal_palette_free(palette_t *palette)
{
	/* free per-color data */
	if (palette->entry_color != NULL)
		free(palette->entry_color);
	if (palette->entry_contrast != NULL)
		free(palette->entry_contrast);

	/* free per-group data */
	if (palette->group_bright != NULL)
		free(palette->group_bright);
	if (palette->group_contrast != NULL)
		free(palette->group_contrast);

	/* free adjusted data */
	if (palette->adjusted_color != NULL)
		free(palette->adjusted_color);
	if (palette->adjusted_rgb15 != NULL)
		free(palette->adjusted_rgb15);

	/* and the palette itself */
	free(palette);
}


/*-------------------------------------------------
    update_adjusted_color - update a color index
    by group and index pair
-------------------------------------------------*/

static void update_adjusted_color(palette_t *palette, UINT32 group, UINT32 index)
{
	UINT32 finalindex = group * palette->numcolors + index;
	palette_client *client;
	rgb_t adjusted;

	/* compute the adjusted value */
	adjusted = adjust_palette_entry(palette->entry_color[index],
				palette->group_bright[group] + palette->brightness,
				palette->group_contrast[group] * palette->entry_contrast[index] * palette->contrast,
				palette->gamma_map);

	/* if not different, ignore */
	if (palette->adjusted_color[finalindex] == adjusted)
		return;

	/* otherwise, modify the adjusted color array */
	palette->adjusted_color[finalindex] = adjusted;
	palette->adjusted_rgb15[finalindex] = rgb_to_rgb15(adjusted);

	/* mark dirty in all clients */
	for (client = palette->client_list; client != NULL; client = client->next)
	{
		client->live.dirty[finalindex / 32] |= 1 << (finalindex % 32);
		client->live.mindirty = MIN(client->live.mindirty, finalindex);
		client->live.maxdirty = MAX(client->live.maxdirty, finalindex);
	}
}
