Compare commits

...

22 Commits

Author SHA1 Message Date
mappu bdcc2f09a6 doc: update README for v2.12.6 release 2020-07-25 12:56:51 +12:00
mappu 5971f07878 go.mod: we are v2 2020-07-25 12:56:37 +12:00
mappu 28784d66f3 vendor: update pngquant to 2.12.6 2020-07-25 12:55:55 +12:00
mappu e6b7d94a14 doc/README: add links to archived releases 2020-05-06 18:24:14 +12:00
mappu 4b569d0798 doc: track documentation image 2020-05-06 18:24:08 +12:00
mappu 1c8bc73943 doc: remove meta tags from readme 2018-12-31 17:55:15 +13:00
mappu ef45e0f75d doc: update readme 2018-12-31 16:37:02 +13:00
mappu 7c7d9a661c update bundled pngquant to v2.12.2 2018-12-31 16:35:00 +13:00
mappu 4d88cab911 hg2git: convert ignores/tags files 2018-12-31 16:33:17 +13:00
mappu c0809e2150 build: remove non-portable Cygwin makefile 2018-12-31 16:32:25 +13:00
mappu e50632c68a build: convert to Go Modules 2018-12-31 16:32:17 +13:00
mappu b28ae05d0d cflags: remove performance flags, to fix build with new Go whitelist 2018-12-31 16:32:07 +13:00
mappu 94eb53400a doc: top-level README.md 2018-12-31 16:24:03 +13:00
mappu 5d2ee53ed0 readme: add go-get details 2018-06-09 17:28:06 +12:00
mappu 2d85b83a5e cflags: add -ldl to fix build on Ubuntu 16.04 2017-03-05 11:50:13 +13:00
mappu d933ec9028 Added tag release-2.9go1.1 for changeset 2a7252984924 2017-03-03 18:41:40 +13:00
mappu 4f6914cd64 doc: update README 2017-03-03 18:41:33 +13:00
mappu 301cec7b07 gopngquant: fix issue with non-square input images 2017-03-03 18:39:14 +13:00
mappu 5d5f02ba1c doc: update readme 2017-03-03 18:25:21 +13:00
mappu a651fa3a20 update bundled libimagequant from 2.8.0 to 2.9.0 2017-03-03 18:24:02 +13:00
mappu 29c239a810 split up cgo CFLAGS into files with separate build tags 2017-03-03 18:16:40 +13:00
mappu ed5a2bf1d3 Added tag release-2.8go1.0 for changeset 795de1317b3a 2017-03-03 18:16:25 +13:00
26 changed files with 835 additions and 612 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$

70
README.md Normal file
View File

