/* pam.c - pam (portable alpha map) utility library ** ** Copyright (C) 1989, 1991 by Jef Poskanzer. ** Copyright (C) 1997, 2000, 2002 by Greg Roelofs; based on an idea by ** Stefan Schneider. ** © 2009-2013 by Kornel Lesinski. ** ** Permission to use, copy, modify, and distribute this software and its ** documentation for any purpose and without fee is hereby granted, provided ** that the above copyright notice appear in all copies and that both that ** copyright notice and this permission notice appear in supporting ** documentation. This software is provided "as is" without express or ** implied warranty. */ #include #include #include "libimagequant.h" #include "pam.h" #include "mempool.h" /* *INDENT-OFF* */ LIQ_PRIVATE bool pam_computeacolorhash (struct acolorhash_table *acht, const rgba_pixel * const pixels[], unsigned int cols, unsigned int rows, const unsigned char *importance_map) /* *INDENT-ON* */ { const unsigned int maxacolors = acht->maxcolors, ignorebits = acht->ignorebits; const unsigned int channel_mask = 255U >> ignorebits << ignorebits; const unsigned int channel_hmask = (255U >> ignorebits) ^ 0xFFU; const unsigned int posterize_mask = channel_mask << 24 | channel_mask << 16 | channel_mask << 8 | channel_mask; const unsigned int posterize_high_mask = channel_hmask << 24 | channel_hmask << 16 | channel_hmask << 8 | channel_hmask; struct acolorhist_arr_head *const buckets = acht->buckets; unsigned int colors = acht->colors; const unsigned int hash_size = acht->hash_size; const unsigned int stacksize = sizeof (acht->freestack) / sizeof (acht->freestack[0]); struct acolorhist_arr_item **freestack = acht->freestack; unsigned int freestackp = acht->freestackp; /* Go through the entire image, building a hash table of colors. */ for (unsigned int row = 0; row < rows; ++row) { float boost = 1.0; for (unsigned int col = 0; col < cols; ++col) { union rgba_as_int px = { pixels[row][col] }; unsigned int hash; struct acolorhist_arr_head *achl; if (importance_map) { boost = 0.5f + (double) *importance_map++ / 255.f; } // RGBA color is casted to long for easier hasing/comparisons if (!px.rgba.a) { // "dirty alpha" has different RGBA values that end up being the same fully transparent color px.l = 0; hash = 0; } else { // mask posterizes all 4 channels in one go px.l = (px.l & posterize_mask) | ((px.l & posterize_high_mask) >> (8 - ignorebits)); // fancier hashing algorithms didn't improve much hash = px.l % hash_size; } /* head of the hash function stores first 2 colors inline (achl->used = 1..2), to reduce number of allocations of achl->other_items. */ achl = &buckets[hash]; if (achl->inline1.color.l == px.l && achl->used) { achl->inline1.perceptual_weight += boost; continue; } if (achl->used) { if (achl->used > 1) { struct acolorhist_arr_item *other_items; unsigned int i = 0; struct acolorhist_arr_item *new_items; unsigned int capacity; if (achl->inline2.color.l == px.l) { achl->inline2.perceptual_weight += boost; continue; } // other items are stored as an array (which gets reallocated if needed) other_items = achl->other_items; for (i = 0; i < achl->used - 2; i++) { if (other_items[i].color.l == px.l) { other_items[i].perceptual_weight += boost; goto continue_outer_loop; } } // the array was allocated with spare items if (i < achl->capacity) { other_items[i] = (struct acolorhist_arr_item) { .color = px,.perceptual_weight = boost, }; achl->used++; ++colors; continue; } if (++colors > maxacolors) { acht->colors = colors; acht->freestackp = freestackp; return false; } if (!other_items) { // there was no array previously, alloc "small" array capacity = 8; if (freestackp <= 0) { // estimate how many colors are going to be + headroom const int mempool_size = ((acht->rows + rows - row) * 2 * colors / (acht->rows + row + 1) + 1024) * sizeof (struct acolorhist_arr_item); new_items = mempool_alloc (&acht->mempool, sizeof (struct acolorhist_arr_item) * capacity, mempool_size); } else { // freestack stores previously freed (reallocated) arrays that can be reused // (all pesimistically assumed to be capacity = 8) new_items = freestack[--freestackp]; } } else { // simply reallocs and copies array to larger capacity capacity = achl->capacity * 2 + 16; if (freestackp < stacksize - 1) { freestack[freestackp++] = other_items; } { const int mempool_size = ((acht->rows + rows - row) * 2 * colors / (acht->rows + row + 1) + 32 * capacity) * sizeof (struct acolorhist_arr_item); new_items = mempool_alloc (&acht->mempool, sizeof (struct acolorhist_arr_item) * capacity, mempool_size); } if (!new_items) return false; memcpy (new_items, other_items, sizeof (other_items[0]) * achl->capacity); } achl->other_items = new_items; achl->capacity = capacity; new_items[i] = (struct acolorhist_arr_item) { .color = px,.perceptual_weight = boost, }; achl->used++; } else { // these are elses for first checks whether first and second inline-stored colors are used achl->inline2.color.l = px.l; achl->inline2.perceptual_weight = boost; achl->used = 2; ++colors; } } else { achl->inline1.color.l = px.l; achl->inline1.perceptual_weight = boost; achl->used = 1; ++colors; } continue_outer_loop:; } } acht->colors = colors; acht->cols = cols; acht->rows += rows; acht->freestackp = freestackp; return true; } LIQ_PRIVATE struct acolorhash_table * pam_allocacolorhash (unsigned int maxcolors, unsigned int surface, unsigned int ignorebits, void *(*malloc) (size_t), void (*free) (void *)) { const unsigned int estimated_colors = MIN (maxcolors, surface / (ignorebits + (surface > 512 * 512 ? 5 : 4))); const unsigned int hash_size = estimated_colors < 66000 ? 6673 : (estimated_colors < 200000 ? 12011 : 24019); mempool m = NULL; const unsigned int buckets_size = hash_size * sizeof (struct acolorhist_arr_head); const unsigned int mempool_size = sizeof (struct acolorhash_table) + buckets_size + estimated_colors * sizeof (struct acolorhist_arr_item); struct acolorhash_table *t = mempool_create (&m, sizeof (*t) + buckets_size, mempool_size, malloc, free); if (!t) return NULL; *t = (struct acolorhash_table) { .mempool = m,.hash_size = hash_size,.maxcolors = maxcolors,.ignorebits = ignorebits, }; memset (t->buckets, 0, hash_size * sizeof (struct acolorhist_arr_head)); return t; } #define PAM_ADD_TO_HIST(entry) { \ hist->achv[j].acolor = to_f(gamma_lut, entry.color.rgba); \ total_weight += hist->achv[j].adjusted_weight = hist->achv[j].perceptual_weight = MIN(entry.perceptual_weight, max_perceptual_weight); \ ++j; \ } LIQ_PRIVATE histogram * pam_acolorhashtoacolorhist (const struct acolorhash_table *acht, const double gamma, void *(*malloc) (size_t), void (*free) (void *)) { histogram *hist = malloc (sizeof (hist[0])); float gamma_lut[256]; float max_perceptual_weight; double total_weight; unsigned int i, j, k; if (!hist || !acht) return NULL; *hist = (histogram) { .achv = malloc (acht->colors * sizeof (hist->achv[0])),.size = acht->colors,.free = free,.ignorebits = acht->ignorebits,}; if (!hist->achv) return NULL; to_f_set_gamma (gamma_lut, gamma); /* Limit perceptual weight to 1/10th of the image surface area to prevent a single color from dominating all others. */ max_perceptual_weight = 0.1f * acht->cols * acht->rows; total_weight = 0; for (j = 0, i = 0; i < acht->hash_size; ++i) { const struct acolorhist_arr_head *const achl = &acht->buckets[i]; if (achl->used) { PAM_ADD_TO_HIST (achl->inline1); if (achl->used > 1) { PAM_ADD_TO_HIST (achl->inline2); for (k = 0; k < achl->used - 2; k++) { PAM_ADD_TO_HIST (achl->other_items[k]); } } } } hist->total_perceptual_weight = total_weight; return hist; } LIQ_PRIVATE void pam_freeacolorhash (struct acolorhash_table *acht) { mempool_destroy (acht->mempool); } LIQ_PRIVATE void pam_freeacolorhist (histogram * hist) { hist->free (hist->achv); hist->free (hist); } LIQ_PRIVATE colormap * pam_colormap (unsigned int colors, void *(*malloc) (size_t), void (*free) (void *)) { const size_t colors_size = colors * sizeof (colormap_item); colormap *map; assert (colors > 0 && colors < 65536); map = malloc (sizeof (colormap) + colors_size); if (!map) return NULL; *map = (colormap) { .malloc = malloc,.free = free,.subset_palette = NULL,.colors = colors,}; memset (map->palette, 0, colors_size); return map; } LIQ_PRIVATE colormap * pam_duplicate_colormap (colormap * map) { colormap *dupe = pam_colormap (map->colors, map->malloc, map->free); for (unsigned int i = 0; i < map->colors; i++) { dupe->palette[i] = map->palette[i]; } if (map->subset_palette) { dupe->subset_palette = pam_duplicate_colormap (map->subset_palette); } return dupe; } LIQ_PRIVATE void pam_freecolormap (colormap * c) { if (c->subset_palette) pam_freecolormap (c->subset_palette); c->free (c); } LIQ_PRIVATE void to_f_set_gamma (float gamma_lut[], const double gamma) { for (int i = 0; i < 256; i++) { gamma_lut[i] = pow ((double) i / 255.0, internal_gamma / gamma); } }