Add method=Image.LIBIMAGEQUANT for quantize()

This commit is contained in:
rr- 2016-05-05 21:36:45 +02:00
parent 12bd44a6fb
commit eb354be7c4
6 changed files with 206 additions and 8 deletions

View File

@ -185,6 +185,7 @@ ADAPTIVE = 1
MEDIANCUT = 0
MAXCOVERAGE = 1
FASTOCTREE = 2
LIBIMAGEQUANT = 3
# categories
NORMAL = 0
@ -967,6 +968,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
@ -981,10 +983,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

View File

@ -15,6 +15,14 @@ class TestImageQuantize(PillowTestCase):
im = im.quantize(palette=hopper("P"))
self.assert_image(im, "P", im.size)
def test_libimagequant_quantize(self):
im = hopper()
im = im.quantize(100, Image.LIBIMAGEQUANT)
self.assert_image(im, "P", im.size)
assert len(im.getcolors()) == 100
def test_octree_quantize(self):
im = hopper()

View File

@ -1484,7 +1484,7 @@ ImagingQuantize(Imaging im, int colors, int mode, int kmeans)
return ImagingError_ModeError();
/* only octree supports RGBA */
if (!strcmp(im->mode, "RGBA") && mode != 2)
if (!strcmp(im->mode, "RGBA") && mode != 2 && mode != 3)
return ImagingError_ModeError();
p = malloc(sizeof(Pixel) * im->xsize * im->ysize);
@ -1503,8 +1503,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 +1519,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 +1575,21 @@ ImagingQuantize(Imaging im, int colors, int mode, int kmeans)
withAlpha
);
break;
case 3:
if (!strcmp(im->mode, "RGBA")) {
withAlpha = 1;
}
result = quantize_pngquant(
p,
im->xsize,
im->ysize,
colors,
&palette,
&paletteLength,
&newData,
withAlpha
);
break;
default:
result = 0;
break;

141
libImaging/QuantPngQuant.c Normal file
View File

@ -0,0 +1,141 @@
/* Copyright (c) 2010 Oliver Tonnhofer <olt@bogosoft.com>, Omniscale
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.
*/
/*
// This file implements a quantization using libimagequant, a part of pngquant.
*/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "QuantPngQuant.h"
#ifdef HAVE_LIBIMAGEQUANT
#include "libimagequant.h"
int
quantize_pngquant(
Pixel *pixelData,
uint32_t width,
uint32_t 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;
char *charMatrix = NULL;
char **charMatrixRows = NULL;
*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 (unsigned int 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(char*));
if (!charMatrixRows) { goto err; }
for (unsigned int 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 (int 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;
}
#else
/* Offer dummy implementation */
int
quantize_pngquant(
Pixel *pixelData,
uint32_t width,
uint32_t height,
uint32_t quantPixels,
Pixel **palette,
uint32_t *paletteLength,
uint32_t **quantizedPixels,
int withAlpha)
{
return 0;
}
#endif

View File

@ -0,0 +1,15 @@
#ifndef __QUANT_PNGQUANT_H__
#define __QUANT_PNGQUANT_H__
#include "QuantTypes.h"
int quantize_pngquant(Pixel *,
uint32_t,
uint32_t,
uint32_t,
Pixel **,
uint32_t *,
uint32_t **,
int);
#endif

View File

@ -35,7 +35,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
@ -114,6 +114,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
@ -122,7 +123,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'])
@ -185,7 +186,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:
@ -478,6 +479,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'):
@ -591,6 +600,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))
@ -698,6 +710,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"),