From 1111e9fb35d42a778dddcd4e473a6b61d4032519 Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Fri, 19 Jun 2015 00:49:18 +1000 Subject: [PATCH 01/56] Fixed frame position when seeking past the end of file --- PIL/FliImagePlugin.py | 8 +++++++- PIL/GifImagePlugin.py | 10 ++++++++-- Tests/test_file_dcx.py | 12 ++++++++++++ Tests/test_file_fli.py | 14 +++++++++++++- Tests/test_file_gif.py | 17 ++++++++++++++++- Tests/test_file_im.py | 12 ++++++++++++ Tests/test_file_mpo.py | 12 ++++++++++++ Tests/test_file_psd.py | 13 +++++++++++++ Tests/test_file_tiff.py | 12 ++++++++++++ 9 files changed, 105 insertions(+), 5 deletions(-) diff --git a/PIL/FliImagePlugin.py b/PIL/FliImagePlugin.py index df6a4eb8e..772140076 100644 --- a/PIL/FliImagePlugin.py +++ b/PIL/FliImagePlugin.py @@ -127,8 +127,14 @@ class FliImageFile(ImageFile.ImageFile): return if frame < self.__frame: self._seek(0) + + last_frame = self.__frame for f in range(self.__frame + 1, frame + 1): - self._seek(f) + try: + self._seek(f) + except EOFError: + self.seek(last_frame) + raise EOFError("no more images in FLI file") def _seek(self, frame): if frame == 0: diff --git a/PIL/GifImagePlugin.py b/PIL/GifImagePlugin.py index bd974b651..35fb95164 100644 --- a/PIL/GifImagePlugin.py +++ b/PIL/GifImagePlugin.py @@ -107,8 +107,14 @@ class GifImageFile(ImageFile.ImageFile): return if frame < self.__frame: self._seek(0) + + last_frame = self.__frame for f in range(self.__frame + 1, frame + 1): - self._seek(f) + try: + self._seek(f) + except EOFError: + self.seek(last_frame) + raise EOFError("no more images in GIF file") def _seek(self, frame): @@ -241,7 +247,7 @@ class GifImageFile(ImageFile.ImageFile): if not self.tile: # self.__fp = None - raise EOFError("no more images in GIF file") + raise EOFError self.mode = "L" if self.palette: diff --git a/Tests/test_file_dcx.py b/Tests/test_file_dcx.py index 7d2ae32d8..d59a5a785 100644 --- a/Tests/test_file_dcx.py +++ b/Tests/test_file_dcx.py @@ -34,6 +34,18 @@ class TestFileDcx(PillowTestCase): im = Image.open(TEST_FILE) self.assertEqual(im.n_frames, 1) + def test_eoferror(self): + im = Image.open(TEST_FILE) + + n_frames = im.n_frames + while True: + n_frames -= 1 + try: + im.seek(n_frames) + break + except EOFError: + self.assertTrue(im.tell() < n_frames) + def test_seek_too_far(self): # Arrange im = Image.open(TEST_FILE) diff --git a/Tests/test_file_fli.py b/Tests/test_file_fli.py index 04c2006c9..1b95f793f 100644 --- a/Tests/test_file_fli.py +++ b/Tests/test_file_fli.py @@ -20,7 +20,19 @@ class TestFileFli(PillowTestCase): def test_n_frames(self): im = Image.open(test_file) - self.assertEqual(im.n_frames, 2) + self.assertEqual(im.n_frames, 1) + + def test_eoferror(self): + im = Image.open(test_file) + + n_frames = im.n_frames + while True: + n_frames -= 1 + try: + im.seek(n_frames) + break + except EOFError: + self.assertTrue(im.tell() < n_frames) if __name__ == '__main__': diff --git a/Tests/test_file_gif.py b/Tests/test_file_gif.py index b15d32097..a38c360c9 100644 --- a/Tests/test_file_gif.py +++ b/Tests/test_file_gif.py @@ -135,8 +135,23 @@ class TestFileGif(PillowTestCase): self.assertEqual(framecount, 5) def test_n_frames(self): + im = Image.open(TEST_GIF) + self.assertEqual(im.n_frames, 1) + im = Image.open("Tests/images/iss634.gif") - self.assertEqual(im.n_frames, 43) + self.assertEqual(im.n_frames, 42) + + def test_eoferror(self): + im = Image.open(TEST_GIF) + + n_frames = im.n_frames + while True: + n_frames -= 1 + try: + im.seek(n_frames) + break + except EOFError: + self.assertTrue(im.tell() < n_frames) def test_dispose_none(self): img = Image.open("Tests/images/dispose_none.gif") diff --git a/Tests/test_file_im.py b/Tests/test_file_im.py index 24e00b2f0..76bf250a0 100644 --- a/Tests/test_file_im.py +++ b/Tests/test_file_im.py @@ -19,6 +19,18 @@ class TestFileIm(PillowTestCase): im = Image.open(TEST_IM) self.assertEqual(im.n_frames, 1) + def test_eoferror(self): + im = Image.open(TEST_IM) + + n_frames = im.n_frames + while True: + n_frames -= 1 + try: + im.seek(n_frames) + break + except EOFError: + self.assertTrue(im.tell() < n_frames) + def test_roundtrip(self): out = self.tempfile('temp.im') im = hopper() diff --git a/Tests/test_file_mpo.py b/Tests/test_file_mpo.py index 1a0ebc453..c5562097d 100644 --- a/Tests/test_file_mpo.py +++ b/Tests/test_file_mpo.py @@ -99,6 +99,18 @@ class TestFileMpo(PillowTestCase): im = Image.open("Tests/images/sugarshack.mpo") self.assertEqual(im.n_frames, 2) + def test_eoferror(self): + im = Image.open("Tests/images/sugarshack.mpo") + + n_frames = im.n_frames + while True: + n_frames -= 1 + try: + im.seek(n_frames) + break + except EOFError: + self.assertTrue(im.tell() < n_frames) + def test_image_grab(self): for test_file in test_files: im = Image.open(test_file) diff --git a/Tests/test_file_psd.py b/Tests/test_file_psd.py index dca3601b2..ea3856fce 100644 --- a/Tests/test_file_psd.py +++ b/Tests/test_file_psd.py @@ -23,6 +23,19 @@ class TestImagePsd(PillowTestCase): im = Image.open(test_file) self.assertEqual(im.n_frames, 2) + def test_eoferror(self): + im = Image.open(test_file) + + n_frames = im.n_frames + while True: + n_frames -= 1 + try: + # PSD seek index starts at 1 rather than 0 + im.seek(n_frames+1) + break + except EOFError: + self.assertTrue(im.tell() < n_frames) + if __name__ == '__main__': unittest.main() diff --git a/Tests/test_file_tiff.py b/Tests/test_file_tiff.py index 02a63586c..a88b49d7f 100644 --- a/Tests/test_file_tiff.py +++ b/Tests/test_file_tiff.py @@ -157,6 +157,18 @@ class TestFileTiff(PillowTestCase): im = Image.open('Tests/images/multipage.tiff') self.assertEqual(im.n_frames, 3) + def test_eoferror(self): + im = Image.open('Tests/images/multipage-lastframe.tif') + + n_frames = im.n_frames + while True: + n_frames -= 1 + try: + im.seek(n_frames) + break + except EOFError: + self.assertTrue(im.tell() < n_frames) + def test_multipage(self): # issue #862 im = Image.open('Tests/images/multipage.tiff') From 3e6e50476b50ac8525aed58f47dcebd96774b479 Mon Sep 17 00:00:00 2001 From: hugovk Date: Thu, 25 Jun 2015 23:17:21 +0300 Subject: [PATCH 02/56] DPI should be tuple of ints, not floats --- PIL/BmpImagePlugin.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/PIL/BmpImagePlugin.py b/PIL/BmpImagePlugin.py index 793bd8edd..9a297b3b8 100644 --- a/PIL/BmpImagePlugin.py +++ b/PIL/BmpImagePlugin.py @@ -103,7 +103,7 @@ class BmpImageFile(ImageFile.ImageFile): file_info['pixels_per_meter'] = (i32(header_data[20:24]), i32(header_data[24:28])) file_info['colors'] = i32(header_data[28:32]) file_info['palette_padding'] = 4 - self.info["dpi"] = tuple(map(lambda x: math.ceil(x / 39.3701), file_info['pixels_per_meter'])) + self.info["dpi"] = tuple(map(lambda x: int(math.ceil(x / 39.3701)), file_info['pixels_per_meter'])) if file_info['compression'] == self.BITFIELDS: if len(header_data) >= 52: for idx, mask in enumerate(['r_mask', 'g_mask', 'b_mask', 'a_mask']): From 0564979cf9568a3b4422923bd237cb1a324a7150 Mon Sep 17 00:00:00 2001 From: hugovk Date: Thu, 25 Jun 2015 23:19:07 +0300 Subject: [PATCH 03/56] Formatting --- PIL/BmpImagePlugin.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/PIL/BmpImagePlugin.py b/PIL/BmpImagePlugin.py index 9a297b3b8..b109d2696 100644 --- a/PIL/BmpImagePlugin.py +++ b/PIL/BmpImagePlugin.py @@ -103,7 +103,9 @@ class BmpImageFile(ImageFile.ImageFile): file_info['pixels_per_meter'] = (i32(header_data[20:24]), i32(header_data[24:28])) file_info['colors'] = i32(header_data[28:32]) file_info['palette_padding'] = 4 - self.info["dpi"] = tuple(map(lambda x: int(math.ceil(x / 39.3701)), file_info['pixels_per_meter'])) + self.info["dpi"] = tuple( + map(lambda x: int(math.ceil(x / 39.3701)), + file_info['pixels_per_meter'])) if file_info['compression'] == self.BITFIELDS: if len(header_data) >= 52: for idx, mask in enumerate(['r_mask', 'g_mask', 'b_mask', 'a_mask']): From 36fc250a52164d0678dde9a5009b2a8a00389a01 Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Fri, 26 Jun 2015 11:01:40 +1000 Subject: [PATCH 04/56] Updated tiff and tk tcl 8.5 versions --- winbuild/config.py | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/winbuild/config.py b/winbuild/config.py index ffd8079fe..71fc8a27b 100644 --- a/winbuild/config.py +++ b/winbuild/config.py @@ -22,9 +22,9 @@ libs = {'zlib': { 'dir': 'jpeg-9a', }, 'tiff': { - 'url': 'ftp://ftp.remotesensing.org/pub/libtiff/tiff-4.0.3.zip', - 'hash': 'md5:dd70349cedb3981371686e1c9b89a7f9', # not found - generated by wiredfool - 'dir': 'tiff-4.0.3', + 'url': 'ftp://ftp.remotesensing.org/pub/libtiff/tiff-4.0.4.zip', + 'hash': 'md5:8f538a34156188f9a8dcddb679c65d1e', # not found - generated by wiredfool + 'dir': 'tiff-4.0.4', }, 'freetype': { 'url': 'http://download.savannah.gnu.org/releases/freetype/freetype-2.6.tar.gz', @@ -37,13 +37,13 @@ libs = {'zlib': { 'dir': 'lcms2-2.7', }, 'tcl-8.5': { - 'url': SF_MIRROR+'/project/tcl/Tcl/8.5.13/tcl8513-src.zip', - 'hash': 'sha1:3e01585c91293c532a3cd594ec59deca92153a5e', + 'url': SF_MIRROR+'/project/tcl/Tcl/8.5.18/tcl8518-src.zip', + 'hash': 'sha1:4c2aed9043088c630a4c795265e2738ef1b4db3b', 'dir': '', }, 'tk-8.5': { - 'url': SF_MIRROR+'/project/tcl/Tcl/8.5.13/tk8513-src.zip', - 'hash': 'sha1:23a1d7ddd416e11e06dfdb9f86111d4bab9420b4', + 'url': SF_MIRROR+'/project/tcl/Tcl/8.5.18/tk8518-src.zip', + 'hash': 'sha1:273f55148777413774aa722ecad25cabda1e31ae', 'dir': '', }, 'tcl-8.6': { From 951923f7556dd37e5ddc98ec92e00d3ec82c212e Mon Sep 17 00:00:00 2001 From: Hugo van Kemenade Date: Fri, 26 Jun 2015 10:18:07 +0300 Subject: [PATCH 05/56] Update CHANGES.rst [CI skip] --- CHANGES.rst | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/CHANGES.rst b/CHANGES.rst index a91e4be5c..6838185d3 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -3,6 +3,10 @@ Changelog (Pillow) 2.9.0 (Unreleased) ------------------ + +- Update tiff and tk tcl 8.5 versions #1303 + [radarhere] + - Add functions to convert: Image <-> QImage; Image <-> QPixmap #1217 [radarhere, rominf] From afa4cadb2327935d6b5ebb036e4f26a831c84827 Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Wed, 24 Jun 2015 10:35:37 +1000 Subject: [PATCH 06/56] Added width and height properties --- PIL/Image.py | 8 ++++++++ Tests/test_image.py | 9 +++++++++ 2 files changed, 17 insertions(+) diff --git a/PIL/Image.py b/PIL/Image.py index a6b08d196..3740b51c6 100644 --- a/PIL/Image.py +++ b/PIL/Image.py @@ -504,6 +504,14 @@ class Image(object): self.readonly = 0 self.pyaccess = None + @property + def width(self): + return self.size[0] + + @property + def height(self): + return self.size[1] + def _new(self, im): new = Image() new.im = im diff --git a/Tests/test_image.py b/Tests/test_image.py index 469045909..bd5fd3522 100644 --- a/Tests/test_image.py +++ b/Tests/test_image.py @@ -30,6 +30,15 @@ class TestImage(PillowTestCase): # self.assertRaises( # MemoryError, lambda: Image.new("L", (1000000, 1000000))) + def test_width_height(self): + im = Image.new("RGB", (1, 2)) + self.assertEqual(im.width, 1) + self.assertEqual(im.height, 2) + + im.size = (3, 4) + self.assertEqual(im.width, 3) + self.assertEqual(im.height, 4) + def test_invalid_image(self): if str is bytes: import StringIO From eb73fe3bdbc9f8d29e3917fd82e4ce5e2e84d7d0 Mon Sep 17 00:00:00 2001 From: hugovk Date: Fri, 26 Jun 2015 10:39:13 +0300 Subject: [PATCH 07/56] flake8 --- winbuild/build.py | 10 ++- winbuild/build_dep.py | 8 +- winbuild/config.py | 178 ++++++++++++++++++++-------------------- winbuild/get_pythons.py | 4 +- winbuild/test.py | 5 +- 5 files changed, 104 insertions(+), 101 deletions(-) diff --git a/winbuild/build.py b/winbuild/build.py index 9e5e0ea1c..bdede470a 100644 --- a/winbuild/build.py +++ b/winbuild/build.py @@ -13,8 +13,8 @@ def setup_vms(): ret = [] for py in pythons.keys(): for arch in ('', X64_EXT): - ret.append("virtualenv -p c:/Python%s%s/python.exe --clear %s%s%s" % - (py, arch, VIRT_BASE, py, arch)) + ret.append("virtualenv -p c:/Python%s%s/python.exe --clear %s%s%s" + % (py, arch, VIRT_BASE, py, arch)) ret.append("%s%s%s\Scripts\pip.exe install nose" % (VIRT_BASE, py, arch)) if py == '26': @@ -109,13 +109,15 @@ def main(op): scripts.append((py_version, "\n".join([header(op), build_one(py_version, - compilers[(compiler_version, 32)]), + compilers[(compiler_version, + 32)]), footer()]))) scripts.append(("%s%s" % (py_version, X64_EXT), "\n".join([header(op), build_one("%sx64" % py_version, - compilers[(compiler_version, 64)]), + compilers[(compiler_version, + 64)]), footer()]))) results = map(run_script, scripts) diff --git a/winbuild/build_dep.py b/winbuild/build_dep.py index a837b31f5..827e8d53b 100644 --- a/winbuild/build_dep.py +++ b/winbuild/build_dep.py @@ -4,7 +4,7 @@ from untar import untar import os import hashlib -from config import * +from config import bin_libs, compilers, compiler_fromEnv, libs def _relpath(*args): @@ -64,10 +64,12 @@ def fetch_libs(): if name == 'openjpeg': filename = check_hash(fetch(lib['url']), lib['hash']) for compiler in compilers.values(): - if not os.path.exists(os.path.join(build_dir, lib['dir']+compiler['inc_dir'])): + if not os.path.exists(os.path.join( + build_dir, lib['dir']+compiler['inc_dir'])): extract(filename, build_dir) os.rename(os.path.join(build_dir, lib['dir']), - os.path.join(build_dir, lib['dir']+compiler['inc_dir'])) + os.path.join( + build_dir, lib['dir']+compiler['inc_dir'])) else: extract(check_hash(fetch(lib['url']), lib['hash']), build_dir) diff --git a/winbuild/config.py b/winbuild/config.py index 71fc8a27b..6846669bf 100644 --- a/winbuild/config.py +++ b/winbuild/config.py @@ -11,108 +11,106 @@ pythons = {#'26': 7, VIRT_BASE = "c:/vp/" X64_EXT = os.environ.get('X64_EXT', "x64") -libs = {'zlib': { - 'url': 'http://zlib.net/zlib128.zip', - 'hash': 'md5:126f8676442ffbd97884eb4d6f32afb4', - 'dir': 'zlib-1.2.8', +libs = { + 'zlib': { + 'url': 'http://zlib.net/zlib128.zip', + 'hash': 'md5:126f8676442ffbd97884eb4d6f32afb4', + 'dir': 'zlib-1.2.8', }, - 'jpeg': { - 'url': 'http://www.ijg.org/files/jpegsr9a.zip', - 'hash': 'md5:a34f3c82760270ee1e1885b15b90a72e', # not found - generated by wiredfool - 'dir': 'jpeg-9a', + 'jpeg': { + 'url': 'http://www.ijg.org/files/jpegsr9a.zip', + 'hash': 'md5:a34f3c82760270ee1e1885b15b90a72e', # not found - generated by wiredfool + 'dir': 'jpeg-9a', }, - 'tiff': { - 'url': 'ftp://ftp.remotesensing.org/pub/libtiff/tiff-4.0.4.zip', - 'hash': 'md5:8f538a34156188f9a8dcddb679c65d1e', # not found - generated by wiredfool - 'dir': 'tiff-4.0.4', - }, - 'freetype': { - 'url': 'http://download.savannah.gnu.org/releases/freetype/freetype-2.6.tar.gz', - 'hash': 'md5:1d733ea6c1b7b3df38169fbdbec47d2b', - 'dir': 'freetype-2.6', + 'tiff': { + 'url': 'ftp://ftp.remotesensing.org/pub/libtiff/tiff-4.0.4.zip', + 'hash': 'md5:8f538a34156188f9a8dcddb679c65d1e', # not found - generated by wiredfool + 'dir': 'tiff-4.0.4', }, - 'lcms': { - 'url': SF_MIRROR+'/project/lcms/lcms/2.7/lcms2-2.7.zip', - 'hash': 'sha1:7ff1a5b721ca719760ba6eb4ec6f38d5e65381cf', - 'dir': 'lcms2-2.7', - }, - 'tcl-8.5': { - 'url': SF_MIRROR+'/project/tcl/Tcl/8.5.18/tcl8518-src.zip', - 'hash': 'sha1:4c2aed9043088c630a4c795265e2738ef1b4db3b', - 'dir': '', - }, - 'tk-8.5': { - 'url': SF_MIRROR+'/project/tcl/Tcl/8.5.18/tk8518-src.zip', - 'hash': 'sha1:273f55148777413774aa722ecad25cabda1e31ae', - 'dir': '', + 'freetype': { + 'url': 'http://download.savannah.gnu.org/releases/freetype/freetype-2.6.tar.gz', + 'hash': 'md5:1d733ea6c1b7b3df38169fbdbec47d2b', + 'dir': 'freetype-2.6', }, - 'tcl-8.6': { - 'url': SF_MIRROR+'/project/tcl/Tcl/8.6.4/tcl864-src.zip', - 'hash': 'md5:35748d2fc61e08a2fdb23b85c6f8c4a0', - 'dir': '', - }, - 'tk-8.6': { - 'url': SF_MIRROR+'/project/tcl/Tcl/8.6.4/tk864-src.zip', - 'hash': 'md5:111d45061a69e7f5250b6ec8ca7c4f35', - 'dir': '', + 'lcms': { + 'url': SF_MIRROR+'/project/lcms/lcms/2.7/lcms2-2.7.zip', + 'hash': 'sha1:7ff1a5b721ca719760ba6eb4ec6f38d5e65381cf', + 'dir': 'lcms2-2.7', }, - 'webp': { - 'url': 'http://downloads.webmproject.org/releases/webp/libwebp-0.4.3.tar.gz', - 'hash': 'sha1:1c307a61c4d0018620b4ba9a58e8f48a8d6640ef', - 'dir': 'libwebp-0.4.3', - + 'tcl-8.5': { + 'url': SF_MIRROR+'/project/tcl/Tcl/8.5.18/tcl8518-src.zip', + 'hash': 'sha1:4c2aed9043088c630a4c795265e2738ef1b4db3b', + 'dir': '', }, - 'openjpeg': { - 'url': SF_MIRROR+'/project/openjpeg/openjpeg/2.1.0/openjpeg-2.1.0.tar.gz', - 'hash': 'md5:f6419fcc233df84f9a81eb36633c6db6', - 'dir': 'openjpeg-2.1.0', + 'tk-8.5': { + 'url': SF_MIRROR+'/project/tcl/Tcl/8.5.18/tk8518-src.zip', + 'hash': 'sha1:273f55148777413774aa722ecad25cabda1e31ae', + 'dir': '', }, - - } + 'tcl-8.6': { + 'url': SF_MIRROR+'/project/tcl/Tcl/8.6.4/tcl864-src.zip', + 'hash': 'md5:35748d2fc61e08a2fdb23b85c6f8c4a0', + 'dir': '', + }, + 'tk-8.6': { + 'url': SF_MIRROR+'/project/tcl/Tcl/8.6.4/tk864-src.zip', + 'hash': 'md5:111d45061a69e7f5250b6ec8ca7c4f35', + 'dir': '', + }, + 'webp': { + 'url': 'http://downloads.webmproject.org/releases/webp/libwebp-0.4.3.tar.gz', + 'hash': 'sha1:1c307a61c4d0018620b4ba9a58e8f48a8d6640ef', + 'dir': 'libwebp-0.4.3', + }, + 'openjpeg': { + 'url': SF_MIRROR+'/project/openjpeg/openjpeg/2.1.0/openjpeg-2.1.0.tar.gz', + 'hash': 'md5:f6419fcc233df84f9a81eb36633c6db6', + 'dir': 'openjpeg-2.1.0', + }, +} bin_libs = { - 'openjpeg': { - 'filename': 'openjpeg-2.0.0-win32-x86.zip', - 'hash': 'sha1:xxx', - 'version': '2.0' - }, - } - -compilers = {(7, 64): { - 'env_version': 'v7.0', - 'vc_version': '2008', - 'env_flags': '/x64 /xp', - 'inc_dir': 'msvcr90-x64', - 'platform': 'x64', - 'webp_platform': 'x64', + 'openjpeg': { + 'filename': 'openjpeg-2.0.0-win32-x86.zip', + 'hash': 'sha1:xxx', + 'version': '2.0' }, - (7, 32): { - 'env_version': 'v7.0', - 'vc_version': '2008', - 'env_flags': '/x86 /xp', - 'inc_dir': 'msvcr90-x32', - 'platform': 'Win32', - 'webp_platform': 'x86', - }, +} - (7.1, 64): { - 'env_version': 'v7.1', - 'vc_version': '2010', - 'env_flags': '/x64 /vista', - 'inc_dir': 'msvcr10-x64', - 'platform': 'x64', - 'webp_platform': 'x64', - }, - (7.1, 32): { - 'env_version': 'v7.1', - 'vc_version': '2010', - 'env_flags': '/x86 /vista', - 'inc_dir': 'msvcr10-x32', - 'platform': 'Win32', - 'webp_platform': 'x86', +compilers = { + (7, 64): { + 'env_version': 'v7.0', + 'vc_version': '2008', + 'env_flags': '/x64 /xp', + 'inc_dir': 'msvcr90-x64', + 'platform': 'x64', + 'webp_platform': 'x64', }, - - } + (7, 32): { + 'env_version': 'v7.0', + 'vc_version': '2008', + 'env_flags': '/x86 /xp', + 'inc_dir': 'msvcr90-x32', + 'platform': 'Win32', + 'webp_platform': 'x86', + }, + (7.1, 64): { + 'env_version': 'v7.1', + 'vc_version': '2010', + 'env_flags': '/x64 /vista', + 'inc_dir': 'msvcr10-x64', + 'platform': 'x64', + 'webp_platform': 'x64', + }, + (7.1, 32): { + 'env_version': 'v7.1', + 'vc_version': '2010', + 'env_flags': '/x86 /vista', + 'inc_dir': 'msvcr10-x32', + 'platform': 'Win32', + 'webp_platform': 'x86', + }, +} def pyversion_fromEnv(): diff --git a/winbuild/get_pythons.py b/winbuild/get_pythons.py index 04d8591ac..8ac3b1d4a 100644 --- a/winbuild/get_pythons.py +++ b/winbuild/get_pythons.py @@ -5,8 +5,8 @@ if __name__ == '__main__': for version in ['2.6.5', '2.7.6', '3.2.5', '3.3.5', '3.4.3']: for platform in ['', '.amd64']: for extension in ['', '.asc']: - fetch('https://www.python.org/ftp/python/%s/python-%s%s.msi%s' % ( - version, version, platform, extension)) + fetch('https://www.python.org/ftp/python/%s/python-%s%s.msi%s' + % (version, version, platform, extension)) # find pip, if it's not in the path! os.system('pip install virtualenv') diff --git a/winbuild/test.py b/winbuild/test.py index 3310fb556..84e071308 100644 --- a/winbuild/test.py +++ b/winbuild/test.py @@ -5,14 +5,15 @@ import os import glob import sys -from config import * +from config import pythons, VIRT_BASE, X64_EXT def test_one(params): python, architecture = params try: print("Running: %s, %s" % params) - command = [r'%s\%s%s\Scripts\python.exe' % (VIRT_BASE, python, architecture), + command = [r'%s\%s%s\Scripts\python.exe' % + (VIRT_BASE, python, architecture), 'test-installed.py', '--processes=-0', '--process-timeout=30', From 3f7ec4e98180914e2bae4eea2179e55f3289cdab Mon Sep 17 00:00:00 2001 From: Hugo van Kemenade Date: Fri, 26 Jun 2015 13:02:05 +0300 Subject: [PATCH 08/56] Update CHANGES.rst [CI skip] --- CHANGES.rst | 3 +++ 1 file changed, 3 insertions(+) diff --git a/CHANGES.rst b/CHANGES.rst index 6838185d3..534da7fbb 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -4,6 +4,9 @@ Changelog (Pillow) 2.9.0 (Unreleased) ------------------ +- Added width and height properties #1304 + [radarhere] + - Update tiff and tk tcl 8.5 versions #1303 [radarhere] From 65f5f05b4f66dd283c870e10a6fa89d6150c1512 Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Fri, 26 Jun 2015 20:14:26 +1000 Subject: [PATCH 09/56] Added documentation for width and height properties [ci skip] --- docs/reference/Image.rst | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/docs/reference/Image.rst b/docs/reference/Image.rst index 974d84a6e..ac8b6f506 100644 --- a/docs/reference/Image.rst +++ b/docs/reference/Image.rst @@ -181,6 +181,18 @@ Instances of the :py:class:`Image` class have the following attributes: :type: ``(width, height)`` +.. py:attribute:: width + + Image width, in pixels. + + :type: :py:class:`int` + +.. py:attribute:: height + + Image height, in pixels. + + :type: :py:class:`int` + .. py:attribute:: palette Colour palette table, if any. If mode is ā€œPā€, this should be an instance of From 77f45aa4028e7ccc8c53ea62b94ed8a5a5b0e427 Mon Sep 17 00:00:00 2001 From: wiredfool Date: Fri, 26 Jun 2015 09:04:04 -0700 Subject: [PATCH 10/56] Workaround for clone failing Workaround for https://github.com/appveyor/ci/issues/315 --- appveyor.yml | 1 - 1 file changed, 1 deletion(-) diff --git a/appveyor.yml b/appveyor.yml index 868f34f7e..179c329a8 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -1,5 +1,4 @@ version: 2.9.pre.{build} -shallow_clone: true clone_folder: c:\pillow init: - ECHO %PYTHON% From 26e6bb25ef09dd6b47b7f489fce8decc3bb900fb Mon Sep 17 00:00:00 2001 From: wiredfool Date: Fri, 26 Jun 2015 10:08:40 -0700 Subject: [PATCH 11/56] updated tcl/tk extract directories --- winbuild/build_dep.py | 23 ++++++++++++----------- winbuild/config.py | 8 +++++--- 2 files changed, 17 insertions(+), 14 deletions(-) diff --git a/winbuild/build_dep.py b/winbuild/build_dep.py index a837b31f5..2ca4613c7 100644 --- a/winbuild/build_dep.py +++ b/winbuild/build_dep.py @@ -91,18 +91,19 @@ endlocal """ % compiler -def cp_tk(): +def cp_tk(ver_85, ver_86): + versions = {'ver_85':ver_85, 'ver_86':ver_86} return r""" -mkdir %INCLIB%\tcl85\include\X11 -copy /Y /B %BUILD%\tcl8.5.13\generic\*.h %INCLIB%\tcl85\include\ -copy /Y /B %BUILD%\tk8.5.13\generic\*.h %INCLIB%\tcl85\include\ -copy /Y /B %BUILD%\tk8.5.13\xlib\X11\* %INCLIB%\tcl85\include\X11\ +mkdir %%INCLIB%%\tcl85\include\X11 +copy /Y /B %%BUILD%%\tcl%(ver_85)s\generic\*.h %%INCLIB%%\tcl85\include\ +copy /Y /B %%BUILD%%\tk%(ver_85)s\generic\*.h %%INCLIB%%\tcl85\include\ +copy /Y /B %%BUILD%%\tk%(ver_85)s\xlib\X11\* %%INCLIB%%\tcl85\include\X11\ -mkdir %INCLIB%\tcl86\include\X11 -copy /Y /B %BUILD%\tcl8.6.4\generic\*.h %INCLIB%\tcl86\include\ -copy /Y /B %BUILD%\tk8.6.4\generic\*.h %INCLIB%\tcl86\include\ -copy /Y /B %BUILD%\tk8.6.4\xlib\X11\* %INCLIB%\tcl86\include\X11\ -""" +mkdir %%INCLIB%%\tcl86\include\X11 +copy /Y /B %%BUILD%%\tcl%(ver_86)s\generic\*.h %%INCLIB%%\tcl86\include\ +copy /Y /B %%BUILD%%\tk%(ver_86)s\generic\*.h %%INCLIB%%\tcl86\include\ +copy /Y /B %%BUILD%%\tk%(ver_86)s\xlib\X11\* %%INCLIB%%\tcl86\include\X11\ +""" % versions def header(): @@ -305,7 +306,7 @@ def add_compiler(compiler): mkdirs() fetch_libs() # extract_binlib() -script = [header(), cp_tk()] +script = [header(), cp_tk(libs['tk-8.5']['version'],libs['tk-8.6']['version'] )] if 'PYTHON' in os.environ: diff --git a/winbuild/config.py b/winbuild/config.py index 71fc8a27b..f19a881f5 100644 --- a/winbuild/config.py +++ b/winbuild/config.py @@ -23,7 +23,7 @@ libs = {'zlib': { }, 'tiff': { 'url': 'ftp://ftp.remotesensing.org/pub/libtiff/tiff-4.0.4.zip', - 'hash': 'md5:8f538a34156188f9a8dcddb679c65d1e', # not found - generated by wiredfool + 'hash': 'md5:8f538a34156188f9a8dcddb679c65d1e', 'dir': 'tiff-4.0.4', }, 'freetype': { @@ -45,6 +45,7 @@ libs = {'zlib': { 'url': SF_MIRROR+'/project/tcl/Tcl/8.5.18/tk8518-src.zip', 'hash': 'sha1:273f55148777413774aa722ecad25cabda1e31ae', 'dir': '', + 'version':'8.5.18', }, 'tcl-8.6': { 'url': SF_MIRROR+'/project/tcl/Tcl/8.6.4/tcl864-src.zip', @@ -55,12 +56,13 @@ libs = {'zlib': { 'url': SF_MIRROR+'/project/tcl/Tcl/8.6.4/tk864-src.zip', 'hash': 'md5:111d45061a69e7f5250b6ec8ca7c4f35', 'dir': '', + 'version':'8.6.4', }, 'webp': { 'url': 'http://downloads.webmproject.org/releases/webp/libwebp-0.4.3.tar.gz', 'hash': 'sha1:1c307a61c4d0018620b4ba9a58e8f48a8d6640ef', 'dir': 'libwebp-0.4.3', - + }, 'openjpeg': { 'url': SF_MIRROR+'/project/openjpeg/openjpeg/2.1.0/openjpeg-2.1.0.tar.gz', @@ -68,7 +70,7 @@ libs = {'zlib': { 'dir': 'openjpeg-2.1.0', }, - } +} bin_libs = { 'openjpeg': { From 1c7a140298eb2b0ef7bebb1ef4d0e9d48c143527 Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Sat, 27 Jun 2015 09:10:01 +1000 Subject: [PATCH 12/56] Fixed tox test script path --- tox.ini | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tox.ini b/tox.ini index 80f7edef4..ebc63d85a 100644 --- a/tox.ini +++ b/tox.ini @@ -11,4 +11,4 @@ commands = {envpython} setup.py clean {envpython} setup.py build_ext --inplace {envpython} selftest.py - {envpython} Tests/run.py --installed + {envpython} test-installed.py --installed From 552e7a579b133d384b2285aae8e4041e98640010 Mon Sep 17 00:00:00 2001 From: Hugo van Kemenade Date: Sat, 27 Jun 2015 20:53:32 +0300 Subject: [PATCH 13/56] Update CHANGES.rst [CI skip] --- CHANGES.rst | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/CHANGES.rst b/CHANGES.rst index 534da7fbb..c8f39899d 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -4,11 +4,14 @@ Changelog (Pillow) 2.9.0 (Unreleased) ------------------ +- Fixed tox test script path #1308 + [radarhere] + - Added width and height properties #1304 [radarhere] - Update tiff and tk tcl 8.5 versions #1303 - [radarhere] + [radarhere, wiredfool] - Add functions to convert: Image <-> QImage; Image <-> QPixmap #1217 [radarhere, rominf] From 46fad0fd636b8e39e03f4c9823c2118f91389240 Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Sun, 28 Jun 2015 12:03:11 +1000 Subject: [PATCH 14/56] Fixed typo --- PIL/TiffImagePlugin.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/PIL/TiffImagePlugin.py b/PIL/TiffImagePlugin.py index 59de84273..83bbd3f93 100644 --- a/PIL/TiffImagePlugin.py +++ b/PIL/TiffImagePlugin.py @@ -548,7 +548,7 @@ class ImageFileDirectory(collections.MutableMapping): # and doesn't match the tiff spec: 8-bit byte that # contains a 7-bit ASCII code; the last byte must be # NUL (binary zero). Also, I don't think this was well - # excersized before. + # exercised before. data = value = b"" + value.encode('ascii', 'replace') + b"\0" else: # integer data From ac7b4b825aacc57301b9ccfccf6cf29fa2f116f8 Mon Sep 17 00:00:00 2001 From: David Schmidt Date: Sun, 28 Jun 2015 13:04:46 +0200 Subject: [PATCH 15/56] gif doc improved - missing closing brackt - EOFError mentioned --- docs/handbook/image-file-formats.rst | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/handbook/image-file-formats.rst b/docs/handbook/image-file-formats.rst index ed0c3ae5b..11ec60401 100644 --- a/docs/handbook/image-file-formats.rst +++ b/docs/handbook/image-file-formats.rst @@ -77,8 +77,8 @@ Reading sequences ~~~~~~~~~~~~~~~~~ The GIF loader supports the :py:meth:`~file.seek` and :py:meth:`~file.tell` -methods. You can seek to the next frame (``im.seek(im.tell() + 1``), or rewind -the file by seeking to the first frame. Random access is not supported. +methods. You can seek to the next frame (``im.seek(im.tell() + 1)``), or rewind +the file by seeking to the first frame. Random access is not supported. ``im.seek()`` raises an ``EOFError`` if you try to seek after the last frame. Reading local images ~~~~~~~~~~~~~~~~~~~~ From 7a3268d4aec2b910b3adc5905017d7e69fdc4d07 Mon Sep 17 00:00:00 2001 From: Alex Clark Date: Sun, 28 Jun 2015 14:57:51 -0400 Subject: [PATCH 16/56] Set default makefile target "make" runs release test --- Makefile | 1 + 1 file changed, 1 insertion(+) diff --git a/Makefile b/Makefile index 4d96c497d..e123a74c7 100644 --- a/Makefile +++ b/Makefile @@ -1,5 +1,6 @@ # https://www.gnu.org/software/make/manual/html_node/Phony-Targets.html .PHONY: clean coverage doc docserve help inplace install install-req release-test sdist test upload upload-test +.DEFAULT_GOAL := release-test clean: python setup.py clean From efe925c26f4fb78613b5ed98d488f71a723d03e8 Mon Sep 17 00:00:00 2001 From: Alex Clark Date: Sun, 28 Jun 2015 15:07:01 -0400 Subject: [PATCH 17/56] Fix manifest [ci skip] --- MANIFEST.in | 21 ++++++++++++++------- 1 file changed, 14 insertions(+), 7 deletions(-) diff --git a/MANIFEST.in b/MANIFEST.in index 30e73c075..ad5bc3bed 100644 --- a/MANIFEST.in +++ b/MANIFEST.in @@ -1,14 +1,16 @@ + include *.c include *.h +include *.in include *.md include *.py -include *.sh include *.rst +include *.sh include *.txt include *.yaml +include *.yml include .coveragerc include .gitattributes -include .travis.yml include LICENSE include Makefile include tox.ini @@ -16,7 +18,6 @@ recursive-include PIL *.md recursive-include Scripts *.py recursive-include Scripts *.rst recursive-include Scripts *.sh -recursive-include Scripts README.rst recursive-include Tests *.bdf recursive-include Tests *.bin recursive-include Tests *.bmp @@ -33,11 +34,13 @@ recursive-include Tests *.html recursive-include Tests *.icc recursive-include Tests *.icns recursive-include Tests *.ico +recursive-include Tests *.im recursive-include Tests *.j2k recursive-include Tests *.jp2 recursive-include Tests *.jpg recursive-include Tests *.lut recursive-include Tests *.mpo +recursive-include Tests *.msp recursive-include Tests *.pbm recursive-include Tests *.pcf recursive-include Tests *.pcx @@ -59,8 +62,8 @@ recursive-include Tests *.tiff recursive-include Tests *.ttf recursive-include Tests *.txt recursive-include Tests *.webp +recursive-include Tests *.xbm recursive-include Tests *.xpm -recursive-include Tests *.msp recursive-include Tk *.c recursive-include Tk *.rst recursive-include depends *.rst @@ -71,9 +74,13 @@ recursive-include docs *.html recursive-include docs *.py recursive-include docs *.rst recursive-include docs *.txt -recursive-include docs BUILDME -recursive-include docs COPYING -recursive-include docs Guardfile recursive-include docs Makefile +recursive-include docs Guardfile +recursive-include docs COPYING +recursive-include docs BUILDME recursive-include libImaging *.c recursive-include libImaging *.h +recursive-include winbuild *.gitignore +recursive-include winbuild *.md +recursive-include winbuild *.opt +recursive-include winbuild *.py From 3dd3c4e28ca32ddbfd3e20dd277824b104a9550b Mon Sep 17 00:00:00 2001 From: Christoph Gohlke Date: Sun, 28 Jun 2015 20:50:17 -0700 Subject: [PATCH 18/56] TST: fix ValueError on Python 2.6 --- Tests/test_image_toqimage.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Tests/test_image_toqimage.py b/Tests/test_image_toqimage.py index 38b83c023..6c79b4c6d 100644 --- a/Tests/test_image_toqimage.py +++ b/Tests/test_image_toqimage.py @@ -19,7 +19,7 @@ class TestToQImage(PillowQtTestCase, PillowTestCase): self.assertFalse(data.isNull()) # Test saving the file - tempfile = self.tempfile('temp_{}.png'.format(mode)) + tempfile = self.tempfile('temp_{0}.png'.format(mode)) data.save(tempfile) From 29cb7d24b80a3db240725d7d7116ee55827322fc Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Mon, 29 Jun 2015 14:59:05 +1000 Subject: [PATCH 19/56] Fixed ValueError in Python 2.6 --- Tests/test_image_toqpixmap.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Tests/test_image_toqpixmap.py b/Tests/test_image_toqpixmap.py index 95db2e8f7..8fabab13d 100644 --- a/Tests/test_image_toqpixmap.py +++ b/Tests/test_image_toqpixmap.py @@ -19,7 +19,7 @@ class TestToQPixmap(PillowQPixmapTestCase, PillowTestCase): self.assertFalse(data.isNull()) # Test saving the file - tempfile = self.tempfile('temp_{}.png'.format(mode)) + tempfile = self.tempfile('temp_{0}.png'.format(mode)) data.save(tempfile) From fc18f08eb5a3f7ab5b9294fcbb24410eeb29fd2a Mon Sep 17 00:00:00 2001 From: Hugo van Kemenade Date: Mon, 29 Jun 2015 10:22:03 +0300 Subject: [PATCH 20/56] Update CHANGES.rst [CI skip] --- CHANGES.rst | 3 +++ 1 file changed, 3 insertions(+) diff --git a/CHANGES.rst b/CHANGES.rst index c8f39899d..89655a630 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -4,6 +4,9 @@ Changelog (Pillow) 2.9.0 (Unreleased) ------------------ +- Fixed ValueError in Python 2.6 #1315 #1316 + [cgohlke, radarhere] + - Fixed tox test script path #1308 [radarhere] From 96944e2dd664efb98e25d0e86671420af26fda40 Mon Sep 17 00:00:00 2001 From: Alex Clark Date: Mon, 29 Jun 2015 08:57:55 -0400 Subject: [PATCH 21/56] Bump --- PIL/__init__.py | 2 +- _imaging.c | 2 +- setup.py | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/PIL/__init__.py b/PIL/__init__.py index 6d51a5dcb..64f8953d6 100644 --- a/PIL/__init__.py +++ b/PIL/__init__.py @@ -12,7 +12,7 @@ # ;-) VERSION = '1.1.7' # PIL version -PILLOW_VERSION = '2.9.0.dev0' # Pillow +PILLOW_VERSION = '2.9.0.dev1' # Pillow _plugins = ['BmpImagePlugin', 'BufrStubImagePlugin', diff --git a/_imaging.c b/_imaging.c index 09345c0dd..646fedf8b 100644 --- a/_imaging.c +++ b/_imaging.c @@ -71,7 +71,7 @@ * See the README file for information on usage and redistribution. */ -#define PILLOW_VERSION "2.9.0.dev0" +#define PILLOW_VERSION "2.9.0.dev1" #include "Python.h" diff --git a/setup.py b/setup.py index f669873d6..7e9e3a37e 100644 --- a/setup.py +++ b/setup.py @@ -90,7 +90,7 @@ except (ImportError, OSError): NAME = 'Pillow' -PILLOW_VERSION = '2.9.0.dev0' +PILLOW_VERSION = '2.9.0.dev1' TCL_ROOT = None JPEG_ROOT = None JPEG2K_ROOT = None From 24eceff7df828aabc3d9c93cbb173232353a0a28 Mon Sep 17 00:00:00 2001 From: hugovk Date: Mon, 29 Jun 2015 16:58:53 +0300 Subject: [PATCH 22/56] Run check-manifest --- .travis.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.travis.yml b/.travis.yml index f94f8376a..2397376fb 100644 --- a/.travis.yml +++ b/.travis.yml @@ -20,6 +20,7 @@ install: - "travis_retry sudo apt-get -qq install libfreetype6-dev liblcms2-dev python-qt4 ghostscript libffi-dev libjpeg-turbo-progs cmake imagemagick" - "travis_retry pip install cffi" - "travis_retry pip install coverage nose" + - "travis_retry pip install check-manifest" # Pyroma tests sometimes hang on PyPy; skip for PyPy - if [ $TRAVIS_PYTHON_VERSION != "pypy" ]; then travis_retry pip install pyroma; fi @@ -38,6 +39,7 @@ script: - coverage run --append --include=PIL/* selftest.py - coverage run --append --include=PIL/* -m nose -vx Tests/test_*.py + - check-manifest --ignore "depends/*" after_success: # gather the coverage data From 4e754e9c55aeeb8716d3f544f65a1e541cd82e07 Mon Sep 17 00:00:00 2001 From: Antony Lee Date: Fri, 6 Feb 2015 10:58:07 -0800 Subject: [PATCH 23/56] Use logging instead of print. cf. #1191. Only TiffImagePlugin and OLEFileIO still rely on (their own) DEBUG flag. I left TiffImagePlugin as it is because I hope #1059 gets merged in first, and OLEFileIO because it uses its own logic. Untested, as usual. --- PIL/Image.py | 20 ++++------ PIL/ImageFile.py | 23 ++++-------- PIL/PcxImagePlugin.py | 19 +++++----- PIL/PngImagePlugin.py | 16 ++++---- PIL/PyAccess.py | 17 +++++---- PIL/TiffImagePlugin.py | 75 +++++++++++++++++++------------------ Scripts/pilfile.py | 6 ++- Scripts/player.py | 3 -- Tests/test_file_libtiff.py | 16 +++----- Tests/test_file_tiff.py | 16 +++----- Tests/test_imagesequence.py | 3 -- 11 files changed, 96 insertions(+), 118 deletions(-) diff --git a/PIL/Image.py b/PIL/Image.py index 3740b51c6..2bd1a3aa4 100644 --- a/PIL/Image.py +++ b/PIL/Image.py @@ -28,8 +28,11 @@ from __future__ import print_function from PIL import VERSION, PILLOW_VERSION, _plugins +import logging import warnings +logger = logging.getLogger(__name__) + class DecompressionBombWarning(RuntimeWarning): pass @@ -138,11 +141,6 @@ def isImageType(t): """ return hasattr(t, "im") -# -# Debug level - -DEBUG = 0 - # # Constants (also defined in _imagingmodule.c!) @@ -386,13 +384,10 @@ def init(): for plugin in _plugins: try: - if DEBUG: - print("Importing %s" % plugin) + logger.debug("Importing %s", plugin) __import__("PIL.%s" % plugin, globals(), locals(), []) - except ImportError: - if DEBUG: - print("Image: failed to import", end=' ') - print(plugin, ":", sys.exc_info()[1]) + except ImportError as e: + logger.debug("Image: failed to import %s: %s", plugin, e) if OPEN or SAVE: _initialized = 2 @@ -554,8 +549,7 @@ class Image(object): try: self.fp.close() except Exception as msg: - if DEBUG: - print("Error closing: %s" % msg) + logger.debug("Error closing: %s" % msg) # Instead of simply setting to None, we're setting up a # deferred error that will better explain that the core image diff --git a/PIL/ImageFile.py b/PIL/ImageFile.py index cc1d73f09..52d21e1e8 100644 --- a/PIL/ImageFile.py +++ b/PIL/ImageFile.py @@ -30,10 +30,13 @@ from PIL import Image from PIL._util import isPath import io +import logging import os import sys import traceback +logger = logging.getLogger(__name__) + MAXBLOCK = 65536 SAFEBLOCK = 1024*1024 @@ -95,21 +98,11 @@ class ImageFile(Image.Image): try: self._open() - except IndexError as v: # end of data - if Image.DEBUG > 1: - traceback.print_exc() - raise SyntaxError(v) - except TypeError as v: # end of data (ord) - if Image.DEBUG > 1: - traceback.print_exc() - raise SyntaxError(v) - except KeyError as v: # unsupported mode - if Image.DEBUG > 1: - traceback.print_exc() - raise SyntaxError(v) - except EOFError as v: # got header but not the first frame - if Image.DEBUG > 1: - traceback.print_exc() + except (IndexError, # end of data + TypeError, # end of data (ord) + KeyError, # unsupported mode + EOFError) as v: # got header but not the first frame + logger.exception("%s") raise SyntaxError(v) if not self.mode or self.size[0] <= 0: diff --git a/PIL/PcxImagePlugin.py b/PIL/PcxImagePlugin.py index 40fafa0dc..906c38a35 100644 --- a/PIL/PcxImagePlugin.py +++ b/PIL/PcxImagePlugin.py @@ -27,8 +27,11 @@ from __future__ import print_function +import logging from PIL import Image, ImageFile, ImagePalette, _binary +logger = logging.getLogger(__name__) + i8 = _binary.i8 i16 = _binary.i16le o8 = _binary.o8 @@ -59,17 +62,15 @@ class PcxImageFile(ImageFile.ImageFile): bbox = i16(s, 4), i16(s, 6), i16(s, 8)+1, i16(s, 10)+1 if bbox[2] <= bbox[0] or bbox[3] <= bbox[1]: raise SyntaxError("bad PCX image size") - if Image.DEBUG: - print("BBox: %s %s %s %s" % bbox) + logger.debug("BBox: %s %s %s %s", *bbox) # format version = i8(s[1]) bits = i8(s[3]) planes = i8(s[65]) stride = i16(s, 66) - if Image.DEBUG: - print("PCX version %s, bits %s, planes %s, stride %s" % - (version, bits, planes, stride)) + logger.debug("PCX version %s, bits %s, planes %s, stride %s", + version, bits, planes, stride) self.info["dpi"] = i16(s, 12), i16(s, 14) @@ -107,8 +108,7 @@ class PcxImageFile(ImageFile.ImageFile): self.size = bbox[2]-bbox[0], bbox[3]-bbox[1] bbox = (0, 0) + self.size - if Image.DEBUG: - print("size: %sx%s" % self.size) + logger.debug("size: %sx%s", *self.size) self.tile = [("pcx", bbox, self.fp.tell(), (rawmode, planes * stride))] @@ -144,9 +144,8 @@ def _save(im, fp, filename, check=0): # Ideally it should be passed in in the state, but the bytes value # gets overwritten. - if Image.DEBUG: - print("PcxImagePlugin._save: xwidth: %d, bits: %d, stride: %d" % ( - im.size[0], bits, stride)) + logger.debug("PcxImagePlugin._save: xwidth: %d, bits: %d, stride: %d", + im.size[0], bits, stride) # under windows, we could determine the current screen size with # "Image.core.display_mode()[1]", but I think that's overkill... diff --git a/PIL/PngImagePlugin.py b/PIL/PngImagePlugin.py index 214ff9385..9fcc9fe4e 100644 --- a/PIL/PngImagePlugin.py +++ b/PIL/PngImagePlugin.py @@ -35,10 +35,13 @@ from __future__ import print_function __version__ = "0.9" +import logging import re +import zlib from PIL import Image, ImageFile, ImagePalette, _binary -import zlib + +logger = logging.getLogger(__name__) i8 = _binary.i8 i16 = _binary.i16be @@ -129,8 +132,7 @@ class ChunkStream(object): def call(self, cid, pos, length): "Call the appropriate chunk handler" - if Image.DEBUG: - print("STREAM", cid, pos, length) + logger.debug("STREAM %s %s %s", cid, pos, length) return getattr(self, "chunk_" + cid.decode('ascii'))(pos, length) def crc(self, cid, data): @@ -293,9 +295,8 @@ class PngStream(ChunkStream): # Compression method 1 byte (0) # Compressed profile n bytes (zlib with deflate compression) i = s.find(b"\0") - if Image.DEBUG: - print("iCCP profile name", s[:i]) - print("Compression method", i8(s[i])) + logger.debug("iCCP profile name %s", s[:i]) + logger.debug("Compression method %s", i8(s[i])) comp_method = i8(s[i]) if comp_method != 0: raise SyntaxError("Unknown compression method %s in iCCP chunk" % @@ -507,8 +508,7 @@ class PngImageFile(ImageFile.ImageFile): except EOFError: break except AttributeError: - if Image.DEBUG: - print(cid, pos, length, "(unknown)") + logger.debug("%s %s %s (unknown)", cid, pos, length) s = ImageFile._safe_read(self.fp, length) self.png.crc(cid, s) diff --git a/PIL/PyAccess.py b/PIL/PyAccess.py index 4924facd5..cb4f00cad 100644 --- a/PIL/PyAccess.py +++ b/PIL/PyAccess.py @@ -22,10 +22,14 @@ from __future__ import print_function -from cffi import FFI +import logging import sys -DEBUG = 0 +from cffi import FFI + + +logger = logging.getLogger(__name__) + defs = """ struct Pixel_RGBA { @@ -50,8 +54,7 @@ class PyAccess(object): self.xsize = vals['xsize'] self.ysize = vals['ysize'] - if DEBUG: - print(vals) + logger.debug("%s", vals) self._post_init() def _post_init(self): @@ -305,11 +308,9 @@ else: def new(img, readonly=False): access_type = mode_map.get(img.mode, None) if not access_type: - if DEBUG: - print("PyAccess Not Implemented: %s" % img.mode) + logger.debug("PyAccess Not Implemented: %s", img.mode) return None - if DEBUG: - print("New PyAccess: %s" % img.mode) + logger.debug("New PyAccess: %s", img.mode) return access_type(img, readonly) # End of file diff --git a/PIL/TiffImagePlugin.py b/PIL/TiffImagePlugin.py index 83bbd3f93..0068efd2c 100644 --- a/PIL/TiffImagePlugin.py +++ b/PIL/TiffImagePlugin.py @@ -42,6 +42,7 @@ from __future__ import print_function __version__ = "1.3.5" +DEBUG = False # Needs to be merged with the new logging approach. from PIL import Image, ImageFile from PIL import ImagePalette @@ -434,7 +435,7 @@ class ImageFileDirectory(collections.MutableMapping): tag, typ = i16(ifd), i16(ifd, 2) - if Image.DEBUG: + if DEBUG: from PIL import TiffTags tagname = TiffTags.TAGS.get(tag, "unknown") typname = TiffTags.TYPES.get(typ, "unknown") @@ -444,7 +445,7 @@ class ImageFileDirectory(collections.MutableMapping): try: dispatch = self.load_dispatch[typ] except KeyError: - if Image.DEBUG: + if DEBUG: print("- unsupported type", typ) continue # ignore unsupported type @@ -455,10 +456,10 @@ class ImageFileDirectory(collections.MutableMapping): # Get and expand tag value if size > 4: here = fp.tell() - if Image.DEBUG: + if DEBUG: print("Tag Location: %s" % here) fp.seek(i32(ifd, 8)) - if Image.DEBUG: + if DEBUG: print("Data Location: %s" % fp.tell()) data = ImageFile._safe_read(fp, size) fp.seek(here) @@ -474,7 +475,7 @@ class ImageFileDirectory(collections.MutableMapping): self.tagdata[tag] = data self.tagtype[tag] = typ - if Image.DEBUG: + if DEBUG: if tag in (COLORMAP, IPTC_NAA_CHUNK, PHOTOSHOP_CHUNK, ICCPROFILE, XMP): print("- value: " % size) @@ -517,8 +518,8 @@ class ImageFileDirectory(collections.MutableMapping): if tag in self.tagtype: typ = self.tagtype[tag] - if Image.DEBUG: - print("Tag %s, Type: %s, Value: %s" % (tag, typ, value)) + if DEBUG: + print ("Tag %s, Type: %s, Value: %s" % (tag, typ, value)) if typ == 1: # byte data @@ -571,7 +572,7 @@ class ImageFileDirectory(collections.MutableMapping): else: data = b"".join(map(o32, value)) - if Image.DEBUG: + if DEBUG: from PIL import TiffTags tagname = TiffTags.TAGS.get(tag, "unknown") typname = TiffTags.TYPES.get(typ, "unknown") @@ -608,7 +609,7 @@ class ImageFileDirectory(collections.MutableMapping): # pass 2: write directory to file for tag, typ, count, value, data in directory: - if Image.DEBUG > 1: + if DEBUG > 1: print(tag, typ, count, repr(value), repr(data)) fp.write(o16(tag) + o16(typ) + o32(count) + value) @@ -651,10 +652,10 @@ class TiffImageFile(ImageFile.ImageFile): self._frame_pos = [] self._n_frames = None - if Image.DEBUG: - print("*** TiffImageFile._open ***") - print("- __first:", self.__first) - print("- ifh: ", ifh) + if DEBUG: + print ("*** TiffImageFile._open ***") + print ("- __first:", self.__first) + print ("- ifh: ", ifh) # and load the first frame self._seek(0) @@ -685,7 +686,7 @@ class TiffImageFile(ImageFile.ImageFile): while len(self._frame_pos) <= frame: if not self.__next: raise EOFError("no more images in TIFF file") - if Image.DEBUG: + if DEBUG: print("Seeking to frame %s, on frame %s, __next %s, location: %s" % (frame, self.__frame, self.__next, self.fp.tell())) # reset python3 buffered io handle in case fp @@ -693,7 +694,7 @@ class TiffImageFile(ImageFile.ImageFile): self.fp.tell() self.fp.seek(self.__next) self._frame_pos.append(self.__next) - if Image.DEBUG: + if DEBUG: print("Loading tags, location: %s" % self.fp.tell()) self.tag.load(self.fp) self.__next = self.tag.next @@ -771,20 +772,20 @@ class TiffImageFile(ImageFile.ImageFile): # Rearranging for supporting byteio items, since they have a fileno # that returns an IOError if there's no underlying fp. Easier to # deal with here by reordering. - if Image.DEBUG: - print("have getvalue. just sending in a string from getvalue") + if DEBUG: + print ("have getvalue. just sending in a string from getvalue") n, err = decoder.decode(self.fp.getvalue()) elif hasattr(self.fp, "fileno"): # we've got a actual file on disk, pass in the fp. - if Image.DEBUG: - print("have fileno, calling fileno version of the decoder.") + if DEBUG: + print ("have fileno, calling fileno version of the decoder.") self.fp.seek(0) # 4 bytes, otherwise the trace might error out n, err = decoder.decode(b"fpfp") else: # we have something else. - if Image.DEBUG: - print("don't have fileno or getvalue. just reading") + if DEBUG: + print ("don't have fileno or getvalue. just reading") # UNDONE -- so much for that buffer size thing. n, err = decoder.decode(self.fp.read()) @@ -821,7 +822,7 @@ class TiffImageFile(ImageFile.ImageFile): fillorder = getscalar(FILLORDER, 1) - if Image.DEBUG: + if DEBUG: print("*** Summary ***") print("- compression:", self._compression) print("- photometric_interpretation:", photo) @@ -833,7 +834,7 @@ class TiffImageFile(ImageFile.ImageFile): ysize = getscalar(IMAGELENGTH) self.size = xsize, ysize - if Image.DEBUG: + if DEBUG: print("- size:", self.size) format = getscalar(SAMPLEFORMAT, 1) @@ -844,16 +845,16 @@ class TiffImageFile(ImageFile.ImageFile): self.tag.get(BITSPERSAMPLE, (1,)), self.tag.get(EXTRASAMPLES, ()) ) - if Image.DEBUG: + if DEBUG: print("format key:", key) try: self.mode, rawmode = OPEN_INFO[key] except KeyError: - if Image.DEBUG: + if DEBUG: print("- unsupported format") raise SyntaxError("unknown pixel mode") - if Image.DEBUG: + if DEBUG: print("- raw mode:", rawmode) print("- pil mode:", self.mode) @@ -893,7 +894,7 @@ class TiffImageFile(ImageFile.ImageFile): "tiff_sgilog", "tiff_sgilog24", "tiff_raw_16"]: - # if Image.DEBUG: + # if DEBUG: # print "Activating g4 compression for whole file" # Decoder expects entire file as one tile. @@ -934,7 +935,7 @@ class TiffImageFile(ImageFile.ImageFile): self.tag.get(BITSPERSAMPLE, (1,)), self.tag.get(EXTRASAMPLES, ()) ) - if Image.DEBUG: + if DEBUG: print("format key:", key) # this should always work, since all the # fillorder==2 modes have a corresponding @@ -963,8 +964,8 @@ class TiffImageFile(ImageFile.ImageFile): (self._compression, (0, min(y, ysize), w, min(y+h, ysize)), offsets[i], a)) - if Image.DEBUG: - print("tiles: ", self.tile) + if DEBUG: + print ("tiles: ", self.tile) y = y + h if y >= self.size[1]: x = y = 0 @@ -992,7 +993,7 @@ class TiffImageFile(ImageFile.ImageFile): l += 1 a = None else: - if Image.DEBUG: + if DEBUG: print("- unsupported data organization") raise SyntaxError("unknown data organization") @@ -1073,7 +1074,7 @@ def _save(im, fp, filename): # write any arbitrary tags passed in as an ImageFileDirectory info = im.encoderinfo.get("tiffinfo", {}) - if Image.DEBUG: + if DEBUG: print("Tiffinfo Keys: %s" % info.keys) keys = list(info.keys()) for key in keys: @@ -1148,9 +1149,9 @@ def _save(im, fp, filename): ifd[COMPRESSION] = COMPRESSION_INFO_REV.get(compression, 1) if libtiff: - if Image.DEBUG: - print("Saving using libtiff encoder") - print(ifd.items()) + if DEBUG: + print ("Saving using libtiff encoder") + print (ifd.items()) _fp = 0 if hasattr(fp, "fileno"): try: @@ -1206,8 +1207,8 @@ def _save(im, fp, filename): # int or similar atts[k] = v - if Image.DEBUG: - print(atts) + if DEBUG: + print (atts) # libtiff always expects the bytes in native order. # we're storing image byte order. So, if the rawmode diff --git a/Scripts/pilfile.py b/Scripts/pilfile.py index b954114ac..dab240e2f 100644 --- a/Scripts/pilfile.py +++ b/Scripts/pilfile.py @@ -21,6 +21,7 @@ from __future__ import print_function import getopt import glob +import logging import sys from PIL import Image @@ -42,6 +43,7 @@ except getopt.error as v: sys.exit(1) verbose = quiet = verify = 0 +logging_level = "WARNING" for o, a in opt: if o == "-f": @@ -58,7 +60,9 @@ for o, a in opt: elif o == "-v": verify = 1 elif o == "-D": - Image.DEBUG += 1 + logging_level = "DEBUG" + +logging.basicConfig(level=logging_level) def globfix(files): diff --git a/Scripts/player.py b/Scripts/player.py index 43877415a..ac9eb817f 100644 --- a/Scripts/player.py +++ b/Scripts/player.py @@ -15,9 +15,6 @@ from PIL import Image, ImageTk import sys -Image.DEBUG = 0 - - # -------------------------------------------------------------------- # an image animation player diff --git a/Tests/test_file_libtiff.py b/Tests/test_file_libtiff.py index 896038b9d..8d5b383a9 100644 --- a/Tests/test_file_libtiff.py +++ b/Tests/test_file_libtiff.py @@ -1,11 +1,14 @@ from __future__ import print_function from helper import unittest, PillowTestCase, hopper, py3 -import os import io +import logging +import os from PIL import Image, TiffImagePlugin +logger = logging.getLogger(__name__) + class LibTiffTestCase(PillowTestCase): @@ -231,7 +234,6 @@ class TestFileLibTiff(LibTiffTestCase): """ Are we generating the same interpretation of the image as Imagemagick is? """ TiffImagePlugin.READ_LIBTIFF = True - # Image.DEBUG = True im = Image.open('Tests/images/12bit.cropped.tif') im.load() TiffImagePlugin.READ_LIBTIFF = False @@ -243,14 +245,8 @@ class TestFileLibTiff(LibTiffTestCase): im2 = Image.open('Tests/images/12in16bit.tif') - if Image.DEBUG: - print(im.getpixel((0, 0))) - print(im.getpixel((0, 1))) - print(im.getpixel((0, 2))) - - print(im2.getpixel((0, 0))) - print(im2.getpixel((0, 1))) - print(im2.getpixel((0, 2))) + logger.debug("%s", [img.getpixel((0, idx)) + for img in [im, im2] for idx in range(3)]) self.assert_image_equal(im, im2) diff --git a/Tests/test_file_tiff.py b/Tests/test_file_tiff.py index 02a63586c..6780c9792 100644 --- a/Tests/test_file_tiff.py +++ b/Tests/test_file_tiff.py @@ -1,9 +1,12 @@ from __future__ import print_function +import logging +import struct + from helper import unittest, PillowTestCase, hopper, py3 from PIL import Image, TiffImagePlugin -import struct +logger = logging.getLogger(__name__) class TestFileTiff(PillowTestCase): @@ -118,7 +121,6 @@ class TestFileTiff(PillowTestCase): """ Are we generating the same interpretation of the image as Imagemagick is? """ - # Image.DEBUG = True im = Image.open('Tests/images/12bit.cropped.tif') # to make the target -- @@ -129,14 +131,8 @@ class TestFileTiff(PillowTestCase): im2 = Image.open('Tests/images/12in16bit.tif') - if Image.DEBUG: - print(im.getpixel((0, 0))) - print(im.getpixel((0, 1))) - print(im.getpixel((0, 2))) - - print(im2.getpixel((0, 0))) - print(im2.getpixel((0, 1))) - print(im2.getpixel((0, 2))) + logger.debug("%s", [img.getpixel((0, idx)) + for img in [im, im2] for idx in range(3)]) self.assert_image_equal(im, im2) diff --git a/Tests/test_imagesequence.py b/Tests/test_imagesequence.py index 1b4bb3c02..d2aa17df7 100644 --- a/Tests/test_imagesequence.py +++ b/Tests/test_imagesequence.py @@ -23,14 +23,11 @@ class TestImageSequence(PillowTestCase): self.assertEqual(index, 1) def _test_multipage_tiff(self, dbg=False): - # debug had side effect of calling fp.tell. - Image.DEBUG = dbg im = Image.open('Tests/images/multipage.tiff') for index, frame in enumerate(ImageSequence.Iterator(im)): frame.load() self.assertEqual(index, im.tell()) frame.convert('RGB') - Image.DEBUG = False def test_tiff(self): # self._test_multipage_tiff(True) From 983c4602b5d015b5a5c77ca454fbe440879d5af7 Mon Sep 17 00:00:00 2001 From: Antony Lee Date: Tue, 16 Jun 2015 14:44:10 -0700 Subject: [PATCH 24/56] Also log plugin opening failures. This allows obtaining tracebacks of failures by plugins to open files by setting the log-level to DEBUG, rather than by having to uncomment the "traceback.print_exc" lines in Image.open. --- PIL/Image.py | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/PIL/Image.py b/PIL/Image.py index 2bd1a3aa4..a292e0e79 100644 --- a/PIL/Image.py +++ b/PIL/Image.py @@ -2300,9 +2300,7 @@ def open(fp, mode="r"): _decompression_bomb_check(im.size) return im except (SyntaxError, IndexError, TypeError, struct.error): - # import traceback - # traceback.print_exc() - pass + logger.debug("", exc_info=True) if init(): @@ -2315,9 +2313,7 @@ def open(fp, mode="r"): _decompression_bomb_check(im.size) return im except (SyntaxError, IndexError, TypeError, struct.error): - # import traceback - # traceback.print_exc() - pass + logger.debug("", exc_info=True) raise IOError("cannot identify image file %r" % (filename if filename else fp)) From d20eef450b7f685d4c5aab810d78f036c039f37f Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Tue, 30 Jun 2015 13:25:00 +1000 Subject: [PATCH 25/56] Added is_animated --- PIL/DcxImagePlugin.py | 4 ++++ PIL/FliImagePlugin.py | 15 +++++++++++++++ PIL/GifImagePlugin.py | 15 +++++++++++++++ PIL/ImImagePlugin.py | 4 ++++ PIL/MicImagePlugin.py | 4 ++++ PIL/MpoImagePlugin.py | 4 ++++ PIL/PsdImagePlugin.py | 4 ++++ PIL/SpiderImagePlugin.py | 4 ++++ PIL/TiffImagePlugin.py | 15 +++++++++++++++ Tests/test_file_dcx.py | 1 + Tests/test_file_fli.py | 1 + Tests/test_file_gif.py | 2 ++ Tests/test_file_im.py | 1 + Tests/test_file_mpo.py | 1 + Tests/test_file_psd.py | 2 ++ Tests/test_file_spider.py | 1 + Tests/test_file_tiff.py | 2 ++ 17 files changed, 80 insertions(+) diff --git a/PIL/DcxImagePlugin.py b/PIL/DcxImagePlugin.py index b3f43b4cf..c6c9b8773 100644 --- a/PIL/DcxImagePlugin.py +++ b/PIL/DcxImagePlugin.py @@ -66,6 +66,10 @@ class DcxImageFile(PcxImageFile): def n_frames(self): return len(self._offset) + @property + def is_animated(self): + return len(self._offset) > 1 + def seek(self, frame): if frame >= len(self._offset): raise EOFError("attempt to seek outside DCX directory") diff --git a/PIL/FliImagePlugin.py b/PIL/FliImagePlugin.py index 772140076..e3eaff970 100644 --- a/PIL/FliImagePlugin.py +++ b/PIL/FliImagePlugin.py @@ -90,6 +90,7 @@ class FliImageFile(ImageFile.ImageFile): self.__fp = self.fp self.__rewind = self.fp.tell() self._n_frames = None + self._is_animated = None self.seek(0) def _palette(self, palette, shift): @@ -122,6 +123,20 @@ class FliImageFile(ImageFile.ImageFile): self.seek(current) return self._n_frames + @property + def is_animated(self): + if self._is_animated is None: + current = self.tell() + + try: + self.seek(1) + self._is_animated = True + except EOFError: + self._is_animated = False + + self.seek(current) + return self._is_animated + def seek(self, frame): if frame == self.__frame: return diff --git a/PIL/GifImagePlugin.py b/PIL/GifImagePlugin.py index 35fb95164..4e8ba9f58 100644 --- a/PIL/GifImagePlugin.py +++ b/PIL/GifImagePlugin.py @@ -88,6 +88,7 @@ class GifImageFile(ImageFile.ImageFile): self.__fp = self.fp # FIXME: hack self.__rewind = self.fp.tell() self._n_frames = None + self._is_animated = None self._seek(0) # get ready to read first frame @property @@ -102,6 +103,20 @@ class GifImageFile(ImageFile.ImageFile): self.seek(current) return self._n_frames + @property + def is_animated(self): + if self._is_animated is None: + current = self.tell() + + try: + self.seek(1) + self._is_animated = True + except EOFError: + self._is_animated = False + + self.seek(current) + return self._is_animated + def seek(self, frame): if frame == self.__frame: return diff --git a/PIL/ImImagePlugin.py b/PIL/ImImagePlugin.py index 589928d0e..0a0a666ce 100644 --- a/PIL/ImImagePlugin.py +++ b/PIL/ImImagePlugin.py @@ -264,6 +264,10 @@ class ImImageFile(ImageFile.ImageFile): def n_frames(self): return self.info[FRAMES] + @property + def is_animated(self): + return self.info[FRAMES] > 1 + def seek(self, frame): if frame < 0 or frame >= self.info[FRAMES]: diff --git a/PIL/MicImagePlugin.py b/PIL/MicImagePlugin.py index aa41bf359..8e3e1b11d 100644 --- a/PIL/MicImagePlugin.py +++ b/PIL/MicImagePlugin.py @@ -75,6 +75,10 @@ class MicImageFile(TiffImagePlugin.TiffImageFile): def n_frames(self): return len(self.images) + @property + def is_animated(self): + return len(self.images) > 1 + def seek(self, frame): try: diff --git a/PIL/MpoImagePlugin.py b/PIL/MpoImagePlugin.py index 9d21728b9..b7e6c5756 100644 --- a/PIL/MpoImagePlugin.py +++ b/PIL/MpoImagePlugin.py @@ -66,6 +66,10 @@ class MpoImageFile(JpegImagePlugin.JpegImageFile): def n_frames(self): return self.__framecount + @property + def is_animated(self): + return self.__framecount > 1 + def seek(self, frame): if frame < 0 or frame >= self.__framecount: raise EOFError("no more images in MPO file") diff --git a/PIL/PsdImagePlugin.py b/PIL/PsdImagePlugin.py index d30695adb..030f5144c 100644 --- a/PIL/PsdImagePlugin.py +++ b/PIL/PsdImagePlugin.py @@ -136,6 +136,10 @@ class PsdImageFile(ImageFile.ImageFile): def n_frames(self): return len(self.layers) + @property + def is_animated(self): + return len(self.layers) > 1 + def seek(self, layer): # seek to given layer (1..max) if layer == self.frame: diff --git a/PIL/SpiderImagePlugin.py b/PIL/SpiderImagePlugin.py index 7de5156b1..f306538ae 100644 --- a/PIL/SpiderImagePlugin.py +++ b/PIL/SpiderImagePlugin.py @@ -158,6 +158,10 @@ class SpiderImageFile(ImageFile.ImageFile): def n_frames(self): return self._nimages + @property + def is_animated(self): + return self._nimages > 1 + # 1st image index is zero (although SPIDER imgnumber starts at 1) def tell(self): if self.imgnumber < 1: diff --git a/PIL/TiffImagePlugin.py b/PIL/TiffImagePlugin.py index 83bbd3f93..99c890459 100644 --- a/PIL/TiffImagePlugin.py +++ b/PIL/TiffImagePlugin.py @@ -650,6 +650,7 @@ class TiffImageFile(ImageFile.ImageFile): self.__fp = self.fp self._frame_pos = [] self._n_frames = None + self._is_animated = None if Image.DEBUG: print("*** TiffImageFile._open ***") @@ -671,6 +672,20 @@ class TiffImageFile(ImageFile.ImageFile): self.seek(current) return self._n_frames + @property + def is_animated(self): + if self._is_animated is None: + current = self.tell() + + try: + self.seek(1) + self._is_animated = True + except EOFError: + self._is_animated = False + + self.seek(current) + return self._is_animated + def seek(self, frame): "Select a given frame as current image" self._seek(max(frame, 0)) # Questionable backwards compatibility. diff --git a/Tests/test_file_dcx.py b/Tests/test_file_dcx.py index d59a5a785..7f804eb89 100644 --- a/Tests/test_file_dcx.py +++ b/Tests/test_file_dcx.py @@ -33,6 +33,7 @@ class TestFileDcx(PillowTestCase): def test_n_frames(self): im = Image.open(TEST_FILE) self.assertEqual(im.n_frames, 1) + self.assertFalse(im.is_animated) def test_eoferror(self): im = Image.open(TEST_FILE) diff --git a/Tests/test_file_fli.py b/Tests/test_file_fli.py index 1b95f793f..a0ea6af04 100644 --- a/Tests/test_file_fli.py +++ b/Tests/test_file_fli.py @@ -21,6 +21,7 @@ class TestFileFli(PillowTestCase): def test_n_frames(self): im = Image.open(test_file) self.assertEqual(im.n_frames, 1) + self.assertFalse(im.is_animated) def test_eoferror(self): im = Image.open(test_file) diff --git a/Tests/test_file_gif.py b/Tests/test_file_gif.py index a38c360c9..abf7d547b 100644 --- a/Tests/test_file_gif.py +++ b/Tests/test_file_gif.py @@ -137,9 +137,11 @@ class TestFileGif(PillowTestCase): def test_n_frames(self): im = Image.open(TEST_GIF) self.assertEqual(im.n_frames, 1) + self.assertFalse(im.is_animated) im = Image.open("Tests/images/iss634.gif") self.assertEqual(im.n_frames, 42) + self.assertTrue(im.is_animated) def test_eoferror(self): im = Image.open(TEST_GIF) diff --git a/Tests/test_file_im.py b/Tests/test_file_im.py index 76bf250a0..602db4b1d 100644 --- a/Tests/test_file_im.py +++ b/Tests/test_file_im.py @@ -18,6 +18,7 @@ class TestFileIm(PillowTestCase): def test_n_frames(self): im = Image.open(TEST_IM) self.assertEqual(im.n_frames, 1) + self.assertFalse(im.is_animated) def test_eoferror(self): im = Image.open(TEST_IM) diff --git a/Tests/test_file_mpo.py b/Tests/test_file_mpo.py index c5562097d..b1d3b7413 100644 --- a/Tests/test_file_mpo.py +++ b/Tests/test_file_mpo.py @@ -98,6 +98,7 @@ class TestFileMpo(PillowTestCase): def test_n_frames(self): im = Image.open("Tests/images/sugarshack.mpo") self.assertEqual(im.n_frames, 2) + self.assertTrue(im.is_animated) def test_eoferror(self): im = Image.open("Tests/images/sugarshack.mpo") diff --git a/Tests/test_file_psd.py b/Tests/test_file_psd.py index ea3856fce..6492787ec 100644 --- a/Tests/test_file_psd.py +++ b/Tests/test_file_psd.py @@ -19,9 +19,11 @@ class TestImagePsd(PillowTestCase): def test_n_frames(self): im = Image.open("Tests/images/hopper_merged.psd") self.assertEqual(im.n_frames, 1) + self.assertFalse(im.is_animated) im = Image.open(test_file) self.assertEqual(im.n_frames, 2) + self.assertTrue(im.is_animated) def test_eoferror(self): im = Image.open(test_file) diff --git a/Tests/test_file_spider.py b/Tests/test_file_spider.py index 7d24b2fe5..1ddecd365 100644 --- a/Tests/test_file_spider.py +++ b/Tests/test_file_spider.py @@ -45,6 +45,7 @@ class TestImageSpider(PillowTestCase): def test_n_frames(self): im = Image.open(TEST_FILE) self.assertEqual(im.n_frames, 1) + self.assertFalse(im.is_animated) def test_loadImageSeries(self): # Arrange diff --git a/Tests/test_file_tiff.py b/Tests/test_file_tiff.py index a88b49d7f..1a1bbaef7 100644 --- a/Tests/test_file_tiff.py +++ b/Tests/test_file_tiff.py @@ -153,9 +153,11 @@ class TestFileTiff(PillowTestCase): def test_n_frames(self): im = Image.open('Tests/images/multipage-lastframe.tif') self.assertEqual(im.n_frames, 1) + self.assertFalse(im.is_animated) im = Image.open('Tests/images/multipage.tiff') self.assertEqual(im.n_frames, 3) + self.assertTrue(im.is_animated) def test_eoferror(self): im = Image.open('Tests/images/multipage-lastframe.tif') From 593a910e92f983ba9d1652713f144e66e17aea59 Mon Sep 17 00:00:00 2001 From: hugovk Date: Tue, 30 Jun 2015 07:09:35 +0300 Subject: [PATCH 26/56] Unit test and image: convert hopper.png hopper.bmp --- Tests/images/hopper.bmp | Bin 0 -> 49290 bytes Tests/test_file_bmp.py | 16 ++++++++++++++++ 2 files changed, 16 insertions(+) create mode 100644 Tests/images/hopper.bmp diff --git a/Tests/images/hopper.bmp b/Tests/images/hopper.bmp new file mode 100644 index 0000000000000000000000000000000000000000..785700d2276bda53f9e8729253a15e138228e587 GIT binary patch literal 49290 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 z Date: Tue, 30 Jun 2015 18:02:48 +1000 Subject: [PATCH 27/56] Merged gifmaker into GifImagePlugin --- PIL/GifImagePlugin.py | 59 ++++++++++++++++++------- PIL/Image.py | 26 +++++++++-- Scripts/gifmaker.py | 100 ++---------------------------------------- 3 files changed, 68 insertions(+), 117 deletions(-) diff --git a/PIL/GifImagePlugin.py b/PIL/GifImagePlugin.py index 35fb95164..4cef91ee6 100644 --- a/PIL/GifImagePlugin.py +++ b/PIL/GifImagePlugin.py @@ -24,7 +24,7 @@ # See the README file for information on usage and redistribution. # -from PIL import Image, ImageFile, ImagePalette, _binary +from PIL import Image, ImageFile, ImagePalette, ImageChops, ImageSequence, _binary __version__ = "0.9" @@ -284,8 +284,10 @@ RAWMODE = { "P": "P", } +def _save_all(im, fp, filename): + _save(im, fp, filename, save_all=True) -def _save(im, fp, filename): +def _save(im, fp, filename, save_all=False): if _imaging_gif: # call external driver @@ -315,23 +317,47 @@ def _save(im, fp, filename): palette = None im.encoderinfo["optimize"] = im.encoderinfo.get("optimize", True) - header, used_palette_colors = getheader(im_out, palette, im.encoderinfo) - for s in header: - fp.write(s) + if save_all: + previous = None - flags = 0 + for im_frame in ImageSequence.Iterator(im_out): + # To specify duration, add the time in milliseconds to getdata(), + # e.g. getdata(im_frame, duration=1000) + if not previous: + # global header + for s in getheader(im_frame, palette, im.encoderinfo)[0] + getdata(im_frame): + fp.write(s) + else: + # delta frame + delta = ImageChops.subtract_modulo(im_frame, previous) + bbox = delta.getbbox() - if get_interlace(im): - flags = flags | 64 + if bbox: + # compress difference + for s in getdata(im_frame.crop(bbox), offset=bbox[:2]): + fp.write(s) + else: + # FIXME: what should we do in this case? + pass + previous = im_frame.copy() + else: + header = getheader(im_out, palette, im.encoderinfo)[0] + for s in header: + fp.write(s) - # local image header - get_local_header(fp, im, (0, 0), flags) + flags = 0 - im_out.encoderconfig = (8, get_interlace(im)) - ImageFile._save(im_out, fp, [("gif", (0, 0)+im.size, 0, - RAWMODE[im_out.mode])]) + if get_interlace(im): + flags = flags | 64 - fp.write(b"\0") # end of image data + # local image header + _get_local_header(fp, im, (0, 0), flags) + + im_out.encoderconfig = (8, get_interlace(im)) + ImageFile._save(im_out, fp, [("gif", (0, 0)+im.size, 0, + RAWMODE[im_out.mode])]) + + fp.write(b"\0") # end of image data fp.write(b";") # end of file @@ -354,7 +380,7 @@ def get_interlace(im): return interlace -def get_local_header(fp, im, offset, flags): +def _get_local_header(fp, im, offset, flags): transparent_color_exists = False try: transparency = im.encoderinfo["transparency"] @@ -577,7 +603,7 @@ def getdata(im, offset=(0, 0), **params): im.encoderinfo = params # local image header - get_local_header(fp, im, offset, 0) + _get_local_header(fp, im, offset, 0) ImageFile._save(im, fp, [("gif", (0, 0)+im.size, 0, RAWMODE[im.mode])]) @@ -594,6 +620,7 @@ def getdata(im, offset=(0, 0), **params): Image.register_open(GifImageFile.format, GifImageFile, _accept) Image.register_save(GifImageFile.format, _save) +Image.register_save_all(GifImageFile.format, _save_all) Image.register_extension(GifImageFile.format, ".gif") Image.register_mime(GifImageFile.format, "image/gif") diff --git a/PIL/Image.py b/PIL/Image.py index 3740b51c6..141d1b264 100644 --- a/PIL/Image.py +++ b/PIL/Image.py @@ -204,6 +204,7 @@ ID = [] OPEN = {} MIME = {} SAVE = {} +SAVE_ALL = {} EXTENSION = {} # -------------------------------------------------------------------- @@ -1669,6 +1670,10 @@ class Image(object): # may mutate self! self.load() + save_all = False + if 'save_all' in params: + save_all = params['save_all'] + del params['save_all'] self.encoderinfo = params self.encoderconfig = () @@ -1686,11 +1691,12 @@ class Image(object): except KeyError: raise KeyError(ext) # unknown extension - try: - save_handler = SAVE[format.upper()] - except KeyError: + if format.upper() not in SAVE: init() - save_handler = SAVE[format.upper()] # unknown format + if save_all: + save_handler = SAVE_ALL[format.upper()] + else: + save_handler = SAVE[format.upper()] if isPath(fp): fp = builtins.open(fp, "wb") @@ -2469,6 +2475,18 @@ def register_save(id, driver): SAVE[id.upper()] = driver +def register_save_all(id, driver): + """ + Registers an image function to save all the frames + of a multiframe format. This function should not be + used in application code. + + :param id: An image format identifier. + :param driver: A function to save images in this format. + """ + SAVE_ALL[id.upper()] = driver + + def register_extension(id, extension): """ Registers an image extension. This function should not be diff --git a/Scripts/gifmaker.py b/Scripts/gifmaker.py index bf162eb2f..c0679ca79 100644 --- a/Scripts/gifmaker.py +++ b/Scripts/gifmaker.py @@ -14,104 +14,9 @@ # See the README file for information on usage and redistribution. # -# -# For special purposes, you can import this module and call -# the makedelta or compress functions yourself. For example, -# if you have an application that generates a sequence of -# images, you can convert it to a GIF animation using some- -# thing like the following code: -# -# import Image -# import gifmaker -# -# sequence = [] -# -# # generate sequence -# for i in range(100): -# im = -# sequence.append(im) -# -# # write GIF animation -# fp = open("out.gif", "wb") -# gifmaker.makedelta(fp, sequence) -# fp.close() -# -# Alternatively, use an iterator to generate the sequence, and -# write data directly to a socket. Or something... -# - from __future__ import print_function -from PIL import Image, ImageChops, ImageSequence - -from PIL.GifImagePlugin import getheader, getdata - -# -------------------------------------------------------------------- -# straightforward delta encoding - - -def makedelta(fp, sequence): - """Convert list of image frames to a GIF animation file""" - - frames = 0 - - previous = None - - for im in sequence: - - # To specify duration, add the time in milliseconds to getdata(), - # e.g. getdata(im, duration=1000) - - if not previous: - - # global header - for s in getheader(im)[0] + getdata(im): - fp.write(s) - - else: - - # delta frame - delta = ImageChops.subtract_modulo(im, previous) - - bbox = delta.getbbox() - - if bbox: - - # compress difference - for s in getdata(im.crop(bbox), offset=bbox[:2]): - fp.write(s) - - else: - # FIXME: what should we do in this case? - pass - - previous = im.copy() - - frames += 1 - - fp.write(b";") - - return frames - -# -------------------------------------------------------------------- -# main hack - - -def compress(infile, outfile): - - # open input image, and force loading of first frame - im = Image.open(infile) - im.load() - - # open output file - fp = open(outfile, "wb") - - seq = ImageSequence.Iterator(im) - - makedelta(fp, seq) - - fp.close() - +from PIL import Image if __name__ == "__main__": @@ -122,4 +27,5 @@ if __name__ == "__main__": print("Usage: gifmaker infile outfile") sys.exit(1) - compress(sys.argv[1], sys.argv[2]) + im = Image.open(sys.argv[1]) + im.save(sys.argv[2], save_all=True) From 7227b4d01d76cc210b2f755808db5c1dd3d0bc2c Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Tue, 30 Jun 2015 18:07:23 +1000 Subject: [PATCH 28/56] Added test --- Tests/test_file_gif.py | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/Tests/test_file_gif.py b/Tests/test_file_gif.py index a38c360c9..169707499 100644 --- a/Tests/test_file_gif.py +++ b/Tests/test_file_gif.py @@ -71,6 +71,14 @@ class TestFileGif(PillowTestCase): self.assert_image_similar(reread.convert('RGB'), hopper(), 50) + def test_roundtrip_save_all(self): + out = self.tempfile('temp.gif') + im = hopper() + im.save(out, save_all=True) + reread = Image.open(out) + + self.assert_image_similar(reread.convert('RGB'), im, 50) + def test_palette_handling(self): # see https://github.com/python-pillow/Pillow/issues/513 From 445a8c06fce647249e6a832f595fcdfff1743ad0 Mon Sep 17 00:00:00 2001 From: Alex Clark Date: Tue, 30 Jun 2015 08:04:35 -0400 Subject: [PATCH 29/56] Bump --- PIL/__init__.py | 2 +- _imaging.c | 2 +- setup.py | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/PIL/__init__.py b/PIL/__init__.py index 64f8953d6..ee3581d65 100644 --- a/PIL/__init__.py +++ b/PIL/__init__.py @@ -12,7 +12,7 @@ # ;-) VERSION = '1.1.7' # PIL version -PILLOW_VERSION = '2.9.0.dev1' # Pillow +PILLOW_VERSION = '2.9.0.dev2' # Pillow _plugins = ['BmpImagePlugin', 'BufrStubImagePlugin', diff --git a/_imaging.c b/_imaging.c index 646fedf8b..932135980 100644 --- a/_imaging.c +++ b/_imaging.c @@ -71,7 +71,7 @@ * See the README file for information on usage and redistribution. */ -#define PILLOW_VERSION "2.9.0.dev1" +#define PILLOW_VERSION "2.9.0.dev2" #include "Python.h" diff --git a/setup.py b/setup.py index 7e9e3a37e..388100d78 100644 --- a/setup.py +++ b/setup.py @@ -90,7 +90,7 @@ except (ImportError, OSError): NAME = 'Pillow' -PILLOW_VERSION = '2.9.0.dev1' +PILLOW_VERSION = '2.9.0.dev2' TCL_ROOT = None JPEG_ROOT = None JPEG2K_ROOT = None From 4fbea3e553d304544228dd7e7a215f772640b021 Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Wed, 1 Jul 2015 09:18:05 +1000 Subject: [PATCH 30/56] Added multiframe GIF test --- PIL/GifImagePlugin.py | 27 +++++++++++++++++---------- Tests/test_file_gif.py | 10 ++++++++++ 2 files changed, 27 insertions(+), 10 deletions(-) diff --git a/PIL/GifImagePlugin.py b/PIL/GifImagePlugin.py index 51fd11999..08567fcd0 100644 --- a/PIL/GifImagePlugin.py +++ b/PIL/GifImagePlugin.py @@ -299,9 +299,22 @@ RAWMODE = { "P": "P", } + +def _convert_mode(im): + # convert on the fly (EXPERIMENTAL -- I'm not sure PIL + # should automatically convert images on save...) + if Image.getmodebase(im.mode) == "RGB": + palette_size = 256 + if im.palette: + palette_size = len(im.palette.getdata()[1]) // 3 + return im.convert("P", palette=1, colors=palette_size) + return im.convert("L") + + def _save_all(im, fp, filename): _save(im, fp, filename, save_all=True) + def _save(im, fp, filename, save_all=False): if _imaging_gif: @@ -315,15 +328,7 @@ def _save(im, fp, filename, save_all=False): if im.mode in RAWMODE: im_out = im.copy() else: - # convert on the fly (EXPERIMENTAL -- I'm not sure PIL - # should automatically convert images on save...) - if Image.getmodebase(im.mode) == "RGB": - palette_size = 256 - if im.palette: - palette_size = len(im.palette.getdata()[1]) // 3 - im_out = im.convert("P", palette=1, colors=palette_size) - else: - im_out = im.convert("L") + im_out = _convert_mode(im) # header try: @@ -335,7 +340,9 @@ def _save(im, fp, filename, save_all=False): if save_all: previous = None - for im_frame in ImageSequence.Iterator(im_out): + for im_frame in ImageSequence.Iterator(im): + im_frame = _convert_mode(im_frame) + # To specify duration, add the time in milliseconds to getdata(), # e.g. getdata(im_frame, duration=1000) if not previous: diff --git a/Tests/test_file_gif.py b/Tests/test_file_gif.py index 1141e0372..666e1f16d 100644 --- a/Tests/test_file_gif.py +++ b/Tests/test_file_gif.py @@ -72,6 +72,7 @@ class TestFileGif(PillowTestCase): self.assert_image_similar(reread.convert('RGB'), hopper(), 50) def test_roundtrip_save_all(self): + # Single frame image out = self.tempfile('temp.gif') im = hopper() im.save(out, save_all=True) @@ -79,6 +80,15 @@ class TestFileGif(PillowTestCase): self.assert_image_similar(reread.convert('RGB'), im, 50) + # Multiframe image + im = Image.open("Tests/images/dispose_bgnd.gif") + + out = self.tempfile('temp.gif') + im.save(out, save_all=True) + reread = Image.open(out) + + self.assertEqual(im.n_frames, 5) + def test_palette_handling(self): # see https://github.com/python-pillow/Pillow/issues/513 From abe25b71914bca5a7d23a3316c6c8ce6da7a887f Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Wed, 1 Jul 2015 09:19:28 +1000 Subject: [PATCH 31/56] Rearranged format handler fetching --- PIL/Image.py | 9 ++------- 1 file changed, 2 insertions(+), 7 deletions(-) diff --git a/PIL/Image.py b/PIL/Image.py index 141d1b264..62ae9b5ac 100644 --- a/PIL/Image.py +++ b/PIL/Image.py @@ -1682,14 +1682,9 @@ class Image(object): ext = os.path.splitext(filename)[1].lower() if not format: - try: - format = EXTENSION[ext] - except KeyError: + if ext not in EXTENSION: init() - try: - format = EXTENSION[ext] - except KeyError: - raise KeyError(ext) # unknown extension + format = EXTENSION[ext] if format.upper() not in SAVE: init() From 63a32a9c5b13d7cbe067831c1196b3929840a907 Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Wed, 1 Jul 2015 15:47:47 +1000 Subject: [PATCH 32/56] Added test for GimpPaletteFile --- MANIFEST.in | 1 + Tests/images/test.gpl | 4 ++++ Tests/test_file_gimppalette.py | 19 +++++++++++++++++++ 3 files changed, 24 insertions(+) create mode 100644 Tests/images/test.gpl create mode 100644 Tests/test_file_gimppalette.py diff --git a/MANIFEST.in b/MANIFEST.in index ad5bc3bed..70ca05b01 100644 --- a/MANIFEST.in +++ b/MANIFEST.in @@ -29,6 +29,7 @@ recursive-include Tests *.eps recursive-include Tests *.fli recursive-include Tests *.ggr recursive-include Tests *.gif +recursive-include Tests *.gpl recursive-include Tests *.gnuplot recursive-include Tests *.html recursive-include Tests *.icc diff --git a/Tests/images/test.gpl b/Tests/images/test.gpl new file mode 100644 index 000000000..7436a3099 --- /dev/null +++ b/Tests/images/test.gpl @@ -0,0 +1,4 @@ +GIMP Palette +Name: Test +Columns: 0 +# diff --git a/Tests/test_file_gimppalette.py b/Tests/test_file_gimppalette.py new file mode 100644 index 000000000..bb18e8026 --- /dev/null +++ b/Tests/test_file_gimppalette.py @@ -0,0 +1,19 @@ +from helper import unittest, PillowTestCase + +from PIL.GimpPaletteFile import GimpPaletteFile + + +class TestImage(PillowTestCase): + + def test_sanity(self): + with open('Tests/images/test.gpl', 'rb') as fp: + GimpPaletteFile(fp) + + with open('Tests/images/hopper.jpg', 'rb') as fp: + self.assertRaises(SyntaxError, lambda: GimpPaletteFile(fp)) + + +if __name__ == '__main__': + unittest.main() + +# End of file From 6a85d5184c5556a35dfbf5c0d3ed3d0903781193 Mon Sep 17 00:00:00 2001 From: Hugo van Kemenade Date: Wed, 1 Jul 2015 09:09:26 +0300 Subject: [PATCH 33/56] Update CHANGES.rst [CI skip] --- CHANGES.rst | 3 +++ 1 file changed, 3 insertions(+) diff --git a/CHANGES.rst b/CHANGES.rst index 89655a630..80bc189d8 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -4,6 +4,9 @@ Changelog (Pillow) 2.9.0 (Unreleased) ------------------ +- Added test for GimpPaletteFile #1324 + [radarhere] + - Fixed ValueError in Python 2.6 #1315 #1316 [cgohlke, radarhere] From 0c7cf4070e9abc078736837674e57615d346917f Mon Sep 17 00:00:00 2001 From: hugovk Date: Wed, 1 Jul 2015 09:56:52 +0300 Subject: [PATCH 34/56] Report failures sooner --- appveyor.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/appveyor.yml b/appveyor.yml index 179c329a8..6280a97d9 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -24,3 +24,5 @@ test_script: - cd c:\pillow - '%PYTHON%\Scripts\pip.exe install nose' - '%PYTHON%\python.exe test-installed.py' +matrix: + fast_finish: true From dba0e7960afd59bb7348bd36260b361939901a2c Mon Sep 17 00:00:00 2001 From: hugovk Date: Wed, 1 Jul 2015 14:28:34 +0300 Subject: [PATCH 35/56] More GIMP palette tests --- Tests/images/bad_palette_entry.gpl | 12 ++++++++++++ Tests/images/bad_palette_file.gpl | 12 ++++++++++++ Tests/images/custom_gimp_palette.gpl | 12 ++++++++++++ Tests/test_file_gimppalette.py | 17 +++++++++++++++++ 4 files changed, 53 insertions(+) create mode 100644 Tests/images/bad_palette_entry.gpl create mode 100644 Tests/images/bad_palette_file.gpl create mode 100644 Tests/images/custom_gimp_palette.gpl diff --git a/Tests/images/bad_palette_entry.gpl b/Tests/images/bad_palette_entry.gpl new file mode 100644 index 000000000..162037184 --- /dev/null +++ b/Tests/images/bad_palette_entry.gpl @@ -0,0 +1,12 @@ +GIMP Palette +Name: badpaletteentry +Columns: 4 +# + 0 0 0 Index 3 + 65 38 +103 62 49 Index 6 + 79 73 72 Index 7 +114 101 97 Index 8 +208 127 100 Index 9 +151 144 142 Index 10 +221 207 199 Index 11 diff --git a/Tests/images/bad_palette_file.gpl b/Tests/images/bad_palette_file.gpl new file mode 100644 index 000000000..c366cc8db --- /dev/null +++ b/Tests/images/bad_palette_file.gpl @@ -0,0 +1,12 @@ +GIMP Palette +Name: badpalettefile +Columns: 4 +# + 0 0 0 Index 3 +01234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789 +103 62 49 Index 6 + 79 73 72 Index 7 +114 101 97 Index 8 +208 127 100 Index 9 +151 144 142 Index 10 +221 207 199 Index 11 diff --git a/Tests/images/custom_gimp_palette.gpl b/Tests/images/custom_gimp_palette.gpl new file mode 100644 index 000000000..08ea70028 --- /dev/null +++ b/Tests/images/custom_gimp_palette.gpl @@ -0,0 +1,12 @@ +GIMP Palette +Name: custompalette +Columns: 4 +# + 0 0 0 Index 3 + 65 38 30 Index 4 +103 62 49 Index 6 + 79 73 72 Index 7 +114 101 97 Index 8 +208 127 100 Index 9 +151 144 142 Index 10 +221 207 199 Index 11 diff --git a/Tests/test_file_gimppalette.py b/Tests/test_file_gimppalette.py index bb18e8026..dfa2845ac 100644 --- a/Tests/test_file_gimppalette.py +++ b/Tests/test_file_gimppalette.py @@ -12,6 +12,23 @@ class TestImage(PillowTestCase): with open('Tests/images/hopper.jpg', 'rb') as fp: self.assertRaises(SyntaxError, lambda: GimpPaletteFile(fp)) + with open('Tests/images/bad_palette_file.gpl', 'rb') as fp: + self.assertRaises(SyntaxError, lambda: GimpPaletteFile(fp)) + + with open('Tests/images/bad_palette_entry.gpl', 'rb') as fp: + self.assertRaises(ValueError, lambda: GimpPaletteFile(fp)) + + def test_get_palette(self): + # Arrange + with open('Tests/images/custom_gimp_palette.gpl', 'rb') as fp: + palette_file = GimpPaletteFile(fp) + + # Act + palette, mode = palette_file.getpalette() + + # Assert + self.assertEqual(mode, "RGB") + if __name__ == '__main__': unittest.main() From 80672b61e8596c7d6dab7b4ef3ef1e4783902f51 Mon Sep 17 00:00:00 2001 From: Alex Clark Date: Wed, 1 Jul 2015 14:33:56 -0400 Subject: [PATCH 36/56] This is 2.9.0 Fixes #1174 --- PIL/__init__.py | 2 +- _imaging.c | 2 +- setup.py | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/PIL/__init__.py b/PIL/__init__.py index ee3581d65..179230d7f 100644 --- a/PIL/__init__.py +++ b/PIL/__init__.py @@ -12,7 +12,7 @@ # ;-) VERSION = '1.1.7' # PIL version -PILLOW_VERSION = '2.9.0.dev2' # Pillow +PILLOW_VERSION = '2.9.0' # Pillow _plugins = ['BmpImagePlugin', 'BufrStubImagePlugin', diff --git a/_imaging.c b/_imaging.c index 932135980..1d0c3b7fb 100644 --- a/_imaging.c +++ b/_imaging.c @@ -71,7 +71,7 @@ * See the README file for information on usage and redistribution. */ -#define PILLOW_VERSION "2.9.0.dev2" +#define PILLOW_VERSION "2.9.0" #include "Python.h" diff --git a/setup.py b/setup.py index 388100d78..9062a21f6 100644 --- a/setup.py +++ b/setup.py @@ -90,7 +90,7 @@ except (ImportError, OSError): NAME = 'Pillow' -PILLOW_VERSION = '2.9.0.dev2' +PILLOW_VERSION = '2.9.0' TCL_ROOT = None JPEG_ROOT = None JPEG2K_ROOT = None From efa0fa1b4f4775a109bed56ec1875a8bac6c5944 Mon Sep 17 00:00:00 2001 From: hugovk Date: Wed, 1 Jul 2015 22:51:38 +0300 Subject: [PATCH 37/56] More ImageFont tests --- Tests/test_imagefont.py | 50 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 50 insertions(+) diff --git a/Tests/test_imagefont.py b/Tests/test_imagefont.py index 8414584f9..fd1c8af9d 100644 --- a/Tests/test_imagefont.py +++ b/Tests/test_imagefont.py @@ -256,6 +256,34 @@ try: # Check boxes a and b are same size self.assertEqual(box_size_a, box_size_b) + def test_rotated_transposed_font_get_mask(self): + # Arrange + text = "mask this" + font = ImageFont.truetype(FONT_PATH, FONT_SIZE) + orientation = Image.ROTATE_90 + transposed_font = ImageFont.TransposedFont( + font, orientation=orientation) + + # Act + mask = transposed_font.getmask(text) + + # Assert + self.assertEqual(mask.size, (13, 108)) + + def test_unrotated_transposed_font_get_mask(self): + # Arrange + text = "mask this" + font = ImageFont.truetype(FONT_PATH, FONT_SIZE) + orientation = None + transposed_font = ImageFont.TransposedFont( + font, orientation=orientation) + + # Act + mask = transposed_font.getmask(text) + + # Assert + self.assertEqual(mask.size, (108, 13)) + def test_free_type_font_get_name(self): # Arrange font = ImageFont.truetype(FONT_PATH, FONT_SIZE) @@ -278,6 +306,28 @@ try: self.assertIsInstance(descent, int) self.assertEqual((ascent, descent), (16, 4)) # too exact check? + def test_free_type_font_get_offset(self): + # Arrange + font = ImageFont.truetype(FONT_PATH, FONT_SIZE) + text = "offset this" + + # Act + offset = font.getoffset(text) + + # Assert + self.assertEqual(offset, (0, 3)) + + def test_free_type_font_get_mask(self): + # Arrange + font = ImageFont.truetype(FONT_PATH, FONT_SIZE) + text = "mask this" + + # Act + mask = font.getmask(text) + + # Assert + self.assertEqual(mask.size, (108, 13)) + def test_load_path_not_found(self): # Arrange filename = "somefilenamethatdoesntexist.ttf" From bfa6b0774143ec891b8460cb339c6aa8cfea8f56 Mon Sep 17 00:00:00 2001 From: hugovk Date: Wed, 1 Jul 2015 22:53:18 +0300 Subject: [PATCH 38/56] flake8 --- Tests/test_imagefont.py | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/Tests/test_imagefont.py b/Tests/test_imagefont.py index fd1c8af9d..1fd70b3d8 100644 --- a/Tests/test_imagefont.py +++ b/Tests/test_imagefont.py @@ -182,7 +182,10 @@ try: ttf = ImageFont.truetype(FONT_PATH, FONT_SIZE) # Act/Assert - self.assertRaises(AssertionError, lambda: draw.multiline_text((0, 0), TEST_TEXT, font=ttf, align="unknown")) + self.assertRaises(AssertionError, + lambda: draw.multiline_text((0, 0), TEST_TEXT, + font=ttf, + align="unknown")) def test_multiline_size(self): ttf = ImageFont.truetype(FONT_PATH, FONT_SIZE) @@ -199,7 +202,8 @@ try: draw = ImageDraw.Draw(im) self.assertEqual(draw.textsize("longest line", font=ttf)[0], - draw.multiline_textsize("longest line\nline", font=ttf)[0]) + draw.multiline_textsize("longest line\nline", + font=ttf)[0]) def test_multiline_spacing(self): ttf = ImageFont.truetype(FONT_PATH, FONT_SIZE) From 93eb15bec7920adfd08a47125a6a92f357182b98 Mon Sep 17 00:00:00 2001 From: Alex Clark Date: Thu, 2 Jul 2015 05:20:15 -0400 Subject: [PATCH 39/56] Commence 3.0.0.dev0 --- PIL/__init__.py | 2 +- _imaging.c | 2 +- setup.py | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/PIL/__init__.py b/PIL/__init__.py index 179230d7f..665474f9b 100644 --- a/PIL/__init__.py +++ b/PIL/__init__.py @@ -12,7 +12,7 @@ # ;-) VERSION = '1.1.7' # PIL version -PILLOW_VERSION = '2.9.0' # Pillow +PILLOW_VERSION = '3.0.0.dev0' # Pillow _plugins = ['BmpImagePlugin', 'BufrStubImagePlugin', diff --git a/_imaging.c b/_imaging.c index 1d0c3b7fb..895faac54 100644 --- a/_imaging.c +++ b/_imaging.c @@ -71,7 +71,7 @@ * See the README file for information on usage and redistribution. */ -#define PILLOW_VERSION "2.9.0" +#define PILLOW_VERSION "3.0.0.dev0" #include "Python.h" diff --git a/setup.py b/setup.py index 9062a21f6..ba95cdd58 100644 --- a/setup.py +++ b/setup.py @@ -90,7 +90,7 @@ except (ImportError, OSError): NAME = 'Pillow' -PILLOW_VERSION = '2.9.0' +PILLOW_VERSION = '3.0.0.dev0' TCL_ROOT = None JPEG_ROOT = None JPEG2K_ROOT = None From f0e0e7a167b52f4e6156200ddc67c97554cbd120 Mon Sep 17 00:00:00 2001 From: hugovk Date: Fri, 3 Jul 2015 07:57:33 +0300 Subject: [PATCH 40/56] Update AppVeyor build number --- RELEASING.md | 8 ++++---- appveyor.yml | 2 +- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/RELEASING.md b/RELEASING.md index d9c4d761e..fbfd8f2b4 100644 --- a/RELEASING.md +++ b/RELEASING.md @@ -7,9 +7,9 @@ Released quarterly on the first day of January, April, July, October. * [ ] Open a release ticket e.g. https://github.com/python-pillow/Pillow/issues/1174 * [ ] Develop and prepare release in ``master`` branch. * [ ] Check [Travis CI](https://travis-ci.org/python-pillow/Pillow) to confirm passing tests in ``master`` branch. -* [ ] In compliance with https://www.python.org/dev/peps/pep-0440/, update version identifier in: +* [ ] In compliance with https://www.python.org/dev/peps/pep-0440/, update version identifier in: ``` - PIL/__init__.py setup.py _imaging.c + PIL/__init__.py setup.py _imaging.c appveyor.yml ``` * [ ] Update `CHANGES.rst`. * [ ] Run pre-release check via `make pre`. @@ -32,14 +32,14 @@ Released quarterly on the first day of January, April, July, October. Released as needed for security, installation or critical bug fixes. * [ ] Make necessary changes in ``master`` branch. -* [ ] Update `CHANGES.rst`. +* [ ] Update `CHANGES.rst`. * [ ] Cherry pick individual commits from ``master`` branch to release branch e.g. ``2.9.x``. * [ ] Check [Travis CI](https://travis-ci.org/python-pillow/Pillow) to confirm passing tests in release branch e.g. ``2.9.x``. * [ ] Checkout release branch e.g.: ``` git checkout -t remotes/origin/2.9.x ``` -* [ ] In compliance with https://www.python.org/dev/peps/pep-0440/, update version identifier in: +* [ ] In compliance with https://www.python.org/dev/peps/pep-0440/, update version identifier in: ``` PIL/__init__.py setup.py _imaging.c ``` diff --git a/appveyor.yml b/appveyor.yml index 6280a97d9..6e953c46e 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -1,4 +1,4 @@ -version: 2.9.pre.{build} +version: 3.0.pre.{build} clone_folder: c:\pillow init: - ECHO %PYTHON% From a06b59bd52f8bbcd311b6a96d6d6ab4a1229331f Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Fri, 3 Jul 2015 15:03:25 +1000 Subject: [PATCH 41/56] Added various tests --- Tests/check_imaging_leaks.py | 2 +- Tests/test_cffi.py | 12 ++++++++++++ Tests/test_file_bmp.py | 6 +++++- Tests/test_file_bufrstub.py | 15 +++++++++++++++ Tests/test_file_cur.py | 3 +++ Tests/test_file_dcx.py | 4 ++++ Tests/test_file_eps.py | 3 +++ Tests/test_file_fitsstub.py | 15 +++++++++++++++ Tests/test_file_fli.py | 5 ++++- Tests/test_file_fpx.py | 15 +++++++++++++++ Tests/test_file_gbr.py | 15 +++++++++++++++ Tests/test_file_gif.py | 3 +++ Tests/test_file_gribstub.py | 15 +++++++++++++++ Tests/test_file_hdf5stub.py | 15 +++++++++++++++ Tests/test_file_ico.py | 6 +++++- Tests/test_file_im.py | 6 +++++- Tests/test_file_jpeg2k.py | 5 ++++- Tests/test_file_mcidas.py | 15 +++++++++++++++ Tests/test_file_mic.py | 15 +++++++++++++++ Tests/test_file_msp.py | 5 ++++- Tests/test_file_pcx.py | 5 ++++- Tests/test_file_pdf.py | 6 ++++++ Tests/test_file_png.py | 3 +++ Tests/test_file_psd.py | 5 ++++- Tests/test_file_sgi.py | 5 ++++- Tests/test_file_sun.py | 4 +++- Tests/test_file_tga.py | 19 +++++++++++++++++++ Tests/test_file_webp.py | 5 +++++ Tests/test_file_xpm.py | 5 ++++- Tests/test_font_bdf.py | 4 ++++ Tests/test_font_pcf.py | 4 ++++ Tests/test_image_filter.py | 6 ++++++ Tests/test_image_mode.py | 2 ++ Tests/test_imagecolor.py | 2 ++ Tests/test_imagedraw.py | 5 +++++ Tests/test_imagefile.py | 3 +++ Tests/test_imagesequence.py | 2 ++ Tests/test_olefileio.py | 27 +++++++++++---------------- 38 files changed, 264 insertions(+), 28 deletions(-) create mode 100644 Tests/test_file_bufrstub.py create mode 100644 Tests/test_file_fitsstub.py create mode 100644 Tests/test_file_fpx.py create mode 100644 Tests/test_file_gbr.py create mode 100644 Tests/test_file_gribstub.py create mode 100644 Tests/test_file_hdf5stub.py create mode 100644 Tests/test_file_mcidas.py create mode 100644 Tests/test_file_mic.py diff --git a/Tests/check_imaging_leaks.py b/Tests/check_imaging_leaks.py index c6d99b8d1..e79429f8f 100644 --- a/Tests/check_imaging_leaks.py +++ b/Tests/check_imaging_leaks.py @@ -3,7 +3,7 @@ from __future__ import division from helper import unittest, PillowTestCase import sys -from PIL import Image, ImageFilter +from PIL import Image min_iterations = 100 max_iterations = 10000 diff --git a/Tests/test_cffi.py b/Tests/test_cffi.py index cea0db093..361b59a39 100644 --- a/Tests/test_cffi.py +++ b/Tests/test_cffi.py @@ -61,6 +61,10 @@ class TestCffi(PillowTestCase): for y in range(0, h, 10): self.assertEqual(access[(x, y)], caccess[(x, y)]) + # Access an out-of-range pixel + self.assertRaises(ValueError, + lambda: access[(access.xsize+1, access.ysize+1)]) + def test_get_vs_c(self): rgb = hopper('RGB') rgb.load() @@ -103,6 +107,14 @@ class TestCffi(PillowTestCase): access[(x, y)] = color self.assertEqual(color, caccess[(x, y)]) + # Attempt to set the value on a read-only image + access = PyAccess.new(im, True) + try: + access[(0, 0)] = color + except ValueError: + return + self.fail("Putpixel did not fail on a read-only image") + def test_set_vs_c(self): rgb = hopper('RGB') rgb.load() diff --git a/Tests/test_file_bmp.py b/Tests/test_file_bmp.py index fd0f29470..15d470c90 100644 --- a/Tests/test_file_bmp.py +++ b/Tests/test_file_bmp.py @@ -1,6 +1,6 @@ from helper import unittest, PillowTestCase, hopper -from PIL import Image +from PIL import Image, BmpImagePlugin import io @@ -25,6 +25,10 @@ class TestFileBmp(PillowTestCase): self.roundtrip(hopper("P")) self.roundtrip(hopper("RGB")) + def test_invalid_file(self): + with open("Tests/images/flower.jpg", "rb") as fp: + self.assertRaises(SyntaxError, lambda: BmpImagePlugin.BmpImageFile(fp)) + def test_save_to_bytes(self): output = io.BytesIO() im = hopper() diff --git a/Tests/test_file_bufrstub.py b/Tests/test_file_bufrstub.py new file mode 100644 index 000000000..8cf496533 --- /dev/null +++ b/Tests/test_file_bufrstub.py @@ -0,0 +1,15 @@ +from helper import unittest, PillowTestCase + +from PIL import BufrStubImagePlugin + + +class TestFileBufrStub(PillowTestCase): + + def test_invalid_file(self): + self.assertRaises(SyntaxError, lambda: BufrStubImagePlugin.BufrStubImageFile("Tests/images/flower.jpg")) + + +if __name__ == '__main__': + unittest.main() + +# End of file diff --git a/Tests/test_file_cur.py b/Tests/test_file_cur.py index 07bf3a750..d9acb5f78 100644 --- a/Tests/test_file_cur.py +++ b/Tests/test_file_cur.py @@ -20,6 +20,9 @@ class TestFileCur(PillowTestCase): self.assertEqual(im.getpixel((11, 1)), (253, 254, 254, 1)) self.assertEqual(im.getpixel((16, 16)), (84, 87, 86, 255)) + def test_invalid_file(self): + self.assertRaises(SyntaxError, lambda: CurImagePlugin.CurImageFile("Tests/images/flower.jpg")) + if __name__ == '__main__': unittest.main() diff --git a/Tests/test_file_dcx.py b/Tests/test_file_dcx.py index 7f804eb89..62760fd0c 100644 --- a/Tests/test_file_dcx.py +++ b/Tests/test_file_dcx.py @@ -20,6 +20,10 @@ class TestFileDcx(PillowTestCase): orig = hopper() self.assert_image_equal(im, orig) + def test_invalid_file(self): + with open("Tests/images/flower.jpg", "rb") as fp: + self.assertRaises(SyntaxError, lambda: DcxImagePlugin.DcxImageFile(fp)) + def test_tell(self): # Arrange im = Image.open(TEST_FILE) diff --git a/Tests/test_file_eps.py b/Tests/test_file_eps.py index 08d2d875a..931b788a3 100644 --- a/Tests/test_file_eps.py +++ b/Tests/test_file_eps.py @@ -51,6 +51,9 @@ class TestFileEps(PillowTestCase): self.assertEqual(image2_scale2.size, (720, 504)) self.assertEqual(image2_scale2.format, "EPS") + def test_invalid_file(self): + self.assertRaises(SyntaxError, lambda: EpsImagePlugin.EpsImageFile("Tests/images/flower.jpg")) + def test_file_object(self): # issue 479 image1 = Image.open(file1) diff --git a/Tests/test_file_fitsstub.py b/Tests/test_file_fitsstub.py new file mode 100644 index 000000000..189d002d6 --- /dev/null +++ b/Tests/test_file_fitsstub.py @@ -0,0 +1,15 @@ +from helper import unittest, PillowTestCase + +from PIL import FitsStubImagePlugin + + +class TestFileFitsStub(PillowTestCase): + + def test_invalid_file(self): + self.assertRaises(SyntaxError, lambda: FitsStubImagePlugin.FITSStubImageFile("Tests/images/flower.jpg")) + + +if __name__ == '__main__': + unittest.main() + +# End of file diff --git a/Tests/test_file_fli.py b/Tests/test_file_fli.py index a0ea6af04..daaebdc69 100644 --- a/Tests/test_file_fli.py +++ b/Tests/test_file_fli.py @@ -1,6 +1,6 @@ from helper import unittest, PillowTestCase -from PIL import Image +from PIL import Image, FliImagePlugin # sample ppm stream # created as an export of a palette image from Gimp2.6 @@ -18,6 +18,9 @@ class TestFileFli(PillowTestCase): self.assertEqual(im.size, (128, 128)) self.assertEqual(im.format, "FLI") + def test_invalid_file(self): + self.assertRaises(SyntaxError, lambda: FliImagePlugin.FliImageFile("Tests/images/flower.jpg")) + def test_n_frames(self): im = Image.open(test_file) self.assertEqual(im.n_frames, 1) diff --git a/Tests/test_file_fpx.py b/Tests/test_file_fpx.py new file mode 100644 index 000000000..4b3756fb9 --- /dev/null +++ b/Tests/test_file_fpx.py @@ -0,0 +1,15 @@ +from helper import unittest, PillowTestCase + +from PIL import FpxImagePlugin + + +class TestFileFpx(PillowTestCase): + + def test_invalid_file(self): + self.assertRaises(SyntaxError, lambda: FpxImagePlugin.FpxImageFile("Tests/images/flower.jpg")) + + +if __name__ == '__main__': + unittest.main() + +# End of file diff --git a/Tests/test_file_gbr.py b/Tests/test_file_gbr.py new file mode 100644 index 000000000..ecec0189b --- /dev/null +++ b/Tests/test_file_gbr.py @@ -0,0 +1,15 @@ +from helper import unittest, PillowTestCase + +from PIL import GbrImagePlugin + + +class TestFileGbr(PillowTestCase): + + def test_invalid_file(self): + self.assertRaises(SyntaxError, lambda: GbrImagePlugin.GbrImageFile("Tests/images/flower.jpg")) + + +if __name__ == '__main__': + unittest.main() + +# End of file diff --git a/Tests/test_file_gif.py b/Tests/test_file_gif.py index 666e1f16d..33e04cb5e 100644 --- a/Tests/test_file_gif.py +++ b/Tests/test_file_gif.py @@ -25,6 +25,9 @@ class TestFileGif(PillowTestCase): self.assertEqual(im.size, (128, 128)) self.assertEqual(im.format, "GIF") + def test_invalid_file(self): + self.assertRaises(SyntaxError, lambda: GifImagePlugin.GifImageFile("Tests/images/flower.jpg")) + def test_optimize(self): from io import BytesIO diff --git a/Tests/test_file_gribstub.py b/Tests/test_file_gribstub.py new file mode 100644 index 000000000..84bf8a5be --- /dev/null +++ b/Tests/test_file_gribstub.py @@ -0,0 +1,15 @@ +from helper import unittest, PillowTestCase + +from PIL import GribStubImagePlugin + + +class TestFileGribStub(PillowTestCase): + + def test_invalid_file(self): + self.assertRaises(SyntaxError, lambda: GribStubImagePlugin.GribStubImageFile("Tests/images/flower.jpg")) + + +if __name__ == '__main__': + unittest.main() + +# End of file diff --git a/Tests/test_file_hdf5stub.py b/Tests/test_file_hdf5stub.py new file mode 100644 index 000000000..2b7e208d2 --- /dev/null +++ b/Tests/test_file_hdf5stub.py @@ -0,0 +1,15 @@ +from helper import unittest, PillowTestCase + +from PIL import Hdf5StubImagePlugin + + +class TestFileHdf5Stub(PillowTestCase): + + def test_invalid_file(self): + self.assertRaises(SyntaxError, lambda: Hdf5StubImagePlugin.HDF5StubImageFile("Tests/images/flower.jpg")) + + +if __name__ == '__main__': + unittest.main() + +# End of file diff --git a/Tests/test_file_ico.py b/Tests/test_file_ico.py index f7b52b124..d2adf8dcc 100644 --- a/Tests/test_file_ico.py +++ b/Tests/test_file_ico.py @@ -1,7 +1,7 @@ from helper import unittest, PillowTestCase, hopper import io -from PIL import Image +from PIL import Image, IcoImagePlugin # sample ppm stream TEST_ICO_FILE = "Tests/images/hopper.ico" @@ -17,6 +17,10 @@ class TestFileIco(PillowTestCase): self.assertEqual(im.size, (16, 16)) self.assertEqual(im.format, "ICO") + def test_invalid_file(self): + with open("Tests/images/flower.jpg", "rb") as fp: + self.assertRaises(SyntaxError, lambda: IcoImagePlugin.IcoImageFile(fp)) + def test_save_to_bytes(self): output = io.BytesIO() im = hopper() diff --git a/Tests/test_file_im.py b/Tests/test_file_im.py index 602db4b1d..01b6fd561 100644 --- a/Tests/test_file_im.py +++ b/Tests/test_file_im.py @@ -1,6 +1,6 @@ from helper import unittest, PillowTestCase, hopper -from PIL import Image +from PIL import Image, ImImagePlugin # sample im TEST_IM = "Tests/images/hopper.im" @@ -40,6 +40,10 @@ class TestFileIm(PillowTestCase): self.assert_image_equal(reread, im) + def test_invalid_file(self): + self.assertRaises(SyntaxError, lambda: ImImagePlugin.ImImageFile("Tests/images/flower.jpg")) + + if __name__ == '__main__': unittest.main() diff --git a/Tests/test_file_jpeg2k.py b/Tests/test_file_jpeg2k.py index 9768a881d..a97a7e9da 100644 --- a/Tests/test_file_jpeg2k.py +++ b/Tests/test_file_jpeg2k.py @@ -1,6 +1,6 @@ from helper import unittest, PillowTestCase -from PIL import Image +from PIL import Image, Jpeg2KImagePlugin from io import BytesIO codecs = dir(Image.core) @@ -39,6 +39,9 @@ class TestFileJpeg2k(PillowTestCase): self.assertEqual(im.size, (640, 480)) self.assertEqual(im.format, 'JPEG2000') + def test_invalid_file(self): + self.assertRaises(SyntaxError, lambda: Jpeg2KImagePlugin.Jpeg2KImageFile("Tests/images/flower.jpg")) + def test_bytesio(self): with open('Tests/images/test-card-lossless.jp2', 'rb') as f: data = BytesIO(f.read()) diff --git a/Tests/test_file_mcidas.py b/Tests/test_file_mcidas.py new file mode 100644 index 000000000..44130746a --- /dev/null +++ b/Tests/test_file_mcidas.py @@ -0,0 +1,15 @@ +from helper import unittest, PillowTestCase + +from PIL import McIdasImagePlugin + + +class TestFileMcIdas(PillowTestCase): + + def test_invalid_file(self): + self.assertRaises(SyntaxError, lambda: McIdasImagePlugin.McIdasImageFile("Tests/images/flower.jpg")) + + +if __name__ == '__main__': + unittest.main() + +# End of file diff --git a/Tests/test_file_mic.py b/Tests/test_file_mic.py new file mode 100644 index 000000000..de106bc14 --- /dev/null +++ b/Tests/test_file_mic.py @@ -0,0 +1,15 @@ +from helper import unittest, PillowTestCase + +from PIL import MicImagePlugin + + +class TestFileMic(PillowTestCase): + + def test_invalid_file(self): + self.assertRaises(SyntaxError, lambda: MicImagePlugin.MicImageFile("Tests/images/flower.jpg")) + + +if __name__ == '__main__': + unittest.main() + +# End of file diff --git a/Tests/test_file_msp.py b/Tests/test_file_msp.py index f4b1af75e..94fcfd895 100644 --- a/Tests/test_file_msp.py +++ b/Tests/test_file_msp.py @@ -1,6 +1,6 @@ from helper import unittest, PillowTestCase, hopper -from PIL import Image +from PIL import Image, MspImagePlugin TEST_FILE = "Tests/images/hopper.msp" @@ -18,6 +18,9 @@ class TestFileMsp(PillowTestCase): self.assertEqual(im.size, (128, 128)) self.assertEqual(im.format, "MSP") + def test_invalid_file(self): + self.assertRaises(SyntaxError, lambda: MspImagePlugin.MspImageFile("Tests/images/flower.jpg")) + def test_open(self): # Arrange # Act diff --git a/Tests/test_file_pcx.py b/Tests/test_file_pcx.py index 10d17d349..5a1a2e9a6 100644 --- a/Tests/test_file_pcx.py +++ b/Tests/test_file_pcx.py @@ -1,6 +1,6 @@ from helper import unittest, PillowTestCase, hopper -from PIL import Image +from PIL import Image, PcxImagePlugin class TestFilePcx(PillowTestCase): @@ -19,6 +19,9 @@ class TestFilePcx(PillowTestCase): for mode in ('1', 'L', 'P', 'RGB'): self._roundtrip(hopper(mode)) + def test_invalid_file(self): + self.assertRaises(SyntaxError, lambda: PcxImagePlugin.PcxImageFile("Tests/images/flower.jpg")) + def test_odd(self): # see issue #523, odd sized images should have a stride that's even. # not that imagemagick or gimp write pcx that way. diff --git a/Tests/test_file_pdf.py b/Tests/test_file_pdf.py index 9424bc09d..8dad9822c 100644 --- a/Tests/test_file_pdf.py +++ b/Tests/test_file_pdf.py @@ -52,6 +52,12 @@ class TestFilePdf(PillowTestCase): # Act / Assert self.helper_save_as_pdf(mode) + def test_unsupported_mode(self): + im = hopper("LA") + outfile = self.tempfile("temp_LA.pdf") + + self.assertRaises(ValueError, lambda: im.save(outfile)) + if __name__ == '__main__': unittest.main() diff --git a/Tests/test_file_png.py b/Tests/test_file_png.py index f438e24cc..dfd83baa6 100644 --- a/Tests/test_file_png.py +++ b/Tests/test_file_png.py @@ -81,6 +81,9 @@ class TestFilePng(PillowTestCase): hopper("I").save(test_file) im = Image.open(test_file) + def test_invalid_file(self): + self.assertRaises(SyntaxError, lambda: PngImagePlugin.PngImageFile("Tests/images/flower.jpg")) + def test_broken(self): # Check reading of totally broken files. In this case, the test # file was checked into Subversion as a text file. diff --git a/Tests/test_file_psd.py b/Tests/test_file_psd.py index 6492787ec..af1780d9d 100644 --- a/Tests/test_file_psd.py +++ b/Tests/test_file_psd.py @@ -1,6 +1,6 @@ from helper import unittest, PillowTestCase -from PIL import Image +from PIL import Image, PsdImagePlugin # sample ppm stream test_file = "Tests/images/hopper.psd" @@ -16,6 +16,9 @@ class TestImagePsd(PillowTestCase): self.assertEqual(im.size, (128, 128)) self.assertEqual(im.format, "PSD") + def test_invalid_file(self): + self.assertRaises(SyntaxError, lambda: PsdImagePlugin.PsdImageFile("Tests/images/flower.jpg")) + def test_n_frames(self): im = Image.open("Tests/images/hopper_merged.psd") self.assertEqual(im.n_frames, 1) diff --git a/Tests/test_file_sgi.py b/Tests/test_file_sgi.py index d49086c51..9842b3991 100644 --- a/Tests/test_file_sgi.py +++ b/Tests/test_file_sgi.py @@ -1,6 +1,6 @@ from helper import unittest, PillowTestCase -from PIL import Image +from PIL import Image, SgiImagePlugin class TestFileSgi(PillowTestCase): @@ -32,6 +32,9 @@ class TestFileSgi(PillowTestCase): # Act / Assert self.assertRaises(ValueError, lambda: Image.open(test_file)) + def test_invalid_file(self): + self.assertRaises(ValueError, lambda: SgiImagePlugin.SgiImageFile("Tests/images/flower.jpg")) + if __name__ == '__main__': unittest.main() diff --git a/Tests/test_file_sun.py b/Tests/test_file_sun.py index 332104062..e476ebf13 100644 --- a/Tests/test_file_sun.py +++ b/Tests/test_file_sun.py @@ -1,6 +1,6 @@ from helper import unittest, PillowTestCase -from PIL import Image +from PIL import Image, SunImagePlugin class TestFileSun(PillowTestCase): @@ -16,6 +16,8 @@ class TestFileSun(PillowTestCase): # Assert self.assertEqual(im.size, (128, 128)) + self.assertRaises(SyntaxError, lambda: SunImagePlugin.SunImageFile("Tests/images/flower.jpg")) + if __name__ == '__main__': unittest.main() diff --git a/Tests/test_file_tga.py b/Tests/test_file_tga.py index ea94dee64..cccd9bf96 100644 --- a/Tests/test_file_tga.py +++ b/Tests/test_file_tga.py @@ -15,6 +15,25 @@ class TestFileTga(PillowTestCase): # Assert self.assertEqual(im.size, (100, 100)) + def test_save(self): + test_file = "Tests/images/tga_id_field.tga" + im = Image.open(test_file) + + test_file = self.tempfile("temp.tga") + + # Save + im.save(test_file) + test_im = Image.open(test_file) + self.assertEqual(test_im.size, (100, 100)) + + # RGBA save + im.convert("RGBA").save(test_file) + test_im = Image.open(test_file) + self.assertEqual(test_im.size, (100, 100)) + + # Unsupported mode save + self.assertRaises(IOError, lambda: im.convert("LA").save(test_file)) + if __name__ == '__main__': unittest.main() diff --git a/Tests/test_file_webp.py b/Tests/test_file_webp.py index 8c8313dd9..d1c8e580e 100644 --- a/Tests/test_file_webp.py +++ b/Tests/test_file_webp.py @@ -72,6 +72,11 @@ class TestFileWebp(PillowTestCase): target = hopper("RGB") self.assert_image_similar(image, target, 12) + def test_write_unsupported_mode(self): + temp_file = self.tempfile("temp.webp") + + self.assertRaises(IOError, lambda: hopper("L").save(temp_file)) + if __name__ == '__main__': unittest.main() diff --git a/Tests/test_file_xpm.py b/Tests/test_file_xpm.py index 6a6817048..3e3c35e18 100644 --- a/Tests/test_file_xpm.py +++ b/Tests/test_file_xpm.py @@ -1,6 +1,6 @@ from helper import unittest, PillowTestCase, hopper -from PIL import Image +from PIL import Image, XpmImagePlugin # sample ppm stream TEST_FILE = "Tests/images/hopper.xpm" @@ -18,6 +18,9 @@ class TestFileXpm(PillowTestCase): # large error due to quantization->44 colors. self.assert_image_similar(im.convert('RGB'), hopper('RGB'), 60) + def test_invalid_file(self): + self.assertRaises(SyntaxError, lambda: XpmImagePlugin.XpmImageFile("Tests/images/flower.jpg")) + def test_load_read(self): # Arrange im = Image.open(TEST_FILE) diff --git a/Tests/test_font_bdf.py b/Tests/test_font_bdf.py index b844f1228..4d88ae46f 100644 --- a/Tests/test_font_bdf.py +++ b/Tests/test_font_bdf.py @@ -15,6 +15,10 @@ class TestFontBdf(PillowTestCase): self.assertIsInstance(font, FontFile.FontFile) self.assertEqual(len([_f for _f in font.glyph if _f]), 190) + def test_invalid_file(self): + with open("Tests/images/flower.jpg", "rb") as fp: + self.assertRaises(SyntaxError, lambda: BdfFontFile.BdfFontFile(fp)) + if __name__ == '__main__': unittest.main() diff --git a/Tests/test_font_pcf.py b/Tests/test_font_pcf.py index 3cc6afa64..f6dd9265e 100644 --- a/Tests/test_font_pcf.py +++ b/Tests/test_font_pcf.py @@ -30,6 +30,10 @@ class TestFontPcf(PillowTestCase): def test_sanity(self): self.save_font() + def test_invalid_file(self): + with open("Tests/images/flower.jpg", "rb") as fp: + self.assertRaises(SyntaxError, lambda: PcfFontFile.PcfFontFile(fp)) + def xtest_draw(self): tempname = self.save_font() diff --git a/Tests/test_image_filter.py b/Tests/test_image_filter.py index 6a694b3ca..a62431dd9 100644 --- a/Tests/test_image_filter.py +++ b/Tests/test_image_filter.py @@ -88,6 +88,12 @@ class TestImageFilter(PillowTestCase): self.assertEqual(rankfilter("I"), (0, 4, 8)) self.assertEqual(rankfilter("F"), (0.0, 4.0, 8.0)) + def test_rankfilter_properties(self): + rankfilter = ImageFilter.RankFilter(1,2) + + self.assertEqual(rankfilter.size, 1) + self.assertEqual(rankfilter.rank, 2) + if __name__ == '__main__': unittest.main() diff --git a/Tests/test_image_mode.py b/Tests/test_image_mode.py index 6441c7d1b..c20db50aa 100644 --- a/Tests/test_image_mode.py +++ b/Tests/test_image_mode.py @@ -21,12 +21,14 @@ class TestImageMode(PillowTestCase): m = ImageMode.getmode("1") self.assertEqual(m.mode, "1") + self.assertEqual(str(m), "1") self.assertEqual(m.bands, ("1",)) self.assertEqual(m.basemode, "L") self.assertEqual(m.basetype, "L") m = ImageMode.getmode("RGB") self.assertEqual(m.mode, "RGB") + self.assertEqual(str(m), "RGB") self.assertEqual(m.bands, ("R", "G", "B")) self.assertEqual(m.basemode, "RGB") self.assertEqual(m.basetype, "L") diff --git a/Tests/test_imagecolor.py b/Tests/test_imagecolor.py index 5d8944852..d55e73b9c 100644 --- a/Tests/test_imagecolor.py +++ b/Tests/test_imagecolor.py @@ -18,6 +18,8 @@ class TestImageColor(PillowTestCase): (255, 0, 0, 0), ImageColor.getrgb("rgba(255, 0, 0, 0)")) self.assertEqual((255, 0, 0), ImageColor.getrgb("red")) + self.assertRaises(ValueError, lambda: ImageColor.getrgb("invalid color")) + # look for rounding errors (based on code by Tim Hatch) def test_rounding_errors(self): diff --git a/Tests/test_imagedraw.py b/Tests/test_imagedraw.py index ffefa6504..2e746438d 100644 --- a/Tests/test_imagedraw.py +++ b/Tests/test_imagedraw.py @@ -52,6 +52,11 @@ class TestImageDraw(PillowTestCase): self.assert_warning(DeprecationWarning, lambda: draw.setink(0)) self.assert_warning(DeprecationWarning, lambda: draw.setfill(0)) + def test_mode_mismatch(self): + im = hopper("RGB").copy() + + self.assertRaises(ValueError, lambda: ImageDraw.ImageDraw(im, mode="L")) + def helper_arc(self, bbox): # Arrange im = Image.new("RGB", (W, H)) diff --git a/Tests/test_imagefile.py b/Tests/test_imagefile.py index bc81575b6..fbd10d47b 100644 --- a/Tests/test_imagefile.py +++ b/Tests/test_imagefile.py @@ -93,6 +93,9 @@ class TestImageFile(PillowTestCase): self.assert_image_equal(im1, im2) + def test_raise_ioerror(self): + self.assertRaises(IOError, lambda: ImageFile.raise_ioerror(1)) + if __name__ == '__main__': unittest.main() diff --git a/Tests/test_imagesequence.py b/Tests/test_imagesequence.py index d2aa17df7..3bd6dc582 100644 --- a/Tests/test_imagesequence.py +++ b/Tests/test_imagesequence.py @@ -22,6 +22,8 @@ class TestImageSequence(PillowTestCase): self.assertEqual(index, 1) + self.assertRaises(AttributeError, lambda: ImageSequence.Iterator(0)) + def _test_multipage_tiff(self, dbg=False): im = Image.open('Tests/images/multipage.tiff') for index, frame in enumerate(ImageSequence.Iterator(im)): diff --git a/Tests/test_olefileio.py b/Tests/test_olefileio.py index 1cff273a1..d3842e977 100644 --- a/Tests/test_olefileio.py +++ b/Tests/test_olefileio.py @@ -7,25 +7,20 @@ import PIL.OleFileIO as OleFileIO class TestOleFileIo(PillowTestCase): - def test_isOleFile_false(self): - # Arrange - non_ole_file = "Tests/images/flower.jpg" - - # Act - is_ole = OleFileIO.isOleFile(non_ole_file) - - # Assert - self.assertFalse(is_ole) - - def test_isOleFile_true(self): - # Arrange + def test_isOleFile(self): ole_file = "Tests/images/test-ole-file.doc" - # Act - is_ole = OleFileIO.isOleFile(ole_file) + self.assertTrue(OleFileIO.isOleFile(ole_file)) + with open(ole_file, 'rb') as fp: + self.assertTrue(OleFileIO.isOleFile(fp)) + self.assertTrue(OleFileIO.isOleFile(fp.read())) - # Assert - self.assertTrue(is_ole) + non_ole_file = "Tests/images/flower.jpg" + + self.assertFalse(OleFileIO.isOleFile(non_ole_file)) + with open(non_ole_file, 'rb') as fp: + self.assertFalse(OleFileIO.isOleFile(fp)) + self.assertFalse(OleFileIO.isOleFile(fp.read())) def test_exists_worddocument(self): # Arrange From 309ab1fc3d167b38a463e6bd75438aba4dc3419e Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Fri, 3 Jul 2015 16:22:56 +1000 Subject: [PATCH 42/56] Various Flake8 fixes --- Tests/check_imaging_leaks.py | 3 ++- Tests/check_png_dos.py | 3 ++- Tests/test_cffi.py | 2 +- Tests/test_file_bmp.py | 3 ++- Tests/test_file_bufrstub.py | 6 +++++- Tests/test_file_cur.py | 5 ++++- Tests/test_file_dcx.py | 3 ++- Tests/test_file_eps.py | 9 +++++++-- Tests/test_file_fitsstub.py | 6 +++++- Tests/test_file_fli.py | 5 ++++- Tests/test_file_fpx.py | 5 ++++- Tests/test_file_gbr.py | 5 ++++- Tests/test_file_gif.py | 5 ++++- Tests/test_file_gribstub.py | 6 +++++- Tests/test_file_hdf5stub.py | 6 +++++- Tests/test_file_ico.py | 9 ++++++--- Tests/test_file_im.py | 5 ++++- Tests/test_file_jpeg.py | 17 ++++++++++------- Tests/test_file_jpeg2k.py | 6 +++++- Tests/test_file_mcidas.py | 6 +++++- Tests/test_file_mic.py | 5 ++++- Tests/test_file_msp.py | 5 ++++- Tests/test_file_pcx.py | 5 ++++- Tests/test_file_png.py | 5 ++++- Tests/test_file_psd.py | 5 ++++- Tests/test_file_sgi.py | 6 +++++- Tests/test_file_sun.py | 4 +++- Tests/test_file_tga.py | 2 +- Tests/test_file_tiff.py | 3 ++- Tests/test_file_webp_alpha.py | 3 ++- Tests/test_file_xpm.py | 5 ++++- Tests/test_image_filter.py | 2 +- Tests/test_imagecolor.py | 7 ++++--- Tests/test_imagedraw.py | 3 ++- Tests/test_imagefont_bitmap.py | 11 +++++++---- Tests/test_imageops_usm.py | 4 +++- Tests/test_imagewin.py | 3 ++- Tests/test_scipy.py | 4 ++-- 38 files changed, 144 insertions(+), 53 deletions(-) diff --git a/Tests/check_imaging_leaks.py b/Tests/check_imaging_leaks.py index e79429f8f..a31cd2180 100644 --- a/Tests/check_imaging_leaks.py +++ b/Tests/check_imaging_leaks.py @@ -31,7 +31,8 @@ class TestImagingLeaks(PillowTestCase): def test_leak_putdata(self): im = Image.new('RGB', (25, 25)) - self._test_leak(min_iterations, max_iterations, im.putdata, im.getdata()) + self._test_leak(min_iterations, max_iterations, + im.putdata, im.getdata()) def test_leak_getlist(self): im = Image.new('P', (25, 25)) diff --git a/Tests/check_png_dos.py b/Tests/check_png_dos.py index 762c9607a..c24eeb359 100644 --- a/Tests/check_png_dos.py +++ b/Tests/check_png_dos.py @@ -42,7 +42,8 @@ class TestPngDos(PillowTestCase): total_len = 0 for txt in im2.text.values(): total_len += len(txt) - self.assertLess(total_len, 64*1024*1024, "Total text chunks greater than 64M") + self.assertLess(total_len, 64*1024*1024, + "Total text chunks greater than 64M") if __name__ == '__main__': unittest.main() diff --git a/Tests/test_cffi.py b/Tests/test_cffi.py index 361b59a39..02d1ff7d3 100644 --- a/Tests/test_cffi.py +++ b/Tests/test_cffi.py @@ -74,7 +74,7 @@ class TestCffi(PillowTestCase): self._test_get_access(hopper('LA')) self._test_get_access(hopper('1')) self._test_get_access(hopper('P')) - # self._test_get_access(hopper('PA')) # PA -- how do I make a PA image? + # self._test_get_access(hopper('PA')) # PA -- how do I make a PA image? self._test_get_access(hopper('F')) im = Image.new('I;16', (10, 10), 40000) diff --git a/Tests/test_file_bmp.py b/Tests/test_file_bmp.py index 15d470c90..25f70139d 100644 --- a/Tests/test_file_bmp.py +++ b/Tests/test_file_bmp.py @@ -27,7 +27,8 @@ class TestFileBmp(PillowTestCase): def test_invalid_file(self): with open("Tests/images/flower.jpg", "rb") as fp: - self.assertRaises(SyntaxError, lambda: BmpImagePlugin.BmpImageFile(fp)) + self.assertRaises(SyntaxError, + lambda: BmpImagePlugin.BmpImageFile(fp)) def test_save_to_bytes(self): output = io.BytesIO() diff --git a/Tests/test_file_bufrstub.py b/Tests/test_file_bufrstub.py index 8cf496533..05266e9fd 100644 --- a/Tests/test_file_bufrstub.py +++ b/Tests/test_file_bufrstub.py @@ -6,7 +6,11 @@ from PIL import BufrStubImagePlugin class TestFileBufrStub(PillowTestCase): def test_invalid_file(self): - self.assertRaises(SyntaxError, lambda: BufrStubImagePlugin.BufrStubImageFile("Tests/images/flower.jpg")) + invalid_file = "Tests/images/flower.jpg" + + self.assertRaises(SyntaxError, + lambda: + BufrStubImagePlugin.BufrStubImageFile(invalid_file)) if __name__ == '__main__': diff --git a/Tests/test_file_cur.py b/Tests/test_file_cur.py index d9acb5f78..fa4242629 100644 --- a/Tests/test_file_cur.py +++ b/Tests/test_file_cur.py @@ -21,7 +21,10 @@ class TestFileCur(PillowTestCase): self.assertEqual(im.getpixel((16, 16)), (84, 87, 86, 255)) def test_invalid_file(self): - self.assertRaises(SyntaxError, lambda: CurImagePlugin.CurImageFile("Tests/images/flower.jpg")) + invalid_file = "Tests/images/flower.jpg" + + self.assertRaises(SyntaxError, + lambda: CurImagePlugin.CurImageFile(invalid_file)) if __name__ == '__main__': diff --git a/Tests/test_file_dcx.py b/Tests/test_file_dcx.py index 62760fd0c..2c0f90c1f 100644 --- a/Tests/test_file_dcx.py +++ b/Tests/test_file_dcx.py @@ -22,7 +22,8 @@ class TestFileDcx(PillowTestCase): def test_invalid_file(self): with open("Tests/images/flower.jpg", "rb") as fp: - self.assertRaises(SyntaxError, lambda: DcxImagePlugin.DcxImageFile(fp)) + self.assertRaises(SyntaxError, + lambda: DcxImagePlugin.DcxImageFile(fp)) def test_tell(self): # Arrange diff --git a/Tests/test_file_eps.py b/Tests/test_file_eps.py index 931b788a3..6e4c63e4f 100644 --- a/Tests/test_file_eps.py +++ b/Tests/test_file_eps.py @@ -52,7 +52,10 @@ class TestFileEps(PillowTestCase): self.assertEqual(image2_scale2.format, "EPS") def test_invalid_file(self): - self.assertRaises(SyntaxError, lambda: EpsImagePlugin.EpsImageFile("Tests/images/flower.jpg")) + invalid_file = "Tests/images/flower.jpg" + + self.assertRaises(SyntaxError, + lambda: EpsImagePlugin.EpsImageFile(invalid_file)) def test_file_object(self): # issue 479 @@ -152,7 +155,9 @@ class TestFileEps(PillowTestCase): Image.open(file3) def _test_readline(self, t, ending): - ending = "Failure with line ending: %s" % ("".join("%s" % ord(s) for s in ending)) + ending = "Failure with line ending: %s" % ("".join( + "%s" % ord(s) + for s in ending)) self.assertEqual(t.readline().strip('\r\n'), 'something', ending) self.assertEqual(t.readline().strip('\r\n'), 'else', ending) self.assertEqual(t.readline().strip('\r\n'), 'baz', ending) diff --git a/Tests/test_file_fitsstub.py b/Tests/test_file_fitsstub.py index 189d002d6..cc38b4a3a 100644 --- a/Tests/test_file_fitsstub.py +++ b/Tests/test_file_fitsstub.py @@ -6,7 +6,11 @@ from PIL import FitsStubImagePlugin class TestFileFitsStub(PillowTestCase): def test_invalid_file(self): - self.assertRaises(SyntaxError, lambda: FitsStubImagePlugin.FITSStubImageFile("Tests/images/flower.jpg")) + invalid_file = "Tests/images/flower.jpg" + + self.assertRaises(SyntaxError, + lambda: + FitsStubImagePlugin.FITSStubImageFile(invalid_file)) if __name__ == '__main__': diff --git a/Tests/test_file_fli.py b/Tests/test_file_fli.py index daaebdc69..aa2fa5ae5 100644 --- a/Tests/test_file_fli.py +++ b/Tests/test_file_fli.py @@ -19,7 +19,10 @@ class TestFileFli(PillowTestCase): self.assertEqual(im.format, "FLI") def test_invalid_file(self): - self.assertRaises(SyntaxError, lambda: FliImagePlugin.FliImageFile("Tests/images/flower.jpg")) + invalid_file = "Tests/images/flower.jpg" + + self.assertRaises(SyntaxError, + lambda: FliImagePlugin.FliImageFile(invalid_file)) def test_n_frames(self): im = Image.open(test_file) diff --git a/Tests/test_file_fpx.py b/Tests/test_file_fpx.py index 4b3756fb9..35119a612 100644 --- a/Tests/test_file_fpx.py +++ b/Tests/test_file_fpx.py @@ -6,7 +6,10 @@ from PIL import FpxImagePlugin class TestFileFpx(PillowTestCase): def test_invalid_file(self): - self.assertRaises(SyntaxError, lambda: FpxImagePlugin.FpxImageFile("Tests/images/flower.jpg")) + invalid_file = "Tests/images/flower.jpg" + + self.assertRaises(SyntaxError, + lambda: FpxImagePlugin.FpxImageFile(invalid_file)) if __name__ == '__main__': diff --git a/Tests/test_file_gbr.py b/Tests/test_file_gbr.py index ecec0189b..57b301ada 100644 --- a/Tests/test_file_gbr.py +++ b/Tests/test_file_gbr.py @@ -6,7 +6,10 @@ from PIL import GbrImagePlugin class TestFileGbr(PillowTestCase): def test_invalid_file(self): - self.assertRaises(SyntaxError, lambda: GbrImagePlugin.GbrImageFile("Tests/images/flower.jpg")) + invalid_file = "Tests/images/flower.jpg" + + self.assertRaises(SyntaxError, + lambda: GbrImagePlugin.GbrImageFile(invalid_file)) if __name__ == '__main__': diff --git a/Tests/test_file_gif.py b/Tests/test_file_gif.py index 33e04cb5e..c40e5f6b0 100644 --- a/Tests/test_file_gif.py +++ b/Tests/test_file_gif.py @@ -26,7 +26,10 @@ class TestFileGif(PillowTestCase): self.assertEqual(im.format, "GIF") def test_invalid_file(self): - self.assertRaises(SyntaxError, lambda: GifImagePlugin.GifImageFile("Tests/images/flower.jpg")) + invalid_file = "Tests/images/flower.jpg" + + self.assertRaises(SyntaxError, + lambda: GifImagePlugin.GifImageFile(invalid_file)) def test_optimize(self): from io import BytesIO diff --git a/Tests/test_file_gribstub.py b/Tests/test_file_gribstub.py index 84bf8a5be..dd4ee84ff 100644 --- a/Tests/test_file_gribstub.py +++ b/Tests/test_file_gribstub.py @@ -6,7 +6,11 @@ from PIL import GribStubImagePlugin class TestFileGribStub(PillowTestCase): def test_invalid_file(self): - self.assertRaises(SyntaxError, lambda: GribStubImagePlugin.GribStubImageFile("Tests/images/flower.jpg")) + invalid_file = "Tests/images/flower.jpg" + + self.assertRaises(SyntaxError, + lambda: + GribStubImagePlugin.GribStubImageFile(invalid_file)) if __name__ == '__main__': diff --git a/Tests/test_file_hdf5stub.py b/Tests/test_file_hdf5stub.py index 2b7e208d2..485931b36 100644 --- a/Tests/test_file_hdf5stub.py +++ b/Tests/test_file_hdf5stub.py @@ -6,7 +6,11 @@ from PIL import Hdf5StubImagePlugin class TestFileHdf5Stub(PillowTestCase): def test_invalid_file(self): - self.assertRaises(SyntaxError, lambda: Hdf5StubImagePlugin.HDF5StubImageFile("Tests/images/flower.jpg")) + test_file = "Tests/images/flower.jpg" + + self.assertRaises(SyntaxError, + lambda: + Hdf5StubImagePlugin.HDF5StubImageFile(test_file)) if __name__ == '__main__': diff --git a/Tests/test_file_ico.py b/Tests/test_file_ico.py index d2adf8dcc..70e00c083 100644 --- a/Tests/test_file_ico.py +++ b/Tests/test_file_ico.py @@ -19,7 +19,8 @@ class TestFileIco(PillowTestCase): def test_invalid_file(self): with open("Tests/images/flower.jpg", "rb") as fp: - self.assertRaises(SyntaxError, lambda: IcoImagePlugin.IcoImageFile(fp)) + self.assertRaises(SyntaxError, + lambda: IcoImagePlugin.IcoImageFile(fp)) def test_save_to_bytes(self): output = io.BytesIO() @@ -34,7 +35,8 @@ class TestFileIco(PillowTestCase): 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.LANCZOS)) + self.assert_image_equal(reloaded, + hopper().resize((64, 64), Image.LANCZOS)) # the other one output.seek(0) @@ -44,7 +46,8 @@ class TestFileIco(PillowTestCase): 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.LANCZOS)) + self.assert_image_equal(reloaded, + hopper().resize((32, 32), Image.LANCZOS)) if __name__ == '__main__': diff --git a/Tests/test_file_im.py b/Tests/test_file_im.py index 01b6fd561..e3d92d1d5 100644 --- a/Tests/test_file_im.py +++ b/Tests/test_file_im.py @@ -41,7 +41,10 @@ class TestFileIm(PillowTestCase): self.assert_image_equal(reread, im) def test_invalid_file(self): - self.assertRaises(SyntaxError, lambda: ImImagePlugin.ImImageFile("Tests/images/flower.jpg")) + invalid_file = "Tests/images/flower.jpg" + + self.assertRaises(SyntaxError, + lambda: ImImagePlugin.ImImageFile(invalid_file)) if __name__ == '__main__': diff --git a/Tests/test_file_jpeg.py b/Tests/test_file_jpeg.py index d4929dd58..bb2d7b61e 100644 --- a/Tests/test_file_jpeg.py +++ b/Tests/test_file_jpeg.py @@ -291,22 +291,24 @@ class TestFileJpeg(PillowTestCase): # dict of qtable lists self.assert_image_similar(im, - self.roundtrip(im, - qtables={0: standard_l_qtable, - 1: standard_chrominance_qtable}), - 30) + self.roundtrip(im, qtables={ + 0: standard_l_qtable, + 1: standard_chrominance_qtable + }), 30) # not a sequence self.assertRaises(Exception, lambda: self.roundtrip(im, qtables='a')) # sequence wrong length self.assertRaises(Exception, lambda: self.roundtrip(im, qtables=[])) # sequence wrong length - self.assertRaises(Exception, lambda: self.roundtrip(im, qtables=[1, 2, 3, 4, 5])) + self.assertRaises(Exception, + lambda: self.roundtrip(im, qtables=[1, 2, 3, 4, 5])) # qtable entry not a sequence self.assertRaises(Exception, lambda: self.roundtrip(im, qtables=[1])) # qtable entry has wrong number of items - self.assertRaises(Exception, lambda: self.roundtrip(im, qtables=[[1, 2, 3, 4]])) + self.assertRaises(Exception, + lambda: self.roundtrip(im, qtables=[[1, 2, 3, 4]])) @unittest.skipUnless(djpeg_available(), "djpeg not available") def test_load_djpeg(self): @@ -337,7 +339,8 @@ class TestFileJpeg(PillowTestCase): """ Generates a very hard to compress file :param size: tuple """ - return Image.frombytes('RGB', size, os.urandom(size[0]*size[1] * 3)) + return Image.frombytes('RGB', + size, os.urandom(size[0]*size[1] * 3)) im = gen_random_image((512, 512)) f = self.tempfile("temp.jpeg") diff --git a/Tests/test_file_jpeg2k.py b/Tests/test_file_jpeg2k.py index a97a7e9da..4b7286a20 100644 --- a/Tests/test_file_jpeg2k.py +++ b/Tests/test_file_jpeg2k.py @@ -40,7 +40,11 @@ class TestFileJpeg2k(PillowTestCase): self.assertEqual(im.format, 'JPEG2000') def test_invalid_file(self): - self.assertRaises(SyntaxError, lambda: Jpeg2KImagePlugin.Jpeg2KImageFile("Tests/images/flower.jpg")) + invalid_file = "Tests/images/flower.jpg" + + self.assertRaises(SyntaxError, + lambda: + Jpeg2KImagePlugin.Jpeg2KImageFile(invalid_file)) def test_bytesio(self): with open('Tests/images/test-card-lossless.jp2', 'rb') as f: diff --git a/Tests/test_file_mcidas.py b/Tests/test_file_mcidas.py index 44130746a..d547011e2 100644 --- a/Tests/test_file_mcidas.py +++ b/Tests/test_file_mcidas.py @@ -6,7 +6,11 @@ from PIL import McIdasImagePlugin class TestFileMcIdas(PillowTestCase): def test_invalid_file(self): - self.assertRaises(SyntaxError, lambda: McIdasImagePlugin.McIdasImageFile("Tests/images/flower.jpg")) + invalid_file = "Tests/images/flower.jpg" + + self.assertRaises(SyntaxError, + lambda: + McIdasImagePlugin.McIdasImageFile(invalid_file)) if __name__ == '__main__': diff --git a/Tests/test_file_mic.py b/Tests/test_file_mic.py index de106bc14..0a574422a 100644 --- a/Tests/test_file_mic.py +++ b/Tests/test_file_mic.py @@ -6,7 +6,10 @@ from PIL import MicImagePlugin class TestFileMic(PillowTestCase): def test_invalid_file(self): - self.assertRaises(SyntaxError, lambda: MicImagePlugin.MicImageFile("Tests/images/flower.jpg")) + invalid_file = "Tests/images/flower.jpg" + + self.assertRaises(SyntaxError, + lambda: MicImagePlugin.MicImageFile(invalid_file)) if __name__ == '__main__': diff --git a/Tests/test_file_msp.py b/Tests/test_file_msp.py index 94fcfd895..3dbca6e60 100644 --- a/Tests/test_file_msp.py +++ b/Tests/test_file_msp.py @@ -19,7 +19,10 @@ class TestFileMsp(PillowTestCase): self.assertEqual(im.format, "MSP") def test_invalid_file(self): - self.assertRaises(SyntaxError, lambda: MspImagePlugin.MspImageFile("Tests/images/flower.jpg")) + invalid_file = "Tests/images/flower.jpg" + + self.assertRaises(SyntaxError, + lambda: MspImagePlugin.MspImageFile(invalid_file)) def test_open(self): # Arrange diff --git a/Tests/test_file_pcx.py b/Tests/test_file_pcx.py index 5a1a2e9a6..f6342bed9 100644 --- a/Tests/test_file_pcx.py +++ b/Tests/test_file_pcx.py @@ -20,7 +20,10 @@ class TestFilePcx(PillowTestCase): self._roundtrip(hopper(mode)) def test_invalid_file(self): - self.assertRaises(SyntaxError, lambda: PcxImagePlugin.PcxImageFile("Tests/images/flower.jpg")) + invalid_file = "Tests/images/flower.jpg" + + self.assertRaises(SyntaxError, + lambda: PcxImagePlugin.PcxImageFile(invalid_file)) def test_odd(self): # see issue #523, odd sized images should have a stride that's even. diff --git a/Tests/test_file_png.py b/Tests/test_file_png.py index dfd83baa6..cb72b2d73 100644 --- a/Tests/test_file_png.py +++ b/Tests/test_file_png.py @@ -82,7 +82,10 @@ class TestFilePng(PillowTestCase): im = Image.open(test_file) def test_invalid_file(self): - self.assertRaises(SyntaxError, lambda: PngImagePlugin.PngImageFile("Tests/images/flower.jpg")) + invalid_file = "Tests/images/flower.jpg" + + self.assertRaises(SyntaxError, + lambda: PngImagePlugin.PngImageFile(invalid_file)) def test_broken(self): # Check reading of totally broken files. In this case, the test diff --git a/Tests/test_file_psd.py b/Tests/test_file_psd.py index af1780d9d..4890839f1 100644 --- a/Tests/test_file_psd.py +++ b/Tests/test_file_psd.py @@ -17,7 +17,10 @@ class TestImagePsd(PillowTestCase): self.assertEqual(im.format, "PSD") def test_invalid_file(self): - self.assertRaises(SyntaxError, lambda: PsdImagePlugin.PsdImageFile("Tests/images/flower.jpg")) + invalid_file = "Tests/images/flower.jpg" + + self.assertRaises(SyntaxError, + lambda: PsdImagePlugin.PsdImageFile(invalid_file)) def test_n_frames(self): im = Image.open("Tests/images/hopper_merged.psd") diff --git a/Tests/test_file_sgi.py b/Tests/test_file_sgi.py index 9842b3991..e78488913 100644 --- a/Tests/test_file_sgi.py +++ b/Tests/test_file_sgi.py @@ -33,7 +33,11 @@ class TestFileSgi(PillowTestCase): self.assertRaises(ValueError, lambda: Image.open(test_file)) def test_invalid_file(self): - self.assertRaises(ValueError, lambda: SgiImagePlugin.SgiImageFile("Tests/images/flower.jpg")) + invalid_file = "Tests/images/flower.jpg" + + self.assertRaises(ValueError, + lambda: + SgiImagePlugin.SgiImageFile(invalid_file)) if __name__ == '__main__': diff --git a/Tests/test_file_sun.py b/Tests/test_file_sun.py index e476ebf13..16c43b921 100644 --- a/Tests/test_file_sun.py +++ b/Tests/test_file_sun.py @@ -16,7 +16,9 @@ class TestFileSun(PillowTestCase): # Assert self.assertEqual(im.size, (128, 128)) - self.assertRaises(SyntaxError, lambda: SunImagePlugin.SunImageFile("Tests/images/flower.jpg")) + invalid_file = "Tests/images/flower.jpg" + self.assertRaises(SyntaxError, + lambda: SunImagePlugin.SunImageFile(invalid_file)) if __name__ == '__main__': diff --git a/Tests/test_file_tga.py b/Tests/test_file_tga.py index cccd9bf96..459e766d5 100644 --- a/Tests/test_file_tga.py +++ b/Tests/test_file_tga.py @@ -30,7 +30,7 @@ class TestFileTga(PillowTestCase): im.convert("RGBA").save(test_file) test_im = Image.open(test_file) self.assertEqual(test_im.size, (100, 100)) - + # Unsupported mode save self.assertRaises(IOError, lambda: im.convert("LA").save(test_file)) diff --git a/Tests/test_file_tiff.py b/Tests/test_file_tiff.py index 98b346016..e1702bcfe 100644 --- a/Tests/test_file_tiff.py +++ b/Tests/test_file_tiff.py @@ -86,7 +86,8 @@ class TestFileTiff(PillowTestCase): try: Image.open('Tests/images/hopper_bad_exif.jpg')._getexif() except struct.error: - self.fail("Bad EXIF data should not pass incorrect values to _binary unpack") + self.fail( + "Bad EXIF data passed incorrect values to _binary unpack") def test_little_endian(self): im = Image.open('Tests/images/16bit.cropped.tif') diff --git a/Tests/test_file_webp_alpha.py b/Tests/test_file_webp_alpha.py index f316b71e1..e7df62ee6 100644 --- a/Tests/test_file_webp_alpha.py +++ b/Tests/test_file_webp_alpha.py @@ -83,7 +83,8 @@ class TestFileWebpAlpha(PillowTestCase): image.load() image.getdata() - # early versions of webp are known to produce higher deviations: deal with it + # early versions of webp are known to produce higher deviations: + # deal with it if _webp.WebPDecoderVersion(self) <= 0x201: self.assert_image_similar(image, pil_image, 3.0) else: diff --git a/Tests/test_file_xpm.py b/Tests/test_file_xpm.py index 3e3c35e18..e589a8d26 100644 --- a/Tests/test_file_xpm.py +++ b/Tests/test_file_xpm.py @@ -19,7 +19,10 @@ class TestFileXpm(PillowTestCase): self.assert_image_similar(im.convert('RGB'), hopper('RGB'), 60) def test_invalid_file(self): - self.assertRaises(SyntaxError, lambda: XpmImagePlugin.XpmImageFile("Tests/images/flower.jpg")) + invalid_file = "Tests/images/flower.jpg" + + self.assertRaises(SyntaxError, + lambda: XpmImagePlugin.XpmImageFile(invalid_file)) def test_load_read(self): # Arrange diff --git a/Tests/test_image_filter.py b/Tests/test_image_filter.py index a62431dd9..f75a1891d 100644 --- a/Tests/test_image_filter.py +++ b/Tests/test_image_filter.py @@ -89,7 +89,7 @@ class TestImageFilter(PillowTestCase): self.assertEqual(rankfilter("F"), (0.0, 4.0, 8.0)) def test_rankfilter_properties(self): - rankfilter = ImageFilter.RankFilter(1,2) + rankfilter = ImageFilter.RankFilter(1, 2) self.assertEqual(rankfilter.size, 1) self.assertEqual(rankfilter.rank, 2) diff --git a/Tests/test_imagecolor.py b/Tests/test_imagecolor.py index d55e73b9c..a38c8b070 100644 --- a/Tests/test_imagecolor.py +++ b/Tests/test_imagecolor.py @@ -18,7 +18,8 @@ class TestImageColor(PillowTestCase): (255, 0, 0, 0), ImageColor.getrgb("rgba(255, 0, 0, 0)")) self.assertEqual((255, 0, 0), ImageColor.getrgb("red")) - self.assertRaises(ValueError, lambda: ImageColor.getrgb("invalid color")) + self.assertRaises(ValueError, + lambda: ImageColor.getrgb("invalid color")) # look for rounding errors (based on code by Tim Hatch) def test_rounding_errors(self): @@ -45,8 +46,8 @@ class TestImageColor(PillowTestCase): self.assertEqual(0, ImageColor.getcolor("black", "L")) self.assertEqual(255, ImageColor.getcolor("white", "L")) - self.assertEqual( - 162, ImageColor.getcolor("rgba(0, 255, 115, 33)", "L")) + self.assertEqual(162, + ImageColor.getcolor("rgba(0, 255, 115, 33)", "L")) Image.new("L", (1, 1), "white") self.assertEqual(0, ImageColor.getcolor("black", "1")) diff --git a/Tests/test_imagedraw.py b/Tests/test_imagedraw.py index 2e746438d..803677616 100644 --- a/Tests/test_imagedraw.py +++ b/Tests/test_imagedraw.py @@ -55,7 +55,8 @@ class TestImageDraw(PillowTestCase): def test_mode_mismatch(self): im = hopper("RGB").copy() - self.assertRaises(ValueError, lambda: ImageDraw.ImageDraw(im, mode="L")) + self.assertRaises(ValueError, + lambda: ImageDraw.ImageDraw(im, mode="L")) def helper_arc(self, bbox): # Arrange diff --git a/Tests/test_imagefont_bitmap.py b/Tests/test_imagefont_bitmap.py index 27141f4b3..a9d745b22 100644 --- a/Tests/test_imagefont_bitmap.py +++ b/Tests/test_imagefont_bitmap.py @@ -5,16 +5,19 @@ from PIL import Image, ImageFont, ImageDraw class TestImageFontBitmap(PillowTestCase): def test_similar(self): text = 'EmbeddedBitmap' - font_outline = ImageFont.truetype(font='Tests/fonts/DejaVuSans.ttf', size=24) - font_bitmap = ImageFont.truetype(font='Tests/fonts/DejaVuSans-bitmap.ttf', size=24) + font_outline = ImageFont.truetype( + font='Tests/fonts/DejaVuSans.ttf', size=24) + font_bitmap = ImageFont.truetype( + font='Tests/fonts/DejaVuSans-bitmap.ttf', size=24) size_outline, size_bitmap = font_outline.getsize(text), font_bitmap.getsize(text) size_final = max(size_outline[0], size_bitmap[0]), max(size_outline[1], size_bitmap[1]) im_bitmap = Image.new('RGB', size_final, (255, 255, 255)) im_outline = im_bitmap.copy() draw_bitmap, draw_outline = ImageDraw.Draw(im_bitmap), ImageDraw.Draw(im_outline) - # Metrics are different on the bitmap and ttf fonts, more so on some platforms - # and versions of freetype than others. Mac has a 1px difference, linux doesn't. + # Metrics are different on the bitmap and ttf fonts, + # more so on some platforms and versions of freetype than others. + # Mac has a 1px difference, linux doesn't. draw_bitmap.text((0, size_final[1] - size_bitmap[1]), text, fill=(0, 0, 0), font=font_bitmap) draw_outline.text((0, size_final[1] - size_outline[1]), diff --git a/Tests/test_imageops_usm.py b/Tests/test_imageops_usm.py index f6eae640b..89c63c774 100644 --- a/Tests/test_imageops_usm.py +++ b/Tests/test_imageops_usm.py @@ -75,7 +75,9 @@ class TestImageOpsUsm(PillowTestCase): (4, 3, 2), (4, 2, 2)]: self.assertGreaterEqual(i.im.getpixel((x, y))[c], 250) # Fuzzy match. - gp = lambda x, y: i.im.getpixel((x, y)) + + def gp(x, y): + return i.im.getpixel((x, y)) self.assertTrue(236 <= gp(7, 4)[0] <= 239) self.assertTrue(236 <= gp(7, 5)[2] <= 239) self.assertTrue(236 <= gp(7, 6)[2] <= 239) diff --git a/Tests/test_imagewin.py b/Tests/test_imagewin.py index 7ceea86ee..8a5bc125f 100644 --- a/Tests/test_imagewin.py +++ b/Tests/test_imagewin.py @@ -115,7 +115,8 @@ class TestImageWinDib(PillowTestCase): # Act/Assert self.assert_warning(DeprecationWarning, dib.tostring) - self.assert_warning(DeprecationWarning, lambda: dib.fromstring(test_buffer)) + self.assert_warning(DeprecationWarning, + lambda: dib.fromstring(test_buffer)) if __name__ == '__main__': diff --git a/Tests/test_scipy.py b/Tests/test_scipy.py index 1632d9475..1d7568148 100644 --- a/Tests/test_scipy.py +++ b/Tests/test_scipy.py @@ -30,10 +30,10 @@ class Test_scipy_resize(PillowTestCase): def test_imresize4(self): im = np.array([[1, 2], [3, 4]]) - res = np.array([[1. , 1.25, 1.75, 2. ], + res = np.array([[1., 1.25, 1.75, 2.], [1.5, 1.75, 2.25, 2.5], [2.5, 2.75, 3.25, 3.5], - [3. , 3.25, 3.75, 4. ]], dtype=np.float32) + [3., 3.25, 3.75, 4.]], dtype=np.float32) # Check that resizing by target size, float and int are the same im2 = misc.imresize(im, (4, 4), mode='F') # output size im3 = misc.imresize(im, 2., mode='F') # fraction From 76d14d6cf142e0888edf42da865d53156078f089 Mon Sep 17 00:00:00 2001 From: Hugo van Kemenade Date: Fri, 3 Jul 2015 09:45:09 +0300 Subject: [PATCH 43/56] Update CHANGES.rst [CI skip] --- CHANGES.rst | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/CHANGES.rst b/CHANGES.rst index 80bc189d8..9a244ea22 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -1,7 +1,19 @@ Changelog (Pillow) ================== -2.9.0 (Unreleased) +3.0.0 (Unreleased) +------------------ + +- Added various tests #1330 + [radarhere] + +- More ImageFont tests #1327 + [hugovk] + +- Use logging instead of print #1207 + [anntzer] + +2.9.0 (2015-07-01) ------------------ - Added test for GimpPaletteFile #1324 From d92879f379ae6d8ee8fecae817fa4004b8b06df0 Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Wed, 8 Jul 2015 14:04:02 +1000 Subject: [PATCH 44/56] Updated example Tcl version numbers --- setup.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.py b/setup.py index ba95cdd58..34277b09c 100644 --- a/setup.py +++ b/setup.py @@ -468,7 +468,7 @@ class pil_build_ext(build_ext): feature.lcms = "lcms2_static" if _tkinter and _find_include_file(self, "tk.h"): - # the library names may vary somewhat (e.g. tcl84 or tcl8.4) + # the library names may vary somewhat (e.g. tcl85 or tcl8.5) version = TCL_VERSION[0] + TCL_VERSION[2] if feature.want('tcl'): if _find_library_file(self, "tcl" + version): From 7e991a804389d27834ff984d9a4cb5d8eea89576 Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Wed, 8 Jul 2015 15:32:24 +1000 Subject: [PATCH 45/56] Allow Tcl/Tk frameworks to be disabled on OS X by setup arguments --- setup.py | 49 +++++++++++++++++++++++++------------------------ 1 file changed, 25 insertions(+), 24 deletions(-) diff --git a/setup.py b/setup.py index 34277b09c..69c65ddb9 100644 --- a/setup.py +++ b/setup.py @@ -571,32 +571,33 @@ class pil_build_ext(build_ext): exts.append(Extension( "PIL._webp", ["_webp.c"], libraries=libs, define_macros=defs)) - if sys.platform == "darwin": - # locate Tcl/Tk frameworks - frameworks = [] - framework_roots = [ - "/Library/Frameworks", - "/System/Library/Frameworks"] - for root in framework_roots: - if ( - os.path.exists(os.path.join(root, "Tcl.framework")) and - os.path.exists(os.path.join(root, "Tk.framework"))): - print("--- using frameworks at %s" % root) - frameworks = ["-framework", "Tcl", "-framework", "Tk"] - dir = os.path.join(root, "Tcl.framework", "Headers") - _add_directory(self.compiler.include_dirs, dir, 0) - dir = os.path.join(root, "Tk.framework", "Headers") - _add_directory(self.compiler.include_dirs, dir, 1) - break - if frameworks: + if feature.tcl and feature.tk: + if sys.platform == "darwin": + # locate Tcl/Tk frameworks + frameworks = [] + framework_roots = [ + "/Library/Frameworks", + "/System/Library/Frameworks"] + for root in framework_roots: + root_tcl = os.path.join(root, "Tcl.framework") + root_tk = os.path.join(root, "Tk.framework") + if (os.path.exists(root_tcl) and os.path.exists(root_tk)): + print("--- using frameworks at %s" % root) + frameworks = ["-framework", "Tcl", "-framework", "Tk"] + dir = os.path.join(root_tcl, "Headers") + _add_directory(self.compiler.include_dirs, dir, 0) + dir = os.path.join(root_tk, "Headers") + _add_directory(self.compiler.include_dirs, dir, 1) + break + if frameworks: + exts.append(Extension( + "PIL._imagingtk", ["_imagingtk.c", "Tk/tkImaging.c"], + extra_compile_args=frameworks, + extra_link_args=frameworks)) + else: exts.append(Extension( "PIL._imagingtk", ["_imagingtk.c", "Tk/tkImaging.c"], - extra_compile_args=frameworks, extra_link_args=frameworks)) - feature.tcl = feature.tk = 1 # mark as present - elif feature.tcl and feature.tk: - exts.append(Extension( - "PIL._imagingtk", ["_imagingtk.c", "Tk/tkImaging.c"], - libraries=[feature.tcl, feature.tk])) + libraries=[feature.tcl, feature.tk])) if os.path.isfile("_imagingmath.c"): exts.append(Extension("PIL._imagingmath", ["_imagingmath.c"])) From 60a3702fd52c7bb2fe2d67038754923dcbc766d1 Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Wed, 8 Jul 2015 15:32:50 +1000 Subject: [PATCH 46/56] Flake8 fix --- setup.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.py b/setup.py index 69c65ddb9..edfd24c41 100644 --- a/setup.py +++ b/setup.py @@ -464,7 +464,7 @@ class pil_build_ext(build_ext): if _find_library_file(self, "lcms2"): feature.lcms = "lcms2" elif _find_library_file(self, "lcms2_static"): - #alternate Windows name. + # alternate Windows name. feature.lcms = "lcms2_static" if _tkinter and _find_include_file(self, "tk.h"): From 9c20c6d38aabc7be82deb2aa3032b8069315592b Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Wed, 8 Jul 2015 23:08:00 +1000 Subject: [PATCH 47/56] Added service argument to coveralls image URL [ci skip] --- README.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.rst b/README.rst index d39070bbe..169547be1 100644 --- a/README.rst +++ b/README.rst @@ -26,7 +26,7 @@ Pillow is the "friendly PIL fork" by `Alex Clark and Contributors Date: Sun, 12 Jul 2015 17:42:55 +0300 Subject: [PATCH 48/56] Update Image.py --- PIL/Image.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/PIL/Image.py b/PIL/Image.py index 28289d0d9..861599bf7 100644 --- a/PIL/Image.py +++ b/PIL/Image.py @@ -2265,7 +2265,7 @@ def open(fp, mode="r"): :py:meth:`~PIL.Image.Image.load` method). See :py:func:`~PIL.Image.new`. - :param file: A filename (string) or a file object. The file object + :param fp: A filename (string) or a file object. The file object must implement :py:meth:`~file.read`, :py:meth:`~file.seek`, and :py:meth:`~file.tell` methods, and be opened in binary mode. :param mode: The mode. If given, this argument must be "r". From c7339c244e0254e9c648efd686877657d9df1d88 Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Mon, 13 Jul 2015 23:37:44 +1000 Subject: [PATCH 49/56] Added additional tests for TiffImagePlugin --- Tests/test_file_tiff.py | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/Tests/test_file_tiff.py b/Tests/test_file_tiff.py index e1702bcfe..08f1f1880 100644 --- a/Tests/test_file_tiff.py +++ b/Tests/test_file_tiff.py @@ -82,6 +82,13 @@ class TestFileTiff(PillowTestCase): im._setup() self.assertEqual(im.info['dpi'], (72., 72.)) + def test_invalid_file(self): + invalid_file = "Tests/images/flower.jpg" + + self.assertRaises(SyntaxError, + lambda: TiffImagePlugin.TiffImageFile(invalid_file)) + + def test_bad_exif(self): try: Image.open('Tests/images/hopper_bad_exif.jpg')._getexif() @@ -89,6 +96,12 @@ class TestFileTiff(PillowTestCase): self.fail( "Bad EXIF data passed incorrect values to _binary unpack") + def test_save_unsupported_mode(self): + im = hopper("HSV") + outfile = self.tempfile("temp.tif") + + self.assertRaises(IOError, lambda: im.save(outfile)) + def test_little_endian(self): im = Image.open('Tests/images/16bit.cropped.tif') self.assertEqual(im.getpixel((0, 0)), 480) From 4666fcf8bedf865a3ace3459da37e471563f3a16 Mon Sep 17 00:00:00 2001 From: Hugo van Kemenade Date: Mon, 13 Jul 2015 20:21:14 +0300 Subject: [PATCH 50/56] Update CHANGES.rst [CI skip] --- CHANGES.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGES.rst b/CHANGES.rst index 9a244ea22..53524da1f 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -4,7 +4,7 @@ Changelog (Pillow) 3.0.0 (Unreleased) ------------------ -- Added various tests #1330 +- Added various tests #1330, #1344 [radarhere] - More ImageFont tests #1327 From 7598f6c955c2211e2a4acc96bc8d4b31cf8ba943 Mon Sep 17 00:00:00 2001 From: Hugo van Kemenade Date: Wed, 15 Jul 2015 14:00:47 +0300 Subject: [PATCH 51/56] Add missing fill param And re-order params to match method. [CI skip] --- docs/reference/ImageDraw.rst | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/docs/reference/ImageDraw.rst b/docs/reference/ImageDraw.rst index e030147e9..a56457be9 100644 --- a/docs/reference/ImageDraw.rst +++ b/docs/reference/ImageDraw.rst @@ -234,8 +234,9 @@ Methods :param xy: Top left corner of the text. :param text: Text to be drawn. If it contains any newline characters, the text is passed on to mulitiline_text() - :param font: An :py:class:`~PIL.ImageFont.ImageFont` instance. :param fill: Color to use for the text. + :param font: An :py:class:`~PIL.ImageFont.ImageFont` instance. + .. py:method:: PIL.ImageDraw.Draw.multiline_text(xy, text, fill=None, font=None, anchor=None, spacing=0, align="left") @@ -244,6 +245,7 @@ Methods :param xy: Top left corner of the text. :param text: Text to be drawn. If it contains any newline characters, the text is split and passed on to mulitiline_text() + :param fill: Color to use for the text. :param font: An :py:class:`~PIL.ImageFont.ImageFont` instance. :param spacing: The number of pixels between lines. :param align: "left", "center" or "right". From d072ee469c72f13e78f54a4523ff461468137f86 Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Wed, 15 Jul 2015 22:43:52 +1000 Subject: [PATCH 52/56] Corrected ImageDraw multiline text param documentation [ci skip] --- docs/reference/ImageDraw.rst | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/docs/reference/ImageDraw.rst b/docs/reference/ImageDraw.rst index a56457be9..1c9c242c7 100644 --- a/docs/reference/ImageDraw.rst +++ b/docs/reference/ImageDraw.rst @@ -243,8 +243,7 @@ Methods Draws the string at the given position. :param xy: Top left corner of the text. - :param text: Text to be drawn. If it contains any newline characters, - the text is split and passed on to mulitiline_text() + :param text: Text to be drawn. :param fill: Color to use for the text. :param font: An :py:class:`~PIL.ImageFont.ImageFont` instance. :param spacing: The number of pixels between lines. @@ -262,8 +261,7 @@ Methods Return the size of the given string, in pixels. - :param text: Text to be measured. If it contains any newline characters, - the text is split and passed on to mulitiline_textsize() + :param text: Text to be measured. :param font: An :py:class:`~PIL.ImageFont.ImageFont` instance. :param spacing: The number of pixels between lines. From 2a3a34d2ff9a691836aab50c0b88ae6c5faa58c7 Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Sun, 19 Jul 2015 22:56:04 +1000 Subject: [PATCH 53/56] Treat MPO with unknown header as base JPEG file --- PIL/JpegImagePlugin.py | 7 ++++++- Tests/images/sugarshack_bad_mpo_header.jpg | Bin 0 -> 120198 bytes Tests/test_file_jpeg.py | 11 +++++++++++ 3 files changed, 17 insertions(+), 1 deletion(-) create mode 100644 Tests/images/sugarshack_bad_mpo_header.jpg diff --git a/PIL/JpegImagePlugin.py b/PIL/JpegImagePlugin.py index 5cae90073..7cb280764 100644 --- a/PIL/JpegImagePlugin.py +++ b/PIL/JpegImagePlugin.py @@ -37,6 +37,7 @@ __version__ = "0.6" import array import struct import io +import warnings from struct import unpack from PIL import Image, ImageFile, TiffImagePlugin, _binary from PIL.JpegPresets import presets @@ -713,8 +714,8 @@ def _save_cjpeg(im, fp, filename): # Factory for making JPEG and MPO instances def jpeg_factory(fp=None, filename=None): im = JpegImageFile(fp, filename) - mpheader = im._getmp() try: + mpheader = im._getmp() if mpheader[45057] > 1: # It's actually an MPO from .MpoImagePlugin import MpoImageFile @@ -722,6 +723,10 @@ def jpeg_factory(fp=None, filename=None): except (TypeError, IndexError): # It is really a JPEG pass + except SyntaxError: + warnings.warn("Image appears to be a malformed MPO file, it will be " + "interpreted as a base JPEG file") + pass return im diff --git a/Tests/images/sugarshack_bad_mpo_header.jpg b/Tests/images/sugarshack_bad_mpo_header.jpg new file mode 100644 index 0000000000000000000000000000000000000000..6e2d75ab62847a721abe90d3051fae49dd034675 GIT binary patch literal 120198 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 A!2kdN literal 0 HcmV?d00001 diff --git a/Tests/test_file_jpeg.py b/Tests/test_file_jpeg.py index bb2d7b61e..25c599a17 100644 --- a/Tests/test_file_jpeg.py +++ b/Tests/test_file_jpeg.py @@ -353,6 +353,17 @@ class TestFileJpeg(PillowTestCase): reloaded.save(f, quality='keep', progressive=True) reloaded.save(f, quality='keep', optimize=True) + def test_bad_mpo_header(self): + """ Treat unknown MPO as JPEG """ + # Arrange + + # Act + # Shouldn't raise error + im = Image.open("Tests/images/sugarshack_bad_mpo_header.jpg") + + # Assert + self.assertEqual(im.format, "JPEG") + if __name__ == '__main__': unittest.main() From eee98f8043353040eefb645a1c8fc9704ff99940 Mon Sep 17 00:00:00 2001 From: Hugo van Kemenade Date: Sun, 19 Jul 2015 20:27:53 +0300 Subject: [PATCH 54/56] Update CHANGES.rst [CI skip] --- CHANGES.rst | 3 +++ 1 file changed, 3 insertions(+) diff --git a/CHANGES.rst b/CHANGES.rst index 53524da1f..c3f7ebad8 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -4,6 +4,9 @@ Changelog (Pillow) 3.0.0 (Unreleased) ------------------ +- Treat MPO with unknown header as base JPEG file #1350 + [hugovk, radarhere] + - Added various tests #1330, #1344 [radarhere] From 31edcad8f84b7a7e9bde085f34f00c3c010270b2 Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Tue, 21 Jul 2015 18:23:55 +1000 Subject: [PATCH 55/56] Set landscape max-line-length --- .landscape.yaml | 1 + 1 file changed, 1 insertion(+) diff --git a/.landscape.yaml b/.landscape.yaml index c869da5b4..ddd9cef32 100644 --- a/.landscape.yaml +++ b/.landscape.yaml @@ -1,2 +1,3 @@ strictness: medium test-warnings: yes +max-line-length: 80 From a90e72076a60670b10db9ce7b87f243a4fa132cc Mon Sep 17 00:00:00 2001 From: Anton Vlasenko Date: Thu, 23 Jul 2015 11:29:26 +0200 Subject: [PATCH 56/56] Fixing typo to have proper testing --- Tests/test_file_gif.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Tests/test_file_gif.py b/Tests/test_file_gif.py index c40e5f6b0..70438eb03 100644 --- a/Tests/test_file_gif.py +++ b/Tests/test_file_gif.py @@ -93,7 +93,7 @@ class TestFileGif(PillowTestCase): im.save(out, save_all=True) reread = Image.open(out) - self.assertEqual(im.n_frames, 5) + self.assertEqual(reread.n_frames, 5) def test_palette_handling(self): # see https://github.com/python-pillow/Pillow/issues/513