mirror of
https://github.com/python-pillow/Pillow.git
synced 2024-12-26 18:06:18 +03:00
Merge pull request #1889 from rr-/libpngquant
Add libimagequant support in quantize()
This commit is contained in:
commit
3657bc10a6
|
@ -45,6 +45,9 @@ install:
|
|||
# openjpeg
|
||||
- pushd depends && ./install_openjpeg.sh && popd
|
||||
|
||||
# libimagequant
|
||||
- pushd depends && ./install_imagequant.sh && popd
|
||||
|
||||
script:
|
||||
- if [ "$TRAVIS_PYTHON_VERSION" != "nightly" ]; then coverage erase; fi
|
||||
- python setup.py clean
|
||||
|
|
|
@ -185,6 +185,7 @@ ADAPTIVE = 1
|
|||
MEDIANCUT = 0
|
||||
MAXCOVERAGE = 1
|
||||
FASTOCTREE = 2
|
||||
LIBIMAGEQUANT = 3
|
||||
|
||||
# categories
|
||||
NORMAL = 0
|
||||
|
@ -961,6 +962,7 @@ class Image(object):
|
|||
:param method: 0 = median cut
|
||||
1 = maximum coverage
|
||||
2 = fast octree
|
||||
3 = libimagequant
|
||||
:param kmeans: Integer
|
||||
:param palette: Quantize to the :py:class:`PIL.ImagingPalette` palette.
|
||||
:returns: A new image
|
||||
|
@ -975,10 +977,11 @@ class Image(object):
|
|||
if self.mode == 'RGBA':
|
||||
method = 2
|
||||
|
||||
if self.mode == 'RGBA' and method != 2:
|
||||
if self.mode == 'RGBA' and method not in (2, 3):
|
||||
# Caller specified an invalid mode.
|
||||
raise ValueError('Fast Octree (method == 2) is the ' +
|
||||
' only valid method for quantizing RGBA images')
|
||||
raise ValueError(
|
||||
'Fast Octree (method == 2) and libimagequant (method == 3) ' +
|
||||
'are the only valid methods for quantizing RGBA images')
|
||||
|
||||
if palette:
|
||||
# use palette from reference image
|
||||
|
|
|
@ -6,31 +6,46 @@ from PIL import Image
|
|||
class TestImageQuantize(PillowTestCase):
|
||||
|
||||
def test_sanity(self):
|
||||
im = hopper()
|
||||
image = hopper()
|
||||
converted = image.quantize()
|
||||
self.assert_image(converted, 'P', converted.size)
|
||||
self.assert_image_similar(converted.convert('RGB'), image, 10)
|
||||
|
||||
im = im.quantize()
|
||||
self.assert_image(im, "P", im.size)
|
||||
image = hopper()
|
||||
converted = image.quantize(palette=hopper('P'))
|
||||
self.assert_image(converted, 'P', converted.size)
|
||||
self.assert_image_similar(converted.convert('RGB'), image, 60)
|
||||
|
||||
im = hopper()
|
||||
im = im.quantize(palette=hopper("P"))
|
||||
self.assert_image(im, "P", im.size)
|
||||
def test_libimagequant_quantize(self):
|
||||
image = hopper()
|
||||
try:
|
||||
converted = image.quantize(100, Image.LIBIMAGEQUANT)
|
||||
except ValueError as ex:
|
||||
if 'dependency' in str(ex).lower():
|
||||
self.skipTest('libimagequant support not available')
|
||||
else:
|
||||
raise
|
||||
self.assert_image(converted, 'P', converted.size)
|
||||
self.assert_image_similar(converted.convert('RGB'), image, 15)
|
||||
assert len(converted.getcolors()) == 100
|
||||
|
||||
def test_octree_quantize(self):
|
||||
im = hopper()
|
||||
|
||||
im = im.quantize(100, Image.FASTOCTREE)
|
||||
self.assert_image(im, "P", im.size)
|
||||
|
||||
assert len(im.getcolors()) == 100
|
||||
image = hopper()
|
||||
converted = image.quantize(100, Image.FASTOCTREE)
|
||||
self.assert_image(converted, 'P', converted.size)
|
||||
self.assert_image_similar(converted.convert('RGB'), image, 20)
|
||||
assert len(converted.getcolors()) == 100
|
||||
|
||||
def test_rgba_quantize(self):
|
||||
im = hopper('RGBA')
|
||||
im.quantize()
|
||||
self.assertRaises(Exception, lambda: im.quantize(method=0))
|
||||
image = hopper('RGBA')
|
||||
image.quantize()
|
||||
self.assertRaises(Exception, lambda: image.quantize(method=0))
|
||||
|
||||
def test_quantize(self):
|
||||
im = Image.open('Tests/images/caption_6_33_22.png')
|
||||
im.convert('RGB').quantize().convert('RGB')
|
||||
image = Image.open('Tests/images/caption_6_33_22.png').convert('RGB')
|
||||
converted = image.quantize()
|
||||
self.assert_image(converted, 'P', converted.size)
|
||||
self.assert_image_similar(converted.convert('RGB'), image, 1)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
|
|
|
@ -1,8 +1,8 @@
|
|||
Depends
|
||||
=======
|
||||
|
||||
``install_openjpeg.sh`` and ``install_webp.sh`` can be used to
|
||||
download, build & install non-packaged dependencies; useful for
|
||||
``install_openjpeg.sh``, ``install_webp.sh`` and ``install_imagequant.sh`` can
|
||||
be used to download, build & install non-packaged dependencies; useful for
|
||||
testing with Travis CI.
|
||||
|
||||
The other scripts can be used to install all of the dependencies for
|
||||
|
|
|
@ -14,3 +14,4 @@ sudo apt-get -y install libtiff5-dev libjpeg62-turbo-dev zlib1g-dev \
|
|||
python-tk python3-tk
|
||||
|
||||
./install_openjpeg.sh
|
||||
./install_imagequant.sh
|
||||
|
|
12
depends/install_imagequant.sh
Executable file
12
depends/install_imagequant.sh
Executable file
|
@ -0,0 +1,12 @@
|
|||
#!/bin/bash
|
||||
# install libimagequant
|
||||
|
||||
git clone -b 2.6.0 https://github.com/pornel/pngquant
|
||||
|
||||
pushd pngquant
|
||||
|
||||
make -C lib shared
|
||||
sudo cp lib/libimagequant.so* /usr/lib/
|
||||
sudo cp lib/libimagequant.h /usr/include/
|
||||
|
||||
popd
|
|
@ -14,3 +14,4 @@ sudo apt-get install libtiff4-dev libjpeg8-dev zlib1g-dev \
|
|||
|
||||
./install_openjpeg.sh
|
||||
./install_webp.sh
|
||||
./install_imagequant.sh
|
||||
|
|
|
@ -12,3 +12,4 @@ sudo apt-get -y install libtiff5-dev libjpeg8-dev zlib1g-dev \
|
|||
python-tk python3-tk
|
||||
|
||||
./install_openjpeg.sh
|
||||
./install_imagequant.sh
|
||||
|
|
|
@ -140,6 +140,8 @@ Many of Pillow's features require external libraries:
|
|||
|
||||
* **libfreetype** provides type related services
|
||||
|
||||
* **libimagequant** provides improved color quantization
|
||||
|
||||
* **littlecms** provides color management
|
||||
|
||||
* Pillow version 2.2.1 and below uses liblcms1, Pillow 2.3.0 and
|
||||
|
@ -190,17 +192,17 @@ Build Options
|
|||
* Build flags: ``--disable-zlib``, ``--disable-jpeg``,
|
||||
``--disable-tiff``, ``--disable-freetype``, ``--disable-tcl``,
|
||||
``--disable-tk``, ``--disable-lcms``, ``--disable-webp``,
|
||||
``--disable-webpmux``, ``--disable-jpeg2000``. Disable building the
|
||||
corresponding feature even if the development libraries are present
|
||||
on the building machine.
|
||||
``--disable-webpmux``, ``--disable-jpeg2000``, ``--disable-imagequant``.
|
||||
Disable building the corresponding feature even if the development
|
||||
libraries are present on the building machine.
|
||||
|
||||
* Build flags: ``--enable-zlib``, ``--enable-jpeg``,
|
||||
``--enable-tiff``, ``--enable-freetype``, ``--enable-tcl``,
|
||||
``--enable-tk``, ``--enable-lcms``, ``--enable-webp``,
|
||||
``--enable-webpmux``, ``--enable-jpeg2000``. Require that the
|
||||
corresponding feature is built. The build will raise an exception if
|
||||
the libraries are not found. Webpmux (WebP metadata) relies on WebP
|
||||
support. Tcl and Tk also must be used together.
|
||||
``--enable-webpmux``, ``--enable-jpeg2000``, ``--enable-imagequant``.
|
||||
Require that the corresponding feature is built. The build will raise
|
||||
an exception if the libraries are not found. Webpmux (WebP metadata)
|
||||
relies on WebP support. Tcl and Tk also must be used together.
|
||||
|
||||
* Build flag: ``--disable-platform-guessing``. Skips all of the
|
||||
platform dependent guessing of include and library directories for
|
||||
|
|
|
@ -27,6 +27,7 @@
|
|||
|
||||
#include "QuantTypes.h"
|
||||
#include "QuantOctree.h"
|
||||
#include "QuantPngQuant.h"
|
||||
#include "QuantHash.h"
|
||||
#include "QuantHeap.h"
|
||||
|
||||
|
@ -1483,8 +1484,8 @@ ImagingQuantize(Imaging im, int colors, int mode, int kmeans)
|
|||
strcmp(im->mode, "RGB") != 0 && strcmp(im->mode, "RGBA") !=0)
|
||||
return ImagingError_ModeError();
|
||||
|
||||
/* only octree supports RGBA */
|
||||
if (!strcmp(im->mode, "RGBA") && mode != 2)
|
||||
/* only octree and imagequant supports RGBA */
|
||||
if (!strcmp(im->mode, "RGBA") && mode != 2 && mode != 3)
|
||||
return ImagingError_ModeError();
|
||||
|
||||
p = malloc(sizeof(Pixel) * im->xsize * im->ysize);
|
||||
|
@ -1503,8 +1504,10 @@ ImagingQuantize(Imaging im, int colors, int mode, int kmeans)
|
|||
should be done by a simple copy... */
|
||||
|
||||
for (i = y = 0; y < im->ysize; y++)
|
||||
for (x = 0; x < im->xsize; x++, i++)
|
||||
for (x = 0; x < im->xsize; x++, i++) {
|
||||
p[i].c.r = p[i].c.g = p[i].c.b = im->image8[y][x];
|
||||
p[i].c.a = 255;
|
||||
}
|
||||
|
||||
} else if (!strcmp(im->mode, "P")) {
|
||||
/* palette */
|
||||
|
@ -1517,6 +1520,7 @@ ImagingQuantize(Imaging im, int colors, int mode, int kmeans)
|
|||
p[i].c.r = pp[v*4+0];
|
||||
p[i].c.g = pp[v*4+1];
|
||||
p[i].c.b = pp[v*4+2];
|
||||
p[i].c.a = pp[v*4+3];
|
||||
}
|
||||
|
||||
} else if (!strcmp(im->mode, "RGB") || !strcmp(im->mode, "RGBA")) {
|
||||
|
@ -1572,6 +1576,25 @@ ImagingQuantize(Imaging im, int colors, int mode, int kmeans)
|
|||
withAlpha
|
||||
);
|
||||
break;
|
||||
case 3:
|
||||
#ifdef HAVE_LIBIMAGEQUANT
|
||||
if (!strcmp(im->mode, "RGBA")) {
|
||||
withAlpha = 1;
|
||||
}
|
||||
result = quantize_pngquant(
|
||||
p,
|
||||
im->xsize,
|
||||
im->ysize,
|
||||
colors,
|
||||
&palette,
|
||||
&paletteLength,
|
||||
&newData,
|
||||
withAlpha
|
||||
);
|
||||
#else
|
||||
result = -1;
|
||||
#endif
|
||||
break;
|
||||
default:
|
||||
result = 0;
|
||||
break;
|
||||
|
@ -1580,7 +1603,7 @@ ImagingQuantize(Imaging im, int colors, int mode, int kmeans)
|
|||
free(p);
|
||||
ImagingSectionLeave(&cookie);
|
||||
|
||||
if (result) {
|
||||
if (result > 0) {
|
||||
imOut = ImagingNew("P", im->xsize, im->ysize);
|
||||
ImagingSectionEnter(&cookie);
|
||||
|
||||
|
@ -1620,6 +1643,12 @@ ImagingQuantize(Imaging im, int colors, int mode, int kmeans)
|
|||
|
||||
} else {
|
||||
|
||||
if (result == -1) {
|
||||
return (Imaging) ImagingError_ValueError(
|
||||
"dependency required by this method was not "
|
||||
"enabled at compile time");
|
||||
}
|
||||
|
||||
return (Imaging) ImagingError_ValueError("quantization error");
|
||||
|
||||
}
|
||||
|
|
110
libImaging/QuantPngQuant.c
Normal file
110
libImaging/QuantPngQuant.c
Normal file
|
@ -0,0 +1,110 @@
|
|||
/*
|
||||
* The Python Imaging Library
|
||||
* $Id$
|
||||
*
|
||||
* quantization using libimagequant, a part of pngquant.
|
||||
*
|
||||
* Copyright (c) 2016 Marcin Kurczewski <rr-@sakuya.pl>
|
||||
*
|
||||
*/
|
||||
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
|
||||
#include "QuantPngQuant.h"
|
||||
|
||||
#ifdef HAVE_LIBIMAGEQUANT
|
||||
#include "libimagequant.h"
|
||||
|
||||
int
|
||||
quantize_pngquant(
|
||||
Pixel *pixelData,
|
||||
int width,
|
||||
int height,
|
||||
uint32_t quantPixels,
|
||||
Pixel **palette,
|
||||
uint32_t *paletteLength,
|
||||
uint32_t **quantizedPixels,
|
||||
int withAlpha)
|
||||
{
|
||||
int result = 0;
|
||||
liq_image *image = NULL;
|
||||
liq_attr *attr = NULL;
|
||||
liq_result *remap = NULL;
|
||||
unsigned char *charMatrix = NULL;
|
||||
unsigned char **charMatrixRows = NULL;
|
||||
unsigned int i, y;
|
||||
*palette = NULL;
|
||||
*paletteLength = 0;
|
||||
*quantizedPixels = NULL;
|
||||
|
||||
/* configure pngquant */
|
||||
attr = liq_attr_create();
|
||||
if (!attr) { goto err; }
|
||||
if (quantPixels) {
|
||||
liq_set_max_colors(attr, quantPixels);
|
||||
}
|
||||
|
||||
/* prepare input image */
|
||||
image = liq_image_create_rgba(
|
||||
attr,
|
||||
pixelData,
|
||||
width,
|
||||
height,
|
||||
0.45455 /* gamma */);
|
||||
if (!image) { goto err; }
|
||||
|
||||
/* quantize the image */
|
||||
remap = liq_quantize_image(attr, image);
|
||||
if (!remap) { goto err; }
|
||||
liq_set_output_gamma(remap, 0.45455);
|
||||
liq_set_dithering_level(remap, 1);
|
||||
|
||||
/* write output palette */
|
||||
const liq_palette *l_palette = liq_get_palette(remap);
|
||||
*paletteLength = l_palette->count;
|
||||
*palette = malloc(sizeof(Pixel) * l_palette->count);
|
||||
if (!*palette) { goto err; }
|
||||
for (i = 0; i < l_palette->count; i++) {
|
||||
(*palette)[i].c.b = l_palette->entries[i].b;
|
||||
(*palette)[i].c.g = l_palette->entries[i].g;
|
||||
(*palette)[i].c.r = l_palette->entries[i].r;
|
||||
(*palette)[i].c.a = l_palette->entries[i].a;
|
||||
}
|
||||
|
||||
/* write output pixels (pngquant uses char array) */
|
||||
charMatrix = malloc(width * height);
|
||||
if (!charMatrix) { goto err; }
|
||||
charMatrixRows = malloc(height * sizeof(unsigned char*));
|
||||
if (!charMatrixRows) { goto err; }
|
||||
for (y = 0; y < height; y++) {
|
||||
charMatrixRows[y] = &charMatrix[y * width];
|
||||
}
|
||||
if (LIQ_OK != liq_write_remapped_image_rows(remap, image, charMatrixRows)) {
|
||||
goto err;
|
||||
}
|
||||
|
||||
/* transcribe output pixels (pillow uses uint32_t array) */
|
||||
*quantizedPixels = malloc(sizeof(uint32_t) * width * height);
|
||||
if (!*quantizedPixels) { goto err; }
|
||||
for (i = 0; i < width * height; i++) {
|
||||
(*quantizedPixels)[i] = charMatrix[i];
|
||||
}
|
||||
|
||||
result = 1;
|
||||
|
||||
err:
|
||||
if (attr) liq_attr_destroy(attr);
|
||||
if (image) liq_image_destroy(image);
|
||||
if (remap) liq_result_destroy(remap);
|
||||
free(charMatrix);
|
||||
free(charMatrixRows);
|
||||
if (!result) {
|
||||
free(*quantizedPixels);
|
||||
free(*palette);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
#endif
|
15
libImaging/QuantPngQuant.h
Normal file
15
libImaging/QuantPngQuant.h
Normal file
|
@ -0,0 +1,15 @@
|
|||
#ifndef __QUANT_PNGQUANT_H__
|
||||
#define __QUANT_PNGQUANT_H__
|
||||
|
||||
#include "QuantTypes.h"
|
||||
|
||||
int quantize_pngquant(Pixel *,
|
||||
int,
|
||||
int,
|
||||
uint32_t,
|
||||
Pixel **,
|
||||
uint32_t *,
|
||||
uint32_t **,
|
||||
int);
|
||||
|
||||
#endif
|
19
setup.py
19
setup.py
|
@ -36,7 +36,7 @@ _LIB_IMAGING = (
|
|||
"RankFilter", "RawDecode", "RawEncode", "Storage", "SunRleDecode",
|
||||
"TgaRleDecode", "Unpack", "UnpackYCC", "UnsharpMask", "XbmDecode",
|
||||
"XbmEncode", "ZipDecode", "ZipEncode", "TiffDecode", "Incremental",
|
||||
"Jpeg2KDecode", "Jpeg2KEncode", "BoxBlur")
|
||||
"Jpeg2KDecode", "Jpeg2KEncode", "BoxBlur", "QuantPngQuant")
|
||||
|
||||
DEBUG = False
|
||||
|
||||
|
@ -115,6 +115,7 @@ TCL_ROOT = None
|
|||
JPEG_ROOT = None
|
||||
JPEG2K_ROOT = None
|
||||
ZLIB_ROOT = None
|
||||
IMAGEQUANT_ROOT = None
|
||||
TIFF_ROOT = None
|
||||
FREETYPE_ROOT = None
|
||||
LCMS_ROOT = None
|
||||
|
@ -123,7 +124,7 @@ LCMS_ROOT = None
|
|||
class pil_build_ext(build_ext):
|
||||
class feature:
|
||||
features = ['zlib', 'jpeg', 'tiff', 'freetype', 'tcl', 'tk', 'lcms',
|
||||
'webp', 'webpmux', 'jpeg2000']
|
||||
'webp', 'webpmux', 'jpeg2000', 'imagequant']
|
||||
|
||||
required = set(['jpeg', 'zlib'])
|
||||
|
||||
|
@ -193,7 +194,7 @@ class pil_build_ext(build_ext):
|
|||
# add configured kits
|
||||
|
||||
for root in (TCL_ROOT, JPEG_ROOT, JPEG2K_ROOT, TIFF_ROOT, ZLIB_ROOT,
|
||||
FREETYPE_ROOT, LCMS_ROOT):
|
||||
FREETYPE_ROOT, LCMS_ROOT, IMAGEQUANT_ROOT):
|
||||
if isinstance(root, type(())):
|
||||
lib_root, include_root = root
|
||||
else:
|
||||
|
@ -490,6 +491,14 @@ class pil_build_ext(build_ext):
|
|||
feature.openjpeg_version = '.'.join([str(x) for x in
|
||||
best_version])
|
||||
|
||||
if feature.want('imagequant'):
|
||||
_dbg('Looking for imagequant')
|
||||
if _find_include_file(self, 'libimagequant.h'):
|
||||
if _find_library_file(self, "imagequant"):
|
||||
feature.imagequant = "imagequant"
|
||||
elif _find_library_file(self, "libimagequant"):
|
||||
feature.imagequant = "libimagequant"
|
||||
|
||||
if feature.want('tiff'):
|
||||
_dbg('Looking for tiff')
|
||||
if _find_include_file(self, 'tiff.h'):
|
||||
|
@ -603,6 +612,9 @@ class pil_build_ext(build_ext):
|
|||
if feature.zlib:
|
||||
libs.append(feature.zlib)
|
||||
defs.append(("HAVE_LIBZ", None))
|
||||
if feature.imagequant:
|
||||
libs.append(feature.imagequant)
|
||||
defs.append(("HAVE_LIBIMAGEQUANT", None))
|
||||
if feature.tiff:
|
||||
libs.append(feature.tiff)
|
||||
defs.append(("HAVE_LIBTIFF", None))
|
||||
|
@ -710,6 +722,7 @@ class pil_build_ext(build_ext):
|
|||
(feature.jpeg2000, "OPENJPEG (JPEG2000)",
|
||||
feature.openjpeg_version),
|
||||
(feature.zlib, "ZLIB (PNG/ZIP)"),
|
||||
(feature.imagequant, "LIBIMAGEQUANT"),
|
||||
(feature.tiff, "LIBTIFF"),
|
||||
(feature.freetype, "FREETYPE2"),
|
||||
(feature.lcms, "LITTLECMS2"),
|
||||
|
|
|
@ -15,4 +15,4 @@ For more extensive info, see the windows build instructions `docs/build.rst`.
|
|||
* `python test.py` runs the tests on Pillow in all the virtual envs.
|
||||
* Currently working with zlib, libjpeg, freetype, and libtiff on Python 2.7, 3.3, and 3.4, both 32 and 64 bit, on a local win7 pro machine and appveyor.com
|
||||
* Webp is built, not detected.
|
||||
* LCMS and OpenJpeg are not building.
|
||||
* LCMS, OpenJpeg and libimagequant are not building.
|
||||
|
|
Loading…
Reference in New Issue
Block a user