diff --git a/CHANGES.rst b/CHANGES.rst index e5fc39bfd..1b4826451 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -3,6 +3,24 @@ Changelog (Pillow) 2.7.0 (unreleased) ------------------ +- Fix for saving TIFF image into an io.BytesIO buffer #1011 + [mfergie] + +- Fix antialias compilation on debug versions of Python #1010 + [wiredfool] + +- Fix for Image.putdata segfault #1009 + [wiredfool] + +- Ico save, additional tests #1007 + [exherb] + +- Use PyQt4 if it has already been imported, otherwise prefer PyQt5. #1003 + [AurelienBallier] + +- Speedup stretch implementation up to 2.5 times. #977 + [homm] + - Speed up rotation by using cache aware loops, added transpose to rotations. #994 [homm] diff --git a/PIL/IcoImagePlugin.py b/PIL/IcoImagePlugin.py index 2dc46ea18..145816094 100644 --- a/PIL/IcoImagePlugin.py +++ b/PIL/IcoImagePlugin.py @@ -24,6 +24,9 @@ __version__ = "0.1" +import struct +from io import BytesIO + from PIL import Image, ImageFile, BmpImagePlugin, PngImagePlugin, _binary from math import log, ceil @@ -37,6 +40,42 @@ i32 = _binary.i32le _MAGIC = b"\0\0\1\0" +def _save(im, fp, filename): + fp.write(_MAGIC) # (2+2) + sizes = im.encoderinfo.get("sizes", + [(16, 16), (24, 24), (32, 32), (48, 48), + (64, 64), (128, 128), (255, 255)]) + width, height = im.size + filter(lambda x: False if (x[0] > width or x[1] > height or + x[0] > 255 or x[1] > 255) else True, sizes) + sizes = sorted(sizes, key=lambda x: x[0]) + fp.write(struct.pack("H", len(sizes))) # idCount(2) + offset = fp.tell() + len(sizes)*16 + for size in sizes: + width, height = size + fp.write(struct.pack("B", width)) # bWidth(1) + fp.write(struct.pack("B", height)) # bHeight(1) + fp.write(b"\0") # bColorCount(1) + fp.write(b"\0") # bReserved(1) + fp.write(b"\0\0") # wPlanes(2) + fp.write(struct.pack("H", 32)) # wBitCount(2) + + image_io = BytesIO() + tmp = im.copy() + tmp.thumbnail(size, Image.ANTIALIAS) + tmp.save(image_io, "png") + image_io.seek(0) + image_bytes = image_io.read() + bytes_len = len(image_bytes) + fp.write(struct.pack("I", bytes_len)) # dwBytesInRes(4) + fp.write(struct.pack("I", offset)) # dwImageOffset(4) + current = fp.tell() + fp.seek(offset) + fp.write(image_bytes) + offset = offset + bytes_len + fp.seek(current) + + def _accept(prefix): return prefix[:4] == _MAGIC @@ -241,4 +280,5 @@ class IcoImageFile(ImageFile.ImageFile): # -------------------------------------------------------------------- Image.register_open("ICO", IcoImageFile, _accept) +Image.register_save("ICO", _save) Image.register_extension("ICO", ".ico") diff --git a/PIL/ImageQt.py b/PIL/ImageQt.py index 88df3e564..fd662c894 100644 --- a/PIL/ImageQt.py +++ b/PIL/ImageQt.py @@ -18,12 +18,15 @@ from PIL import Image from PIL._util import isPath +import sys -try: +if 'PyQt4.QtGui' not in sys.modules: + try: from PyQt5.QtGui import QImage, qRgba -except: + except: from PyQt4.QtGui import QImage, qRgba - +else: #PyQt4 is used + from PyQt4.QtGui import QImage, qRgba ## # (Internal) Turns an RGB color into a Qt compatible color integer. diff --git a/PIL/TiffImagePlugin.py b/PIL/TiffImagePlugin.py index 6a52e5e5c..ae7d56947 100644 --- a/PIL/TiffImagePlugin.py +++ b/PIL/TiffImagePlugin.py @@ -54,6 +54,7 @@ import sys import collections import itertools import os +import io # Set these to true to force use of libtiff for reading or writing. READ_LIBTIFF = False @@ -1128,8 +1129,11 @@ def _save(im, fp, filename): print (ifd.items()) _fp = 0 if hasattr(fp, "fileno"): - fp.seek(0) - _fp = os.dup(fp.fileno()) + try: + fp.seek(0) + _fp = os.dup(fp.fileno()) + except io.UnsupportedOperation: + pass # ICC Profile crashes. blocklist = [STRIPOFFSETS, STRIPBYTECOUNTS, ROWSPERSTRIP, ICCPROFILE] diff --git a/Tests/test_file_ico.py b/Tests/test_file_ico.py index 12f4ed3f3..01d3f5904 100644 --- a/Tests/test_file_ico.py +++ b/Tests/test_file_ico.py @@ -1,5 +1,6 @@ -from helper import unittest, PillowTestCase +from helper import unittest, PillowTestCase, hopper +import io from PIL import Image # sample ppm stream @@ -16,6 +17,32 @@ class TestFileIco(PillowTestCase): self.assertEqual(im.size, (16, 16)) self.assertEqual(im.format, "ICO") + def test_save_to_bytes(self): + output = io.BytesIO() + im = hopper() + im.save(output, "ico", sizes=[(32, 32), (64, 64)]) + + # the default image + output.seek(0) + reloaded = Image.open(output) + self.assertEqual(reloaded.info['sizes'],set([(32, 32), (64, 64)])) + + self.assertEqual(im.mode, reloaded.mode) + self.assertEqual((64, 64), reloaded.size) + self.assertEqual(reloaded.format, "ICO") + self.assert_image_equal(reloaded, hopper().resize((64,64), Image.ANTIALIAS)) + + # the other one + output.seek(0) + reloaded = Image.open(output) + reloaded.size = (32,32) + + self.assertEqual(im.mode, reloaded.mode) + self.assertEqual((32, 32), reloaded.size) + self.assertEqual(reloaded.format, "ICO") + self.assert_image_equal(reloaded, hopper().resize((32,32), Image.ANTIALIAS)) + + if __name__ == '__main__': unittest.main() diff --git a/Tests/test_file_libtiff.py b/Tests/test_file_libtiff.py index efd2d5817..a9377bf63 100644 --- a/Tests/test_file_libtiff.py +++ b/Tests/test_file_libtiff.py @@ -1,9 +1,11 @@ from helper import unittest, PillowTestCase, hopper, py3 import os +import io from PIL import Image, TiffImagePlugin + class LibTiffTestCase(PillowTestCase): def setUp(self): @@ -31,6 +33,7 @@ class LibTiffTestCase(PillowTestCase): out = self.tempfile("temp.png") im.save(out) + class TestFileLibTiff(LibTiffTestCase): def test_g4_tiff(self): @@ -59,9 +62,8 @@ class TestFileLibTiff(LibTiffTestCase): def test_g4_tiff_bytesio(self): """Testing the stringio loading code path""" - from io import BytesIO file = "Tests/images/hopper_g4_500.tif" - s = BytesIO() + s = io.BytesIO() with open(file, 'rb') as f: s.write(f.read()) s.seek(0) @@ -288,7 +290,7 @@ class TestFileLibTiff(LibTiffTestCase): im2 = Image.open(out) self.assert_image_equal(im, im2) - def xtest_bw_compression_wRGB(self): + def xtest_bw_compression_w_rgb(self): """ This test passes, but when running all tests causes a failure due to output on stderr from the error thrown by libtiff. We need to capture that but not now""" @@ -319,19 +321,19 @@ class TestFileLibTiff(LibTiffTestCase): # file is a multipage tiff, 10x10 green, 10x10 red, 20x20 blue im.seek(0) - self.assertEqual(im.size, (10,10)) - self.assertEqual(im.convert('RGB').getpixel((0,0)), (0,128,0)) + self.assertEqual(im.size, (10, 10)) + self.assertEqual(im.convert('RGB').getpixel((0, 0)), (0, 128, 0)) self.assertTrue(im.tag.next) im.seek(1) - self.assertEqual(im.size, (10,10)) - self.assertEqual(im.convert('RGB').getpixel((0,0)), (255,0,0)) + self.assertEqual(im.size, (10, 10)) + self.assertEqual(im.convert('RGB').getpixel((0, 0)), (255, 0, 0)) self.assertTrue(im.tag.next) im.seek(2) self.assertFalse(im.tag.next) - self.assertEqual(im.size, (20,20)) - self.assertEqual(im.convert('RGB').getpixel((0,0)), (0,0,255)) + self.assertEqual(im.size, (20, 20)) + self.assertEqual(im.convert('RGB').getpixel((0, 0)), (0, 0, 255)) TiffImagePlugin.READ_LIBTIFF = False @@ -357,7 +359,32 @@ class TestFileLibTiff(LibTiffTestCase): self.assertEqual(im.mode, "L") self.assert_image_similar(im, original, 7.3) + def test_save_bytesio(self): + # PR 1011 + # Test TIFF saving to io.BytesIO() object. + TiffImagePlugin.WRITE_LIBTIFF = True + TiffImagePlugin.READ_LIBTIFF = True + + # Generate test image + pilim = hopper() + + def save_bytesio(compression=None): + + buffer_io = io.BytesIO() + pilim.save(buffer_io, format="tiff", compression=compression) + buffer_io.seek(0) + + pilim_load = Image.open(buffer_io) + self.assert_image_similar(pilim, pilim_load, 0) + + # save_bytesio() + save_bytesio('raw') + save_bytesio("packbits") + save_bytesio("tiff_lzw") + + TiffImagePlugin.WRITE_LIBTIFF = False + TiffImagePlugin.READ_LIBTIFF = False if __name__ == '__main__': diff --git a/Tests/test_file_tiff.py b/Tests/test_file_tiff.py index 3939ac074..fd8090196 100644 --- a/Tests/test_file_tiff.py +++ b/Tests/test_file_tiff.py @@ -326,7 +326,7 @@ class TestFileTiff(PillowTestCase): # Should not divide by zero im.save(outfile) - + if __name__ == '__main__': unittest.main() diff --git a/Tests/test_image_putdata.py b/Tests/test_image_putdata.py index 46a2ace71..3b7dc836a 100644 --- a/Tests/test_image_putdata.py +++ b/Tests/test_image_putdata.py @@ -1,4 +1,5 @@ from helper import unittest, PillowTestCase, hopper +from array import array import sys @@ -63,6 +64,25 @@ class TestImagePutData(PillowTestCase): target = [2.0 * float(elt) + 256.0 for elt in data] self.assertEqual(list(im.getdata()), target) + def test_array_B(self): + # shouldn't segfault + # see https://github.com/python-pillow/Pillow/issues/1008 + + arr = array('B', [0])*15000 + im = Image.new('L', (150, 100)) + im.putdata(arr) + + self.assertEqual(len(im.getdata()),len(arr)) + + def test_array_F(self): + # shouldn't segfault + # see https://github.com/python-pillow/Pillow/issues/1008 + + im = Image.new('F', (150, 100)) + arr = array('f', [0.0])*15000 + im.putdata(arr) + + self.assertEqual(len(im.getdata()),len(arr)) if __name__ == '__main__': unittest.main() diff --git a/Tests/test_numpy.py b/Tests/test_numpy.py index b00b8b36b..7b6f22b2b 100644 --- a/Tests/test_numpy.py +++ b/Tests/test_numpy.py @@ -121,6 +121,16 @@ class TestNumpy(PillowTestCase): im.point(lut) + def test_putdata(self): + # shouldn't segfault + # see https://github.com/python-pillow/Pillow/issues/1008 + + im = Image.new('F', (150, 100)) + arr = numpy.zeros((15000,), numpy.float32) + im.putdata(arr) + + self.assertEqual(len(im.getdata()),len(arr)) + if __name__ == '__main__': unittest.main() diff --git a/_imaging.c b/_imaging.c index 97349f187..a84350cf2 100644 --- a/_imaging.c +++ b/_imaging.c @@ -1269,7 +1269,7 @@ _putdata(ImagingObject* self, PyObject* args) if (scale == 1.0 && offset == 0.0) { /* Clipped data */ for (i = x = y = 0; i < n; i++) { - op = PySequence_Fast_GET_ITEM(data, i); + op = PySequence_Fast_GET_ITEM(seq, i); image->image8[y][x] = (UINT8) CLIP(PyInt_AsLong(op)); if (++x >= (int) image->xsize){ x = 0, y++; @@ -1279,7 +1279,7 @@ _putdata(ImagingObject* self, PyObject* args) } else { /* Scaled and clipped data */ for (i = x = y = 0; i < n; i++) { - PyObject *op = PySequence_Fast_GET_ITEM(data, i); + PyObject *op = PySequence_Fast_GET_ITEM(seq, i); image->image8[y][x] = CLIP( (int) (PyFloat_AsDouble(op) * scale + offset)); if (++x >= (int) image->xsize){ @@ -1299,7 +1299,7 @@ _putdata(ImagingObject* self, PyObject* args) switch (image->type) { case IMAGING_TYPE_INT32: for (i = x = y = 0; i < n; i++) { - op = PySequence_Fast_GET_ITEM(data, i); + op = PySequence_Fast_GET_ITEM(seq, i); IMAGING_PIXEL_INT32(image, x, y) = (INT32) (PyFloat_AsDouble(op) * scale + offset); if (++x >= (int) image->xsize){ @@ -1310,7 +1310,7 @@ _putdata(ImagingObject* self, PyObject* args) break; case IMAGING_TYPE_FLOAT32: for (i = x = y = 0; i < n; i++) { - op = PySequence_Fast_GET_ITEM(data, i); + op = PySequence_Fast_GET_ITEM(seq, i); IMAGING_PIXEL_FLOAT32(image, x, y) = (FLOAT32) (PyFloat_AsDouble(op) * scale + offset); if (++x >= (int) image->xsize){ @@ -1326,7 +1326,7 @@ _putdata(ImagingObject* self, PyObject* args) INT32 inkint; } u; - op = PySequence_Fast_GET_ITEM(data, i); + op = PySequence_Fast_GET_ITEM(seq, i); if (!op || !getink(op, image, u.ink)) { return NULL; } diff --git a/docs/handbook/image-file-formats.rst b/docs/handbook/image-file-formats.rst index 50eecd9da..a1961fa7c 100644 --- a/docs/handbook/image-file-formats.rst +++ b/docs/handbook/image-file-formats.rst @@ -589,6 +589,14 @@ ICO ICO is used to store icons on Windows. The largest available icon is read. +The :py:meth:`~PIL.Image.Image.save` method supports the following options: + +**sizes** + A list of sizes including in this ico file; these are a 2-tuple, + ``(width, height)``; Default to ``[(16, 16), (24, 24), (32, 32), (48, 48), + (64, 64), (128, 128), (255, 255)]``. Any size is bigger then the original + size or 255 will be ignored. + ICNS ^^^^ diff --git a/docs/installation.rst b/docs/installation.rst index ac02a645c..c48b30265 100644 --- a/docs/installation.rst +++ b/docs/installation.rst @@ -129,7 +129,7 @@ Prerequisites are installed on **Ubuntu 12.04 LTS** or **Raspian Wheezy Prerequisites are installed on **Ubuntu 14.04 LTS** with:: - $ sudo apt-get install libtiff4-dev libjpeg8-dev zlib1g-dev \ + $ sudo apt-get install libtiff5-dev libjpeg8-dev zlib1g-dev \ libfreetype6-dev liblcms2-dev libwebp-dev tcl8.6-dev tk8.6-dev python-tk Prerequisites are installed on **Fedora 20** with:: diff --git a/docs/reference/Image.rst b/docs/reference/Image.rst index 249543004..08af684d5 100644 --- a/docs/reference/Image.rst +++ b/docs/reference/Image.rst @@ -116,6 +116,7 @@ ITU-R 709, using the D65 luminant) to the CIE XYZ color space: .. automethod:: PIL.Image.Image.getcolors .. automethod:: PIL.Image.Image.getdata .. automethod:: PIL.Image.Image.getextrema +.. automethod:: PIL.Image.Image.getpalette .. automethod:: PIL.Image.Image.getpixel .. automethod:: PIL.Image.Image.histogram .. automethod:: PIL.Image.Image.offset diff --git a/libImaging/Antialias.c b/libImaging/Antialias.c index a5f0fde83..d6b10c431 100644 --- a/libImaging/Antialias.c +++ b/libImaging/Antialias.c @@ -93,7 +93,7 @@ static inline UINT8 clip8(float in) /* This is work around bug in GCC prior 4.9 in 64 bit mode. GCC generates code with partial dependency which 3 times slower. See: http://stackoverflow.com/a/26588074/253146 */ -#if defined(__x86_64__) && defined(__SSE__) && \ +#if defined(__x86_64__) && defined(__SSE__) && ! defined(__NO_INLINE__) && \ ! defined(__clang__) && defined(GCC_VERSION) && (GCC_VERSION < 40900) static float __attribute__((always_inline)) i2f(int v) { float x;