update bundled libimagequant from 2.8.0 to 2.9.0
This commit is contained in:
parent
29c239a810
commit
a651fa3a20
@ -19,7 +19,7 @@ along with libimagequant. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
#include "libimagequant.h"
|
||||
#include "pam.h"
|
||||
#include "viter.h"
|
||||
#include "kmeans.h"
|
||||
#include "nearest.h"
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
@ -32,16 +32,16 @@ along with libimagequant. If not, see <http://www.gnu.org/licenses/>.
|
||||
#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;
|
||||
}
|
19
kmeans.h
Normal file
19
kmeans.h
Normal 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
|
168
libimagequant.c
168
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)) {
|
||||
|
@ -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;
|
||||
|
||||
|
60
mediancut.c
60
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) {
|
||||
|
@ -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);
|
||||
|
||||
|
@ -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);
|
||||
|
80
pam.c
80
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;
|
||||
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_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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
9
pam.h
9
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);
|
||||
|
||||
|
19
viter.h
19
viter.h
@ -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
|
Loading…
Reference in New Issue
Block a user