diff --git a/viter.c b/kmeans.c similarity index 68% rename from viter.c rename to kmeans.c index f526326..f596009 100644 --- a/viter.c +++ b/kmeans.c @@ -19,7 +19,7 @@ along with libimagequant. If not, see . #include "libimagequant.h" #include "pam.h" -#include "viter.h" +#include "kmeans.h" #include "nearest.h" #include #include @@ -32,16 +32,16 @@ along with libimagequant. If not, see . #endif /* - * Voronoi iteration: new palette color is computed from weighted average of colors that map to that palette entry. + * K-Means iteration: new palette color is computed from weighted average of colors that map to that palette entry. */ -LIQ_PRIVATE void viter_init(const colormap *map, const unsigned int max_threads, viter_state average_color[]) +LIQ_PRIVATE void kmeans_init(const colormap *map, const unsigned int max_threads, kmeans_state average_color[]) { - memset(average_color, 0, sizeof(average_color[0])*(VITER_CACHE_LINE_GAP+map->colors)*max_threads); + memset(average_color, 0, sizeof(average_color[0])*(KMEANS_CACHE_LINE_GAP+map->colors)*max_threads); } -LIQ_PRIVATE void viter_update_color(const f_pixel acolor, const float value, const colormap *map, unsigned int match, const unsigned int thread, viter_state average_color[]) +LIQ_PRIVATE void kmeans_update_color(const f_pixel acolor, const float value, const colormap *map, unsigned int match, const unsigned int thread, kmeans_state average_color[]) { - match += thread * (VITER_CACHE_LINE_GAP+map->colors); + match += thread * (KMEANS_CACHE_LINE_GAP+map->colors); average_color[match].a += acolor.a * value; average_color[match].r += acolor.r * value; average_color[match].g += acolor.g * value; @@ -49,14 +49,14 @@ LIQ_PRIVATE void viter_update_color(const f_pixel acolor, const float value, con average_color[match].total += value; } -LIQ_PRIVATE void viter_finalize(colormap *map, const unsigned int max_threads, const viter_state average_color[]) +LIQ_PRIVATE void kmeans_finalize(colormap *map, const unsigned int max_threads, const kmeans_state average_color[]) { for (unsigned int i=0; i < map->colors; i++) { double a=0, r=0, g=0, b=0, total=0; // Aggregate results from all threads for(unsigned int t=0; t < max_threads; t++) { - const unsigned int offset = (VITER_CACHE_LINE_GAP+map->colors) * t + i; + const unsigned int offset = (KMEANS_CACHE_LINE_GAP+map->colors) * t + i; a += average_color[offset].a; r += average_color[offset].r; @@ -77,12 +77,12 @@ LIQ_PRIVATE void viter_finalize(colormap *map, const unsigned int max_threads, c } } -LIQ_PRIVATE double viter_do_iteration(histogram *hist, colormap *const map, viter_callback callback, const bool fast_palette) +LIQ_PRIVATE double kmeans_do_iteration(histogram *hist, colormap *const map, kmeans_callback callback) { const unsigned int max_threads = omp_get_max_threads(); - viter_state average_color[(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); + kmeans_state average_color[(KMEANS_CACHE_LINE_GAP+map->colors) * max_threads]; + kmeans_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; @@ -95,13 +95,13 @@ LIQ_PRIVATE double viter_do_iteration(histogram *hist, colormap *const map, vite achv[j].tmp.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); + kmeans_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); + kmeans_finalize(map, max_threads, average_color); return total_diff / hist->total_perceptual_weight; } diff --git a/kmeans.h b/kmeans.h new file mode 100644 index 0000000..c51d7bb --- /dev/null +++ b/kmeans.h @@ -0,0 +1,19 @@ + +#ifndef KMEANS_H +#define KMEANS_H + +// Spread memory touched by different threads at least 64B apart which I assume is the cache line size. This should avoid memory write contention. +#define KMEANS_CACHE_LINE_GAP ((64+sizeof(kmeans_state)-1)/sizeof(kmeans_state)) + +typedef struct { + double a, r, g, b, total; +} kmeans_state; + +typedef void (*kmeans_callback)(hist_item *item, float diff); + +LIQ_PRIVATE void kmeans_init(const colormap *map, const unsigned int max_threads, kmeans_state state[]); +LIQ_PRIVATE void kmeans_update_color(const f_pixel acolor, const float value, const colormap *map, unsigned int match, const unsigned int thread, kmeans_state average_color[]); +LIQ_PRIVATE void kmeans_finalize(colormap *map, const unsigned int max_threads, const kmeans_state state[]); +LIQ_PRIVATE double kmeans_do_iteration(histogram *hist, colormap *const map, kmeans_callback callback); + +#endif diff --git a/libimagequant.c b/libimagequant.c index c17b93d..08f6962 100644 --- a/libimagequant.c +++ b/libimagequant.c @@ -54,7 +54,7 @@ #include "mediancut.h" #include "nearest.h" #include "blur.h" -#include "viter.h" +#include "kmeans.h" #define LIQ_HIGH_MEMORY_LIMIT (1<<26) /* avoid allocating buffers larger than 64MB */ @@ -73,12 +73,12 @@ struct liq_attr { void* (*malloc)(size_t); void (*free)(void*); - double target_mse, max_mse, voronoi_iteration_limit; + double target_mse, max_mse, kmeans_iteration_limit; float min_opaque_val; unsigned int max_colors, max_histogram_entries; unsigned int min_posterization_output /* user setting */, min_posterization_input /* speed setting */; - unsigned int voronoi_iterations, feedback_loop_trials; - bool last_index_transparent, use_contrast_maps, use_dither_map, fast_palette; + unsigned int kmeans_iterations, feedback_loop_trials; + bool last_index_transparent, use_contrast_maps, use_dither_map; unsigned char speed; unsigned char progress_stage1, progress_stage2, progress_stage3; @@ -141,7 +141,7 @@ struct liq_result { float dither_level; double gamma, palette_error; int min_posterization_output; - bool use_dither_map, fast_palette; + bool use_dither_map; }; struct liq_histogram { @@ -349,13 +349,12 @@ LIQ_EXPORT LIQ_NONNULL liq_error liq_set_speed(liq_attr* attr, int speed) if (speed < 1 || speed > 10) return LIQ_VALUE_OUT_OF_RANGE; unsigned int iterations = MAX(8-speed, 0); iterations += iterations * iterations/2; - attr->voronoi_iterations = iterations; - attr->voronoi_iteration_limit = 1.0/(double)(1<<(23-speed)); + attr->kmeans_iterations = iterations; + attr->kmeans_iteration_limit = 1.0/(double)(1<<(23-speed)); attr->feedback_loop_trials = MAX(56-9*speed, 0); attr->max_histogram_entries = (1<<17) + (1<<18)*(10-speed); attr->min_posterization_input = (speed >= 8) ? 1 : 0; - attr->fast_palette = (speed >= 7); attr->use_dither_map = (speed <= (omp_get_max_threads() > 1 ? 7 : 5)); // parallelized dither map might speed up floyd remapping attr->use_contrast_maps = (speed <= 7) || attr->use_dither_map; attr->speed = speed; @@ -461,7 +460,7 @@ LIQ_EXPORT LIQ_NONNULL void liq_attr_destroy(liq_attr *attr) attr->free(attr); } -LIQ_EXPORT LIQ_NONNULL liq_attr* liq_attr_copy(liq_attr *orig) +LIQ_EXPORT LIQ_NONNULL liq_attr* liq_attr_copy(const liq_attr *orig) { if (!CHECK_STRUCT_TYPE(orig, liq_attr)) { return NULL; @@ -528,11 +527,11 @@ LIQ_EXPORT liq_attr* liq_attr_create_with_allocator(void* (*custom_malloc)(size_ LIQ_EXPORT LIQ_NONNULL liq_error liq_image_add_fixed_color(liq_image *img, liq_color color) { if (!CHECK_STRUCT_TYPE(img, liq_image)) return LIQ_INVALID_POINTER; - if (img->fixed_colors_count > 255) return LIQ_BUFFER_TOO_SMALL; + if (img->fixed_colors_count > 255) return LIQ_UNSUPPORTED; float gamma_lut[256]; to_f_set_gamma(gamma_lut, img->gamma); - img->fixed_colors[img->fixed_colors_count++] = to_f(gamma_lut, (rgba_pixel){ + img->fixed_colors[img->fixed_colors_count++] = rgba_to_f(gamma_lut, (rgba_pixel){ .r = color.r, .g = color.g, .b = color.b, @@ -543,7 +542,7 @@ LIQ_EXPORT LIQ_NONNULL liq_error liq_image_add_fixed_color(liq_image *img, liq_c LIQ_NONNULL static liq_error liq_histogram_add_fixed_color_internal(liq_histogram *hist, f_pixel color) { - if (hist->fixed_colors_count > 255) return LIQ_BUFFER_TOO_SMALL; + if (hist->fixed_colors_count > 255) return LIQ_UNSUPPORTED; hist->fixed_colors[hist->fixed_colors_count++] = color; return LIQ_OK; @@ -750,7 +749,7 @@ LIQ_NONNULL static void convert_row_to_f(liq_image *img, f_pixel *row_f_pixels, const rgba_pixel *const row_pixels = liq_image_get_row_rgba(img, row); for(unsigned int col=0; col < img->width; col++) { - row_f_pixels[col] = to_f(gamma_lut, row_pixels[col]); + row_f_pixels[col] = rgba_to_f(gamma_lut, row_pixels[col]); } } @@ -853,7 +852,7 @@ LIQ_EXPORT LIQ_NONNULL void liq_image_destroy(liq_image *input_image) input_image->free(input_image); } -LIQ_EXPORT liq_histogram* liq_histogram_create(liq_attr* attr) +LIQ_EXPORT liq_histogram* liq_histogram_create(const liq_attr* attr) { if (!CHECK_STRUCT_TYPE(attr, liq_attr)) { return NULL; @@ -893,7 +892,7 @@ LIQ_EXPORT LIQ_NONNULL liq_error liq_image_quantize(liq_image *const img, liq_at { if (!CHECK_STRUCT_TYPE(attr, liq_attr)) return LIQ_INVALID_POINTER; if (!liq_image_has_rgba_pixels(img)) { - return LIQ_INVALID_POINTER; + return LIQ_UNSUPPORTED; } liq_histogram *hist = liq_histogram_create(attr); @@ -1011,7 +1010,7 @@ LIQ_EXPORT LIQ_NONNULL void liq_result_destroy(liq_result *res) } -LIQ_EXPORT LIQ_NONNULL double liq_get_quantization_error(liq_result *result) { +LIQ_EXPORT LIQ_NONNULL double liq_get_quantization_error(const liq_result *result) { if (!CHECK_STRUCT_TYPE(result, liq_result)) return -1; if (result->palette_error >= 0) { @@ -1021,7 +1020,7 @@ LIQ_EXPORT LIQ_NONNULL double liq_get_quantization_error(liq_result *result) { return -1; } -LIQ_EXPORT LIQ_NONNULL double liq_get_remapping_error(liq_result *result) { +LIQ_EXPORT LIQ_NONNULL double liq_get_remapping_error(const liq_result *result) { if (!CHECK_STRUCT_TYPE(result, liq_result)) return -1; if (result->remapping && result->remapping->palette_error >= 0) { @@ -1031,7 +1030,7 @@ LIQ_EXPORT LIQ_NONNULL double liq_get_remapping_error(liq_result *result) { return -1; } -LIQ_EXPORT LIQ_NONNULL int liq_get_quantization_quality(liq_result *result) { +LIQ_EXPORT LIQ_NONNULL int liq_get_quantization_quality(const liq_result *result) { if (!CHECK_STRUCT_TYPE(result, liq_result)) return -1; if (result->palette_error >= 0) { @@ -1041,7 +1040,7 @@ LIQ_EXPORT LIQ_NONNULL int liq_get_quantization_quality(liq_result *result) { return -1; } -LIQ_EXPORT LIQ_NONNULL int liq_get_remapping_quality(liq_result *result) { +LIQ_EXPORT LIQ_NONNULL int liq_get_remapping_quality(const liq_result *result) { if (!CHECK_STRUCT_TYPE(result, liq_result)) return -1; if (result->remapping && result->remapping->palette_error >= 0) { @@ -1138,14 +1137,14 @@ LIQ_NONNULL static void set_rounded_palette(liq_palette *const dest, colormap *c dest->count = map->colors; for(unsigned int x = 0; x < map->colors; ++x) { - rgba_pixel px = to_rgb(gamma, map->palette[x].acolor); + rgba_pixel px = f_to_rgb(gamma, map->palette[x].acolor); px.r = posterize_channel(px.r, posterize); px.g = posterize_channel(px.g, posterize); px.b = posterize_channel(px.b, posterize); px.a = posterize_channel(px.a, posterize); - map->palette[x].acolor = to_f(gamma_lut, px); /* saves rounding error introduced by to_rgb, which makes remapping & dithering more accurate */ + map->palette[x].acolor = rgba_to_f(gamma_lut, px); /* saves rounding error introduced by to_rgb, which makes remapping & dithering more accurate */ if (!px.a && !map->palette[x].fixed) { px.r = 71; px.g = 112; px.b = 76; @@ -1169,7 +1168,7 @@ LIQ_EXPORT LIQ_NONNULL const liq_palette *liq_get_palette(liq_result *result) return &result->int_palette; } -LIQ_NONNULL static float remap_to_palette(liq_image *const input_image, unsigned char *const *const output_pixels, colormap *const map, const bool fast) +LIQ_NONNULL static float remap_to_palette(liq_image *const input_image, unsigned char *const *const output_pixels, colormap *const map) { const int rows = input_image->height; const unsigned int cols = input_image->width; @@ -1179,11 +1178,11 @@ LIQ_NONNULL static float remap_to_palette(liq_image *const input_image, unsigned return -1; } - struct nearest_map *const n = nearest_init(map, fast); + struct nearest_map *const n = nearest_init(map); const unsigned int max_threads = omp_get_max_threads(); - viter_state average_color[(VITER_CACHE_LINE_GAP+map->colors) * max_threads]; - viter_init(map, max_threads, average_color); + kmeans_state average_color[(KMEANS_CACHE_LINE_GAP+map->colors) * max_threads]; + kmeans_init(map, max_threads, average_color); #pragma omp parallel for if (rows*cols > 3000) \ schedule(static) default(none) shared(average_color) reduction(+:remapping_error) @@ -1195,11 +1194,11 @@ LIQ_NONNULL static float remap_to_palette(liq_image *const input_image, unsigned output_pixels[row][col] = last_match = nearest_search(n, &row_pixels[col], last_match, &diff); remapping_error += diff; - viter_update_color(row_pixels[col], 1.0, map, last_match, omp_get_thread_num(), average_color); + kmeans_update_color(row_pixels[col], 1.0, map, last_match, omp_get_thread_num(), average_color); } } - viter_finalize(map, max_threads, average_color); + kmeans_finalize(map, max_threads, average_color); nearest_free(n); @@ -1219,12 +1218,12 @@ inline static f_pixel get_dithered_pixel(const float dither_level, const float m const float max_underflow = -0.1f; // allowing some overflow prevents undithered bands caused by clamping of all channels - if (px.r + sr > max_overflow) ratio = MIN(ratio, (max_overflow -px.r)/sr); - else if (px.r + sr < max_underflow) ratio = MIN(ratio, (max_underflow-px.r)/sr); - if (px.g + sg > max_overflow) ratio = MIN(ratio, (max_overflow -px.g)/sg); - else if (px.g + sg < max_underflow) ratio = MIN(ratio, (max_underflow-px.g)/sg); - if (px.b + sb > max_overflow) ratio = MIN(ratio, (max_overflow -px.b)/sb); - else if (px.b + sb < max_underflow) ratio = MIN(ratio, (max_underflow-px.b)/sb); + if (px.r + sr > max_overflow) ratio = MIN(ratio, (max_overflow -px.r)/sr); + else { if (px.r + sr < max_underflow) ratio = MIN(ratio, (max_underflow-px.r)/sr); } + if (px.g + sg > max_overflow) ratio = MIN(ratio, (max_overflow -px.g)/sg); + else { if (px.g + sg < max_underflow) ratio = MIN(ratio, (max_underflow-px.g)/sg); } + if (px.b + sb > max_overflow) ratio = MIN(ratio, (max_overflow -px.b)/sb); + else { if (px.b + sb < max_underflow) ratio = MIN(ratio, (max_underflow-px.b)/sb); } float a = px.a + sa; if (a > 1.0) { a = 1.0; } @@ -1255,7 +1254,7 @@ inline static f_pixel get_dithered_pixel(const float dither_level, const float m */ LIQ_NONNULL static bool remap_to_palette_floyd(liq_image *input_image, unsigned char *const output_pixels[], liq_remapping_result *quant, const float max_dither_error, const bool output_image_is_remapped) { - const unsigned int rows = input_image->height, cols = input_image->width; + const int rows = input_image->height, cols = input_image->width; const unsigned char *dither_map = quant->use_dither_map ? (input_image->dither_map ? input_image->dither_map : input_image->edges) : NULL; const colormap *map = quant->palette; @@ -1270,7 +1269,7 @@ LIQ_NONNULL static bool remap_to_palette_floyd(liq_image *input_image, unsigned nexterr = thiserr + (cols + 2); bool ok = true; - struct nearest_map *const n = nearest_init(map, false); + struct nearest_map *const n = nearest_init(map); // response to this value is non-linear and without it any value < 0.8 would give almost no dithering float base_dithering_level = quant->dither_level; @@ -1281,9 +1280,9 @@ LIQ_NONNULL static bool remap_to_palette_floyd(liq_image *input_image, unsigned } base_dithering_level *= 15.0/16.0; // prevent small errors from accumulating - bool fs_direction = true; + int fs_direction = 1; unsigned int last_match=0; - for (unsigned int row = 0; row < rows; ++row) { + for (int row = 0; row < rows; ++row) { if (liq_remap_progress(quant, quant->progress_stage1 + row * (100.f - quant->progress_stage1) / rows)) { ok = false; break; @@ -1291,7 +1290,7 @@ LIQ_NONNULL static bool remap_to_palette_floyd(liq_image *input_image, unsigned memset(nexterr, 0, (cols + 2) * sizeof(*nexterr)); - unsigned int col = (fs_direction) ? 0 : (cols - 1); + int col = (fs_direction > 0) ? 0 : (cols - 1); const f_pixel *const row_pixels = liq_image_get_row_f(input_image, row); do { @@ -1323,7 +1322,7 @@ LIQ_NONNULL static bool remap_to_palette_floyd(liq_image *input_image, unsigned } /* Propagate Floyd-Steinberg error terms. */ - if (fs_direction) { + if (fs_direction > 0) { thiserr[col + 2].a += err.a * (7.f/16.f); thiserr[col + 2].r += err.r * (7.f/16.f); thiserr[col + 2].g += err.g * (7.f/16.f); @@ -1367,19 +1366,18 @@ LIQ_NONNULL static bool remap_to_palette_floyd(liq_image *input_image, unsigned } // remapping is done in zig-zag - if (fs_direction) { - ++col; + col += fs_direction; + if (fs_direction > 0) { if (col >= cols) break; } else { if (col <= 0) break; - --col; } } while(1); f_pixel *const temperr = thiserr; thiserr = nexterr; nexterr = temperr; - fs_direction = !fs_direction; + fs_direction = -fs_direction; } input_image->free(MIN(thiserr, nexterr)); // MIN because pointers were swapped @@ -1404,8 +1402,63 @@ LIQ_NONNULL static void remove_fixed_colors_from_histogram(histogram *hist, cons } } -LIQ_EXPORT LIQ_NONNULL liq_error liq_histogram_add_image(liq_histogram *input_hist, liq_attr *options, liq_image *input_image) +LIQ_EXPORT LIQ_NONNULL liq_error liq_histogram_add_colors(liq_histogram *input_hist, const liq_attr *options, const liq_histogram_entry entries[], int num_entries, double gamma) { + if (!CHECK_STRUCT_TYPE(options, liq_attr)) return LIQ_INVALID_POINTER; + if (!CHECK_STRUCT_TYPE(input_hist, liq_histogram)) return LIQ_INVALID_POINTER; + if (!CHECK_USER_POINTER(entries)) return LIQ_INVALID_POINTER; + if (gamma < 0 || gamma >= 1.0) return LIQ_VALUE_OUT_OF_RANGE; + if (num_entries <= 0 || num_entries > 1<<30) return LIQ_VALUE_OUT_OF_RANGE; + + if (input_hist->ignorebits > 0 && input_hist->had_image_added) { + return LIQ_UNSUPPORTED; + } + input_hist->ignorebits = 0; + + input_hist->had_image_added = true; + input_hist->gamma = gamma ? gamma : 0.45455; + + if (!input_hist->acht) { + input_hist->acht = pam_allocacolorhash(~0, num_entries*num_entries, 0, options->malloc, options->free); + if (!input_hist->acht) { + return LIQ_OUT_OF_MEMORY; + } + } + // Fake image size. It's only for hash size estimates. + if (!input_hist->acht->cols) { + input_hist->acht->cols = num_entries; + } + input_hist->acht->rows += num_entries; + + const unsigned int hash_size = input_hist->acht->hash_size; + for(int i=0; i < num_entries; i++) { + const rgba_pixel rgba = { + .r = entries[i].color.r, + .g = entries[i].color.g, + .b = entries[i].color.b, + .a = entries[i].color.a, + }; + union rgba_as_int px = {rgba}; + unsigned int hash; + if (px.rgba.a) { + hash = px.l % hash_size; + } else { + hash=0; px.l=0; + } + if (!pam_add_to_hash(input_hist->acht, hash, entries[i].count, px, i, num_entries)) { + return LIQ_OUT_OF_MEMORY; + } + } + + return LIQ_OK; +} + +LIQ_EXPORT LIQ_NONNULL liq_error liq_histogram_add_image(liq_histogram *input_hist, const liq_attr *options, liq_image *input_image) +{ + if (!CHECK_STRUCT_TYPE(options, liq_attr)) return LIQ_INVALID_POINTER; + if (!CHECK_STRUCT_TYPE(input_hist, liq_histogram)) return LIQ_INVALID_POINTER; + if (!CHECK_STRUCT_TYPE(input_image, liq_image)) return LIQ_INVALID_POINTER; + const unsigned int cols = input_image->width, rows = input_image->height; if (!input_image->noise && options->use_contrast_maps) { @@ -1588,7 +1641,7 @@ LIQ_NONNULL static void contrast_maps(liq_image *image) z *= 256.f; noise[j*cols+i] = z < 256 ? z : 255; z = (1.f-edge)*256.f; - edges[j*cols+i] = z < 256 ? z : 255; + edges[j*cols+i] = z > 0 ? (z < 256 ? z : 255) : 0; } } @@ -1729,11 +1782,11 @@ static colormap *find_best_palette(histogram *hist, const liq_attr *options, con } // after palette has been created, total error (MSE) is calculated to keep the best palette - // at the same time Voronoi iteration is done to improve the palette + // at the same time K-Means iteration is done to improve the palette // and histogram weights are adjusted based on remapping error to give more weight to poorly matched colors const bool first_run_of_target_mse = !acolormap && target_mse > 0; - double total_error = viter_do_iteration(hist, newmap, first_run_of_target_mse ? NULL : adjust_histogram_callback, !acolormap || options->fast_palette); + double total_error = kmeans_do_iteration(hist, newmap, first_run_of_target_mse ? NULL : adjust_histogram_callback); // goal is to increase quality or to reduce number of colors used if quality is good enough if (!acolormap || total_error < least_error || (total_error <= target_mse && newmap->colors < max_colors)) { @@ -1741,7 +1794,7 @@ static colormap *find_best_palette(histogram *hist, const liq_attr *options, con acolormap = newmap; if (total_error < target_mse && total_error > 0) { - // voronoi iteration improves quality above what mediancut aims for + // K-Means iteration improves quality above what mediancut aims for // this compensates for it, making mediancut aim for worse target_mse_overshoot = MIN(target_mse_overshoot*1.25, target_mse/total_error); } @@ -1794,8 +1847,6 @@ LIQ_NONNULL static liq_error pngquant_quantize(histogram *hist, const liq_attr * assert((verbose_print(options, "SLOW debug checks enabled. Recompile with NDEBUG for normal operation."),1)); - // no point having perfect match with imperfect colors (ignorebits > 0) - const bool fast_palette = options->fast_palette || hist->ignorebits > 0; const bool few_input_colors = hist->size+fixed_colors_count <= options->max_colors; if (liq_progress(options, options->progress_stage1)) return LIQ_ABORTED; @@ -1812,14 +1863,14 @@ LIQ_NONNULL static liq_error pngquant_quantize(histogram *hist, const liq_attr * return LIQ_VALUE_OUT_OF_RANGE; } - // Voronoi iteration approaches local minimum for the palette - const double iteration_limit = options->voronoi_iteration_limit; - unsigned int iterations = options->voronoi_iterations; + // K-Means iteration approaches local minimum for the palette + const double iteration_limit = options->kmeans_iteration_limit; + unsigned int iterations = options->kmeans_iterations; if (!iterations && palette_error < 0 && max_mse < MAX_DIFF) iterations = 1; // otherwise total error is never calculated and MSE limit won't work if (iterations) { - // likely_colormap_index (used and set in viter_do_iteration) can't point to index outside colormap + // likely_colormap_index (used and set in kmeans_do_iteration) can't point to index outside colormap if (acolormap->colors < 256) for(unsigned int j=0; j < hist->size; j++) { if (hist->achv[j].tmp.likely_colormap_index >= acolormap->colors) { hist->achv[j].tmp.likely_colormap_index = 0; // actual value doesn't matter, as the guess is out of date anyway @@ -1831,7 +1882,7 @@ LIQ_NONNULL static liq_error pngquant_quantize(histogram *hist, const liq_attr * double previous_palette_error = MAX_DIFF; for(unsigned int i=0; i < iterations; i++) { - palette_error = viter_do_iteration(hist, acolormap, NULL, i==0 || options->fast_palette); + palette_error = kmeans_do_iteration(hist, acolormap, NULL); if (liq_progress(options, options->progress_stage1 + options->progress_stage2 + (i * options->progress_stage3 * 0.9f) / iterations)) { break; @@ -1882,7 +1933,6 @@ LIQ_NONNULL static liq_error pngquant_quantize(histogram *hist, const liq_attr * .free = options->free, .palette = acolormap, .palette_error = palette_error, - .fast_palette = fast_palette, .use_dither_map = options->use_dither_map, .gamma = gamma, .min_posterization_output = options->min_posterization_output, @@ -1946,12 +1996,12 @@ LIQ_EXPORT LIQ_NONNULL liq_error liq_write_remapped_image_rows(liq_result *quant float remapping_error = result->palette_error; if (result->dither_level == 0) { set_rounded_palette(&result->int_palette, result->palette, result->gamma, quant->min_posterization_output); - remapping_error = remap_to_palette(input_image, row_pointers, result->palette, quant->fast_palette); + remapping_error = remap_to_palette(input_image, row_pointers, result->palette); } else { const bool generate_dither_map = result->use_dither_map && (input_image->edges && !input_image->dither_map); if (generate_dither_map) { // If dithering (with dither map) is required, this image is used to find areas that require dithering - remapping_error = remap_to_palette(input_image, row_pointers, result->palette, quant->fast_palette); + remapping_error = remap_to_palette(input_image, row_pointers, result->palette); update_dither_map(row_pointers, input_image); } @@ -1959,7 +2009,7 @@ LIQ_EXPORT LIQ_NONNULL liq_error liq_write_remapped_image_rows(liq_result *quant return LIQ_ABORTED; } - // remapping above was the last chance to do voronoi iteration, hence the final palette is set after remapping + // remapping above was the last chance to do K-Means iteration, hence the final palette is set after remapping set_rounded_palette(&result->int_palette, result->palette, result->gamma, quant->min_posterization_output); if (!remap_to_palette_floyd(input_image, row_pointers, result, MAX(remapping_error*2.4, 16.f/256.f), generate_dither_map)) { diff --git a/libimagequant.h b/libimagequant.h index ae503b1..c86f5a5 100644 --- a/libimagequant.h +++ b/libimagequant.h @@ -13,8 +13,8 @@ #define LIQ_EXPORT extern #endif -#define LIQ_VERSION 20800 -#define LIQ_VERSION_STRING "2.8.0" +#define LIQ_VERSION 20900 +#define LIQ_VERSION_STRING "2.9.0" #ifndef LIQ_PRIVATE #if defined(__GNUC__) || defined (__llvm__) @@ -57,17 +57,24 @@ typedef enum liq_error { LIQ_BITMAP_NOT_AVAILABLE, LIQ_BUFFER_TOO_SMALL, LIQ_INVALID_POINTER, + LIQ_UNSUPPORTED, } liq_error; enum liq_ownership {LIQ_OWN_ROWS=4, LIQ_OWN_PIXELS=8}; +typedef struct liq_histogram_entry { + liq_color color; + unsigned int count; +} liq_histogram_entry; + LIQ_EXPORT LIQ_USERESULT liq_attr* liq_attr_create(void); LIQ_EXPORT LIQ_USERESULT liq_attr* liq_attr_create_with_allocator(void* (*malloc)(size_t), void (*free)(void*)); -LIQ_EXPORT LIQ_USERESULT liq_attr* liq_attr_copy(liq_attr *orig) LIQ_NONNULL; +LIQ_EXPORT LIQ_USERESULT liq_attr* liq_attr_copy(const liq_attr *orig) LIQ_NONNULL; LIQ_EXPORT void liq_attr_destroy(liq_attr *attr) LIQ_NONNULL; -LIQ_EXPORT LIQ_USERESULT liq_histogram* liq_histogram_create(liq_attr* attr); -LIQ_EXPORT LIQ_USERESULT liq_error liq_histogram_add_image(liq_histogram *hist, liq_attr *attr, liq_image* image); +LIQ_EXPORT LIQ_USERESULT liq_histogram* liq_histogram_create(const liq_attr* attr); +LIQ_EXPORT liq_error liq_histogram_add_image(liq_histogram *hist, const liq_attr *attr, liq_image* image) LIQ_NONNULL; +LIQ_EXPORT liq_error liq_histogram_add_colors(liq_histogram *hist, const liq_attr *attr, const liq_histogram_entry entries[], int num_entries, double gamma) LIQ_NONNULL; LIQ_EXPORT void liq_histogram_destroy(liq_histogram *hist) LIQ_NONNULL; LIQ_EXPORT liq_error liq_set_max_colors(liq_attr* attr, int colors) LIQ_NONNULL; @@ -117,10 +124,10 @@ LIQ_EXPORT LIQ_USERESULT const liq_palette *liq_get_palette(liq_result *result) LIQ_EXPORT liq_error liq_write_remapped_image(liq_result *result, liq_image *input_image, void *buffer, size_t buffer_size) LIQ_NONNULL; LIQ_EXPORT liq_error liq_write_remapped_image_rows(liq_result *result, liq_image *input_image, unsigned char **row_pointers) LIQ_NONNULL; -LIQ_EXPORT double liq_get_quantization_error(liq_result *result) LIQ_NONNULL; -LIQ_EXPORT int liq_get_quantization_quality(liq_result *result) LIQ_NONNULL; -LIQ_EXPORT double liq_get_remapping_error(liq_result *result) LIQ_NONNULL; -LIQ_EXPORT int liq_get_remapping_quality(liq_result *result) LIQ_NONNULL; +LIQ_EXPORT double liq_get_quantization_error(const liq_result *result) LIQ_NONNULL; +LIQ_EXPORT int liq_get_quantization_quality(const liq_result *result) LIQ_NONNULL; +LIQ_EXPORT double liq_get_remapping_error(const liq_result *result) LIQ_NONNULL; +LIQ_EXPORT int liq_get_remapping_quality(const liq_result *result) LIQ_NONNULL; LIQ_EXPORT void liq_result_destroy(liq_result *) LIQ_NONNULL; diff --git a/mediancut.c b/mediancut.c index 9d93d49..6e3e590 100644 --- a/mediancut.c +++ b/mediancut.c @@ -38,7 +38,7 @@ #define index_of_channel(ch) (offsetof(f_pixel,ch)/sizeof(float)) -static f_pixel averagepixels(unsigned int clrs, const hist_item achv[], const f_pixel center); +static f_pixel averagepixels(unsigned int clrs, const hist_item achv[]); struct box { f_pixel color; @@ -63,7 +63,7 @@ static f_pixel box_variance(const hist_item achv[], const struct box *box) 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; + const 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; @@ -208,10 +208,10 @@ static double prepare_sort(struct box *b, hist_item achv[]) ** Sort dimensions by their variance, and then sort colors first by dimension with highest variance */ channelvariance channels[4] = { + {index_of_channel(a), b->variance.a}, {index_of_channel(r), b->variance.r}, {index_of_channel(g), b->variance.g}, {index_of_channel(b), b->variance.b}, - {index_of_channel(a), b->variance.a}, }; qsort(channels, 4, sizeof(channels[0]), comparevariance); @@ -245,7 +245,7 @@ static f_pixel get_median(const struct box *b, hist_item achv[]) // technically the second color is not guaranteed to be sorted correctly // but most of the time it is good enough to be useful - return averagepixels(2, &achv[b->ind + median_start], (f_pixel){0.5,0.5,0.5,0.5}); + return averagepixels(2, &achv[b->ind + median_start]); } /* @@ -278,13 +278,11 @@ static int best_splittable_box(struct box* bv, unsigned int boxes, const double 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 < 1.f/256.f/256.f) diff /= 2.f; return sqrt(diff) * (sqrt(1.0+h.adjusted_weight)-1.0); } static void set_colormap_from_boxes(colormap *map, struct box* bv, unsigned int boxes, hist_item *achv); -static void adjust_histogram(hist_item *achv, const colormap *map, const struct box* bv, unsigned int boxes); +static void adjust_histogram(hist_item *achv, const struct box* bv, unsigned int boxes); static double box_error(const struct box *box, const hist_item achv[]) { @@ -323,6 +321,16 @@ static bool total_box_error_below_target(double target_mse, struct box bv[], uns return true; } +static void box_init(struct box *box, const hist_item *achv, const unsigned int ind, const unsigned int colors, const double sum) { + box->ind = ind; + box->colors = colors; + box->sum = sum; + box->total_error = -1; + box->color = averagepixels(colors, &achv[ind]); + box->variance = box_variance(achv, box); + box->max_error = box_max_error(achv, box); +} + /* ** Here is the fun part, the median-cut colormap generator. This is based ** on Paul Heckbert's paper, "Color Image Quantization for Frame Buffer @@ -336,14 +344,11 @@ LIQ_PRIVATE colormap *mediancut(histogram *hist, unsigned int newcolors, const d /* ** Set up the initial box. */ - bv[0].ind = 0; - bv[0].colors = hist->size; - bv[0].color = averagepixels(bv[0].colors, &achv[bv[0].ind], (f_pixel){0.5,0.5,0.5,0.5}); - bv[0].variance = box_variance(achv, &bv[0]); - bv[0].max_error = box_max_error(achv, &bv[0]); - bv[0].sum = 0; - bv[0].total_error = -1; - for(unsigned int i=0; i < bv[0].colors; i++) bv[0].sum += achv[i].adjusted_weight; + double sum = 0; + for(unsigned int i=0; i < hist->size; i++) { + sum += achv[i].adjusted_weight; + } + box_init(&bv[0], achv, 0, hist->size, sum); unsigned int boxes = 1; @@ -388,20 +393,8 @@ LIQ_PRIVATE colormap *mediancut(histogram *hist, unsigned int newcolors, const d double lowersum = 0; for(unsigned int i=0; i < break_at; i++) lowersum += achv[indx + i].adjusted_weight; - const f_pixel previous_center = bv[bi].color; - bv[bi].colors = break_at; - bv[bi].sum = lowersum; - bv[bi].color = averagepixels(bv[bi].colors, &achv[bv[bi].ind], previous_center); - bv[bi].total_error = -1; - bv[bi].variance = box_variance(achv, &bv[bi]); - bv[bi].max_error = box_max_error(achv, &bv[bi]); - bv[boxes].ind = indx + break_at; - bv[boxes].colors = clrs - break_at; - bv[boxes].sum = sm - lowersum; - bv[boxes].color = averagepixels(bv[boxes].colors, &achv[bv[boxes].ind], previous_center); - bv[boxes].total_error = -1; - bv[boxes].variance = box_variance(achv, &bv[boxes]); - bv[boxes].max_error = box_max_error(achv, &bv[boxes]); + box_init(&bv[bi], achv, bv[bi].ind, break_at, lowersum); + box_init(&bv[boxes], achv, indx + break_at, clrs - break_at, sm - lowersum); ++boxes; @@ -413,7 +406,7 @@ LIQ_PRIVATE colormap *mediancut(histogram *hist, unsigned int newcolors, const d colormap *map = pam_colormap(boxes, malloc, free); set_colormap_from_boxes(map, bv, boxes, achv); - adjust_histogram(achv, map, bv, boxes); + adjust_histogram(achv, bv, boxes); return map; } @@ -440,17 +433,16 @@ static void set_colormap_from_boxes(colormap *map, struct box* bv, unsigned int } /* 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) +static void adjust_histogram(hist_item *achv, 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)/2.0); achv[i].tmp.likely_colormap_index = bi; } } } -static f_pixel averagepixels(unsigned int clrs, const hist_item achv[], f_pixel center) +static f_pixel averagepixels(unsigned int clrs, const hist_item achv[]) { double r = 0, g = 0, b = 0, a = 0, sum = 0; @@ -459,10 +451,10 @@ static f_pixel averagepixels(unsigned int clrs, const hist_item achv[], f_pixel const double weight = achv[i].adjusted_weight; sum += weight; + a += px.a * weight; r += px.r * weight; g += px.g * weight; b += px.b * weight; - a += px.a * weight; } if (sum) { diff --git a/nearest.c b/nearest.c index 448ab3a..2f1e926 100644 --- a/nearest.c +++ b/nearest.c @@ -117,7 +117,7 @@ static vp_node *vp_create_node(mempool *m, vp_sort_tmp *indexes, int num_indexes return node; } -LIQ_PRIVATE struct nearest_map *nearest_init(const colormap *map, const bool fast) { +LIQ_PRIVATE struct nearest_map *nearest_init(const colormap *map) { mempool m = NULL; struct nearest_map *handle = mempool_create(&m, sizeof(handle[0]), sizeof(handle[0]) + sizeof(vp_node)*map->colors+16, map->malloc, map->free); diff --git a/nearest.h b/nearest.h index 0a98ca6..e20233b 100644 --- a/nearest.h +++ b/nearest.h @@ -3,6 +3,6 @@ // pngquant // struct nearest_map; -LIQ_PRIVATE struct nearest_map *nearest_init(const colormap *palette, const bool fast); +LIQ_PRIVATE struct nearest_map *nearest_init(const colormap *palette); LIQ_PRIVATE unsigned int nearest_search(const struct nearest_map *map, const f_pixel *px, const int palette_index_guess, float *diff); LIQ_PRIVATE void nearest_free(struct nearest_map *map); diff --git a/pam.c b/pam.c index aae2927..74165e1 100644 --- a/pam.c +++ b/pam.c @@ -22,20 +22,14 @@ 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) { - const unsigned int maxacolors = acht->maxcolors, ignorebits = acht->ignorebits; + const unsigned int ignorebits = acht->ignorebits; const unsigned int channel_mask = 255U>>ignorebits<>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) { @@ -59,19 +53,32 @@ LIQ_PRIVATE bool pam_computeacolorhash(struct acolorhash_table *acht, const rgba hash = px.l % hash_size; } + if (!pam_add_to_hash(acht, hash, boost, px, row, rows)) { + return false; + } + } + + } + acht->cols = cols; + acht->rows += rows; + return true; +} + +LIQ_PRIVATE bool pam_add_to_hash(struct acolorhash_table *acht, unsigned int hash, float boost, union rgba_as_int px, unsigned int row, unsigned int rows) +{ /* head of the hash function stores first 2 colors inline (achl->used = 1..2), to reduce number of allocations of achl->other_items. */ - struct acolorhist_arr_head *achl = &buckets[hash]; + struct acolorhist_arr_head *achl = &acht->buckets[hash]; if (achl->inline1.color.l == px.l && achl->used) { achl->inline1.perceptual_weight += boost; - continue; + return true; } if (achl->used) { if (achl->used > 1) { if (achl->inline2.color.l == px.l) { achl->inline2.perceptual_weight += boost; - continue; + return true; } // other items are stored as an array (which gets reallocated if needed) struct acolorhist_arr_item *other_items = achl->other_items; @@ -79,7 +86,7 @@ LIQ_PRIVATE bool pam_computeacolorhash(struct acolorhash_table *acht, const rgba for (; i < achl->used-2; i++) { if (other_items[i].color.l == px.l) { other_items[i].perceptual_weight += boost; - goto continue_outer_loop; + return true; } } @@ -90,13 +97,11 @@ LIQ_PRIVATE bool pam_computeacolorhash(struct acolorhash_table *acht, const rgba .perceptual_weight = boost, }; achl->used++; - ++colors; - continue; + ++acht->colors; + return true; } - if (++colors > maxacolors) { - acht->colors = colors; - acht->freestackp = freestackp; + if (++acht->colors > acht->maxcolors) { return false; } @@ -104,22 +109,24 @@ LIQ_PRIVATE bool pam_computeacolorhash(struct acolorhash_table *acht, const rgba unsigned int capacity; if (!other_items) { // there was no array previously, alloc "small" array capacity = 8; - if (freestackp <= 0) { + if (acht->freestackp <= 0) { // estimate how many colors are going to be + headroom - const size_t mempool_size = ((acht->rows + rows-row) * 2 * colors / (acht->rows + row + 1) + 1024) * sizeof(struct acolorhist_arr_item); + const size_t mempool_size = ((acht->rows + rows-row) * 2 * acht->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]; + new_items = acht->freestack[--acht->freestackp]; } } else { + const unsigned int stacksize = sizeof(acht->freestack)/sizeof(acht->freestack[0]); + // simply reallocs and copies array to larger capacity capacity = achl->capacity*2 + 16; - if (freestackp < stacksize-1) { - freestack[freestackp++] = other_items; + if (acht->freestackp < stacksize-1) { + acht->freestack[acht->freestackp++] = other_items; } - const size_t mempool_size = ((acht->rows + rows-row) * 2 * colors / (acht->rows + row + 1) + 32*capacity) * sizeof(struct acolorhist_arr_item); + const size_t mempool_size = ((acht->rows + rows-row) * 2 * acht->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); @@ -137,23 +144,14 @@ LIQ_PRIVATE bool pam_computeacolorhash(struct acolorhash_table *acht, const rgba achl->inline2.color.l = px.l; achl->inline2.perceptual_weight = boost; achl->used = 2; - ++colors; + ++acht->colors; } } else { achl->inline1.color.l = px.l; achl->inline1.perceptual_weight = boost; achl->used = 1; - ++colors; + ++acht->colors; } - - continue_outer_loop:; - } - - } - acht->colors = colors; - acht->cols = cols; - acht->rows += rows; - acht->freestackp = freestackp; return true; } @@ -177,10 +175,12 @@ LIQ_PRIVATE struct acolorhash_table *pam_allocacolorhash(unsigned int maxcolors, 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; \ +ALWAYS_INLINE static float pam_add_to_hist(const float *gamma_lut, hist_item *achv, unsigned int j, const struct acolorhist_arr_item *entry, const float max_perceptual_weight) +{ + achv[j].acolor = rgba_to_f(gamma_lut, entry->color.rgba); + const float w = MIN(entry->perceptual_weight, max_perceptual_weight); + achv[j].adjusted_weight = achv[j].perceptual_weight = w; + return w; } LIQ_PRIVATE histogram *pam_acolorhashtoacolorhist(const struct acolorhash_table *acht, const double gamma, void* (*malloc)(size_t), void (*free)(void*)) @@ -206,13 +206,13 @@ LIQ_PRIVATE histogram *pam_acolorhashtoacolorhist(const struct acolorhash_table for(unsigned int 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); + total_weight += pam_add_to_hist(gamma_lut, hist->achv, j++, &achl->inline1, max_perceptual_weight); if (achl->used > 1) { - PAM_ADD_TO_HIST(achl->inline2); + total_weight += pam_add_to_hist(gamma_lut, hist->achv, j++, &achl->inline2, max_perceptual_weight); for(unsigned int k=0; k < achl->used-2; k++) { - PAM_ADD_TO_HIST(achl->other_items[k]); + total_weight += pam_add_to_hist(gamma_lut, hist->achv, j++, &achl->other_items[k], max_perceptual_weight); } } } diff --git a/pam.h b/pam.h index 3af703e..6657a14 100644 --- a/pam.h +++ b/pam.h @@ -93,8 +93,8 @@ LIQ_PRIVATE void to_f_set_gamma(float gamma_lut[], const double gamma); Converts 8-bit color to internal gamma and premultiplied alpha. (premultiplied color space is much better for blending of semitransparent colors) */ -ALWAYS_INLINE static f_pixel to_f(const float gamma_lut[], const rgba_pixel px); -inline static f_pixel to_f(const float gamma_lut[], const rgba_pixel px) +ALWAYS_INLINE static f_pixel rgba_to_f(const float gamma_lut[], const rgba_pixel px); +inline static f_pixel rgba_to_f(const float gamma_lut[], const rgba_pixel px) { float a = px.a/255.f; @@ -106,7 +106,7 @@ inline static f_pixel to_f(const float gamma_lut[], const rgba_pixel px) }; } -inline static rgba_pixel to_rgb(const float gamma, const f_pixel px) +inline static rgba_pixel f_to_rgb(const float gamma, const f_pixel px) { if (px.a < 1.f/256.f) { return (rgba_pixel){0,0,0,0}; @@ -226,7 +226,7 @@ typedef struct { typedef struct { f_pixel acolor; float popularity; - bool fixed; // if true it's user-supplied and must not be changed (e.g in voronoi iteration) + bool fixed; // if true it's user-supplied and must not be changed (e.g in K-Means iteration) } colormap_item; typedef struct colormap { @@ -260,6 +260,7 @@ LIQ_PRIVATE void pam_freeacolorhash(struct acolorhash_table *acht); LIQ_PRIVATE struct acolorhash_table *pam_allocacolorhash(unsigned int maxcolors, unsigned int surface, unsigned int ignorebits, void* (*malloc)(size_t), void (*free)(void*)); LIQ_PRIVATE histogram *pam_acolorhashtoacolorhist(const struct acolorhash_table *acht, const double gamma, void* (*malloc)(size_t), void (*free)(void*)); 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); +LIQ_PRIVATE bool pam_add_to_hash(struct acolorhash_table *acht, unsigned int hash, float boost, union rgba_as_int px, unsigned int row, unsigned int rows); LIQ_PRIVATE void pam_freeacolorhist(histogram *h); diff --git a/viter.h b/viter.h deleted file mode 100644 index bbbaaa1..0000000 --- a/viter.h +++ /dev/null @@ -1,19 +0,0 @@ - -#ifndef VITER_H -#define VITER_H - -// Spread memory touched by different threads at least 64B apart which I assume is the cache line size. This should avoid memory write contention. -#define VITER_CACHE_LINE_GAP ((64+sizeof(viter_state)-1)/sizeof(viter_state)) - -typedef struct { - double a, r, g, b, total; -} viter_state; - -typedef void (*viter_callback)(hist_item *item, float diff); - -LIQ_PRIVATE void viter_init(const colormap *map, const unsigned int max_threads, viter_state state[]); -LIQ_PRIVATE void viter_update_color(const f_pixel acolor, const float value, const colormap *map, unsigned int match, const unsigned int thread, viter_state average_color[]); -LIQ_PRIVATE void viter_finalize(colormap *map, const unsigned int max_threads, const viter_state state[]); -LIQ_PRIVATE double viter_do_iteration(histogram *hist, colormap *const map, viter_callback callback, const bool fast_palette); - -#endif