/****************************************************************************** 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 #include /*************************************************************************** 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); } }