Skip to content

Commit

Permalink
Merge branch 'master' into msvc
Browse files Browse the repository at this point in the history
* master:
  Bump
  Use error-adjusted weighed average in voronoi
  Linearize leaf searches
  Allow sqrt to execute out of order
  End sooner when on the wrong track
  Remove IE6-bug workaround
  Support OpenMP in clang
  Deprecated std error
  initialize likely_colormap_index
  • Loading branch information
kornelski committed Oct 18, 2020
2 parents e5d454b + b9ceb0c commit 1176b31
Show file tree
Hide file tree
Showing 10 changed files with 90 additions and 88 deletions.
8 changes: 4 additions & 4 deletions Cargo.toml
@@ -1,7 +1,7 @@
# libimagequant is a pure C library.
# Rust/Cargo is entirely optional. You can also use ./configure && make
[package]
version = "2.12.6"
version = "2.13.0"
authors = ["Kornel Lesiński <kornel@pngquant.org>"]
build = "rust-sys/build.rs"
categories = ["external-ffi-bindings"]
Expand All @@ -17,14 +17,14 @@ description = "Statically linked C part of imagequant library powering tools suc
edition = "2018"

[build-dependencies]
cc = "1.0.38"
cc = "1.0.58"

[dependencies]
rgb = "0.8.13"
rgb = "0.8.20"

[dependencies.openmp-sys]
optional = true
version = "0.1.5"
version = "1.0.0"

[features]
default = ["sse"]
Expand Down
10 changes: 5 additions & 5 deletions README.md
Expand Up @@ -343,15 +343,13 @@ Returns the value set by `liq_set_speed()`.

liq_error liq_set_min_opacity(liq_attr* attr, int min);

Alpha values higher than this will be rounded to opaque. This is a workaround for Internet Explorer 6, but because this browser is not used any more, this option is deprecated and will be removed. The default is `255` (no change).

Returns `LIQ_VALUE_OUT_OF_RANGE` if the value is outside the 0-255 range.
This was a workaround for Internet Explorer 6, but because this browser is not used any more, this option has been deprecated and removed.

----

int liq_get_min_opacity(liq_attr* attr);

Returns the value set by `liq_set_min_opacity()`.
This function has been deprecated.

----

Expand Down Expand Up @@ -684,7 +682,9 @@ The library needs to sort unique colors present in the image. Although the sorti

### OpenMP

The library will parallelize some operations if compiled with OpenMP.
The library can parallelize some operations if compiled with OpenMP.

GCC 9 or later is required for correct OpenMP support. Older compilers *will cause bugs* when OpenMP is enabled.

You must not increase number of maximum threads after `liq_image` has been created, as it allocates some per-thread buffers.

