Compare commits
22 Commits
v2.8.0-go1
...
master
Author | SHA1 | Date |
---|---|---|
mappu | bdcc2f09a6 | |
mappu | 5971f07878 | |
mappu | 28784d66f3 | |
mappu | e6b7d94a14 | |
mappu | 4b569d0798 | |
mappu | 1c8bc73943 | |
mappu | ef45e0f75d | |
mappu | 7c7d9a661c | |
mappu | 4d88cab911 | |
mappu | c0809e2150 | |
mappu | e50632c68a | |
mappu | b28ae05d0d | |
mappu | 94eb53400a | |
mappu | 5d2ee53ed0 | |
mappu | 2d85b83a5e | |
mappu | d933ec9028 | |
mappu | 4f6914cd64 | |
mappu | 301cec7b07 | |
mappu | 5d5f02ba1c | |
mappu | a651fa3a20 | |
mappu | 29c239a810 | |
mappu | ed5a2bf1d3 |
|
@ -0,0 +1,2 @@
|
|||
cmd/gopngquant/gopngquant
|
||||
cmd/gopngquant/gopngquant.exe
|
|
@ -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
|
|
@ -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
4
blur.h
|
@ -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
|
||||
|
|
|
@ -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"
|
|
@ -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"
|
|
@ -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' )
|
|
@ -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])
|
||||
}
|
||||
}
|
||||
|
||||
|
|
Binary file not shown.
After Width: | Height: | Size: 12 KiB |
|
@ -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() {
|
||||
|
|
|
@ -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;
|
||||
}
|
|
@ -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
|
562
libimagequant.c
562
libimagequant.c
File diff suppressed because it is too large
Load Diff
|
@ -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;
|
||||
|
||||
|
|
206
mediancut.c
206
mediancut.c
|
@ -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) {
|
||||
|
|
|
@ -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
|
||||
|
|
31
mempool.c
31
mempool.c
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
35
nearest.c
35
nearest.c
|
@ -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;
|
||||
|
|
|
@ -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
129
pam.c
|
@ -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
25
pam.h
|
@ -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
107
viter.c
|
@ -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
19
viter.h
|
@ -1,19 +0,0 @@
|
|||
|
||||
#ifndef VITER_H
|
||||
#define VITER_H
|
||||
|
||||
// Spread memory touched by different threads at least 64B apart which I assume is the cache line size. This should avoid memory write contention.
|
||||
#define VITER_CACHE_LINE_GAP ((64+sizeof(viter_state)-1)/sizeof(viter_state))
|
||||
|
||||
typedef struct {
|
||||
double a, r, g, b, total;
|
||||
} viter_state;
|
||||
|
||||
typedef void (*viter_callback)(hist_item *item, float diff);
|
||||
|
||||
LIQ_PRIVATE void viter_init(const colormap *map, const unsigned int max_threads, viter_state state[]);
|
||||
LIQ_PRIVATE void viter_update_color(const f_pixel acolor, const float value, const colormap *map, unsigned int match, const unsigned int thread, viter_state average_color[]);
|
||||
LIQ_PRIVATE void viter_finalize(colormap *map, const unsigned int max_threads, const viter_state state[]);
|
||||
LIQ_PRIVATE double viter_do_iteration(histogram *hist, colormap *const map, viter_callback callback, const bool fast_palette);
|
||||
|
||||
#endif
|
Loading…
Reference in New Issue