LIQ_PRIVATE double viter_do_iteration(histogram *hist, colormap *const map, const float min_opaque_val, viter_callback callback, const bool fast_palette) { const unsigned int max_threads = omp_get_max_threads(); // viter_state average_color[(VITER_CACHE_LINE_GAP+map->colors) * max_threads]; viter_state *average_color = (viter_state *)malloc((VITER_CACHE_LINE_GAP+map->colors) * max_threads); viter_init(map, max_threads, average_color); struct nearest_map *const n = nearest_init(map, fast_palette); hist_item *const achv = hist->achv; const int hist_size = hist->size; double total_diff=0; #pragma omp parallel for if (hist_size > 3000) \ schedule(static) default(none) shared(average_color,callback) reduction(+:total_diff) for(int j=0; j < hist_size; j++) { float diff; unsigned int match = nearest_search(n, achv[j].acolor, achv[j].likely_colormap_index, min_opaque_val, &diff); achv[j].likely_colormap_index = match; total_diff += diff * achv[j].perceptual_weight; viter_update_color(achv[j].acolor, achv[j].perceptual_weight, map, match, omp_get_thread_num(), average_color); if (callback) callback(&achv[j], diff); } nearest_free(n); viter_finalize(map, max_threads, average_color); free(average_color); return total_diff / hist->total_perceptual_weight; }
void viter_finalize(colormap *map, const int max_threads, const viter_state average_color[]) { for (int i=0; i < map->colors; i++) { double a=0, r=0, g=0, b=0, total=0; // Aggregate results from all threads for(int t=0; t < max_threads; t++) { const int offset = map->colors * t + i; a += average_color[offset].a; r += average_color[offset].r; g += average_color[offset].g; b += average_color[offset].b; total += average_color[offset].total; } if (total) { map->palette[i].acolor = (f_pixel){ .a = a / total, .r = r / total, .g = g / total, .b = b / total, }; } map->palette[i].popularity = total; } } double viter_do_iteration(histogram *hist, colormap *const map, const float min_opaque_val, viter_callback callback) { const int max_threads = omp_get_max_threads(); viter_state average_color[map->colors * max_threads]; viter_init(map, max_threads, average_color); struct nearest_map *const n = nearest_init(map); hist_item *const achv = hist->achv; const int hist_size = hist->size; double total_diff=0; #pragma omp parallel for if (hist_size > 3000) \ default(none) shared(average_color,callback) reduction(+:total_diff) for(int j=0; j < hist_size; j++) { float diff; int match = nearest_search(n, achv[j].acolor, min_opaque_val, &diff); total_diff += diff * achv[j].perceptual_weight; viter_update_color(achv[j].acolor, achv[j].perceptual_weight, map, match, omp_get_thread_num(), average_color); if (callback) callback(&achv[j], diff); } nearest_free(n); viter_finalize(map, max_threads, average_color); return total_diff / hist->total_perceptual_weight; }
float remap_to_palette(read_info *input_image, write_info *output_image, colormap *map, float min_opaque_val, int ie_bug) { rgb_pixel **input_pixels = (rgb_pixel **)input_image->row_pointers; unsigned char **row_pointers = output_image->row_pointers; int rows = input_image->height, cols = input_image->width; double gamma = input_image->gamma; int remapped_pixels=0; float remapping_error=0; int transparent_ind = best_color_index((f_pixel){0,0,0,0}, map, min_opaque_val, NULL); f_pixel average_color[map->colors]; float average_color_count[map->colors]; viter_init(map, average_color, average_color_count, NULL, NULL); for (int row = 0; row < rows; ++row) { for(int col = 0; col < cols; ++col) { f_pixel px = to_f(gamma, input_pixels[row][col]); int match; if (px.a < 1.0/256.0) { match = transparent_ind; } else { float diff; match = best_color_index(px, map,min_opaque_val, &diff); remapped_pixels++; remapping_error += diff; } row_pointers[row][col] = match; viter_update_color(px, 1.0, map, match, average_color, average_color_count, NULL, NULL); } } viter_finalize(map, average_color, average_color_count); return remapping_error / MAX(1,remapped_pixels); }
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]; } } double viter_do_iteration(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); struct nearest_map *n = nearest_init(map); double total_diff=0; for(int j=0; j < hist->size; j++) { float diff; int match = nearest_search(n, achv[j].acolor, min_opaque_val, &diff); total_diff += diff * achv[j].perceptual_weight; viter_update_color(achv[j].acolor, achv[j].perceptual_weight, map, match, average_color,average_color_count); } nearest_free(n); viter_finalize(map, average_color,average_color_count); return total_diff / hist->total_perceptual_weight; }
/* * 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; }