Expand Down
9 changes: 7 additions & 2 deletions kmeans.c
Expand Up @@ -74,17 +74,22 @@ LIQ_PRIVATE double kmeans_do_iteration(histogram *hist, colormap *const map, kme

double total_diff=0;
int j;
#if __GNUC__ >= 9 || __clang__
#pragma omp parallel for if (hist_size > 2000) \
schedule(static) default(none) shared(achv,average_color,callback,hist_size,map,n) reduction(+:total_diff)
#else
#pragma omp parallel for if (hist_size > 2000) \
schedule(static) default(none) shared(average_color,callback) reduction(+:total_diff)
#endif
for(j=0; j < hist_size; j++) {
float diff;
unsigned int match = nearest_search(n, &achv[j].acolor, achv[j].tmp.likely_colormap_index, &diff);
achv[j].tmp.likely_colormap_index = match;
total_diff += diff * achv[j].perceptual_weight;

kmeans_update_color(achv[j].acolor, achv[j].perceptual_weight, map, match, omp_get_thread_num(), average_color);

if (callback) callback(&achv[j], diff);

kmeans_update_color(achv[j].acolor, achv[j].perceptual_weight, map, match, omp_get_thread_num(), average_color);
}

nearest_free(n);
Expand Down
63 changes: 13 additions & 50 deletions libimagequant.c
Expand Up @@ -54,7 +54,6 @@ struct liq_attr {
void (*free)(void*);

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 kmeans_iterations, feedback_loop_trials;
Expand Down Expand Up @@ -87,7 +86,6 @@ struct liq_image {
liq_image_get_rgba_row_callback *row_callback;
void *row_callback_user_info;
liq_image *background;
float min_opaque_val;
f_pixel fixed_colors[256];
unsigned short fixed_colors_count;
bool free_pixels, free_rows, free_rows_internal;
Expand Down Expand Up @@ -140,7 +138,6 @@ struct liq_histogram {
bool had_image_added;
};

static void modify_alpha(liq_image *input_image, rgba_pixel *const row_pixels) LIQ_NONNULL;
static void contrast_maps(liq_image *image) LIQ_NONNULL;
static liq_error finalize_histogram(liq_histogram *input_hist, liq_attr *options, histogram **hist_output) LIQ_NONNULL;
static const rgba_pixel *liq_image_get_row_rgba(liq_image *input_image, unsigned int row) LIQ_NONNULL;
Expand Down Expand Up @@ -384,18 +381,12 @@ LIQ_EXPORT LIQ_NONNULL liq_error liq_set_output_gamma(liq_result* res, double ga

LIQ_EXPORT LIQ_NONNULL liq_error liq_set_min_opacity(liq_attr* attr, int min)
{
if (!CHECK_STRUCT_TYPE(attr, liq_attr)) return LIQ_INVALID_POINTER;
if (min < 0 || min > 255) return LIQ_VALUE_OUT_OF_RANGE;

attr->min_opaque_val = (double)min/255.0;
return LIQ_OK;
}

LIQ_EXPORT LIQ_NONNULL int liq_get_min_opacity(const liq_attr *attr)
{
if (!CHECK_STRUCT_TYPE(attr, liq_attr)) return -1;

return MIN(255.f, 256.f * attr->min_opaque_val);
return 0;
}

LIQ_EXPORT LIQ_NONNULL void liq_set_last_index_transparent(liq_attr* attr, int is_last)
Expand Down Expand Up @@ -510,7 +501,6 @@ LIQ_EXPORT liq_attr* liq_attr_create_with_allocator(void* (*custom_malloc)(size_
.malloc = custom_malloc,
.free = custom_free,
.max_colors = 256,
.min_opaque_val = 1, // whether preserve opaque colors for IE (1.0=no, does not affect alpha)
.last_index_transparent = false, // puts transparent color at last index. This is workaround for blu-ray subtitles.
.target_mse = 0,
.max_mse = MAX_DIFF,
Expand Down Expand Up @@ -592,10 +582,9 @@ static liq_image *liq_image_create_internal(const liq_attr *attr, rgba_pixel* ro
.rows = rows,
.row_callback = row_callback,
.row_callback_user_info = row_callback_user_info,
.min_opaque_val = attr->min_opaque_val,
};

if (!rows || attr->min_opaque_val < 1.f) {
if (!rows) {
img->temp_row = attr->malloc(sizeof(img->temp_row[0]) * LIQ_TEMP_ROW_WIDTH(width) * omp_get_max_threads());
if (!img->temp_row) return NULL;
}
Expand All @@ -606,10 +595,6 @@ static liq_image *liq_image_create_internal(const liq_attr *attr, rgba_pixel* ro
if (!liq_image_use_low_memory(img)) return NULL;
}

if (img->min_opaque_val < 1.f) {
verbose_print(attr, " Working around IE6 bug by making image less transparent...");
}

return img;
}

Expand Down Expand Up @@ -779,9 +764,7 @@ LIQ_NONNULL inline static bool liq_image_has_rgba_pixels(const liq_image *img)
LIQ_NONNULL inline static bool liq_image_can_use_rgba_rows(const liq_image *img)
{
assert(liq_image_has_rgba_pixels(img));

const bool iebug = img->min_opaque_val < 1.f;
return (img->rows && !iebug);
return img->rows;
}

LIQ_NONNULL static const rgba_pixel *liq_image_get_row_rgba(liq_image *img, unsigned int row)
Expand All @@ -798,7 +781,6 @@ LIQ_NONNULL static const rgba_pixel *liq_image_get_row_rgba(liq_image *img, unsi
liq_executing_user_callback(img->row_callback, (liq_color*)temp_row, row, img->width, img->row_callback_user_info);
}

if (img->min_opaque_val < 1.f) modify_alpha(img, temp_row);
return temp_row;
}

Expand Down Expand Up @@ -1279,8 +1261,13 @@ LIQ_NONNULL static float remap_to_palette(liq_image *const input_image, unsigned
kmeans_init(map, max_threads, average_color);

int row;
#if __GNUC__ >= 9 || __clang__
#pragma omp parallel for if (rows*cols > 3000) \
schedule(static) default(none) shared(acolormap,average_color,cols,input_image,map,n,output_pixels,rows,transparent_index) reduction(+:remapping_error)
#else
#pragma omp parallel for if (rows*cols > 3000) \
schedule(static) default(none) shared(acolormap) shared(average_color) reduction(+:remapping_error)
#endif
for(row = 0; row < rows; ++row) {
const f_pixel *const row_pixels = liq_image_get_row_f(input_image, row);
const f_pixel *const bg_pixels = input_image->background && acolormap[transparent_index].acolor.a < 1.f/256.f ? liq_image_get_row_f(input_image->background, row) : NULL;
Expand Down Expand Up @@ -1668,29 +1655,6 @@ LIQ_NONNULL static liq_error finalize_histogram(liq_histogram *input_hist, liq_a
return LIQ_OK;
}

LIQ_NONNULL static void modify_alpha(liq_image *input_image, rgba_pixel *const row_pixels)
{
/* IE6 makes colors with even slightest transparency completely transparent,
thus to improve situation in IE, make colors that are less than ~10% transparent
completely opaque */

const float min_opaque_val = input_image->min_opaque_val;
const float almost_opaque_val = min_opaque_val * 169.f/256.f;
const unsigned int almost_opaque_val_int = (min_opaque_val * 169.f/256.f)*255.f;

for(unsigned int col = 0; col < input_image->width; col++) {
const rgba_pixel px = row_pixels[col];

/* ie bug: to avoid visible step caused by forced opaqueness, linearily raise opaqueness of almost-opaque colors */
if (px.a >= almost_opaque_val_int) {
float al = px.a / 255.f;
al = almost_opaque_val + (al-almost_opaque_val) * (1.f-almost_opaque_val) / (min_opaque_val-almost_opaque_val);
al *= 256.f;
row_pixels[col].a = al >= 255.f ? 255 : al;
}
}
}

/**
Builds two maps:
importance_map - approximation of areas with high-frequency noise, except straight edges. 1=flat, 0=noisy.
Expand Down Expand Up @@ -1885,6 +1849,7 @@ static colormap *find_best_palette(histogram *hist, const liq_attr *options, con
double least_error = MAX_DIFF;
double target_mse_overshoot = feedback_loop_trials>0 ? 1.05 : 1.0;
const float total_trials = (float)(feedback_loop_trials>0?feedback_loop_trials:1);
int fails_in_a_row=0;

do {
colormap *newmap;
Expand Down Expand Up @@ -1929,15 +1894,13 @@ static colormap *find_best_palette(histogram *hist, const liq_attr *options, con
max_colors = MIN(newmap->colors+1, max_colors);

feedback_loop_trials -= 1; // asymptotic improvement could make it go on forever
fails_in_a_row = 0;
} else {
for(unsigned int j=0; j < hist->size; j++) {
hist->achv[j].adjusted_weight = (hist->achv[j].perceptual_weight + hist->achv[j].adjusted_weight)/2.0;
}

fails_in_a_row++;
target_mse_overshoot = 1.0;
feedback_loop_trials -= 6;

// if error is really bad, it's unlikely to improve, so end sooner
if (total_error > least_error*4) feedback_loop_trials -= 3;
feedback_loop_trials -= 5 + fails_in_a_row;
pam_freecolormap(newmap);
}

Expand Down
4 changes: 2 additions & 2 deletions libimagequant.h
Expand Up @@ -11,8 +11,8 @@
#define LIQ_EXPORT extern
#endif

#define LIQ_VERSION 21206
#define LIQ_VERSION_STRING "2.12.6"
#define LIQ_VERSION 21300
#define LIQ_VERSION_STRING "2.13.0"

#ifndef LIQ_PRIVATE
#if defined(__GNUC__) || defined (__llvm__)
Expand Down
2 changes: 1 addition & 1 deletion mediancut.c
Expand Up @@ -195,7 +195,7 @@ static double prepare_sort(struct box *b, hist_item achv[])

const unsigned int ind1 = b->ind;
const unsigned int colors = b->colors;
#if __GNUC__ >= 9
#if __GNUC__ >= 9 || __clang__
#pragma omp parallel for if (colors > 25000) \
schedule(static) default(none) shared(achv, channels, colors, ind1)
#else
Expand Down
53 changes: 44 additions & 9 deletions nearest.c
Expand Up @@ -19,15 +19,23 @@ typedef struct vp_sort_tmp {

typedef struct vp_search_tmp {
float distance;
float distance_squared;
unsigned int idx;
int exclude;
} vp_search_tmp;

struct leaf {
f_pixel color;
unsigned int idx;
};

typedef struct vp_node {
struct vp_node *near, *far;
f_pixel vantage_point;
float radius;
unsigned int idx;
float radius, radius_squared;
struct leaf *rest;
unsigned short idx;
unsigned short restcount;
} vp_node;

struct nearest_map {
Expand Down Expand Up @@ -79,6 +87,7 @@ static vp_node *vp_create_node(mempoolptr *m, vp_sort_tmp indexes[], int num_ind
.vantage_point = items[indexes[0].idx].acolor,
.idx = indexes[0].idx,
.radius = MAX_DIFF,
.radius_squared = MAX_DIFF,
};
return node;
}
Expand All @@ -99,9 +108,19 @@ static vp_node *vp_create_node(mempoolptr *m, vp_sort_tmp indexes[], int num_ind
.vantage_point = items[ref_idx].acolor,
.idx = ref_idx,
.radius = sqrtf(indexes[half_idx].distance_squared),
.radius_squared = indexes[half_idx].distance_squared,
};
node->near = vp_create_node(m, indexes, half_idx, items);
node->far = vp_create_node(m, &indexes[half_idx], num_indexes - half_idx, items);
if (num_indexes < 7) {
node->rest = mempool_alloc(m, sizeof(node->rest[0]) * num_indexes, 0);
node->restcount = num_indexes;
for(int i=0; i < num_indexes; i++) {
node->rest[i].idx = indexes[i].idx;
node->rest[i].color = items[indexes[i].idx].acolor;
}
} else {
node->near = vp_create_node(m, indexes, half_idx, items);
node->far = vp_create_node(m, &indexes[half_idx], num_indexes - half_idx, items);
}

return node;
}
Expand All @@ -126,6 +145,7 @@ LIQ_PRIVATE struct nearest_map *nearest_init(const colormap *map) {
for(unsigned int i=0; i < map->colors; i++) {
vp_search_tmp best = {
.distance = MAX_DIFF,
.distance_squared = MAX_DIFF,
.exclude = i,
};
vp_search_node(root, &map->palette[i].acolor, &best);
Expand All @@ -137,15 +157,29 @@ LIQ_PRIVATE struct nearest_map *nearest_init(const colormap *map) {

static void vp_search_node(const vp_node *node, const f_pixel *const needle, vp_search_tmp *const best_candidate) {
do {
const float distance = sqrtf(colordifference(node->vantage_point, *needle));
const float distance_squared = colordifference(node->vantage_point, *needle);
const float distance = sqrtf(distance_squared);

if (distance < best_candidate->distance && best_candidate->exclude != node->idx) {
if (distance_squared < best_candidate->distance_squared && best_candidate->exclude != node->idx) {
best_candidate->distance = distance;
best_candidate->distance_squared = distance_squared;
best_candidate->idx = node->idx;
}

if (node->restcount) {
for(int i=0; i < node->restcount; i++) {
const float distance_squared = colordifference(node->rest[i].color, *needle);
if (distance_squared < best_candidate->distance_squared && best_candidate->exclude != node->rest[i].idx) {
best_candidate->distance = sqrtf(distance_squared);
best_candidate->distance_squared = distance_squared;
best_candidate->idx = node->rest[i].idx;
}
}
return;
}

// Recurse towards most likely candidate first to narrow best candidate's distance as soon as possible
if (distance < node->radius) {
if (distance_squared < node->radius_squared) {
if (node->near) {
vp_search_node(node->near, needle, best_candidate);
}
Expand All @@ -155,7 +189,7 @@ static void vp_search_node(const vp_node *node, const f_pixel *const needle, vp_
if (node->far && distance >= node->radius - best_candidate->distance) {
node = node->far; // Fast tail recursion
} else {
break;
return;
}
} else {
if (node->far) {
Expand All @@ -164,7 +198,7 @@ static void vp_search_node(const vp_node *node, const f_pixel *const needle, vp_
if (node->near && distance <= node->radius + best_candidate->distance) {
node = node->near; // Fast tail recursion
} else {
break;
return;
}
}
} while(true);
Expand All @@ -179,6 +213,7 @@ LIQ_PRIVATE unsigned int nearest_search(const struct nearest_map *handle, const

vp_search_tmp best_candidate = {
.distance = sqrtf(guess_diff),
.distance_squared = guess_diff,
.idx = likely_colormap_index,
.exclude = -1,
};
Expand Down
3 changes: 3 additions & 0 deletions pam.c
Expand Up @@ -226,6 +226,9 @@ LIQ_PRIVATE histogram *pam_acolorhashtoacolorhist(const struct acolorhash_table
}
hist->size = j;
hist->total_perceptual_weight = total_weight;
for(unsigned int k=0; k < hist->size; k++) {
hist->achv[k].tmp.likely_colormap_index = 0;
}
if (!j) {
pam_freeacolorhist(hist);
return NULL;
Expand Down

0 comments on commit 1176b31

Please sign in to comment.