update bundled libimagequant from 2.8.0 to 2.9.0

This commit is contained in:
mappu 2017-03-03 18:24:02 +13:00
parent 29c239a810
commit a651fa3a20
10 changed files with 231 additions and 181 deletions

View File

@ -19,7 +19,7 @@ along with libimagequant. If not, see <http://www.gnu.org/licenses/>.
#include "libimagequant.h" #include "libimagequant.h"
#include "pam.h" #include "pam.h"
#include "viter.h" #include "kmeans.h"
#include "nearest.h" #include "nearest.h"
#include <stdlib.h> #include <stdlib.h>
#include <string.h> #include <string.h>
@ -32,16 +32,16 @@ along with libimagequant. If not, see <http://www.gnu.org/licenses/>.
#endif #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].a += acolor.a * value;
average_color[match].r += acolor.r * value; average_color[match].r += acolor.r * value;
average_color[match].g += acolor.g * 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; 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++) { for (unsigned int i=0; i < map->colors; i++) {
double a=0, r=0, g=0, b=0, total=0; double a=0, r=0, g=0, b=0, total=0;
// Aggregate results from all threads // Aggregate results from all threads
for(unsigned int t=0; t < max_threads; t++) { 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; a += average_color[offset].a;
r += average_color[offset].r; 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(); const unsigned int max_threads = omp_get_max_threads();
viter_state average_color[(VITER_CACHE_LINE_GAP+map->colors) * max_threads]; kmeans_state average_color[(KMEANS_CACHE_LINE_GAP+map->colors) * max_threads];
viter_init(map, max_threads, average_color); kmeans_init(map, max_threads, average_color);
struct nearest_map *const n = nearest_init(map, fast_palette); struct nearest_map *const n = nearest_init(map);
hist_item *const achv = hist->achv; hist_item *const achv = hist->achv;
const int hist_size = hist->size; 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; achv[j].tmp.likely_colormap_index = match;
total_diff += diff * achv[j].perceptual_weight; 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); if (callback) callback(&achv[j], diff);
} }
nearest_free(n); nearest_free(n);
viter_finalize(map, max_threads, average_color); kmeans_finalize(map, max_threads, average_color);
return total_diff / hist->total_perceptual_weight; return total_diff / hist->total_perceptual_weight;
} }

19
kmeans.h Normal file
View File

@ -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

View File

