Script 'mail_helper' called by obssrc Hello community, here is the log from the commit of package libimagequant for openSUSE:Factory checked in at 2021-06-05 23:30:48 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ Comparing /work/SRC/openSUSE:Factory/libimagequant (Old) and /work/SRC/openSUSE:Factory/.libimagequant.new.1898 (New) ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Package is "libimagequant" Sat Jun 5 23:30:48 2021 rev:7 rq:896625 version:2.14.1 Changes: -------- --- /work/SRC/openSUSE:Factory/libimagequant/libimagequant.changes 2020-12-30 17:12:14.364832334 +0100 +++ /work/SRC/openSUSE:Factory/.libimagequant.new.1898/libimagequant.changes 2021-06-05 23:31:10.956401503 +0200 @@ -1,0 +2,7 @@ +Sat May 8 18:10:01 UTC 2021 - Dirk M??ller <[email protected]> + +- update to 2.14.1: + * improved Rust API + * quality improvements for remapping overlays over a background + +------------------------------------------------------------------- Old: ---- libimagequant-2.13.1.tar.gz New: ---- libimagequant-2.14.1.tar.gz ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ Other differences: ------------------ ++++++ libimagequant.spec ++++++ --- /var/tmp/diff_new_pack.eNJyQa/_old 2021-06-05 23:31:11.440402345 +0200 +++ /var/tmp/diff_new_pack.eNJyQa/_new 2021-06-05 23:31:11.444402351 +0200 @@ -1,7 +1,7 @@ # # spec file for package libimagequant # -# Copyright (c) 2020 SUSE LLC +# Copyright (c) 2021 SUSE LLC # # All modifications and additions to the file contributed by third parties # remain the property of their copyright owners, unless otherwise agreed @@ -19,7 +19,7 @@ %define sover 0 %define libname %{name}%{sover} Name: libimagequant -Version: 2.13.1 +Version: 2.14.1 Release: 0 Summary: Palette quantization library License: GPL-3.0-or-later ++++++ libimagequant-2.13.1.tar.gz -> libimagequant-2.14.1.tar.gz ++++++ diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/libimagequant-2.13.1/CHANGELOG new/libimagequant-2.14.1/CHANGELOG --- old/libimagequant-2.13.1/CHANGELOG 2020-11-22 14:18:54.000000000 +0100 +++ new/libimagequant-2.14.1/CHANGELOG 2021-02-28 19:12:44.000000000 +0100 @@ -1,3 +1,14 @@ +version 2.14 +------------ + - improved Rust API + - quality improvements for remapping overlays over a background + +version 2.13 +------------ + - support OpenMP in clang + - dropped old Internet Explorer workarounds + - speed and quality improvements + version 2.12 ------------ - new liq_histogram_add_fixed_color() diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/libimagequant-2.13.1/CMakeLists.txt new/libimagequant-2.14.1/CMakeLists.txt --- old/libimagequant-2.13.1/CMakeLists.txt 1970-01-01 01:00:00.000000000 +0100 +++ new/libimagequant-2.14.1/CMakeLists.txt 2021-02-28 19:12:44.000000000 +0100 @@ -0,0 +1,32 @@ +cmake_minimum_required(VERSION 2.6) + +project(imagequant C) + +option(BUILD_SHARED_LIBS "Build shared libraries" OFF) +option(BUILD_WITH_SSE "Use SSE" ON) + +if(BUILD_WITH_SSE) + add_definitions(-DUSE_SSE=1) +endif() + +find_package(OpenMP) +if(OPENMP_FOUND) + set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} ${OpenMP_C_FLAGS}") + set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} ${OpenMP_CXX_FLAGS}") +endif() + +include_directories(${CMAKE_SOURCE_DIR}) + +if(CMAKE_COMPILER_IS_GNUCC) + add_compile_options("-std=c99") +endif() + +add_library(imagequant SHARED + libimagequant.c + blur.c + mediancut.c + mempool.c + nearest.c + pam.c + kmeans.c +) \ No newline at end of file diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/libimagequant-2.13.1/Cargo.toml new/libimagequant-2.14.1/Cargo.toml --- old/libimagequant-2.13.1/Cargo.toml 2020-11-22 14:18:54.000000000 +0100 +++ new/libimagequant-2.14.1/Cargo.toml 2021-02-28 19:12:44.000000000 +0100 @@ -1,7 +1,7 @@ # libimagequant is a pure C library. # Rust/Cargo is entirely optional. You can also use ./configure && make [package] -version = "2.13.1" +version = "3.0.5+sys2.14.1" authors = ["Kornel Lesin??ski <[email protected]>"] build = "rust-sys/build.rs" categories = ["external-ffi-bindings"] @@ -21,10 +21,11 @@ [dependencies] rgb = "0.8.25" +bitflags = "1.2.1" [dependencies.openmp-sys] optional = true -version = "1.0.0" +version = "1.2.0-alpha.1" [features] default = ["sse"] diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/libimagequant-2.13.1/Makefile new/libimagequant-2.14.1/Makefile --- old/libimagequant-2.13.1/Makefile 2020-11-22 14:18:54.000000000 +0100 +++ new/libimagequant-2.14.1/Makefile 2021-02-28 19:12:44.000000000 +0100 @@ -93,7 +93,7 @@ cargo test example: example.c lodepng.h lodepng.c $(STATICLIB) - $(CC) -g $(CFLAGS) -Wall example.c $(STATICLIB) -o example + $(CC) -g $(CFLAGS) -Wall example.c $(STATICLIB) -o example -lm lodepng.h: curl -o lodepng.h -L https://raw.githubusercontent.com/lvandeve/lodepng/master/lodepng.h @@ -104,6 +104,7 @@ clean: rm -f $(OBJS) $(SHAREDOBJS) $(SHAREDLIBVER) $(SHAREDLIB) $(STATICLIB) $(TARFILE) $(DLL) '$(DLLIMP)' '$(DLLDEF)' rm -f $(JAVAHEADERS) $(JAVACLASSES) $(JNILIB) example + rm -rf target rust-api/target rust-sys/target distclean: clean rm -f config.mk diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/libimagequant-2.13.1/README.md new/libimagequant-2.14.1/README.md --- old/libimagequant-2.13.1/README.md 2020-11-22 14:18:54.000000000 +0100 +++ new/libimagequant-2.14.1/README.md 2021-02-28 19:12:44.000000000 +0100 @@ -49,10 +49,23 @@ ### Compiling on Windows/Visual Studio -The library can be compiled with any C compiler that has at least basic support for C99 (GCC, clang, ICC, C++ Builder, even Tiny C Compiler), but Visual Studio 2012 and older are not up to date with the 1999 C standard. There are 2 options for using `libimagequant` on Windows: +The library can be compiled with any C compiler that has at least basic support for C99 (GCC, clang, ICC, C++ Builder, even Tiny C Compiler), but Visual Studio 2012 and older are not up to date with the 1999 C standard. Use Visual Studio **2015** and the [MSVC-compatible branch of the library](https://github.com/ImageOptim/libimagequant/tree/msvc). - * Use Visual Studio **2015** and an [MSVC-compatible branch of the library](https://github.com/ImageOptim/libimagequant/tree/msvc) - * Or use GCC from [MinGW](http://www.mingw.org) or [MSYS2](http://www.msys2.org/). Use GCC to build `libimagequant.a` (using the instructions above for Unix) and add it along with `libgcc.a` (shipped with the MinGW compiler) to your VC project. +To build on Windows, install CMake and use it to generate a makefile/project for your build system. + +Build instructions + + mkdir build + cd build + cmake .. + cmake --build . + +To generate a 64-bit Visual Studio project instead: + + mkdir build + cd build + cmake -G "Visual Studio 15 2017 Win64" .. + cmake --build . ### Building as shared library diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/libimagequant-2.13.1/kmeans.c new/libimagequant-2.14.1/kmeans.c --- old/libimagequant-2.13.1/kmeans.c 2020-11-22 14:18:54.000000000 +0100 +++ new/libimagequant-2.14.1/kmeans.c 2021-02-28 19:12:44.000000000 +0100 @@ -61,11 +61,12 @@ .b = b / total, }; } else { - unsigned int r = (i + rand()%7); - map->palette[i].acolor.a = map->palette[r%map->colors].acolor.a; - map->palette[i].acolor.r = map->palette[r%map->colors].acolor.r; - map->palette[i].acolor.g = map->palette[(r+1)%map->colors].acolor.g; - map->palette[i].acolor.b = map->palette[(r+2)%map->colors].acolor.b; + // if a color is useless, make a new one + // (it was supposed to be random, but Android NDK has problematic stdlib headers) + map->palette[i].acolor.a = map->palette[(i+1)%map->colors].acolor.a; + map->palette[i].acolor.r = map->palette[(i+2)%map->colors].acolor.r; + map->palette[i].acolor.g = map->palette[(i+3)%map->colors].acolor.g; + map->palette[i].acolor.b = map->palette[(i+4)%map->colors].acolor.b; } } } diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/libimagequant-2.13.1/libimagequant.c new/libimagequant-2.14.1/libimagequant.c --- old/libimagequant-2.13.1/libimagequant.c 2020-11-22 14:18:54.000000000 +0100 +++ new/libimagequant-2.14.1/libimagequant.c 2021-02-28 19:12:44.000000000 +0100 @@ -626,6 +626,7 @@ } LIQ_NONNULL static void liq_image_free_maps(liq_image *input_image); +LIQ_NONNULL static void liq_image_free_dither_map(liq_image *input_image); LIQ_NONNULL static void liq_image_free_importance_map(liq_image *input_image); LIQ_EXPORT LIQ_NONNULL liq_error liq_image_set_importance_map(liq_image *img, unsigned char importance_map[], size_t buffer_size, enum liq_ownership ownership) { @@ -671,7 +672,7 @@ } img->background = background; - liq_image_free_maps(img); // Force them to be re-analyzed with the background + liq_image_free_dither_map(img); // Force it to be re-analyzed with the background return LIQ_OK; } @@ -848,7 +849,16 @@ typedef void free_func(void*); -LIQ_NONNULL static free_func *get_default_free_func(liq_image *img) +LIQ_NONNULL static free_func *get_default_image_free_func(liq_image *img) +{ + // When default allocator is used then user-supplied pointers must be freed with free() + if (img->free != liq_aligned_free) { + return img->free; + } + return free; +} + +LIQ_NONNULL static free_func *get_default_rows_free_func(liq_image *img) { // When default allocator is used then user-supplied pointers must be freed with free() if (img->free_rows_internal || img->free != liq_aligned_free) { @@ -860,12 +870,12 @@ LIQ_NONNULL static void liq_image_free_rgba_source(liq_image *input_image) { if (input_image->free_pixels && input_image->pixels) { - get_default_free_func(input_image)(input_image->pixels); + get_default_image_free_func(input_image)(input_image->pixels); input_image->pixels = NULL; } if (input_image->free_rows && input_image->rows) { - get_default_free_func(input_image)(input_image->rows); + get_default_rows_free_func(input_image)(input_image->rows); input_image->rows = NULL; } } @@ -884,7 +894,10 @@ input_image->free(input_image->edges); input_image->edges = NULL; } + liq_image_free_dither_map(input_image); +} +LIQ_NONNULL static void liq_image_free_dither_map(liq_image *input_image) { if (input_image->dither_map) { input_image->free(input_image->dither_map); input_image->dither_map = NULL; @@ -968,6 +981,7 @@ } liq_error err = liq_histogram_add_image(hist, attr, img); if (LIQ_OK != err) { + liq_histogram_destroy(hist); return err; } @@ -1251,7 +1265,12 @@ const colormap_item *acolormap = map->palette; struct nearest_map *const n = nearest_init(map); - const int transparent_index = input_image->background ? nearest_search(n, &(f_pixel){0,0,0,0}, 0, NULL) : 0; + liq_image *background = input_image->background; + const int transparent_index = background ? nearest_search(n, &(f_pixel){0,0,0,0}, 0, NULL) : -1; + if (background && acolormap[transparent_index].acolor.a > 1.f/256.f) { + // palette unsuitable for using the bg + background = NULL; + } const unsigned int max_threads = omp_get_max_threads(); @@ -1260,26 +1279,29 @@ #if __GNUC__ >= 9 || __clang__ #pragma omp parallel for if (rows*cols > 3000) \ - schedule(static) default(none) shared(acolormap,average_color,cols,input_image,map,n,output_pixels,rows,transparent_index) reduction(+:remapping_error) -#else - #pragma omp parallel for if (rows*cols > 3000) \ - schedule(static) default(none) shared(acolormap) shared(average_color) reduction(+:remapping_error) + schedule(static) default(none) shared(background,acolormap,average_color,cols,input_image,map,n,output_pixels,rows,transparent_index) reduction(+:remapping_error) #endif for(int row = 0; row < rows; ++row) { const f_pixel *const row_pixels = liq_image_get_row_f(input_image, row); - const f_pixel *const bg_pixels = input_image->background && acolormap[transparent_index].acolor.a < 1.f/256.f ? liq_image_get_row_f(input_image->background, row) : NULL; + const f_pixel *const bg_pixels = background && acolormap[transparent_index].acolor.a < 1.f/256.f ? liq_image_get_row_f(background, row) : NULL; unsigned int last_match=0; for(unsigned int col = 0; col < cols; ++col) { float diff; last_match = nearest_search(n, &row_pixels[col], last_match, &diff); - if (bg_pixels && colordifference(bg_pixels[col], acolormap[last_match].acolor) <= diff) { - last_match = transparent_index; + if (bg_pixels) { + float bg_diff = colordifference(bg_pixels[col], acolormap[last_match].acolor); + if (bg_diff <= diff) { + diff = bg_diff; + last_match = transparent_index; + } } output_pixels[row][col] = last_match; remapping_error += diff; - kmeans_update_color(row_pixels[col], 1.0, map, last_match, omp_get_thread_num(), average_color); + if (last_match != transparent_index) { + kmeans_update_color(row_pixels[col], 1.0, map, last_match, omp_get_thread_num(), average_color); + } } } @@ -1361,7 +1383,12 @@ bool ok = true; struct nearest_map *const n = nearest_init(map); - const int transparent_index = input_image->background ? nearest_search(n, &(f_pixel){0,0,0,0}, 0, NULL) : 0; + liq_image *background = input_image->background; + const int transparent_index = background ? nearest_search(n, &(f_pixel){0,0,0,0}, 0, NULL) : -1; + if (background && acolormap[transparent_index].acolor.a > 1.f/256.f) { + // palette unsuitable for using the bg + background = NULL; + } // response to this value is non-linear and without it any value < 0.8 would give almost no dithering float base_dithering_level = quant->dither_level; @@ -1384,7 +1411,8 @@ int col = (fs_direction > 0) ? 0 : (cols - 1); const f_pixel *const row_pixels = liq_image_get_row_f(input_image, row); - const f_pixel *const bg_pixels = input_image->background && acolormap[transparent_index].acolor.a < 1.f/256.f ? liq_image_get_row_f(input_image->background, row) : NULL; + const f_pixel *const bg_pixels = background && acolormap[transparent_index].acolor.a < 1.f/256.f ? liq_image_get_row_f(background, row) : NULL; + int undithered_bg_used = 0; do { float dither_level = base_dithering_level; @@ -1395,16 +1423,42 @@ const f_pixel spx = get_dithered_pixel(dither_level, max_dither_error, thiserr[col + 1], row_pixels[col]); const unsigned int guessed_match = output_image_is_remapped ? output_pixels[row][col] : last_match; - float diff; - last_match = nearest_search(n, &spx, guessed_match, &diff); + float dither_diff; + last_match = nearest_search(n, &spx, guessed_match, &dither_diff); f_pixel output_px = acolormap[last_match].acolor; - if (bg_pixels && colordifference(bg_pixels[col], output_px) <= diff) { - output_px = bg_pixels[col]; - output_pixels[row][col] = transparent_index; - } else { - output_pixels[row][col] = last_match; + // this is for animgifs + if (bg_pixels) { + // if the background makes better match *with* dithering, it's a definitive win + float bg_for_dither_diff = colordifference(spx, bg_pixels[col]); + if (bg_for_dither_diff <= dither_diff) { + output_px = bg_pixels[col]; + last_match = transparent_index; + } else if (undithered_bg_used > 1) { + // the undithered fallback can cause artifacts when too many undithered pixels accumulate a big dithering error + // so periodically ignore undithered fallback to prevent that + undithered_bg_used = 0; + } else { + // if dithering is not applied, there's a high risk of creating artifacts (flat areas, error accumulating badly), + // OTOH poor dithering disturbs static backgrounds and creates oscilalting frames that break backgrounds + // back and forth in two differently bad ways + float max_diff = colordifference(row_pixels[col], bg_pixels[col]); + float dithered_diff = colordifference(row_pixels[col], output_px); + // if dithering is worse than natural difference between frames + // (this rule dithers moving areas, but does not dither static areas) + if (dithered_diff > max_diff) { + // then see if an undithered color is closer to the ideal + float undithered_diff = colordifference(row_pixels[col], acolormap[guessed_match].acolor); + if (undithered_diff < max_diff) { + undithered_bg_used++; + output_px = acolormap[guessed_match].acolor; + last_match = guessed_match; + } + } + } } + output_pixels[row][col] = last_match; + f_pixel err = { .r = (spx.r - output_px.r), .g = (spx.g - output_px.g), diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/libimagequant-2.13.1/libimagequant.h new/libimagequant-2.14.1/libimagequant.h --- old/libimagequant-2.13.1/libimagequant.h 2020-11-22 14:18:54.000000000 +0100 +++ new/libimagequant-2.14.1/libimagequant.h 2021-02-28 19:12:44.000000000 +0100 @@ -13,8 +13,8 @@ #define LIQ_EXPORT extern #endif -#define LIQ_VERSION 21301 -#define LIQ_VERSION_STRING "2.13.1" +#define LIQ_VERSION 21401 +#define LIQ_VERSION_STRING "2.14.1" #ifndef LIQ_PRIVATE #if defined(__GNUC__) || defined (__llvm__) diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/libimagequant-2.13.1/pam.c new/libimagequant-2.14.1/pam.c --- old/libimagequant-2.13.1/pam.c 2020-11-22 14:18:54.000000000 +0100 +++ new/libimagequant-2.14.1/pam.c 2021-02-28 19:12:44.000000000 +0100 @@ -178,7 +178,7 @@ 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) { + if (entry->perceptual_weight == 0 && *j > 0) { return 0; } const float w = MIN(entry->perceptual_weight/128.f, max_perceptual_weight); diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/libimagequant-2.13.1/pam.h new/libimagequant-2.14.1/pam.h --- old/libimagequant-2.13.1/pam.h 2020-11-22 14:18:54.000000000 +0100 +++ new/libimagequant-2.14.1/pam.h 2021-02-28 19:12:44.000000000 +0100 @@ -182,8 +182,17 @@ inline static float colordifference(f_pixel px, f_pixel py) { #if USE_SSE +#ifdef _MSC_VER + /* In MSVC we cannot use the align attribute in parameters. + * This is used a lot, so we just use an unaligned load. + * Also the compiler incorrectly inlines vpx and vpy without + * the volatile when optimization is applied for x86_64. */ + const volatile __m128 vpx = _mm_loadu_ps((const float*)&px); + const volatile __m128 vpy = _mm_loadu_ps((const float*)&py); +#else const __m128 vpx = _mm_load_ps((const float*)&px); const __m128 vpy = _mm_load_ps((const float*)&py); +#endif // y.a - x.a __m128 alphas = _mm_sub_ss(vpy, vpx); diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/libimagequant-2.13.1/rust-api/Cargo.toml new/libimagequant-2.14.1/rust-api/Cargo.toml --- old/libimagequant-2.13.1/rust-api/Cargo.toml 2020-11-22 14:18:54.000000000 +0100 +++ new/libimagequant-2.14.1/rust-api/Cargo.toml 2021-02-28 19:12:44.000000000 +0100 @@ -10,12 +10,13 @@ name = "imagequant" readme = "README.md" repository = "https://github.com/ImageOptim/libimagequant" -version = "2.13.0" +version = "3.0.4-alpha.2" edition = "2018" [dependencies] -imagequant-sys = { version = "2.13.0", path = "../" } +imagequant-sys = { version = "3.0.4-alpha.2", path = "../" } libc = "0.2.60" +rgb = "0.8.25" [features] default = ["sse"] diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/libimagequant-2.13.1/rust-api/examples/basic.rs new/libimagequant-2.14.1/rust-api/examples/basic.rs --- old/libimagequant-2.13.1/rust-api/examples/basic.rs 2020-11-22 14:18:54.000000000 +0100 +++ new/libimagequant-2.14.1/rust-api/examples/basic.rs 2021-02-28 19:12:44.000000000 +0100 @@ -4,7 +4,7 @@ // Image loading/saving is outside scope of this library let width = 10usize; let height = 10usize; - let fakebitmap = vec![255u8; 4 * width * height]; + let fakebitmap = vec![imagequant::RGBA {r:0, g:0, b:0, a:0}; width * height]; // http://pngquant.org/lib/ diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/libimagequant-2.13.1/rust-api/src/lib.rs new/libimagequant-2.14.1/rust-api/src/lib.rs --- old/libimagequant-2.13.1/rust-api/src/lib.rs 2020-11-22 14:18:54.000000000 +0100 +++ new/libimagequant-2.14.1/rust-api/src/lib.rs 2021-02-28 19:12:44.000000000 +0100 @@ -8,16 +8,19 @@ #![doc(html_logo_url = "https://pngquant.org/pngquant-logo.png")] #![warn(missing_docs)] -extern crate imagequant_sys as ffi; - pub use crate::ffi::liq_error; pub use crate::ffi::liq_error::*; + +use imagequant_sys as ffi; use std::fmt; -use std::marker; +use std::marker::PhantomData; use std::mem; +use std::mem::MaybeUninit; use std::os::raw::c_int; use std::ptr; +pub use rgb::RGBA8 as RGBA; + /// 8-bit RGBA. This is the only color format used by the library. pub type Color = ffi::liq_color; @@ -26,7 +29,7 @@ /// Used if you're building histogram manually. Otherwise see `add_image()` pub type HistogramEntry = ffi::liq_histogram_entry; -/// Settings for the conversion proces. Start here. +/// Settings for the conversion process. Start here. pub struct Attributes { handle: *mut ffi::liq_attr, } @@ -35,7 +38,7 @@ pub struct Image<'a> { handle: *mut ffi::liq_image, /// Holds row pointers for images with stride - _marker: marker::PhantomData<&'a [u8]>, + _marker: PhantomData<&'a [u8]>, } /// Palette inside. @@ -50,6 +53,7 @@ } impl Drop for Attributes { + #[inline] fn drop(&mut self) { unsafe { if !self.handle.is_null() { @@ -60,6 +64,7 @@ } impl<'a> Drop for Image<'a> { + #[inline] fn drop(&mut self) { unsafe { ffi::liq_image_destroy(&mut *self.handle); @@ -68,6 +73,7 @@ } impl Drop for QuantizationResult { + #[inline] fn drop(&mut self) { unsafe { ffi::liq_result_destroy(&mut *self.handle); @@ -76,6 +82,7 @@ } impl<'a> Drop for Histogram<'a> { + #[inline] fn drop(&mut self) { unsafe { ffi::liq_histogram_destroy(&mut *self.handle); @@ -84,12 +91,14 @@ } impl Clone for Attributes { + #[inline] fn clone(&self) -> Attributes { unsafe { Attributes { handle: ffi::liq_attr_copy(&*self.handle) } } } } impl Default for Attributes { + #[inline(always)] fn default() -> Attributes { Attributes::new() } @@ -99,6 +108,8 @@ /// New handle for library configuration /// /// See also `new_image()` + #[inline] + #[must_use] pub fn new() -> Self { let handle = unsafe { ffi::liq_attr_create() }; assert!(!handle.is_null(), "SSE-capable CPU is required for this build."); @@ -106,6 +117,7 @@ } /// It's better to use `set_quality()` + #[inline] pub fn set_max_colors(&mut self, value: i32) -> liq_error { unsafe { ffi::liq_set_max_colors(&mut *self.handle, value) } } @@ -113,11 +125,13 @@ /// Number of least significant bits to ignore. /// /// Useful for generating palettes for VGA, 15-bit textures, or other retro platforms. + #[inline] pub fn set_min_posterization(&mut self, value: i32) -> liq_error { unsafe { ffi::liq_set_min_posterization(&mut *self.handle, value) } } /// Returns number of bits of precision truncated + #[inline] pub fn min_posterization(&mut self) -> i32 { unsafe { ffi::liq_get_min_posterization(&*self.handle) } } @@ -127,11 +141,13 @@ /// If minimum quality can't be met, quantization will fail. /// /// Default is min 0, max 100. + #[inline] pub fn set_quality(&mut self, min: u32, max: u32) -> liq_error { unsafe { ffi::liq_set_quality(&mut *self.handle, min as c_int, max as c_int) } } /// Reads values set with `set_quality` + #[inline] pub fn quality(&mut self) -> (u32, u32) { unsafe { (ffi::liq_get_min_quality(&*self.handle) as u32, @@ -143,6 +159,7 @@ /// /// Faster speeds generate images of lower quality, but may be useful /// for real-time generation of images. + #[inline] pub fn set_speed(&mut self, value: i32) -> liq_error { unsafe { ffi::liq_set_speed(&mut *self.handle, value) } } @@ -150,16 +167,21 @@ /// Move transparent color to the last entry in the palette /// /// This is less efficient for PNG, but required by some broken software + #[inline] pub fn set_last_index_transparent(&mut self, value: bool) { unsafe { ffi::liq_set_last_index_transparent(&mut *self.handle, value as c_int) } } /// Return currently set speed/quality trade-off setting + #[inline(always)] + #[must_use] pub fn speed(&mut self) -> i32 { unsafe { ffi::liq_get_speed(&*self.handle) } } /// Return max number of colors set + #[inline(always)] + #[must_use] pub fn max_colors(&mut self) -> i32 { unsafe { ffi::liq_get_max_colors(&*self.handle) } } @@ -167,18 +189,28 @@ /// Describe dimensions of a slice of RGBA pixels /// /// Use 0.0 for gamma if the image is sRGB (most images are). - pub fn new_image<'a, RGBA: Copy>(&self, bitmap: &'a [RGBA], width: usize, height: usize, gamma: f64) -> Result<Image<'a>, liq_error> { + #[inline] + pub fn new_image<'a>(&self, bitmap: &'a [RGBA], width: usize, height: usize, gamma: f64) -> Result<Image<'a>, liq_error> { Image::new(self, bitmap, width, height, gamma) } /// Stride is in pixels. Allows defining regions of larger images or images with padding without copying. - pub fn new_image_stride<'a, RGBA: Copy>(&self, bitmap: &'a [RGBA], width: usize, height: usize, stride: usize, gamma: f64) -> Result<Image<'a>, liq_error> { + #[inline] + pub fn new_image_stride<'a>(&self, bitmap: &'a [RGBA], width: usize, height: usize, stride: usize, gamma: f64) -> Result<Image<'a>, liq_error> { Image::new_stride(self, bitmap, width, height, stride, gamma) } + /// Like `new_image_stride`, but makes a copy of the pixels + #[inline] + pub fn new_image_stride_copy(&self, bitmap: &[RGBA], width: usize, height: usize, stride: usize, gamma: f64) -> Result<Image<'static>, liq_error> { + Image::new_stride_copy(self, bitmap, width, height, stride, gamma) + } + /// Create new histogram /// /// Use to make one palette suitable for many images + #[inline(always)] + #[must_use] pub fn new_histogram(&self) -> Histogram<'_> { Histogram::new(&self) } @@ -196,6 +228,8 @@ } /// Start here: creates new handle for library configuration +#[inline(always)] +#[must_use] pub fn new() -> Attributes { Attributes::new() } @@ -204,6 +238,8 @@ /// Creates histogram object that will be used to collect color statistics from multiple images. /// /// All options should be set on `attr` before the histogram object is created. Options changed later may not have effect. + #[inline] + #[must_use] pub fn new(attr: &'a Attributes) -> Self { Histogram { attr, @@ -214,6 +250,7 @@ /// "Learns" colors from the image, which will be later used to generate the palette. /// /// Fixed colors added to the image are also added to the histogram. If total number of fixed colors exceeds 256, this function will fail with `LIQ_BUFFER_TOO_SMALL`. + #[inline] pub fn add_image(&mut self, image: &mut Image<'_>) -> liq_error { unsafe { ffi::liq_histogram_add_image(&mut *self.handle, &*self.attr.handle, &mut *image.handle) } } @@ -221,6 +258,7 @@ /// Alternative to `add_image()`. Intead of counting colors in an image, it directly takes an array of colors and their counts. /// /// This function is only useful if you already have a histogram of the image from another source. + #[inline] pub fn add_colors(&mut self, colors: &[HistogramEntry], gamma: f64) -> liq_error { unsafe { ffi::liq_histogram_add_colors(&mut *self.handle, &*self.attr.handle, colors.as_ptr(), colors.len() as c_int, gamma) @@ -231,6 +269,7 @@ /// /// Palette generated using this function won't be improved during remapping. /// If you're generating palette for only one image, it's better not to use the `Histogram`. + #[inline] pub fn quantize(&mut self) -> Result<QuantizationResult, liq_error> { unsafe { let mut h = ptr::null_mut(); @@ -255,8 +294,8 @@ /// `bitmap` must be either `&[u8]` or a slice with one element per pixel (`&[RGBA]`). /// /// Use `0.` for gamma if the image is sRGB (most images are). - #[inline] - pub fn new<PixelType: Copy>(attr: &Attributes, bitmap: &'bitmap [PixelType], width: usize, height: usize, gamma: f64) -> Result<Self, liq_error> { + #[inline(always)] + pub fn new(attr: &Attributes, bitmap: &'bitmap [RGBA], width: usize, height: usize, gamma: f64) -> Result<Self, liq_error> { Self::new_stride(attr, bitmap, width, height, width, gamma) } @@ -267,62 +306,85 @@ /// /// The user data must be compatible with a primitive pointer /// (i.e. not a slice, not a Trait object. `Box` it if you must). + #[inline] pub fn new_unsafe_fn<CustomData: Send + Sync + 'bitmap>(attr: &Attributes, convert_row_fn: ConvertRowUnsafeFn<CustomData>, user_data: *mut CustomData, width: usize, height: usize, gamma: f64) -> Result<Self, liq_error> { unsafe { - match ffi::liq_image_create_custom(&*attr.handle, mem::transmute(convert_row_fn), user_data as *mut _, width as c_int, height as c_int, gamma) { - handle if !handle.is_null() => Ok(Image { handle, _marker: marker::PhantomData }), + match ffi::liq_image_create_custom(&*attr.handle, mem::transmute(convert_row_fn), user_data.cast(), width as c_int, height as c_int, gamma) { + handle if !handle.is_null() => Ok(Image { handle, _marker: PhantomData }), _ => Err(LIQ_INVALID_POINTER), } } } /// Stride is in pixels. Allows defining regions of larger images or images with padding without copying. - pub fn new_stride<PixelType: Copy>(attr: &Attributes, bitmap: &'bitmap [PixelType], width: usize, height: usize, stride: usize, gamma: f64) -> Result<Self, liq_error> { - let bytes_per_pixel = mem::size_of::<PixelType>(); - match bytes_per_pixel { - 1 | 4 => {} - _ => return Err(LIQ_UNSUPPORTED), - } - if bitmap.len() * bytes_per_pixel < (stride * height + width - stride) * 4 { - eprintln!("Buffer length is {}??{} bytes, which is not enough for {}??{}??4 RGBA bytes", bitmap.len(), bytes_per_pixel, stride, height); + #[inline(always)] + pub fn new_stride(attr: &Attributes, bitmap: &'bitmap [RGBA], width: usize, height: usize, stride: usize, gamma: f64) -> Result<Self, liq_error> { + // Type definition preserves the lifetime, so it's not unsafe + unsafe { Self::new_stride_internal(attr, bitmap, width, height, stride, gamma, false) } + } + + /// Create new image by copying `bitmap` to an internal buffer, so that it makes a self-contained type. + #[inline(always)] + pub fn new_stride_copy(attr: &Attributes, bitmap: &[RGBA], width: usize, height: usize, stride: usize, gamma: f64) -> Result<Image<'static>, liq_error> { + // copy guarantees the image doesn't reference the bitmap any more + unsafe { Self::new_stride_internal(attr, bitmap, width, height, stride, gamma, true) } + } + + unsafe fn new_stride_internal<'varies>(attr: &Attributes, bitmap: &[RGBA], width: usize, height: usize, stride: usize, gamma: f64, copy: bool) -> Result<Image<'varies>, liq_error> { + if bitmap.len() < (stride * height + width - stride) { + eprintln!("Buffer length is {} bytes, which is not enough for {}??{}??4 RGBA bytes", bitmap.len()*4, stride, height); return Err(LIQ_BUFFER_TOO_SMALL); } - unsafe { - let rows = Self::malloc_image_rows(bitmap, stride, height, bytes_per_pixel); - match ffi::liq_image_create_rgba_rows(&*attr.handle, rows, width as c_int, height as c_int, gamma) { - h if !h.is_null() && ffi::liq_image_set_memory_ownership(&*h, ffi::liq_ownership::LIQ_OWN_ROWS).is_ok() => { - Ok(Image { - handle: h, - _marker: marker::PhantomData, - }) - }, - _ => { - libc::free(rows as *mut _); - Err(LIQ_INVALID_POINTER) - } - } + let (bitmap, ownership) = if copy { + let copied = libc::malloc(4 * bitmap.len()) as *mut RGBA; + ptr::copy_nonoverlapping(bitmap.as_ptr(), copied, bitmap.len()); + (copied as *const _, ffi::liq_ownership::LIQ_OWN_ROWS | ffi::liq_ownership::LIQ_OWN_PIXELS) + } else { + (bitmap.as_ptr(), ffi::liq_ownership::LIQ_OWN_ROWS) + }; + let rows = Self::malloc_image_rows(bitmap, stride, height); + let h = ffi::liq_image_create_rgba_rows(&*attr.handle, rows, width as c_int, height as c_int, gamma); + if h.is_null() { + libc::free(rows.cast()); + return Err(LIQ_INVALID_POINTER); + } + let img = Image { + handle: h, + _marker: PhantomData, + }; + match ffi::liq_image_set_memory_ownership(&*h, ownership) { + LIQ_OK => Ok(img), + err => { + drop(img); + libc::free(rows.cast()); + Err(err) + }, } } /// For arbitrary stride libimagequant requires rows. It's most convenient if they're allocated using libc, /// so they can be owned and freed automatically by the C library. - unsafe fn malloc_image_rows<PixelType: Copy>(bitmap: &'bitmap [PixelType], stride: usize, height: usize, bytes_per_pixel: usize) -> *mut *const u8 { - let mut byte_ptr = bitmap.as_ptr() as *const u8; - let stride_bytes = (stride * bytes_per_pixel) as isize; + unsafe fn malloc_image_rows(bitmap: *const RGBA, stride: usize, height: usize) -> *mut *const u8 { + let mut byte_ptr = bitmap as *const u8; + let stride_bytes = stride * 4; let rows = libc::malloc(mem::size_of::<*const u8>() * height) as *mut *const u8; - for y in 0..height as isize { - *rows.offset(y) = byte_ptr; - byte_ptr = byte_ptr.offset(stride_bytes); + for y in 0..height { + *rows.add(y) = byte_ptr; + byte_ptr = byte_ptr.add(stride_bytes); } rows } /// Width of the image in pixels + #[inline] + #[must_use] pub fn width(&self) -> usize { unsafe { ffi::liq_image_get_width(&*self.handle) as usize } } /// Height of the image in pixels + #[inline] + #[must_use] pub fn height(&self) -> usize { unsafe { ffi::liq_image_get_height(&*self.handle) as usize } } @@ -334,10 +396,9 @@ /// It must be called before the image is quantized. /// /// Returns error if more than 256 colors are added. If image is quantized to fewer colors than the number of fixed colors added, then excess fixed colors will be ignored. + #[inline] pub fn add_fixed_color(&mut self, color: ffi::liq_color) -> liq_error { - unsafe { - ffi::liq_image_add_fixed_color(&mut *self.handle, color) - } + unsafe { ffi::liq_image_add_fixed_color(&mut *self.handle, color) } } /// Remap pixels assuming they will be displayed on this background. @@ -345,6 +406,7 @@ /// Pixels that match the background color will be made transparent if there's a fully transparent color available in the palette. /// /// The background image's pixels must outlive this image + #[inline] pub fn set_background<'own, 'bg: 'own>(&'own mut self, background: Image<'bg>) -> Result<(), liq_error> { unsafe { ffi::liq_image_set_background(&mut *self.handle, background.into_raw()).ok() @@ -354,12 +416,14 @@ /// Set which pixels are more important (and more likely to get a palette entry) /// /// The map must be `width`??`height` pixels large. Higher numbers = more important. + #[inline] pub fn set_importance_map(&mut self, map: &[u8]) -> Result<(), liq_error> { unsafe { ffi::liq_image_set_importance_map(&mut *self.handle, map.as_ptr() as *mut _, map.len(), ffi::liq_ownership::LIQ_COPY_PIXELS).ok() } } + #[inline] fn into_raw(mut self) -> *mut ffi::liq_image { let handle = self.handle; self.handle = ptr::null_mut(); @@ -369,11 +433,13 @@ impl QuantizationResult { /// Set to 1.0 to get nice smooth image + #[inline] pub fn set_dithering_level(&mut self, value: f32) -> liq_error { unsafe { ffi::liq_set_dithering_level(&mut *self.handle, value) } } /// The default is sRGB gamma (~1/2.2) + #[inline] pub fn set_output_gamma(&mut self, value: f64) -> liq_error { unsafe { ffi::liq_set_output_gamma(&mut *self.handle, value) } } @@ -381,16 +447,22 @@ /// Approximate gamma correction value used for the output /// /// Colors are converted from input gamma to this gamma + #[inline] + #[must_use] pub fn output_gamma(&mut self) -> f64 { unsafe { ffi::liq_get_output_gamma(&*self.handle) } } /// Number 0-100 guessing how nice the input image will look if remapped to this palette + #[inline] + #[must_use] pub fn quantization_quality(&self) -> i32 { unsafe { ffi::liq_get_quantization_quality(&*self.handle) as i32 } } /// Approximate mean square error of the palette + #[inline] + #[must_use] pub fn quantization_error(&self) -> Option<f64> { match unsafe { ffi::liq_get_quantization_error(&*self.handle) } { x if x < 0. => None, @@ -401,23 +473,52 @@ /// Final palette /// /// It's slighly better if you get palette from the `remapped()` call instead + #[must_use] pub fn palette(&mut self) -> Vec<Color> { + self.palette_ref().to_vec() + } + + /// Final palette (as a temporary slice) + /// + /// It's slighly better if you get palette from the `remapped()` call instead + /// + /// Use when ownership of the palette colors is not needed + #[inline] + pub fn palette_ref(&mut self) -> &[Color] { unsafe { let pal = &*ffi::liq_get_palette(&mut *self.handle); - pal.entries.iter().cloned().take(pal.count as usize).collect() + std::slice::from_raw_parts(pal.entries.as_ptr(), (pal.count as usize).min(pal.entries.len())) } } - /// Remap image + /// Remap image into a `Vec` /// - /// Returns palette and 1-byte-per-pixel uncompresed bitmap + /// Returns the palette and a 1-byte-per-pixel uncompressed bitmap pub fn remapped(&mut self, image: &mut Image<'_>) -> Result<(Vec<Color>, Vec<u8>), liq_error> { let len = image.width() * image.height(); + // Capacity is essential here, as it creates uninitialized buffer let mut buf = Vec::with_capacity(len); unsafe { - buf.set_len(len); // Creates uninitialized buffer - match ffi::liq_write_remapped_image(&mut *self.handle, &mut *image.handle, buf.as_mut_ptr(), buf.len()) { - LIQ_OK => Ok((self.palette(), buf)), + let uninit_slice = std::slice::from_raw_parts_mut(buf.as_ptr() as *mut _, buf.capacity()); + self.remap_into(image, uninit_slice)?; + buf.set_len(uninit_slice.len()); + } + Ok((self.palette(), buf)) + } + + /// Remap image into an existing buffer. + /// + /// This is a low-level call for use when existing memory has to be reused. Use `remapped()` if possible. + /// + /// Writes 1-byte-per-pixel uncompressed bitmap into the pre-allocated buffer. + /// + /// You should call `palette()` or `palette_ref()` _after_ this call, but not before it, + /// because remapping changes the palette. + #[inline] + pub fn remap_into(&mut self, image: &mut Image<'_>, output_buf: &mut [MaybeUninit<u8>]) -> Result<(), liq_error> { + unsafe { + match ffi::liq_write_remapped_image(&mut *self.handle, &mut *image.handle, output_buf.as_mut_ptr().cast(), output_buf.len()) { + LIQ_OK => Ok(()), err => Err(err), } } @@ -425,6 +526,7 @@ } impl fmt::Debug for QuantizationResult { + #[cold] fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { write!(f, "QuantizationResult(q={})", self.quantization_quality()) } @@ -436,12 +538,17 @@ unsafe impl<'a> Send for Histogram<'a> {} #[test] +fn copy_img() { + let tmp = vec![RGBA::new(1,2,3,4); 10*100]; + let liq = Attributes::new(); + let _ = liq.new_image_stride_copy(&tmp, 10, 100, 10, 0.).unwrap(); +} + +#[test] fn takes_rgba() { let liq = Attributes::new(); - #[allow(dead_code)] - #[derive(Copy, Clone)] - struct RGBA {r:u8, g:u8, b:u8, a:u8}; + use rgb::RGBA8 as RGBA; let img = vec![RGBA {r:0, g:0, b:0, a:0}; 8]; @@ -450,13 +557,6 @@ liq.new_image(&img, 8, 1, 0.0).unwrap(); assert!(liq.new_image(&img, 9, 1, 0.0).is_err()); assert!(liq.new_image(&img, 4, 3, 0.0).is_err()); - - #[allow(dead_code)] - #[derive(Copy, Clone)] - struct RGB {r:u8, g:u8, b:u8}; - let badimg = vec![RGB {r:0, g:0, b:0}; 8]; - assert!(liq.new_image(&badimg, 1, 1, 0.0).is_err()); - assert!(liq.new_image(&badimg, 100, 100, 0.0).is_err()); } #[test] @@ -464,11 +564,11 @@ let attr = Attributes::new(); let mut hist = attr.new_histogram(); - let bitmap1 = vec![0u8; 4]; + let bitmap1 = vec![RGBA {r:0, g:0, b:0, a:0}; 1]; let mut image1 = attr.new_image(&bitmap1[..], 1, 1, 0.0).unwrap(); hist.add_image(&mut image1); - let bitmap2 = vec![255u8; 4]; + let bitmap2 = vec![RGBA {r:255, g:255, b:255, a:255}; 1]; let mut image2 = attr.new_image(&bitmap2[..], 1, 1, 0.0).unwrap(); hist.add_image(&mut image2); @@ -486,11 +586,11 @@ fn poke_it() { let width = 10usize; let height = 10usize; - let mut fakebitmap = vec![255u8; 4*width*height]; + let mut fakebitmap = vec![RGBA::new(255,255,255,255); width*height]; - fakebitmap[0] = 0x55; - fakebitmap[1] = 0x66; - fakebitmap[2] = 0x77; + fakebitmap[0].r = 0x55; + fakebitmap[0].g = 0x66; + fakebitmap[0].b = 0x77; // Configure the library let mut liq = Attributes::new(); @@ -539,7 +639,7 @@ fn thread() { let liq = Attributes::new(); std::thread::spawn(move || { - let b = vec![0u8;4]; + let b = vec![RGBA::new(0,0,0,0);1]; liq.new_image(&b, 1, 1, 0.).unwrap(); }).join().unwrap(); } diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/libimagequant-2.13.1/rust-sys/build.rs new/libimagequant-2.14.1/rust-sys/build.rs --- old/libimagequant-2.13.1/rust-sys/build.rs 2020-11-22 14:18:54.000000000 +0100 +++ new/libimagequant-2.14.1/rust-sys/build.rs 2021-02-28 19:12:44.000000000 +0100 @@ -19,7 +19,8 @@ } if cfg!(feature = "openmp") { - cc.flag(&env::var("DEP_OPENMP_FLAG").unwrap()); + env::var("DEP_OPENMP_FLAG").expect("openmp-sys failed") + .split(" ").for_each(|f| { cc.flag(f); }); } let target_arch = env::var("CARGO_CFG_TARGET_ARCH").expect("Needs CARGO_CFG_TARGET_ARCH"); @@ -60,4 +61,12 @@ } cc.compile("libimagequant.a"); + + if cfg!(feature = "openmp") { + if let Some(link) = env::var_os("DEP_OPENMP_CARGO_LINK_INSTRUCTIONS") { + for i in env::split_paths(&link) { + println!("cargo:{}", i.display()); + } + } + } } diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/libimagequant-2.13.1/rust-sys/libimagequant.rs new/libimagequant-2.14.1/rust-sys/libimagequant.rs --- old/libimagequant-2.13.1/rust-sys/libimagequant.rs 2020-11-22 14:18:54.000000000 +0100 +++ new/libimagequant-2.14.1/rust-sys/libimagequant.rs 2021-02-28 19:12:44.000000000 +0100 @@ -60,12 +60,16 @@ LIQ_UNSUPPORTED, } -#[repr(C)] -#[derive(Copy, Clone)] -pub enum liq_ownership { - LIQ_OWN_ROWS = 4, - LIQ_OWN_PIXELS = 8, - LIQ_COPY_PIXELS = 16, +bitflags::bitflags! { + #[repr(C)] + pub struct liq_ownership: c_int { + /// Moves ownership of the rows array. It will free it using `free()` or custom allocator. + const LIQ_OWN_ROWS = 4; + /// Moves ownership of the pixel data. It will free it using `free()` or custom allocator. + const LIQ_OWN_PIXELS = 8; + /// Makes a copy of the pixels, so the `liq_image` is not tied to pixel's lifetime. + const LIQ_COPY_PIXELS = 16; + } } #[repr(C)] @@ -286,6 +290,11 @@ } #[test] +fn ownership_bitflags() { + assert_eq!(4+16, (liq_ownership::LIQ_OWN_ROWS | liq_ownership::LIQ_COPY_PIXELS).bits()); +} + +#[test] fn links_and_runs() { use std::ptr; unsafe {
