static int best_color_index(f_pixel px, const colormap *map, float min_opaque_val, float *dist_out) { const colormap_item *const acolormap = map->palette; const int numcolors = map->colors; int ind=0; const int iebug = px.a > min_opaque_val; float dist = colordifference(px,acolormap[0].acolor); for(int i = 1; i < numcolors; i++) { float newdist = colordifference(px,acolormap[i].acolor); if (newdist < dist) { /* penalty for making holes in IE */ if (iebug && acolormap[i].acolor.a < 1) { if (newdist+1.f/1024.f > dist) continue; } ind = i; dist = newdist; } } if (dist_out) *dist_out = dist; return ind; }
/** Weighted per-channel variance of the box. It's used to decide which channel to split by */ static f_pixel box_variance(const hist_item achv[], const struct box *box) { f_pixel mean = box->color; double variancea=0, variancer=0, varianceg=0, varianceb=0; for(unsigned int i = 0; i < box->colors; ++i) { f_pixel px = achv[box->ind + i].acolor; double weight = achv[box->ind + i].adjusted_weight; variancea += variance_diff(mean.a - px.a, 2.0/256.0)*weight; variancer += variance_diff(mean.r - px.r, 1.0/256.0)*weight; varianceg += variance_diff(mean.g - px.g, 1.0/256.0)*weight; varianceb += variance_diff(mean.b - px.b, 1.0/256.0)*weight; } return (f_pixel){ .a = variancea*(4.0/16.0), .r = variancer*(7.0/16.0), .g = varianceg*(9.0/16.0), .b = varianceb*(5.0/16.0), }; } static double box_max_error(const hist_item achv[], const struct box *box) { f_pixel mean = box->color; double max_error = 0; for(unsigned int i = 0; i < box->colors; ++i) { const double diff = colordifference(mean, achv[box->ind + i].acolor); if (diff > max_error) { max_error = diff; } } return max_error; }
inline static double color_weight(f_pixel median, hist_item h) { float diff = colordifference(median, h.acolor); // if color is "good enough", don't split further if (diff < 2.f/256.f/256.f) diff /= 2.f; return sqrt(diff) * (sqrt(1.0+h.adjusted_weight)-1.0); }
static inline double color_weight(f_pixel median, const hist_item& h) { double diff = colordifference(median, h.acolor); // if color is "good enough", don't split further if (diff < 1.0/256.0/256.0) diff /= 2.0; return sqrt(diff) * (sqrt(1.0+h.adjusted_weight)-1.0); }
/* increase histogram popularity by difference from the final color (this is used as part of feedback loop) */ static void adjust_histogram(hist_item *achv, const colormap *map, const struct box* bv, unsigned int boxes) { for(unsigned int bi = 0; bi < boxes; ++bi) { for(unsigned int i=bv[bi].ind; i < bv[bi].ind+bv[bi].colors; i++) { achv[i].adjusted_weight *= sqrt(1.0 +colordifference(map->palette[bi].acolor, achv[i].acolor)/4.0); achv[i].likely_colormap_index = bi; } } }
double box_error(const struct box *box, const hist_item achv[]) { f_pixel avg = box->color; double total_error=0; for (unsigned int i = 0; i < box->colors; ++i) { total_error += colordifference(avg, achv[box->ind + i].acolor) * achv[box->ind + i].perceptual_weight; } return total_error; }
static double box_max_error(const hist_item achv[], const struct box *box) { f_pixel mean = box->color; double max_error = 0; unsigned int i; for(i = 0; i < box->colors; ++i) { const double diff = colordifference(mean, achv[box->ind + i].acolor); if (diff > max_error) { max_error = diff; } } return max_error; }
/* increase histogram popularity by difference from the final color (this is used as part of feedback loop) */ static void adjust_histogram( hist_item* achv, const colormap* map, const box* bv, uint boxes ) { for (uint bi=0; bi<boxes; ++bi) { const box& bx = bv[bi]; f_pixel pc = map->palette[bi].acolor; for (uint i=bx.ind; i<bx.ind+bx.colors; i++) { hist_item& hist = achv[i]; hist.adjusted_weight *= sqrt(1.0 + colordifference(pc, hist.acolor) / 2.0); } } }
struct nearest_map *nearest_init(const colormap *map) { mempool m = NULL; struct nearest_map *centroids = mempool_new(&m, sizeof(*centroids)); centroids->mempool = m; unsigned int skipped=0; unsigned int skip_index[map->colors]; for(unsigned int j=0; j < map->colors; j++) skip_index[j]=0; colormap *subset_palette = get_subset_palette(map); const int selected_heads = subset_palette->colors; centroids->heads = mempool_new(¢roids->mempool, sizeof(centroids->heads[0])*(selected_heads+1)); // +1 is fallback head unsigned int h=0; for(; h < selected_heads; h++) { unsigned int num_candiadtes = 1+(map->colors - skipped)/((1+selected_heads-h)/2); centroids->heads[h] = build_head(subset_palette->palette[h].acolor, map, num_candiadtes, ¢roids->mempool, skip_index, &skipped); if (centroids->heads[h].num_candidates == 0) { break; } } centroids->heads[h].radius = MAX_DIFF; centroids->heads[h].center = (f_pixel){0,0,0,0}; centroids->heads[h].num_candidates = 0; centroids->heads[h].candidates = mempool_new(¢roids->mempool, (map->colors - skipped) * sizeof(centroids->heads[h].candidates[0])); for(unsigned int i=0; i < map->colors; i++) { if (skip_index[i]) continue; centroids->heads[h].candidates[centroids->heads[h].num_candidates++] = (struct color_entry) { .color = map->palette[i].acolor, .index = i, .radius = 999, }; } centroids->num_heads = ++h; // get_subset_palette could have created a copy if (subset_palette != map->subset_palette) { pam_freecolormap(subset_palette); } return centroids; } unsigned int nearest_search(const struct nearest_map *centroids, const f_pixel px, const float min_opaque_val, float *diff) { const int iebug = px.a > min_opaque_val; const struct head *const heads = centroids->heads; for(unsigned int i=0; i < centroids->num_heads; i++) { float headdist = colordifference(px, heads[i].center); if (headdist <= heads[i].radius) { assert(heads[i].num_candidates); unsigned int ind=heads[i].candidates[0].index; float dist = colordifference(px, heads[i].candidates[0].color); /* penalty for making holes in IE */ if (iebug && heads[i].candidates[0].color.a < 1) { dist += 1.f/1024.f; } for(unsigned int j=1; j < heads[i].num_candidates; j++) { float newdist = colordifference(px, heads[i].candidates[j].color); /* penalty for making holes in IE */ if (iebug && heads[i].candidates[j].color.a < 1) { newdist += 1.f/1024.f; } if (newdist < dist) { dist = newdist; ind = heads[i].candidates[j].index; } } if (diff) *diff = dist; return ind; } } assert(0); return 0; } void nearest_free(struct nearest_map *centroids) { mempool_free(centroids->mempool); }
static struct head build_head(f_pixel px, const colormap *map, int num_candidates, mempool *m, unsigned int skip_index[], unsigned int *skipped) { struct sorttmp colors[map->colors]; unsigned int colorsused=0; for(unsigned int i=0; i < map->colors; i++) { if (skip_index[i]) continue; colors[colorsused].index = i; colors[colorsused].radius = colordifference(px, map->palette[i].acolor); colorsused++; } qsort(&colors, colorsused, sizeof(colors[0]), compareradius); assert(colorsused < 2 || colors[0].radius <= colors[1].radius); num_candidates = MIN(colorsused, num_candidates); struct head h; h.candidates = mempool_new(m, num_candidates * sizeof(h.candidates[0])); h.center = px; h.num_candidates = num_candidates; for(unsigned int i=0; i < num_candidates; i++) { h.candidates[i] = (struct color_entry) { .color = map->palette[colors[i].index].acolor, .index = colors[i].index, .radius = colors[i].radius, }; } h.radius = colors[num_candidates-1].radius/4.0f; // /2 squared for(unsigned int i=0; i < num_candidates; i++) { assert(colors[i].radius <= h.radius*4.0f); // divide again as that's matching certain subset within radius-limited subset // - 1/256 is a tolerance for miscalculation (seems like colordifference isn't exact) if (colors[i].radius < h.radius/4.f - 1.f/256.f) { skip_index[colors[i].index]=1; (*skipped)++; } } return h; } static colormap *get_subset_palette(const colormap *map) { // it may happen that it gets palette without subset palette or the subset is too large int subset_size = (map->colors+3)/4; if (map->subset_palette && map->subset_palette->colors <= subset_size) { return map->subset_palette; } const colormap *source = map->subset_palette ? map->subset_palette : map; colormap *subset_palette = pam_colormap(subset_size); for(unsigned int i=0; i < subset_size; i++) { subset_palette->palette[i] = source->palette[i]; } return subset_palette; }