From 86a64dfd60939a4c549de3c8e43cfc4adc50e341 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Aur=C3=A9lien=20Ballier?= Date: Mon, 4 Nov 2013 12:44:41 +0100 Subject: [PATCH 01/24] Add Qt5 support. --- PIL/ImageQt5.py | 85 +++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 85 insertions(+) create mode 100644 PIL/ImageQt5.py diff --git a/PIL/ImageQt5.py b/PIL/ImageQt5.py new file mode 100644 index 000000000..69efd18f9 --- /dev/null +++ b/PIL/ImageQt5.py @@ -0,0 +1,85 @@ +# +# The Python Imaging Library. +# $Id$ +# +# a simple Qt image interface. +# +# history: +# 2006-06-03 fl: created +# 2006-06-04 fl: inherit from QImage instead of wrapping it +# 2006-06-05 fl: removed toimage helper; move string support to ImageQt +# +# Copyright (c) 2006 by Secret Labs AB +# Copyright (c) 2006 by Fredrik Lundh +# +# See the README file for information on usage and redistribution. +# + +from PIL import Image +from PIL._util import isPath + +from PyQt5.QtGui import QImage, qRgb + +## +# (Internal) Turns an RGB color into a Qt compatible color integer. + +def rgb(r, g, b): + # use qRgb to pack the colors, and then turn the resulting long + # into a negative integer with the same bitpattern. + return (qRgb(r, g, b) & 0xffffff) - 0x1000000 + +## +# An PIL image wrapper for Qt. This is a subclass of PyQt5's QImage +# class. +# +# @param im A PIL Image object, or a file name (given either as Python +# string or a PyQt string object). + +class ImageQt(QImage): + + def __init__(self, im): + + data = None + colortable = None + + # handle filename, if given instead of image name + if hasattr(im, "toUtf8"): + # FIXME - is this really the best way to do this? + im = unicode(im.toUtf8(), "utf-8") + if isPath(im): + im = Image.open(im) + + if im.mode == "1": + format = QImage.Format_Mono + elif im.mode == "L": + format = QImage.Format_Indexed8 + colortable = [] + for i in range(256): + colortable.append(rgb(i, i, i)) + elif im.mode == "P": + format = QImage.Format_Indexed8 + colortable = [] + palette = im.getpalette() + for i in range(0, len(palette), 3): + colortable.append(rgb(*palette[i:i+3])) + elif im.mode == "RGB": + data = im.tobytes("raw", "BGRX") + format = QImage.Format_RGB32 + elif im.mode == "RGBA": + try: + data = im.tobytes("raw", "BGRA") + except SystemError: + # workaround for earlier versions + r, g, b, a = im.split() + im = Image.merge("RGBA", (b, g, r, a)) + format = QImage.Format_ARGB32 + else: + raise ValueError("unsupported image mode %r" % im.mode) + + # must keep a reference, or Qt will crash! + self.__data = data or im.tobytes() + + QImage.__init__(self, self.__data, im.size[0], im.size[1], format) + + if colortable: + self.setColorTable(colortable) From 6121dd5aa5cd93d1c324067e58b9f985c0ebdef8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Aur=C3=A9lien=20Ballier?= Date: Tue, 5 Nov 2013 19:14:15 +0100 Subject: [PATCH 02/24] Add Qt5 support. --- PIL/ImageQt5.py | 1 + 1 file changed, 1 insertion(+) diff --git a/PIL/ImageQt5.py b/PIL/ImageQt5.py index 69efd18f9..936e3698f 100644 --- a/PIL/ImageQt5.py +++ b/PIL/ImageQt5.py @@ -8,6 +8,7 @@ # 2006-06-03 fl: created # 2006-06-04 fl: inherit from QImage instead of wrapping it # 2006-06-05 fl: removed toimage helper; move string support to ImageQt +# 2013-11-05 fl: add support for Qt5 (aurelien.ballier@cyclonit.com) # # Copyright (c) 2006 by Secret Labs AB # Copyright (c) 2006 by Fredrik Lundh From 2a3422335b2495082797cf5d099e053011020934 Mon Sep 17 00:00:00 2001 From: Aurelien Ballier Date: Wed, 13 Nov 2013 12:34:09 +0100 Subject: [PATCH 03/24] Fix PyQt4&5 support. --- PIL/ImageQt.py | 6 +++- PIL/ImageQt5.py | 86 ------------------------------------------------- 2 files changed, 5 insertions(+), 87 deletions(-) delete mode 100644 PIL/ImageQt5.py diff --git a/PIL/ImageQt.py b/PIL/ImageQt.py index d8b838d25..b0c477d03 100644 --- a/PIL/ImageQt.py +++ b/PIL/ImageQt.py @@ -8,6 +8,7 @@ # 2006-06-03 fl: created # 2006-06-04 fl: inherit from QImage instead of wrapping it # 2006-06-05 fl: removed toimage helper; move string support to ImageQt +# 2013-11-13 fl: add support for Qt5 (aurelien.ballier@cyclonit.com) # # Copyright (c) 2006 by Secret Labs AB # Copyright (c) 2006 by Fredrik Lundh @@ -18,7 +19,10 @@ from PIL import Image from PIL._util import isPath -from PyQt4.QtGui import QImage, qRgb +try: + from PyQt5.QtGui import QImage, qRgb +except: + from PyQt4.QtGui import QImage, qRgb ## # (Internal) Turns an RGB color into a Qt compatible color integer. diff --git a/PIL/ImageQt5.py b/PIL/ImageQt5.py deleted file mode 100644 index 936e3698f..000000000 --- a/PIL/ImageQt5.py +++ /dev/null @@ -1,86 +0,0 @@ -# -# The Python Imaging Library. -# $Id$ -# -# a simple Qt image interface. -# -# history: -# 2006-06-03 fl: created -# 2006-06-04 fl: inherit from QImage instead of wrapping it -# 2006-06-05 fl: removed toimage helper; move string support to ImageQt -# 2013-11-05 fl: add support for Qt5 (aurelien.ballier@cyclonit.com) -# -# Copyright (c) 2006 by Secret Labs AB -# Copyright (c) 2006 by Fredrik Lundh -# -# See the README file for information on usage and redistribution. -# - -from PIL import Image -from PIL._util import isPath - -from PyQt5.QtGui import QImage, qRgb - -## -# (Internal) Turns an RGB color into a Qt compatible color integer. - -def rgb(r, g, b): - # use qRgb to pack the colors, and then turn the resulting long - # into a negative integer with the same bitpattern. - return (qRgb(r, g, b) & 0xffffff) - 0x1000000 - -## -# An PIL image wrapper for Qt. This is a subclass of PyQt5's QImage -# class. -# -# @param im A PIL Image object, or a file name (given either as Python -# string or a PyQt string object). - -class ImageQt(QImage): - - def __init__(self, im): - - data = None - colortable = None - - # handle filename, if given instead of image name - if hasattr(im, "toUtf8"): - # FIXME - is this really the best way to do this? - im = unicode(im.toUtf8(), "utf-8") - if isPath(im): - im = Image.open(im) - - if im.mode == "1": - format = QImage.Format_Mono - elif im.mode == "L": - format = QImage.Format_Indexed8 - colortable = [] - for i in range(256): - colortable.append(rgb(i, i, i)) - elif im.mode == "P": - format = QImage.Format_Indexed8 - colortable = [] - palette = im.getpalette() - for i in range(0, len(palette), 3): - colortable.append(rgb(*palette[i:i+3])) - elif im.mode == "RGB": - data = im.tobytes("raw", "BGRX") - format = QImage.Format_RGB32 - elif im.mode == "RGBA": - try: - data = im.tobytes("raw", "BGRA") - except SystemError: - # workaround for earlier versions - r, g, b, a = im.split() - im = Image.merge("RGBA", (b, g, r, a)) - format = QImage.Format_ARGB32 - else: - raise ValueError("unsupported image mode %r" % im.mode) - - # must keep a reference, or Qt will crash! - self.__data = data or im.tobytes() - - QImage.__init__(self, self.__data, im.size[0], im.size[1], format) - - if colortable: - self.setColorTable(colortable) From f3af439a401186f771280569bf934e749736baa0 Mon Sep 17 00:00:00 2001 From: George Thomas Date: Wed, 5 Nov 2014 15:09:54 +0530 Subject: [PATCH 04/24] Update installation.rst libtiff4-dev is obsolete --- docs/installation.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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:: From b38c8e0df21a0c7c616e34f124a44b312e9db56a Mon Sep 17 00:00:00 2001 From: Herb Date: Sat, 8 Nov 2014 03:01:46 +0800 Subject: [PATCH 05/24] add ico save support --- PIL/IcoImagePlugin.py | 42 ++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 42 insertions(+) diff --git a/PIL/IcoImagePlugin.py b/PIL/IcoImagePlugin.py index 2dc46ea18..d18b37cfc 100644 --- a/PIL/IcoImagePlugin.py +++ b/PIL/IcoImagePlugin.py @@ -24,6 +24,12 @@ __version__ = "0.1" +import struct +try: + from io import BytesIO +except ImportError: + from cStringIO import StringIO as BytesIO + from PIL import Image, ImageFile, BmpImagePlugin, PngImagePlugin, _binary from math import log, ceil @@ -37,6 +43,41 @@ 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], reverse=True) + 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() + im.thumbnail(size, Image.ANTIALIAS) + im.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 +282,5 @@ class IcoImageFile(ImageFile.ImageFile): # -------------------------------------------------------------------- Image.register_open("ICO", IcoImageFile, _accept) +Image.register_save("ICO", _save) Image.register_extension("ICO", ".ico") From 79c7c7a01a0fc89e8b1b581bc9dbd6e326062881 Mon Sep 17 00:00:00 2001 From: Herb Date: Sat, 8 Nov 2014 14:49:50 +0800 Subject: [PATCH 06/24] add tests and docs --- PIL/IcoImagePlugin.py | 12 +++++------- Tests/test_file_ico.py | 15 ++++++++++++++- docs/handbook/image-file-formats.rst | 8 ++++++++ 3 files changed, 27 insertions(+), 8 deletions(-) diff --git a/PIL/IcoImagePlugin.py b/PIL/IcoImagePlugin.py index d18b37cfc..145816094 100644 --- a/PIL/IcoImagePlugin.py +++ b/PIL/IcoImagePlugin.py @@ -25,10 +25,7 @@ __version__ = "0.1" import struct -try: - from io import BytesIO -except ImportError: - from cStringIO import StringIO as BytesIO +from io import BytesIO from PIL import Image, ImageFile, BmpImagePlugin, PngImagePlugin, _binary from math import log, ceil @@ -51,7 +48,7 @@ def _save(im, fp, filename): 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], reverse=True) + 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: @@ -64,8 +61,9 @@ def _save(im, fp, filename): fp.write(struct.pack("H", 32)) # wBitCount(2) image_io = BytesIO() - im.thumbnail(size, Image.ANTIALIAS) - im.save(image_io, "png") + 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) diff --git a/Tests/test_file_ico.py b/Tests/test_file_ico.py index 12f4ed3f3..200b6ba56 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,18 @@ 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)]) + + output.seek(0) + reloaded = Image.open(output) + + self.assertEqual(im.mode, reloaded.mode) + self.assertEqual((64, 64), reloaded.size) + self.assertEqual(reloaded.format, "ICO") + if __name__ == '__main__': unittest.main() 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 ^^^^ From 482f65870442ae42bb833bc9b9b4df949e7ecc04 Mon Sep 17 00:00:00 2001 From: wiredfool Date: Sun, 9 Nov 2014 10:50:38 -0800 Subject: [PATCH 07/24] Update CHANGES.rst [ci skip] --- CHANGES.rst | 3 +++ 1 file changed, 3 insertions(+) diff --git a/CHANGES.rst b/CHANGES.rst index e5fc39bfd..f9434c877 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -3,6 +3,9 @@ Changelog (Pillow) 2.7.0 (unreleased) ------------------ +- Optimize stretch implementation. #977 + [homm] + - Speed up rotation by using cache aware loops, added transpose to rotations. #994 [homm] From aff274011b666dab6bc3b540fc5e7532b32f9ca6 Mon Sep 17 00:00:00 2001 From: wiredfool Date: Sun, 9 Nov 2014 11:42:39 -0800 Subject: [PATCH 08/24] Update CHANGES.rst [ci skip] --- CHANGES.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGES.rst b/CHANGES.rst index f9434c877..8c0ac2bf1 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -3,7 +3,7 @@ Changelog (Pillow) 2.7.0 (unreleased) ------------------ -- Optimize stretch implementation. #977 +- Speedup stretch implementation up to 2.5 times. #977 [homm] - Speed up rotation by using cache aware loops, added transpose to rotations. #994 From ba178bef85de878fd0b9391da32220e9d6672df8 Mon Sep 17 00:00:00 2001 From: wiredfool Date: Sun, 9 Nov 2014 20:51:20 -0800 Subject: [PATCH 09/24] Added ref to getpalette --- docs/reference/Image.rst | 1 + 1 file changed, 1 insertion(+) 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 From 37252a67cc251fd865384fe0fb9db7a5ebd443a0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Aur=C3=A9lien=20Ballier?= Date: Tue, 11 Nov 2014 19:39:05 +0100 Subject: [PATCH 10/24] =?UTF-8?q?Fix=20bad=20import=C3=83?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- PIL/ImageQt.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/PIL/ImageQt.py b/PIL/ImageQt.py index d7c91df13..fd662c894 100644 --- a/PIL/ImageQt.py +++ b/PIL/ImageQt.py @@ -22,11 +22,11 @@ import sys if 'PyQt4.QtGui' not in sys.modules: try: - from PyQt5.QtGui import QImage, qRgb + from PyQt5.QtGui import QImage, qRgba except: - from PyQt4.QtGui import QImage, qRgb + from PyQt4.QtGui import QImage, qRgba else: #PyQt4 is used - from PyQt4.QtGui import QImage, qRgb + from PyQt4.QtGui import QImage, qRgba ## # (Internal) Turns an RGB color into a Qt compatible color integer. From ad1b9193423a669f92352b37dac9f4b31c3586a7 Mon Sep 17 00:00:00 2001 From: wiredfool Date: Wed, 12 Nov 2014 22:15:26 -0800 Subject: [PATCH 11/24] Update CHANGES.rst [ci skip] --- CHANGES.rst | 3 +++ 1 file changed, 3 insertions(+) diff --git a/CHANGES.rst b/CHANGES.rst index 8c0ac2bf1..aac45dfcf 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -3,6 +3,9 @@ Changelog (Pillow) 2.7.0 (unreleased) ------------------ +- Use PyQt4 if it has already been imported, otherwise prefer PyQt5. #1003 + [AurelienBallier] + - Speedup stretch implementation up to 2.5 times. #977 [homm] From 03d20d3b6a2894a9053512b7b2d2c382bffd9a59 Mon Sep 17 00:00:00 2001 From: wiredfool Date: Wed, 12 Nov 2014 22:45:35 -0800 Subject: [PATCH 12/24] More tests for ico save --- Tests/test_file_ico.py | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/Tests/test_file_ico.py b/Tests/test_file_ico.py index 200b6ba56..01d3f5904 100644 --- a/Tests/test_file_ico.py +++ b/Tests/test_file_ico.py @@ -22,13 +22,27 @@ class TestFileIco(PillowTestCase): 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() From ccba6af498708a22520402efd647690ad1ce50fa Mon Sep 17 00:00:00 2001 From: Hugo Date: Thu, 13 Nov 2014 10:10:42 +0200 Subject: [PATCH 13/24] Update CHANGES.rst [CI skip] --- CHANGES.rst | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/CHANGES.rst b/CHANGES.rst index aac45dfcf..ed81f995e 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -3,6 +3,10 @@ Changelog (Pillow) 2.7.0 (unreleased) ------------------ + +- Ico save, additional tests #1007 + [exherb] + - Use PyQt4 if it has already been imported, otherwise prefer PyQt5. #1003 [AurelienBallier] From a105cd7edb26c06a22c9cbadc26ae0eaa8cf19c4 Mon Sep 17 00:00:00 2001 From: wiredfool Date: Thu, 13 Nov 2014 14:56:31 -0800 Subject: [PATCH 14/24] Test for issue #1008 --- Tests/test_image_putdata.py | 20 ++++++++++++++++++++ Tests/test_numpy.py | 10 ++++++++++ 2 files changed, 30 insertions(+) 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() From f75c5562c7a6a887bdcb8c7f93022fa1fdfcee09 Mon Sep 17 00:00:00 2001 From: wiredfool Date: Thu, 13 Nov 2014 14:56:59 -0800 Subject: [PATCH 15/24] Fix #1008, Correct argument to PySequence_Fast_GET_ITEM --- _imaging.c | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/_imaging.c b/_imaging.c index f7ea510bc..28ac4600e 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; } From 112a4815f010dbc36df3907b2ff0bedfc925fc5a Mon Sep 17 00:00:00 2001 From: wiredfool Date: Thu, 13 Nov 2014 15:53:51 -0800 Subject: [PATCH 16/24] Fix compilation on debug versions of python --- libImaging/Antialias.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/libImaging/Antialias.c b/libImaging/Antialias.c index 5c9514103..a97141013 100644 --- a/libImaging/Antialias.c +++ b/libImaging/Antialias.c @@ -94,7 +94,8 @@ static inline UINT8 clip8(float in) GCC generates code with partial dependency which 3 times slower. See: http://stackoverflow.com/a/26588074/253146 */ #if defined(__x86_64__) && defined(__SSE__) && \ - ! defined(__clang__) && defined(GCC_VERSION) && (GCC_VERSION < 40900) + ! defined(__clang__) && defined(GCC_VERSION) && (GCC_VERSION < 40900) && \ + ! defined(Py_DEBUG) static float __attribute__((always_inline)) i2f(int v) { float x; __asm__("xorps %0, %0; cvtsi2ss %1, %0" : "=X"(x) : "r"(v) ); From 9d1fde0fff84e520fa7ff87ce8e0dac5537bed0b Mon Sep 17 00:00:00 2001 From: wiredfool Date: Thu, 13 Nov 2014 21:35:06 -0800 Subject: [PATCH 17/24] Use __NO_INLINE__ instead of Py_DEBUG --- libImaging/Antialias.c | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/libImaging/Antialias.c b/libImaging/Antialias.c index a97141013..1799e7fdc 100644 --- a/libImaging/Antialias.c +++ b/libImaging/Antialias.c @@ -93,9 +93,8 @@ 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__) && \ - ! defined(__clang__) && defined(GCC_VERSION) && (GCC_VERSION < 40900) && \ - ! defined(Py_DEBUG) +#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; __asm__("xorps %0, %0; cvtsi2ss %1, %0" : "=X"(x) : "r"(v) ); From ebbee834c9cd568d4ed95e32176baea1270bbb02 Mon Sep 17 00:00:00 2001 From: Hugo Date: Fri, 14 Nov 2014 08:40:06 +0200 Subject: [PATCH 18/24] Update CHANGES.rst [CI skip] --- CHANGES.rst | 3 +++ 1 file changed, 3 insertions(+) diff --git a/CHANGES.rst b/CHANGES.rst index ed81f995e..e61c3d745 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -4,6 +4,9 @@ Changelog (Pillow) 2.7.0 (unreleased) ------------------ +- Fix for Image.putdata segfault #1009 + [wiredfool] + - Ico save, additional tests #1007 [exherb] From 9da99dee8d61f100437c6a041d003e6bdae8f844 Mon Sep 17 00:00:00 2001 From: Hugo Date: Fri, 14 Nov 2014 08:42:41 +0200 Subject: [PATCH 19/24] Update CHANGES.rst [CI skip] --- CHANGES.rst | 3 +++ 1 file changed, 3 insertions(+) diff --git a/CHANGES.rst b/CHANGES.rst index e61c3d745..3edc1b577 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -4,6 +4,9 @@ Changelog (Pillow) 2.7.0 (unreleased) ------------------ +- Fix antialias compilation on debug versions of Python #1010 + [wiredfool] + - Fix for Image.putdata segfault #1009 [wiredfool] From dd3a46520c6a5c5e43de7001c369f0a1803e8e57 Mon Sep 17 00:00:00 2001 From: Martin Fergie Date: Fri, 14 Nov 2014 12:29:10 +0000 Subject: [PATCH 20/24] Fix for saving to io.BytesIO() --- PIL/TiffImagePlugin.py | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/PIL/TiffImagePlugin.py b/PIL/TiffImagePlugin.py index 6a52e5e5c..21ff13ec6 100644 --- a/PIL/TiffImagePlugin.py +++ b/PIL/TiffImagePlugin.py @@ -1128,8 +1128,12 @@ def _save(im, fp, filename): print (ifd.items()) _fp = 0 if hasattr(fp, "fileno"): - fp.seek(0) - _fp = os.dup(fp.fileno()) + import io + try: + fp.seek(0) + _fp = os.dup(fp.fileno()) + except io.UnsupportedOperation: + pass # ICC Profile crashes. blocklist = [STRIPOFFSETS, STRIPBYTECOUNTS, ROWSPERSTRIP, ICCPROFILE] From 8b0ecb6192b13066f288cc51ed3c453b70bdef50 Mon Sep 17 00:00:00 2001 From: Martin Fergie Date: Fri, 14 Nov 2014 14:37:03 +0000 Subject: [PATCH 21/24] Added test for different compression types. --- Tests/test_file_tiff.py | 27 +++++++++++++++++++++++++++ 1 file changed, 27 insertions(+) diff --git a/Tests/test_file_tiff.py b/Tests/test_file_tiff.py index 3939ac074..f3958f548 100644 --- a/Tests/test_file_tiff.py +++ b/Tests/test_file_tiff.py @@ -326,7 +326,34 @@ class TestFileTiff(PillowTestCase): # Should not divide by zero im.save(outfile) + def test_save_bytesio(self): + # PR 1011 + # Test TIFF saving to io.BytesIO() object. + + # Generate test image + pilim = Image.new('F', (100, 100), 0) + def save_bytesio(compression=None): + import io + + testfile = self.tempfile("temp_.tiff".format(compression)) + + buffer_io = io.BytesIO() + pilim.save(buffer_io, format="tiff", compression=compression) + buffer_io.seek(0) + data = buffer_io.read() + buffer_io.close() + with open(testfile, "wb") as fd: + fd.write(data) + + pilim_load = Image.open(testfile) + self.assert_image_similar(pilim, pilim_load, 0) + + # save_bytesio() + save_bytesio("packbits") + save_bytesio("tiff_lzw") + + if __name__ == '__main__': unittest.main() From 9a2cc7df83167f56deb5a32e02c1c487ea152260 Mon Sep 17 00:00:00 2001 From: wiredfool Date: Sat, 15 Nov 2014 20:38:52 -0800 Subject: [PATCH 22/24] hoist import, move test to libtiff --- PIL/TiffImagePlugin.py | 2 +- Tests/test_file_libtiff.py | 32 ++++++++++++++++++++++++++++++-- Tests/test_file_tiff.py | 27 --------------------------- 3 files changed, 31 insertions(+), 30 deletions(-) diff --git a/PIL/TiffImagePlugin.py b/PIL/TiffImagePlugin.py index 21ff13ec6..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,7 +1129,6 @@ def _save(im, fp, filename): print (ifd.items()) _fp = 0 if hasattr(fp, "fileno"): - import io try: fp.seek(0) _fp = os.dup(fp.fileno()) diff --git a/Tests/test_file_libtiff.py b/Tests/test_file_libtiff.py index efd2d5817..12f25165f 100644 --- a/Tests/test_file_libtiff.py +++ b/Tests/test_file_libtiff.py @@ -1,6 +1,7 @@ from helper import unittest, PillowTestCase, hopper, py3 import os +import io from PIL import Image, TiffImagePlugin @@ -59,9 +60,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) @@ -357,6 +357,34 @@ 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): + + testfile = self.tempfile("temp_.tiff".format(compression)) + + 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 diff --git a/Tests/test_file_tiff.py b/Tests/test_file_tiff.py index f3958f548..fd8090196 100644 --- a/Tests/test_file_tiff.py +++ b/Tests/test_file_tiff.py @@ -326,33 +326,6 @@ class TestFileTiff(PillowTestCase): # Should not divide by zero im.save(outfile) - def test_save_bytesio(self): - # PR 1011 - # Test TIFF saving to io.BytesIO() object. - - # Generate test image - pilim = Image.new('F', (100, 100), 0) - def save_bytesio(compression=None): - import io - - testfile = self.tempfile("temp_.tiff".format(compression)) - - buffer_io = io.BytesIO() - pilim.save(buffer_io, format="tiff", compression=compression) - buffer_io.seek(0) - data = buffer_io.read() - buffer_io.close() - - with open(testfile, "wb") as fd: - fd.write(data) - - pilim_load = Image.open(testfile) - self.assert_image_similar(pilim, pilim_load, 0) - - # save_bytesio() - save_bytesio("packbits") - save_bytesio("tiff_lzw") - if __name__ == '__main__': unittest.main() From a40854499fc76095eee4cc1bda5b11ba639ae1e9 Mon Sep 17 00:00:00 2001 From: wiredfool Date: Sat, 15 Nov 2014 21:33:21 -0800 Subject: [PATCH 23/24] Update CHANGES.rst [ci skip] --- CHANGES.rst | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/CHANGES.rst b/CHANGES.rst index 3edc1b577..1b4826451 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -3,7 +3,9 @@ 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] From 02476b7689085d91a1edc30e141d74e4ac41d976 Mon Sep 17 00:00:00 2001 From: Hugo Date: Mon, 17 Nov 2014 10:33:31 +0200 Subject: [PATCH 24/24] Remove unused variable, and other minor flake8 warnings --- Tests/test_file_libtiff.py | 27 +++++++++++++-------------- 1 file changed, 13 insertions(+), 14 deletions(-) diff --git a/Tests/test_file_libtiff.py b/Tests/test_file_libtiff.py index 12f25165f..a9377bf63 100644 --- a/Tests/test_file_libtiff.py +++ b/Tests/test_file_libtiff.py @@ -5,6 +5,7 @@ import io from PIL import Image, TiffImagePlugin + class LibTiffTestCase(PillowTestCase): def setUp(self): @@ -32,6 +33,7 @@ class LibTiffTestCase(PillowTestCase): out = self.tempfile("temp.png") im.save(out) + class TestFileLibTiff(LibTiffTestCase): def test_g4_tiff(self): @@ -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 @@ -360,7 +362,7 @@ class TestFileLibTiff(LibTiffTestCase): def test_save_bytesio(self): # PR 1011 # Test TIFF saving to io.BytesIO() object. - + TiffImagePlugin.WRITE_LIBTIFF = True TiffImagePlugin.READ_LIBTIFF = True @@ -368,26 +370,23 @@ class TestFileLibTiff(LibTiffTestCase): pilim = hopper() def save_bytesio(compression=None): - - testfile = self.tempfile("temp_.tiff".format(compression)) - + 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__': unittest.main()