Import pngquant 2.4.1 from github.com/pornel/pngquant
This commit is contained in:
parent
01380588ba
commit
d9ba2d006e
33 changed files with 6991 additions and 0 deletions
11
deps/pngquant/.gitignore
vendored
Normal file
11
deps/pngquant/.gitignore
vendored
Normal file
|
@ -0,0 +1,11 @@
|
||||||
|
*.dylib
|
||||||
|
*.mode1v3
|
||||||
|
*.o
|
||||||
|
*.pbxproj
|
||||||
|
*.pbxuser
|
||||||
|
build
|
||||||
|
DerivedData
|
||||||
|
pngquant
|
||||||
|
build_configuration
|
||||||
|
lib/libimagequant.a
|
||||||
|
config.mk
|
125
deps/pngquant/CHANGELOG
vendored
Normal file
125
deps/pngquant/CHANGELOG
vendored
Normal file
|
@ -0,0 +1,125 @@
|
||||||
|
version 2.4
|
||||||
|
-----------
|
||||||
|
- fixed remapping of bright colors when dithering
|
||||||
|
- added libimagequant API to add fixed preset colors to the palette
|
||||||
|
|
||||||
|
version 2.3
|
||||||
|
-----------
|
||||||
|
- added ./configure script for better support of Intel C compiler and dependencies [thanks to pdknsk]
|
||||||
|
- tweaked quality metric to better estimate quality of images with large solid backgrounds [thanks to Rolf Timmermans]
|
||||||
|
- atomic file saves and fixed --skip-if-larger
|
||||||
|
- avoid applying quality setting to images that use palette already
|
||||||
|
- preserving standard PNG chunks (requires libpng 1.6)
|
||||||
|
- deprecated libpng 1.2 support
|
||||||
|
|
||||||
|
version 2.2
|
||||||
|
-----------
|
||||||
|
- preserving of unknown PNG chunks (enables optimized Android 9-patch images)
|
||||||
|
- improved color profile support: cHRM & gAMA as alternative to ICC profiles, OpenMP acceleration
|
||||||
|
- improved support for Intel C Compiler, speedup in 32-bit GCC, and some workarounds for Visual Studio's incomplete C support
|
||||||
|
|
||||||
|
version 2.1
|
||||||
|
-----------
|
||||||
|
- option to save files only if they're compressed better than the original
|
||||||
|
- option to generate posterized output (for use with 16-bit textures)
|
||||||
|
- support for ICC profiles via Little CMS library
|
||||||
|
|
||||||
|
version 2.0
|
||||||
|
-----------
|
||||||
|
- refactored codebase into pngquant and standalone libimagequant
|
||||||
|
- reduced memory usage by further 30% (and more for very large images)
|
||||||
|
- less precise remapping improving speed by 25% in higher speed settings
|
||||||
|
- --output option for writing converted file under the given path
|
||||||
|
- light dithering with --floyd=0.5
|
||||||
|
- fixed regression in dithering of alpha channel
|
||||||
|
|
||||||
|
version 1.8
|
||||||
|
-----------
|
||||||
|
- min/max quality option (number of colors is automatically adjusted for desired quality level)
|
||||||
|
- switched option parsing to getopt_long (syntax such as -s1 and --ext=ext is supported)
|
||||||
|
- significantly improved performance thanks to custom partial sorting
|
||||||
|
- optional Cocoa (Mac OS X) image reader for color profile support
|
||||||
|
- reduced memory usage by 20%
|
||||||
|
- remapping improved for very low number of colors
|
||||||
|
|
||||||
|
version 1.7
|
||||||
|
-----------
|
||||||
|
- new, accurate RGBA color similarity algorithm
|
||||||
|
- change of optional SSE3 code to SSE2 that is always enabled on x86-64
|
||||||
|
- optional OpenMP-based parallelisation of remapping
|
||||||
|
- changed long options to use double hyphen (-force to --force) [thanks to Jari Aalto]
|
||||||
|
|
||||||
|
version 1.6
|
||||||
|
-----------
|
||||||
|
- novel dithering algorithm that doesn't add noise unless necessary
|
||||||
|
- perceptual weighting of colors taking into account edges and noise
|
||||||
|
- much faster remapping
|
||||||
|
- improved portability, makefiles and man page
|
||||||
|
|
||||||
|
version 1.5
|
||||||
|
-----------
|
||||||
|
- palettes postprocessed with Voronoi iteration
|
||||||
|
- better RGBA color similarity algorithm and Floyd-Steinberg remapping
|
||||||
|
- SSE optimisations
|
||||||
|
|
||||||
|
version 1.4
|
||||||
|
-----------
|
||||||
|
- median cut is applied many times in a feedback loop
|
||||||
|
- speed/quality trade-off option
|
||||||
|
- faster remap of transparent areas
|
||||||
|
|
||||||
|
version 1.3
|
||||||
|
-----------
|
||||||
|
- significant changes to the algorithm: use of variance
|
||||||
|
to find largest dimensioin and to split most varying boxes
|
||||||
|
- use of premultiplied alpha for color blending
|
||||||
|
- conversion of output to gamma 2.2
|
||||||
|
|
||||||
|
version 1.2
|
||||||
|
-----------
|
||||||
|
- color computation done in floating point
|
||||||
|
- gamma correction applied
|
||||||
|
- dropped support for very old systems & compilers
|
||||||
|
|
||||||
|
version 1.1
|
||||||
|
-----------
|
||||||
|
- alpha-sensitive color reduction and dithering
|
||||||
|
- support -- and - arguments in command line
|
||||||
|
- number of colors optional (defaults to 256)
|
||||||
|
- increased maximum number of colors in histogram
|
||||||
|
|
||||||
|
version 1.0
|
||||||
|
-----------
|
||||||
|
- cleaned up Makefile.unx (better gcc optimizations, "clean" target)
|
||||||
|
- recompiled binaries with zlib 1.1.4
|
||||||
|
|
||||||
|
version 0.95
|
||||||
|
------------
|
||||||
|
- fixed Win32 filter bug (binary mode for stdin/stdout)
|
||||||
|
- fixed cosmetic "choosing colors" verbosity buglet
|
||||||
|
- fixed palette-size bug when number of colors in image < number requested
|
||||||
|
- fixed sample-depth bug (png_set_packing() not retroactively smart)
|
||||||
|
|
||||||
|
version 0.91
|
||||||
|
------------
|
||||||
|
- fixed some verbose/non-verbose oopers
|
||||||
|
- fixed Win32 (MSVC) portability issues (getpid(), random(), srandom())
|
||||||
|
- added Makefile.w32 for MSVC (tested with 5.0)
|
||||||
|
|
||||||
|
version 0.90
|
||||||
|
------------
|
||||||
|
- added support for multiple files on command line
|
||||||
|
- changed stdin support to write PNG stream to stdout (not "stdin-fs8.png")
|
||||||
|
|
||||||
|
version 0.75
|
||||||
|
------------
|
||||||
|
- added support for any type of input file [Glenn Randers-Pehrson]
|
||||||
|
- fixed palette-(re)scaling bug
|
||||||
|
- added -verbose and -quiet options (default now is -quiet)
|
||||||
|
- added palette-remapping to minimize size of tRNS chunk
|
||||||
|
- made Floyd-Steinberg dithering default
|
||||||
|
- changed output naming scheme to -fs8.png and -or8.png (FS or ordered dither)
|
||||||
|
|
||||||
|
version 0.70
|
||||||
|
------------
|
||||||
|
- first public release
|
56
deps/pngquant/COPYRIGHT
vendored
Normal file
56
deps/pngquant/COPYRIGHT
vendored
Normal file
|
@ -0,0 +1,56 @@
|
||||||
|
The quantization and dithering code in pngquant is lifted from Jef Poskanzer's
|
||||||
|
'ppmquant', part of his wonderful PBMPLUS tool suite.
|
||||||
|
|
||||||
|
Greg Roelofs hacked it into a (in his words) "slightly cheesy" 'pamquant' back
|
||||||
|
in 1997 (see http://pobox.com/~newt/greg_rgba.html) and finally he ripped out
|
||||||
|
the cheesy file-I/O parts and replaced them with nice PNG code in December
|
||||||
|
2000. The PNG reading and writing code is a merged and slightly simplified
|
||||||
|
version of readpng, readpng2, and writepng from his book "PNG: The Definitive
|
||||||
|
Guide."
|
||||||
|
In 2014 Greg has relicensed the code under the simplified BSD license.
|
||||||
|
|
||||||
|
Note that both licenses are basically BSD-like; that is, use the code however
|
||||||
|
you like, as long as you acknowledge its origins.
|
||||||
|
|
||||||
|
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
||||||
|
|
||||||
|
pngquant.c:
|
||||||
|
|
||||||
|
© 1989, 1991 by Jef Poskanzer.
|
||||||
|
|
||||||
|
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.
|
||||||
|
|
||||||
|
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
||||||
|
|
||||||
|
pngquant.c and rwpng.c/h:
|
||||||
|
|
||||||
|
© 1997-2002 by Greg Roelofs; based on an idea by Stefan Schneider.
|
||||||
|
© 2009-2014 by Kornel Lesiński.
|
||||||
|
|
||||||
|
All rights reserved.
|
||||||
|
|
||||||
|
Redistribution and use in source and binary forms, with or without modification,
|
||||||
|
are permitted provided that the following conditions are met:
|
||||||
|
|
||||||
|
1. Redistributions of source code must retain the above copyright notice,
|
||||||
|
this list of conditions and the following disclaimer.
|
||||||
|
|
||||||
|
2. Redistributions in binary form must reproduce the above copyright notice,
|
||||||
|
this list of conditions and the following disclaimer in the documentation
|
||||||
|
and/or other materials provided with the distribution.
|
||||||
|
|
||||||
|
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||||
|
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||||
|
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||||
|
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
|
||||||
|
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
||||||
|
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
||||||
|
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
||||||
|
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
||||||
|
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||||
|
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
55
deps/pngquant/INSTALL
vendored
Normal file
55
deps/pngquant/INSTALL
vendored
Normal file
|
@ -0,0 +1,55 @@
|
||||||
|
To build pngquant from source on Mac OS X and most Linux distributions,
|
||||||
|
simply run:
|
||||||
|
|
||||||
|
$ make
|
||||||
|
|
||||||
|
It will create pngquant executable in the current directory. If you'd like
|
||||||
|
to install it system-wide:
|
||||||
|
|
||||||
|
$ sudo make install
|
||||||
|
|
||||||
|
By default it will be installed in /usr/local/bin. To install it in another
|
||||||
|
directory run `./configure --prefix=dir && make`.
|
||||||
|
|
||||||
|
pngquant uses GNU Makefile. To compile on FreeBSD you will need to use gmake,
|
||||||
|
and on Windows the MinGW compiler (MSVC does not support C99).
|
||||||
|
|
||||||
|
pngquant will compile with libpng 1.2, but you should use 1.5 or later.
|
||||||
|
|
||||||
|
|
||||||
|
##Compilation with OpenMP
|
||||||
|
|
||||||
|
$ ./configure --with-openmp && make
|
||||||
|
|
||||||
|
This makes pngquant faster in wall-clock time on multicore machines when one
|
||||||
|
image at a time is processed.
|
||||||
|
|
||||||
|
However, it increases total CPU time used, and thus it's not most optimal
|
||||||
|
for server-side and parallelized batch jobs which run many pngquant
|
||||||
|
instances at a time.
|
||||||
|
|
||||||
|
On OS X you may need to install gcc and add `CC=gcc-4.9` to `./configure`,
|
||||||
|
because clang doesn't support OpenMP (yet).
|
||||||
|
|
||||||
|
|
||||||
|
##Compilation without Cocoa image reader
|
||||||
|
|
||||||
|
Mac OS X version uses Cocoa to read images. This adds support for color profiles
|
||||||
|
and other image formats as input. `./configure --without-cocoa` switches back
|
||||||
|
to libpng.
|
||||||
|
|
||||||
|
|
||||||
|
##Compilation with Little CMS 2
|
||||||
|
|
||||||
|
$ ./configure --with-lcms2 && make
|
||||||
|
|
||||||
|
Enables support for ICC v2/v4 color profiles when reading images.
|
||||||
|
Requires Little CMS library available via `pgk-config` (e.g. install `liblcms2-dev`).
|
||||||
|
|
||||||
|
|
||||||
|
##Compilation of `libimagequant.a` only
|
||||||
|
|
||||||
|
If you want to use pngquant's conversion algorithm without loading/saving PNG
|
||||||
|
files, then you can run `make` in the `lib/` directory.
|
||||||
|
The library doesn't need libpng or zlib.
|
||||||
|
|
75
deps/pngquant/Makefile
vendored
Normal file
75
deps/pngquant/Makefile
vendored
Normal file
|
@ -0,0 +1,75 @@
|
||||||
|
-include config.mk
|
||||||
|
|
||||||
|
BIN ?= pngquant
|
||||||
|
BINPREFIX = $(DESTDIR)$(PREFIX)/bin
|
||||||
|
|
||||||
|
OBJS = pngquant.o rwpng.o
|
||||||
|
COCOA_OBJS = rwpng_cocoa.o
|
||||||
|
|
||||||
|
ifeq (1, $(COCOA_READER))
|
||||||
|
OBJS += $(COCOA_OBJS)
|
||||||
|
endif
|
||||||
|
|
||||||
|
STATICLIB = lib/libimagequant.a
|
||||||
|
|
||||||
|
DISTFILES = *.[chm] pngquant.1 Makefile configure README.md INSTALL CHANGELOG COPYRIGHT
|
||||||
|
TARNAME = pngquant-$(VERSION)
|
||||||
|
TARFILE = $(TARNAME)-src.tar.bz2
|
||||||
|
|
||||||
|
LIBDISTFILES = lib/*.[ch] lib/COPYRIGHT lib/MANUAL.md lib/configure lib/Makefile
|
||||||
|
|
||||||
|
DLL=libimagequant.dll
|
||||||
|
DLLIMP=libimagequant_dll.a
|
||||||
|
DLLDEF=libimagequant_dll.def
|
||||||
|
|
||||||
|
all: $(BIN)
|
||||||
|
|
||||||
|
staticlib:
|
||||||
|
$(MAKE) -C lib static
|
||||||
|
|
||||||
|
$(STATICLIB): config.mk staticlib
|
||||||
|
|
||||||
|
$(OBJS): $(wildcard *.h) config.mk
|
||||||
|
|
||||||
|
rwpng_cocoa.o: rwpng_cocoa.m
|
||||||
|
$(CC) -Wno-enum-conversion -c $(CFLAGS) -o $@ $< || clang -Wno-enum-conversion -c -O3 $(CFLAGS) -o $@ $<
|
||||||
|
|
||||||
|
$(BIN): $(OBJS) $(STATICLIB)
|
||||||
|
$(CC) $^ $(LDFLAGS) -o $@
|
||||||
|
|
||||||
|
test: $(BIN)
|
||||||
|
./test/test.sh ./test $(BIN)
|
||||||
|
|
||||||
|
dist: $(TARFILE)
|
||||||
|
|
||||||
|
$(TARFILE): $(DISTFILES)
|
||||||
|
rm -rf $(TARFILE) $(TARNAME)
|
||||||
|
mkdir -p $(TARNAME)/lib
|
||||||
|
cp $(DISTFILES) $(TARNAME)
|
||||||
|
cp $(LIBDISTFILES) $(TARNAME)/lib
|
||||||
|
tar -cjf $(TARFILE) --numeric-owner --exclude='._*' $(TARNAME)
|
||||||
|
rm -rf $(TARNAME)
|
||||||
|
-shasum $(TARFILE)
|
||||||
|
|
||||||
|
install: $(BIN)
|
||||||
|
-mkdir -p '$(BINPREFIX)'
|
||||||
|
install -m 0755 -p '$(BIN)' '$(BINPREFIX)/$(BIN)'
|
||||||
|
|
||||||
|
uninstall:
|
||||||
|
rm -f '$(BINPREFIX)/$(BIN)'
|
||||||
|
|
||||||
|
clean:
|
||||||
|
$(MAKE) -C lib clean
|
||||||
|
rm -f '$(BIN)' $(OBJS) $(COCOA_OBJS) $(STATICLIB) $(TARFILE)
|
||||||
|
|
||||||
|
distclean: clean
|
||||||
|
$(MAKE) -C lib distclean
|
||||||
|
rm -f config.mk pngquant-*-src.tar.bz2
|
||||||
|
|
||||||
|
config.mk:
|
||||||
|
ifeq ($(filter %clean %distclean, $(MAKECMDGOALS)), )
|
||||||
|
./configure
|
||||||
|
endif
|
||||||
|
|
||||||
|
.PHONY: all clean dist distclean dll install uninstall test staticlib
|
||||||
|
.DELETE_ON_ERROR:
|
97
deps/pngquant/README.md
vendored
Normal file
97
deps/pngquant/README.md
vendored
Normal file
|
@ -0,0 +1,97 @@
|
||||||
|
#pngquant 2
|
||||||
|
|
||||||
|
This is the official `pngquant` and `libimagequant`.
|
||||||
|
|
||||||
|
[pngquant](http://pngquant.org) converts 24/32-bit RGBA PNGs to 8-bit palette with *alpha channel preserved*.
|
||||||
|
Such images are compatible with all modern browsers, and a special compatibility setting exists which helps transparency degrade well in Internet Explorer 6.
|
||||||
|
|
||||||
|
Quantized files are often 40-70% smaller than their 24/32-bit version.
|
||||||
|
|
||||||
|
This utility works on Linux, Mac OS X and Windows.
|
||||||
|
|
||||||
|
##Usage
|
||||||
|
|
||||||
|
- batch conversion of multiple files: `pngquant *.png`
|
||||||
|
- Unix-style stdin/stdout chaining: `… | pngquant - | …`
|
||||||
|
|
||||||
|
To further reduce file size, try [optipng](http://optipng.sourceforge.net) or [ImageOptim](http://imageoptim.pornel.net).
|
||||||
|
|
||||||
|
##Improvements since 1.0
|
||||||
|
|
||||||
|
Generated files are both smaller and look much better.
|
||||||
|
|
||||||
|
* Significantly better quality of quantisation
|
||||||
|
|
||||||
|
- more accurate remapping of semitransparent colors
|
||||||
|
- special dithering algorithm that does not add noise in well-quantized areas of the image
|
||||||
|
- uses variance instead of popularity for box selection (improvement suggested in the original median cut paper)
|
||||||
|
- feedback loop that repeats median cut for poorly quantized colors
|
||||||
|
- additional colormap improvement using Voronoi iteration
|
||||||
|
- supports much larger number of colors in input images without degradation of quality
|
||||||
|
- gamma correction (output is always generated with gamma 2.2 for web compatibility)
|
||||||
|
|
||||||
|
* More flexible commandline usage
|
||||||
|
|
||||||
|
- number of colors defaults to 256
|
||||||
|
- long options and standard switches like `--` and `-` are allowed
|
||||||
|
|
||||||
|
* Refactored and modernised code
|
||||||
|
|
||||||
|
- C99 with no workarounds for old systems
|
||||||
|
- floating-point math used throughout
|
||||||
|
- Intel SSE optimisations
|
||||||
|
- multicore support via OpenMP
|
||||||
|
- quantization moved to standalone libimagequant
|
||||||
|
|
||||||
|
##Options
|
||||||
|
|
||||||
|
See `pngquant -h` for full list.
|
||||||
|
|
||||||
|
###`--quality min-max`
|
||||||
|
|
||||||
|
`min` and `max` are numbers in range 0 (worst) to 100 (perfect), similar to JPEG. pngquant will use the least amount of colors required to meet or exceed the `max` quality. If conversion results in quality below the `min` quality the image won't be saved (if outputting to stdin, 24-bit original will be output) and pngquant will exit with status code 99.
|
||||||
|
|
||||||
|
pngquant --quality=65-80 image.png
|
||||||
|
|
||||||
|
###`--ext new.png`
|
||||||
|
|
||||||
|
Set custom extension (suffix) for output filename. By default `-or8.png` or `-fs8.png` is used. If you use `--ext=.png --force` options pngquant will overwrite input files in place (use with caution).
|
||||||
|
|
||||||
|
###`-o out.png` or `--output out.png`
|
||||||
|
|
||||||
|
Writes converted file to the given path. When this option is used only single input file is allowed.
|
||||||
|
|
||||||
|
###`--skip-if-larger`
|
||||||
|
|
||||||
|
Don't write converted files if the conversion isn't worth it.
|
||||||
|
|
||||||
|
###`--speed N`
|
||||||
|
|
||||||
|
Speed/quality trade-off from 1 (brute-force) to 11 (fastest). The default is 3. Speed 10 has 5% lower quality, but is 8 times faster than the default. Speed 11 disables dithering and lowers compression level.
|
||||||
|
|
||||||
|
###`--nofs`
|
||||||
|
|
||||||
|
Disables Floyd-Steinberg dithering.
|
||||||
|
|
||||||
|
###`--floyd=0.5`
|
||||||
|
|
||||||
|
Controls level of dithering (0 = none, 1 = full).
|
||||||
|
|
||||||
|
###`--posterize bits`
|
||||||
|
|
||||||
|
Reduce precision of the palette by number of bits. Use when the image will be displayed on low-depth screens (e.g. 16-bit displays or compressed textures in ARGB444 format).
|
||||||
|
|
||||||
|
###`--version`
|
||||||
|
|
||||||
|
Print version information to stdout.
|
||||||
|
|
||||||
|
###`-`
|
||||||
|
|
||||||
|
Read image from stdin and send result to stdout.
|
||||||
|
|
||||||
|
###`--`
|
||||||
|
|
||||||
|
Stops processing of arguments. This allows use of file names that start with `-`. If you're using pngquant in a script, it's advisable to put this before file names:
|
||||||
|
|
||||||
|
pngquant $OPTIONS -- "$FILE"
|
||||||
|
|
427
deps/pngquant/configure
vendored
Executable file
427
deps/pngquant/configure
vendored
Executable file
|
@ -0,0 +1,427 @@
|
||||||
|
#!/usr/bin/env bash
|
||||||
|
|
||||||
|
CONFIG="config.mk"
|
||||||
|
PREFIX="/usr/local"
|
||||||
|
VERSION=$(grep LIQ_VERSION_STRING lib/libimagequant.h | grep -Eo "2\.[0-9.]+")
|
||||||
|
|
||||||
|
DEBUG=
|
||||||
|
SSE=auto
|
||||||
|
OPENMP=
|
||||||
|
if [[ "$OSTYPE" =~ "darwin" ]]; then
|
||||||
|
COCOA_READER=auto
|
||||||
|
LCMS2=0
|
||||||
|
else
|
||||||
|
COCOA_READER=0
|
||||||
|
LCMS2=auto
|
||||||
|
fi
|
||||||
|
EXTRA_CFLAGS=
|
||||||
|
EXTRA_LDFLAGS=
|
||||||
|
|
||||||
|
# make gcc default compiler unless CC is already set
|
||||||
|
CC=${CC:-gcc}
|
||||||
|
|
||||||
|
help() {
|
||||||
|
printf "%4s %s\n" "" "$1"
|
||||||
|
}
|
||||||
|
|
||||||
|
for i in "$@"; do
|
||||||
|
case $i in
|
||||||
|
--help)
|
||||||
|
echo
|
||||||
|
help "--prefix= installation directory [$PREFIX]"
|
||||||
|
help "--extra-cflags= append to CFLAGS"
|
||||||
|
help "--extra-ldflags= append to LDFLAGS"
|
||||||
|
echo
|
||||||
|
help "--enable-debug"
|
||||||
|
help "--enable-sse/--disable-sse enable/disable SSE instructions"
|
||||||
|
echo
|
||||||
|
help "--with-openmp compile with multicore support"
|
||||||
|
help "--with-lcms2/--without-lcms2 compile with color profile support"
|
||||||
|
if [[ "$OSTYPE" =~ "darwin" ]]; then
|
||||||
|
help "--with-cocoa/--without-cocoa use Cocoa framework to read images"
|
||||||
|
fi
|
||||||
|
echo
|
||||||
|
help "CC=<compiler> use given compiler command"
|
||||||
|
help "CFLAGS=<flags> pass options to the compiler"
|
||||||
|
help "LDFLAGS=<flags> pass options to the linker"
|
||||||
|
echo
|
||||||
|
exit 0
|
||||||
|
;;
|
||||||
|
# Can be set before or after configure. Latter overrides former.
|
||||||
|
CC=*)
|
||||||
|
CC=${i#*=}
|
||||||
|
;;
|
||||||
|
CFLAGS=*)
|
||||||
|
CFLAGS=${i#*=}
|
||||||
|
;;
|
||||||
|
LDFLAGS=*)
|
||||||
|
LDFLAGS=${i#*=}
|
||||||
|
;;
|
||||||
|
--enable-debug)
|
||||||
|
DEBUG=1
|
||||||
|
;;
|
||||||
|
--enable-sse)
|
||||||
|
SSE=1
|
||||||
|
;;
|
||||||
|
--disable-sse)
|
||||||
|
SSE=0
|
||||||
|
;;
|
||||||
|
--with-openmp)
|
||||||
|
OPENMP=1
|
||||||
|
;;
|
||||||
|
--with-lcms2)
|
||||||
|
LCMS2=1
|
||||||
|
COCOA_READER=0
|
||||||
|
;;
|
||||||
|
--without-lcms2)
|
||||||
|
LCMS2=0
|
||||||
|
;;
|
||||||
|
--with-cocoa)
|
||||||
|
COCOA_READER=1
|
||||||
|
LCMS2=0
|
||||||
|
;;
|
||||||
|
--without-cocoa)
|
||||||
|
COCOA_READER=0
|
||||||
|
;;
|
||||||
|
--prefix=*)
|
||||||
|
PREFIX=${i#*=}
|
||||||
|
;;
|
||||||
|
# can be used multiple times or in quotes to set multiple flags
|
||||||
|
--extra-cflags=*)
|
||||||
|
EXTRA_CFLAGS="$EXTRA_CFLAGS ${i#*=}"
|
||||||
|
;;
|
||||||
|
--extra-ldflags=*)
|
||||||
|
EXTRA_LDFLAGS="$EXTRA_LDFLAGS ${i#*=}"
|
||||||
|
;;
|
||||||
|
*)
|
||||||
|
echo "error: unknown switch ${i%%=*} (see $0 --help for the list)"
|
||||||
|
exit 1
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
done
|
||||||
|
|
||||||
|
# If someone runs sudo make install as very first command, and configure later,
|
||||||
|
# $CONFIG cannot be overwritten, and must be deleted before continuing.
|
||||||
|
if [[ -f "$CONFIG" && ! -w "$CONFIG" ]]; then
|
||||||
|
echo "Cannot overwrite file $CONFIG! Please delete it."
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
cflags() {
|
||||||
|
CFLAGS="$CFLAGS $1"
|
||||||
|
}
|
||||||
|
|
||||||
|
lflags() {
|
||||||
|
LDFLAGS="$LDFLAGS $1"
|
||||||
|
}
|
||||||
|
|
||||||
|
status() {
|
||||||
|
printf "%10s: %s\n" "$1" "$2"
|
||||||
|
}
|
||||||
|
|
||||||
|
# Append to CFLAGS if compiler supports flag, with optional prerequisite.
|
||||||
|
# Fails on errors and warnings.
|
||||||
|
conditional_cflags() {
|
||||||
|
if [ -z "echo | $("$CC" -xc -S -o /dev/null $2 $1 2>&1)" ]; then
|
||||||
|
cflags "$1"
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
# returns first matching file in directory
|
||||||
|
find_f() {
|
||||||
|
echo $(find "$1" -not -type d -name "$2" -print -quit 2> /dev/null)
|
||||||
|
}
|
||||||
|
|
||||||
|
# returns first matching file in directory (no symlinks)
|
||||||
|
find_h() {
|
||||||
|
echo $(find "$1" -type f -name "$2" -print -quit 2> /dev/null)
|
||||||
|
}
|
||||||
|
|
||||||
|
find_pkgconfig() {
|
||||||
|
LIBNAME=$1
|
||||||
|
if pkg-config --exists "$LIBNAME" &> /dev/null; then
|
||||||
|
cflags "$(pkg-config --cflags "$LIBNAME")"
|
||||||
|
lflags "$(pkg-config --libs "$LIBNAME")"
|
||||||
|
status "$LIBNAME" "shared ($(pkg-config --modversion "$LIBNAME"))"
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
return 1
|
||||||
|
}
|
||||||
|
|
||||||
|
find_static() {
|
||||||
|
LIBNAME=$1
|
||||||
|
HEADERPATTERN=$2
|
||||||
|
STATICPATTERN=$3
|
||||||
|
|
||||||
|
HPATH=$(find_h . "$HEADERPATTERN")
|
||||||
|
if [ -n "$HPATH" ]; then
|
||||||
|
APATH=$(find_f . "$STATICPATTERN")
|
||||||
|
if [ -n "$APATH" ]; then
|
||||||
|
cflags "-I${HPATH%/*}"
|
||||||
|
lflags "${APATH}"
|
||||||
|
status "$LIBNAME" "static"
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
return 1
|
||||||
|
}
|
||||||
|
|
||||||
|
find_library() {
|
||||||
|
LIBNAME=$1
|
||||||
|
DYNAMICLIBNAME=$2
|
||||||
|
HEADERPATTERN=$3
|
||||||
|
STATICPATTERN=$4
|
||||||
|
DYNAMICPATTERN=$5
|
||||||
|
|
||||||
|
# try static in current directory first
|
||||||
|
if find_static "$LIBNAME" "$HEADERPATTERN" "$STATICPATTERN"; then
|
||||||
|
return 0;
|
||||||
|
fi
|
||||||
|
|
||||||
|
# try shared
|
||||||
|
if find_pkgconfig "$LIBNAME"; then
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
for i in "${DIRS[@]}"; do
|
||||||
|
DIR=($i)
|
||||||
|
HPATH=$(find_h "${DIR[0]}" "$HEADERPATTERN")
|
||||||
|
if [ -n "$HPATH" ]; then
|
||||||
|
SOPATH=$(find_f "${DIR[1]}" "$DYNAMICPATTERN")
|
||||||
|
if [ -n "$SOPATH" ]; then
|
||||||
|
cflags "-I${HPATH%/*}"
|
||||||
|
lflags "-L${SOPATH%/*} -l$DYNAMICLIBNAME"
|
||||||
|
status "$LIBNAME" "shared ... $SOPATH"
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
return 1
|
||||||
|
}
|
||||||
|
|
||||||
|
# returns full png.h version string
|
||||||
|
pngh_string() {
|
||||||
|
echo "$(grep -m1 "define PNG_LIBPNG_VER_STRING" "$1" | \
|
||||||
|
grep -Eo '"[^"]+"' | grep -Eo '[^"]+')"
|
||||||
|
}
|
||||||
|
|
||||||
|
# returns major minor version numbers from png.h
|
||||||
|
pngh_majmin() {
|
||||||
|
local MAJ=$(grep -m1 "define PNG_LIBPNG_VER_MAJOR" "$1" | grep -Eo "[0-9]+")
|
||||||
|
local MIN=$(grep -m1 "define PNG_LIBPNG_VER_MINOR" "$1" | grep -Eo "[0-9]+")
|
||||||
|
echo "${MAJ}${MIN}"
|
||||||
|
}
|
||||||
|
|
||||||
|
error() {
|
||||||
|
status "$1" "error ... $2"
|
||||||
|
echo
|
||||||
|
exit 1
|
||||||
|
}
|
||||||
|
|
||||||
|
echo
|
||||||
|
|
||||||
|
# basic check
|
||||||
|
if ! echo "int main(){}" | "$CC" -xc -std=c99 -o /dev/null - &> /dev/null; then
|
||||||
|
error "Compiler" "$CC failed to compile anything (make sure it's installed and supports C99)"
|
||||||
|
fi
|
||||||
|
|
||||||
|
status "Compiler" "$CC"
|
||||||
|
|
||||||
|
# init flags
|
||||||
|
CFLAGS=${CFLAGS:--O3 -fno-math-errno -funroll-loops -fomit-frame-pointer -Wall}
|
||||||
|
cflags "-std=c99 -I."
|
||||||
|
|
||||||
|
# DEBUG
|
||||||
|
if [ -z "$DEBUG" ]; then
|
||||||
|
cflags "-DNDEBUG"
|
||||||
|
status "Debug" "no"
|
||||||
|
else
|
||||||
|
cflags "-g"
|
||||||
|
status "Debug" "yes"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# SSE
|
||||||
|
if [ "$SSE" = 'auto' ]; then
|
||||||
|
if [[ "$(uname -m)" =~ (amd|x86_)64 ||
|
||||||
|
"$(grep -E -m1 "^flags" /proc/cpuinfo)" =~ "sse" ]]; then
|
||||||
|
SSE=1
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [ "$SSE" -eq 1 ]; then
|
||||||
|
status "SSE" "yes"
|
||||||
|
cflags "-DUSE_SSE=1"
|
||||||
|
cflags "-msse"
|
||||||
|
# Silence a later ICC warning due to -msse working slightly different.
|
||||||
|
conditional_cflags "-wd10121"
|
||||||
|
# Must be set explicitly for GCC on x86_32. Other compilers imply it.
|
||||||
|
conditional_cflags "-mfpmath=sse" "-msse"
|
||||||
|
elif [ "$SSE" -eq 0 ]; then
|
||||||
|
status "SSE" "no"
|
||||||
|
cflags "-DUSE_SSE=0"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# OpenMP
|
||||||
|
if [ -n "$OPENMP" ]; then
|
||||||
|
if [[ "$("$CC" -xc -E -fopenmp <(echo "#ifdef _OPENMP
|
||||||
|
#include <omp.h>
|
||||||
|
#endif") 2>&1)" =~ "omp_get_thread_num" ]]; then
|
||||||
|
cflags "-fopenmp"
|
||||||
|
lflags "-fopenmp"
|
||||||
|
status "OpenMP" "yes"
|
||||||
|
else
|
||||||
|
error "OpenMP" "not supported by compiler (please install a compiler that supports OpenMP (e.g. gcc) and specify it with the CC= argument)"
|
||||||
|
fi
|
||||||
|
else
|
||||||
|
# silence warnings about omp pragmas
|
||||||
|
cflags "-Wno-unknown-pragmas"
|
||||||
|
conditional_cflags "-wd3180" # ICC
|
||||||
|
status "OpenMP" "no"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Cocoa
|
||||||
|
if [[ "$OSTYPE" =~ "darwin" ]]; then
|
||||||
|
cflags "-mmacosx-version-min=10.6"
|
||||||
|
lflags "-mmacosx-version-min=10.6"
|
||||||
|
|
||||||
|
if [ "$COCOA_READER" != 0 ] && "$CC" 2>/dev/null 1>/dev/null -xc -E <(echo "#import <Cocoa/Cocoa.h>"); then
|
||||||
|
COCOA_READER=1
|
||||||
|
cflags "-DUSE_COCOA=1"
|
||||||
|
lflags "-framework Cocoa"
|
||||||
|
status "Cocoa" "yes"
|
||||||
|
else
|
||||||
|
status "Cocoa" "no"
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
|
# pairs of possible *.h and lib*.so locations
|
||||||
|
DIRS=("/usr/local/include /usr/local/lib"
|
||||||
|
"/usr/include /usr/lib"
|
||||||
|
"/opt/local/include /opt/local/lib" # macports
|
||||||
|
)
|
||||||
|
|
||||||
|
if [[ "$OSTYPE" =~ "darwin" ]]; then
|
||||||
|
SOLIBSUFFIX=dylib
|
||||||
|
|
||||||
|
# Search Developer SDK paths, since Apple seems to have dropped the standard Unixy ones
|
||||||
|
XCODE_CMD="xcode-select"
|
||||||
|
XCODE_PATH=$($XCODE_CMD -p)
|
||||||
|
DIRS+=("$XCODE_PATH/Platforms/MacOSX.platform/Developer/SDKs/MacOSX10.10.sdk/usr/include $XCODE_PATH/Platforms/MacOSX.platform/Developer/SDKs/MacOSX10.10.sdk/usr/lib")
|
||||||
|
DIRS+=("$XCODE_PATH/Platforms/MacOSX.platform/Developer/SDKs/MacOSX10.8.sdk/usr/include $XCODE_PATH/Platforms/MacOSX.platform/Developer/SDKs/MacOSX10.8.sdk/usr/lib")
|
||||||
|
else
|
||||||
|
SOLIBSUFFIX=so
|
||||||
|
fi
|
||||||
|
|
||||||
|
|
||||||
|
# libpng
|
||||||
|
SUCCESS=0
|
||||||
|
# try static in current directory first
|
||||||
|
PNGH=$(find_h . "png.h")
|
||||||
|
if [ -n "$PNGH" ]; then
|
||||||
|
PNGH_STRING=$(pngh_string "$PNGH")
|
||||||
|
PNGH_MAJMIN=$(pngh_majmin "$PNGH")
|
||||||
|
if [[ -n "$PNGH_STRING" && -n "$PNGH_MAJMIN" ]]; then
|
||||||
|
LIBPNGA=$(find_f . "libpng${PNGH_MAJMIN}.a")
|
||||||
|
if [ -n "$LIBPNGA" ]; then
|
||||||
|
cflags "-I${PNGH%/*}"
|
||||||
|
lflags "${LIBPNGA}"
|
||||||
|
status "libpng" "static (${PNGH_STRING})"
|
||||||
|
SUCCESS=1
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
# try shared
|
||||||
|
if [ "$SUCCESS" -eq 0 ]; then
|
||||||
|
if find_pkgconfig libpng; then
|
||||||
|
SUCCESS=1
|
||||||
|
else
|
||||||
|
for i in "${DIRS[@]}"; do
|
||||||
|
DIR=($i)
|
||||||
|
PNGH=$(find_h "${DIR[0]}" "png.h")
|
||||||
|
if [ -n "$PNGH" ]; then
|
||||||
|
PNGH_STRING=$(pngh_string "$PNGH")
|
||||||
|
PNGH_MAJMIN=$(pngh_majmin "$PNGH")
|
||||||
|
if [[ -n "$PNGH_STRING" && -n "$PNGH_MAJMIN" ]]; then
|
||||||
|
LIBPNGSO=$(find_f "${DIR[1]}" "libpng${PNGH_MAJMIN}.$SOLIBSUFFIX*")
|
||||||
|
if [ -n "$LIBPNGSO" ]; then
|
||||||
|
cflags "-I${PNGH%/*}"
|
||||||
|
lflags "-L${LIBPNGSO%/*} -lpng${PNGH_MAJMIN}"
|
||||||
|
status "libpng" "shared (${PNGH_STRING})"
|
||||||
|
SUCCESS=1
|
||||||
|
break
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
if [ "$SUCCESS" -eq 0 ]; then
|
||||||
|
if [[ "$OSTYPE" =~ "darwin" ]]; then
|
||||||
|
LIBPNG_CMD='`brew install libpng`'
|
||||||
|
else
|
||||||
|
LIBPNG_CMD='`apt-get install libpng-dev` or `yum install libpng-devel`'
|
||||||
|
fi
|
||||||
|
error "libpng" "not found (try: $LIBPNG_CMD)"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# zlib
|
||||||
|
if ! find_library "zlib" "z" "zlib.h" "libz.a" "libz.$SOLIBSUFFIX*"; then
|
||||||
|
error "zlib" "not found (please install zlib-devel package)"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# lcms2
|
||||||
|
if [ "$LCMS2" != 0 ]; then
|
||||||
|
if find_library "lcms2" "lcms2" "lcms2.h" "liblcms2.a" "liblcms2.$SOLIBSUFFIX*"; then
|
||||||
|
cflags "-DUSE_LCMS=1"
|
||||||
|
else
|
||||||
|
if [ "$LCMS2" = 'auto' ]; then
|
||||||
|
status "lcms2" "no"
|
||||||
|
else
|
||||||
|
error "lcms2" "not found (please install libcms2-devel package)"
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
else
|
||||||
|
status "lcms2" "no"
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo
|
||||||
|
|
||||||
|
# As of GCC 4.5, 387 fp math is significantly slower in C99 mode without this.
|
||||||
|
# Note: CPUs without SSE2 use 387 for doubles, even when SSE fp math is set.
|
||||||
|
conditional_cflags "-fexcess-precision=fast"
|
||||||
|
|
||||||
|
# Intel C++ Compiler
|
||||||
|
|
||||||
|
# ICC does usually only produce fast(er) code when it can optimize to the full
|
||||||
|
# capabilites of the (Intel) CPU. This is equivalent to -march=native for GCC.
|
||||||
|
conditional_cflags "-xHOST"
|
||||||
|
|
||||||
|
# Disable unsafe fp optimizations and enforce fp precision as set in the source.
|
||||||
|
conditional_cflags "-fp-model source"
|
||||||
|
|
||||||
|
# Silence a gold linker warning about string misalignment.
|
||||||
|
conditional_cflags "-falign-stack=maintain-16-byte"
|
||||||
|
|
||||||
|
lflags "-lm" # Ubuntu requires this library last, issue #38
|
||||||
|
|
||||||
|
if [ -n "$EXTRA_CFLAGS" ]; then
|
||||||
|
cflags "$EXTRA_CFLAGS"
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [ -n "$EXTRA_LDFLAGS" ]; then
|
||||||
|
lflags "$EXTRA_LDFLAGS"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Overwrite previous configuration.
|
||||||
|
echo "
|
||||||
|
# auto-generated by configure
|
||||||
|
PREFIX = $PREFIX
|
||||||
|
VERSION = $VERSION
|
||||||
|
CC = $CC
|
||||||
|
CFLAGS = $CFLAGS
|
||||||
|
LDFLAGS = $LDFLAGS
|
||||||
|
COCOA_READER = $COCOA_READER
|
||||||
|
" > "$CONFIG"
|
||||||
|
|
||||||
|
# Configure static library the same way
|
||||||
|
cp "$CONFIG" lib/
|
36
deps/pngquant/lib/COPYRIGHT
vendored
Normal file
36
deps/pngquant/lib/COPYRIGHT
vendored
Normal file
|
@ -0,0 +1,36 @@
|
||||||
|
© 1997-2002 by Greg Roelofs; based on an idea by Stefan Schneider.
|
||||||
|
© 2009-2014 by Kornel Lesiński.
|
||||||
|
|
||||||
|
All rights reserved.
|
||||||
|
|
||||||
|
Redistribution and use in source and binary forms, with or without modification,
|
||||||
|
are permitted provided that the following conditions are met:
|
||||||
|
|
||||||
|
1. Redistributions of source code must retain the above copyright notice,
|
||||||
|
this list of conditions and the following disclaimer.
|
||||||
|
|
||||||
|
2. Redistributions in binary form must reproduce the above copyright notice,
|
||||||
|
this list of conditions and the following disclaimer in the documentation
|
||||||
|
and/or other materials provided with the distribution.
|
||||||
|
|
||||||
|
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||||
|
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||||
|
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||||
|
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
|
||||||
|
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
||||||
|
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
||||||
|
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
||||||
|
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
||||||
|
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||||
|
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||||
|
|
||||||
|
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
||||||
|
|
||||||
|
© 1989, 1991 by Jef Poskanzer.
|
||||||
|
|
||||||
|
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.
|
490
deps/pngquant/lib/MANUAL.md
vendored
Normal file
490
deps/pngquant/lib/MANUAL.md
vendored
Normal file
|
@ -0,0 +1,490 @@
|
||||||
|
# libimagequant—Image Quantization Library
|
||||||
|
|
||||||
|
Small, portable C library for high-quality conversion of RGBA images to 8-bit indexed-color (palette) images.
|
||||||
|
It's powering [pngquant2](http://pngquant.org).
|
||||||
|
|
||||||
|
## License
|
||||||
|
|
||||||
|
[BSD](https://raw.github.com/pornel/pngquant/master/lib/COPYRIGHT).
|
||||||
|
It can be linked with both free and closed-source software.
|
||||||
|
|
||||||
|
## Download
|
||||||
|
|
||||||
|
The [library](http://pngquant.org/lib) is currently a part of the [pngquant2 project](https://github.com/pornel/pngquant/tree/master/lib).
|
||||||
|
|
||||||
|
Files needed for the library are only in the `lib/` directory inside the repository (and you can ignore the rest).
|
||||||
|
|
||||||
|
## Compiling and Linking
|
||||||
|
|
||||||
|
The library can be linked with ANSI C and C++ programs. It has no external dependencies.
|
||||||
|
|
||||||
|
To build on Unix-like systems run:
|
||||||
|
|
||||||
|
make -C lib
|
||||||
|
|
||||||
|
it will create `lib/libimagequant.a` which you can link with your program.
|
||||||
|
|
||||||
|
gcc yourprogram.c /path/to/lib/libimagequant.a
|
||||||
|
|
||||||
|
On BSD, use `gmake` (GNU make) rather than the native `make`.
|
||||||
|
|
||||||
|
Alternatively you can compile the library with your program simply by including all `.c` files (and define `NDEBUG` to get a fast version):
|
||||||
|
|
||||||
|
gcc -std=c99 -O3 -DNDEBUG lib/*.c yourprogram.c
|
||||||
|
|
||||||
|
### 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:
|
||||||
|
|
||||||
|
* Use Visual Studio **2013** (MSVC 18) and an [MSVC-compatible branch of the library](https://github.com/pornel/pngquant/tree/msvc/lib)
|
||||||
|
* Or use GCC from [MinGW](http://www.mingw.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.
|
||||||
|
|
||||||
|
## Overview
|
||||||
|
|
||||||
|
The basic flow is:
|
||||||
|
|
||||||
|
1. Create attributes object and configure the library.
|
||||||
|
2. Create image object from RGBA bitmap or data source.
|
||||||
|
3. Perform quantization (generate palette).
|
||||||
|
4. Store remapped image and final palette.
|
||||||
|
5. Free memory.
|
||||||
|
|
||||||
|
Please note that libimagequant only handles raw uncompressed bitmaps in memory and is completely independent of any file format.
|
||||||
|
|
||||||
|
<p>
|
||||||
|
|
||||||
|
#include "lib/libimagequant.h"
|
||||||
|
|
||||||
|
liq_attr *attr = liq_attr_create();
|
||||||
|
liq_image *image = liq_image_create_rgba(attr, bitmap, width, height, 0);
|
||||||
|
liq_result *res = liq_quantize_image(attr, image);
|
||||||
|
|
||||||
|
liq_write_remapped_image(res, image, bitmap, bitmap_size);
|
||||||
|
const liq_palette *pal = liq_get_palette(res);
|
||||||
|
|
||||||
|
// use image and palette here
|
||||||
|
|
||||||
|
liq_attr_destroy(attr);
|
||||||
|
liq_image_destroy(image);
|
||||||
|
liq_result_destroy(res);
|
||||||
|
|
||||||
|
Functions returning `liq_error` return `LIQ_OK` (`0`) on success and non-zero on error.
|
||||||
|
|
||||||
|
It's safe to pass `NULL` to any function accepting `liq_attr`, `liq_image`, `liq_result` (in that case the error code `LIQ_INVALID_POINTER` will be returned). These objects can be reused multiple times.
|
||||||
|
|
||||||
|
There are 3 ways to create image object for quantization:
|
||||||
|
|
||||||
|
* `liq_image_create_rgba()` for simple, contiguous RGBA bitmaps (width×height×4 bytes large array).
|
||||||
|
* `liq_image_create_rgba_rows()` for non-contiguous RGBA bitmaps (that have padding between rows or reverse order, e.g. BMP).
|
||||||
|
* `liq_image_create_custom()` for RGB, ABGR, YUV and all other formats that can be converted on-the-fly to RGBA (you have to supply the conversion function).
|
||||||
|
|
||||||
|
Note that "image" here means raw uncompressed pixels. If you have a compressed image file, such as PNG, you must use another library (e.g. libpng or lodepng) to decode it first.
|
||||||
|
|
||||||
|
## Functions
|
||||||
|
|
||||||
|
----
|
||||||
|
|
||||||
|
liq_attr* liq_attr_create(void);
|
||||||
|
|
||||||
|
Returns object that will hold initial settings (attributes) for the library. The object should be freed using `liq_attr_destroy()` after it's no longer needed.
|
||||||
|
|
||||||
|
Returns `NULL` in the unlikely case that the library cannot run on the current machine (e.g. the library has been compiled for SSE-capable x86 CPU and run on VIA C3 CPU).
|
||||||
|
|
||||||
|
----
|
||||||
|
|
||||||
|
liq_error liq_set_max_colors(liq_attr* attr, int colors);
|
||||||
|
|
||||||
|
Specifies maximum number of colors to use. The default is 256. Instead of setting a fixed limit it's better to use `liq_set_quality()`.
|
||||||
|
|
||||||
|
Returns `LIQ_VALUE_OUT_OF_RANGE` if number of colors is outside the range 2-256.
|
||||||
|
|
||||||
|
----
|
||||||
|
|
||||||
|
int liq_get_max_colors(liq_attr* attr);
|
||||||
|
|
||||||
|
Returns the value set by `liq_set_max_colors()`.
|
||||||
|
|
||||||
|
----
|
||||||
|
|
||||||
|
liq_error liq_set_quality(liq_attr* attr, int minimum, int maximum);
|
||||||
|
|
||||||
|
Quality is in range `0` (worst) to `100` (best) and values are analoguous to JPEG quality (i.e. `80` is usually good enough).
|
||||||
|
|
||||||
|
Quantization will attempt to use the lowest number of colors needed to achieve `maximum` quality. `maximum` value of `100` is the default and means conversion as good as possible.
|
||||||
|
|
||||||
|
If it's not possible to convert the image with at least `minimum` quality (i.e. 256 colors is not enough to meet the minimum quality), then `liq_quantize_image()` will fail. The default minumum is `0` (proceeds regardless of quality).
|
||||||
|
|
||||||
|
Quality measures how well the generated palette fits image given to `liq_quantize_image()`. If a different image is remapped with `liq_write_remapped_image()` then actual quality may be different.
|
||||||
|
|
||||||
|
Regardless of the quality settings the number of colors won't exceed the maximum (see `liq_set_max_colors()`).
|
||||||
|
|
||||||
|
Returns `LIQ_VALUE_OUT_OF_RANGE` if target is lower than minimum or any of them is outside the 0-100 range.
|
||||||
|
Returns `LIQ_INVALID_POINTER` if `attr` appears to be invalid.
|
||||||
|
|
||||||
|
liq_attr *attr = liq_attr_create();
|
||||||
|
liq_set_quality(attr, 50, 80); // use quality 80 if possible. Give up if quality drops below 50.
|
||||||
|
|
||||||
|
----
|
||||||
|
|
||||||
|
int liq_get_min_quality(liq_attr* attr);
|
||||||
|
|
||||||
|
Returns the lower bound set by `liq_set_quality()`.
|
||||||
|
|
||||||
|
----
|
||||||
|
|
||||||
|
int liq_get_max_quality(liq_attr* attr);
|
||||||
|
|
||||||
|
Returns the upper bound set by `liq_set_quality()`.
|
||||||
|
|
||||||
|
----
|
||||||
|
|
||||||
|
liq_image *liq_image_create_rgba(liq_attr *attr, void* bitmap, int width, int height, double gamma);
|
||||||
|
|
||||||
|
Creates image object that represents a bitmap later used for quantization and remapping. The bitmap must be contiguous run of RGBA pixels (alpha is the last component, 0 = transparent, 255 = opaque).
|
||||||
|
|
||||||
|
The bitmap must not be modified or freed until this object is freed with `liq_image_destroy()`. See also `liq_image_set_memory_ownership()`.
|
||||||
|
|
||||||
|
`width` and `height` are dimensions in pixels. An image 10x10 pixel large will need 400-byte bitmap.
|
||||||
|
|
||||||
|
`gamma` can be `0` for images with the typical 1/2.2 [gamma](http://en.wikipedia.org/wiki/Gamma_correction).
|
||||||
|
Otherwise `gamma` must be > 0 and < 1, e.g. `0.45455` (1/2.2) or `0.55555` (1/1.8). Generated palette will use the same gamma unless `liq_set_output_gamma()` is used. If `liq_set_output_gamma` is not used, then it only affects whether brighter or darker areas of the image will get more palette colors allocated.
|
||||||
|
|
||||||
|
Returns `NULL` on failure, e.g. if `bitmap` is `NULL` or `width`/`height` is <= 0.
|
||||||
|
|
||||||
|
----
|
||||||
|
|
||||||
|
liq_image *liq_image_create_rgba_rows(liq_attr *attr, void* rows[], int width, int height, double gamma);
|
||||||
|
|
||||||
|
Same as `liq_image_create_rgba()`, but takes array of pointers to rows in the bitmap. This allows defining bitmaps with reversed rows (like in BMP), "stride" different than width or using only fragment of a larger bitmap, etc.
|
||||||
|
|
||||||
|
`rows` array must have at least `height` elements and each row must be at least `width` RGBA pixels wide.
|
||||||
|
|
||||||
|
unsigned char *bitmap = …;
|
||||||
|
void *rows = malloc(height * sizeof(void*));
|
||||||
|
int bytes_per_row = width * 4 + padding; // stride
|
||||||
|
for(int i=0; i < height; i++) {
|
||||||
|
rows[i] = bitmap + i * bytes_per_row;
|
||||||
|
}
|
||||||
|
liq_image *img = liq_image_create_rgba_rows(attr, rows, width, height, 0);
|
||||||
|
// …
|
||||||
|
liq_image_destroy(img);
|
||||||
|
free(rows);
|
||||||
|
|
||||||
|
The row pointers and bitmap must not be modified or freed until this object is freed with `liq_image_destroy()` (you can change that with `liq_image_set_memory_ownership()`).
|
||||||
|
|
||||||
|
See also `liq_image_create_rgba()` and `liq_image_create_custom()`.
|
||||||
|
|
||||||
|
----
|
||||||
|
|
||||||
|
liq_result *liq_quantize_image(liq_attr *attr, liq_image *input_image);
|
||||||
|
|
||||||
|
Performs quantization (palette generation) based on settings in `attr` and pixels of the image.
|
||||||
|
|
||||||
|
Returns `NULL` if quantization fails, e.g. due to limit set in `liq_set_quality()`.
|
||||||
|
|
||||||
|
See `liq_write_remapped_image()`.
|
||||||
|
|
||||||
|
----
|
||||||
|
|
||||||
|
liq_error liq_set_dithering_level(liq_result *res, float dither_level);
|
||||||
|
|
||||||
|
Enables/disables dithering in `liq_write_remapped_image()`. Dithering level must be between `0` and `1` (inclusive). Dithering level `0` enables fast non-dithered remapping. Otherwise a variation of Floyd-Steinberg error diffusion is used.
|
||||||
|
|
||||||
|
Precision of the dithering algorithm depends on the speed setting, see `liq_set_speed()`.
|
||||||
|
|
||||||
|
Returns `LIQ_VALUE_OUT_OF_RANGE` if the dithering level is outside the 0-1 range.
|
||||||
|
|
||||||
|
----
|
||||||
|
|
||||||
|
liq_error liq_write_remapped_image(liq_result *result, liq_image *input_image, void *buffer, size_t buffer_size);
|
||||||
|
|
||||||
|
Remaps the image to palette and writes its pixels to the given buffer, 1 pixel per byte. Buffer must be large enough to fit the entire image, i.e. width×height bytes large. For safety, pass size of the buffer as `buffer_size`.
|
||||||
|
|
||||||
|
For best performance call `liq_get_palette()` *after* this function, as palette is improved during remapping.
|
||||||
|
|
||||||
|
Returns `LIQ_BUFFER_TOO_SMALL` if given size of the buffer is not enough to fit the entire image.
|
||||||
|
|
||||||
|
int buffer_size = width*height;
|
||||||
|
char *buffer = malloc(buffer_size);
|
||||||
|
if (LIQ_OK == liq_write_remapped_image(result, input_image, buffer, buffer_size)) {
|
||||||
|
liq_palette *pal = liq_get_palette(result);
|
||||||
|
// save image
|
||||||
|
}
|
||||||
|
|
||||||
|
See `liq_get_palette()` and `liq_write_remapped_image_rows()`.
|
||||||
|
|
||||||
|
Please note that it only writes raw uncompressed pixels to memory. It does not perform any compression. If you'd like to create a PNG file then you need to pass the raw pixel data to another library, e.g. libpng or lodepng. See `rwpng.c` in `pngquant` project for an example how to do that.
|
||||||
|
|
||||||
|
----
|
||||||
|
|
||||||
|
const liq_palette *liq_get_palette(liq_result *result);
|
||||||
|
|
||||||
|
Returns pointer to palette optimized for image that has been quantized or remapped (final refinements are applied to the palette during remapping).
|
||||||
|
|
||||||
|
It's valid to call this method before remapping, if you don't plan to remap any images or want to use same palette for multiple images.
|
||||||
|
|
||||||
|
`liq_palette->count` contains number of colors (up to 256), `liq_palette->entries[n]` contains RGBA value for nth palette color.
|
||||||
|
|
||||||
|
The palette is **temporary and read-only**. You must copy the palette elsewhere *before* calling `liq_result_destroy()`.
|
||||||
|
|
||||||
|
Returns `NULL` on error.
|
||||||
|
|
||||||
|
----
|
||||||
|
|
||||||
|
void liq_attr_destroy(liq_attr *);
|
||||||
|
void liq_image_destroy(liq_image *);
|
||||||
|
void liq_result_destroy(liq_result *);
|
||||||
|
|
||||||
|
Releases memory owned by the given object. Object must not be used any more after it has been freed.
|
||||||
|
|
||||||
|
Freeing `liq_result` also frees any `liq_palette` obtained from it.
|
||||||
|
|
||||||
|
## Advanced Functions
|
||||||
|
|
||||||
|
----
|
||||||
|
|
||||||
|
liq_error liq_set_speed(liq_attr* attr, int speed);
|
||||||
|
|
||||||
|
Higher speed levels disable expensive algorithms and reduce quantization precision. The default speed is `3`. Speed `1` gives marginally better quality at significant CPU cost. Speed `10` has usually 5% lower quality, but is 8 times faster than the default.
|
||||||
|
|
||||||
|
High speeds combined with `liq_set_quality()` will use more colors than necessary and will be less likely to meet minimum required quality.
|
||||||
|
|
||||||
|
<table><caption>Features dependent on speed</caption>
|
||||||
|
<tr><th>Noise-sensitive dithering</th><td>speed 1 to 5</td></tr>
|
||||||
|
<tr><th>Forced posterization</th><td>8-10 or if image has more than million colors</td></tr>
|
||||||
|
<tr><th>Quantization error known</th><td>1-7 or if minimum quality is set</td></tr>
|
||||||
|
<tr><th>Additional quantization techniques</th><td>1-6</td></tr>
|
||||||
|
</table>
|
||||||
|
|
||||||
|
Returns `LIQ_VALUE_OUT_OF_RANGE` if the speed is outside the 1-10 range.
|
||||||
|
|
||||||
|
----
|
||||||
|
|
||||||
|
int liq_get_speed(liq_attr* attr);
|
||||||
|
|
||||||
|
Returns the value set by `liq_set_speed()`.
|
||||||
|
|
||||||
|
----
|
||||||
|
|
||||||
|
liq_error liq_set_min_opacity(liq_attr* attr, int min);
|
||||||
|
|
||||||
|
Alpha values higher than this will be rounded to opaque. This is a workaround for Internet Explorer 6 that truncates semitransparent values to completely transparent. The default is `255` (no change). 238 is a suggested value.
|
||||||
|
|
||||||
|
Returns `LIQ_VALUE_OUT_OF_RANGE` if the value is outside the 0-255 range.
|
||||||
|
|
||||||
|
----
|
||||||
|
|
||||||
|
int liq_get_min_opacity(liq_attr* attr);
|
||||||
|
|
||||||
|
Returns the value set by `liq_set_min_opacity()`.
|
||||||
|
|
||||||
|
----
|
||||||
|
|
||||||
|
liq_set_min_posterization(liq_attr* attr, int bits);
|
||||||
|
|
||||||
|
Ignores given number of least significant bits in all channels, posterizing image to `2^bits` levels. `0` gives full quality. Use `2` for VGA or 16-bit RGB565 displays, `4` if image is going to be output on a RGB444/RGBA4444 display (e.g. low-quality textures on Android).
|
||||||
|
|
||||||
|
Returns `LIQ_VALUE_OUT_OF_RANGE` if the value is outside the 0-4 range.
|
||||||
|
|
||||||
|
----
|
||||||
|
|
||||||
|
int liq_get_min_posterization(liq_attr* attr);
|
||||||
|
|
||||||
|
Returns the value set by `liq_set_min_posterization()`.
|
||||||
|
|
||||||
|
----
|
||||||
|
|
||||||
|
liq_set_last_index_transparent(liq_attr* attr, int is_last);
|
||||||
|
|
||||||
|
`0` (default) makes alpha colors sorted before opaque colors. Non-`0` mixes colors together except completely transparent color, which is moved to the end of the palette. This is a workaround for programs that blindly assume the last palette entry is transparent.
|
||||||
|
|
||||||
|
----
|
||||||
|
|
||||||
|
liq_image *liq_image_create_custom(liq_attr *attr, liq_image_get_rgba_row_callback *row_callback, void *user_info, int width, int height, double gamma);
|
||||||
|
|
||||||
|
<p>
|
||||||
|
|
||||||
|
void image_get_rgba_row_callback(liq_color row_out[], int row_index, int width, void *user_info) {
|
||||||
|
for(int column_index=0; column_index < width; column_index++) {
|
||||||
|
row_out[column_index] = /* generate pixel at (row_index, column_index) */;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Creates image object that will use callback to read image data. This allows on-the-fly conversion of images that are not in the RGBA color space.
|
||||||
|
|
||||||
|
`user_info` value will be passed to the callback. It may be useful for storing pointer to program's internal representation of the image.
|
||||||
|
|
||||||
|
The callback must read/generate `row_index`-th row and write its RGBA pixels to the `row_out` array. Row `width` is given for convenience and will always equal to image width.
|
||||||
|
|
||||||
|
The callback will be called multiple times for each row. Quantization and remapping require at least two full passes over image data, so caching of callback's work makes no sense — in such case it's better to convert entire image and use `liq_image_create_rgba()` instead.
|
||||||
|
|
||||||
|
To use RGB image:
|
||||||
|
|
||||||
|
void rgb_to_rgba_callback(liq_color row_out[], int row_index, int width, void *user_info) {
|
||||||
|
unsigned char *rgb_row = ((unsigned char *)user_info) + 3*width*row_index;
|
||||||
|
|
||||||
|
for(int i=0; i < width; i++) {
|
||||||
|
row_out[i].r = rgb_row[i*3];
|
||||||
|
row_out[i].g = rgb_row[i*3+1];
|
||||||
|
row_out[i].b = rgb_row[i*3+2];
|
||||||
|
row_out[i].a = 255;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
liq_image *img = liq_image_create_custom(attr, rgb_to_rgba_callback, rgb_bitmap, width, height, 0);
|
||||||
|
|
||||||
|
The library doesn't support RGB bitmaps "natively", because supporting only single format allows compiler to inline more code, 4-byte pixel alignment is faster, and SSE instructions operate on 4 values at once, so alpha support is almost free.
|
||||||
|
|
||||||
|
----
|
||||||
|
|
||||||
|
liq_error liq_image_set_memory_ownership(liq_image *image, int ownership_flags);
|
||||||
|
|
||||||
|
Passes ownership of bitmap and/or rows memory to the `liq_image` object, so you don't have to free it yourself. Memory owned by the object will be freed at its discretion with `free` function specified in `liq_attr_create_with_allocator()` (by default it's stdlib's `free()`).
|
||||||
|
|
||||||
|
* `LIQ_OWN_PIXELS` makes bitmap owned by the object. The bitmap will be freed automatically at any point when it's no longer needed. If you set this flag you must **not** free the bitmap yourself. If the image has been created with `liq_image_create_rgba_rows()` then the bitmap address is assumed to be the lowest address of any row.
|
||||||
|
|
||||||
|
* `LIQ_OWN_ROWS` makes array of row pointers (but not bitmap pointed by these rows) owned by the object. Rows will be freed when object is deallocated. If you set this flag you must **not** free the rows array yourself. This flag is valid only if the object has been created with `liq_image_create_rgba_rows()`.
|
||||||
|
|
||||||
|
These flags can be combined with binary *or*, i.e. `LIQ_OWN_PIXELS | LIQ_OWN_ROWS`.
|
||||||
|
|
||||||
|
This function must not be used if the image has been created with `liq_image_create_custom()`.
|
||||||
|
|
||||||
|
Returns `LIQ_VALUE_OUT_OF_RANGE` if invalid flags are specified or image is not backed by a bitmap.
|
||||||
|
|
||||||
|
----
|
||||||
|
|
||||||
|
liq_error liq_write_remapped_image_rows(liq_result *result, liq_image *input_image, unsigned char **row_pointers);
|
||||||
|
|
||||||
|
Similar to `liq_write_remapped_image()`. Writes remapped image, at 1 byte per pixel, to each row pointed by `row_pointers` array. The array must have at least as many elements as height of the image, and each row must have at least as many bytes as width of the image. Rows must not overlap.
|
||||||
|
|
||||||
|
For best performance call `liq_get_palette()` *after* this function, as remapping may change the palette.
|
||||||
|
|
||||||
|
Returns `LIQ_INVALID_POINTER` if `result` or `input_image` is `NULL`.
|
||||||
|
|
||||||
|
----
|
||||||
|
|
||||||
|
double liq_get_quantization_error(liq_result *result);
|
||||||
|
|
||||||
|
Returns mean square error of quantization (square of difference between pixel values in the original image and remapped image). Alpha channel and gamma correction are taken into account, so the result isn't exactly the mean square error of all channels.
|
||||||
|
|
||||||
|
For most images MSE 1-5 is excellent. 7-10 is OK. 20-30 will have noticeable errors. 100 is awful.
|
||||||
|
|
||||||
|
This function should be called *after* `liq_write_remapped_image()`. It may return `-1` if the value is not available (this is affected by `liq_set_speed()` and `liq_set_quality()`).
|
||||||
|
|
||||||
|
----
|
||||||
|
|
||||||
|
double liq_get_quantization_quality(liq_result *result);
|
||||||
|
|
||||||
|
Analoguous to `liq_get_quantization_error()`, but returns quantization error as quality value in the same 0-100 range that is used by `liq_set_quality()`.
|
||||||
|
|
||||||
|
This function should be called *after* `liq_write_remapped_image()`. It may return `-1` if the value is not available (this is affected by `liq_set_speed()` and `liq_set_quality()`).
|
||||||
|
|
||||||
|
This function can be used to add upper limit to quality options presented to the user, e.g.
|
||||||
|
|
||||||
|
liq_attr *attr = liq_attr_create();
|
||||||
|
liq_image *img = liq_image_create_rgba(…);
|
||||||
|
liq_result *res = liq_quantize_image(attr, img);
|
||||||
|
int max_attainable_quality = liq_get_quantization_quality(res);
|
||||||
|
printf("Please select quality between 0 and %d: ", max_attainable_quality);
|
||||||
|
int user_selected_quality = prompt();
|
||||||
|
if (user_selected_quality < max_attainable_quality) {
|
||||||
|
liq_set_quality(user_selected_quality, 0);
|
||||||
|
liq_result_destroy(res);
|
||||||
|
res = liq_quantize_image(attr, img);
|
||||||
|
}
|
||||||
|
liq_write_remapped_image(…);
|
||||||
|
|
||||||
|
----
|
||||||
|
|
||||||
|
void liq_set_log_callback(liq_attr*, liq_log_callback_function*, void *user_info);
|
||||||
|
|
||||||
|
<p>
|
||||||
|
|
||||||
|
void log_callback_function(const liq_attr*, const char *message, void *user_info) {}
|
||||||
|
|
||||||
|
----
|
||||||
|
|
||||||
|
void liq_set_log_flush_callback(liq_attr*, liq_log_flush_callback_function*, void *user_info);
|
||||||
|
<p>
|
||||||
|
|
||||||
|
void log_flush_callback_function(const liq_attr*, void *user_info) {}
|
||||||
|
|
||||||
|
Sets up callback function to be called when the library reports work progress or errors. The callback must not call any library functions.
|
||||||
|
|
||||||
|
`user_info` value will be passed to the callback.
|
||||||
|
|
||||||
|
`NULL` callback clears the current callback.
|
||||||
|
|
||||||
|
In the log callback the `message` is a zero-terminated string containing informative message to output. It is valid only until the callback returns.
|
||||||
|
|
||||||
|
`liq_set_log_flush_callback()` sets up callback function that will be called after the last log callback, which can be used to flush buffers and free resources used by the log callback.
|
||||||
|
|
||||||
|
----
|
||||||
|
|
||||||
|
liq_attr* liq_attr_create_with_allocator(void* (*malloc)(size_t), void (*free)(void*));
|
||||||
|
|
||||||
|
Same as `liq_attr_create`, but uses given `malloc` and `free` replacements to allocate all memory used by the library.
|
||||||
|
|
||||||
|
The `malloc` function must return 16-byte aligned memory on x86 (and on other architectures memory aligned for `double` and pointers). Conversely, if your stdlib's `malloc` doesn't return appropriately aligned memory, you should use this function to provide aligned replacements.
|
||||||
|
|
||||||
|
----
|
||||||
|
|
||||||
|
liq_attr* liq_attr_copy(liq_attr *orig);
|
||||||
|
|
||||||
|
Creates an independent copy of `liq_attr`. The copy should also be freed using `liq_attr_destroy()`.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
liq_error liq_set_output_gamma(liq_result* res, double gamma);
|
||||||
|
|
||||||
|
Sets gamma correction for generated palette and remapped image. Must be > 0 and < 1, e.g. `0.45455` for gamma 1/2.2 in PNG images. By default output gamma is same as gamma of the input image.
|
||||||
|
|
||||||
|
----
|
||||||
|
|
||||||
|
int liq_image_get_width(const liq_image *img);
|
||||||
|
int liq_image_get_height(const liq_image *img);
|
||||||
|
double liq_get_output_gamma(const liq_result *result);
|
||||||
|
|
||||||
|
Getters for `width`, `height` and `gamma` of the input image.
|
||||||
|
|
||||||
|
If the input is invalid, these all return -1.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
liq_error liq_image_add_fixed_color(liq_image* img, liq_color color);
|
||||||
|
|
||||||
|
Reserves a color in the output palette created from this image. It behaves as if the given color was used in the image and was very important.
|
||||||
|
|
||||||
|
RGB values of `liq_color` are assumed to have the same gamma as the image.
|
||||||
|
|
||||||
|
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.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
int liq_version();
|
||||||
|
|
||||||
|
Returns version of the library as an integer. Same as `LIQ_VERSION`. Human-readable version is defined as `LIQ_VERSION_STRING`.
|
||||||
|
|
||||||
|
## Multithreading
|
||||||
|
|
||||||
|
The library is stateless and doesn't use any global or thread-local storage. It doesn't use any locks.
|
||||||
|
|
||||||
|
* Different threads can perform unrelated quantizations/remappings at the same time (e.g. each thread working on a different image).
|
||||||
|
* The same `liq_attr`, `liq_result`, etc. can be accessed from different threads, but not at the same time (e.g. you can create `liq_attr` in one thread and free it in another).
|
||||||
|
|
||||||
|
The library needs to sort unique colors present in the image. Although the sorting algorithm does few things to make stack usage minimal in typical cases, there is no guarantee against extremely degenerate cases, so threads should have automatically growing stack.
|
||||||
|
|
||||||
|
### OpenMP
|
||||||
|
|
||||||
|
The library will parallelize some operations if compiled with OpenMP.
|
||||||
|
|
||||||
|
You must not increase number of maximum threads after `liq_image` has been created, as it allocates some per-thread buffers.
|
||||||
|
|
||||||
|
Callback of `liq_image_create_custom()` may be called from different threads at the same time.
|
||||||
|
|
||||||
|
## Acknowledgements
|
||||||
|
|
||||||
|
Thanks to Irfan Skiljan for helping test the first version of the library.
|
||||||
|
|
||||||
|
The library is developed by [Kornel Lesiński](mailto:%20kornel@pngquant.org).
|
65
deps/pngquant/lib/Makefile
vendored
Normal file
65
deps/pngquant/lib/Makefile
vendored
Normal file
|
@ -0,0 +1,65 @@
|
||||||
|
-include config.mk
|
||||||
|
|
||||||
|
STATICLIB=libimagequant.a
|
||||||
|
SHAREDLIB=libimagequant.so.0
|
||||||
|
|
||||||
|
DLL=libimagequant.dll
|
||||||
|
DLLIMP=libimagequant_dll.a
|
||||||
|
DLLDEF=libimagequant_dll.def
|
||||||
|
|
||||||
|
OBJS = pam.o mediancut.o blur.o mempool.o viter.o nearest.o libimagequant.o
|
||||||
|
SHAREDOBJS = $(subst .o,.lo,$(OBJS))
|
||||||
|
|
||||||
|
BUILD_CONFIGURATION="$(CC) $(CFLAGS) $(LDFLAGS)"
|
||||||
|
|
||||||
|
DISTFILES = $(OBJS:.o=.c) *.h MANUAL.md COPYRIGHT Makefile configure
|
||||||
|
TARNAME = libimagequant-$(VERSION)
|
||||||
|
TARFILE = $(TARNAME)-src.tar.bz2
|
||||||
|
|
||||||
|
all: static shared
|
||||||
|
|
||||||
|
static: $(STATICLIB)
|
||||||
|
|
||||||
|
shared: $(SHAREDLIB)
|
||||||
|
|
||||||
|
dll:
|
||||||
|
$(MAKE) CFLAGSADD="-DLIQ_EXPORT='__declspec(dllexport)'" $(DLL)
|
||||||
|
|
||||||
|
|
||||||
|
$(DLL) $(DLLIMP): $(OBJS)
|
||||||
|
$(CC) -fPIC -shared -o $(DLL) $^ $(LDFLAGS) -Wl,--out-implib,$(DLLIMP),--output-def,$(DLLDEF)
|
||||||
|
|
||||||
|
$(STATICLIB): $(OBJS)
|
||||||
|
$(AR) $(ARFLAGS) $@ $^
|
||||||
|
|
||||||
|
$(SHAREDOBJS):
|
||||||
|
$(CC) -fPIC $(CFLAGS) -c $(@:.lo=.c) -o $@
|
||||||
|
|
||||||
|
$(SHAREDLIB): $(SHAREDOBJS)
|
||||||
|
$(CC) -shared -o $@ $^ $(LDFLAGS)
|
||||||
|
|
||||||
|
$(OBJS): $(wildcard *.h) config.mk
|
||||||
|
|
||||||
|
dist: $(TARFILE)
|
||||||
|
|
||||||
|
$(TARFILE): $(DISTFILES)
|
||||||
|
rm -rf $(TARFILE) $(TARNAME)
|
||||||
|
mkdir $(TARNAME)
|
||||||
|
cp $(DISTFILES) $(TARNAME)
|
||||||
|
tar -cjf $(TARFILE) --numeric-owner --exclude='._*' $(TARNAME)
|
||||||
|
rm -rf $(TARNAME)
|
||||||
|
-shasum $(TARFILE)
|
||||||
|
|
||||||
|
clean:
|
||||||
|
rm -f $(OBJS) $(SHAREDOBJS) $(SHAREDLIB) $(STATICLIB) $(TARFILE) $(DLL) $(DLLIMP) $(DLLDEF)
|
||||||
|
|
||||||
|
distclean: clean
|
||||||
|
rm -f config.mk
|
||||||
|
|
||||||
|
config.mk:
|
||||||
|
ifeq ($(filter %clean %distclean, $(MAKECMDGOALS)), )
|
||||||
|
./configure
|
||||||
|
endif
|
||||||
|
|
||||||
|
.PHONY: all static shared clean dist distclean dll
|
||||||
|
.DELETE_ON_ERROR:
|
112
deps/pngquant/lib/blur.c
vendored
Normal file
112
deps/pngquant/lib/blur.c
vendored
Normal file
|
@ -0,0 +1,112 @@
|
||||||
|
|
||||||
|
#include "libimagequant.h"
|
||||||
|
#include "pam.h"
|
||||||
|
#include "blur.h"
|
||||||
|
|
||||||
|
/*
|
||||||
|
Blurs image horizontally (width 2*size+1) and writes it transposed to dst (called twice gives 2d blur)
|
||||||
|
*/
|
||||||
|
static void transposing_1d_blur(unsigned char *restrict src, unsigned char *restrict dst, unsigned int width, unsigned int height, const unsigned int size)
|
||||||
|
{
|
||||||
|
for(unsigned int j=0; j < height; j++) {
|
||||||
|
unsigned char *restrict row = src + j*width;
|
||||||
|
|
||||||
|
// accumulate sum for pixels outside line
|
||||||
|
unsigned int sum;
|
||||||
|
sum = row[0]*size;
|
||||||
|
for(unsigned int i=0; i < size; i++) {
|
||||||
|
sum += row[i];
|
||||||
|
}
|
||||||
|
|
||||||
|
// blur with left side outside line
|
||||||
|
for(unsigned int i=0; i < size; i++) {
|
||||||
|
sum -= row[0];
|
||||||
|
sum += row[i+size];
|
||||||
|
|
||||||
|
dst[i*height + j] = sum / (size*2);
|
||||||
|
}
|
||||||
|
|
||||||
|
for(unsigned int i=size; i < width-size; i++) {
|
||||||
|
sum -= row[i-size];
|
||||||
|
sum += row[i+size];
|
||||||
|
|
||||||
|
dst[i*height + j] = sum / (size*2);
|
||||||
|
}
|
||||||
|
|
||||||
|
// blur with right side outside line
|
||||||
|
for(unsigned int i=width-size; i < width; i++) {
|
||||||
|
sum -= row[i-size];
|
||||||
|
sum += row[width-1];
|
||||||
|
|
||||||
|
dst[i*height + j] = sum / (size*2);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Picks maximum of neighboring pixels (blur + lighten)
|
||||||
|
*/
|
||||||
|
LIQ_PRIVATE void liq_max3(unsigned char *src, unsigned char *dst, unsigned int width, unsigned int height)
|
||||||
|
{
|
||||||
|
for(unsigned int j=0; j < height; j++) {
|
||||||
|
const unsigned char *row = src + j*width,
|
||||||
|
*prevrow = src + (j > 1 ? j-1 : 0)*width,
|
||||||
|
*nextrow = src + MIN(height-1,j+1)*width;
|
||||||
|
|
||||||
|
unsigned char prev,curr=row[0],next=row[0];
|
||||||
|
|
||||||
|
for(unsigned int i=0; i < width-1; i++) {
|
||||||
|
prev=curr;
|
||||||
|
curr=next;
|
||||||
|
next=row[i+1];
|
||||||
|
|
||||||
|
unsigned char t1 = MAX(prev,next);
|
||||||
|
unsigned char t2 = MAX(nextrow[i],prevrow[i]);
|
||||||
|
*dst++ = MAX(curr,MAX(t1,t2));
|
||||||
|
}
|
||||||
|
unsigned char t1 = MAX(curr,next);
|
||||||
|
unsigned char t2 = MAX(nextrow[width-1],prevrow[width-1]);
|
||||||
|
*dst++ = MAX(t1,t2);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Picks minimum of neighboring pixels (blur + darken)
|
||||||
|
*/
|
||||||
|
LIQ_PRIVATE void liq_min3(unsigned char *src, unsigned char *dst, unsigned int width, unsigned int height)
|
||||||
|
{
|
||||||
|
for(unsigned int j=0; j < height; j++) {
|
||||||
|
const unsigned char *row = src + j*width,
|
||||||
|
*prevrow = src + (j > 1 ? j-1 : 0)*width,
|
||||||
|
*nextrow = src + MIN(height-1,j+1)*width;
|
||||||
|
|
||||||
|
unsigned char prev,curr=row[0],next=row[0];
|
||||||
|
|
||||||
|
for(unsigned int i=0; i < width-1; i++) {
|
||||||
|
prev=curr;
|
||||||
|
curr=next;
|
||||||
|
next=row[i+1];
|
||||||
|
|
||||||
|
unsigned char t1 = MIN(prev,next);
|
||||||
|
unsigned char t2 = MIN(nextrow[i],prevrow[i]);
|
||||||
|
*dst++ = MIN(curr,MIN(t1,t2));
|
||||||
|
}
|
||||||
|
unsigned char t1 = MIN(curr,next);
|
||||||
|
unsigned char t2 = MIN(nextrow[width-1],prevrow[width-1]);
|
||||||
|
*dst++ = MIN(t1,t2);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
Filters src image and saves it to dst, overwriting tmp in the process.
|
||||||
|
Image must be width*height pixels high. Size controls radius of box blur.
|
||||||
|
*/
|
||||||
|
LIQ_PRIVATE void liq_blur(unsigned char *src, unsigned char *tmp, unsigned char *dst, unsigned int width, unsigned int height, unsigned int size)
|
||||||
|
{
|
||||||
|
assert(size > 0);
|
||||||
|
if (width < 2*size+1 || height < 2*size+1) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
transposing_1d_blur(src, tmp, width, height, size);
|
||||||
|
transposing_1d_blur(tmp, dst, height, width, size);
|
||||||
|
}
|
4
deps/pngquant/lib/blur.h
vendored
Normal file
4
deps/pngquant/lib/blur.h
vendored
Normal file
|
@ -0,0 +1,4 @@
|
||||||
|
|
||||||
|
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);
|
205
deps/pngquant/lib/configure
vendored
Executable file
205
deps/pngquant/lib/configure
vendored
Executable file
|
@ -0,0 +1,205 @@
|
||||||
|
#!/usr/bin/env bash
|
||||||
|
|
||||||
|
CONFIG="config.mk"
|
||||||
|
PREFIX="/usr/local"
|
||||||
|
VERSION=$(grep LIQ_VERSION_STRING libimagequant.h | grep -Eo "2\.[0-9.]+")
|
||||||
|
|
||||||
|
DEBUG=
|
||||||
|
SSE=auto
|
||||||
|
OPENMP=
|
||||||
|
EXTRA_CFLAGS=
|
||||||
|
EXTRA_LDFLAGS=
|
||||||
|
|
||||||
|
# make gcc default compiler unless CC is already set
|
||||||
|
CC=${CC:-gcc}
|
||||||
|
|
||||||
|
help() {
|
||||||
|
printf "%4s %s\n" "" "$1"
|
||||||
|
}
|
||||||
|
|
||||||
|
for i in "$@"; do
|
||||||
|
case $i in
|
||||||
|
--help)
|
||||||
|
echo
|
||||||
|
help "--prefix= installation directory [$PREFIX]"
|
||||||
|
help "--extra-cflags= append to CFLAGS"
|
||||||
|
help "--extra-ldflags= append to LDFLAGS"
|
||||||
|
echo
|
||||||
|
help "--enable-debug"
|
||||||
|
help "--enable-sse/--disable-sse enable/disable SSE instructions"
|
||||||
|
echo
|
||||||
|
help "--with-openmp compile with multicore support"
|
||||||
|
echo
|
||||||
|
exit 0
|
||||||
|
;;
|
||||||
|
# Can be set before or after configure. Latter overrides former.
|
||||||
|
CC=*)
|
||||||
|
CC=${i#*=}
|
||||||
|
;;
|
||||||
|
CFLAGS=*)
|
||||||
|
CFLAGS=${i#*=}
|
||||||
|
;;
|
||||||
|
LDFLAGS=*)
|
||||||
|
LDFLAGS=${i#*=}
|
||||||
|
;;
|
||||||
|
--enable-debug)
|
||||||
|
DEBUG=1
|
||||||
|
;;
|
||||||
|
--enable-sse)
|
||||||
|
SSE=1
|
||||||
|
;;
|
||||||
|
--disable-sse)
|
||||||
|
SSE=0
|
||||||
|
;;
|
||||||
|
--with-openmp)
|
||||||
|
OPENMP=1
|
||||||
|
;;
|
||||||
|
--prefix=*)
|
||||||
|
PREFIX=${i#*=}
|
||||||
|
;;
|
||||||
|
# can be used multiple times or in quotes to set multiple flags
|
||||||
|
--extra-cflags=*)
|
||||||
|
EXTRA_CFLAGS="$EXTRA_CFLAGS ${i#*=}"
|
||||||
|
;;
|
||||||
|
--extra-ldflags=*)
|
||||||
|
EXTRA_LDFLAGS="$EXTRA_LDFLAGS ${i#*=}"
|
||||||
|
;;
|
||||||
|
*)
|
||||||
|
echo "error: unknown switch ${i%%=*}"
|
||||||
|
exit 1
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
done
|
||||||
|
|
||||||
|
# If someone runs sudo make install as very first command, and configure later,
|
||||||
|
# $CONFIG cannot be overwritten, and must be deleted before continuing.
|
||||||
|
if [[ -f "$CONFIG" && ! -w "$CONFIG" ]]; then
|
||||||
|
echo "Cannot overwrite file $CONFIG! Please delete it."
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
cflags() {
|
||||||
|
CFLAGS="$CFLAGS $1"
|
||||||
|
}
|
||||||
|
|
||||||
|
lflags() {
|
||||||
|
LDFLAGS="$LDFLAGS $1"
|
||||||
|
}
|
||||||
|
|
||||||
|
status() {
|
||||||
|
printf "%10s: %s\n" "$1" "$2"
|
||||||
|
}
|
||||||
|
|
||||||
|
# Append to CFLAGS if compiler supports flag, with optional prerequisite.
|
||||||
|
# Fails on errors and warnings.
|
||||||
|
conditional_cflags() {
|
||||||
|
if [ -z "$("$CC" -xc -S -o /dev/null $2 $1 <(echo) 2>&1)" ]; then
|
||||||
|
cflags "$1"
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
error() {
|
||||||
|
status "$1" "error ... $2"
|
||||||
|
echo
|
||||||
|
exit 1
|
||||||
|
}
|
||||||
|
|
||||||
|
echo
|
||||||
|
|
||||||
|
# basic check
|
||||||
|
if ! "$CC" -xc -std=c99 <(echo "int main(){}") -o /dev/null &> /dev/null; then
|
||||||
|
error "Compiler" "$CC is no C compiler"
|
||||||
|
fi
|
||||||
|
|
||||||
|
status "Compiler" "$CC"
|
||||||
|
|
||||||
|
# init flags
|
||||||
|
CFLAGS=${CFLAGS:--O3 -fno-math-errno -funroll-loops -fomit-frame-pointer -Wall}
|
||||||
|
cflags "-std=c99 -I."
|
||||||
|
|
||||||
|
# DEBUG
|
||||||
|
if [ -z "$DEBUG" ]; then
|
||||||
|
cflags "-DNDEBUG"
|
||||||
|
status "Debug" "no"
|
||||||
|
else
|
||||||
|
cflags "-g"
|
||||||
|
status "Debug" "yes"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# SSE
|
||||||
|
if [ "$SSE" = 'auto' ]; then
|
||||||
|
if [[ "$(uname -m)" =~ (amd|x86_)64 ||
|
||||||
|
"$(grep -E -m1 "^flags" /proc/cpuinfo)" =~ "sse" ]]; then
|
||||||
|
SSE=1
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [ "$SSE" -eq 1 ]; then
|
||||||
|
status "SSE" "yes"
|
||||||
|
cflags "-DUSE_SSE=1"
|
||||||
|
cflags "-msse"
|
||||||
|
# Silence a later ICC warning due to -msse working slightly different.
|
||||||
|
conditional_cflags "-wd10121"
|
||||||
|
# Must be set explicitly for GCC on x86_32. Other compilers imply it.
|
||||||
|
conditional_cflags "-mfpmath=sse" "-msse"
|
||||||
|
elif [ "$SSE" -eq 0 ]; then
|
||||||
|
status "SSE" "no"
|
||||||
|
cflags "-DUSE_SSE=0"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# OpenMP
|
||||||
|
if [ -n "$OPENMP" ]; then
|
||||||
|
if [[ "$("$CC" -xc -E -fopenmp <(echo -e \
|
||||||
|
"#ifdef _OPENMP
|
||||||
|
#include <omp.h>
|
||||||
|
#endif") 2>&1)" =~ "omp_get_thread_num" ]]; then
|
||||||
|
cflags "-fopenmp"
|
||||||
|
lflags "-fopenmp"
|
||||||
|
status "OpenMP" "yes"
|
||||||
|
else
|
||||||
|
error "OpenMP" "not supported by compiler"
|
||||||
|
fi
|
||||||
|
else
|
||||||
|
# silence warnings about omp pragmas
|
||||||
|
cflags "-Wno-unknown-pragmas"
|
||||||
|
conditional_cflags "-wd3180" # ICC
|
||||||
|
status "OpenMP" "no"
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo
|
||||||
|
|
||||||
|
# As of GCC 4.5, 387 fp math is significantly slower in C99 mode without this.
|
||||||
|
# Note: CPUs without SSE2 use 387 for doubles, even when SSE fp math is set.
|
||||||
|
conditional_cflags "-fexcess-precision=fast"
|
||||||
|
|
||||||
|
# Intel C++ Compiler
|
||||||
|
|
||||||
|
# ICC does usually only produce fast(er) code when it can optimize to the full
|
||||||
|
# capabilites of the (Intel) CPU. This is equivalent to -march=native for GCC.
|
||||||
|
conditional_cflags "-xHOST"
|
||||||
|
|
||||||
|
# Disable unsafe fp optimizations and enforce fp precision as set in the source.
|
||||||
|
conditional_cflags "-fp-model source"
|
||||||
|
|
||||||
|
# Silence a gold linker warning about string misalignment.
|
||||||
|
conditional_cflags "-falign-stack=maintain-16-byte"
|
||||||
|
|
||||||
|
lflags "-lm" # Ubuntu requires this library last, issue #38
|
||||||
|
|
||||||
|
if [ -n "$EXTRA_CFLAGS" ]; then
|
||||||
|
cflags "$EXTRA_CFLAGS"
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [ -n "$EXTRA_LDFLAGS" ]; then
|
||||||
|
lflags "$EXTRA_LDFLAGS"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Overwrite previous configuration.
|
||||||
|
echo "
|
||||||
|
# auto-generated by configure
|
||||||
|
PREFIX = $PREFIX
|
||||||
|
VERSION = $VERSION
|
||||||
|
CC = $CC
|
||||||
|
CFLAGS = $CFLAGS
|
||||||
|
LDFLAGS = $LDFLAGS
|
||||||
|
" > $CONFIG
|
1708
deps/pngquant/lib/libimagequant.c
vendored
Normal file
1708
deps/pngquant/lib/libimagequant.c
vendored
Normal file
File diff suppressed because it is too large
Load diff
110
deps/pngquant/lib/libimagequant.h
vendored
Normal file
110
deps/pngquant/lib/libimagequant.h
vendored
Normal file
|
@ -0,0 +1,110 @@
|
||||||
|
/*
|
||||||
|
* http://pngquant.org
|
||||||
|
*/
|
||||||
|
|
||||||
|
#ifndef LIBIMAGEQUANT_H
|
||||||
|
#define LIBIMAGEQUANT_H
|
||||||
|
|
||||||
|
#ifndef LIQ_EXPORT
|
||||||
|
#define LIQ_EXPORT extern
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#define LIQ_VERSION 20401
|
||||||
|
#define LIQ_VERSION_STRING "2.4.1"
|
||||||
|
|
||||||
|
#ifndef LIQ_PRIVATE
|
||||||
|
#if defined(__GNUC__) || defined (__llvm__)
|
||||||
|
#define LIQ_PRIVATE __attribute__((visibility("hidden")))
|
||||||
|
#else
|
||||||
|
#define LIQ_PRIVATE
|
||||||
|
#endif
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#ifdef __cplusplus
|
||||||
|
extern "C" {
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#include <stddef.h>
|
||||||
|
|
||||||
|
typedef struct liq_attr liq_attr;
|
||||||
|
typedef struct liq_image liq_image;
|
||||||
|
typedef struct liq_result liq_result;
|
||||||
|
|
||||||
|
typedef struct liq_color {
|
||||||
|
unsigned char r, g, b, a;
|
||||||
|
} liq_color;
|
||||||
|
|
||||||
|
typedef struct liq_palette {
|
||||||
|
unsigned int count;
|
||||||
|
liq_color entries[256];
|
||||||
|
} liq_palette;
|
||||||
|
|
||||||
|
typedef enum liq_error {
|
||||||
|
LIQ_OK = 0,
|
||||||
|
LIQ_QUALITY_TOO_LOW = 99,
|
||||||
|
LIQ_VALUE_OUT_OF_RANGE = 100,
|
||||||
|
LIQ_OUT_OF_MEMORY,
|
||||||
|
LIQ_NOT_READY,
|
||||||
|
LIQ_BITMAP_NOT_AVAILABLE,
|
||||||
|
LIQ_BUFFER_TOO_SMALL,
|
||||||
|
LIQ_INVALID_POINTER,
|
||||||
|
} liq_error;
|
||||||
|
|
||||||
|
enum liq_ownership {LIQ_OWN_ROWS=4, LIQ_OWN_PIXELS=8};
|
||||||
|
|
||||||
|
LIQ_EXPORT liq_attr* liq_attr_create(void);
|
||||||
|
LIQ_EXPORT liq_attr* liq_attr_create_with_allocator(void* (*malloc)(size_t), void (*free)(void*));
|
||||||
|
LIQ_EXPORT liq_attr* liq_attr_copy(liq_attr *orig);
|
||||||
|
LIQ_EXPORT void liq_attr_destroy(liq_attr *attr);
|
||||||
|
|
||||||
|
LIQ_EXPORT liq_error liq_set_max_colors(liq_attr* attr, int colors);
|
||||||
|
LIQ_EXPORT int liq_get_max_colors(const liq_attr* attr);
|
||||||
|
LIQ_EXPORT liq_error liq_set_speed(liq_attr* attr, int speed);
|
||||||
|
LIQ_EXPORT int liq_get_speed(const liq_attr* attr);
|
||||||
|
LIQ_EXPORT liq_error liq_set_min_opacity(liq_attr* attr, int min);
|
||||||
|
LIQ_EXPORT int liq_get_min_opacity(const liq_attr* attr);
|
||||||
|
LIQ_EXPORT liq_error liq_set_min_posterization(liq_attr* attr, int bits);
|
||||||
|
LIQ_EXPORT int liq_get_min_posterization(const liq_attr* attr);
|
||||||
|
LIQ_EXPORT liq_error liq_set_quality(liq_attr* attr, int minimum, int maximum);
|
||||||
|
LIQ_EXPORT int liq_get_min_quality(const liq_attr* attr);
|
||||||
|
LIQ_EXPORT int liq_get_max_quality(const liq_attr* attr);
|
||||||
|
LIQ_EXPORT void liq_set_last_index_transparent(liq_attr* attr, int is_last);
|
||||||
|
|
||||||
|
typedef void liq_log_callback_function(const liq_attr*, const char *message, void* user_info);
|
||||||
|
typedef void liq_log_flush_callback_function(const liq_attr*, void* user_info);
|
||||||
|
LIQ_EXPORT void liq_set_log_callback(liq_attr*, liq_log_callback_function*, void* user_info);
|
||||||
|
LIQ_EXPORT void liq_set_log_flush_callback(liq_attr*, liq_log_flush_callback_function*, void* user_info);
|
||||||
|
|
||||||
|
LIQ_EXPORT liq_image *liq_image_create_rgba_rows(liq_attr *attr, void* rows[], int width, int height, double gamma);
|
||||||
|
LIQ_EXPORT liq_image *liq_image_create_rgba(liq_attr *attr, void* bitmap, int width, int height, double gamma);
|
||||||
|
|
||||||
|
typedef void liq_image_get_rgba_row_callback(liq_color row_out[], int row, int width, void* user_info);
|
||||||
|
LIQ_EXPORT liq_image *liq_image_create_custom(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_EXPORT liq_error liq_image_add_fixed_color(liq_image *img, liq_color color);
|
||||||
|
LIQ_EXPORT int liq_image_get_width(const liq_image *img);
|
||||||
|
LIQ_EXPORT int liq_image_get_height(const liq_image *img);
|
||||||
|
LIQ_EXPORT void liq_image_destroy(liq_image *img);
|
||||||
|
|
||||||
|
LIQ_EXPORT liq_result *liq_quantize_image(liq_attr *options, liq_image *input_image);
|
||||||
|
|
||||||
|
LIQ_EXPORT liq_error liq_set_dithering_level(liq_result *res, float dither_level);
|
||||||
|
LIQ_EXPORT liq_error liq_set_output_gamma(liq_result* res, double gamma);
|
||||||
|
LIQ_EXPORT double liq_get_output_gamma(const liq_result *result);
|
||||||
|
|
||||||
|
LIQ_EXPORT 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_EXPORT liq_error liq_write_remapped_image_rows(liq_result *result, liq_image *input_image, unsigned char **row_pointers);
|
||||||
|
|
||||||
|
LIQ_EXPORT double liq_get_quantization_error(liq_result *result);
|
||||||
|
LIQ_EXPORT int liq_get_quantization_quality(liq_result *result);
|
||||||
|
|
||||||
|
LIQ_EXPORT void liq_result_destroy(liq_result *);
|
||||||
|
|
||||||
|
#ifdef __cplusplus
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#endif
|
507
deps/pngquant/lib/mediancut.c
vendored
Normal file
507
deps/pngquant/lib/mediancut.c
vendored
Normal file
|
@ -0,0 +1,507 @@
|
||||||
|
/*
|
||||||
|
** Copyright (C) 1989, 1991 by Jef Poskanzer.
|
||||||
|
** Copyright (C) 1997, 2000, 2002 by Greg Roelofs; based on an idea by
|
||||||
|
** Stefan Schneider.
|
||||||
|
** © 2009-2013 by Kornel Lesinski.
|
||||||
|
**
|
||||||
|
** 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.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include <stdlib.h>
|
||||||
|
#include <stddef.h>
|
||||||
|
|
||||||
|
#include "libimagequant.h"
|
||||||
|
#include "pam.h"
|
||||||
|
#include "mediancut.h"
|
||||||
|
|
||||||
|
#define index_of_channel(ch) (offsetof(f_pixel,ch)/sizeof(float))
|
||||||
|
|
||||||
|
static f_pixel averagepixels(unsigned int clrs, const hist_item achv[], float min_opaque_val, const f_pixel center);
|
||||||
|
|
||||||
|
struct box {
|
||||||
|
f_pixel color;
|
||||||
|
f_pixel variance;
|
||||||
|
double sum, total_error, max_error;
|
||||||
|
unsigned int ind;
|
||||||
|
unsigned int colors;
|
||||||
|
};
|
||||||
|
|
||||||
|
ALWAYS_INLINE static double variance_diff(double val, const double good_enough);
|
||||||
|
inline static double variance_diff(double val, const double good_enough)
|
||||||
|
{
|
||||||
|
val *= val;
|
||||||
|
if (val < good_enough*good_enough) return val*0.25;
|
||||||
|
return val;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Weighted per-channel variance of the box. It's used to decide which channel to split by */
|
||||||
|
static f_pixel box_variance(const hist_item achv[], const struct box *box)
|
||||||
|
{
|
||||||
|
f_pixel mean = box->color;
|
||||||
|
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;
|
||||||
|
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;
|
||||||
|
varianceg += variance_diff(mean.g - px.g, 1.0/256.0)*weight;
|
||||||
|
varianceb += variance_diff(mean.b - px.b, 1.0/256.0)*weight;
|
||||||
|
}
|
||||||
|
|
||||||
|
return (f_pixel){
|
||||||
|
.a = variancea*(4.0/16.0),
|
||||||
|
.r = variancer*(7.0/16.0),
|
||||||
|
.g = varianceg*(9.0/16.0),
|
||||||
|
.b = varianceb*(5.0/16.0),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
static double box_max_error(const hist_item achv[], const struct box *box)
|
||||||
|
{
|
||||||
|
f_pixel mean = box->color;
|
||||||
|
double max_error = 0;
|
||||||
|
|
||||||
|
for(unsigned int i = 0; i < box->colors; ++i) {
|
||||||
|
const double diff = colordifference(mean, achv[box->ind + i].acolor);
|
||||||
|
if (diff > max_error) {
|
||||||
|
max_error = diff;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return max_error;
|
||||||
|
}
|
||||||
|
|
||||||
|
ALWAYS_INLINE static double color_weight(f_pixel median, hist_item h);
|
||||||
|
|
||||||
|
static inline void hist_item_swap(hist_item *l, hist_item *r)
|
||||||
|
{
|
||||||
|
if (l != r) {
|
||||||
|
hist_item t = *l;
|
||||||
|
*l = *r;
|
||||||
|
*r = t;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ALWAYS_INLINE static unsigned int qsort_pivot(const hist_item *const base, const unsigned int len);
|
||||||
|
inline static unsigned int qsort_pivot(const hist_item *const base, const unsigned int len)
|
||||||
|
{
|
||||||
|
if (len < 32) {
|
||||||
|
return len/2;
|
||||||
|
}
|
||||||
|
|
||||||
|
const unsigned int aidx=8, bidx=len/2, cidx=len-1;
|
||||||
|
const unsigned int a=base[aidx].tmp.sort_value, b=base[bidx].tmp.sort_value, c=base[cidx].tmp.sort_value;
|
||||||
|
return (a < b) ? ((b < c) ? bidx : ((a < c) ? cidx : aidx ))
|
||||||
|
: ((b > c) ? bidx : ((a < c) ? aidx : cidx ));
|
||||||
|
}
|
||||||
|
|
||||||
|
ALWAYS_INLINE static unsigned int qsort_partition(hist_item *const base, const unsigned int len);
|
||||||
|
inline static unsigned int qsort_partition(hist_item *const base, const unsigned int len)
|
||||||
|
{
|
||||||
|
unsigned int l = 1, r = len;
|
||||||
|
if (len >= 8) {
|
||||||
|
hist_item_swap(&base[0], &base[qsort_pivot(base,len)]);
|
||||||
|
}
|
||||||
|
|
||||||
|
const unsigned int pivot_value = base[0].tmp.sort_value;
|
||||||
|
while (l < r) {
|
||||||
|
if (base[l].tmp.sort_value >= pivot_value) {
|
||||||
|
l++;
|
||||||
|
} else {
|
||||||
|
while(l < --r && base[r].tmp.sort_value <= pivot_value) {}
|
||||||
|
hist_item_swap(&base[l], &base[r]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
l--;
|
||||||
|
hist_item_swap(&base[0], &base[l]);
|
||||||
|
|
||||||
|
return l;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** quick select algorithm */
|
||||||
|
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;
|
||||||
|
|
||||||
|
if (l > 0 && sort_start < l) {
|
||||||
|
len = l;
|
||||||
|
}
|
||||||
|
else if (r < len && sort_start > r) {
|
||||||
|
base += r; len -= r; sort_start -= r;
|
||||||
|
}
|
||||||
|
else break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 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)
|
||||||
|
{
|
||||||
|
do {
|
||||||
|
const unsigned int l = qsort_partition(base, len), r = l+1;
|
||||||
|
|
||||||
|
// check if sum of left side is smaller than half,
|
||||||
|
// if it is, then it doesn't need to be sorted
|
||||||
|
unsigned int t = 0; double tmpsum = *lowervar;
|
||||||
|
while (t <= l && tmpsum < halfvar) tmpsum += base[t++].color_weight;
|
||||||
|
|
||||||
|
if (tmpsum < halfvar) {
|
||||||
|
*lowervar = tmpsum;
|
||||||
|
} else {
|
||||||
|
if (l > 0) {
|
||||||
|
hist_item *res = hist_item_sort_halfvar(base, l, lowervar, halfvar);
|
||||||
|
if (res) return res;
|
||||||
|
} else {
|
||||||
|
// End of left recursion. This will be executed in order from the first element.
|
||||||
|
*lowervar += base[0].color_weight;
|
||||||
|
if (*lowervar > halfvar) return &base[0];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (len > r) {
|
||||||
|
base += r; len -= r; // tail-recursive "call"
|
||||||
|
} else {
|
||||||
|
*lowervar += base[r].color_weight;
|
||||||
|
return (*lowervar > halfvar) ? &base[r] : NULL;
|
||||||
|
}
|
||||||
|
} while(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
static f_pixel get_median(const struct box *b, hist_item achv[]);
|
||||||
|
|
||||||
|
typedef struct {
|
||||||
|
unsigned int chan; float variance;
|
||||||
|
} channelvariance;
|
||||||
|
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Finds which channels need to be sorted first and preproceses achv for fast sort */
|
||||||
|
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(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;
|
||||||
|
// 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);
|
||||||
|
}
|
||||||
|
|
||||||
|
const f_pixel median = get_median(b, 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;
|
||||||
|
for(unsigned int j=ind; j < end; j++) totalvar += (achv[j].color_weight = color_weight(median, achv[j]));
|
||||||
|
return totalvar / 2.0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** finds median in unsorted set by sorting only minimum required */
|
||||||
|
static f_pixel get_median(const struct box *b, hist_item achv[])
|
||||||
|
{
|
||||||
|
const unsigned int median_start = (b->colors-1)/2;
|
||||||
|
|
||||||
|
hist_item_sort_range(&(achv[b->ind]), b->colors,
|
||||||
|
median_start);
|
||||||
|
|
||||||
|
if (b->colors&1) return achv[b->ind + median_start].acolor;
|
||||||
|
|
||||||
|
// 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], 1.0, (f_pixel){0.5,0.5,0.5,0.5});
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
** 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)
|
||||||
|
{
|
||||||
|
int bi=-1; double maxsum=0;
|
||||||
|
for(unsigned int i=0; i < boxes; i++) {
|
||||||
|
if (bv[i].colors < 2) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// looks only at max variance, because it's only going to split by it
|
||||||
|
const double cv = MAX(bv[i].variance.r, MAX(bv[i].variance.g,bv[i].variance.b));
|
||||||
|
double thissum = bv[i].sum * MAX(bv[i].variance.a, cv);
|
||||||
|
|
||||||
|
if (bv[i].max_error > max_mse) {
|
||||||
|
thissum = thissum* bv[i].max_error/max_mse;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (thissum > maxsum) {
|
||||||
|
maxsum = thissum;
|
||||||
|
bi = i;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return bi;
|
||||||
|
}
|
||||||
|
|
||||||
|
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 < 2.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);
|
||||||
|
|
||||||
|
double box_error(const struct box *box, const hist_item achv[])
|
||||||
|
{
|
||||||
|
f_pixel avg = box->color;
|
||||||
|
|
||||||
|
double total_error=0;
|
||||||
|
for (unsigned int i = 0; i < box->colors; ++i) {
|
||||||
|
total_error += colordifference(avg, achv[box->ind + i].acolor) * achv[box->ind + i].perceptual_weight;
|
||||||
|
}
|
||||||
|
|
||||||
|
return total_error;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
static bool total_box_error_below_target(double target_mse, struct box bv[], unsigned int boxes, const histogram *hist)
|
||||||
|
{
|
||||||
|
target_mse *= hist->total_perceptual_weight;
|
||||||
|
double total_error=0;
|
||||||
|
|
||||||
|
for(unsigned int i=0; i < boxes; i++) {
|
||||||
|
// error is (re)calculated lazily
|
||||||
|
if (bv[i].total_error >= 0) {
|
||||||
|
total_error += bv[i].total_error;
|
||||||
|
}
|
||||||
|
if (total_error > target_mse) return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
for(unsigned int i=0; i < boxes; i++) {
|
||||||
|
if (bv[i].total_error < 0) {
|
||||||
|
bv[i].total_error = box_error(&bv[i], hist->achv);
|
||||||
|
total_error += bv[i].total_error;
|
||||||
|
}
|
||||||
|
if (total_error > target_mse) return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
** Here is the fun part, the median-cut colormap generator. This is based
|
||||||
|
** on Paul Heckbert's paper, "Color Image Quantization for Frame Buffer
|
||||||
|
** Display," SIGGRAPH 1982 Proceedings, page 297.
|
||||||
|
*/
|
||||||
|
LIQ_PRIVATE colormap *mediancut(histogram *hist, const float min_opaque_val, 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];
|
||||||
|
|
||||||
|
/*
|
||||||
|
** 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], min_opaque_val, (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;
|
||||||
|
|
||||||
|
unsigned int boxes = 1;
|
||||||
|
|
||||||
|
// remember smaller palette for fast searching
|
||||||
|
colormap *representative_subset = NULL;
|
||||||
|
unsigned int subset_size = ceilf(powf(newcolors,0.7f));
|
||||||
|
|
||||||
|
/*
|
||||||
|
** Main loop: split boxes until we have enough.
|
||||||
|
*/
|
||||||
|
while (boxes < newcolors) {
|
||||||
|
|
||||||
|
if (boxes == subset_size) {
|
||||||
|
representative_subset = pam_colormap(boxes, malloc, free);
|
||||||
|
set_colormap_from_boxes(representative_subset, bv, boxes, achv);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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.
|
||||||
|
*/
|
||||||
|
|
||||||
|
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;
|
||||||
|
|
||||||
|
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], min_opaque_val, 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], min_opaque_val, 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]);
|
||||||
|
|
||||||
|
++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);
|
||||||
|
|
||||||
|
map->subset_palette = representative_subset;
|
||||||
|
adjust_histogram(achv, map, bv, boxes);
|
||||||
|
|
||||||
|
return map;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void set_colormap_from_boxes(colormap *map, struct box* bv, unsigned int boxes, hist_item *achv)
|
||||||
|
{
|
||||||
|
/*
|
||||||
|
** Ok, we've got enough boxes. Now choose a representative color for
|
||||||
|
** each box. There are a number of possible ways to make this choice.
|
||||||
|
** One would be to choose the center of the box; this ignores any structure
|
||||||
|
** within the boxes. Another method would be to average all the colors in
|
||||||
|
** the box - this is the method specified in Heckbert's paper.
|
||||||
|
*/
|
||||||
|
|
||||||
|
for(unsigned int bi = 0; bi < boxes; ++bi) {
|
||||||
|
map->palette[bi].acolor = bv[bi].color;
|
||||||
|
|
||||||
|
/* store total color popularity (perceptual_weight is approximation of it) */
|
||||||
|
map->palette[bi].popularity = 0;
|
||||||
|
for(unsigned int i=bv[bi].ind; i < bv[bi].ind+bv[bi].colors; i++) {
|
||||||
|
map->palette[bi].popularity += achv[i].perceptual_weight;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 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)
|
||||||
|
{
|
||||||
|
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)/4.0);
|
||||||
|
achv[i].tmp.likely_colormap_index = bi;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static f_pixel averagepixels(unsigned int clrs, const hist_item achv[], const float min_opaque_val, const f_pixel center)
|
||||||
|
{
|
||||||
|
double r = 0, g = 0, b = 0, a = 0, new_a=0, sum = 0;
|
||||||
|
float maxa = 0;
|
||||||
|
|
||||||
|
// first find final opacity in order to blend colors at that opacity
|
||||||
|
for(unsigned int i = 0; i < clrs; ++i) {
|
||||||
|
const f_pixel px = achv[i].acolor;
|
||||||
|
new_a += px.a * achv[i].adjusted_weight;
|
||||||
|
sum += achv[i].adjusted_weight;
|
||||||
|
|
||||||
|
/* find if there are opaque colors, in case we're supposed to preserve opacity exactly (ie_bug) */
|
||||||
|
if (px.a > maxa) maxa = px.a;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (sum) new_a /= sum;
|
||||||
|
|
||||||
|
/** if there was at least one completely opaque color, "round" final color to opaque */
|
||||||
|
if (new_a >= min_opaque_val && maxa >= (255.0/256.0)) new_a = 1;
|
||||||
|
|
||||||
|
sum=0;
|
||||||
|
// reverse iteration for cache locality with previous loop
|
||||||
|
for(int i = clrs-1; i >= 0; i--) {
|
||||||
|
double tmp, weight = 1.0f;
|
||||||
|
f_pixel px = achv[i].acolor;
|
||||||
|
|
||||||
|
/* give more weight to colors that are further away from average
|
||||||
|
this is intended to prevent desaturation of images and fading of whites
|
||||||
|
*/
|
||||||
|
tmp = (center.r - px.r);
|
||||||
|
weight += tmp*tmp;
|
||||||
|
tmp = (center.g - px.g);
|
||||||
|
weight += tmp*tmp;
|
||||||
|
tmp = (center.b - px.b);
|
||||||
|
weight += tmp*tmp;
|
||||||
|
|
||||||
|
weight *= achv[i].adjusted_weight;
|
||||||
|
sum += weight;
|
||||||
|
|
||||||
|
if (px.a) {
|
||||||
|
px.r /= px.a;
|
||||||
|
px.g /= px.a;
|
||||||
|
px.b /= px.a;
|
||||||
|
}
|
||||||
|
|
||||||
|
r += px.r * new_a * weight;
|
||||||
|
g += px.g * new_a * weight;
|
||||||
|
b += px.b * new_a * weight;
|
||||||
|
a += new_a * weight;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (sum) {
|
||||||
|
a /= sum;
|
||||||
|
r /= sum;
|
||||||
|
g /= sum;
|
||||||
|
b /= sum;
|
||||||
|
}
|
||||||
|
|
||||||
|
assert(!isnan(r) && !isnan(g) && !isnan(b) && !isnan(a));
|
||||||
|
|
||||||
|
return (f_pixel){.r=r, .g=g, .b=b, .a=a};
|
||||||
|
}
|
2
deps/pngquant/lib/mediancut.h
vendored
Normal file
2
deps/pngquant/lib/mediancut.h
vendored
Normal file
|
@ -0,0 +1,2 @@
|
||||||
|
|
||||||
|
LIQ_PRIVATE colormap *mediancut(histogram *hist, const float min_opaque_val, unsigned int newcolors, const double target_mse, const double max_mse, void* (*malloc)(size_t), void (*free)(void*));
|
63
deps/pngquant/lib/mempool.c
vendored
Normal file
63
deps/pngquant/lib/mempool.c
vendored
Normal file
|
@ -0,0 +1,63 @@
|
||||||
|
|
||||||
|
#include "libimagequant.h"
|
||||||
|
#include "mempool.h"
|
||||||
|
#include <stdlib.h>
|
||||||
|
#include <stdint.h>
|
||||||
|
#include <assert.h>
|
||||||
|
|
||||||
|
#define ALIGN_MASK 15UL
|
||||||
|
#define MEMPOOL_RESERVED ((sizeof(struct mempool)+ALIGN_MASK) & ~ALIGN_MASK)
|
||||||
|
|
||||||
|
struct mempool {
|
||||||
|
unsigned int used, size;
|
||||||
|
void* (*malloc)(size_t);
|
||||||
|
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*))
|
||||||
|
{
|
||||||
|
if (*mptr && ((*mptr)->used+size) <= (*mptr)->size) {
|
||||||
|
unsigned int prevused = (*mptr)->used;
|
||||||
|
(*mptr)->used += (size+15UL) & ~0xFUL;
|
||||||
|
return ((char*)(*mptr)) + prevused;
|
||||||
|
}
|
||||||
|
|
||||||
|
mempool old = *mptr;
|
||||||
|
if (!max_size) max_size = (1<<17);
|
||||||
|
max_size = size+ALIGN_MASK > max_size ? size+ALIGN_MASK : max_size;
|
||||||
|
|
||||||
|
*mptr = malloc(MEMPOOL_RESERVED + max_size);
|
||||||
|
if (!*mptr) return NULL;
|
||||||
|
**mptr = (struct mempool){
|
||||||
|
.malloc = malloc,
|
||||||
|
.free = free,
|
||||||
|
.size = MEMPOOL_RESERVED + max_size,
|
||||||
|
.used = sizeof(struct mempool),
|
||||||
|
.next = old,
|
||||||
|
};
|
||||||
|
uintptr_t mptr_used_start = (uintptr_t)(*mptr) + (*mptr)->used;
|
||||||
|
(*mptr)->used += (ALIGN_MASK + 1 - (mptr_used_start & ALIGN_MASK)) & ALIGN_MASK; // reserve bytes required to make subsequent allocations aligned
|
||||||
|
assert(!(((uintptr_t)(*mptr) + (*mptr)->used) & ALIGN_MASK));
|
||||||
|
|
||||||
|
return mempool_alloc(mptr, size, size);
|
||||||
|
}
|
||||||
|
|
||||||
|
LIQ_PRIVATE void* mempool_alloc(mempool *mptr, unsigned int size, unsigned int max_size)
|
||||||
|
{
|
||||||
|
if (((*mptr)->used+size) <= (*mptr)->size) {
|
||||||
|
unsigned int prevused = (*mptr)->used;
|
||||||
|
(*mptr)->used += (size + ALIGN_MASK) & ~ALIGN_MASK;
|
||||||
|
return ((char*)(*mptr)) + prevused;
|
||||||
|
}
|
||||||
|
|
||||||
|
return mempool_create(mptr, size, max_size, (*mptr)->malloc, (*mptr)->free);
|
||||||
|
}
|
||||||
|
|
||||||
|
LIQ_PRIVATE void mempool_destroy(mempool m)
|
||||||
|
{
|
||||||
|
while (m) {
|
||||||
|
mempool next = m->next;
|
||||||
|
m->free(m);
|
||||||
|
m = next;
|
||||||
|
}
|
||||||
|
}
|
13
deps/pngquant/lib/mempool.h
vendored
Normal file
13
deps/pngquant/lib/mempool.h
vendored
Normal file
|
@ -0,0 +1,13 @@
|
||||||
|
#ifndef MEMPOOL_H
|
||||||
|
#define MEMPOOL_H
|
||||||
|
|
||||||
|
#include <stddef.h>
|
||||||
|
|
||||||
|
struct mempool;
|
||||||
|
typedef struct mempool *mempool;
|
||||||
|
|
||||||
|
LIQ_PRIVATE void* mempool_create(mempool *mptr, unsigned int size, unsigned int capacity, void* (*malloc)(size_t), void (*free)(void*));
|
||||||
|
LIQ_PRIVATE void* mempool_alloc(mempool *mptr, unsigned int size, unsigned int capacity);
|
||||||
|
LIQ_PRIVATE void mempool_destroy(mempool m);
|
||||||
|
|
||||||
|
#endif
|
207
deps/pngquant/lib/nearest.c
vendored
Normal file
207
deps/pngquant/lib/nearest.c
vendored
Normal file
|
@ -0,0 +1,207 @@
|
||||||
|
|
||||||
|
#include "libimagequant.h"
|
||||||
|
#include "pam.h"
|
||||||
|
#include "nearest.h"
|
||||||
|
#include "mempool.h"
|
||||||
|
#include <stdlib.h>
|
||||||
|
|
||||||
|
struct sorttmp {
|
||||||
|
float radius;
|
||||||
|
unsigned int index;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct head {
|
||||||
|
// colors less than radius away from vantage_point color will have best match in candidates
|
||||||
|
f_pixel vantage_point;
|
||||||
|
float radius;
|
||||||
|
unsigned int num_candidates;
|
||||||
|
f_pixel *candidates_color;
|
||||||
|
unsigned short *candidates_index;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct nearest_map {
|
||||||
|
const colormap *map;
|
||||||
|
float nearest_other_color_dist[256];
|
||||||
|
mempool mempool;
|
||||||
|
struct head heads[];
|
||||||
|
};
|
||||||
|
|
||||||
|
static float distance_from_nearest_other_color(const colormap *map, const unsigned int i)
|
||||||
|
{
|
||||||
|
float second_best=MAX_DIFF;
|
||||||
|
for(unsigned int j=0; j < map->colors; j++) {
|
||||||
|
if (i == j) continue;
|
||||||
|
float diff = colordifference(map->palette[i].acolor, map->palette[j].acolor);
|
||||||
|
if (diff <= second_best) {
|
||||||
|
second_best = diff;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return second_best;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int compareradius(const void *ap, const void *bp)
|
||||||
|
{
|
||||||
|
float a = ((const struct sorttmp*)ap)->radius;
|
||||||
|
float b = ((const struct sorttmp*)bp)->radius;
|
||||||
|
return a > b ? 1 : (a < b ? -1 : 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
static struct head build_head(f_pixel px, const colormap *map, unsigned int num_candidates, mempool *m, float error_margin, bool skip_index[], unsigned int *skipped)
|
||||||
|
{
|
||||||
|
struct sorttmp colors[map->colors];
|
||||||
|
unsigned int colorsused=0;
|
||||||
|
|
||||||
|
for(unsigned int i=0; i < map->colors; i++) {
|
||||||
|
if (skip_index[i]) continue; // colors in skip_index have been eliminated already in previous heads
|
||||||
|
colors[colorsused].index = i;
|
||||||
|
colors[colorsused].radius = colordifference(px, map->palette[i].acolor);
|
||||||
|
colorsused++;
|
||||||
|
}
|
||||||
|
|
||||||
|
qsort(&colors, colorsused, sizeof(colors[0]), compareradius);
|
||||||
|
assert(colorsused < 2 || colors[0].radius <= colors[1].radius); // closest first
|
||||||
|
|
||||||
|
num_candidates = MIN(colorsused, num_candidates);
|
||||||
|
|
||||||
|
struct head h = {
|
||||||
|
.candidates_color = mempool_alloc(m, num_candidates * sizeof(h.candidates_color[0]), 0),
|
||||||
|
.candidates_index = mempool_alloc(m, num_candidates * sizeof(h.candidates_index[0]), 0),
|
||||||
|
.vantage_point = px,
|
||||||
|
.num_candidates = num_candidates,
|
||||||
|
};
|
||||||
|
for(unsigned int i=0; i < num_candidates; i++) {
|
||||||
|
h.candidates_color[i] = map->palette[colors[i].index].acolor;
|
||||||
|
h.candidates_index[i] = colors[i].index;
|
||||||
|
}
|
||||||
|
// if all colors within this radius are included in candidates, then there cannot be any other better match
|
||||||
|
// farther away from the vantage point than half of the radius. Due to alpha channel must assume pessimistic radius.
|
||||||
|
h.radius = min_colordifference(px, h.candidates_color[num_candidates-1])/4.0f; // /4 = half of radius, but radius is squared
|
||||||
|
|
||||||
|
for(unsigned int i=0; i < num_candidates; i++) {
|
||||||
|
// divide again as that's matching certain subset within radius-limited subset
|
||||||
|
// - 1/256 is a tolerance for miscalculation (seems like colordifference isn't exact)
|
||||||
|
if (colors[i].radius < h.radius/4.f - error_margin) {
|
||||||
|
skip_index[colors[i].index]=true;
|
||||||
|
(*skipped)++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return h;
|
||||||
|
}
|
||||||
|
|
||||||
|
static colormap *get_subset_palette(const colormap *map)
|
||||||
|
{
|
||||||
|
if (map->subset_palette) {
|
||||||
|
return map->subset_palette;
|
||||||
|
}
|
||||||
|
|
||||||
|
unsigned int subset_size = (map->colors+3)/4;
|
||||||
|
colormap *subset_palette = pam_colormap(subset_size, map->malloc, map->free);
|
||||||
|
|
||||||
|
for(unsigned int i=0; i < subset_size; i++) {
|
||||||
|
subset_palette->palette[i] = map->palette[i];
|
||||||
|
}
|
||||||
|
|
||||||
|
return subset_palette;
|
||||||
|
}
|
||||||
|
|
||||||
|
LIQ_PRIVATE struct nearest_map *nearest_init(const colormap *map, bool fast)
|
||||||
|
{
|
||||||
|
colormap *subset_palette = get_subset_palette(map);
|
||||||
|
const unsigned int num_vantage_points = map->colors > 16 ? MIN(map->colors/(fast ? 4 : 3), subset_palette->colors) : 0;
|
||||||
|
const unsigned long heads_size = sizeof(struct head) * (num_vantage_points+1); // +1 is fallback head
|
||||||
|
|
||||||
|
const unsigned long mempool_size = (sizeof(f_pixel) + sizeof(unsigned int)) * subset_palette->colors * map->colors/5 + (1<<14);
|
||||||
|
mempool m = NULL;
|
||||||
|
struct nearest_map *centroids = mempool_create(&m, sizeof(*centroids) + heads_size /* heads array is appended to it */, mempool_size, map->malloc, map->free);
|
||||||
|
centroids->mempool = m;
|
||||||
|
|
||||||
|
for(unsigned int i=0; i < map->colors; i++) {
|
||||||
|
const float dist = distance_from_nearest_other_color(map,i);
|
||||||
|
centroids->nearest_other_color_dist[i] = dist / 4.f; // half of squared distance
|
||||||
|
}
|
||||||
|
|
||||||
|
centroids->map = map;
|
||||||
|
|
||||||
|
unsigned int skipped=0;
|
||||||
|
assert(map->colors > 0);
|
||||||
|
bool skip_index[map->colors]; for(unsigned int j=0; j < map->colors; j++) skip_index[j]=false;
|
||||||
|
|
||||||
|
// floats and colordifference calculations are not perfect
|
||||||
|
const float error_margin = fast ? 0 : 8.f/256.f/256.f;
|
||||||
|
unsigned int h=0;
|
||||||
|
for(; h < num_vantage_points; h++) {
|
||||||
|
unsigned int num_candiadtes = 1+(map->colors - skipped)/((1+num_vantage_points-h)/2);
|
||||||
|
|
||||||
|
centroids->heads[h] = build_head(subset_palette->palette[h].acolor, map, num_candiadtes, ¢roids->mempool, error_margin, skip_index, &skipped);
|
||||||
|
if (centroids->heads[h].num_candidates == 0) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// assumption that there is no better color within radius of vantage point color
|
||||||
|
// holds true only for colors within convex hull formed by palette colors.
|
||||||
|
// The fallback must contain all colors, since there are too many edge cases to cover.
|
||||||
|
if (!fast) for(unsigned int j=0; j < map->colors; j++) {
|
||||||
|
skip_index[j] = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
centroids->heads[h] = build_head((f_pixel){0,0,0,0}, map, map->colors, ¢roids->mempool, error_margin, skip_index, &skipped);
|
||||||
|
centroids->heads[h].radius = MAX_DIFF;
|
||||||
|
|
||||||
|
// get_subset_palette could have created a copy
|
||||||
|
if (subset_palette != map->subset_palette) {
|
||||||
|
pam_freecolormap(subset_palette);
|
||||||
|
}
|
||||||
|
|
||||||
|
return centroids;
|
||||||
|
}
|
||||||
|
|
||||||
|
LIQ_PRIVATE unsigned int nearest_search(const struct nearest_map *centroids, const f_pixel px, int likely_colormap_index, const float min_opaque_val, float *diff)
|
||||||
|
{
|
||||||
|
const bool iebug = px.a > min_opaque_val;
|
||||||
|
|
||||||
|
const struct head *const heads = centroids->heads;
|
||||||
|
|
||||||
|
assert(likely_colormap_index < centroids->map->colors);
|
||||||
|
const float guess_diff = colordifference(centroids->map->palette[likely_colormap_index].acolor, px);
|
||||||
|
if (guess_diff < centroids->nearest_other_color_dist[likely_colormap_index]) {
|
||||||
|
if (diff) *diff = guess_diff;
|
||||||
|
return likely_colormap_index;
|
||||||
|
}
|
||||||
|
|
||||||
|
for(unsigned int i=0; /* last head will always be selected */ ; i++) {
|
||||||
|
float vantage_point_dist = colordifference(px, heads[i].vantage_point);
|
||||||
|
|
||||||
|
if (vantage_point_dist <= heads[i].radius) {
|
||||||
|
assert(heads[i].num_candidates);
|
||||||
|
unsigned int ind=0;
|
||||||
|
float dist = colordifference(px, heads[i].candidates_color[0]);
|
||||||
|
|
||||||
|
/* penalty for making holes in IE */
|
||||||
|
if (iebug && heads[i].candidates_color[0].a < 1) {
|
||||||
|
dist += 1.f/1024.f;
|
||||||
|
}
|
||||||
|
|
||||||
|
for(unsigned int j=1; j < heads[i].num_candidates; j++) {
|
||||||
|
float newdist = colordifference(px, heads[i].candidates_color[j]);
|
||||||
|
|
||||||
|
/* penalty for making holes in IE */
|
||||||
|
if (iebug && heads[i].candidates_color[j].a < 1) {
|
||||||
|
newdist += 1.f/1024.f;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (newdist < dist) {
|
||||||
|
dist = newdist;
|
||||||
|
ind = j;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (diff) *diff = dist;
|
||||||
|
return heads[i].candidates_index[ind];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
LIQ_PRIVATE void nearest_free(struct nearest_map *centroids)
|
||||||
|
{
|
||||||
|
mempool_destroy(centroids->mempool);
|
||||||
|
}
|
8
deps/pngquant/lib/nearest.h
vendored
Normal file
8
deps/pngquant/lib/nearest.h
vendored
Normal file
|
@ -0,0 +1,8 @@
|
||||||
|
//
|
||||||
|
// nearest.h
|
||||||
|
// pngquant
|
||||||
|
//
|
||||||
|
struct nearest_map;
|
||||||
|
LIQ_PRIVATE struct nearest_map *nearest_init(const colormap *palette, const bool fast);
|
||||||
|
LIQ_PRIVATE unsigned int nearest_search(const struct nearest_map *map, const f_pixel px, const int palette_index_guess, const float min_opaque, float *diff);
|
||||||
|
LIQ_PRIVATE void nearest_free(struct nearest_map *map);
|
278
deps/pngquant/lib/pam.c
vendored
Normal file
278
deps/pngquant/lib/pam.c
vendored
Normal file
|
@ -0,0 +1,278 @@
|
||||||
|
/* 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-2013 by Kornel Lesinski.
|
||||||
|
**
|
||||||
|
** 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.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include <stdlib.h>
|
||||||
|
#include <string.h>
|
||||||
|
|
||||||
|
#include "libimagequant.h"
|
||||||
|
#include "pam.h"
|
||||||
|
#include "mempool.h"
|
||||||
|
|
||||||
|
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 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;
|
||||||
|
}
|
||||||
|
|
||||||
|
// RGBA color is casted to long for easier hasing/comparisons
|
||||||
|
union rgba_as_int px = {pixels[row][col]};
|
||||||
|
unsigned int hash;
|
||||||
|
if (!px.rgba.a) {
|
||||||
|
// "dirty alpha" has different RGBA values that end up being the same fully transparent color
|
||||||
|
px.l=0; hash=0;
|
||||||
|
} 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;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 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];
|
||||||
|
if (achl->inline1.color.l == px.l && achl->used) {
|
||||||
|
achl->inline1.perceptual_weight += boost;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (achl->used) {
|
||||||
|
if (achl->used > 1) {
|
||||||
|
if (achl->inline2.color.l == px.l) {
|
||||||
|
achl->inline2.perceptual_weight += boost;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
// other items are stored as an array (which gets reallocated if needed)
|
||||||
|
struct acolorhist_arr_item *other_items = achl->other_items;
|
||||||
|
unsigned int i = 0;
|
||||||
|
for (; i < achl->used-2; i++) {
|
||||||
|
if (other_items[i].color.l == px.l) {
|
||||||
|
other_items[i].perceptual_weight += boost;
|
||||||
|
goto continue_outer_loop;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// the array was allocated with spare items
|
||||||
|
if (i < achl->capacity) {
|
||||||
|
other_items[i] = (struct acolorhist_arr_item){
|
||||||
|
.color = px,
|
||||||
|
.perceptual_weight = boost,
|
||||||
|
};
|
||||||
|
achl->used++;
|
||||||
|
++colors;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (++colors > maxacolors) {
|
||||||
|
acht->colors = colors;
|
||||||
|
acht->freestackp = freestackp;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
struct acolorhist_arr_item *new_items;
|
||||||
|
unsigned int capacity;
|
||||||
|
if (!other_items) { // there was no array previously, alloc "small" array
|
||||||
|
capacity = 8;
|
||||||
|
if (freestackp <= 0) {
|
||||||
|
// estimate how many colors are going to be + headroom
|
||||||
|
const int mempool_size = ((acht->rows + rows-row) * 2 * 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];
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// simply reallocs and copies array to larger capacity
|
||||||
|
capacity = achl->capacity*2 + 16;
|
||||||
|
if (freestackp < stacksize-1) {
|
||||||
|
freestack[freestackp++] = other_items;
|
||||||
|
}
|
||||||
|
const int mempool_size = ((acht->rows + rows-row) * 2 * 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);
|
||||||
|
}
|
||||||
|
|
||||||
|
achl->other_items = new_items;
|
||||||
|
achl->capacity = capacity;
|
||||||
|
new_items[i] = (struct acolorhist_arr_item){
|
||||||
|
.color = px,
|
||||||
|
.perceptual_weight = boost,
|
||||||
|
};
|
||||||
|
achl->used++;
|
||||||
|
} else {
|
||||||
|
// these are elses for first checks whether first and second inline-stored colors are used
|
||||||
|
achl->inline2.color.l = px.l;
|
||||||
|
achl->inline2.perceptual_weight = boost;
|
||||||
|
achl->used = 2;
|
||||||
|
++colors;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
achl->inline1.color.l = px.l;
|
||||||
|
achl->inline1.perceptual_weight = boost;
|
||||||
|
achl->used = 1;
|
||||||
|
++colors;
|
||||||
|
}
|
||||||
|
|
||||||
|
continue_outer_loop:;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
acht->colors = colors;
|
||||||
|
acht->cols = cols;
|
||||||
|
acht->rows += rows;
|
||||||
|
acht->freestackp = freestackp;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
LIQ_PRIVATE struct acolorhash_table *pam_allocacolorhash(unsigned int maxcolors, unsigned int surface, unsigned int ignorebits, void* (*malloc)(size_t), void (*free)(void*))
|
||||||
|
{
|
||||||
|
const unsigned int estimated_colors = MIN(maxcolors, surface/(ignorebits + (surface > 512*512 ? 5 : 4)));
|
||||||
|
const unsigned int hash_size = estimated_colors < 66000 ? 6673 : (estimated_colors < 200000 ? 12011 : 24019);
|
||||||
|
|
||||||
|
mempool m = NULL;
|
||||||
|
const unsigned int buckets_size = hash_size * sizeof(struct acolorhist_arr_head);
|
||||||
|
const unsigned int 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);
|
||||||
|
if (!t) return NULL;
|
||||||
|
*t = (struct acolorhash_table){
|
||||||
|
.mempool = m,
|
||||||
|
.hash_size = hash_size,
|
||||||
|
.maxcolors = maxcolors,
|
||||||
|
.ignorebits = ignorebits,
|
||||||
|
};
|
||||||
|
memset(t->buckets, 0, hash_size * sizeof(struct acolorhist_arr_head));
|
||||||
|
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; \
|
||||||
|
}
|
||||||
|
|
||||||
|
LIQ_PRIVATE histogram *pam_acolorhashtoacolorhist(const struct acolorhash_table *acht, const double gamma, void* (*malloc)(size_t), void (*free)(void*))
|
||||||
|
{
|
||||||
|
histogram *hist = malloc(sizeof(hist[0]));
|
||||||
|
if (!hist || !acht) return NULL;
|
||||||
|
*hist = (histogram){
|
||||||
|
.achv = malloc(acht->colors * sizeof(hist->achv[0])),
|
||||||
|
.size = acht->colors,
|
||||||
|
.free = free,
|
||||||
|
.ignorebits = acht->ignorebits,
|
||||||
|
};
|
||||||
|
if (!hist->achv) return NULL;
|
||||||
|
|
||||||
|
float gamma_lut[256];
|
||||||
|
to_f_set_gamma(gamma_lut, gamma);
|
||||||
|
|
||||||
|
/* Limit perceptual weight to 1/10th of the image surface area to prevent
|
||||||
|
a single color from dominating all others. */
|
||||||
|
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) {
|
||||||
|
const struct acolorhist_arr_head *const achl = &acht->buckets[i];
|
||||||
|
if (achl->used) {
|
||||||
|
PAM_ADD_TO_HIST(achl->inline1);
|
||||||
|
|
||||||
|
if (achl->used > 1) {
|
||||||
|
PAM_ADD_TO_HIST(achl->inline2);
|
||||||
|
|
||||||
|
for(unsigned int k=0; k < achl->used-2; k++) {
|
||||||
|
PAM_ADD_TO_HIST(achl->other_items[k]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
hist->total_perceptual_weight = total_weight;
|
||||||
|
return hist;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
LIQ_PRIVATE void pam_freeacolorhash(struct acolorhash_table *acht)
|
||||||
|
{
|
||||||
|
mempool_destroy(acht->mempool);
|
||||||
|
}
|
||||||
|
|
||||||
|
LIQ_PRIVATE void pam_freeacolorhist(histogram *hist)
|
||||||
|
{
|
||||||
|
hist->free(hist->achv);
|
||||||
|
hist->free(hist);
|
||||||
|
}
|
||||||
|
|
||||||
|
LIQ_PRIVATE colormap *pam_colormap(unsigned int colors, void* (*malloc)(size_t), void (*free)(void*))
|
||||||
|
{
|
||||||
|
assert(colors > 0 && colors < 65536);
|
||||||
|
|
||||||
|
colormap *map;
|
||||||
|
const size_t colors_size = colors * sizeof(map->palette[0]);
|
||||||
|
map = malloc(sizeof(colormap) + colors_size);
|
||||||
|
if (!map) return NULL;
|
||||||
|
*map = (colormap){
|
||||||
|
.malloc = malloc,
|
||||||
|
.free = free,
|
||||||
|
.subset_palette = NULL,
|
||||||
|
.colors = colors,
|
||||||
|
};
|
||||||
|
memset(map->palette, 0, colors_size);
|
||||||
|
return map;
|
||||||
|
}
|
||||||
|
|
||||||
|
LIQ_PRIVATE colormap *pam_duplicate_colormap(colormap *map)
|
||||||
|
{
|
||||||
|
colormap *dupe = pam_colormap(map->colors, map->malloc, map->free);
|
||||||
|
for(unsigned int i=0; i < map->colors; i++) {
|
||||||
|
dupe->palette[i] = map->palette[i];
|
||||||
|
}
|
||||||
|
if (map->subset_palette) {
|
||||||
|
dupe->subset_palette = pam_duplicate_colormap(map->subset_palette);
|
||||||
|
}
|
||||||
|
return dupe;
|
||||||
|
}
|
||||||
|
|
||||||
|
LIQ_PRIVATE void pam_freecolormap(colormap *c)
|
||||||
|
{
|
||||||
|
if (c->subset_palette) pam_freecolormap(c->subset_palette);
|
||||||
|
c->free(c);
|
||||||
|
}
|
||||||
|
|
||||||
|
LIQ_PRIVATE void to_f_set_gamma(float gamma_lut[], const double gamma)
|
||||||
|
{
|
||||||
|
for(int i=0; i < 256; i++) {
|
||||||
|
gamma_lut[i] = pow((double)i/255.0, internal_gamma/gamma);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
291
deps/pngquant/lib/pam.h
vendored
Normal file
291
deps/pngquant/lib/pam.h
vendored
Normal file
|
@ -0,0 +1,291 @@
|
||||||
|
/* pam.h - pam (portable alpha map) utility library
|
||||||
|
**
|
||||||
|
** Colormap routines.
|
||||||
|
**
|
||||||
|
** Copyright (C) 1989, 1991 by Jef Poskanzer.
|
||||||
|
** Copyright (C) 1997 by Greg Roelofs.
|
||||||
|
**
|
||||||
|
** 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.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#ifndef PAM_H
|
||||||
|
#define PAM_H
|
||||||
|
|
||||||
|
#include <math.h>
|
||||||
|
#include <assert.h>
|
||||||
|
#include <stdlib.h>
|
||||||
|
#include <stdbool.h>
|
||||||
|
|
||||||
|
#ifndef MAX
|
||||||
|
# define MAX(a,b) ((a) > (b)? (a) : (b))
|
||||||
|
# define MIN(a,b) ((a) < (b)? (a) : (b))
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#define MAX_DIFF 1e20
|
||||||
|
|
||||||
|
#ifndef USE_SSE
|
||||||
|
# if defined(__SSE__) && (defined(WIN32) || defined(__WIN32__))
|
||||||
|
# define USE_SSE 1
|
||||||
|
# else
|
||||||
|
# define USE_SSE 0
|
||||||
|
# endif
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#if USE_SSE
|
||||||
|
# include <xmmintrin.h>
|
||||||
|
# ifdef _MSC_VER
|
||||||
|
# include <intrin.h>
|
||||||
|
# define SSE_ALIGN
|
||||||
|
# else
|
||||||
|
# define SSE_ALIGN __attribute__ ((aligned (16)))
|
||||||
|
# if defined(__i386__) && defined(__PIC__)
|
||||||
|
# define cpuid(func,ax,bx,cx,dx)\
|
||||||
|
__asm__ __volatile__ ( \
|
||||||
|
"push %%ebx\n" \
|
||||||
|
"cpuid\n" \
|
||||||
|
"mov %%ebx, %1\n" \
|
||||||
|
"pop %%ebx\n" \
|
||||||
|
: "=a" (ax), "=r" (bx), "=c" (cx), "=d" (dx) \
|
||||||
|
: "a" (func));
|
||||||
|
# else
|
||||||
|
# define cpuid(func,ax,bx,cx,dx)\
|
||||||
|
__asm__ __volatile__ ("cpuid":\
|
||||||
|
"=a" (ax), "=b" (bx), "=c" (cx), "=d" (dx) : "a" (func));
|
||||||
|
# endif
|
||||||
|
#endif
|
||||||
|
#else
|
||||||
|
# define SSE_ALIGN
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#if defined(__GNUC__) || defined (__llvm__)
|
||||||
|
#define ALWAYS_INLINE __attribute__((always_inline)) inline
|
||||||
|
#define NEVER_INLINE __attribute__ ((noinline))
|
||||||
|
#elif defined(_MSC_VER)
|
||||||
|
#define inline __inline
|
||||||
|
#define restrict __restrict
|
||||||
|
#define ALWAYS_INLINE __forceinline
|
||||||
|
#define NEVER_INLINE __declspec(noinline)
|
||||||
|
#else
|
||||||
|
#define ALWAYS_INLINE inline
|
||||||
|
#define NEVER_INLINE
|
||||||
|
#endif
|
||||||
|
|
||||||
|
/* from pam.h */
|
||||||
|
|
||||||
|
typedef struct {
|
||||||
|
unsigned char r, g, b, a;
|
||||||
|
} rgba_pixel;
|
||||||
|
|
||||||
|
typedef struct {
|
||||||
|
float a, r, g, b;
|
||||||
|
} SSE_ALIGN f_pixel;
|
||||||
|
|
||||||
|
static const double internal_gamma = 0.5499;
|
||||||
|
|
||||||
|
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)
|
||||||
|
{
|
||||||
|
float a = px.a/255.f;
|
||||||
|
|
||||||
|
return (f_pixel) {
|
||||||
|
.a = a,
|
||||||
|
.r = gamma_lut[px.r]*a,
|
||||||
|
.g = gamma_lut[px.g]*a,
|
||||||
|
.b = gamma_lut[px.b]*a,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
inline static rgba_pixel to_rgb(const float gamma, const f_pixel px)
|
||||||
|
{
|
||||||
|
if (px.a < 1.f/256.f) {
|
||||||
|
return (rgba_pixel){0,0,0,0};
|
||||||
|
}
|
||||||
|
|
||||||
|
float r = px.r / px.a,
|
||||||
|
g = px.g / px.a,
|
||||||
|
b = px.b / px.a,
|
||||||
|
a = px.a;
|
||||||
|
|
||||||
|
r = powf(r, gamma/internal_gamma);
|
||||||
|
g = powf(g, gamma/internal_gamma);
|
||||||
|
b = powf(b, gamma/internal_gamma);
|
||||||
|
|
||||||
|
// 256, because numbers are in range 1..255.9999… rounded down
|
||||||
|
r *= 256.f;
|
||||||
|
g *= 256.f;
|
||||||
|
b *= 256.f;
|
||||||
|
a *= 256.f;
|
||||||
|
|
||||||
|
return (rgba_pixel){
|
||||||
|
.r = r>=255.f ? 255 : r,
|
||||||
|
.g = g>=255.f ? 255 : g,
|
||||||
|
.b = b>=255.f ? 255 : b,
|
||||||
|
.a = a>=255.f ? 255 : a,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
ALWAYS_INLINE static double colordifference_ch(const double x, const double y, const double alphas);
|
||||||
|
inline static double colordifference_ch(const double x, const double y, const double alphas)
|
||||||
|
{
|
||||||
|
// maximum of channel blended on white, and blended on black
|
||||||
|
// premultiplied alpha and backgrounds 0/1 shorten the formula
|
||||||
|
const double black = x-y, white = black+alphas;
|
||||||
|
return black*black + white*white;
|
||||||
|
}
|
||||||
|
|
||||||
|
ALWAYS_INLINE static float colordifference_stdc(const f_pixel px, const f_pixel py);
|
||||||
|
inline static float colordifference_stdc(const f_pixel px, const f_pixel py)
|
||||||
|
{
|
||||||
|
// px_b.rgb = px.rgb + 0*(1-px.a) // blend px on black
|
||||||
|
// px_b.a = px.a + 1*(1-px.a)
|
||||||
|
// px_w.rgb = px.rgb + 1*(1-px.a) // blend px on white
|
||||||
|
// px_w.a = px.a + 1*(1-px.a)
|
||||||
|
|
||||||
|
// px_b.rgb = px.rgb // difference same as in opaque RGB
|
||||||
|
// px_b.a = 1
|
||||||
|
// px_w.rgb = px.rgb - px.a // difference simplifies to formula below
|
||||||
|
// px_w.a = 1
|
||||||
|
|
||||||
|
// (px.rgb - px.a) - (py.rgb - py.a)
|
||||||
|
// (px.rgb - py.rgb) + (py.a - px.a)
|
||||||
|
|
||||||
|
const double alphas = py.a-px.a;
|
||||||
|
return colordifference_ch(px.r, py.r, alphas) +
|
||||||
|
colordifference_ch(px.g, py.g, alphas) +
|
||||||
|
colordifference_ch(px.b, py.b, alphas);
|
||||||
|
}
|
||||||
|
|
||||||
|
ALWAYS_INLINE static double min_colordifference_ch(const double x, const double y, const double alphas);
|
||||||
|
inline static double min_colordifference_ch(const double x, const double y, const double alphas)
|
||||||
|
{
|
||||||
|
const double black = x-y, white = black+alphas;
|
||||||
|
return MIN(black*black , white*white) * 2.f;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* least possible difference between colors (difference varies depending on background they're blended on) */
|
||||||
|
ALWAYS_INLINE static float min_colordifference(const f_pixel px, const f_pixel py);
|
||||||
|
inline static float min_colordifference(const f_pixel px, const f_pixel py)
|
||||||
|
{
|
||||||
|
const double alphas = py.a-px.a;
|
||||||
|
return min_colordifference_ch(px.r, py.r, alphas) +
|
||||||
|
min_colordifference_ch(px.g, py.g, alphas) +
|
||||||
|
min_colordifference_ch(px.b, py.b, alphas);
|
||||||
|
}
|
||||||
|
|
||||||
|
ALWAYS_INLINE static float colordifference(f_pixel px, f_pixel py);
|
||||||
|
inline static float colordifference(f_pixel px, f_pixel py)
|
||||||
|
{
|
||||||
|
#if USE_SSE
|
||||||
|
const __m128 vpx = _mm_load_ps((const float*)&px);
|
||||||
|
const __m128 vpy = _mm_load_ps((const float*)&py);
|
||||||
|
|
||||||
|
// y.a - x.a
|
||||||
|
__m128 alphas = _mm_sub_ss(vpy, vpx);
|
||||||
|
alphas = _mm_shuffle_ps(alphas,alphas,0); // copy first to all four
|
||||||
|
|
||||||
|
__m128 onblack = _mm_sub_ps(vpx, vpy); // x - y
|
||||||
|
__m128 onwhite = _mm_add_ps(onblack, alphas); // x - y + (y.a - x.a)
|
||||||
|
|
||||||
|
onblack = _mm_mul_ps(onblack, onblack);
|
||||||
|
onwhite = _mm_mul_ps(onwhite, onwhite);
|
||||||
|
const __m128 max = _mm_add_ps(onwhite, onblack);
|
||||||
|
|
||||||
|
// add rgb, not a
|
||||||
|
const __m128 maxhl = _mm_movehl_ps(max, max);
|
||||||
|
const __m128 tmp = _mm_add_ps(max, maxhl);
|
||||||
|
const __m128 sum = _mm_add_ss(maxhl, _mm_shuffle_ps(tmp, tmp, 1));
|
||||||
|
|
||||||
|
const float res = _mm_cvtss_f32(sum);
|
||||||
|
assert(fabs(res - colordifference_stdc(px,py)) < 0.001);
|
||||||
|
return res;
|
||||||
|
#else
|
||||||
|
return colordifference_stdc(px,py);
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
/* from pamcmap.h */
|
||||||
|
union rgba_as_int {
|
||||||
|
rgba_pixel rgba;
|
||||||
|
unsigned int l;
|
||||||
|
};
|
||||||
|
|
||||||
|
typedef struct {
|
||||||
|
f_pixel acolor;
|
||||||
|
float adjusted_weight, // perceptual weight changed to tweak how mediancut selects colors
|
||||||
|
perceptual_weight; // number of pixels weighted by importance of different areas of the picture
|
||||||
|
|
||||||
|
float color_weight; // these two change every time histogram subset is sorted
|
||||||
|
union {
|
||||||
|
unsigned int sort_value;
|
||||||
|
unsigned char likely_colormap_index;
|
||||||
|
} tmp;
|
||||||
|
} hist_item;
|
||||||
|
|
||||||
|
typedef struct {
|
||||||
|
hist_item *achv;
|
||||||
|
void (*free)(void*);
|
||||||
|
double total_perceptual_weight;
|
||||||
|
unsigned int size;
|
||||||
|
unsigned int ignorebits;
|
||||||
|
} histogram;
|
||||||
|
|
||||||
|
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)
|
||||||
|
} colormap_item;
|
||||||
|
|
||||||
|
typedef struct colormap {
|
||||||
|
unsigned int colors;
|
||||||
|
void* (*malloc)(size_t);
|
||||||
|
void (*free)(void*);
|
||||||
|
struct colormap *subset_palette;
|
||||||
|
colormap_item palette[];
|
||||||
|
} colormap;
|
||||||
|
|
||||||
|
struct acolorhist_arr_item {
|
||||||
|
union rgba_as_int color;
|
||||||
|
float perceptual_weight;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct acolorhist_arr_head {
|
||||||
|
unsigned int used, capacity;
|
||||||
|
struct {
|
||||||
|
union rgba_as_int color;
|
||||||
|
float perceptual_weight;
|
||||||
|
} inline1, inline2;
|
||||||
|
struct acolorhist_arr_item *other_items;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct acolorhash_table {
|
||||||
|
struct mempool *mempool;
|
||||||
|
unsigned int ignorebits, maxcolors, colors, cols, rows;
|
||||||
|
unsigned int hash_size;
|
||||||
|
unsigned int freestackp;
|
||||||
|
struct acolorhist_arr_item *freestack[512];
|
||||||
|
struct acolorhist_arr_head buckets[];
|
||||||
|
};
|
||||||
|
|
||||||
|
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 void pam_freeacolorhist(histogram *h);
|
||||||
|
|
||||||
|
LIQ_PRIVATE colormap *pam_colormap(unsigned int colors, void* (*malloc)(size_t), void (*free)(void*));
|
||||||
|
LIQ_PRIVATE colormap *pam_duplicate_colormap(colormap *map);
|
||||||
|
LIQ_PRIVATE void pam_freecolormap(colormap *c);
|
||||||
|
|
||||||
|
#endif
|
91
deps/pngquant/lib/viter.c
vendored
Normal file
91
deps/pngquant/lib/viter.c
vendored
Normal file
|
@ -0,0 +1,91 @@
|
||||||
|
|
||||||
|
#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,
|
||||||
|
};
|
||||||
|
} else {
|
||||||
|
total = i/1024.0;
|
||||||
|
}
|
||||||
|
map->palette[i].popularity = total;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
LIQ_PRIVATE double viter_do_iteration(histogram *hist, colormap *const map, const float min_opaque_val, 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, min_opaque_val, &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
deps/pngquant/lib/viter.h
vendored
Normal file
19
deps/pngquant/lib/viter.h
vendored
Normal file
|
@ -0,0 +1,19 @@
|
||||||
|
|
||||||
|
#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, const float min_opaque_val, viter_callback callback, const bool fast_palette);
|
||||||
|
|
||||||
|
#endif
|
129
deps/pngquant/pngquant.1
vendored
Normal file
129
deps/pngquant/pngquant.1
vendored
Normal file
|
@ -0,0 +1,129 @@
|
||||||
|
.Dd 2013-02-25
|
||||||
|
.Dt pngquant 1
|
||||||
|
.Sh NAME
|
||||||
|
.Nm pngquant
|
||||||
|
.Nd PNG converter and lossy image compressor
|
||||||
|
.Sh SYNOPSIS
|
||||||
|
.Nm
|
||||||
|
.Aq options
|
||||||
|
.Op ncolors
|
||||||
|
.Pa file
|
||||||
|
.Op Ar
|
||||||
|
.Nm
|
||||||
|
.Aq options
|
||||||
|
.Op ncolors
|
||||||
|
.Fl
|
||||||
|
.Cm < Ns Pa file
|
||||||
|
.Cm > Ns Pa file
|
||||||
|
.Sh DESCRIPTION
|
||||||
|
.Nm
|
||||||
|
converts 32-bit RGBA PNGs to 8-bit (or smaller) RGBA-palette PNGs, optionally using Floyd-Steinberg dithering.
|
||||||
|
The output filename is the same as the input name except that it ends in
|
||||||
|
.Ql -fs8.png
|
||||||
|
or
|
||||||
|
.Ql -or8.png
|
||||||
|
(unless the input is stdin, in which case the quantized image will go to stdout).
|
||||||
|
The default behavior if the output file exists is to skip the conversion; use
|
||||||
|
.Fl Fl force
|
||||||
|
to overwrite.
|
||||||
|
.Sh OPTIONS
|
||||||
|
.Bl -tag -width -indent
|
||||||
|
.It Fl o Ar out.png , Fl Fl output Ar out.png
|
||||||
|
Writes converted file to the given path. When this option is used only single input file is allowed.
|
||||||
|
.It Fl Fl ext Ar new.png
|
||||||
|
File extension (suffix) to use for output files instead of the default
|
||||||
|
.Ql -fs8.png
|
||||||
|
or
|
||||||
|
.Ql -or8.png .
|
||||||
|
.It Fl f , Fl Fl force
|
||||||
|
Overwrite existing output files.
|
||||||
|
.Do
|
||||||
|
.Fl Fl ext
|
||||||
|
.Ar .png
|
||||||
|
.Fl Fl force
|
||||||
|
.Dc
|
||||||
|
can be used to convert files in place (which is unsafe).
|
||||||
|
.It Fl Fl nofs , Fl Fl ordered
|
||||||
|
Disable Floyd-Steinberg dithering.
|
||||||
|
.It Fl Fl floyd Op Ar =N
|
||||||
|
Set dithering level using fractional number between
|
||||||
|
.Cm 0
|
||||||
|
(none) and
|
||||||
|
.Cm 1
|
||||||
|
(full, the default).
|
||||||
|
.It Fl s Ar N , Fl Fl speed Ar N
|
||||||
|
.Cm 1
|
||||||
|
(brute-force) to
|
||||||
|
.Cm 11
|
||||||
|
(fastest). The default is
|
||||||
|
.Cm 3 .
|
||||||
|
Speed
|
||||||
|
.Cm 10
|
||||||
|
has 5% lower quality, but is about 8 times faster than the default. Speed 11 disables dithering and lowers compression level.
|
||||||
|
.It Fl Q Ar min-max , Fl Fl quality Ar min-max
|
||||||
|
.Va min
|
||||||
|
and
|
||||||
|
.Va max
|
||||||
|
are numbers in range
|
||||||
|
.Cm 0
|
||||||
|
(worst) to
|
||||||
|
.Cm 100
|
||||||
|
(perfect), similar to JPEG.
|
||||||
|
.Nm
|
||||||
|
will use the least amount of colors required to meet or exceed the
|
||||||
|
.Va max
|
||||||
|
quality. If conversion results in quality below the
|
||||||
|
.Va min
|
||||||
|
quality the image won't be saved (or if outputting to stdin, 24-bit original will be output) and pngquant will exit with status code
|
||||||
|
.Er 99 .
|
||||||
|
.It Fl Fl skip-if-larger
|
||||||
|
If conversion results in a file larger than the original the image won't be saved and pngquant will exit with status code
|
||||||
|
.Er 98 .
|
||||||
|
Additionally, file size gain must be greater than the amount of quality lost.
|
||||||
|
.It Fl Fl posterize Ar bits
|
||||||
|
Truncate number of least significant bits of color (per channel). Use this when image will be output on low-depth displays (e.g. 16-bit RGB).
|
||||||
|
.It Fl Fl iebug
|
||||||
|
Workaround for Internet Explorer 6, which only displays fully opaque pixels.
|
||||||
|
.Nm
|
||||||
|
will make almost-opaque pixels fully opaque and will reduce amount of semi-transparent colors. When this option is enabled the default filename suffix is
|
||||||
|
.Ql -ie-fs8.png
|
||||||
|
/
|
||||||
|
.Ql -ie-or8.png .
|
||||||
|
.It Fl Fl transbug
|
||||||
|
Workaround for readers that expect fully transparent color to be the last entry in the palette.
|
||||||
|
.It Fl v , Fl Fl verbose
|
||||||
|
Enable verbose messages showing progress and information about input/output. Opposite is
|
||||||
|
.Fl Fl quiet .
|
||||||
|
Errors are output to
|
||||||
|
.Pa stderr
|
||||||
|
regardless of this option.
|
||||||
|
.It Fl V , Fl Fl version
|
||||||
|
Display version on
|
||||||
|
.Pa stdout
|
||||||
|
and exit.
|
||||||
|
.It Fl h , Fl Fl help
|
||||||
|
Display help and exit.
|
||||||
|
.El
|
||||||
|
.Sh EXAMPLE
|
||||||
|
Creating a new image with the number of colors reduced to 64:
|
||||||
|
.Bd -ragged -offset indent
|
||||||
|
.Nm
|
||||||
|
.Cm 64 image.png
|
||||||
|
.Ed
|
||||||
|
.Pp
|
||||||
|
The resulting image will have 64 colors and will be saved as
|
||||||
|
.Pa image-fs8.png .
|
||||||
|
.Pp
|
||||||
|
Overwriting image in-place if it can be reduced without too much quality loss:
|
||||||
|
.Bd -ragged -offset indent
|
||||||
|
.Nm
|
||||||
|
.Cm -f --ext .png --quality 70-95 image.png
|
||||||
|
.Ed
|
||||||
|
.Sh AUTHOR
|
||||||
|
.Nm
|
||||||
|
is developed by Kornel Lesinski
|
||||||
|
.Aq Mt kornel@pngquant.org
|
||||||
|
based on code by Greg Roelofs
|
||||||
|
.Aq Mt newt@pobox.com .
|
||||||
|
.Pp
|
||||||
|
.Lk http://pngquant.org "pngquant homepage" .
|
864
deps/pngquant/pngquant.c
vendored
Normal file
864
deps/pngquant/pngquant.c
vendored
Normal file
|
@ -0,0 +1,864 @@
|
||||||
|
/* pngquant.c - quantize the colors in an alphamap down to a specified number
|
||||||
|
**
|
||||||
|
** Copyright (C) 1989, 1991 by Jef Poskanzer.
|
||||||
|
**
|
||||||
|
** 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.
|
||||||
|
**
|
||||||
|
** - - - -
|
||||||
|
**
|
||||||
|
** © 1997-2002 by Greg Roelofs; based on an idea by Stefan Schneider.
|
||||||
|
** © 2009-2014 by Kornel Lesiński.
|
||||||
|
**
|
||||||
|
** All rights reserved.
|
||||||
|
**
|
||||||
|
** Redistribution and use in source and binary forms, with or without modification,
|
||||||
|
** are permitted provided that the following conditions are met:
|
||||||
|
**
|
||||||
|
** 1. Redistributions of source code must retain the above copyright notice,
|
||||||
|
** this list of conditions and the following disclaimer.
|
||||||
|
**
|
||||||
|
** 2. Redistributions in binary form must reproduce the above copyright notice,
|
||||||
|
** this list of conditions and the following disclaimer in the documentation
|
||||||
|
** and/or other materials provided with the distribution.
|
||||||
|
**
|
||||||
|
** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||||
|
** AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||||
|
** IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||||
|
** DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
|
||||||
|
** FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
||||||
|
** DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
||||||
|
** SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
||||||
|
** CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
||||||
|
** OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||||
|
** OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||||
|
**
|
||||||
|
*/
|
||||||
|
|
||||||
|
#define PNGQUANT_VERSION LIQ_VERSION_STRING " (April 2015)"
|
||||||
|
|
||||||
|
#define PNGQUANT_USAGE "\
|
||||||
|
usage: pngquant [options] [ncolors] -- pngfile [pngfile ...]\n\
|
||||||
|
pngquant [options] [ncolors] - >stdout <stdin\n\n\
|
||||||
|
options:\n\
|
||||||
|
--force overwrite existing output files (synonym: -f)\n\
|
||||||
|
--skip-if-larger only save converted files if they're smaller than original\n\
|
||||||
|
--output file output path, only if one input file is specified (synonym: -o)\n\
|
||||||
|
--ext new.png set custom suffix/extension for output filenames\n\
|
||||||
|
--quality min-max don't save below min, use fewer colors below max (0-100)\n\
|
||||||
|
--speed N speed/quality trade-off. 1=slow, 3=default, 11=fast & rough\n\
|
||||||
|
--nofs disable Floyd-Steinberg dithering\n\
|
||||||
|
--posterize N output lower resolution color (e.g. for ARGB4444 output)\n\
|
||||||
|
--verbose print status messages (synonym: -v)\n\
|
||||||
|
\n\
|
||||||
|
Quantizes one or more 32-bit RGBA PNGs to 8-bit (or smaller) RGBA-palette\n\
|
||||||
|
PNGs using Floyd-Steinberg diffusion dithering (unless disabled).\n\
|
||||||
|
The output filename is the same as the input name except that\n\
|
||||||
|
it ends in \"-fs8.png\", \"-or8.png\" or your custom extension (unless the\n\
|
||||||
|
input is stdin, in which case the quantized image will go to stdout).\n\
|
||||||
|
The default behavior if the output file exists is to skip the conversion;\n\
|
||||||
|
use --force to overwrite. See man page for full list of options.\n"
|
||||||
|
|
||||||
|
|
||||||
|
#include <stdio.h>
|
||||||
|
#include <stdlib.h>
|
||||||
|
#include <string.h>
|
||||||
|
#include <stdarg.h>
|
||||||
|
#include <stdbool.h>
|
||||||
|
#include <getopt.h>
|
||||||
|
#include <unistd.h>
|
||||||
|
|
||||||
|
extern char *optarg;
|
||||||
|
extern int optind, opterr;
|
||||||
|
|
||||||
|
#if defined(WIN32) || defined(__WIN32__)
|
||||||
|
# include <fcntl.h> /* O_BINARY */
|
||||||
|
# include <io.h> /* setmode() */
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#ifdef _OPENMP
|
||||||
|
#include <omp.h>
|
||||||
|
#else
|
||||||
|
#define omp_get_max_threads() 1
|
||||||
|
#define omp_get_thread_num() 0
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#include "rwpng.h" /* typedefs, common macros, public prototypes */
|
||||||
|
#include "lib/libimagequant.h"
|
||||||
|
|
||||||
|
struct pngquant_options {
|
||||||
|
liq_attr *liq;
|
||||||
|
liq_image *fixed_palette_image;
|
||||||
|
liq_log_callback_function *log_callback;
|
||||||
|
void *log_callback_user_info;
|
||||||
|
float floyd;
|
||||||
|
bool using_stdin, using_stdout, force, fast_compression, ie_mode,
|
||||||
|
min_quality_limit, skip_if_larger,
|
||||||
|
verbose;
|
||||||
|
};
|
||||||
|
|
||||||
|
static pngquant_error prepare_output_image(liq_result *result, liq_image *input_image, png8_image *output_image);
|
||||||
|
static void set_palette(liq_result *result, png8_image *output_image);
|
||||||
|
static pngquant_error read_image(liq_attr *options, const char *filename, int using_stdin, png24_image *input_image_p, liq_image **liq_image_p, bool keep_input_pixels, bool verbose);
|
||||||
|
static pngquant_error write_image(png8_image *output_image, png24_image *output_image24, const char *outname, struct pngquant_options *options);
|
||||||
|
static char *add_filename_extension(const char *filename, const char *newext);
|
||||||
|
static bool file_exists(const char *outname);
|
||||||
|
|
||||||
|
static void verbose_printf(struct pngquant_options *context, const char *fmt, ...)
|
||||||
|
{
|
||||||
|
if (context->log_callback) {
|
||||||
|
va_list va;
|
||||||
|
va_start(va, fmt);
|
||||||
|
int required_space = vsnprintf(NULL, 0, fmt, va)+1; // +\0
|
||||||
|
va_end(va);
|
||||||
|
|
||||||
|
char buf[required_space];
|
||||||
|
va_start(va, fmt);
|
||||||
|
vsnprintf(buf, required_space, fmt, va);
|
||||||
|
va_end(va);
|
||||||
|
|
||||||
|
context->log_callback(context->liq, buf, context->log_callback_user_info);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static void log_callback(const liq_attr *attr, const char *msg, void* user_info)
|
||||||
|
{
|
||||||
|
fprintf(stderr, "%s\n", msg);
|
||||||
|
}
|
||||||
|
|
||||||
|
#ifdef _OPENMP
|
||||||
|
#define LOG_BUFFER_SIZE 1300
|
||||||
|
struct buffered_log {
|
||||||
|
int buf_used;
|
||||||
|
char buf[LOG_BUFFER_SIZE];
|
||||||
|
};
|
||||||
|
|
||||||
|
static void log_callback_buferred_flush(const liq_attr *attr, void *context)
|
||||||
|
{
|
||||||
|
struct buffered_log *log = context;
|
||||||
|
if (log->buf_used) {
|
||||||
|
fwrite(log->buf, 1, log->buf_used, stderr);
|
||||||
|
fflush(stderr);
|
||||||
|
log->buf_used = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static void log_callback_buferred(const liq_attr *attr, const char *msg, void* context)
|
||||||
|
{
|
||||||
|
struct buffered_log *log = context;
|
||||||
|
int len = strlen(msg);
|
||||||
|
if (len > LOG_BUFFER_SIZE-2) len = LOG_BUFFER_SIZE-2;
|
||||||
|
|
||||||
|
if (len > LOG_BUFFER_SIZE - log->buf_used - 2) log_callback_buferred_flush(attr, log);
|
||||||
|
memcpy(&log->buf[log->buf_used], msg, len);
|
||||||
|
log->buf_used += len+1;
|
||||||
|
log->buf[log->buf_used-1] = '\n';
|
||||||
|
log->buf[log->buf_used] = '\0';
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
static void print_full_version(FILE *fd)
|
||||||
|
{
|
||||||
|
fprintf(fd, "pngquant, %s, by Greg Roelofs, Kornel Lesinski.\n"
|
||||||
|
#ifndef NDEBUG
|
||||||
|
" DEBUG (slow) version.\n" /* NDEBUG disables assert() */
|
||||||
|
#endif
|
||||||
|
#if USE_SSE
|
||||||
|
" Compiled with SSE instructions.\n"
|
||||||
|
#endif
|
||||||
|
#if _OPENMP
|
||||||
|
" Compiled with OpenMP (multicore support).\n"
|
||||||
|
#endif
|
||||||
|
, PNGQUANT_VERSION);
|
||||||
|
rwpng_version_info(fd);
|
||||||
|
fputs("\n", fd);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void print_usage(FILE *fd)
|
||||||
|
{
|
||||||
|
fputs(PNGQUANT_USAGE, fd);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* N = automatic quality, uses limit unless force is set (N-N or 0-N)
|
||||||
|
* -N = no better than N (same as 0-N)
|
||||||
|
* N-M = no worse than N, no better than M
|
||||||
|
* N- = no worse than N, perfect if possible (same as N-100)
|
||||||
|
*
|
||||||
|
* where N,M are numbers between 0 (lousy) and 100 (perfect)
|
||||||
|
*/
|
||||||
|
static bool parse_quality(const char *quality, liq_attr *options, bool *min_quality_limit)
|
||||||
|
{
|
||||||
|
long limit, target;
|
||||||
|
const char *str = quality; char *end;
|
||||||
|
|
||||||
|
long t1 = strtol(str, &end, 10);
|
||||||
|
if (str == end) return false;
|
||||||
|
str = end;
|
||||||
|
|
||||||
|
if ('\0' == end[0] && t1 < 0) { // quality="-%d"
|
||||||
|
target = -t1;
|
||||||
|
limit = 0;
|
||||||
|
} else if ('\0' == end[0]) { // quality="%d"
|
||||||
|
target = t1;
|
||||||
|
limit = t1*9/10;
|
||||||
|
} else if ('-' == end[0] && '\0' == end[1]) { // quality="%d-"
|
||||||
|
target = 100;
|
||||||
|
limit = t1;
|
||||||
|
} else { // quality="%d-%d"
|
||||||
|
long t2 = strtol(str, &end, 10);
|
||||||
|
if (str == end || t2 > 0) return false;
|
||||||
|
target = -t2;
|
||||||
|
limit = t1;
|
||||||
|
}
|
||||||
|
|
||||||
|
*min_quality_limit = (limit > 0);
|
||||||
|
return LIQ_OK == liq_set_quality(options, limit, target);
|
||||||
|
}
|
||||||
|
|
||||||
|
static const struct {const char *old; const char *newopt;} obsolete_options[] = {
|
||||||
|
{"-fs","--floyd=1"},
|
||||||
|
{"-nofs", "--ordered"},
|
||||||
|
{"-floyd", "--floyd=1"},
|
||||||
|
{"-nofloyd", "--ordered"},
|
||||||
|
{"-ordered", "--ordered"},
|
||||||
|
{"-force", "--force"},
|
||||||
|
{"-noforce", "--no-force"},
|
||||||
|
{"-verbose", "--verbose"},
|
||||||
|
{"-quiet", "--quiet"},
|
||||||
|
{"-noverbose", "--quiet"},
|
||||||
|
{"-noquiet", "--verbose"},
|
||||||
|
{"-help", "--help"},
|
||||||
|
{"-version", "--version"},
|
||||||
|
{"-ext", "--ext"},
|
||||||
|
{"-speed", "--speed"},
|
||||||
|
};
|
||||||
|
|
||||||
|
static void fix_obsolete_options(const unsigned int argc, char *argv[])
|
||||||
|
{
|
||||||
|
for(unsigned int argn=1; argn < argc; argn++) {
|
||||||
|
if ('-' != argv[argn][0]) continue;
|
||||||
|
|
||||||
|
if ('-' == argv[argn][1]) break; // stop on first --option or --
|
||||||
|
|
||||||
|
for(unsigned int i=0; i < sizeof(obsolete_options)/sizeof(obsolete_options[0]); i++) {
|
||||||
|
if (0 == strcmp(obsolete_options[i].old, argv[argn])) {
|
||||||
|
fprintf(stderr, " warning: option '%s' has been replaced with '%s'.\n", obsolete_options[i].old, obsolete_options[i].newopt);
|
||||||
|
argv[argn] = (char*)obsolete_options[i].newopt;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
enum {arg_floyd=1, arg_ordered, arg_ext, arg_no_force, arg_iebug,
|
||||||
|
arg_transbug, arg_map, arg_posterize, arg_skip_larger};
|
||||||
|
|
||||||
|
static const struct option long_options[] = {
|
||||||
|
{"verbose", no_argument, NULL, 'v'},
|
||||||
|
{"quiet", no_argument, NULL, 'q'},
|
||||||
|
{"force", no_argument, NULL, 'f'},
|
||||||
|
{"no-force", no_argument, NULL, arg_no_force},
|
||||||
|
{"floyd", optional_argument, NULL, arg_floyd},
|
||||||
|
{"ordered", no_argument, NULL, arg_ordered},
|
||||||
|
{"nofs", no_argument, NULL, arg_ordered},
|
||||||
|
{"iebug", no_argument, NULL, arg_iebug},
|
||||||
|
{"transbug", no_argument, NULL, arg_transbug},
|
||||||
|
{"ext", required_argument, NULL, arg_ext},
|
||||||
|
{"skip-if-larger", no_argument, NULL, arg_skip_larger},
|
||||||
|
{"output", required_argument, NULL, 'o'},
|
||||||
|
{"speed", required_argument, NULL, 's'},
|
||||||
|
{"quality", required_argument, NULL, 'Q'},
|
||||||
|
{"posterize", required_argument, NULL, arg_posterize},
|
||||||
|
{"map", required_argument, NULL, arg_map},
|
||||||
|
{"version", no_argument, NULL, 'V'},
|
||||||
|
{"help", no_argument, NULL, 'h'},
|
||||||
|
{NULL, 0, NULL, 0},
|
||||||
|
};
|
||||||
|
|
||||||
|
pngquant_error pngquant_file(const char *filename, const char *outname, struct pngquant_options *options);
|
||||||
|
|
||||||
|
|
||||||
|
int main(int argc, char *argv[])
|
||||||
|
{
|
||||||
|
struct pngquant_options options = {
|
||||||
|
.floyd = 1.f, // floyd-steinberg dithering
|
||||||
|
};
|
||||||
|
options.liq = liq_attr_create();
|
||||||
|
|
||||||
|
if (!options.liq) {
|
||||||
|
fputs("SSE-capable CPU is required for this build.\n", stderr);
|
||||||
|
return WRONG_ARCHITECTURE;
|
||||||
|
}
|
||||||
|
|
||||||
|
unsigned int error_count=0, skipped_count=0, file_count=0;
|
||||||
|
pngquant_error latest_error=SUCCESS;
|
||||||
|
const char *newext = NULL, *output_file_path = NULL;
|
||||||
|
|
||||||
|
fix_obsolete_options(argc, argv);
|
||||||
|
|
||||||
|
int opt;
|
||||||
|
do {
|
||||||
|
opt = getopt_long(argc, argv, "Vvqfhs:Q:o:", long_options, NULL);
|
||||||
|
switch (opt) {
|
||||||
|
case 'v':
|
||||||
|
options.verbose = true;
|
||||||
|
break;
|
||||||
|
case 'q':
|
||||||
|
options.verbose = false;
|
||||||
|
break;
|
||||||
|
|
||||||
|
case arg_floyd:
|
||||||
|
options.floyd = optarg ? atof(optarg) : 1.0;
|
||||||
|
if (options.floyd < 0 || options.floyd > 1.f) {
|
||||||
|
fputs("--floyd argument must be in 0..1 range\n", stderr);
|
||||||
|
return INVALID_ARGUMENT;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case arg_ordered: options.floyd = 0; break;
|
||||||
|
|
||||||
|
case 'f': options.force = true; break;
|
||||||
|
case arg_no_force: options.force = false; break;
|
||||||
|
|
||||||
|
case arg_ext: newext = optarg; break;
|
||||||
|
case 'o':
|
||||||
|
if (output_file_path) {
|
||||||
|
fputs("--output option can be used only once\n", stderr);
|
||||||
|
return INVALID_ARGUMENT;
|
||||||
|
}
|
||||||
|
output_file_path = optarg; break;
|
||||||
|
|
||||||
|
case arg_iebug:
|
||||||
|
// opacities above 238 will be rounded up to 255, because IE6 truncates <255 to 0.
|
||||||
|
liq_set_min_opacity(options.liq, 238);
|
||||||
|
options.ie_mode = true;
|
||||||
|
break;
|
||||||
|
|
||||||
|
case arg_transbug:
|
||||||
|
liq_set_last_index_transparent(options.liq, true);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case arg_skip_larger:
|
||||||
|
options.skip_if_larger = true;
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 's':
|
||||||
|
{
|
||||||
|
int speed = atoi(optarg);
|
||||||
|
if (speed >= 10) {
|
||||||
|
options.fast_compression = true;
|
||||||
|
}
|
||||||
|
if (speed == 11) {
|
||||||
|
options.floyd = 0;
|
||||||
|
speed = 10;
|
||||||
|
}
|
||||||
|
if (LIQ_OK != liq_set_speed(options.liq, speed)) {
|
||||||
|
fputs("Speed should be between 1 (slow) and 11 (fast).\n", stderr);
|
||||||
|
return INVALID_ARGUMENT;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 'Q':
|
||||||
|
if (!parse_quality(optarg, options.liq, &options.min_quality_limit)) {
|
||||||
|
fputs("Quality should be in format min-max where min and max are numbers in range 0-100.\n", stderr);
|
||||||
|
return INVALID_ARGUMENT;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
case arg_posterize:
|
||||||
|
if (LIQ_OK != liq_set_min_posterization(options.liq, atoi(optarg))) {
|
||||||
|
fputs("Posterization should be number of bits in range 0-4.\n", stderr);
|
||||||
|
return INVALID_ARGUMENT;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
case arg_map:
|
||||||
|
{
|
||||||
|
png24_image tmp = {};
|
||||||
|
if (SUCCESS != read_image(options.liq, optarg, false, &tmp, &options.fixed_palette_image, false, false)) {
|
||||||
|
fprintf(stderr, " error: unable to load %s", optarg);
|
||||||
|
return INVALID_ARGUMENT;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 'h':
|
||||||
|
print_full_version(stdout);
|
||||||
|
print_usage(stdout);
|
||||||
|
return SUCCESS;
|
||||||
|
|
||||||
|
case 'V':
|
||||||
|
puts(PNGQUANT_VERSION);
|
||||||
|
return SUCCESS;
|
||||||
|
|
||||||
|
case -1: break;
|
||||||
|
|
||||||
|
default:
|
||||||
|
return INVALID_ARGUMENT;
|
||||||
|
}
|
||||||
|
} while (opt != -1);
|
||||||
|
|
||||||
|
int argn = optind;
|
||||||
|
|
||||||
|
if (argn >= argc) {
|
||||||
|
if (argn > 1) {
|
||||||
|
fputs("No input files specified.\n", stderr);
|
||||||
|
} else {
|
||||||
|
print_full_version(stderr);
|
||||||
|
}
|
||||||
|
print_usage(stderr);
|
||||||
|
return MISSING_ARGUMENT;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (options.verbose) {
|
||||||
|
liq_set_log_callback(options.liq, log_callback, NULL);
|
||||||
|
options.log_callback = log_callback;
|
||||||
|
}
|
||||||
|
|
||||||
|
char *colors_end;
|
||||||
|
unsigned long colors = strtoul(argv[argn], &colors_end, 10);
|
||||||
|
if (colors_end != argv[argn] && '\0' == colors_end[0]) {
|
||||||
|
if (LIQ_OK != liq_set_max_colors(options.liq, colors)) {
|
||||||
|
fputs("Number of colors must be between 2 and 256.\n", stderr);
|
||||||
|
return INVALID_ARGUMENT;
|
||||||
|
}
|
||||||
|
argn++;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (newext && output_file_path) {
|
||||||
|
fputs("--ext and --output options can't be used at the same time\n", stderr);
|
||||||
|
return INVALID_ARGUMENT;
|
||||||
|
}
|
||||||
|
|
||||||
|
// new filename extension depends on options used. Typically basename-fs8.png
|
||||||
|
if (newext == NULL) {
|
||||||
|
newext = options.floyd > 0 ? "-ie-fs8.png" : "-ie-or8.png";
|
||||||
|
if (!options.ie_mode) {
|
||||||
|
newext += 3; /* skip "-ie" */
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (argn == argc || (argn == argc-1 && 0==strcmp(argv[argn],"-"))) {
|
||||||
|
options.using_stdin = true;
|
||||||
|
options.using_stdout = !output_file_path;
|
||||||
|
argn = argc-1;
|
||||||
|
}
|
||||||
|
|
||||||
|
const int num_files = argc-argn;
|
||||||
|
|
||||||
|
if (output_file_path && num_files != 1) {
|
||||||
|
fputs("Only one input file is allowed when --output is used\n", stderr);
|
||||||
|
return INVALID_ARGUMENT;
|
||||||
|
}
|
||||||
|
|
||||||
|
#ifdef _OPENMP
|
||||||
|
// if there's a lot of files, coarse parallelism can be used
|
||||||
|
if (num_files > 2*omp_get_max_threads()) {
|
||||||
|
omp_set_nested(0);
|
||||||
|
omp_set_dynamic(1);
|
||||||
|
} else {
|
||||||
|
omp_set_nested(1);
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#pragma omp parallel for \
|
||||||
|
schedule(static, 1) reduction(+:skipped_count) reduction(+:error_count) reduction(+:file_count) shared(latest_error)
|
||||||
|
for(int i=0; i < num_files; i++) {
|
||||||
|
struct pngquant_options opts = options;
|
||||||
|
opts.liq = liq_attr_copy(options.liq);
|
||||||
|
|
||||||
|
const char *filename = opts.using_stdin ? "stdin" : argv[argn+i];
|
||||||
|
|
||||||
|
#ifdef _OPENMP
|
||||||
|
struct buffered_log buf = {};
|
||||||
|
if (opts.log_callback && omp_get_num_threads() > 1 && num_files > 1) {
|
||||||
|
liq_set_log_callback(opts.liq, log_callback_buferred, &buf);
|
||||||
|
liq_set_log_flush_callback(opts.liq, log_callback_buferred_flush, &buf);
|
||||||
|
options.log_callback = log_callback_buferred;
|
||||||
|
options.log_callback_user_info = &buf;
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
|
||||||
|
pngquant_error retval = SUCCESS;
|
||||||
|
|
||||||
|
const char *outname = output_file_path;
|
||||||
|
char *outname_free = NULL;
|
||||||
|
if (!options.using_stdout) {
|
||||||
|
if (!outname) {
|
||||||
|
outname = outname_free = add_filename_extension(filename, newext);
|
||||||
|
}
|
||||||
|
if (!options.force && file_exists(outname)) {
|
||||||
|
fprintf(stderr, " error: '%s' exists; not overwriting\n", outname);
|
||||||
|
retval = NOT_OVERWRITING_ERROR;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (SUCCESS == retval) {
|
||||||
|
retval = pngquant_file(filename, outname, &opts);
|
||||||
|
}
|
||||||
|
|
||||||
|
free(outname_free);
|
||||||
|
|
||||||
|
liq_attr_destroy(opts.liq);
|
||||||
|
|
||||||
|
if (retval) {
|
||||||
|
#pragma omp critical
|
||||||
|
{
|
||||||
|
latest_error = retval;
|
||||||
|
}
|
||||||
|
if (retval == TOO_LOW_QUALITY || retval == TOO_LARGE_FILE) {
|
||||||
|
skipped_count++;
|
||||||
|
} else {
|
||||||
|
error_count++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
++file_count;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (error_count) {
|
||||||
|
verbose_printf(&options, "There were errors quantizing %d file%s out of a total of %d file%s.",
|
||||||
|
error_count, (error_count == 1)? "" : "s", file_count, (file_count == 1)? "" : "s");
|
||||||
|
}
|
||||||
|
if (skipped_count) {
|
||||||
|
verbose_printf(&options, "Skipped %d file%s out of a total of %d file%s.",
|
||||||
|
skipped_count, (skipped_count == 1)? "" : "s", file_count, (file_count == 1)? "" : "s");
|
||||||
|
}
|
||||||
|
if (!skipped_count && !error_count) {
|
||||||
|
verbose_printf(&options, "No errors detected while quantizing %d image%s.",
|
||||||
|
file_count, (file_count == 1)? "" : "s");
|
||||||
|
}
|
||||||
|
|
||||||
|
liq_image_destroy(options.fixed_palette_image);
|
||||||
|
liq_attr_destroy(options.liq);
|
||||||
|
|
||||||
|
return latest_error;
|
||||||
|
}
|
||||||
|
|
||||||
|
pngquant_error pngquant_file(const char *filename, const char *outname, struct pngquant_options *options)
|
||||||
|
{
|
||||||
|
pngquant_error retval = SUCCESS;
|
||||||
|
|
||||||
|
verbose_printf(options, "%s:", filename);
|
||||||
|
|
||||||
|
liq_image *input_image = NULL;
|
||||||
|
png24_image input_image_rwpng = {};
|
||||||
|
bool keep_input_pixels = options->skip_if_larger || (options->using_stdout && options->min_quality_limit); // original may need to be output to stdout
|
||||||
|
if (SUCCESS == retval) {
|
||||||
|
retval = read_image(options->liq, filename, options->using_stdin, &input_image_rwpng, &input_image, keep_input_pixels, options->verbose);
|
||||||
|
}
|
||||||
|
|
||||||
|
int quality_percent = 90; // quality on 0-100 scale, updated upon successful remap
|
||||||
|
png8_image output_image = {};
|
||||||
|
if (SUCCESS == retval) {
|
||||||
|
verbose_printf(options, " read %luKB file", (input_image_rwpng.file_size+1023UL)/1024UL);
|
||||||
|
|
||||||
|
#if USE_LCMS
|
||||||
|
if (input_image_rwpng.lcms_status == ICCP) {
|
||||||
|
verbose_printf(options, " used embedded ICC profile to transform image to sRGB colorspace");
|
||||||
|
} else if (input_image_rwpng.lcms_status == GAMA_CHRM) {
|
||||||
|
verbose_printf(options, " used gAMA and cHRM chunks to transform image to sRGB colorspace");
|
||||||
|
} else if (input_image_rwpng.lcms_status == ICCP_WARN_GRAY) {
|
||||||
|
verbose_printf(options, " warning: ignored ICC profile in GRAY colorspace");
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
if (input_image_rwpng.gamma != 0.45455) {
|
||||||
|
verbose_printf(options, " corrected image from gamma %2.1f to sRGB gamma",
|
||||||
|
1.0/input_image_rwpng.gamma);
|
||||||
|
}
|
||||||
|
|
||||||
|
// when using image as source of a fixed palette the palette is extracted using regular quantization
|
||||||
|
liq_result *remap = liq_quantize_image(options->liq, options->fixed_palette_image ? options->fixed_palette_image : input_image);
|
||||||
|
|
||||||
|
if (remap) {
|
||||||
|
liq_set_output_gamma(remap, 0.45455); // fixed gamma ~2.2 for the web. PNG can't store exact 1/2.2
|
||||||
|
liq_set_dithering_level(remap, options->floyd);
|
||||||
|
|
||||||
|
retval = prepare_output_image(remap, input_image, &output_image);
|
||||||
|
if (SUCCESS == retval) {
|
||||||
|
if (LIQ_OK != liq_write_remapped_image_rows(remap, input_image, output_image.row_pointers)) {
|
||||||
|
retval = OUT_OF_MEMORY_ERROR;
|
||||||
|
}
|
||||||
|
|
||||||
|
set_palette(remap, &output_image);
|
||||||
|
|
||||||
|
double palette_error = liq_get_quantization_error(remap);
|
||||||
|
if (palette_error >= 0) {
|
||||||
|
quality_percent = liq_get_quantization_quality(remap);
|
||||||
|
verbose_printf(options, " mapped image to new colors...MSE=%.3f (Q=%d)", palette_error, quality_percent);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
liq_result_destroy(remap);
|
||||||
|
} else {
|
||||||
|
retval = TOO_LOW_QUALITY;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (SUCCESS == retval) {
|
||||||
|
|
||||||
|
if (options->skip_if_larger) {
|
||||||
|
// this is very rough approximation, but generally avoid losing more quality than is gained in file size.
|
||||||
|
// Quality is squared, because even greater savings are needed to justify big quality loss.
|
||||||
|
double quality = quality_percent/100.0;
|
||||||
|
output_image.maximum_file_size = (input_image_rwpng.file_size-1) * quality*quality;
|
||||||
|
}
|
||||||
|
|
||||||
|
output_image.fast_compression = options->fast_compression;
|
||||||
|
output_image.chunks = input_image_rwpng.chunks; input_image_rwpng.chunks = NULL;
|
||||||
|
retval = write_image(&output_image, NULL, outname, options);
|
||||||
|
|
||||||
|
if (TOO_LARGE_FILE == retval) {
|
||||||
|
verbose_printf(options, " file exceeded expected size of %luKB", (unsigned long)output_image.maximum_file_size/1024UL);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (options->using_stdout && keep_input_pixels && (TOO_LARGE_FILE == retval || TOO_LOW_QUALITY == retval)) {
|
||||||
|
// when outputting to stdout it'd be nasty to create 0-byte file
|
||||||
|
// so if quality is too low, output 24-bit original
|
||||||
|
pngquant_error write_retval = write_image(NULL, &input_image_rwpng, outname, options);
|
||||||
|
if (write_retval) {
|
||||||
|
retval = write_retval;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
liq_image_destroy(input_image);
|
||||||
|
rwpng_free_image24(&input_image_rwpng);
|
||||||
|
rwpng_free_image8(&output_image);
|
||||||
|
|
||||||
|
return retval;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void set_palette(liq_result *result, png8_image *output_image)
|
||||||
|
{
|
||||||
|
const liq_palette *palette = liq_get_palette(result);
|
||||||
|
|
||||||
|
// tRNS, etc.
|
||||||
|
output_image->num_palette = palette->count;
|
||||||
|
output_image->num_trans = 0;
|
||||||
|
for(unsigned int i=0; i < palette->count; i++) {
|
||||||
|
liq_color px = palette->entries[i];
|
||||||
|
if (px.a < 255) {
|
||||||
|
output_image->num_trans = i+1;
|
||||||
|
}
|
||||||
|
output_image->palette[i] = (png_color){.red=px.r, .green=px.g, .blue=px.b};
|
||||||
|
output_image->trans[i] = px.a;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
static bool file_exists(const char *outname)
|
||||||
|
{
|
||||||
|
FILE *outfile = fopen(outname, "rb");
|
||||||
|
if ((outfile ) != NULL) {
|
||||||
|
fclose(outfile);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* build the output filename from the input name by inserting "-fs8" or
|
||||||
|
* "-or8" before the ".png" extension (or by appending that plus ".png" if
|
||||||
|
* there isn't any extension), then make sure it doesn't exist already */
|
||||||
|
static char *add_filename_extension(const char *filename, const char *newext)
|
||||||
|
{
|
||||||
|
size_t x = strlen(filename);
|
||||||
|
|
||||||
|
char* outname = malloc(x+4+strlen(newext)+1);
|
||||||
|
if (!outname) return NULL;
|
||||||
|
|
||||||
|
strncpy(outname, filename, x);
|
||||||
|
if (strncmp(outname+x-4, ".png", 4) == 0 || strncmp(outname+x-4, ".PNG", 4) == 0) {
|
||||||
|
strcpy(outname+x-4, newext);
|
||||||
|
} else {
|
||||||
|
strcpy(outname+x, newext);
|
||||||
|
}
|
||||||
|
|
||||||
|
return outname;
|
||||||
|
}
|
||||||
|
|
||||||
|
static char *temp_filename(const char *basename) {
|
||||||
|
size_t x = strlen(basename);
|
||||||
|
|
||||||
|
char *outname = malloc(x+1+4);
|
||||||
|
if (!outname) return NULL;
|
||||||
|
|
||||||
|
strcpy(outname, basename);
|
||||||
|
strcpy(outname+x, ".tmp");
|
||||||
|
|
||||||
|
return outname;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void set_binary_mode(FILE *fp)
|
||||||
|
{
|
||||||
|
#if defined(WIN32) || defined(__WIN32__)
|
||||||
|
setmode(fp == stdout ? 1 : 0, O_BINARY);
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
static const char *filename_part(const char *path)
|
||||||
|
{
|
||||||
|
const char *outfilename = strrchr(path, '/');
|
||||||
|
if (outfilename) {
|
||||||
|
return outfilename+1;
|
||||||
|
} else {
|
||||||
|
return path;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool replace_file(const char *from, const char *to, const bool force) {
|
||||||
|
#if defined(WIN32) || defined(__WIN32__)
|
||||||
|
if (force) {
|
||||||
|
// On Windows rename doesn't replace
|
||||||
|
unlink(to);
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
return (0 == rename(from, to));
|
||||||
|
}
|
||||||
|
|
||||||
|
static pngquant_error write_image(png8_image *output_image, png24_image *output_image24, const char *outname, struct pngquant_options *options)
|
||||||
|
{
|
||||||
|
FILE *outfile;
|
||||||
|
char *tempname = NULL;
|
||||||
|
|
||||||
|
if (options->using_stdout) {
|
||||||
|
set_binary_mode(stdout);
|
||||||
|
outfile = stdout;
|
||||||
|
|
||||||
|
if (output_image) {
|
||||||
|
verbose_printf(options, " writing %d-color image to stdout", output_image->num_palette);
|
||||||
|
} else {
|
||||||
|
verbose_printf(options, " writing truecolor image to stdout");
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
tempname = temp_filename(outname);
|
||||||
|
if (!tempname) return OUT_OF_MEMORY_ERROR;
|
||||||
|
|
||||||
|
if ((outfile = fopen(tempname, "wb")) == NULL) {
|
||||||
|
fprintf(stderr, " error: cannot open '%s' for writing\n", tempname);
|
||||||
|
free(tempname);
|
||||||
|
return CANT_WRITE_ERROR;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (output_image) {
|
||||||
|
verbose_printf(options, " writing %d-color image as %s", output_image->num_palette, filename_part(outname));
|
||||||
|
} else {
|
||||||
|
verbose_printf(options, " writing truecolor image as %s", filename_part(outname));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pngquant_error retval;
|
||||||
|
#pragma omp critical (libpng)
|
||||||
|
{
|
||||||
|
if (output_image) {
|
||||||
|
retval = rwpng_write_image8(outfile, output_image);
|
||||||
|
} else {
|
||||||
|
retval = rwpng_write_image24(outfile, output_image24);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!options->using_stdout) {
|
||||||
|
fclose(outfile);
|
||||||
|
|
||||||
|
if (SUCCESS == retval) {
|
||||||
|
// Image has been written to a temporary file and then moved over destination.
|
||||||
|
// This makes replacement atomic and avoids damaging destination file on write error.
|
||||||
|
if (!replace_file(tempname, outname, options->force)) {
|
||||||
|
retval = CANT_WRITE_ERROR;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (retval) {
|
||||||
|
unlink(tempname);
|
||||||
|
}
|
||||||
|
free(tempname);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (retval && retval != TOO_LARGE_FILE) {
|
||||||
|
fprintf(stderr, " error: failed writing image to %s\n", outname);
|
||||||
|
}
|
||||||
|
|
||||||
|
return retval;
|
||||||
|
}
|
||||||
|
|
||||||
|
static pngquant_error read_image(liq_attr *options, const char *filename, int using_stdin, png24_image *input_image_p, liq_image **liq_image_p, bool keep_input_pixels, bool verbose)
|
||||||
|
{
|
||||||
|
FILE *infile;
|
||||||
|
|
||||||
|
if (using_stdin) {
|
||||||
|
set_binary_mode(stdin);
|
||||||
|
infile = stdin;
|
||||||
|
} else if ((infile = fopen(filename, "rb")) == NULL) {
|
||||||
|
fprintf(stderr, " error: cannot open %s for reading\n", filename);
|
||||||
|
return READ_ERROR;
|
||||||
|
}
|
||||||
|
|
||||||
|
pngquant_error retval;
|
||||||
|
#pragma omp critical (libpng)
|
||||||
|
{
|
||||||
|
retval = rwpng_read_image24(infile, input_image_p, verbose);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!using_stdin) {
|
||||||
|
fclose(infile);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (retval) {
|
||||||
|
fprintf(stderr, " error: cannot decode image %s\n", using_stdin ? "from stdin" : filename_part(filename));
|
||||||
|
return retval;
|
||||||
|
}
|
||||||
|
|
||||||
|
*liq_image_p = liq_image_create_rgba_rows(options, (void**)input_image_p->row_pointers, input_image_p->width, input_image_p->height, input_image_p->gamma);
|
||||||
|
|
||||||
|
if (!*liq_image_p) {
|
||||||
|
return OUT_OF_MEMORY_ERROR;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!keep_input_pixels) {
|
||||||
|
if (LIQ_OK != liq_image_set_memory_ownership(*liq_image_p, LIQ_OWN_ROWS | LIQ_OWN_PIXELS)) {
|
||||||
|
return OUT_OF_MEMORY_ERROR;
|
||||||
|
}
|
||||||
|
input_image_p->row_pointers = NULL;
|
||||||
|
input_image_p->rgba_data = NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
return SUCCESS;
|
||||||
|
}
|
||||||
|
|
||||||
|
static pngquant_error prepare_output_image(liq_result *result, liq_image *input_image, png8_image *output_image)
|
||||||
|
{
|
||||||
|
output_image->width = liq_image_get_width(input_image);
|
||||||
|
output_image->height = liq_image_get_height(input_image);
|
||||||
|
output_image->gamma = liq_get_output_gamma(result);
|
||||||
|
|
||||||
|
/*
|
||||||
|
** Step 3.7 [GRR]: allocate memory for the entire indexed image
|
||||||
|
*/
|
||||||
|
|
||||||
|
output_image->indexed_data = malloc(output_image->height * output_image->width);
|
||||||
|
output_image->row_pointers = malloc(output_image->height * sizeof(output_image->row_pointers[0]));
|
||||||
|
|
||||||
|
if (!output_image->indexed_data || !output_image->row_pointers) {
|
||||||
|
return OUT_OF_MEMORY_ERROR;
|
||||||
|
}
|
||||||
|
|
||||||
|
for(unsigned int row = 0; row < output_image->height; ++row) {
|
||||||
|
output_image->row_pointers[row] = output_image->indexed_data + row*output_image->width;
|
||||||
|
}
|
||||||
|
|
||||||
|
const liq_palette *palette = liq_get_palette(result);
|
||||||
|
// tRNS, etc.
|
||||||
|
output_image->num_palette = palette->count;
|
||||||
|
output_image->num_trans = 0;
|
||||||
|
for(unsigned int i=0; i < palette->count; i++) {
|
||||||
|
if (palette->entries[i].a < 255) {
|
||||||
|
output_image->num_trans = i+1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return SUCCESS;
|
||||||
|
}
|
62
deps/pngquant/pngquant.spec
vendored
Normal file
62
deps/pngquant/pngquant.spec
vendored
Normal file
|
@ -0,0 +1,62 @@
|
||||||
|
Name: pngquant
|
||||||
|
Version: 2.4.1
|
||||||
|
Release: 1%{?dist}
|
||||||
|
Summary: PNG quantization tool for reducing image file size
|
||||||
|
License: BSD
|
||||||
|
URL: http://pngquant.org
|
||||||
|
Source0: https://github.com/pornel/pngquant/archive/%{version}.tar.gz
|
||||||
|
BuildRequires: libpng-devel%{?_isa} >= 1.2.46-1
|
||||||
|
BuildRequires: zlib-devel%{?_isa} >= 1.2.3-1
|
||||||
|
BuildRequires: gcc%{?_isa} >= 4.2-1
|
||||||
|
Requires: libpng%{?_isa} >= 1.2.46-1
|
||||||
|
Requires: zlib%{?isa} >= 1.2.3-1
|
||||||
|
|
||||||
|
%description
|
||||||
|
pngquant converts 24/32-bit RGBA PNG images to 8-bit palette with
|
||||||
|
alpha channel preserved. Such images are compatible with all modern web
|
||||||
|
browsers and a compatibility setting is available to help transparency
|
||||||
|
degrade well in Internet Explorer 6. Quantized files are often 40-70
|
||||||
|
percent smaller than their 24/32-bit version. pngquant uses the
|
||||||
|
median cut algorithm.
|
||||||
|
|
||||||
|
%prep
|
||||||
|
%setup -q -n pngquant-%{version}
|
||||||
|
|
||||||
|
%build
|
||||||
|
./configure --prefix=%{_prefix}
|
||||||
|
make %{?_smp_mflags}
|
||||||
|
|
||||||
|
|
||||||
|
%install
|
||||||
|
rm -rf %{buildroot}
|
||||||
|
mkdir -p %{buildroot}/%{_bindir}
|
||||||
|
make install PREFIX=%{_prefix} DESTDIR=%{buildroot}
|
||||||
|
install -Dpm0755 pngquant %{buildroot}/%{_bindir}/pngquant
|
||||||
|
install -Dpm0644 pngquant.1 %{buildroot}/%{_mandir}/man1/pngquant.1
|
||||||
|
|
||||||
|
|
||||||
|
%files
|
||||||
|
%defattr(-,root,root,-)
|
||||||
|
%doc README.md CHANGELOG COPYRIGHT
|
||||||
|
%{_bindir}/pngquant
|
||||||
|
%{_mandir}/man1/pngquant.1*
|
||||||
|
|
||||||
|
|
||||||
|
%changelog
|
||||||
|
|
||||||
|
* Fri Sep 12 2014 Michael Dec <grepwood@sucs.org> 1.8.3-1
|
||||||
|
- Update to latest upstream version and corrected the .spec
|
||||||
|
|
||||||
|
* Thu May 03 2012 Craig Barnes <cr@igbarn.es> - 1.7.2-1
|
||||||
|
- Update to latest upstream version
|
||||||
|
|
||||||
|
* Sun Jan 15 2012 Craig Barnes <cr@igbarn.es> - 1.7.0-1
|
||||||
|
- Update to latest upstream version
|
||||||
|
|
||||||
|
* Mon Jan 09 2012 Craig Barnes <cr@igbarn.es> - 1.6.4-1
|
||||||
|
- Update to latest version
|
||||||
|
- Remove Makefile patch (merged upstream)
|
||||||
|
- Use prefix macro when installing (upstream changed the default prefix)
|
||||||
|
|
||||||
|
* Wed Dec 28 2011 Craig Barnes <cr@igbarn.es> - 1.6.2-1
|
||||||
|
- Initial package
|
615
deps/pngquant/rwpng.c
vendored
Normal file
615
deps/pngquant/rwpng.c
vendored
Normal file
|
@ -0,0 +1,615 @@
|
||||||
|
/*---------------------------------------------------------------------------
|
||||||
|
|
||||||
|
pngquant: RGBA -> RGBA-palette quantization program rwpng.c
|
||||||
|
|
||||||
|
---------------------------------------------------------------------------
|
||||||
|
|
||||||
|
© 1998-2000 by Greg Roelofs.
|
||||||
|
© 2009-2014 by Kornel Lesiński.
|
||||||
|
|
||||||
|
All rights reserved.
|
||||||
|
|
||||||
|
Redistribution and use in source and binary forms, with or without modification,
|
||||||
|
are permitted provided that the following conditions are met:
|
||||||
|
|
||||||
|
1. Redistributions of source code must retain the above copyright notice,
|
||||||
|
this list of conditions and the following disclaimer.
|
||||||
|
|
||||||
|
2. Redistributions in binary form must reproduce the above copyright notice,
|
||||||
|
this list of conditions and the following disclaimer in the documentation
|
||||||
|
and/or other materials provided with the distribution.
|
||||||
|
|
||||||
|
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||||
|
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||||
|
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||||
|
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
|
||||||
|
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
||||||
|
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
||||||
|
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
||||||
|
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
||||||
|
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||||
|
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||||
|
|
||||||
|
---------------------------------------------------------------------------*/
|
||||||
|
|
||||||
|
#include <stdio.h>
|
||||||
|
#include <stdlib.h>
|
||||||
|
#include <string.h>
|
||||||
|
|
||||||
|
#include "png.h"
|
||||||
|
#include "rwpng.h"
|
||||||
|
#if USE_LCMS
|
||||||
|
#include "lcms2.h"
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#ifndef Z_BEST_COMPRESSION
|
||||||
|
#define Z_BEST_COMPRESSION 9
|
||||||
|
#endif
|
||||||
|
#ifndef Z_BEST_SPEED
|
||||||
|
#define Z_BEST_SPEED 1
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#ifdef _OPENMP
|
||||||
|
#include <omp.h>
|
||||||
|
#else
|
||||||
|
#define omp_get_max_threads() 1
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#if PNG_LIBPNG_VER < 10500
|
||||||
|
typedef png_const_charp png_const_bytep;
|
||||||
|
#endif
|
||||||
|
|
||||||
|
static void rwpng_error_handler(png_structp png_ptr, png_const_charp msg);
|
||||||
|
static void rwpng_warning_stderr_handler(png_structp png_ptr, png_const_charp msg);
|
||||||
|
static void rwpng_warning_silent_handler(png_structp png_ptr, png_const_charp msg);
|
||||||
|
int rwpng_read_image24_cocoa(FILE *infile, png24_image *mainprog_ptr);
|
||||||
|
|
||||||
|
|
||||||
|
void rwpng_version_info(FILE *fp)
|
||||||
|
{
|
||||||
|
const char *pngver = png_get_header_ver(NULL);
|
||||||
|
|
||||||
|
#if USE_COCOA
|
||||||
|
fprintf(fp, " Using Apple Cocoa image reader and libpng %s.\n", pngver);
|
||||||
|
#elif USE_LCMS
|
||||||
|
fprintf(fp, " Using libpng %s with Little CMS color profile support.\n", pngver);
|
||||||
|
#else
|
||||||
|
fprintf(fp, " Using libpng %s.\n", pngver);
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#if PNG_LIBPNG_VER < 10600
|
||||||
|
if (strcmp(pngver, "1.3.") < 0) {
|
||||||
|
fputs("\nWARNING: Your version of libpng is outdated and may produce corrupted files.\n"
|
||||||
|
"Please recompile pngquant with the current version of libpng (1.6 or later).\n", fp);
|
||||||
|
} else if (strcmp(pngver, "1.6.") < 0) {
|
||||||
|
#if defined(PNG_UNKNOWN_CHUNKS_SUPPORTED)
|
||||||
|
fputs("\nWARNING: Your version of libpng is old and has buggy support for custom chunks.\n"
|
||||||
|
"Please recompile pngquant with the current version of libpng (1.6 or later).\n", fp);
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
struct rwpng_read_data {
|
||||||
|
FILE *const fp;
|
||||||
|
png_size_t bytes_read;
|
||||||
|
};
|
||||||
|
|
||||||
|
static void user_read_data(png_structp png_ptr, png_bytep data, png_size_t length)
|
||||||
|
{
|
||||||
|
struct rwpng_read_data *read_data = (struct rwpng_read_data *)png_get_io_ptr(png_ptr);
|
||||||
|
|
||||||
|
png_size_t read = fread(data, 1, length, read_data->fp);
|
||||||
|
if (!read) {
|
||||||
|
png_error(png_ptr, "Read error");
|
||||||
|
}
|
||||||
|
read_data->bytes_read += read;
|
||||||
|
}
|
||||||
|
|
||||||
|
struct rwpng_write_state {
|
||||||
|
FILE *outfile;
|
||||||
|
png_size_t maximum_file_size;
|
||||||
|
png_size_t bytes_written;
|
||||||
|
pngquant_error retval;
|
||||||
|
};
|
||||||
|
|
||||||
|
static void user_write_data(png_structp png_ptr, png_bytep data, png_size_t length)
|
||||||
|
{
|
||||||
|
struct rwpng_write_state *write_state = (struct rwpng_write_state *)png_get_io_ptr(png_ptr);
|
||||||
|
|
||||||
|
if (SUCCESS != write_state->retval) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!fwrite(data, length, 1, write_state->outfile)) {
|
||||||
|
write_state->retval = CANT_WRITE_ERROR;
|
||||||
|
}
|
||||||
|
|
||||||
|
write_state->bytes_written += length;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void user_flush_data(png_structp png_ptr)
|
||||||
|
{
|
||||||
|
// libpng never calls this :(
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
static png_bytepp rwpng_create_row_pointers(png_infop info_ptr, png_structp png_ptr, unsigned char *base, unsigned int height, unsigned int rowbytes)
|
||||||
|
{
|
||||||
|
if (!rowbytes) {
|
||||||
|
rowbytes = png_get_rowbytes(png_ptr, info_ptr);
|
||||||
|
}
|
||||||
|
|
||||||
|
png_bytepp row_pointers = malloc(height * sizeof(row_pointers[0]));
|
||||||
|
if (!row_pointers) return NULL;
|
||||||
|
for(unsigned int row = 0; row < height; ++row) {
|
||||||
|
row_pointers[row] = base + row * rowbytes;
|
||||||
|
}
|
||||||
|
return row_pointers;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int read_chunk_callback(png_structp png_ptr, png_unknown_chunkp in_chunk)
|
||||||
|
{
|
||||||
|
if (0 == memcmp("iCCP", in_chunk->name, 5) ||
|
||||||
|
0 == memcmp("cHRM", in_chunk->name, 5) ||
|
||||||
|
0 == memcmp("gAMA", in_chunk->name, 5)) {
|
||||||
|
return 0; // not handled
|
||||||
|
}
|
||||||
|
|
||||||
|
struct rwpng_chunk **head = (struct rwpng_chunk **)png_get_user_chunk_ptr(png_ptr);
|
||||||
|
|
||||||
|
struct rwpng_chunk *chunk = malloc(sizeof(struct rwpng_chunk));
|
||||||
|
memcpy(chunk->name, in_chunk->name, 5);
|
||||||
|
chunk->size = in_chunk->size;
|
||||||
|
chunk->location = in_chunk->location;
|
||||||
|
chunk->data = in_chunk->size ? malloc(in_chunk->size) : NULL;
|
||||||
|
if (in_chunk->size) {
|
||||||
|
memcpy(chunk->data, in_chunk->data, in_chunk->size);
|
||||||
|
}
|
||||||
|
|
||||||
|
chunk->next = *head;
|
||||||
|
*head = chunk;
|
||||||
|
|
||||||
|
return 1; // marks as "handled", libpng won't store it
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
retval:
|
||||||
|
0 = success
|
||||||
|
21 = bad sig
|
||||||
|
22 = bad IHDR
|
||||||
|
24 = insufficient memory
|
||||||
|
25 = libpng error (via longjmp())
|
||||||
|
26 = wrong PNG color type (no alpha channel)
|
||||||
|
*/
|
||||||
|
|
||||||
|
pngquant_error rwpng_read_image24_libpng(FILE *infile, png24_image *mainprog_ptr, int verbose)
|
||||||
|
{
|
||||||
|
png_structp png_ptr = NULL;
|
||||||
|
png_infop info_ptr = NULL;
|
||||||
|
png_size_t rowbytes;
|
||||||
|
int color_type, bit_depth;
|
||||||
|
|
||||||
|
png_ptr = png_create_read_struct(PNG_LIBPNG_VER_STRING, mainprog_ptr,
|
||||||
|
rwpng_error_handler, verbose ? rwpng_warning_stderr_handler : rwpng_warning_silent_handler);
|
||||||
|
if (!png_ptr) {
|
||||||
|
return PNG_OUT_OF_MEMORY_ERROR; /* out of memory */
|
||||||
|
}
|
||||||
|
|
||||||
|
info_ptr = png_create_info_struct(png_ptr);
|
||||||
|
if (!info_ptr) {
|
||||||
|
png_destroy_read_struct(&png_ptr, NULL, NULL);
|
||||||
|
return PNG_OUT_OF_MEMORY_ERROR; /* out of memory */
|
||||||
|
}
|
||||||
|
|
||||||
|
/* setjmp() must be called in every function that calls a non-trivial
|
||||||
|
* libpng function */
|
||||||
|
|
||||||
|
if (setjmp(mainprog_ptr->jmpbuf)) {
|
||||||
|
png_destroy_read_struct(&png_ptr, &info_ptr, NULL);
|
||||||
|
return LIBPNG_FATAL_ERROR; /* fatal libpng error (via longjmp()) */
|
||||||
|
}
|
||||||
|
|
||||||
|
#if PNG_LIBPNG_VER >= 10500 && defined(PNG_UNKNOWN_CHUNKS_SUPPORTED)
|
||||||
|
/* copy standard chunks too */
|
||||||
|
png_set_keep_unknown_chunks(png_ptr, PNG_HANDLE_CHUNK_IF_SAFE, (png_const_bytep)"pHYs\0iTXt\0tEXt\0zTXt", 4);
|
||||||
|
#endif
|
||||||
|
png_set_read_user_chunk_fn(png_ptr, &mainprog_ptr->chunks, read_chunk_callback);
|
||||||
|
|
||||||
|
struct rwpng_read_data read_data = {infile, 0};
|
||||||
|
png_set_read_fn(png_ptr, &read_data, user_read_data);
|
||||||
|
|
||||||
|
png_read_info(png_ptr, info_ptr); /* read all PNG info up to image data */
|
||||||
|
|
||||||
|
|
||||||
|
/* alternatively, could make separate calls to png_get_image_width(),
|
||||||
|
* etc., but want bit_depth and color_type for later [don't care about
|
||||||
|
* compression_type and filter_type => NULLs] */
|
||||||
|
|
||||||
|
png_get_IHDR(png_ptr, info_ptr, &mainprog_ptr->width, &mainprog_ptr->height,
|
||||||
|
&bit_depth, &color_type, NULL, NULL, NULL);
|
||||||
|
|
||||||
|
|
||||||
|
/* expand palette images to RGB, low-bit-depth grayscale images to 8 bits,
|
||||||
|
* transparency chunks to full alpha channel; strip 16-bit-per-sample
|
||||||
|
* images to 8 bits per sample; and convert grayscale to RGB[A] */
|
||||||
|
|
||||||
|
/* GRR TO DO: preserve all safe-to-copy ancillary PNG chunks */
|
||||||
|
|
||||||
|
if (!(color_type & PNG_COLOR_MASK_ALPHA)) {
|
||||||
|
#ifdef PNG_READ_FILLER_SUPPORTED
|
||||||
|
png_set_expand(png_ptr);
|
||||||
|
png_set_filler(png_ptr, 65535L, PNG_FILLER_AFTER);
|
||||||
|
#else
|
||||||
|
fprintf(stderr, "pngquant readpng: image is neither RGBA nor GA\n");
|
||||||
|
png_destroy_read_struct(&png_ptr, &info_ptr, NULL);
|
||||||
|
mainprog_ptr->retval = WRONG_INPUT_COLOR_TYPE;
|
||||||
|
return mainprog_ptr->retval;
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
if (bit_depth == 16) {
|
||||||
|
png_set_strip_16(png_ptr);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!(color_type & PNG_COLOR_MASK_COLOR)) {
|
||||||
|
png_set_gray_to_rgb(png_ptr);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/* get source gamma for gamma correction, or use sRGB default */
|
||||||
|
double gamma = 0.45455;
|
||||||
|
if (!png_get_valid(png_ptr, info_ptr, PNG_INFO_sRGB)) {
|
||||||
|
png_get_gAMA(png_ptr, info_ptr, &gamma);
|
||||||
|
if (gamma < 0 || gamma > 1.0) {
|
||||||
|
fprintf(stderr, "pngquant readpng: ignored out-of-range gamma %f\n", gamma);
|
||||||
|
gamma = 0.45455;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
mainprog_ptr->gamma = gamma;
|
||||||
|
|
||||||
|
png_set_interlace_handling(png_ptr);
|
||||||
|
|
||||||
|
/* all transformations have been registered; now update info_ptr data,
|
||||||
|
* get rowbytes and channels, and allocate image memory */
|
||||||
|
|
||||||
|
png_read_update_info(png_ptr, info_ptr);
|
||||||
|
|
||||||
|
rowbytes = png_get_rowbytes(png_ptr, info_ptr);
|
||||||
|
|
||||||
|
if ((mainprog_ptr->rgba_data = malloc(rowbytes*mainprog_ptr->height)) == NULL) {
|
||||||
|
fprintf(stderr, "pngquant readpng: unable to allocate image data\n");
|
||||||
|
png_destroy_read_struct(&png_ptr, &info_ptr, NULL);
|
||||||
|
return PNG_OUT_OF_MEMORY_ERROR;
|
||||||
|
}
|
||||||
|
|
||||||
|
png_bytepp row_pointers = rwpng_create_row_pointers(info_ptr, png_ptr, mainprog_ptr->rgba_data, mainprog_ptr->height, 0);
|
||||||
|
|
||||||
|
/* now we can go ahead and just read the whole image */
|
||||||
|
|
||||||
|
png_read_image(png_ptr, row_pointers);
|
||||||
|
|
||||||
|
/* and we're done! (png_read_end() can be omitted if no processing of
|
||||||
|
* post-IDAT text/time/etc. is desired) */
|
||||||
|
|
||||||
|
png_read_end(png_ptr, NULL);
|
||||||
|
|
||||||
|
#if USE_LCMS
|
||||||
|
#if PNG_LIBPNG_VER < 10500
|
||||||
|
png_charp ProfileData;
|
||||||
|
#else
|
||||||
|
png_bytep ProfileData;
|
||||||
|
#endif
|
||||||
|
png_uint_32 ProfileLen;
|
||||||
|
|
||||||
|
cmsHPROFILE hInProfile = NULL;
|
||||||
|
|
||||||
|
/* color_type is read from the image before conversion to RGBA */
|
||||||
|
int COLOR_PNG = color_type & PNG_COLOR_MASK_COLOR;
|
||||||
|
|
||||||
|
mainprog_ptr->lcms_status = NONE;
|
||||||
|
|
||||||
|
/* embedded ICC profile */
|
||||||
|
if (png_get_iCCP(png_ptr, info_ptr, &(png_charp){0}, &(int){0}, &ProfileData, &ProfileLen)) {
|
||||||
|
|
||||||
|
hInProfile = cmsOpenProfileFromMem(ProfileData, ProfileLen);
|
||||||
|
cmsColorSpaceSignature colorspace = cmsGetColorSpace(hInProfile);
|
||||||
|
|
||||||
|
/* only RGB (and GRAY) valid for PNGs */
|
||||||
|
if (colorspace == cmsSigRgbData && COLOR_PNG) {
|
||||||
|
mainprog_ptr->lcms_status = ICCP;
|
||||||
|
} else {
|
||||||
|
if (colorspace == cmsSigGrayData && !COLOR_PNG) {
|
||||||
|
mainprog_ptr->lcms_status = ICCP_WARN_GRAY;
|
||||||
|
}
|
||||||
|
cmsCloseProfile(hInProfile);
|
||||||
|
hInProfile = NULL;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* build RGB profile from cHRM and gAMA */
|
||||||
|
if (hInProfile == NULL && COLOR_PNG &&
|
||||||
|
!png_get_valid(png_ptr, info_ptr, PNG_INFO_sRGB) &&
|
||||||
|
png_get_valid(png_ptr, info_ptr, PNG_INFO_gAMA) &&
|
||||||
|
png_get_valid(png_ptr, info_ptr, PNG_INFO_cHRM)) {
|
||||||
|
|
||||||
|
cmsCIExyY WhitePoint;
|
||||||
|
cmsCIExyYTRIPLE Primaries;
|
||||||
|
|
||||||
|
png_get_cHRM(png_ptr, info_ptr, &WhitePoint.x, &WhitePoint.y,
|
||||||
|
&Primaries.Red.x, &Primaries.Red.y,
|
||||||
|
&Primaries.Green.x, &Primaries.Green.y,
|
||||||
|
&Primaries.Blue.x, &Primaries.Blue.y);
|
||||||
|
|
||||||
|
WhitePoint.Y = Primaries.Red.Y = Primaries.Green.Y = Primaries.Blue.Y = 1.0;
|
||||||
|
|
||||||
|
cmsToneCurve *GammaTable[3];
|
||||||
|
GammaTable[0] = GammaTable[1] = GammaTable[2] = cmsBuildGamma(NULL, 1/gamma);
|
||||||
|
|
||||||
|
hInProfile = cmsCreateRGBProfile(&WhitePoint, &Primaries, GammaTable);
|
||||||
|
|
||||||
|
cmsFreeToneCurve(GammaTable[0]);
|
||||||
|
|
||||||
|
mainprog_ptr->lcms_status = GAMA_CHRM;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* transform image to sRGB colorspace */
|
||||||
|
if (hInProfile != NULL) {
|
||||||
|
|
||||||
|
cmsHPROFILE hOutProfile = cmsCreate_sRGBProfile();
|
||||||
|
cmsHTRANSFORM hTransform = cmsCreateTransform(hInProfile, TYPE_RGBA_8,
|
||||||
|
hOutProfile, TYPE_RGBA_8,
|
||||||
|
INTENT_PERCEPTUAL,
|
||||||
|
omp_get_max_threads() > 1 ? cmsFLAGS_NOCACHE : 0);
|
||||||
|
|
||||||
|
#pragma omp parallel for \
|
||||||
|
if (mainprog_ptr->height*mainprog_ptr->width > 8000) \
|
||||||
|
schedule(static)
|
||||||
|
for (unsigned int i = 0; i < mainprog_ptr->height; i++) {
|
||||||
|
/* It is safe to use the same block for input and output,
|
||||||
|
when both are of the same TYPE. */
|
||||||
|
cmsDoTransform(hTransform, row_pointers[i],
|
||||||
|
row_pointers[i],
|
||||||
|
mainprog_ptr->width);
|
||||||
|
}
|
||||||
|
|
||||||
|
cmsDeleteTransform(hTransform);
|
||||||
|
cmsCloseProfile(hOutProfile);
|
||||||
|
cmsCloseProfile(hInProfile);
|
||||||
|
|
||||||
|
mainprog_ptr->gamma = 0.45455;
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
png_destroy_read_struct(&png_ptr, &info_ptr, NULL);
|
||||||
|
|
||||||
|
mainprog_ptr->file_size = read_data.bytes_read;
|
||||||
|
mainprog_ptr->row_pointers = (unsigned char **)row_pointers;
|
||||||
|
|
||||||
|
return SUCCESS;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void rwpng_free_chunks(struct rwpng_chunk *chunk) {
|
||||||
|
if (!chunk) return;
|
||||||
|
rwpng_free_chunks(chunk->next);
|
||||||
|
free(chunk->data);
|
||||||
|
free(chunk);
|
||||||
|
}
|
||||||
|
|
||||||
|
void rwpng_free_image24(png24_image *image)
|
||||||
|
{
|
||||||
|
free(image->row_pointers);
|
||||||
|
image->row_pointers = NULL;
|
||||||
|
|
||||||
|
free(image->rgba_data);
|
||||||
|
image->rgba_data = NULL;
|
||||||
|
|
||||||
|
rwpng_free_chunks(image->chunks);
|
||||||
|
image->chunks = NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
void rwpng_free_image8(png8_image *image)
|
||||||
|
{
|
||||||
|
free(image->indexed_data);
|
||||||
|
image->indexed_data = NULL;
|
||||||
|
|
||||||
|
free(image->row_pointers);
|
||||||
|
image->row_pointers = NULL;
|
||||||
|
|
||||||
|
rwpng_free_chunks(image->chunks);
|
||||||
|
image->chunks = NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
pngquant_error rwpng_read_image24(FILE *infile, png24_image *input_image_p, int verbose)
|
||||||
|
{
|
||||||
|
#if USE_COCOA
|
||||||
|
return rwpng_read_image24_cocoa(infile, input_image_p);
|
||||||
|
#else
|
||||||
|
return rwpng_read_image24_libpng(infile, input_image_p, verbose);
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
static pngquant_error rwpng_write_image_init(rwpng_png_image *mainprog_ptr, png_structpp png_ptr_p, png_infopp info_ptr_p, int fast_compression)
|
||||||
|
{
|
||||||
|
/* could also replace libpng warning-handler (final NULL), but no need: */
|
||||||
|
|
||||||
|
*png_ptr_p = png_create_write_struct(PNG_LIBPNG_VER_STRING, mainprog_ptr, rwpng_error_handler, NULL);
|
||||||
|
|
||||||
|
if (!(*png_ptr_p)) {
|
||||||
|
return LIBPNG_INIT_ERROR; /* out of memory */
|
||||||
|
}
|
||||||
|
|
||||||
|
*info_ptr_p = png_create_info_struct(*png_ptr_p);
|
||||||
|
if (!(*info_ptr_p)) {
|
||||||
|
png_destroy_write_struct(png_ptr_p, NULL);
|
||||||
|
return LIBPNG_INIT_ERROR; /* out of memory */
|
||||||
|
}
|
||||||
|
|
||||||
|
/* setjmp() must be called in every function that calls a PNG-writing
|
||||||
|
* libpng function, unless an alternate error handler was installed--
|
||||||
|
* but compatible error handlers must either use longjmp() themselves
|
||||||
|
* (as in this program) or exit immediately, so here we go: */
|
||||||
|
|
||||||
|
if (setjmp(mainprog_ptr->jmpbuf)) {
|
||||||
|
png_destroy_write_struct(png_ptr_p, info_ptr_p);
|
||||||
|
return LIBPNG_INIT_ERROR; /* libpng error (via longjmp()) */
|
||||||
|
}
|
||||||
|
|
||||||
|
png_set_compression_level(*png_ptr_p, fast_compression ? Z_BEST_SPEED : Z_BEST_COMPRESSION);
|
||||||
|
png_set_compression_mem_level(*png_ptr_p, fast_compression ? 9 : 5); // judging by optipng results, smaller mem makes libpng compress slightly better
|
||||||
|
|
||||||
|
return SUCCESS;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
void rwpng_write_end(png_infopp info_ptr_p, png_structpp png_ptr_p, png_bytepp row_pointers)
|
||||||
|
{
|
||||||
|
png_write_info(*png_ptr_p, *info_ptr_p);
|
||||||
|
|
||||||
|
png_set_packing(*png_ptr_p);
|
||||||
|
|
||||||
|
png_write_image(*png_ptr_p, row_pointers);
|
||||||
|
|
||||||
|
png_write_end(*png_ptr_p, NULL);
|
||||||
|
|
||||||
|
png_destroy_write_struct(png_ptr_p, info_ptr_p);
|
||||||
|
}
|
||||||
|
|
||||||
|
void rwpng_set_gamma(png_infop info_ptr, png_structp png_ptr, double gamma)
|
||||||
|
{
|
||||||
|
/* remap sets gamma to 0.45455 */
|
||||||
|
png_set_gAMA(png_ptr, info_ptr, gamma);
|
||||||
|
png_set_sRGB(png_ptr, info_ptr, 0); // 0 = Perceptual
|
||||||
|
}
|
||||||
|
|
||||||
|
pngquant_error rwpng_write_image8(FILE *outfile, const png8_image *mainprog_ptr)
|
||||||
|
{
|
||||||
|
png_structp png_ptr;
|
||||||
|
png_infop info_ptr;
|
||||||
|
|
||||||
|
pngquant_error retval = rwpng_write_image_init((rwpng_png_image*)mainprog_ptr, &png_ptr, &info_ptr, mainprog_ptr->fast_compression);
|
||||||
|
if (retval) return retval;
|
||||||
|
|
||||||
|
struct rwpng_write_state write_state;
|
||||||
|
write_state = (struct rwpng_write_state){
|
||||||
|
.outfile = outfile,
|
||||||
|
.maximum_file_size = mainprog_ptr->maximum_file_size,
|
||||||
|
.retval = SUCCESS,
|
||||||
|
};
|
||||||
|
png_set_write_fn(png_ptr, &write_state, user_write_data, user_flush_data);
|
||||||
|
|
||||||
|
// Palette images generally don't gain anything from filtering
|
||||||
|
png_set_filter(png_ptr, PNG_FILTER_TYPE_BASE, PNG_FILTER_VALUE_NONE);
|
||||||
|
|
||||||
|
rwpng_set_gamma(info_ptr, png_ptr, mainprog_ptr->gamma);
|
||||||
|
|
||||||
|
/* set the image parameters appropriately */
|
||||||
|
int sample_depth;
|
||||||
|
#if PNG_LIBPNG_VER > 10400 /* old libpng corrupts files with low depth */
|
||||||
|
if (mainprog_ptr->num_palette <= 2)
|
||||||
|
sample_depth = 1;
|
||||||
|
else if (mainprog_ptr->num_palette <= 4)
|
||||||
|
sample_depth = 2;
|
||||||
|
else if (mainprog_ptr->num_palette <= 16)
|
||||||
|
sample_depth = 4;
|
||||||
|
else
|
||||||
|
#endif
|
||||||
|
sample_depth = 8;
|
||||||
|
|
||||||
|
struct rwpng_chunk *chunk = mainprog_ptr->chunks;
|
||||||
|
int chunk_num=0;
|
||||||
|
while(chunk) {
|
||||||
|
png_unknown_chunk pngchunk = {
|
||||||
|
.size = chunk->size,
|
||||||
|
.data = chunk->data,
|
||||||
|
.location = chunk->location,
|
||||||
|
};
|
||||||
|
memcpy(pngchunk.name, chunk->name, 5);
|
||||||
|
png_set_unknown_chunks(png_ptr, info_ptr, &pngchunk, 1);
|
||||||
|
|
||||||
|
#if defined(PNG_HAVE_IHDR) && PNG_LIBPNG_VER < 10600
|
||||||
|
png_set_unknown_chunk_location(png_ptr, info_ptr, chunk_num, pngchunk.location ? pngchunk.location : PNG_HAVE_IHDR);
|
||||||
|
#endif
|
||||||
|
|
||||||
|
chunk = chunk->next;
|
||||||
|
chunk_num++;
|
||||||
|
}
|
||||||
|
|
||||||
|
png_set_IHDR(png_ptr, info_ptr, mainprog_ptr->width, mainprog_ptr->height,
|
||||||
|
sample_depth, PNG_COLOR_TYPE_PALETTE,
|
||||||
|
0, PNG_COMPRESSION_TYPE_DEFAULT,
|
||||||
|
PNG_FILTER_TYPE_BASE);
|
||||||
|
|
||||||
|
png_set_PLTE(png_ptr, info_ptr, &mainprog_ptr->palette[0], mainprog_ptr->num_palette);
|
||||||
|
|
||||||
|
if (mainprog_ptr->num_trans > 0) {
|
||||||
|
png_set_tRNS(png_ptr, info_ptr, mainprog_ptr->trans, mainprog_ptr->num_trans, NULL);
|
||||||
|
}
|
||||||
|
|
||||||
|
rwpng_write_end(&info_ptr, &png_ptr, mainprog_ptr->row_pointers);
|
||||||
|
|
||||||
|
if (SUCCESS == write_state.retval && write_state.maximum_file_size && write_state.bytes_written > write_state.maximum_file_size) {
|
||||||
|
return TOO_LARGE_FILE;
|
||||||
|
}
|
||||||
|
|
||||||
|
return write_state.retval;
|
||||||
|
}
|
||||||
|
|
||||||
|
pngquant_error rwpng_write_image24(FILE *outfile, const png24_image *mainprog_ptr)
|
||||||
|
{
|
||||||
|
png_structp png_ptr;
|
||||||
|
png_infop info_ptr;
|
||||||
|
|
||||||
|
pngquant_error retval = rwpng_write_image_init((rwpng_png_image*)mainprog_ptr, &png_ptr, &info_ptr, 0);
|
||||||
|
if (retval) return retval;
|
||||||
|
|
||||||
|
png_init_io(png_ptr, outfile);
|
||||||
|
|
||||||
|
rwpng_set_gamma(info_ptr, png_ptr, mainprog_ptr->gamma);
|
||||||
|
|
||||||
|
png_set_IHDR(png_ptr, info_ptr, mainprog_ptr->width, mainprog_ptr->height,
|
||||||
|
8, PNG_COLOR_TYPE_RGB_ALPHA,
|
||||||
|
0, PNG_COMPRESSION_TYPE_DEFAULT,
|
||||||
|
PNG_FILTER_TYPE_BASE);
|
||||||
|
|
||||||
|
|
||||||
|
png_bytepp row_pointers = rwpng_create_row_pointers(info_ptr, png_ptr, mainprog_ptr->rgba_data, mainprog_ptr->height, 0);
|
||||||
|
|
||||||
|
rwpng_write_end(&info_ptr, &png_ptr, row_pointers);
|
||||||
|
|
||||||
|
free(row_pointers);
|
||||||
|
|
||||||
|
return SUCCESS;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
static void rwpng_warning_stderr_handler(png_structp png_ptr, png_const_charp msg) {
|
||||||
|
fprintf(stderr, " %s\n", msg);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void rwpng_warning_silent_handler(png_structp png_ptr, png_const_charp msg) {
|
||||||
|
}
|
||||||
|
|
||||||
|
static void rwpng_error_handler(png_structp png_ptr, png_const_charp msg)
|
||||||
|
{
|
||||||
|
rwpng_png_image *mainprog_ptr;
|
||||||
|
|
||||||
|
/* This function, aside from the extra step of retrieving the "error
|
||||||
|
* pointer" (below) and the fact that it exists within the application
|
||||||
|
* rather than within libpng, is essentially identical to libpng's
|
||||||
|
* default error handler. The second point is critical: since both
|
||||||
|
* setjmp() and longjmp() are called from the same code, they are
|
||||||
|
* guaranteed to have compatible notions of how big a jmp_buf is,
|
||||||
|
* regardless of whether _BSD_SOURCE or anything else has (or has not)
|
||||||
|
* been defined. */
|
||||||
|
|
||||||
|
fprintf(stderr, " error: %s (libpng failed)\n", msg);
|
||||||
|
fflush(stderr);
|
||||||
|
|
||||||
|
mainprog_ptr = png_get_error_ptr(png_ptr);
|
||||||
|
if (mainprog_ptr == NULL) abort();
|
||||||
|
|
||||||
|
longjmp(mainprog_ptr->jmpbuf, 1);
|
||||||
|
}
|
125
deps/pngquant/rwpng.h
vendored
Normal file
125
deps/pngquant/rwpng.h
vendored
Normal file
|
@ -0,0 +1,125 @@
|
||||||
|
/*---------------------------------------------------------------------------
|
||||||
|
|
||||||
|
pngquant: RGBA -> RGBA-palette quantization program rwpng.h
|
||||||
|
|
||||||
|
---------------------------------------------------------------------------
|
||||||
|
|
||||||
|
© 1998-2000 by Greg Roelofs.
|
||||||
|
© 2009-2014 by Kornel Lesiński.
|
||||||
|
|
||||||
|
All rights reserved.
|
||||||
|
|
||||||
|
Redistribution and use in source and binary forms, with or without modification,
|
||||||
|
are permitted provided that the following conditions are met:
|
||||||
|
|
||||||
|
1. Redistributions of source code must retain the above copyright notice,
|
||||||
|
this list of conditions and the following disclaimer.
|
||||||
|
|
||||||
|
2. Redistributions in binary form must reproduce the above copyright notice,
|
||||||
|
this list of conditions and the following disclaimer in the documentation
|
||||||
|
and/or other materials provided with the distribution.
|
||||||
|
|
||||||
|
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||||
|
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||||
|
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||||
|
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
|
||||||
|
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
||||||
|
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
||||||
|
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
||||||
|
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
||||||
|
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||||
|
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||||
|
|
||||||
|
---------------------------------------------------------------------------*/
|
||||||
|
|
||||||
|
#ifndef RWPNG_H
|
||||||
|
#define RWPNG_H
|
||||||
|
|
||||||
|
#include "png.h" /* if this include fails, you need to install libpng (e.g. libpng-devel package) and run ./configure */
|
||||||
|
#include <setjmp.h>
|
||||||
|
|
||||||
|
#ifndef USE_COCOA
|
||||||
|
#define USE_COCOA 0
|
||||||
|
#endif
|
||||||
|
|
||||||
|
typedef enum {
|
||||||
|
SUCCESS = 0,
|
||||||
|
MISSING_ARGUMENT = 1,
|
||||||
|
READ_ERROR = 2,
|
||||||
|
INVALID_ARGUMENT = 4,
|
||||||
|
NOT_OVERWRITING_ERROR = 15,
|
||||||
|
CANT_WRITE_ERROR = 16,
|
||||||
|
OUT_OF_MEMORY_ERROR = 17,
|
||||||
|
WRONG_ARCHITECTURE = 18, // Missing SSE
|
||||||
|
PNG_OUT_OF_MEMORY_ERROR = 24,
|
||||||
|
LIBPNG_FATAL_ERROR = 25,
|
||||||
|
WRONG_INPUT_COLOR_TYPE = 26,
|
||||||
|
LIBPNG_INIT_ERROR = 35,
|
||||||
|
TOO_LARGE_FILE = 98,
|
||||||
|
TOO_LOW_QUALITY = 99,
|
||||||
|
} pngquant_error;
|
||||||
|
|
||||||
|
struct rwpng_chunk {
|
||||||
|
struct rwpng_chunk *next;
|
||||||
|
png_byte *data;
|
||||||
|
png_size_t size;
|
||||||
|
png_byte name[5];
|
||||||
|
png_byte location;
|
||||||
|
};
|
||||||
|
|
||||||
|
#if USE_LCMS
|
||||||
|
typedef enum {
|
||||||
|
NONE = 0,
|
||||||
|
ICCP = 1, // used ICC profile
|
||||||
|
ICCP_WARN_GRAY = 2, // ignore and warn about GRAY ICC profile
|
||||||
|
GAMA_CHRM = 3, // used gAMA and cHARM
|
||||||
|
} lcms_transform;
|
||||||
|
#endif
|
||||||
|
|
||||||
|
typedef struct {
|
||||||
|
jmp_buf jmpbuf;
|
||||||
|
png_uint_32 width;
|
||||||
|
png_uint_32 height;
|
||||||
|
png_size_t file_size;
|
||||||
|
double gamma;
|
||||||
|
unsigned char **row_pointers;
|
||||||
|
unsigned char *rgba_data;
|
||||||
|
struct rwpng_chunk *chunks;
|
||||||
|
#if USE_LCMS
|
||||||
|
lcms_transform lcms_status;
|
||||||
|
#endif
|
||||||
|
} png24_image;
|
||||||
|
|
||||||
|
typedef struct {
|
||||||
|
jmp_buf jmpbuf;
|
||||||
|
png_uint_32 width;
|
||||||
|
png_uint_32 height;
|
||||||
|
png_size_t maximum_file_size;
|
||||||
|
double gamma;
|
||||||
|
unsigned char **row_pointers;
|
||||||
|
unsigned char *indexed_data;
|
||||||
|
unsigned int num_palette;
|
||||||
|
unsigned int num_trans;
|
||||||
|
png_color palette[256];
|
||||||
|
unsigned char trans[256];
|
||||||
|
struct rwpng_chunk *chunks;
|
||||||
|
char fast_compression;
|
||||||
|
} png8_image;
|
||||||
|
|
||||||
|
typedef union {
|
||||||
|
jmp_buf jmpbuf;
|
||||||
|
png24_image png24;
|
||||||
|
png8_image png8;
|
||||||
|
} rwpng_png_image;
|
||||||
|
|
||||||
|
/* prototypes for public functions in rwpng.c */
|
||||||
|
|
||||||
|
void rwpng_version_info(FILE *fp);
|
||||||
|
|
||||||
|
pngquant_error rwpng_read_image24(FILE *infile, png24_image *mainprog_ptr, int verbose);
|
||||||
|
pngquant_error rwpng_write_image8(FILE *outfile, const png8_image *mainprog_ptr);
|
||||||
|
pngquant_error rwpng_write_image24(FILE *outfile, const png24_image *mainprog_ptr);
|
||||||
|
void rwpng_free_image24(png24_image *);
|
||||||
|
void rwpng_free_image8(png8_image *);
|
||||||
|
|
||||||
|
#endif
|
73
deps/pngquant/rwpng_cocoa.m
vendored
Normal file
73
deps/pngquant/rwpng_cocoa.m
vendored
Normal file
|
@ -0,0 +1,73 @@
|
||||||
|
//
|
||||||
|
// rwpng_cocoa.m
|
||||||
|
// pngquant
|
||||||
|
//
|
||||||
|
|
||||||
|
#include "rwpng.h"
|
||||||
|
|
||||||
|
#ifdef USE_COCOA
|
||||||
|
|
||||||
|
#import <Cocoa/Cocoa.h>
|
||||||
|
#import <CoreGraphics/CoreGraphics.h>
|
||||||
|
#include <stdio.h>
|
||||||
|
#include "lib/libimagequant.h"
|
||||||
|
#include "lib/pam.h"
|
||||||
|
|
||||||
|
int rwpng_read_image24_cocoa(FILE *fp, png24_image *out)
|
||||||
|
{
|
||||||
|
rgba_pixel *pixel_data;
|
||||||
|
int width, height;
|
||||||
|
@autoreleasepool {
|
||||||
|
NSFileHandle *fh = [[NSFileHandle alloc] initWithFileDescriptor:fileno(fp)];
|
||||||
|
NSData *data = [fh readDataToEndOfFile];
|
||||||
|
out->file_size = [data length];
|
||||||
|
CGImageRef image = [[NSBitmapImageRep imageRepWithData:data] CGImage];
|
||||||
|
[fh release];
|
||||||
|
|
||||||
|
if (!image) return READ_ERROR;
|
||||||
|
|
||||||
|
width = CGImageGetWidth(image);
|
||||||
|
height = CGImageGetHeight(image);
|
||||||
|
|
||||||
|
pixel_data = calloc(width*height,4);
|
||||||
|
|
||||||
|
CGColorSpaceRef colorspace = CGColorSpaceCreateWithName(kCGColorSpaceSRGB);
|
||||||
|
|
||||||
|
CGContextRef context = CGBitmapContextCreate(pixel_data,
|
||||||
|
width, height,
|
||||||
|
8, width*4,
|
||||||
|
colorspace,
|
||||||
|
kCGImageAlphaPremultipliedLast);
|
||||||
|
|
||||||
|
CGColorSpaceRelease(colorspace);
|
||||||
|
|
||||||
|
if (!context) return READ_ERROR;
|
||||||
|
|
||||||
|
CGContextDrawImage(context, CGRectMake(0.0, 0.0, width, height), image);
|
||||||
|
CGContextRelease(context);
|
||||||
|
}
|
||||||
|
// reverse premultiplication
|
||||||
|
|
||||||
|
for(int i=0; i < width*height; i++) {
|
||||||
|
if (pixel_data[i].a) {
|
||||||
|
pixel_data[i] = (rgba_pixel){
|
||||||
|
.a = pixel_data[i].a,
|
||||||
|
.r = pixel_data[i].r*255/pixel_data[i].a,
|
||||||
|
.g = pixel_data[i].g*255/pixel_data[i].a,
|
||||||
|
.b = pixel_data[i].b*255/pixel_data[i].a,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
out->gamma = 0.45455;
|
||||||
|
out->width = width;
|
||||||
|
out->height = height;
|
||||||
|
out->rgba_data = (unsigned char *)pixel_data;
|
||||||
|
out->row_pointers = malloc(sizeof(out->row_pointers[0])*out->height);
|
||||||
|
for(int i=0; i < out->height; i++) {
|
||||||
|
out->row_pointers[i] = (unsigned char *)&pixel_data[width*i];
|
||||||
|
}
|
||||||
|
return SUCCESS;
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif
|
BIN
deps/pngquant/test/img/test.png
vendored
Normal file
BIN
deps/pngquant/test/img/test.png
vendored
Normal file
Binary file not shown.
After Width: | Height: | Size: 17 KiB |
68
deps/pngquant/test/test.sh
vendored
Executable file
68
deps/pngquant/test/test.sh
vendored
Executable file
|
@ -0,0 +1,68 @@
|
||||||
|
#!/bin/bash
|
||||||
|
set -eu
|
||||||
|
set -o pipefail
|
||||||
|
|
||||||
|
TESTDIR=$1
|
||||||
|
IMGSRC=$TESTDIR/img
|
||||||
|
TMPDIR=$(mktemp -d -t pngquantXXXXXX)
|
||||||
|
BIN=$2
|
||||||
|
PATH=.:$PATH # Required, since BIN may be just 'pngquant'
|
||||||
|
|
||||||
|
$BIN --version 2>&1 | fgrep 2.
|
||||||
|
$BIN --help | fgrep -q "usage:"
|
||||||
|
|
||||||
|
$BIN 2>/dev/null && { echo "should fail without args"; exit 1; } || true
|
||||||
|
|
||||||
|
function test_overwrite() {
|
||||||
|
cp "$IMGSRC/test.png" "$TMPDIR/overwritetest.png"
|
||||||
|
rm -rf "$TMPDIR/overwritetest-fs8.png" "$TMPDIR/overwritetest-or8.png"
|
||||||
|
|
||||||
|
$BIN "$TMPDIR/overwritetest.png"
|
||||||
|
test -f "$TMPDIR/overwritetest-fs8.png"
|
||||||
|
|
||||||
|
$BIN --floyd=0.5 --force "$TMPDIR/overwritetest.png"
|
||||||
|
test -f "$TMPDIR/overwritetest-fs8.png"
|
||||||
|
test '!' -e "$TMPDIR/overwritetest-or8.png"
|
||||||
|
|
||||||
|
$BIN --nofs "$TMPDIR/overwritetest.png"
|
||||||
|
test -f "$TMPDIR/overwritetest-or8.png"
|
||||||
|
|
||||||
|
rm "$TMPDIR/overwritetest-or8.png"
|
||||||
|
$BIN 2>&1 -nofs "$TMPDIR/overwritetest.png" | fgrep -q warning:
|
||||||
|
test -f "$TMPDIR/overwritetest-or8.png"
|
||||||
|
|
||||||
|
$BIN 2>/dev/null --ordered "$TMPDIR/overwritetest.png" && { echo "should refuse to overwrite"; exit 1; } || true
|
||||||
|
|
||||||
|
{ $BIN 2>&1 --ordered "$TMPDIR/overwritetest.png" && { echo "should refuse to overwrite"; exit 1; } || true; } | fgrep -q 'not overwriting'
|
||||||
|
|
||||||
|
$BIN "$TMPDIR/overwritetest.png" -o "$TMPDIR/overwritedest.png"
|
||||||
|
test -f "$TMPDIR/overwritedest.png"
|
||||||
|
|
||||||
|
$BIN 2>/dev/null "$TMPDIR/overwritetest.png" -o "$TMPDIR/overwritedest.png" && { echo "should refuse to overwrite"; exit 1; } || true
|
||||||
|
}
|
||||||
|
|
||||||
|
function test_skip() {
|
||||||
|
cp "$IMGSRC/test.png" "$TMPDIR/skiptest.png"
|
||||||
|
rm -rf "$TMPDIR/shouldskip.png"
|
||||||
|
|
||||||
|
$BIN 2>/dev/null "$TMPDIR/skiptest.png" -Q 100-100 -o "$TMPDIR/shouldskip.png" && { echo "should skip due to quality"; exit 1; } || RET=$?
|
||||||
|
test "$RET" -eq 99 || { echo "should return 99, not $RET"; exit 1; }
|
||||||
|
test '!' -e "$TMPDIR/shouldskip.png"
|
||||||
|
|
||||||
|
$BIN "$TMPDIR/skiptest.png" -Q 0-50 -o "$TMPDIR/q50output.png"
|
||||||
|
test -f "$TMPDIR/q50output.png"
|
||||||
|
|
||||||
|
$BIN "$TMPDIR/q50output.png" --skip-if-larger -Q 0-49 -o "$TMPDIR/q49output.png" && { echo "should skip due to filesize"; exit 1; } || RET=$?
|
||||||
|
test "$RET" -eq 98 || { echo "should return 98, not $RET"; exit 1; }
|
||||||
|
test '!' -e "$TMPDIR/q49output.png"
|
||||||
|
}
|
||||||
|
|
||||||
|
test_overwrite &
|
||||||
|
test_skip &
|
||||||
|
|
||||||
|
for job in `jobs -p`
|
||||||
|
do
|
||||||
|
wait $job
|
||||||
|
done
|
||||||
|
|
||||||
|
echo "OK"
|
Loading…
Reference in a new issue