@ -54,7 +54,7 @@
#include "mediancut.h" #include "mediancut.h"
#include "nearest.h" #include "nearest.h"
#include "blur.h" #include "blur.h"
#include "viter.h" #include "kmeans.h"
#define LIQ_HIGH_MEMORY_LIMIT (1<<26) /* avoid allocating buffers larger than 64MB */ #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* (*malloc)(size_t);
void (*free)(void*); void (*free)(void*);
double target_mse, max_mse, voronoi_iteration_limit; double target_mse, max_mse, kmeans_iteration_limit;
float min_opaque_val; float min_opaque_val;
unsigned int max_colors, max_histogram_entries; unsigned int max_colors, max_histogram_entries;
unsigned int min_posterization_output /* user setting */, min_posterization_input /* speed setting */; unsigned int min_posterization_output /* user setting */, min_posterization_input /* speed setting */;
unsigned int voronoi_iterations, feedback_loop_trials; unsigned int kmeans_iterations, feedback_loop_trials;
bool last_index_transparent, use_contrast_maps, use_dither_map, fast_palette; bool last_index_transparent, use_contrast_maps, use_dither_map;
unsigned char speed; unsigned char speed;
unsigned char progress_stage1, progress_stage2, progress_stage3; unsigned char progress_stage1, progress_stage2, progress_stage3;
@ -141,7 +141,7 @@ struct liq_result {
float dither_level; float dither_level;
double gamma, palette_error; double gamma, palette_error;
int min_posterization_output; int min_posterization_output;
bool use_dither_map, fast_palette; bool use_dither_map;
}; };
struct liq_histogram { 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; if (speed < 1 || speed > 10) return LIQ_VALUE_OUT_OF_RANGE;
unsigned int iterations = MAX(8-speed, 0); iterations += iterations * iterations/2; unsigned int iterations = MAX(8-speed, 0); iterations += iterations * iterations/2;
attr->voronoi_iterations = iterations; attr->kmeans_iterations = iterations;
attr->voronoi_iteration_limit = 1.0/(double)(1<<(23-speed)); attr->kmeans_iteration_limit = 1.0/(double)(1<<(23-speed));
attr->feedback_loop_trials = MAX(56-9*speed, 0); attr->feedback_loop_trials = MAX(56-9*speed, 0);
attr->max_histogram_entries = (1<<17) + (1<<18)*(10-speed); attr->max_histogram_entries = (1<<17) + (1<<18)*(10-speed);
attr->min_posterization_input = (speed >= 8) ? 1 : 0; 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_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->use_contrast_maps = (speed <= 7) || attr->use_dither_map;
attr->speed = speed; attr->speed = speed;
@ -461,7 +460,7 @@ LIQ_EXPORT LIQ_NONNULL void liq_attr_destroy(liq_attr *attr)
attr->free(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)) { if (!CHECK_STRUCT_TYPE(orig, liq_attr)) {
return NULL; 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) 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 (!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]; float gamma_lut[256];
to_f_set_gamma(gamma_lut, img->gamma); 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, .r = color.r,
.g = color.g, .g = color.g,
.b = color.b, .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) 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; hist->fixed_colors[hist->fixed_colors_count++] = color;
return LIQ_OK; 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); const rgba_pixel *const row_pixels = liq_image_get_row_rgba(img, row);
for(unsigned int col=0; col < img->width; col++) { 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); 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)) { if (!CHECK_STRUCT_TYPE(attr, liq_attr)) {
return NULL; 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 (!CHECK_STRUCT_TYPE(attr, liq_attr)) return LIQ_INVALID_POINTER;
if (!liq_image_has_rgba_pixels(img)) { if (!liq_image_has_rgba_pixels(img)) {
return LIQ_INVALID_POINTER; return LIQ_UNSUPPORTED;
} }
liq_histogram *hist = liq_histogram_create(attr); 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 (!CHECK_STRUCT_TYPE(result, liq_result)) return -1;
if (result->palette_error >= 0) { if (result->palette_error >= 0) {
@ -1021,7 +1020,7 @@ LIQ_EXPORT LIQ_NONNULL double liq_get_quantization_error(liq_result *result) {
return -1; 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 (!CHECK_STRUCT_TYPE(result, liq_result)) return -1;
if (result->remapping && result->remapping->palette_error >= 0) { 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; 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 (!CHECK_STRUCT_TYPE(result, liq_result)) return -1;
if (result->palette_error >= 0) { if (result->palette_error >= 0) {
@ -1041,7 +1040,7 @@ LIQ_EXPORT LIQ_NONNULL int liq_get_quantization_quality(liq_result *result) {
return -1; 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 (!CHECK_STRUCT_TYPE(result, liq_result)) return -1;
if (result->remapping && result->remapping->palette_error >= 0) { 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; dest->count = map->colors;
for(unsigned int x = 0; x < map->colors; ++x) { 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.r = posterize_channel(px.r, posterize);
px.g = posterize_channel(px.g, posterize); px.g = posterize_channel(px.g, posterize);
px.b = posterize_channel(px.b, posterize); px.b = posterize_channel(px.b, posterize);
px.a = posterize_channel(px.a, 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) { if (!px.a && !map->palette[x].fixed) {
px.r = 71; px.g = 112; px.b = 76; 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; 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 int rows = input_image->height;
const unsigned int cols = input_image->width; 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; 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(); const unsigned int max_threads = omp_get_max_threads();
viter_state average_color[(VITER_CACHE_LINE_GAP+map->colors) * max_threads]; kmeans_state average_color[(KMEANS_CACHE_LINE_GAP+map->colors) * max_threads];
viter_init(map, max_threads, average_color); kmeans_init(map, max_threads, average_color);
#pragma omp parallel for if (rows*cols > 3000) \ #pragma omp parallel for if (rows*cols > 3000) \
schedule(static) default(none) shared(average_color) reduction(+:remapping_error) 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); output_pixels[row][col] = last_match = nearest_search(n, &row_pixels[col], last_match, &diff);
remapping_error += 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); 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; const float max_underflow = -0.1f;
// allowing some overflow prevents undithered bands caused by clamping of all channels // 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); 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); 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); 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); 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); 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); else { if (px.b + sb < max_underflow) ratio = MIN(ratio, (max_underflow-px.b)/sb); }
float a = px.a + sa; float a = px.a + sa;
if (a > 1.0) { a = 1.0; } 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) 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 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; 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); nexterr = thiserr + (cols + 2);
bool ok = true; 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 // 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; 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 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; 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)) { if (liq_remap_progress(quant, quant->progress_stage1 + row * (100.f - quant->progress_stage1) / rows)) {
ok = false; ok = false;
break; 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)); 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); const f_pixel *const row_pixels = liq_image_get_row_f(input_image, row);
do { do {
@ -1323,7 +1322,7 @@ LIQ_NONNULL static bool remap_to_palette_floyd(liq_image *input_image, unsigned
} }
/* Propagate Floyd-Steinberg error terms. */ /* 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].a += err.a * (7.f/16.f);
thiserr[col + 2].r += err.r * (7.f/16.f); thiserr[col + 2].r += err.r * (7.f/16.f);
thiserr[col + 2].g += err.g * (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 // remapping is done in zig-zag
if (fs_direction) { col += fs_direction;
++col; if (fs_direction > 0) {
if (col >= cols) break; if (col >= cols) break;
} else { } else {
if (col <= 0) break; if (col <= 0) break;
--col;
} }
} while(1); } while(1);
f_pixel *const temperr = thiserr; f_pixel *const temperr = thiserr;
thiserr = nexterr; thiserr = nexterr;
nexterr = temperr; nexterr = temperr;
fs_direction = !fs_direction; fs_direction = -fs_direction;
} }
input_image->free(MIN(thiserr, nexterr)); // MIN because pointers were swapped 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; const unsigned int cols = input_image->width, rows = input_image->height;
if (!input_image->noise && options->use_contrast_maps) { if (!input_image->noise && options->use_contrast_maps) {
@ -1588,7 +1641,7 @@ LIQ_NONNULL static void contrast_maps(liq_image *image)
z *= 256.f; z *= 256.f;
noise[j*cols+i] = z < 256 ? z : 255; noise[j*cols+i] = z < 256 ? z : 255;
z = (1.f-edge)*256.f; 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 // 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 // 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; 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 // 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)) { 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; acolormap = newmap;
if (total_error < target_mse && total_error > 0) { 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 // this compensates for it, making mediancut aim for worse
target_mse_overshoot = MIN(target_mse_overshoot*1.25, target_mse/total_error); 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)); 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; const bool few_input_colors = hist->size+fixed_colors_count <= options->max_colors;
if (liq_progress(options, options->progress_stage1)) return LIQ_ABORTED; 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; return LIQ_VALUE_OUT_OF_RANGE;
} }
// Voronoi iteration approaches local minimum for the palette // K-Means iteration approaches local minimum for the palette
const double iteration_limit = options->voronoi_iteration_limit; const double iteration_limit = options->kmeans_iteration_limit;
unsigned int iterations = options->voronoi_iterations; 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 && palette_error < 0 && max_mse < MAX_DIFF) iterations = 1; // otherwise total error is never calculated and MSE limit won't work
if (iterations) { 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 (acolormap->colors < 256) for(unsigned int j=0; j < hist->size; j++) {
if (hist->achv[j].tmp.likely_colormap_index >= acolormap->colors) { 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 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; double previous_palette_error = MAX_DIFF;
for(unsigned int i=0; i < iterations; i++) { 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)) { if (liq_progress(options, options->progress_stage1 + options->progress_stage2 + (i * options->progress_stage3 * 0.9f) / iterations)) {
break; break;
@ -1882,7 +1933,6 @@ LIQ_NONNULL static liq_error pngquant_quantize(histogram *hist, const liq_attr *
.free = options->free, .free = options->free,
.palette = acolormap, .palette = acolormap,
.palette_error = palette_error, .palette_error = palette_error,
.fast_palette = fast_palette,
.use_dither_map = options->use_dither_map, .use_dither_map = options->use_dither_map,
.gamma = gamma, .gamma = gamma,
.min_posterization_output = options->min_posterization_output, .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; float remapping_error = result->palette_error;
if (result->dither_level == 0) { if (result->dither_level == 0) {
set_rounded_palette(&result->int_palette, result->palette, result->gamma, quant->min_posterization_output); 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 { } else {
const bool generate_dither_map = result->use_dither_map && (input_image->edges && !input_image->dither_map); const bool generate_dither_map = result->use_dither_map && (input_image->edges && !input_image->dither_map);
if (generate_dither_map) { if (generate_dither_map) {
// If dithering (with dither map) is required, this image is used to find areas that require dithering // 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); 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; 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); 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)) { if (!remap_to_palette_floyd(input_image, row_pointers, result, MAX(remapping_error*2.4, 16.f/256.f), generate_dither_map)) {

View File

@ -13,8 +13,8 @@
#define LIQ_EXPORT extern #define LIQ_EXPORT extern
#endif #endif
#define LIQ_VERSION 20800 #define LIQ_VERSION 20900
#define LIQ_VERSION_STRING "2.8.0" #define LIQ_VERSION_STRING "2.9.0"
#ifndef LIQ_PRIVATE #ifndef LIQ_PRIVATE
#if defined(__GNUC__) || defined (__llvm__) #if defined(__GNUC__) || defined (__llvm__)
@ -57,17 +57,24 @@ typedef enum liq_error {
LIQ_BITMAP_NOT_AVAILABLE, LIQ_BITMAP_NOT_AVAILABLE,
LIQ_BUFFER_TOO_SMALL, LIQ_BUFFER_TOO_SMALL,
LIQ_INVALID_POINTER, LIQ_INVALID_POINTER,
LIQ_UNSUPPORTED,
} liq_error; } liq_error;
enum liq_ownership {LIQ_OWN_ROWS=4, LIQ_OWN_PIXELS=8}; 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(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_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 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_histogram* liq_histogram_create(const 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_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 void liq_histogram_destroy(liq_histogram *hist) LIQ_NONNULL;
LIQ_EXPORT liq_error liq_set_max_colors(liq_attr* attr, int colors) 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(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 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 double liq_get_quantization_error(const liq_result *result) LIQ_NONNULL;
LIQ_EXPORT int liq_get_quantization_quality(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(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(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; LIQ_EXPORT void liq_result_destroy(liq_result *) LIQ_NONNULL;

View File

@ -38,7 +38,7 @@
#define index_of_channel(ch) (offsetof(f_pixel,ch)/sizeof(float)) #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 { struct box {
f_pixel color; 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; double variancea=0, variancer=0, varianceg=0, varianceb=0;
for(unsigned int i = 0; i < box->colors; ++i) { 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; double weight = achv[box->ind + i].adjusted_weight;
variancea += variance_diff(mean.a - px.a, 2.0/256.0)*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; 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 ** Sort dimensions by their variance, and then sort colors first by dimension with highest variance
*/ */
channelvariance channels[4] = { channelvariance channels[4] = {
{index_of_channel(a), b->variance.a},
{index_of_channel(r), b->variance.r}, {index_of_channel(r), b->variance.r},
{index_of_channel(g), b->variance.g}, {index_of_channel(g), b->variance.g},
{index_of_channel(b), b->variance.b}, {index_of_channel(b), b->variance.b},
{index_of_channel(a), b->variance.a},
}; };
qsort(channels, 4, sizeof(channels[0]), comparevariance); 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 // technically the second color is not guaranteed to be sorted correctly
// but most of the time it is good enough to be useful // 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) inline static double color_weight(f_pixel median, hist_item h)
{ {
float diff = colordifference(median, h.acolor); 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); 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 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[]) 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; 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 ** Here is the fun part, the median-cut colormap generator. This is based
** on Paul Heckbert's paper, "Color Image Quantization for Frame Buffer ** 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. ** Set up the initial box.
*/ */
bv[0].ind = 0; double sum = 0;
bv[0].colors = hist->size; for(unsigned int i=0; i < hist->size; i++) {
bv[0].color = averagepixels(bv[0].colors, &achv[bv[0].ind], (f_pixel){0.5,0.5,0.5,0.5}); sum += achv[i].adjusted_weight;
bv[0].variance = box_variance(achv, &bv[0]); }
bv[0].max_error = box_max_error(achv, &bv[0]); box_init(&bv[0], achv, 0, hist->size, sum);
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;
unsigned int boxes = 1; unsigned int boxes = 1;
@ -388,20 +393,8 @@ LIQ_PRIVATE colormap *mediancut(histogram *hist, unsigned int newcolors, const d
double lowersum = 0; double lowersum = 0;
for(unsigned int i=0; i < break_at; i++) lowersum += achv[indx + i].adjusted_weight; for(unsigned int i=0; i < break_at; i++) lowersum += achv[indx + i].adjusted_weight;
const f_pixel previous_center = bv[bi].color; box_init(&bv[bi], achv, bv[bi].ind, break_at, lowersum);
bv[bi].colors = break_at; box_init(&bv[boxes], achv, indx + break_at, clrs - break_at, sm - lowersum);
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]);
++boxes; ++boxes;
@ -413,7 +406,7 @@ LIQ_PRIVATE colormap *mediancut(histogram *hist, unsigned int newcolors, const d
colormap *map = pam_colormap(boxes, malloc, free); colormap *map = pam_colormap(boxes, malloc, free);
set_colormap_from_boxes(map, bv, boxes, achv); set_colormap_from_boxes(map, bv, boxes, achv);
adjust_histogram(achv, map, bv, boxes); adjust_histogram(achv, bv, boxes);
return map; 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) */ /* 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 bi = 0; bi < boxes; ++bi) {
for(unsigned int i=bv[bi].ind; i < bv[bi].ind+bv[bi].colors; i++) { 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; 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; 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; const double weight = achv[i].adjusted_weight;
sum += weight; sum += weight;
a += px.a * weight;
r += px.r * weight; r += px.r * weight;
g += px.g * weight; g += px.g * weight;
b += px.b * weight; b += px.b * weight;
a += px.a * weight;
} }
if (sum) { if (sum) {

View File

@ -117,7 +117,7 @@ static vp_node *vp_create_node(mempool *m, vp_sort_tmp *indexes, int num_indexes
return node; 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; 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); struct nearest_map *handle = mempool_create(&m, sizeof(handle[0]), sizeof(handle[0]) + sizeof(vp_node)*map->colors+16, map->malloc, map->free);

View File

@ -3,6 +3,6 @@
// pngquant // pngquant
// //
struct nearest_map; 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 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); LIQ_PRIVATE void nearest_free(struct nearest_map *map);

80
pam.c
View File

@ -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) 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; const unsigned int channel_mask = 255U>>ignorebits<<ignorebits;
const unsigned int channel_hmask = (255U>>ignorebits) ^ 0xFFU; 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_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; 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 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. */ /* Go through the entire image, building a hash table of colors. */
for(unsigned int row = 0; row < rows; ++row) { 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; 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), /* head of the hash function stores first 2 colors inline (achl->used = 1..2),
to reduce number of allocations of achl->other_items. 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) { if (achl->inline1.color.l == px.l && achl->used) {
achl->inline1.perceptual_weight += boost; achl->inline1.perceptual_weight += boost;
continue; return true;
} }
if (achl->used) { if (achl->used) {
if (achl->used > 1) { if (achl->used > 1) {
if (achl->inline2.color.l == px.l) { if (achl->inline2.color.l == px.l) {
achl->inline2.perceptual_weight += boost; achl->inline2.perceptual_weight += boost;
continue; return true;
} }
// other items are stored as an array (which gets reallocated if needed) // other items are stored as an array (which gets reallocated if needed)
struct acolorhist_arr_item *other_items = achl->other_items; 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++) { for (; i < achl->used-2; i++) {
if (other_items[i].color.l == px.l) { if (other_items[i].color.l == px.l) {
other_items[i].perceptual_weight += boost; 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, .perceptual_weight = boost,
}; };
achl->used++; achl->used++;
++colors; ++acht->colors;
continue; return true;
} }
if (++colors > maxacolors) { if (++acht->colors > acht->maxcolors) {
acht->colors = colors;
acht->freestackp = freestackp;
return false; return false;
} }
@ -104,22 +109,24 @@ LIQ_PRIVATE bool pam_computeacolorhash(struct acolorhash_table *acht, const rgba
unsigned int capacity; unsigned int capacity;
if (!other_items) { // there was no array previously, alloc "small" array if (!other_items) { // there was no array previously, alloc "small" array
capacity = 8; capacity = 8;
if (freestackp <= 0) { if (acht->freestackp <= 0) {
// estimate how many colors are going to be + headroom // 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); new_items = mempool_alloc(&acht->mempool, sizeof(struct acolorhist_arr_item)*capacity, mempool_size);
} else { } else {
// freestack stores previously freed (reallocated) arrays that can be reused // freestack stores previously freed (reallocated) arrays that can be reused
// (all pesimistically assumed to be capacity = 8) // (all pesimistically assumed to be capacity = 8)
new_items = freestack[--freestackp]; new_items = acht->freestack[--acht->freestackp];
} }
} else { } else {
const unsigned int stacksize = sizeof(acht->freestack)/sizeof(acht->freestack[0]);
// simply reallocs and copies array to larger capacity // simply reallocs and copies array to larger capacity
capacity = achl->capacity*2 + 16; capacity = achl->capacity*2 + 16;
if (freestackp < stacksize-1) { if (acht->freestackp < stacksize-1) {
freestack[freestackp++] = other_items; 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); new_items = mempool_alloc(&acht->mempool, sizeof(struct acolorhist_arr_item)*capacity, mempool_size);
if (!new_items) return false; if (!new_items) return false;
memcpy(new_items, other_items, sizeof(other_items[0])*achl->capacity); 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.color.l = px.l;
achl->inline2.perceptual_weight = boost; achl->inline2.perceptual_weight = boost;
achl->used = 2; achl->used = 2;
++colors; ++acht->colors;
} }
} else { } else {
achl->inline1.color.l = px.l; achl->inline1.color.l = px.l;
achl->inline1.perceptual_weight = boost; achl->inline1.perceptual_weight = boost;
achl->used = 1; achl->used = 1;
++colors; ++acht->colors;
} }
continue_outer_loop:;
}
}
acht->colors = colors;
acht->cols = cols;
acht->rows += rows;
acht->freestackp = freestackp;
return true; return true;
} }
@ -177,10 +175,12 @@ LIQ_PRIVATE struct acolorhash_table *pam_allocacolorhash(unsigned int maxcolors,
return t; return t;
} }
#define PAM_ADD_TO_HIST(entry) { \ 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)
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); \ achv[j].acolor = rgba_to_f(gamma_lut, entry->color.rgba);
++j; \ 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*)) 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) { for(unsigned int j=0, i=0; i < acht->hash_size; ++i) {
const struct acolorhist_arr_head *const achl = &acht->buckets[i]; const struct acolorhist_arr_head *const achl = &acht->buckets[i];
if (achl->used) { 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) { 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++) { 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);
} }
} }
} }

9
pam.h
View File

@ -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. Converts 8-bit color to internal gamma and premultiplied alpha.
(premultiplied color space is much better for blending of semitransparent colors) (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); ALWAYS_INLINE static f_pixel rgba_to_f(const float gamma_lut[], const rgba_pixel px);
inline static f_pixel 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; 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) { if (px.a < 1.f/256.f) {
return (rgba_pixel){0,0,0,0}; return (rgba_pixel){0,0,0,0};
@ -226,7 +226,7 @@ typedef struct {
typedef struct { typedef struct {
f_pixel acolor; f_pixel acolor;
float popularity; 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; } colormap_item;
typedef struct colormap { 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 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 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_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); LIQ_PRIVATE void pam_freeacolorhist(histogram *h);

19
viter.h
View File

@ -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