16 Commits

22 changed files with 759 additions and 571 deletions

2
.gitignore vendored Normal file
View File

@@ -0,0 +1,2 @@
cmd/gopngquant/gopngquant
cmd/gopngquant/gopngquant.exe

View File

@@ -1,4 +0,0 @@
mode:regex
^_dist/
\.exe$

View File

@@ -1,3 +1,5 @@
# imagequant
Go bindings for libimagequant
`libimagequant` is a library for lossy recompression of PNG images to reduce their filesize. It is used by the `pngquant` tool. This `go-imagequant` project is a set of bindings for libimagequant to enable its use from the Go programming language.
@@ -6,22 +8,27 @@ This binding was written by hand. The result is somewhat more idiomatic than an
Written in Golang
=USAGE=
## Usage
Usage example is provided by a sample utility `cmd/gopngquant` which mimics some functionality of the upstream `pngquant`.
The sample utility has the following options:
`Usage of gopngquant:
```
Usage of gopngquant:
-In string
Input filename
-Out string
Output filename
-Speed int
Speed (1 slowest, 10 fastest) (default 3)
-Version`
-Version
```
=BUILDING=
## Building
This package can be installed via go get: `go get code.ivysaur.me/imagequant`
[go-get]code.ivysaur.me/imagequant git https://git.ivysaur.me/code.ivysaur.me/imagequant.git[/go-get]
The expected package path is `code.ivysaur.me/imagequant`. Build via `go build`.
@@ -29,16 +36,29 @@ This is a CGO package and requires a C compiler installed. However, if you use `
The `imagequant.go` file also declares a number of `CFLAGS` for GCC that allow the included libimagequant (2.8 git-a425e83) to build in an optimal way without using the upstream configure/make scripts.
=LICENSE=
## License
I am releasing this binding under the ISC license, however, `libimagequant` itself is released under GPLv3-or-later and/or commercial licenses. You must comply with the terms of such a license when using this binding in a Go project.
=CHANGELOG=
## Changelog
2016-11-24 2.8go1.0
2018-12-31 v2.12.2-go1.2
- go-imagequant: Update bundled libimagequant from 2.9.0 to 2.12.2
- build: Switch to Go Modules
- build: Update bundled CFLAGS for new CGo whitelist (reduces performance)
- build: Remove nonportable Cygwin makefile
2017-03-03 v2.9.0-go1.1
- *Previously tagged as 2.9go1.1*
- go-imagequant: Update bundled libimagequant from 2.8.0 to 2.9.0
- go-imagequant: Separate `CGO_LDFLAGS` for Linux and Windows targets
- gopngquant: Fix an issue with non-square images
2016-11-24 v2.8.0-go1.0
- *Previously tagged as 2.8go1.0*
- Initial public release
=SEE ALSO=
## See also
- Pngquant homepage https://pngquant.org/
- Pngquant source code https://github.com/pornel/pngquant

9
cflags_linux.go Normal file
View File

@@ -0,0 +1,9 @@
//+build !windows
package imagequant
/*
#cgo CFLAGS: -O3 -fopenmp -fomit-frame-pointer -Wall -Wno-attributes -std=c99 -DNDEBUG -DUSE_SSE=1 -msse
#cgo LDFLAGS: -lm -fopenmp -ldl
*/
import "C"

9
cflags_windows.go Normal file
View File

@@ -0,0 +1,9 @@
//+build windows
package imagequant
/*
#cgo CFLAGS: -O3 -fno-math-errno -fopenmp -funroll-loops -fomit-frame-pointer -Wall -Wno-attributes -std=c99 -DNDEBUG -DUSE_SSE=1 -msse -fexcess-precision=fast
#cgo LDFLAGS: -fopenmp -static
*/
import "C"

View File

@@ -1,10 +0,0 @@
.PHONY: all clean
all: gopngquant.exe
clean:
rm ./gopngquant.exe || true
gopngquant.exe: main.go
( source ~/.cygwin-toolchain-switcher/use-toolchain-x86_64-w64-mingw32.sh && go build -a -ldflags '-s -w' )

View File

@@ -61,7 +61,7 @@ func Rgb8PaletteToGoImage(w, h int, rgb8data []byte, pal color.Palette) image.Im
for y := 0; y < h; y += 1 {
for x := 0; x < w; x += 1 {
ret.SetColorIndex(x, y, rgb8data[y*h+x])
ret.SetColorIndex(x, y, rgb8data[y*w+x])
}
}

1
go.mod Normal file
View File

@@ -0,0 +1 @@
module code.ivysaur.me/imagequant

View File

@@ -21,8 +21,6 @@ import (
)
/*
#cgo CFLAGS: -O3 -fno-math-errno -fopenmp -funroll-loops -fomit-frame-pointer -Wall -Wno-attributes -std=c99 -DNDEBUG -DUSE_SSE=1 -msse -fexcess-precision=fast
#cgo LDFLAGS: -fopenmp -static
#include "libimagequant.h"
const char* liqVersionString() {

93
kmeans.c Normal file
View File

@@ -0,0 +1,93 @@
/*
** © 2011-2016 by Kornel Lesiński.
** See COPYRIGHT file for license.
*/
#include "libimagequant.h"
#include "pam.h"
#include "kmeans.h"
#include "nearest.h"
#include <stdlib.h>
#include <string.h>
#ifdef _OPENMP
#include <omp.h>
#else
#define omp_get_max_threads() 1
#define omp_get_thread_num() 0
#endif
/*
* K-Means iteration: new palette color is computed from weighted average of colors that map to that palette entry.
*/
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])*(KMEANS_CACHE_LINE_GAP+map->colors)*max_threads);
}
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 * (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;
average_color[match].b += acolor.b * value;
average_color[match].total += value;
}
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 = (KMEANS_CACHE_LINE_GAP+map->colors) * t + i;
a += average_color[offset].a;
r += average_color[offset].r;
g += average_color[offset].g;
b += average_color[offset].b;
total += average_color[offset].total;
}
if (total && !map->palette[i].fixed) {
map->palette[i].acolor = (f_pixel){
.a = a / total,
.r = r / total,
.g = g / total,
.b = b / total,
};
map->palette[i].popularity = total;
}
}
}
LIQ_PRIVATE double kmeans_do_iteration(histogram *hist, colormap *const map, kmeans_callback callback)
{
const unsigned int max_threads = omp_get_max_threads();
LIQ_ARRAY(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;
double total_diff=0;
#pragma omp parallel for if (hist_size > 2000) \
schedule(static) default(none) shared(average_color,callback) reduction(+:total_diff)
for(int j=0; j < hist_size; j++) {
float diff;
unsigned int match = nearest_search(n, &achv[j].acolor, achv[j].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);
}
nearest_free(n);
kmeans_finalize(map, max_threads, average_color);
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

File diff suppressed because it is too large Load Diff

View File

@@ -13,8 +13,8 @@
#define LIQ_EXPORT extern
#endif
#define LIQ_VERSION 20800
#define LIQ_VERSION_STRING "2.8.0"
#define LIQ_VERSION 21200
#define LIQ_VERSION_STRING "2.12.2"
#ifndef LIQ_PRIVATE
#if defined(__GNUC__) || defined (__llvm__)
@@ -57,17 +57,29 @@ 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};
enum liq_ownership {
LIQ_OWN_ROWS=4,
LIQ_OWN_PIXELS=8,
LIQ_COPY_PIXELS=16,
};
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 liq_error liq_histogram_add_fixed_color(liq_histogram *hist, liq_color color, 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;
@@ -100,6 +112,8 @@ typedef void liq_image_get_rgba_row_callback(liq_color row_out[], int row, int w
LIQ_EXPORT LIQ_USERESULT liq_image *liq_image_create_custom(const liq_attr *attr, liq_image_get_rgba_row_callback *row_callback, void* user_info, int width, int height, double gamma);
LIQ_EXPORT liq_error liq_image_set_memory_ownership(liq_image *image, int ownership_flags) LIQ_NONNULL;
LIQ_EXPORT liq_error liq_image_set_background(liq_image *img, liq_image *background_image) LIQ_NONNULL;
LIQ_EXPORT liq_error liq_image_set_importance_map(liq_image *img, unsigned char buffer[], size_t buffer_size, enum liq_ownership memory_handling) LIQ_NONNULL;
LIQ_EXPORT liq_error liq_image_add_fixed_color(liq_image *img, liq_color color) LIQ_NONNULL;
LIQ_EXPORT LIQ_USERESULT int liq_image_get_width(const liq_image *img) LIQ_NONNULL;
LIQ_EXPORT LIQ_USERESULT int liq_image_get_height(const liq_image *img) LIQ_NONNULL;
@@ -117,10 +131,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;

View File

@@ -1,32 +1,9 @@
/*
** © 2009-2015 by Kornel Lesiński.
** © 2009-2018 by Kornel Lesiński.
** © 1989, 1991 by Jef Poskanzer.
** © 1997, 2000, 2002 by Greg Roelofs; based on an idea by Stefan Schneider.
**
** This file is part of libimagequant.
**
** libimagequant is free software: you can redistribute it and/or modify
** it under the terms of the GNU General Public License as published by
** the Free Software Foundation, either version 3 of the License, or
** (at your option) any later version.
**
** libimagequant is distributed in the hope that it will be useful,
** but WITHOUT ANY WARRANTY; without even the implied warranty of
** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
** GNU General Public License for more details.
**
** You should have received a copy of the GNU General Public License
** along with libimagequant. If not, see <http://www.gnu.org/licenses/>.
*/
/*
** Copyright (C) 1989, 1991 by Jef Poskanzer.
** Copyright (C) 1997, 2000, 2002 by Greg Roelofs; based on an idea by
** Stefan Schneider.
**
** Permission to use, copy, modify, and distribute this software and its
** documentation for any purpose and without fee is hereby granted, provided
** that the above copyright notice appear in all copies and that both that
** copyright notice and this permission notice appear in supporting
** documentation. This software is provided "as is" without express or
** implied warranty.
** See COPYRIGHT file for license.
*/
#include <stdlib.h>
@@ -38,7 +15,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 +40,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;
@@ -141,7 +118,7 @@ inline static unsigned int qsort_partition(hist_item *const base, const unsigned
}
/** quick select algorithm */
static void hist_item_sort_range(hist_item *base, unsigned int len, unsigned int sort_start)
static void hist_item_sort_range(hist_item base[], unsigned int len, unsigned int sort_start)
{
for(;;) {
const unsigned int l = qsort_partition(base, len), r = l+1;
@@ -157,7 +134,7 @@ static void hist_item_sort_range(hist_item *base, unsigned int len, unsigned int
}
/** sorts array to make sum of weights lower than halfvar one side, returns edge between <halfvar and >halfvar parts of the set */
static hist_item *hist_item_sort_halfvar(hist_item *base, unsigned int len, double *const lowervar, const double halfvar)
static hist_item *hist_item_sort_halfvar(hist_item base[], unsigned int len, double *const lowervar, const double halfvar)
{
do {
const unsigned int l = qsort_partition(base, len), r = l+1;
@@ -208,19 +185,23 @@ 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);
for(unsigned int i=0; i < b->colors; i++) {
const float *chans = (const float *)&achv[b->ind + i].acolor;
const unsigned int ind1 = b->ind;
const unsigned int colors = b->colors;
#pragma omp parallel for if (colors > 25000) \
schedule(static) default(none) shared(achv, channels)
for(unsigned int i=0; i < colors; i++) {
const float *chans = (const float *)&achv[ind1 + i].acolor;
// Only the first channel really matters. When trying median cut many times
// with different histogram weights, I don't want sort randomness to influence outcome.
achv[b->ind + i].tmp.sort_value = ((unsigned int)(chans[channels[0].chan]*65535.0)<<16) |
achv[ind1 + i].tmp.sort_value = ((unsigned int)(chans[channels[0].chan]*65535.0)<<16) |
(unsigned int)((chans[channels[2].chan] + chans[channels[1].chan]/2.0 + chans[channels[3].chan]/4.0)*65535.0);
}
@@ -229,6 +210,8 @@ static double prepare_sort(struct box *b, hist_item achv[])
// box will be split to make color_weight of each side even
const unsigned int ind = b->ind, end = ind+b->colors;
double totalvar = 0;
#pragma omp parallel for if (end - ind > 15000) \
schedule(static) default(shared) reduction(+:totalvar)
for(unsigned int j=ind; j < end; j++) totalvar += (achv[j].color_weight = color_weight(median, achv[j]));
return totalvar / 2.0;
}
@@ -245,13 +228,13 @@ 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]);
}
/*
** Find the best splittable box. -1 if no boxes are splittable.
*/
static int best_splittable_box(struct box* bv, unsigned int boxes, const double max_mse)
static int best_splittable_box(struct box bv[], unsigned int boxes, const double max_mse)
{
int bi=-1; double maxsum=0;
for(unsigned int i=0; i < boxes; i++) {
@@ -278,13 +261,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 set_colormap_from_boxes(colormap *map, struct box bv[], unsigned int boxes, hist_item *achv);
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 +304,19 @@ 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]);
#pragma omp task if (colors > 5000)
box->variance = box_variance(achv, box);
#pragma omp task if (colors > 8000)
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
@@ -331,21 +325,24 @@ static bool total_box_error_below_target(double target_mse, struct box bv[], uns
LIQ_PRIVATE colormap *mediancut(histogram *hist, unsigned int newcolors, const double target_mse, const double max_mse, void* (*malloc)(size_t), void (*free)(void*))
{
hist_item *achv = hist->achv;
struct box bv[newcolors];
LIQ_ARRAY(struct box, bv, newcolors);
unsigned int boxes = 1;
/*
** 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;
#pragma omp parallel
#pragma omp single
{
double sum = 0;
for(unsigned int i=0; i < hist->size; i++) {
sum += achv[i].adjusted_weight;
}
#pragma omp taskgroup
{
box_init(&bv[0], achv, 0, hist->size, sum);
}
unsigned int boxes = 1;
/*
** Main loop: split boxes until we have enough.
@@ -356,8 +353,9 @@ LIQ_PRIVATE colormap *mediancut(histogram *hist, unsigned int newcolors, const d
// later raises the limit to allow large smooth areas/gradients get colors.
const double current_max_mse = max_mse + (boxes/(double)newcolors)*16.0*max_mse;
const int bi = best_splittable_box(bv, boxes, current_max_mse);
if (bi < 0)
if (bi < 0) {
break; /* ran out of colors! */
}
unsigned int indx = bv[bi].ind;
unsigned int clrs = bv[bi].colors;
@@ -388,20 +386,11 @@ 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]);
#pragma omp taskgroup
{
box_init(&bv[bi], achv, indx, break_at, lowersum);
box_init(&bv[boxes], achv, indx + break_at, clrs - break_at, sm - lowersum);
}
++boxes;
@@ -409,11 +398,12 @@ LIQ_PRIVATE colormap *mediancut(histogram *hist, unsigned int newcolors, const d
break;
}
}
}
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,29 +430,30 @@ 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;
#pragma omp parallel for if (clrs > 25000) \
schedule(static) default(shared) reduction(+:a) reduction(+:r) reduction(+:g) reduction(+:b) reduction(+:sum)
for(unsigned int i = 0; i < clrs; i++) {
const f_pixel px = achv[i].acolor;
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) {

View File

@@ -1,20 +1,9 @@
/*
© 2011-2016 by Kornel Lesiński.
This file is part of libimagequant.
libimagequant is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
libimagequant is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with libimagequant. If not, see <http://www.gnu.org/licenses/>.
** © 2009-2017 by Kornel Lesiński.
** © 1989, 1991 by Jef Poskanzer.
** © 1997, 2000, 2002 by Greg Roelofs; based on an idea by Stefan Schneider.
**
** See COPYRIGHT file for license.
*/
#include "libimagequant.h"
@@ -32,7 +21,7 @@ struct mempool {
void (*free)(void*);
struct mempool *next;
};
LIQ_PRIVATE void* mempool_create(mempool *mptr, const unsigned int size, unsigned int max_size, void* (*malloc)(size_t), void (*free)(void*))
LIQ_PRIVATE void* mempool_create(mempoolptr *mptr, const unsigned int size, unsigned int max_size, void* (*malloc)(size_t), void (*free)(void*))
{
if (*mptr && ((*mptr)->used+size) <= (*mptr)->size) {
unsigned int prevused = (*mptr)->used;
@@ -40,7 +29,7 @@ LIQ_PRIVATE void* mempool_create(mempool *mptr, const unsigned int size, unsigne
return ((char*)(*mptr)) + prevused;
}
mempool old = *mptr;
mempoolptr old = *mptr;
if (!max_size) max_size = (1<<17);
max_size = size+ALIGN_MASK > max_size ? size+ALIGN_MASK : max_size;
@@ -60,7 +49,7 @@ LIQ_PRIVATE void* mempool_create(mempool *mptr, const unsigned int size, unsigne
return mempool_alloc(mptr, size, size);
}
LIQ_PRIVATE void* mempool_alloc(mempool *mptr, const unsigned int size, const unsigned int max_size)
LIQ_PRIVATE void* mempool_alloc(mempoolptr *mptr, const unsigned int size, const unsigned int max_size)
{
if (((*mptr)->used+size) <= (*mptr)->size) {
unsigned int prevused = (*mptr)->used;
@@ -71,10 +60,10 @@ LIQ_PRIVATE void* mempool_alloc(mempool *mptr, const unsigned int size, const un
return mempool_create(mptr, size, max_size, (*mptr)->malloc, (*mptr)->free);
}
LIQ_PRIVATE void mempool_destroy(mempool m)
LIQ_PRIVATE void mempool_destroy(mempoolptr m)
{
while (m) {
mempool next = m->next;
mempoolptr next = m->next;
m->free(m);
m = next;
}

View File

@@ -4,10 +4,10 @@
#include <stddef.h>
struct mempool;
typedef struct mempool *mempool;
typedef struct mempool *mempoolptr;
LIQ_PRIVATE void* mempool_create(mempool *mptr, const unsigned int size, unsigned int capacity, void* (*malloc)(size_t), void (*free)(void*));
LIQ_PRIVATE void* mempool_alloc(mempool *mptr, const unsigned int size, const unsigned int capacity);
LIQ_PRIVATE void mempool_destroy(mempool m);
LIQ_PRIVATE void* mempool_create(mempoolptr *mptr, const unsigned int size, unsigned int capacity, void* (*malloc)(size_t), void (*free)(void*));
LIQ_PRIVATE void* mempool_alloc(mempoolptr *mptr, const unsigned int size, const unsigned int capacity);
LIQ_PRIVATE void mempool_destroy(mempoolptr m);
#endif

View File

@@ -1,20 +1,9 @@
/*
© 2011-2015 by Kornel Lesiński.
This file is part of libimagequant.
libimagequant is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
libimagequant is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with libimagequant. If not, see <http://www.gnu.org/licenses/>.
** © 2009-2015 by Kornel Lesiński.
** © 1989, 1991 by Jef Poskanzer.
** © 1997, 2000, 2002 by Greg Roelofs; based on an idea by Stefan Schneider.
**
** See COPYRIGHT file for license.
*/
#include "libimagequant.h"
@@ -45,7 +34,7 @@ struct nearest_map {
vp_node *root;
const colormap_item *palette;
float nearest_other_color_dist[256];
mempool mempool;
mempoolptr mempool;
};
static void vp_search_node(const vp_node *node, const f_pixel *const needle, vp_search_tmp *const best_candidate);
@@ -56,7 +45,7 @@ static int vp_compare_distance(const void *ap, const void *bp) {
return a > b ? 1 : -1;
}
static void vp_sort_indexes_by_distance(const f_pixel vantage_point, vp_sort_tmp *indexes, int num_indexes, const colormap_item items[]) {
static void vp_sort_indexes_by_distance(const f_pixel vantage_point, vp_sort_tmp indexes[], int num_indexes, const colormap_item items[]) {
for(int i=0; i < num_indexes; i++) {
indexes[i].distance_squared = colordifference(vantage_point, items[indexes[i].idx].acolor);
}
@@ -66,7 +55,7 @@ static void vp_sort_indexes_by_distance(const f_pixel vantage_point, vp_sort_tmp
/*
* Usually it should pick farthest point, but picking most popular point seems to make search quicker anyway
*/
static int vp_find_best_vantage_point_index(vp_sort_tmp *indexes, int num_indexes, const colormap_item items[]) {
static int vp_find_best_vantage_point_index(vp_sort_tmp indexes[], int num_indexes, const colormap_item items[]) {
int best = 0;
float best_popularity = items[indexes[0].idx].popularity;
for(int i = 1; i < num_indexes; i++) {
@@ -78,7 +67,7 @@ static int vp_find_best_vantage_point_index(vp_sort_tmp *indexes, int num_indexe
return best;
}
static vp_node *vp_create_node(mempool *m, vp_sort_tmp *indexes, int num_indexes, const colormap_item items[]) {
static vp_node *vp_create_node(mempoolptr *m, vp_sort_tmp indexes[], int num_indexes, const colormap_item items[]) {
if (num_indexes <= 0) {
return NULL;
}
@@ -117,11 +106,11 @@ 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) {
mempool m = NULL;
LIQ_PRIVATE struct nearest_map *nearest_init(const colormap *map) {
mempoolptr 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);
vp_sort_tmp indexes[map->colors];
LIQ_ARRAY(vp_sort_tmp, indexes, map->colors);
for(unsigned int i=0; i < map->colors; i++) {
indexes[i].idx = i;

View File

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

126
pam.c
View File

@@ -1,16 +1,10 @@
/* pam.c - pam (portable alpha map) utility library
**
** Copyright (C) 1989, 1991 by Jef Poskanzer.
** Copyright (C) 1997, 2000, 2002 by Greg Roelofs; based on an idea by
** Stefan Schneider.
** © 2009-2016 by Kornel Lesinski.
** © 2009-2017 by Kornel Lesiński.
** © 1989, 1991 by Jef Poskanzer.
** © 1997, 2000, 2002 by Greg Roelofs; based on an idea by Stefan Schneider.
**
** Permission to use, copy, modify, and distribute this software and its
** documentation for any purpose and without fee is hereby granted, provided
** that the above copyright notice appear in all copies and that both that
** copyright notice and this permission notice appear in supporting
** documentation. This software is provided "as is" without express or
** implied warranty.
** See COPYRIGHT file for license.
*/
#include <stdlib.h>
@@ -22,28 +16,19 @@
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) {
float boost=1.0;
for(unsigned int col = 0; col < cols; ++col) {
if (importance_map) {
boost = 0.5f+ (double)*importance_map++/255.f;
}
unsigned int boost;
// RGBA color is casted to long for easier hasing/comparisons
union rgba_as_int px = {pixels[row][col]};
@@ -51,27 +36,50 @@ LIQ_PRIVATE bool pam_computeacolorhash(struct acolorhash_table *acht, const rgba
if (!px.rgba.a) {
// "dirty alpha" has different RGBA values that end up being the same fully transparent color
px.l=0; hash=0;
boost = 10;
boost = 2000;
if (importance_map) {
importance_map++;
}
} else {
// mask posterizes all 4 channels in one go
px.l = (px.l & posterize_mask) | ((px.l & posterize_high_mask) >> (8-ignorebits));
// fancier hashing algorithms didn't improve much
hash = px.l % hash_size;
if (importance_map) {
boost = *importance_map++;
} else {
boost = 255;
}
}
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, unsigned int 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 +87,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 +98,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 +110,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 +145,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;
}
@@ -162,7 +161,7 @@ LIQ_PRIVATE struct acolorhash_table *pam_allocacolorhash(unsigned int maxcolors,
const size_t estimated_colors = MIN(maxcolors, surface/(ignorebits + (surface > 512*512 ? 6 : 5)));
const size_t hash_size = estimated_colors < 66000 ? 6673 : (estimated_colors < 200000 ? 12011 : 24019);
mempool m = NULL;
mempoolptr m = NULL;
const size_t buckets_size = hash_size * sizeof(struct acolorhist_arr_head);
const size_t mempool_size = sizeof(struct acolorhash_table) + buckets_size + estimated_colors * sizeof(struct acolorhist_arr_item);
struct acolorhash_table *t = mempool_create(&m, sizeof(*t) + buckets_size, mempool_size, malloc, free);
@@ -177,10 +176,16 @@ 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)
{
if (entry->perceptual_weight == 0) {
return 0;
}
const float w = MIN(entry->perceptual_weight/128.f, max_perceptual_weight);
achv[*j].adjusted_weight = achv[*j].perceptual_weight = w;
achv[*j].acolor = rgba_to_f(gamma_lut, entry->color.rgba);
*j += 1;
return w;
}
LIQ_PRIVATE histogram *pam_acolorhashtoacolorhist(const struct acolorhash_table *acht, const double gamma, void* (*malloc)(size_t), void (*free)(void*))
@@ -203,22 +208,27 @@ LIQ_PRIVATE histogram *pam_acolorhashtoacolorhist(const struct acolorhash_table
float max_perceptual_weight = 0.1f * acht->cols * acht->rows;
double total_weight = 0;
for(unsigned int j=0, i=0; i < acht->hash_size; ++i) {
unsigned int j=0;
for(unsigned int 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);
}
}
}
}
hist->size = j;
hist->total_perceptual_weight = total_weight;
if (!j) {
pam_freeacolorhist(hist);
return NULL;
}
return hist;
}

19
pam.h
View File

@@ -62,6 +62,12 @@
# define SSE_ALIGN
#endif
#ifndef _MSC_VER
#define LIQ_ARRAY(type, var, count) type var[count]
#else
#define LIQ_ARRAY(type, var, count) type* var = (type*)_alloca(sizeof(type)*(count))
#endif
#if defined(__GNUC__) || defined (__llvm__)
#define ALWAYS_INLINE __attribute__((always_inline)) inline
#define NEVER_INLINE __attribute__ ((noinline))
@@ -85,7 +91,7 @@ typedef struct {
float a, r, g, b;
} SSE_ALIGN f_pixel;
static const double internal_gamma = 0.5499;
static const float internal_gamma = 0.5499f;
LIQ_PRIVATE void to_f_set_gamma(float gamma_lut[], const double gamma);
@@ -93,8 +99,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 +112,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 +232,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 {
@@ -238,7 +244,7 @@ typedef struct colormap {
struct acolorhist_arr_item {
union rgba_as_int color;
float perceptual_weight;
unsigned int perceptual_weight;
};
struct acolorhist_arr_head {
@@ -260,6 +266,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, unsigned int boost, union rgba_as_int px, unsigned int row, unsigned int rows);
LIQ_PRIVATE void pam_freeacolorhist(histogram *h);

107
viter.c
View File

@@ -1,107 +0,0 @@
/*
© 2011-2016 by Kornel Lesiński.
This file is part of libimagequant.
libimagequant is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
libimagequant is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with libimagequant. If not, see <http://www.gnu.org/licenses/>.
*/
#include "libimagequant.h"
#include "pam.h"
#include "viter.h"
#include "nearest.h"
#include <stdlib.h>
#include <string.h>
#ifdef _OPENMP
#include <omp.h>
#else
#define omp_get_max_threads() 1
#define omp_get_thread_num() 0
#endif
/*
* Voronoi 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[])
{
memset(average_color, 0, sizeof(average_color[0])*(VITER_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[])
{
match += thread * (VITER_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;
average_color[match].b += acolor.b * value;
average_color[match].total += value;
}
LIQ_PRIVATE void viter_finalize(colormap *map, const unsigned int max_threads, const viter_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;
a += average_color[offset].a;
r += average_color[offset].r;
g += average_color[offset].g;
b += average_color[offset].b;
total += average_color[offset].total;
}
if (total && !map->palette[i].fixed) {
map->palette[i].acolor = (f_pixel){
.a = a / total,
.r = r / total,
.g = g / total,
.b = b / total,
};
map->palette[i].popularity = total;
}
}
}
LIQ_PRIVATE double viter_do_iteration(histogram *hist, colormap *const map, viter_callback callback, const bool fast_palette)
{
const unsigned int max_threads = omp_get_max_threads();
viter_state average_color[(VITER_CACHE_LINE_GAP+map->colors) * max_threads];
viter_init(map, max_threads, average_color);
struct nearest_map *const n = nearest_init(map, fast_palette);
hist_item *const achv = hist->achv;
const int hist_size = hist->size;
double total_diff=0;
#pragma omp parallel for if (hist_size > 3000) \
schedule(static) default(none) shared(average_color,callback) reduction(+:total_diff)
for(int j=0; j < hist_size; j++) {
float diff;
unsigned int match = nearest_search(n, &achv[j].acolor, achv[j].tmp.likely_colormap_index, &diff);
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);
if (callback) callback(&achv[j], diff);
}
nearest_free(n);
viter_finalize(map, max_threads, average_color);
return total_diff / hist->total_perceptual_weight;
}

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