@ -0,0 +1,70 @@
# 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.
This binding was written by hand. The result is somewhat more idiomatic than an automated conversion, but some `defer foo.Release()` calls are required for memory management.
## 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:
-In string
Input filename
-Out string
Output filename
-Speed int
Speed (1 slowest, 10 fastest) (default 3)
-Version
```
## Building
This package can be installed via go get: `go get code.ivysaur.me/imagequant`
Import the package as `code.ivysaur.me/imagequant/v2` and compile via `go build`.
This is a CGO package and requires a C compiler installed. However, if you use `go install` then future invocations of `go build` do not require the C compiler to be present.
The `imagequant.go` file also declares a number of `CFLAGS` for GCC that allow the included libimagequant to build in a reasonably optimal way without using the upstream configure/make scripts.
## 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
2020-07-25 v2.12.6
- Update bundled libimagequant from 2.12.2 to 2.12.6
- Fix missing v2 in go.mod file causing failure to import in Modules mode
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
- [⬇️ go-imagequant-2.9go1.1-src.zip](https://git.ivysaur.me/attachments/381bbeac-c50f-4235-b61a-54785cf49b11) *(56.13 KiB)*
- [⬇️ gopngquant-2.9go1.1-win64.7z](https://git.ivysaur.me/attachments/ced44b97-4666-43cc-a578-44f4272be686) *(497.54 KiB)*
2016-11-24 v2.8.0-go1.0
- *Previously tagged as 2.8go1.0*
- Initial public release
- [⬇️ go-imagequant-2.8go1.0-src.zip](https://git.ivysaur.me/attachments/0f45207f-aeb8-4dd7-a62c-499a3f378f1a) *(55.40 KiB)*
- [⬇️ gopngquant-2.8go1.0-win64.7z](https://git.ivysaur.me/attachments/46229661-fa5d-418d-92ff-d6c4f72a5204) *(497.98 KiB)*
## See also
- Pngquant homepage https://pngquant.org/
- Pngquant source code https://github.com/pornel/pngquant
- Libimagequant source code https://github.com/ImageOptim/libimagequant

View File

@ -1,45 +0,0 @@
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.
This binding was written by hand. The result is somewhat more idiomatic than an automated conversion, but some `defer foo.Release()` calls are required for memory management.
Written in Golang
=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:
-In string
Input filename
-Out string
Output filename
-Speed int
Speed (1 slowest, 10 fastest) (default 3)
-Version`
=BUILDING=
The expected package path is `code.ivysaur.me/imagequant`. Build via `go build`.
This is a CGO package and requires a C compiler installed. However, if you use `go install` then future invocations of `go build` do not require the C compiler to be present.
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=
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=
2016-11-24 2.8go1.0
- Initial public release
=SEE ALSO=
- Pngquant homepage https://pngquant.org/
- Pngquant source code https://github.com/pornel/pngquant
- Libimagequant source code https://github.com/ImageOptim/libimagequant

4
blur.h
View File

@ -1,4 +1,8 @@
#ifndef BLUR_H
#define BLUR_H
LIQ_PRIVATE void liq_blur(unsigned char *src, unsigned char *tmp, unsigned char *dst, unsigned int width, unsigned int height, unsigned int size);
LIQ_PRIVATE void liq_max3(unsigned char *src, unsigned char *dst, unsigned int width, unsigned int height);
LIQ_PRIVATE void liq_min3(unsigned char *src, unsigned char *dst, unsigned int width, unsigned int height);
#endif

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

@ -24,7 +24,7 @@ import (
"image/png"
"os"
"code.ivysaur.me/imagequant"
"code.ivysaur.me/imagequant/v2"
)
func GoImageToRgba32(im image.Image) []byte {
@ -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])
}
}

BIN
doc/sample.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

3
go.mod Normal file
View File

