diff --git a/PIL/Image.py b/PIL/Image.py index 5d7331823..b79335a13 100644 --- a/PIL/Image.py +++ b/PIL/Image.py @@ -143,11 +143,23 @@ FLOYDSTEINBERG = 3 # default WEB = 0 ADAPTIVE = 1 +MEDIANCUT = 0 +MAXCOVERAGE = 1 +FASTOCTREE = 2 + # categories NORMAL = 0 SEQUENCE = 1 CONTAINER = 2 +if hasattr(core, 'DEFAULT_STRATEGY'): + DEFAULT_STRATEGY = core.DEFAULT_STRATEGY + FILTERED = core.FILTERED + HUFFMAN_ONLY = core.HUFFMAN_ONLY + RLE = core.RLE + FIXED = core.FIXED + + # -------------------------------------------------------------------- # Registries @@ -616,18 +628,12 @@ class Image: self.palette.mode = "RGB" self.palette.rawmode = None if "transparency" in self.info: - -# XXX Not sure how this ever worked: -# if self.info["transparency_palette"]: -# Should probably be: - if "transparency_palette" in self.info: -# amirite? - - self.im.putpalettealpha(0, 0, self.info["transparency_palette"]) + if isinstance(self.info["transparency"], str): + self.im.putpalettealphas(self.info["transparency"]) else: self.im.putpalettealpha(self.info["transparency"], 0) - self.palette.mode = "RGBA" + if self.im: return self.im.pixel_access(self.readonly) @@ -724,6 +730,7 @@ class Image: # methods: # 0 = median cut # 1 = maximum coverage + # 2 = fast octree # NOTE: this functionality will be moved to the extended # quantizer interface in a later version of PIL. diff --git a/PIL/PngImagePlugin.py b/PIL/PngImagePlugin.py index 80757fc9b..d556360a9 100644 --- a/PIL/PngImagePlugin.py +++ b/PIL/PngImagePlugin.py @@ -70,6 +70,8 @@ _MODES = { } +_simple_palette = re.compile(b'^\xff+\x00+$') + # -------------------------------------------------------------------- # Support classes. Suitable for PNG and related formats like MNG etc. @@ -251,10 +253,12 @@ class PngStream(ChunkStream): # transparency s = ImageFile._safe_read(self.fp, len) if self.im_mode == "P": - i = s.find(b"\0") - if i >= 0: - self.im_info["transparency"] = i - self.im_info["transparency_palette"] = s + if _simple_palette.match(s): + i = s.find(b"\0") + if i >= 0: + self.im_info["transparency"] = i + else: + self.im_info["transparency"] = s elif self.im_mode == "L": self.im_info["transparency"] = i16(s) elif self.im_mode == "RGB": @@ -514,7 +518,10 @@ def _save(im, fp, filename, chunk=putchunk, check=0): else: dictionary = b"" - im.encoderconfig = ("optimize" in im.encoderinfo, dictionary) + im.encoderconfig = ("optimize" in im.encoderinfo, + im.encoderinfo.get("compress_level", -1), + im.encoderinfo.get("compress_type", -1), + dictionary) # get the corresponding PNG mode try: @@ -538,12 +545,16 @@ def _save(im, fp, filename, chunk=putchunk, check=0): b'\0') # 12: interlace flag if im.mode == "P": - chunk(fp, b"PLTE", im.im.getpalette("RGB")) + palette_bytes = (2 ** bits) * 3 + chunk(fp, b"PLTE", im.im.getpalette("RGB")[:palette_bytes]) if "transparency" in im.encoderinfo: if im.mode == "P": transparency = max(0, min(255, im.encoderinfo["transparency"])) - chunk(fp, b"tRNS", b'\xFF' * transparency + b'\0') + alpha = b'\xFF' * transparency + b'\0' + # limit to actual palette size + alpha_bytes = 2**bits + chunk(fp, b"tRNS", alpha[:alpha_bytes]) elif im.mode == "L": transparency = max(0, min(65535, im.encoderinfo["transparency"])) chunk(fp, b"tRNS", o16(transparency)) @@ -552,6 +563,11 @@ def _save(im, fp, filename, chunk=putchunk, check=0): chunk(fp, b"tRNS", o16(red) + o16(green) + o16(blue)) else: raise IOError("cannot use transparency for this mode") + else: + if im.mode == "P" and im.im.getpalettemode() == "RGBA": + alpha = im.im.getpalette("RGBA", "A") + alpha_bytes = 2**bits + chunk(fp, b"tRNS", alpha[:alpha_bytes]) if 0: # FIXME: to be supported some day diff --git a/Tests/test_file_png.py b/Tests/test_file_png.py index 7086d8709..c8ee36c93 100644 --- a/Tests/test_file_png.py +++ b/Tests/test_file_png.py @@ -117,6 +117,17 @@ def test_interlace(): assert_no_exception(lambda: im.load()) +def test_load_transparent_p(): + file = "Tests/images/pil123p.png" + im = Image.open(file) + + assert_image(im, "P", (162, 150)) + im = im.convert("RGBA") + assert_image(im, "RGBA", (162, 150)) + + # image has 124 uniqe qlpha values + assert_equal(len(im.split()[3].getcolors()), 124) + def test_load_verify(): # Check open/load/verify exception (@PIL150) diff --git a/Tests/test_image_quantize.py b/Tests/test_image_quantize.py index 5dcbab7fe..ce22bd5a6 100644 --- a/Tests/test_image_quantize.py +++ b/Tests/test_image_quantize.py @@ -13,3 +13,10 @@ def test_sanity(): im = im.quantize(palette=lena("P")) assert_image(im, "P", im.size) +def test_octree_quantize(): + im = lena() + + im = im.quantize(100, Image.FASTOCTREE) + assert_image(im, "P", im.size) + + assert len(im.getcolors()) == 100 \ No newline at end of file diff --git a/_imaging.c b/_imaging.c index 8ebaad52b..0ced4d488 100644 --- a/_imaging.c +++ b/_imaging.c @@ -65,7 +65,7 @@ * 2005-10-02 fl Added access proxy * 2006-06-18 fl Always draw last point in polyline * - * Copyright (c) 1997-2006 by Secret Labs AB + * Copyright (c) 1997-2006 by Secret Labs AB * Copyright (c) 1995-2006 by Fredrik Lundh * * See the README file for information on usage and redistribution. @@ -157,7 +157,7 @@ typedef struct { static PyTypeObject PixelAccess_Type; -PyObject* +PyObject* PyImagingNew(Imaging imOut) { ImagingObject* imagep; @@ -584,7 +584,7 @@ getink(PyObject* color, Imaging im, char* ink) /* FACTORIES */ /* -------------------------------------------------------------------- */ -static PyObject* +static PyObject* _fill(PyObject* self, PyObject* args) { char* mode; @@ -592,7 +592,7 @@ _fill(PyObject* self, PyObject* args) PyObject* color; char buffer[4]; Imaging im; - + xsize = ysize = 256; color = NULL; @@ -616,7 +616,7 @@ _fill(PyObject* self, PyObject* args) return PyImagingNew(im); } -static PyObject* +static PyObject* _new(PyObject* self, PyObject* args) { char* mode; @@ -628,7 +628,7 @@ _new(PyObject* self, PyObject* args) return PyImagingNew(ImagingNew(mode, xsize, ysize)); } -static PyObject* +static PyObject* _new_array(PyObject* self, PyObject* args) { char* mode; @@ -640,7 +640,7 @@ _new_array(PyObject* self, PyObject* args) return PyImagingNew(ImagingNewArray(mode, xsize, ysize)); } -static PyObject* +static PyObject* _new_block(PyObject* self, PyObject* args) { char* mode; @@ -652,7 +652,7 @@ _new_block(PyObject* self, PyObject* args) return PyImagingNew(ImagingNewBlock(mode, xsize, ysize)); } -static PyObject* +static PyObject* _getcount(PyObject* self, PyObject* args) { if (!PyArg_ParseTuple(args, ":getcount")) @@ -661,7 +661,7 @@ _getcount(PyObject* self, PyObject* args) return PyInt_FromLong(ImagingNewCount); } -static PyObject* +static PyObject* _linear_gradient(PyObject* self, PyObject* args) { char* mode; @@ -672,7 +672,7 @@ _linear_gradient(PyObject* self, PyObject* args) return PyImagingNew(ImagingFillLinearGradient(mode)); } -static PyObject* +static PyObject* _radial_gradient(PyObject* self, PyObject* args) { char* mode; @@ -683,7 +683,7 @@ _radial_gradient(PyObject* self, PyObject* args) return PyImagingNew(ImagingFillRadialGradient(mode)); } -static PyObject* +static PyObject* _open_ppm(PyObject* self, PyObject* args) { char* filename; @@ -708,13 +708,13 @@ _alpha_composite(ImagingObject* self, PyObject* args) return PyImagingNew(ImagingAlphaComposite(imagep1->image, imagep2->image)); } -static PyObject* +static PyObject* _blend(ImagingObject* self, PyObject* args) { ImagingObject* imagep1; ImagingObject* imagep2; double alpha; - + alpha = 0.5; if (!PyArg_ParseTuple(args, "O!O!|d", &Imaging_Type, &imagep1, @@ -730,7 +730,7 @@ _blend(ImagingObject* self, PyObject* args) /* METHODS */ /* -------------------------------------------------------------------- */ -static PyObject* +static PyObject* _convert(ImagingObject* self, PyObject* args) { char* mode; @@ -741,20 +741,20 @@ _convert(ImagingObject* self, PyObject* args) return NULL; if (paletteimage != NULL) { if (!PyImaging_Check(paletteimage)) { - PyObject_Print((PyObject *)paletteimage, stderr, 0); - PyErr_SetString(PyExc_ValueError, "palette argument must be image with mode 'P'"); - return NULL; + PyObject_Print((PyObject *)paletteimage, stderr, 0); + PyErr_SetString(PyExc_ValueError, "palette argument must be image with mode 'P'"); + return NULL; } if (paletteimage->image->palette == NULL) { - PyErr_SetString(PyExc_ValueError, "null palette"); - return NULL; + PyErr_SetString(PyExc_ValueError, "null palette"); + return NULL; } } return PyImagingNew(ImagingConvert(self->image, mode, paletteimage ? paletteimage->image->palette : NULL, dither)); } -static PyObject* +static PyObject* _convert2(ImagingObject* self, PyObject* args) { ImagingObject* imagep1; @@ -771,7 +771,7 @@ _convert2(ImagingObject* self, PyObject* args) return Py_None; } -static PyObject* +static PyObject* _convert_matrix(ImagingObject* self, PyObject* args) { char* mode; @@ -788,7 +788,7 @@ _convert_matrix(ImagingObject* self, PyObject* args) return PyImagingNew(ImagingConvertMatrix(self->image, mode, m)); } -static PyObject* +static PyObject* _copy(ImagingObject* self, PyObject* args) { if (!PyArg_ParseTuple(args, "")) @@ -797,7 +797,7 @@ _copy(ImagingObject* self, PyObject* args) return PyImagingNew(ImagingCopy(self->image)); } -static PyObject* +static PyObject* _copy2(ImagingObject* self, PyObject* args) { ImagingObject* imagep1; @@ -814,7 +814,7 @@ _copy2(ImagingObject* self, PyObject* args) return Py_None; } -static PyObject* +static PyObject* _crop(ImagingObject* self, PyObject* args) { int x0, y0, x1, y1; @@ -824,7 +824,7 @@ _crop(ImagingObject* self, PyObject* args) return PyImagingNew(ImagingCrop(self->image, x0, y0, x1, y1)); } -static PyObject* +static PyObject* _expand(ImagingObject* self, PyObject* args) { int x, y; @@ -835,7 +835,7 @@ _expand(ImagingObject* self, PyObject* args) return PyImagingNew(ImagingExpand(self->image, x, y, mode)); } -static PyObject* +static PyObject* _filter(ImagingObject* self, PyObject* args) { PyObject* imOut; @@ -848,7 +848,7 @@ _filter(ImagingObject* self, PyObject* args) if (!PyArg_ParseTuple(args, "(ii)ffO", &xsize, &ysize, &divisor, &offset, &kernel)) return NULL; - + /* get user-defined kernel */ kerneldata = getlist(kernel, &kernelsize, NULL, TYPE_FLOAT32); if (!kerneldata) @@ -868,7 +868,7 @@ _filter(ImagingObject* self, PyObject* args) } #ifdef WITH_UNSHARPMASK -static PyObject* +static PyObject* _gaussian_blur(ImagingObject* self, PyObject* args) { Imaging imIn; @@ -890,7 +890,7 @@ _gaussian_blur(ImagingObject* self, PyObject* args) } #endif -static PyObject* +static PyObject* _getpalette(ImagingObject* self, PyObject* args) { PyObject* palette; @@ -924,6 +924,17 @@ _getpalette(ImagingObject* self, PyObject* args) return palette; } +static PyObject* +_getpalettemode(ImagingObject* self, PyObject* args) +{ + if (!self->image->palette) { + PyErr_SetString(PyExc_ValueError, no_palette); + return NULL; + } + + return PyString_FromString(self->image->palette->mode); +} + static inline int _getxy(PyObject* xy, int* x, int *y) { @@ -931,7 +942,7 @@ _getxy(PyObject* xy, int* x, int *y) if (!PyTuple_Check(xy) || PyTuple_GET_SIZE(xy) != 2) goto badarg; - + value = PyTuple_GET_ITEM(xy, 0); if (PyInt_Check(value)) *x = PyInt_AS_LONG(value); @@ -965,7 +976,7 @@ _getxy(PyObject* xy, int* x, int *y) return -1; } -static PyObject* +static PyObject* _getpixel(ImagingObject* self, PyObject* args) { PyObject* xy; @@ -1065,7 +1076,7 @@ _histogram(ImagingObject* self, PyObject* args) } #ifdef WITH_MODEFILTER -static PyObject* +static PyObject* _modefilter(ImagingObject* self, PyObject* args) { int size; @@ -1076,7 +1087,7 @@ _modefilter(ImagingObject* self, PyObject* args) } #endif -static PyObject* +static PyObject* _offset(ImagingObject* self, PyObject* args) { int xoffset, yoffset; @@ -1086,7 +1097,7 @@ _offset(ImagingObject* self, PyObject* args) return PyImagingNew(ImagingOffset(self->image, xoffset, yoffset)); } -static PyObject* +static PyObject* _paste(ImagingObject* self, PyObject* args) { int status; @@ -1251,7 +1262,7 @@ _putdata(ImagingObject* self, PyObject* args) x = image->xsize; memcpy(image->image8[y], p+i, x); } - else + else /* Scaled and clipped string data */ for (i = x = y = 0; i < n; i++) { image->image8[y][x] = CLIP((int) (p[i] * scale + offset)); @@ -1355,7 +1366,7 @@ _putdata(ImagingObject* self, PyObject* args) #ifdef WITH_QUANTIZE #include "Quant.h" -static PyObject* +static PyObject* _quantize(ImagingObject* self, PyObject* args) { int colours = 256; @@ -1375,7 +1386,7 @@ _quantize(ImagingObject* self, PyObject* args) } #endif -static PyObject* +static PyObject* _putpalette(ImagingObject* self, PyObject* args) { ImagingShuffler unpack; @@ -1410,14 +1421,12 @@ _putpalette(ImagingObject* self, PyObject* args) return Py_None; } -static PyObject* +static PyObject* _putpalettealpha(ImagingObject* self, PyObject* args) { int index; int alpha = 0; - char* tpalette = NULL; - int tpaletteSize = 0; - if (!PyArg_ParseTuple(args, "i|i"PY_ARG_BYTES_LENGTH, &index, &alpha, &tpalette, &tpaletteSize)) + if (!PyArg_ParseTuple(args, "i|i", &index, &alpha)) return NULL; if (!self->image->palette) { @@ -1429,23 +1438,43 @@ _putpalettealpha(ImagingObject* self, PyObject* args) PyErr_SetString(PyExc_ValueError, outside_palette); return NULL; } - - strcpy(self->image->palette->mode, "RGBA"); - if (tpaletteSize > 0) { - for (index = 0; index < tpaletteSize; index++) { - self->image->palette->palette[index*4+3] = (UINT8) tpalette[index]; - } + strcpy(self->image->palette->mode, "RGBA"); + self->image->palette->palette[index*4+3] = (UINT8) alpha; + + Py_INCREF(Py_None); + return Py_None; +} + +static PyObject* +_putpalettealphas(ImagingObject* self, PyObject* args) +{ + int i; + UINT8 *values; + int length; + if (!PyArg_ParseTuple(args, "s#", &values, &length)) + return NULL; + + if (!self->image->palette) { + PyErr_SetString(PyExc_ValueError, no_palette); + return NULL; } - else { - self->image->palette->palette[index*4+3] = (UINT8) alpha; + + if (length > 256) { + PyErr_SetString(PyExc_ValueError, outside_palette); + return NULL; + } + + strcpy(self->image->palette->mode, "RGBA"); + for (i=0; iimage->palette->palette[i*4+3] = (UINT8) values[i]; } Py_INCREF(Py_None); return Py_None; } -static PyObject* +static PyObject* _putpixel(ImagingObject* self, PyObject* args) { Imaging im; @@ -1457,7 +1486,7 @@ _putpixel(ImagingObject* self, PyObject* args) return NULL; im = self->image; - + if (x < 0 || x >= im->xsize || y < 0 || y >= im->ysize) { PyErr_SetString(PyExc_IndexError, outside_image); return NULL; @@ -1474,7 +1503,7 @@ _putpixel(ImagingObject* self, PyObject* args) } #ifdef WITH_RANKFILTER -static PyObject* +static PyObject* _rankfilter(ImagingObject* self, PyObject* args) { int size, rank; @@ -1485,7 +1514,7 @@ _rankfilter(ImagingObject* self, PyObject* args) } #endif -static PyObject* +static PyObject* _resize(ImagingObject* self, PyObject* args) { Imaging imIn; @@ -1501,16 +1530,16 @@ _resize(ImagingObject* self, PyObject* args) imOut = ImagingNew(imIn->mode, xsize, ysize); if (imOut) (void) ImagingResize(imOut, imIn, filter); - + return PyImagingNew(imOut); } -static PyObject* +static PyObject* _rotate(ImagingObject* self, PyObject* args) { Imaging imOut; Imaging imIn; - + double theta; int filter = IMAGING_TRANSFORM_NEAREST; if (!PyArg_ParseTuple(args, "d|i", &theta, &filter)) @@ -1556,7 +1585,7 @@ _rotate(ImagingObject* self, PyObject* args) #define IS_RGB(mode)\ (!strcmp(mode, "RGB") || !strcmp(mode, "RGBA") || !strcmp(mode, "RGBX")) -static PyObject* +static PyObject* im_setmode(ImagingObject* self, PyObject* args) { /* attempt to modify the mode of an image in place */ @@ -1594,7 +1623,7 @@ im_setmode(ImagingObject* self, PyObject* args) return Py_None; } -static PyObject* +static PyObject* _stretch(ImagingObject* self, PyObject* args) { Imaging imIn; @@ -1611,7 +1640,7 @@ _stretch(ImagingObject* self, PyObject* args) /* two-pass resize: minimize size of intermediate image */ if (imIn->xsize * ysize < xsize * imIn->ysize) imTemp = ImagingNew(imIn->mode, imIn->xsize, ysize); - else + else imTemp = ImagingNew(imIn->mode, xsize, imIn->ysize); if (!imTemp) return NULL; @@ -1636,11 +1665,11 @@ _stretch(ImagingObject* self, PyObject* args) } ImagingDelete(imTemp); - + return PyImagingNew(imOut); } -static PyObject* +static PyObject* _transform2(ImagingObject* self, PyObject* args) { static const char* wrong_number = "wrong number of matrix entries"; @@ -1715,7 +1744,7 @@ _transform2(ImagingObject* self, PyObject* args) return Py_None; } -static PyObject* +static PyObject* _transpose(ImagingObject* self, PyObject* args) { Imaging imIn; @@ -1726,7 +1755,7 @@ _transpose(ImagingObject* self, PyObject* args) return NULL; imIn = self->image; - + switch (op) { case 0: /* flip left right */ case 1: /* flip top bottom */ @@ -1765,7 +1794,7 @@ _transpose(ImagingObject* self, PyObject* args) } #ifdef WITH_UNSHARPMASK -static PyObject* +static PyObject* _unsharp_mask(ImagingObject* self, PyObject* args) { Imaging imIn; @@ -1791,13 +1820,13 @@ _unsharp_mask(ImagingObject* self, PyObject* args) /* -------------------------------------------------------------------- */ -static PyObject* +static PyObject* _isblock(ImagingObject* self, PyObject* args) { return PyInt_FromLong((long) self->image->block); } -static PyObject* +static PyObject* _getbbox(ImagingObject* self, PyObject* args) { int bbox[4]; @@ -1809,7 +1838,7 @@ _getbbox(ImagingObject* self, PyObject* args) return Py_BuildValue("iiii", bbox[0], bbox[1], bbox[2], bbox[3]); } -static PyObject* +static PyObject* _getcolors(ImagingObject* self, PyObject* args) { ImagingColorItem* items; @@ -1843,7 +1872,7 @@ _getcolors(ImagingObject* self, PyObject* args) return out; } -static PyObject* +static PyObject* _getextrema(ImagingObject* self, PyObject* args) { union { @@ -1852,7 +1881,7 @@ _getextrema(ImagingObject* self, PyObject* args) FLOAT32 f[2]; } extrema; int status; - + status = ImagingGetExtrema(self->image, &extrema); if (status < 0) return NULL; @@ -1871,7 +1900,7 @@ _getextrema(ImagingObject* self, PyObject* args) return Py_None; } -static PyObject* +static PyObject* _getprojection(ImagingObject* self, PyObject* args) { unsigned char* xprofile; @@ -1901,7 +1930,7 @@ _getprojection(ImagingObject* self, PyObject* args) /* -------------------------------------------------------------------- */ -static PyObject* +static PyObject* _getband(ImagingObject* self, PyObject* args) { int band; @@ -1912,7 +1941,7 @@ _getband(ImagingObject* self, PyObject* args) return PyImagingNew(ImagingGetBand(self->image, band)); } -static PyObject* +static PyObject* _fillband(ImagingObject* self, PyObject* args) { int band; @@ -1923,12 +1952,12 @@ _fillband(ImagingObject* self, PyObject* args) if (!ImagingFillBand(self->image, band, color)) return NULL; - + Py_INCREF(Py_None); return Py_None; } -static PyObject* +static PyObject* _putband(ImagingObject* self, PyObject* args) { ImagingObject* imagep; @@ -1949,13 +1978,13 @@ _putband(ImagingObject* self, PyObject* args) #ifdef WITH_IMAGECHOPS -static PyObject* +static PyObject* _chop_invert(ImagingObject* self, PyObject* args) { return PyImagingNew(ImagingNegative(self->image)); } -static PyObject* +static PyObject* _chop_lighter(ImagingObject* self, PyObject* args) { ImagingObject* imagep; @@ -1966,7 +1995,7 @@ _chop_lighter(ImagingObject* self, PyObject* args) return PyImagingNew(ImagingChopLighter(self->image, imagep->image)); } -static PyObject* +static PyObject* _chop_darker(ImagingObject* self, PyObject* args) { ImagingObject* imagep; @@ -1977,7 +2006,7 @@ _chop_darker(ImagingObject* self, PyObject* args) return PyImagingNew(ImagingChopDarker(self->image, imagep->image)); } -static PyObject* +static PyObject* _chop_difference(ImagingObject* self, PyObject* args) { ImagingObject* imagep; @@ -1988,7 +2017,7 @@ _chop_difference(ImagingObject* self, PyObject* args) return PyImagingNew(ImagingChopDifference(self->image, imagep->image)); } -static PyObject* +static PyObject* _chop_multiply(ImagingObject* self, PyObject* args) { ImagingObject* imagep; @@ -1999,7 +2028,7 @@ _chop_multiply(ImagingObject* self, PyObject* args) return PyImagingNew(ImagingChopMultiply(self->image, imagep->image)); } -static PyObject* +static PyObject* _chop_screen(ImagingObject* self, PyObject* args) { ImagingObject* imagep; @@ -2010,7 +2039,7 @@ _chop_screen(ImagingObject* self, PyObject* args) return PyImagingNew(ImagingChopScreen(self->image, imagep->image)); } -static PyObject* +static PyObject* _chop_add(ImagingObject* self, PyObject* args) { ImagingObject* imagep; @@ -2028,7 +2057,7 @@ _chop_add(ImagingObject* self, PyObject* args) scale, offset)); } -static PyObject* +static PyObject* _chop_subtract(ImagingObject* self, PyObject* args) { ImagingObject* imagep; @@ -2046,7 +2075,7 @@ _chop_subtract(ImagingObject* self, PyObject* args) scale, offset)); } -static PyObject* +static PyObject* _chop_and(ImagingObject* self, PyObject* args) { ImagingObject* imagep; @@ -2057,7 +2086,7 @@ _chop_and(ImagingObject* self, PyObject* args) return PyImagingNew(ImagingChopAnd(self->image, imagep->image)); } -static PyObject* +static PyObject* _chop_or(ImagingObject* self, PyObject* args) { ImagingObject* imagep; @@ -2068,7 +2097,7 @@ _chop_or(ImagingObject* self, PyObject* args) return PyImagingNew(ImagingChopOr(self->image, imagep->image)); } -static PyObject* +static PyObject* _chop_xor(ImagingObject* self, PyObject* args) { ImagingObject* imagep; @@ -2079,7 +2108,7 @@ _chop_xor(ImagingObject* self, PyObject* args) return PyImagingNew(ImagingChopXor(self->image, imagep->image)); } -static PyObject* +static PyObject* _chop_add_modulo(ImagingObject* self, PyObject* args) { ImagingObject* imagep; @@ -2090,7 +2119,7 @@ _chop_add_modulo(ImagingObject* self, PyObject* args) return PyImagingNew(ImagingChopAddModulo(self->image, imagep->image)); } -static PyObject* +static PyObject* _chop_subtract_modulo(ImagingObject* self, PyObject* args) { ImagingObject* imagep; @@ -2284,7 +2313,7 @@ _draw_dealloc(ImagingDrawObject* self) extern int PyPath_Flatten(PyObject* data, double **xy); -static PyObject* +static PyObject* _draw_ink(ImagingDrawObject* self, PyObject* args) { INT32 ink = 0; @@ -2299,7 +2328,7 @@ _draw_ink(ImagingDrawObject* self, PyObject* args) return PyInt_FromLong((int) ink); } -static PyObject* +static PyObject* _draw_arc(ImagingDrawObject* self, PyObject* args) { int x0, y0, x1, y1; @@ -2319,7 +2348,7 @@ _draw_arc(ImagingDrawObject* self, PyObject* args) return Py_None; } -static PyObject* +static PyObject* _draw_bitmap(ImagingDrawObject* self, PyObject* args) { double *xy; @@ -2356,7 +2385,7 @@ _draw_bitmap(ImagingDrawObject* self, PyObject* args) return Py_None; } -static PyObject* +static PyObject* _draw_chord(ImagingDrawObject* self, PyObject* args) { int x0, y0, x1, y1; @@ -2374,7 +2403,7 @@ _draw_chord(ImagingDrawObject* self, PyObject* args) return Py_None; } -static PyObject* +static PyObject* _draw_ellipse(ImagingDrawObject* self, PyObject* args) { double* xy; @@ -2401,7 +2430,7 @@ _draw_ellipse(ImagingDrawObject* self, PyObject* args) self->image->image, (int) xy[0], (int) xy[1], (int) xy[2], (int) xy[3], &ink, fill, self->blend ); - + free(xy); if (n < 0) @@ -2411,7 +2440,7 @@ _draw_ellipse(ImagingDrawObject* self, PyObject* args) return Py_None; } -static PyObject* +static PyObject* _draw_line(ImagingDrawObject* self, PyObject* args) { int x0, y0, x1, y1; @@ -2427,7 +2456,7 @@ _draw_line(ImagingDrawObject* self, PyObject* args) return Py_None; } -static PyObject* +static PyObject* _draw_lines(ImagingDrawObject* self, PyObject* args) { double *xy; @@ -2480,7 +2509,7 @@ _draw_lines(ImagingDrawObject* self, PyObject* args) return Py_None; } -static PyObject* +static PyObject* _draw_point(ImagingDrawObject* self, PyObject* args) { int x, y; @@ -2495,7 +2524,7 @@ _draw_point(ImagingDrawObject* self, PyObject* args) return Py_None; } -static PyObject* +static PyObject* _draw_points(ImagingDrawObject* self, PyObject* args) { double *xy; @@ -2530,7 +2559,7 @@ _draw_points(ImagingDrawObject* self, PyObject* args) /* from outline.c */ extern ImagingOutline PyOutline_AsOutline(PyObject* outline); -static PyObject* +static PyObject* _draw_outline(ImagingDrawObject* self, PyObject* args) { ImagingOutline outline; @@ -2557,7 +2586,7 @@ _draw_outline(ImagingDrawObject* self, PyObject* args) #endif -static PyObject* +static PyObject* _draw_pieslice(ImagingDrawObject* self, PyObject* args) { int x0, y0, x1, y1; @@ -2575,7 +2604,7 @@ _draw_pieslice(ImagingDrawObject* self, PyObject* args) return Py_None; } -static PyObject* +static PyObject* _draw_polygon(ImagingDrawObject* self, PyObject* args) { double *xy; @@ -2621,7 +2650,7 @@ _draw_polygon(ImagingDrawObject* self, PyObject* args) return Py_None; } -static PyObject* +static PyObject* _draw_rectangle(ImagingDrawObject* self, PyObject* args) { double* xy; @@ -2648,7 +2677,7 @@ _draw_rectangle(ImagingDrawObject* self, PyObject* args) self->image->image, (int) xy[0], (int) xy[1], (int) xy[2], (int) xy[3], &ink, fill, self->blend ); - + free(xy); if (n < 0) @@ -2759,7 +2788,7 @@ pixel_access_setitem(PixelAccessObject *self, PyObject *xy, PyObject *color) #ifdef WITH_EFFECTS -static PyObject* +static PyObject* _effect_mandelbrot(ImagingObject* self, PyObject* args) { int xsize = 512; @@ -2778,7 +2807,7 @@ _effect_mandelbrot(ImagingObject* self, PyObject* args) return PyImagingNew(ImagingEffectMandelbrot(xsize, ysize, extent, quality)); } -static PyObject* +static PyObject* _effect_noise(ImagingObject* self, PyObject* args) { int xsize, ysize; @@ -2789,7 +2818,7 @@ _effect_noise(ImagingObject* self, PyObject* args) return PyImagingNew(ImagingEffectNoise(xsize, ysize, sigma)); } -static PyObject* +static PyObject* _effect_spread(ImagingObject* self, PyObject* args) { int dist; @@ -2806,7 +2835,7 @@ _effect_spread(ImagingObject* self, PyObject* args) /* UTILITIES */ /* -------------------------------------------------------------------- */ -static PyObject* +static PyObject* _crc32(PyObject* self, PyObject* args) { unsigned char* buffer; @@ -2827,7 +2856,7 @@ _crc32(PyObject* self, PyObject* args) return Py_BuildValue("ii", (crc >> 16) & 0xFFFF, crc & 0xFFFF); } -static PyObject* +static PyObject* _getcodecstatus(PyObject* self, PyObject* args) { int status; @@ -2861,7 +2890,7 @@ _getcodecstatus(PyObject* self, PyObject* args) #ifdef WITH_DEBUG -static PyObject* +static PyObject* _save_ppm(ImagingObject* self, PyObject* args) { char* filename; @@ -2935,10 +2964,12 @@ static struct PyMethodDef methods[] = { {"fillband", (PyCFunction)_fillband, 1}, {"setmode", (PyCFunction)im_setmode, 1}, - + {"getpalette", (PyCFunction)_getpalette, 1}, + {"getpalettemode", (PyCFunction)_getpalettemode, 1}, {"putpalette", (PyCFunction)_putpalette, 1}, {"putpalettealpha", (PyCFunction)_putpalettealpha, 1}, + {"putpalettealphas", (PyCFunction)_putpalettealphas, 1}, #ifdef WITH_IMAGECHOPS /* Channel operations (ImageChops) */ @@ -3334,7 +3365,7 @@ static PyMethodDef functions[] = { #ifdef WITH_IMAGEPATH {"path", (PyCFunction)PyPath_Create, 1}, #endif - + /* Experimental arrow graphics stuff */ #ifdef WITH_ARROW {"outline", (PyCFunction)PyOutline_Create, 1}, @@ -3371,6 +3402,13 @@ setup_module(PyObject* m) { #endif #ifdef HAVE_LIBZ +#include "zlib.h" + /* zip encoding strategies */ + PyModule_AddIntConstant(m, "DEFAULT_STRATEGY", Z_DEFAULT_STRATEGY); + PyModule_AddIntConstant(m, "FILTERED", Z_FILTERED); + PyModule_AddIntConstant(m, "HUFFMAN_ONLY", Z_HUFFMAN_ONLY); + PyModule_AddIntConstant(m, "RLE", Z_RLE); + PyModule_AddIntConstant(m, "FIXED", Z_FIXED); { extern const char* ImagingZipVersion(void); PyDict_SetItemString(d, "zlib_version", PyUnicode_FromString(ImagingZipVersion())); @@ -3409,4 +3447,3 @@ init_imaging(void) } #endif - diff --git a/encode.c b/encode.c index 123a3dc79..f6191613f 100644 --- a/encode.c +++ b/encode.c @@ -445,10 +445,14 @@ PyImaging_ZipEncoderNew(PyObject* self, PyObject* args) char* mode; char* rawmode; int optimize = 0; + int compress_level = -1; + int compress_type = -1; char* dictionary = NULL; int dictionary_size = 0; - if (!PyArg_ParseTuple(args, "ss|i"PY_ARG_BYTES_LENGTH, &mode, &rawmode, - &optimize, &dictionary, &dictionary_size)) + if (!PyArg_ParseTuple(args, "ss|iii"PY_ARG_BYTES_LENGTH, &mode, &rawmode, + &optimize, + &compress_level, &compress_type, + &dictionary, &dictionary_size)) return NULL; /* Copy to avoid referencing Python's memory, but there's no mechanism to @@ -477,6 +481,8 @@ PyImaging_ZipEncoderNew(PyObject* self, PyObject* args) ((ZIPSTATE*)encoder->state.context)->mode = ZIP_PNG_PALETTE; ((ZIPSTATE*)encoder->state.context)->optimize = optimize; + ((ZIPSTATE*)encoder->state.context)->compress_level = compress_level; + ((ZIPSTATE*)encoder->state.context)->compress_type = compress_type; ((ZIPSTATE*)encoder->state.context)->dictionary = dictionary; ((ZIPSTATE*)encoder->state.context)->dictionary_size = dictionary_size; @@ -512,16 +518,16 @@ static unsigned int** get_qtables_arrays(PyObject* qtables) { PyObject* table_data; int i, j, num_tables; unsigned int **qarrays; - + if (qtables == Py_None) { return NULL; } - + if (!PySequence_Check(qtables)) { PyErr_SetString(PyExc_ValueError, "Invalid quantization tables"); return NULL; } - + tables = PySequence_Fast(qtables, "expected a sequence"); num_tables = PySequence_Size(qtables); if (num_tables < 2 || num_tables > NUM_QUANT_TBLS) { diff --git a/libImaging/Quant.c b/libImaging/Quant.c index 5d8d13500..ff15ac01f 100644 --- a/libImaging/Quant.c +++ b/libImaging/Quant.c @@ -26,6 +26,7 @@ #include #include "Quant.h" +#include "QuantOctree.h" #include "QuantDefines.h" #include "QuantHash.h" @@ -1485,6 +1486,8 @@ ImagingQuantize(Imaging im, int colors, int mode, int kmeans) int result; unsigned long* newData; Imaging imOut; + int withAlpha = 0; + ImagingSectionCookie cookie; if (!im) return ImagingError_ModeError(); @@ -1494,9 +1497,13 @@ ImagingQuantize(Imaging im, int colors, int mode, int kmeans) return (Imaging) ImagingError_ValueError("bad number of colors"); if (strcmp(im->mode, "L") != 0 && strcmp(im->mode, "P") != 0 && - strcmp(im->mode, "RGB")) + strcmp(im->mode, "RGB") != 0 && strcmp(im->mode, "RGBA") !=0) return ImagingError_ModeError(); + /* only octree supports RGBA */ + if (!strcmp(im->mode, "RGBA") && mode != 2) + return ImagingError_ModeError(); + p = malloc(sizeof(Pixel) * im->xsize * im->ysize); if (!p) return ImagingError_MemoryError(); @@ -1529,7 +1536,7 @@ ImagingQuantize(Imaging im, int colors, int mode, int kmeans) p[i].c.b = pp[v*4+2]; } - } else if (!strcmp(im->mode, "RGB")) { + } else if (!strcmp(im->mode, "RGB") || !strcmp(im->mode, "RGBA")) { /* true colour */ for (i = y = 0; y < im->ysize; y++) @@ -1541,6 +1548,8 @@ ImagingQuantize(Imaging im, int colors, int mode, int kmeans) return (Imaging) ImagingError_ValueError("internal error"); } + ImagingSectionEnter(&cookie); + switch (mode) { case 0: /* median cut */ @@ -1566,16 +1575,31 @@ ImagingQuantize(Imaging im, int colors, int mode, int kmeans) kmeans ); break; + case 2: + if (!strcmp(im->mode, "RGBA")) { + withAlpha = 1; + } + result = quantize_octree( + p, + im->xsize*im->ysize, + colors, + &palette, + &paletteLength, + &newData, + withAlpha + ); + break; default: result = 0; break; } free(p); + ImagingSectionLeave(&cookie); if (result) { - imOut = ImagingNew("P", im->xsize, im->ysize); + ImagingSectionEnter(&cookie); for (i = y = 0; y < im->ysize; y++) for (x=0; x < im->xsize; x++) @@ -1589,7 +1613,11 @@ ImagingQuantize(Imaging im, int colors, int mode, int kmeans) *pp++ = palette[i].c.r; *pp++ = palette[i].c.g; *pp++ = palette[i].c.b; - *pp++ = 255; + if (withAlpha) { + *pp++ = palette[i].c.a; + } else { + *pp++ = 255; + } } for (; i < 256; i++) { *pp++ = 0; @@ -1598,7 +1626,12 @@ ImagingQuantize(Imaging im, int colors, int mode, int kmeans) *pp++ = 255; } + if (withAlpha) { + strcpy(imOut->palette->mode, "RGBA"); + } + free(palette); + ImagingSectionLeave(&cookie); return imOut; diff --git a/libImaging/QuantOctree.c b/libImaging/QuantOctree.c new file mode 100644 index 000000000..fcdf9e0b0 --- /dev/null +++ b/libImaging/QuantOctree.c @@ -0,0 +1,454 @@ +/* Copyright (c) 2010 Oliver Tonnhofer , 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 variation of the octree color quantization algorithm. +*/ + +#include +#include +#include + +#include "Quant.h" + +typedef struct _ColorBucket{ + /* contains palette index when used for look up cube */ + unsigned long count; + unsigned long r; + unsigned long g; + unsigned long b; + unsigned long a; +} *ColorBucket; + +typedef struct _ColorCube{ + unsigned int rBits, gBits, bBits, aBits; + unsigned int rWidth, gWidth, bWidth, aWidth; + unsigned int rOffset, gOffset, bOffset, aOffset; + + long size; + ColorBucket buckets; +} *ColorCube; + +#define MAX(a, b) (a)>(b) ? (a) : (b) + +static ColorCube +new_color_cube(int r, int g, int b, int a) { + ColorCube cube; + + cube = malloc(sizeof(struct _ColorCube)); + if (!cube) return NULL; + + cube->rBits = MAX(r, 0); + cube->gBits = MAX(g, 0); + cube->bBits = MAX(b, 0); + cube->aBits = MAX(a, 0); + + /* the width of the cube for each dimension */ + cube->rWidth = 1<rBits; + cube->gWidth = 1<gBits; + cube->bWidth = 1<bBits; + cube->aWidth = 1<aBits; + + /* the offsets of each color */ + + cube->rOffset = cube->gBits + cube->bBits + cube->aBits; + cube->gOffset = cube->bBits + cube->aBits; + cube->bOffset = cube->aBits; + cube->aOffset = 0; + + /* the number of color buckets */ + cube->size = cube->rWidth * cube->gWidth * cube->bWidth * cube->aWidth; + cube->buckets = calloc(cube->size, sizeof(struct _ColorBucket)); + + if (!cube->buckets) { + free(cube); + return NULL; + } + return cube; +} + +static void +free_color_cube(ColorCube cube) { + if (cube != NULL) { + free(cube->buckets); + free(cube); + } +} + +static long +color_bucket_offset_pos(const ColorCube cube, + unsigned int r, unsigned int g, unsigned int b, unsigned int a) +{ + return r<rOffset | g<gOffset | b<bOffset | a<aOffset; +} + +static long +color_bucket_offset(const ColorCube cube, const Pixel *p) { + unsigned int r = p->c.r>>(8-cube->rBits); + unsigned int g = p->c.g>>(8-cube->gBits); + unsigned int b = p->c.b>>(8-cube->bBits); + unsigned int a = p->c.a>>(8-cube->aBits); + return color_bucket_offset_pos(cube, r, g, b, a); +} + +static ColorBucket +color_bucket_from_cube(const ColorCube cube, const Pixel *p) { + unsigned int offset = color_bucket_offset(cube, p); + return &cube->buckets[offset]; +} + +static void +add_color_to_color_cube(const ColorCube cube, const Pixel *p) { + ColorBucket bucket = color_bucket_from_cube(cube, p); + bucket->count += 1; + bucket->r += p->c.r; + bucket->g += p->c.g; + bucket->b += p->c.b; + bucket->a += p->c.a; +} + +static long +count_used_color_buckets(const ColorCube cube) { + long usedBuckets = 0; + long i; + for (i=0; i < cube->size; i++) { + if (cube->buckets[i].count > 0) { + usedBuckets += 1; + } + } + return usedBuckets; +} + +static void +avg_color_from_color_bucket(const ColorBucket bucket, Pixel *dst) { + float count = bucket->count; + dst->c.r = (int)(bucket->r / count); + dst->c.g = (int)(bucket->g / count); + dst->c.b = (int)(bucket->b / count); + dst->c.a = (int)(bucket->a / count); +} + +static int +compare_bucket_count(const ColorBucket a, const ColorBucket b) { + return b->count - a->count; +} + +static ColorBucket +create_sorted_color_palette(const ColorCube cube) { + ColorBucket buckets; + buckets = malloc(sizeof(struct _ColorBucket)*cube->size); + if (!buckets) return NULL; + memcpy(buckets, cube->buckets, sizeof(struct _ColorBucket)*cube->size); + + qsort(buckets, cube->size, sizeof(struct _ColorBucket), + (int (*)(void const *, void const *))&compare_bucket_count); + + return buckets; +} + +void add_bucket_values(ColorBucket src, ColorBucket dst) { + dst->count += src->count; + dst->r += src->r; + dst->g += src->g; + dst->b += src->b; + dst->a += src->a; +} + +/* expand or shrink a given cube to level */ +static ColorCube copy_color_cube(const ColorCube cube, + int rBits, int gBits, int bBits, int aBits) +{ + unsigned int r, g, b, a; + long src_pos, dst_pos; + unsigned int src_reduce[4] = {0}, dst_reduce[4] = {0}; + unsigned int width[4]; + ColorCube result; + + result = new_color_cube(rBits, gBits, bBits, aBits); + if (!result) return NULL; + + if (cube->rBits > rBits) { + dst_reduce[0] = cube->rBits - result->rBits; + width[0] = cube->rWidth; + } else { + src_reduce[0] = result->rBits - cube->rBits; + width[0] = result->rWidth; + } + if (cube->gBits > gBits) { + dst_reduce[1] = cube->gBits - result->gBits; + width[1] = cube->gWidth; + } else { + src_reduce[1] = result->gBits - cube->gBits; + width[1] = result->gWidth; + } + if (cube->bBits > bBits) { + dst_reduce[2] = cube->bBits - result->bBits; + width[2] = cube->bWidth; + } else { + src_reduce[2] = result->bBits - cube->bBits; + width[2] = result->bWidth; + } + if (cube->aBits > aBits) { + dst_reduce[3] = cube->aBits - result->aBits; + width[3] = cube->aWidth; + } else { + src_reduce[3] = result->aBits - cube->aBits; + width[3] = result->aWidth; + } + + for (r=0; r>src_reduce[0], + g>>src_reduce[1], + b>>src_reduce[2], + a>>src_reduce[3]); + dst_pos = color_bucket_offset_pos(result, + r>>dst_reduce[0], + g>>dst_reduce[1], + b>>dst_reduce[2], + a>>dst_reduce[3]); + add_bucket_values( + &cube->buckets[src_pos], + &result->buckets[dst_pos] + ); + } + } + } + } + return result; +} + +void +subtract_color_buckets(ColorCube cube, ColorBucket buckets, long nBuckets) { + ColorBucket minuend, subtrahend; + long i; + Pixel p; + for (i=0; icount -= subtrahend->count; + minuend->r -= subtrahend->r; + minuend->g -= subtrahend->g; + minuend->b -= subtrahend->b; + minuend->a -= subtrahend->a; + } +} + +static void +set_lookup_value(const ColorCube cube, const Pixel *p, long value) { + ColorBucket bucket = color_bucket_from_cube(cube, p); + bucket->count = value; +} + +unsigned long +lookup_color(const ColorCube cube, const Pixel *p) { + ColorBucket bucket = color_bucket_from_cube(cube, p); + return bucket->count; +} + +void add_lookup_buckets(ColorCube cube, ColorBucket palette, long nColors, long offset) { + long i; + Pixel p; + for (i=offset; i 64). + + For a quantization to 256 colors all 64 coarse colors will be used + plus the 192 most used color buckets from the fine color cube. + The average of all colors within one bucket is used as the actual + color for that bucket. + + For images with alpha the cubes gets a forth dimension, + 8x16x8x8 and 4x4x4x4. + */ + + /* create fine cube */ + fineCube = new_color_cube(cubeBits[0], cubeBits[1], + cubeBits[2], cubeBits[3]); + if (!fineCube) goto error; + for (i=0; i nQuantPixels) + nCoarseColors = nQuantPixels; + + /* how many space do we have in our palette for fine colors? */ + nFineColors = nQuantPixels - nCoarseColors; + + /* create fine color palette */ + paletteBucketsFine = create_sorted_color_palette(fineCube); + if (!paletteBucketsFine) goto error; + + /* remove the used fine colors from the coarse cube */ + subtract_color_buckets(coarseCube, paletteBucketsFine, nFineColors); + + /* did the substraction cleared one or more coarse bucket? */ + while (nCoarseColors > count_used_color_buckets(coarseCube)) { + /* then we can use the free buckets for fine colors */ + nAlreadySubtracted = nFineColors; + nCoarseColors = count_used_color_buckets(coarseCube); + nFineColors = nQuantPixels - nCoarseColors; + subtract_color_buckets(coarseCube, &paletteBucketsFine[nAlreadySubtracted], + nFineColors-nAlreadySubtracted); + } + + /* create our palette buckets with fine and coarse combined */ + paletteBucketsCoarse = create_sorted_color_palette(coarseCube); + if (!paletteBucketsCoarse) goto error; + paletteBuckets = combined_palette(paletteBucketsCoarse, nCoarseColors, + paletteBucketsFine, nFineColors); + + free(paletteBucketsFine); + paletteBucketsFine = NULL; + free(paletteBucketsCoarse); + paletteBucketsCoarse = NULL; + + /* add all coarse colors to our coarse lookup cube. */ + coarseLookupCube = new_color_cube(cubeBits[4], cubeBits[5], + cubeBits[6], cubeBits[7]); + if (!coarseLookupCube) goto error; + add_lookup_buckets(coarseLookupCube, paletteBuckets, nCoarseColors, 0); + + /* expand coarse cube (64) to larger fine cube (4k). the value of each + coarse bucket is then present in the according 64 fine buckets. */ + lookupCube = copy_color_cube(coarseLookupCube, cubeBits[0], cubeBits[1], + cubeBits[2], cubeBits[3]); + if (!lookupCube) goto error; + + /* add fine colors to the lookup cube */ + add_lookup_buckets(lookupCube, paletteBuckets, nFineColors, nCoarseColors); + + /* create result pixles and map palatte indices */ + qp = malloc(sizeof(Pixel)*nPixels); + if (!qp) goto error; + map_image_pixels(pixelData, nPixels, lookupCube, qp); + + /* convert palette buckets to RGB pixel palette */ + *palette = create_palette_array(paletteBuckets, nQuantPixels); + if (!(*palette)) goto error; + + *quantizedPixels = qp; + *paletteLength = nQuantPixels; + + free_color_cube(coarseCube); + free_color_cube(fineCube); + free_color_cube(lookupCube); + free_color_cube(coarseLookupCube); + free(paletteBuckets); + return 1; + +error: + /* everything is initialized to NULL + so we are safe to call free */ + free(qp); + free_color_cube(lookupCube); + free_color_cube(coarseLookupCube); + free(paletteBucketsCoarse); + free(paletteBucketsFine); + free_color_cube(coarseCube); + free_color_cube(fineCube); + return 0; +} diff --git a/libImaging/QuantOctree.h b/libImaging/QuantOctree.h new file mode 100644 index 000000000..fd7b7dbb7 --- /dev/null +++ b/libImaging/QuantOctree.h @@ -0,0 +1,12 @@ +#ifndef __QUANT_OCTREE_H__ +#define __QUANT_OCTREE_H__ + +int quantize_octree(Pixel *, + unsigned long, + unsigned long, + Pixel **, + unsigned long *, + unsigned long **, + int); + +#endif \ No newline at end of file diff --git a/libImaging/Zip.h b/libImaging/Zip.h index d961407e3..e9d96b9e9 100644 --- a/libImaging/Zip.h +++ b/libImaging/Zip.h @@ -27,6 +27,11 @@ typedef struct { /* Optimize (max compression) SLOW!!! */ int optimize; + + /* 0 no compression, 9 best compression, -1 default compression */ + int compress_level; + /* compression strategy Z_XXX */ + int compress_type; /* Predefined dictionary (experimental) */ char* dictionary; diff --git a/libImaging/ZipEncode.c b/libImaging/ZipEncode.c index 19b2b7787..7b56e437e 100644 --- a/libImaging/ZipEncode.c +++ b/libImaging/ZipEncode.c @@ -26,6 +26,7 @@ ImagingZipEncode(Imaging im, ImagingCodecState state, UINT8* buf, int bytes) { ZIPSTATE* context = (ZIPSTATE*) state->context; int err; + int compress_level, compress_type; UINT8* ptr; int i, bpp, s, sum; ImagingSectionCookie cookie; @@ -73,17 +74,25 @@ ImagingZipEncode(Imaging im, ImagingCodecState state, UINT8* buf, int bytes) context->z_stream.next_in = 0; context->z_stream.avail_in = 0; + compress_level = (context->optimize) ? Z_BEST_COMPRESSION + : context->compress_level; + + if (context->compress_type == -1) { + compress_type = (context->mode == ZIP_PNG) ? Z_FILTERED + : Z_DEFAULT_STRATEGY; + } else { + compress_type = context->compress_type; + } + err = deflateInit2(&context->z_stream, /* compression level */ - (context->optimize) ? Z_BEST_COMPRESSION - : Z_DEFAULT_COMPRESSION, + compress_level, /* compression method */ Z_DEFLATED, /* compression memory resources */ 15, 9, /* compression strategy (image data are filtered)*/ - (context->mode == ZIP_PNG) ? Z_FILTERED - : Z_DEFAULT_STRATEGY); + compress_type); if (err < 0) { state->errcode = IMAGING_CODEC_CONFIG; return -1; diff --git a/selftest.py b/selftest.py index f0cbe0ba4..01c78a3ff 100644 --- a/selftest.py +++ b/selftest.py @@ -157,7 +157,7 @@ def testimage(): def check_module(feature, module): try: - __import__(module) + __import__("PIL." + module) except ImportError: print("***", feature, "support not installed") else: diff --git a/setup.py b/setup.py index d05bada12..cd3e3380f 100644 --- a/setup.py +++ b/setup.py @@ -22,7 +22,7 @@ _LIB_IMAGING = ( "Geometry", "GetBBox", "GifDecode", "GifEncode", "HexDecode", "Histo", "JpegDecode", "JpegEncode", "LzwDecode", "Matrix", "ModeFilter", "MspDecode", "Negative", "Offset", "Pack", - "PackDecode", "Palette", "Paste", "Quant", "QuantHash", + "PackDecode", "Palette", "Paste", "Quant", "QuantOctree", "QuantHash", "QuantHeap", "PcdDecode", "PcxDecode", "PcxEncode", "Point", "RankFilter", "RawDecode", "RawEncode", "Storage", "SunRleDecode", "TgaRleDecode", "Unpack", "UnpackYCC", "UnsharpMask", "XbmDecode",