/* * Voronoi iteration: new palette color is computed from weighted average of colors that map to that palette entry. */ static void viter_init(const colormap *map, f_pixel *average_color, float *average_color_count, f_pixel *base_color, float *base_color_count) { colormap_item *newmap = map->palette; int newcolors = map->colors; for (int i=0; i < newcolors; i++) { average_color_count[i] = 0; average_color[i] = (f_pixel){0,0,0,0}; } // Rather than only using separate mapping and averaging steps // new palette colors are computed at the same time as mapping is done // but to avoid first few matches moving the entry too much // some base color and weight is added if (base_color) { for (int i=0; i < newcolors; i++) { float value = 1.0+newmap[i].popularity/2.0; base_color_count[i] = value; base_color[i] = (f_pixel){ .a = newmap[i].acolor.a * value, .r = newmap[i].acolor.r * value, .g = newmap[i].acolor.g * value, .b = newmap[i].acolor.b * value, }; } } } static void viter_update_color(f_pixel acolor, float value, colormap *map, int match, f_pixel *average_color, float *average_color_count, const f_pixel *base_color, const float *base_color_count) { average_color[match].a += acolor.a * value; average_color[match].r += acolor.r * value; average_color[match].g += acolor.g * value; average_color[match].b += acolor.b * value; average_color_count[match] += value; if (base_color) { map->palette[match].acolor = (f_pixel){ .a = (average_color[match].a + base_color[match].a) / (average_color_count[match] + base_color_count[match]), .r = (average_color[match].r + base_color[match].r) / (average_color_count[match] + base_color_count[match]), .g = (average_color[match].g + base_color[match].g) / (average_color_count[match] + base_color_count[match]), .b = (average_color[match].b + base_color[match].b) / (average_color_count[match] + base_color_count[match]), }; } } static void viter_finalize(colormap *map, f_pixel *average_color, float *average_color_count) { for (int i=0; i < map->colors; i++) { if (average_color_count[i]) { map->palette[i].acolor = (f_pixel){ .a = (average_color[i].a) / average_color_count[i], .r = (average_color[i].r) / average_color_count[i], .g = (average_color[i].g) / average_color_count[i], .b = (average_color[i].b) / average_color_count[i], }; } map->palette[i].popularity = average_color_count[i]; } } void viter_do_interation(const hist *hist, colormap *map, float min_opaque_val) { f_pixel average_color[map->colors]; float average_color_count[map->colors]; hist_item *achv = hist->achv; viter_init(map, average_color,average_color_count, NULL,NULL); for(int j=0; j < hist->size; j++) { int match = best_color_index(achv[j].acolor, map, min_opaque_val, NULL); viter_update_color(achv[j].acolor, achv[j].perceptual_weight, map, match, average_color,average_color_count, NULL,NULL); } viter_finalize(map, average_color,average_color_count); } pngquant_error pngquant(read_info *input_image, write_info *output_image, int floyd, int reqcolors, int ie_bug, int speed_tradeoff) { float min_opaque_val; verbose_printf(" reading file corrected for gamma %2.1f\n", 1.0/input_image->gamma); min_opaque_val = modify_alpha(input_image,ie_bug); assert(min_opaque_val>0); hist *hist = histogram(input_image, reqcolors, speed_tradeoff); hist_item *achv = hist->achv; colormap *acolormap = NULL; float least_error = -1; int feedback_loop_trials = 56-9*speed_tradeoff; const double percent = (double)(feedback_loop_trials>0?feedback_loop_trials:1)/100.0; do { verbose_printf(" selecting colors"); colormap *newmap = mediancut(hist, min_opaque_val, reqcolors); verbose_printf("..."); float total_error=0; f_pixel average_color[newmap->colors], base_color[newmap->colors]; float average_color_count[newmap->colors], base_color_count[newmap->colors]; if (feedback_loop_trials) { viter_init(newmap, average_color,average_color_count,base_color,base_color_count); for(int i=0; i < hist->size; i++) { float diff; int match = best_color_index(achv[i].acolor, newmap, min_opaque_val, &diff); assert(diff >= 0); assert(achv[i].perceptual_weight > 0); total_error += diff * achv[i].perceptual_weight; viter_update_color(achv[i].acolor, achv[i].perceptual_weight, newmap, match, average_color,average_color_count,base_color,base_color_count); achv[i].adjusted_weight = (achv[i].perceptual_weight+achv[i].adjusted_weight) * (1.0+sqrtf(diff)); } } if (total_error < least_error || !acolormap) { if (acolormap) pam_freecolormap(acolormap); acolormap = newmap; viter_finalize(acolormap, average_color,average_color_count); least_error = total_error; feedback_loop_trials -= 1; // asymptotic improvement could make it go on forever } else { feedback_loop_trials -= 6; if (total_error > least_error*4) feedback_loop_trials -= 3; pam_freecolormap(newmap); } verbose_printf("%d%%\n",100-MAX(0,(int)(feedback_loop_trials/percent))); } while(feedback_loop_trials > 0); verbose_printf(" moving colormap towards local minimum\n"); int iterations = MAX(5-speed_tradeoff,0); iterations *= iterations; for(int i=0; i < iterations; i++) { viter_do_interation(hist, acolormap, min_opaque_val); } pam_freeacolorhist(hist); output_image->width = input_image->width; output_image->height = input_image->height; output_image->gamma = 0.45455; /* ** Step 3.7 [GRR]: allocate memory for the entire indexed image */ output_image->indexed_data = malloc(output_image->height * output_image->width); output_image->row_pointers = malloc(output_image->height * sizeof(output_image->row_pointers[0])); if (!output_image->indexed_data || !output_image->row_pointers) { fprintf(stderr, " insufficient memory for indexed data and/or row pointers\n"); return OUT_OF_MEMORY_ERROR; } for (int row = 0; row < output_image->height; ++row) { output_image->row_pointers[row] = output_image->indexed_data + row*output_image->width; } // tRNS, etc. sort_palette(output_image, acolormap); /* ** Step 4: map the colors in the image to their closest match in the ** new colormap, and write 'em out. */ verbose_printf(" mapping image to new colors..."); float remapping_error; if (floyd) { // if dithering, save rounding error and stick to that palette // otherwise palette can be improved after remapping set_palette(output_image, acolormap); remapping_error = remap_to_palette_floyd(input_image, output_image, acolormap, min_opaque_val, ie_bug); } else { remapping_error = remap_to_palette(input_image, output_image, acolormap, min_opaque_val, ie_bug); set_palette(output_image, acolormap); } verbose_printf("MSE=%.3f\n", remapping_error*256.0f); pam_freecolormap(acolormap); return SUCCESS; }
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); }