@ -0,0 +1,3 @@
module code.ivysaur.me/imagequant/v2
go 1.13

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() {

98
kmeans.c Normal file
View File

@ -0,0 +1,98 @@
/*
** © 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;
#if __GNUC__ >= 9
#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(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 21206
#define LIQ_VERSION_STRING "2.12.6"
#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;
@ -198,7 +175,7 @@ typedef struct {
static int comparevariance(const void *ch1, const void *ch2)
{
return ((const channelvariance*)ch1)->variance > ((const channelvariance*)ch2)->variance ? -1 :
(((const channelvariance*)ch1)->variance < ((const channelvariance*)ch2)->variance ? 1 : 0);
(((const channelvariance*)ch1)->variance < ((const channelvariance*)ch2)->variance ? 1 : 0);
}
/** Finds which channels need to be sorted first and preproceses achv for fast sort */
@ -208,20 +185,29 @@ 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;
#if __GNUC__ >= 9
#pragma omp parallel for if (colors > 25000) \
schedule(static) default(none) shared(achv, channels, colors, ind1)
#else
#pragma omp parallel for if (colors > 25000) \
schedule(static) default(none) shared(achv, channels)
#endif
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) |
(unsigned int)((chans[channels[2].chan] + chans[channels[1].chan]/2.0 + chans[channels[3].chan]/4.0)*65535.0);
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);
}
const f_pixel median = get_median(b, achv);
@ -229,6 +215,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 +233,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 +266,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 +309,17 @@ 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
@ -331,89 +328,77 @@ 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;
{
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;
/*
** Main loop: split boxes until we have enough.
*/
while (boxes < newcolors) {
// first splits boxes that exceed quality limit (to have colors for things like odd green pixel),
// 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)
break; /* ran out of colors! */
unsigned int indx = bv[bi].ind;
unsigned int clrs = bv[bi].colors;
/*
Classic implementation tries to get even number of colors or pixels in each subdivision.
Here, instead of popularity I use (sqrt(popularity)*variance) metric.
Each subdivision balances number of pixels (popular colors) and low variance -
boxes can be large if they have similar colors. Later boxes with high variance
will be more likely to be split.
Median used as expected value gives much better results than mean.
** Main loop: split boxes until we have enough.
*/
while (boxes < newcolors) {
const double halfvar = prepare_sort(&bv[bi], achv);
double lowervar=0;
// first splits boxes that exceed quality limit (to have colors for things like odd green pixel),
// 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) {
break; /* ran out of colors! */
}
// hist_item_sort_halfvar sorts and sums lowervar at the same time
// returns item to break at …minus one, which does smell like an off-by-one error.
hist_item *break_p = hist_item_sort_halfvar(&achv[indx], clrs, &lowervar, halfvar);
unsigned int break_at = MIN(clrs-1, break_p - &achv[indx] + 1);
unsigned int indx = bv[bi].ind;
unsigned int clrs = bv[bi].colors;
/*
** Split the box.
*/
double sm = bv[bi].sum;
double lowersum = 0;
for(unsigned int i=0; i < break_at; i++) lowersum += achv[indx + i].adjusted_weight;
/*
Classic implementation tries to get even number of colors or pixels in each subdivision.
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]);
Here, instead of popularity I use (sqrt(popularity)*variance) metric.
Each subdivision balances number of pixels (popular colors) and low variance -
boxes can be large if they have similar colors. Later boxes with high variance
will be more likely to be split.
++boxes;
Median used as expected value gives much better results than mean.
*/
if (total_box_error_below_target(target_mse, bv, boxes, hist)) {
break;
const double halfvar = prepare_sort(&bv[bi], achv);
double lowervar=0;
// hist_item_sort_halfvar sorts and sums lowervar at the same time
// returns item to break at …minus one, which does smell like an off-by-one error.
hist_item *break_p = hist_item_sort_halfvar(&achv[indx], clrs, &lowervar, halfvar);
unsigned int break_at = MIN(clrs-1, break_p - &achv[indx] + 1);
/*
** Split the box.
*/
double sm = bv[bi].sum;
double lowersum = 0;
for(unsigned int i=0; i < break_at; i++) lowersum += achv[indx + i].adjusted_weight;
box_init(&bv[bi], achv, indx, break_at, lowersum);
box_init(&bv[boxes], achv, indx + break_at, clrs - break_at, sm - lowersum);
++boxes;
if (total_box_error_below_target(target_mse, bv, boxes, hist)) {
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 +425,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,2 +1,6 @@
#ifndef MEDIANCUT_H
#define MEDIANCUT_H
LIQ_PRIVATE colormap *mediancut(histogram *hist, unsigned int newcolors, const double target_mse, const double max_mse, void* (*malloc)(size_t), void (*free)(void*));
#endif

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

@ -2,7 +2,13 @@
// nearest.h
// pngquant
//
#ifndef NEAREST_H
#define NEAREST_H
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);
#endif

129
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,30 @@ 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;
for(unsigned int k=0; k < hist->size; k++) {
hist->achv[k].tmp.likely_colormap_index = 0;
}
if (!j) {
pam_freeacolorhist(hist);
return NULL;
}
return hist;
}

25
pam.h
View File

@ -16,6 +16,12 @@
#ifndef PAM_H
#define PAM_H
// accidental debug assertions make color search much slower,
// so force assertions off if there's no explicit setting
#if !defined(NDEBUG) && !defined(DEBUG)
#define NDEBUG
#endif
#include <math.h>
#include <assert.h>
#include <stdlib.h>
@ -62,6 +68,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 +97,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 +105,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 +118,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 +238,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 +250,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 +272,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