From af770a6c555013c87d7c208fb610886fef59d5b8 Mon Sep 17 00:00:00 2001 From: Hugo Date: Thu, 26 Sep 2019 15:12:28 +0300 Subject: [PATCH 01/59] Drop support for EOL Python 2.7 --- .appveyor.yml | 11 --- .github/CONTRIBUTING.md | 4 +- .travis.yml | 8 +- .travis/after_success.sh | 10 +-- .travis/install.sh | 4 +- .travis/script.sh | 2 +- Makefile | 36 ++++---- Tests/helper.py | 6 -- Tests/test_file_iptc.py | 9 +- Tests/test_file_libtiff.py | 17 +--- Tests/test_file_png.py | 12 ++- Tests/test_file_tiff.py | 17 +--- Tests/test_font_pcf.py | 6 +- Tests/test_format_hsv.py | 24 ++---- Tests/test_image.py | 11 +-- Tests/test_image_access.py | 6 -- Tests/test_image_getim.py | 6 +- Tests/test_imagepath.py | 6 +- Tests/test_imagetk.py | 7 +- Tests/test_psdraw.py | 5 +- mp_compile.py | 93 --------------------- setup.py | 15 +--- src/PIL/EpsImagePlugin.py | 14 +--- src/PIL/Image.py | 58 +++---------- src/PIL/ImageFont.py | 7 +- src/PIL/ImageMath.py | 22 +---- src/PIL/ImageQt.py | 7 +- src/PIL/ImageShow.py | 6 +- src/PIL/ImageTk.py | 8 +- src/PIL/PSDraw.py | 3 +- src/PIL/PdfParser.py | 50 +++-------- src/PIL/PngImagePlugin.py | 26 +++--- src/PIL/SgiImagePlugin.py | 4 +- src/PIL/TarIO.py | 7 +- src/PIL/TiffImagePlugin.py | 37 ++------- src/PIL/WalImageFile.py | 9 +- src/PIL/WmfImagePlugin.py | 4 - src/PIL/_binary.py | 19 +---- src/PIL/_tkinter_finder.py | 6 +- src/PIL/_util.py | 23 ++---- src/Tk/tkImaging.c | 8 -- src/_imaging.c | 105 ++++++------------------ src/_imagingcms.c | 34 ++------ src/_imagingft.c | 103 ++++------------------- src/_imagingmath.c | 13 +-- src/_imagingmorph.c | 11 --- src/_imagingtk.c | 10 --- src/_webp.c | 16 +--- src/decode.c | 3 +- src/display.c | 13 +-- src/encode.c | 43 +++++----- src/libImaging/codec_fd.c | 3 +- src/map.c | 2 - src/path.c | 19 ++--- src/py3.h | 56 ------------- tox.ini | 2 +- winbuild/appveyor_install_msys2_deps.sh | 14 ++-- winbuild/appveyor_install_pypy2.cmd | 3 - winbuild/build.py | 5 +- winbuild/config.py | 4 +- winbuild/get_pythons.py | 2 +- 61 files changed, 217 insertions(+), 877 deletions(-) delete mode 100644 mp_compile.py delete mode 100644 src/py3.h delete mode 100644 winbuild/appveyor_install_pypy2.cmd diff --git a/.appveyor.yml b/.appveyor.yml index f299794b6..27c7d7597 100644 --- a/.appveyor.yml +++ b/.appveyor.yml @@ -13,13 +13,7 @@ environment: TEST_OPTIONS: DEPLOY: YES matrix: - - PYTHON: C:/vp/pypy2 - EXECUTABLE: bin/pypy.exe - PIP_DIR: bin - VENV: YES - - PYTHON: C:/Python27-x64 - PYTHON: C:/Python37 - - PYTHON: C:/Python27 - PYTHON: C:/Python37-x64 - PYTHON: C:/Python36 - PYTHON: C:/Python36-x64 @@ -44,11 +38,6 @@ install: - xcopy c:\pillow-depends\*.tar.gz c:\pillow\winbuild\ - xcopy /s c:\pillow-depends\test_images\* c:\pillow\tests\images - cd c:\pillow\winbuild\ -- ps: | - if ($env:PYTHON -eq "c:/vp/pypy2") - { - c:\pillow\winbuild\appveyor_install_pypy2.cmd - } - ps: | if ($env:PYTHON -eq "c:/vp/pypy3") { diff --git a/.github/CONTRIBUTING.md b/.github/CONTRIBUTING.md index b3d456659..3d27b5d88 100644 --- a/.github/CONTRIBUTING.md +++ b/.github/CONTRIBUTING.md @@ -9,14 +9,14 @@ Please send a pull request to the master branch. Please include [documentation]( - Fork the Pillow repository. - Create a branch from master. - Develop bug fixes, features, tests, etc. -- Run the test suite on Python 2.7 and 3.x. You can enable [Travis CI](https://travis-ci.org/profile/) and [AppVeyor](https://ci.appveyor.com/projects/new) on your repo to catch test failures prior to the pull request, and [Codecov](https://codecov.io/gh) to see if the changed code is covered by tests. +- Run the test suite. You can enable [Travis CI](https://travis-ci.org/profile/) and [AppVeyor](https://ci.appveyor.com/projects/new) on your repo to catch test failures prior to the pull request, and [Codecov](https://codecov.io/gh) to see if the changed code is covered by tests. - Create a pull request to pull the changes from your branch to the Pillow master. ### Guidelines - Separate code commits from reformatting commits. - Provide tests for any newly added code. -- Follow PEP8. +- Follow PEP 8. - When committing only documentation changes please include [ci skip] in the commit message to avoid running tests on Travis-CI and AppVeyor. ## Reporting Issues diff --git a/.travis.yml b/.travis.yml index 286f828f2..232688971 100644 --- a/.travis.yml +++ b/.travis.yml @@ -6,8 +6,8 @@ notifications: irc: "chat.freenode.net#pil" # Run fast lint first to get fast feedback. -# Run slow PyPy* next, to give them a headstart and reduce waiting time. -# Run latest 3.x and 2.x next, to get quick compatibility results. +# Run slow PyPy next, to give it a headstart and reduce waiting time. +# Run latest 3.x next, to get quick compatibility results. # Then run the remainder, with fastest Docker jobs last. matrix: @@ -16,15 +16,11 @@ matrix: - python: "3.6" name: "Lint" env: LINT="true" - - python: "pypy" - name: "PyPy2 Xenial" - python: "pypy3" name: "PyPy3 Xenial" - python: '3.7' name: "3.7 Xenial" services: xvfb - - python: '2.7' - name: "2.7 Xenial" - python: '3.6' name: "3.6 Xenial PYTHONOPTIMIZE=1" env: PYTHONOPTIMIZE=1 diff --git a/.travis/after_success.sh b/.travis/after_success.sh index 1dca2ccb9..721a469b6 100755 --- a/.travis/after_success.sh +++ b/.travis/after_success.sh @@ -11,16 +11,12 @@ coveralls-lcov -v -n coverage.filtered.info > coverage.c.json coverage report pip install codecov -if [[ $TRAVIS_PYTHON_VERSION != "2.7_with_system_site_packages" ]]; then - # Not working here. Just skip it, it's being removed soon. - pip install coveralls-merge - coveralls-merge coverage.c.json -fi +pip install coveralls-merge +coveralls-merge coverage.c.json codecov -if [ "$TRAVIS_PYTHON_VERSION" == "2.7" ] && [ "$DOCKER" == "" ]; then +if [ "$TRAVIS_PYTHON_VERSION" == "3.7" ] && [ "$DOCKER" == "" ]; then # Coverage and quality reports on just the latest diff. - # (Installation is very slow on Py3, so just do it for Py2.) depends/diffcover-install.sh depends/diffcover-run.sh fi diff --git a/.travis/install.sh b/.travis/install.sh index 6ad89402e..747acb448 100755 --- a/.travis/install.sh +++ b/.travis/install.sh @@ -20,8 +20,8 @@ if [[ $TRAVIS_PYTHON_VERSION == 3.* ]]; then pip install pyqt5 fi -# docs only on Python 2.7 -if [ "$TRAVIS_PYTHON_VERSION" == "2.7" ]; then pip install -r requirements.txt ; fi +# docs only on Python 3.7 +if [ "$TRAVIS_PYTHON_VERSION" == "3.7" ]; then pip install -r requirements.txt ; fi # webp pushd depends && ./install_webp.sh && popd diff --git a/.travis/script.sh b/.travis/script.sh index af56cc6ab..c2b903601 100755 --- a/.travis/script.sh +++ b/.travis/script.sh @@ -9,4 +9,4 @@ make install-coverage python -m pytest -v -x --cov PIL --cov-report term Tests # Docs -if [ "$TRAVIS_PYTHON_VERSION" == "2.7" ]; then make doccheck; fi +if [ "$TRAVIS_PYTHON_VERSION" == "3.7" ]; then make doccheck; fi diff --git a/Makefile b/Makefile index 1803e617d..fdde3416b 100644 --- a/Makefile +++ b/Makefile @@ -3,7 +3,7 @@ .DEFAULT_GOAL := release-test clean: - python setup.py clean + python3 setup.py clean rm src/PIL/*.so || true rm -r build || true find . -name __pycache__ | xargs rm -r || true @@ -15,8 +15,8 @@ co: done coverage: - python selftest.py - python setup.py test + python3 selftest.py + python3 setup.py test rm -r htmlcov || true coverage report @@ -30,7 +30,7 @@ doccheck: $(MAKE) -C docs linkcheck || true docserve: - cd docs/_build/html && python -mSimpleHTTPServer 2> /dev/null& + cd docs/_build/html && python3 -mSimpleHTTPServer 2> /dev/null& help: @echo "Welcome to Pillow development. Please use \`make \` where is one of" @@ -50,22 +50,22 @@ help: @echo " upload-test build and upload sdists to test.pythonpackages.com" inplace: clean - python setup.py develop build_ext --inplace + python3 setup.py develop build_ext --inplace install: - python setup.py install - python selftest.py + python3 setup.py install + python3 selftest.py install-coverage: - CFLAGS="-coverage" python setup.py build_ext install - python selftest.py + CFLAGS="-coverage" python3 setup.py build_ext install + python3 selftest.py debug: # make a debug version if we don't have a -dbg python. Leaves in symbols # for our stuff, kills optimization, and redirects to dev null so we # see any build failures. make clean > /dev/null - CFLAGS='-g -O0' python setup.py build_ext install > /dev/null + CFLAGS='-g -O0' python3 setup.py build_ext install > /dev/null install-req: pip install -r requirements.txt @@ -76,17 +76,17 @@ install-venv: release-test: $(MAKE) install-req - python setup.py develop - python selftest.py - python -m pytest Tests - python setup.py install - python -m pytest -qq + python3 setup.py develop + python3 selftest.py + python3 -m pytest Tests + python3 setup.py install + python3 -m pytest -qq check-manifest pyroma . viewdoc sdist: - python setup.py sdist --format=gztar + python3 setup.py sdist --format=gztar test: pytest -qq @@ -97,10 +97,10 @@ upload-test: # username: # password: # repository = http://test.pythonpackages.com - python setup.py sdist --format=gztar upload -r test + python3 setup.py sdist --format=gztar upload -r test upload: - python setup.py sdist --format=gztar upload + python3 setup.py sdist --format=gztar upload readme: viewdoc diff --git a/Tests/helper.py b/Tests/helper.py index 78a2f520f..87f034449 100644 --- a/Tests/helper.py +++ b/Tests/helper.py @@ -10,7 +10,6 @@ import tempfile import unittest from PIL import Image, ImageMath -from PIL._util import py3 logger = logging.getLogger(__name__) @@ -277,11 +276,6 @@ class PillowLeakTestCase(PillowTestCase): # helpers -if not py3: - # Remove DeprecationWarning in Python 3 - PillowTestCase.assertRaisesRegex = PillowTestCase.assertRaisesRegexp - PillowTestCase.assertRegex = PillowTestCase.assertRegexpMatches - def fromstring(data): from io import BytesIO diff --git a/Tests/test_file_iptc.py b/Tests/test_file_iptc.py index 800563af1..c1c6ecbad 100644 --- a/Tests/test_file_iptc.py +++ b/Tests/test_file_iptc.py @@ -1,3 +1,6 @@ +import sys +from io import StringIO + from PIL import Image, IptcImagePlugin from .helper import PillowTestCase, hopper @@ -52,12 +55,6 @@ class TestFileIptc(PillowTestCase): # Arrange c = b"abc" # Temporarily redirect stdout - try: - from cStringIO import StringIO - except ImportError: - from io import StringIO - import sys - old_stdout = sys.stdout sys.stdout = mystdout = StringIO() diff --git a/Tests/test_file_libtiff.py b/Tests/test_file_libtiff.py index ea73a7ad5..c1cce1936 100644 --- a/Tests/test_file_libtiff.py +++ b/Tests/test_file_libtiff.py @@ -9,7 +9,6 @@ from collections import namedtuple from ctypes import c_float from PIL import Image, TiffImagePlugin, TiffTags, features -from PIL._util import py3 from .helper import PillowTestCase, hopper @@ -361,12 +360,8 @@ class TestFileLibTiff(LibTiffTestCase): b = im.tobytes() # Bytes are in image native order (little endian) - if py3: - self.assertEqual(b[0], ord(b"\xe0")) - self.assertEqual(b[1], ord(b"\x01")) - else: - self.assertEqual(b[0], b"\xe0") - self.assertEqual(b[1], b"\x01") + self.assertEqual(b[0], ord(b"\xe0")) + self.assertEqual(b[1], ord(b"\x01")) out = self.tempfile("temp.tif") # out = "temp.le.tif" @@ -387,12 +382,8 @@ class TestFileLibTiff(LibTiffTestCase): b = im.tobytes() # Bytes are in image native order (big endian) - if py3: - self.assertEqual(b[0], ord(b"\x01")) - self.assertEqual(b[1], ord(b"\xe0")) - else: - self.assertEqual(b[0], b"\x01") - self.assertEqual(b[1], b"\xe0") + self.assertEqual(b[0], ord(b"\x01")) + self.assertEqual(b[1], ord(b"\xe0")) out = self.tempfile("temp.tif") im.save(out) diff --git a/Tests/test_file_png.py b/Tests/test_file_png.py index 6d76a6caa..3f5f003ac 100644 --- a/Tests/test_file_png.py +++ b/Tests/test_file_png.py @@ -3,7 +3,6 @@ import zlib from io import BytesIO from PIL import Image, ImageFile, PngImagePlugin -from PIL._util import py3 from .helper import PillowLeakTestCase, PillowTestCase, hopper, unittest @@ -460,12 +459,11 @@ class TestFilePng(PillowTestCase): im = roundtrip(im, pnginfo=info) self.assertEqual(im.info, {"Text": value}) - if py3: - rt_text(" Aa" + chr(0xA0) + chr(0xC4) + chr(0xFF)) # Latin1 - rt_text(chr(0x400) + chr(0x472) + chr(0x4FF)) # Cyrillic - # CJK: - rt_text(chr(0x4E00) + chr(0x66F0) + chr(0x9FBA) + chr(0x3042) + chr(0xAC00)) - rt_text("A" + chr(0xC4) + chr(0x472) + chr(0x3042)) # Combined + rt_text(" Aa" + chr(0xA0) + chr(0xC4) + chr(0xFF)) # Latin1 + rt_text(chr(0x400) + chr(0x472) + chr(0x4FF)) # Cyrillic + # CJK: + rt_text(chr(0x4E00) + chr(0x66F0) + chr(0x9FBA) + chr(0x3042) + chr(0xAC00)) + rt_text("A" + chr(0xC4) + chr(0x472) + chr(0x3042)) # Combined def test_scary(self): # Check reading of evil PNG file. For information, see: diff --git a/Tests/test_file_tiff.py b/Tests/test_file_tiff.py index 048539f86..9919bacf8 100644 --- a/Tests/test_file_tiff.py +++ b/Tests/test_file_tiff.py @@ -3,7 +3,6 @@ import sys from io import BytesIO from PIL import Image, TiffImagePlugin -from PIL._util import py3 from PIL.TiffImagePlugin import RESOLUTION_UNIT, X_RESOLUTION, Y_RESOLUTION from .helper import PillowTestCase, hopper, unittest @@ -176,12 +175,8 @@ class TestFileTiff(PillowTestCase): b = im.tobytes() # Bytes are in image native order (little endian) - if py3: - self.assertEqual(b[0], ord(b"\xe0")) - self.assertEqual(b[1], ord(b"\x01")) - else: - self.assertEqual(b[0], b"\xe0") - self.assertEqual(b[1], b"\x01") + self.assertEqual(b[0], ord(b"\xe0")) + self.assertEqual(b[1], ord(b"\x01")) def test_big_endian(self): im = Image.open("Tests/images/16bit.MM.cropped.tif") @@ -191,12 +186,8 @@ class TestFileTiff(PillowTestCase): b = im.tobytes() # Bytes are in image native order (big endian) - if py3: - self.assertEqual(b[0], ord(b"\x01")) - self.assertEqual(b[1], ord(b"\xe0")) - else: - self.assertEqual(b[0], b"\x01") - self.assertEqual(b[1], b"\xe0") + self.assertEqual(b[0], ord(b"\x01")) + self.assertEqual(b[1], ord(b"\xe0")) def test_16bit_s(self): im = Image.open("Tests/images/16bit.s.tif") diff --git a/Tests/test_font_pcf.py b/Tests/test_font_pcf.py index a2b4ef27e..e37f43207 100644 --- a/Tests/test_font_pcf.py +++ b/Tests/test_font_pcf.py @@ -1,5 +1,4 @@ from PIL import FontFile, Image, ImageDraw, ImageFont, PcfFontFile -from PIL._util import py3 from .helper import PillowTestCase @@ -74,6 +73,5 @@ class TestFontPcf(PillowTestCase): def test_high_characters(self): message = "".join(chr(i + 1) for i in range(140, 232)) self._test_high_characters(message) - # accept bytes instances in Py3. - if py3: - self._test_high_characters(message.encode("latin1")) + # accept bytes instances. + self._test_high_characters(message.encode("latin1")) diff --git a/Tests/test_format_hsv.py b/Tests/test_format_hsv.py index ce0524e1e..e80e16ffe 100644 --- a/Tests/test_format_hsv.py +++ b/Tests/test_format_hsv.py @@ -2,7 +2,6 @@ import colorsys import itertools from PIL import Image -from PIL._util import py3 from .helper import PillowTestCase, hopper @@ -49,27 +48,18 @@ class TestFormatHSV(PillowTestCase): (r, g, b) = im.split() - if py3: - conv_func = self.int_to_float - else: - conv_func = self.str_to_float - - if hasattr(itertools, "izip"): - iter_helper = itertools.izip - else: - iter_helper = itertools.zip_longest + conv_func = self.int_to_float converted = [ self.tuple_to_ints(func(conv_func(_r), conv_func(_g), conv_func(_b))) - for (_r, _g, _b) in iter_helper(r.tobytes(), g.tobytes(), b.tobytes()) + for (_r, _g, _b) in itertools.zip_longest( + r.tobytes(), g.tobytes(), b.tobytes() + ) ] - if py3: - new_bytes = b"".join( - bytes(chr(h) + chr(s) + chr(v), "latin-1") for (h, s, v) in converted - ) - else: - new_bytes = b"".join(chr(h) + chr(s) + chr(v) for (h, s, v) in converted) + new_bytes = b"".join( + bytes(chr(h) + chr(s) + chr(v), "latin-1") for (h, s, v) in converted + ) hsv = Image.frombytes(mode, r.size, new_bytes) diff --git a/Tests/test_image.py b/Tests/test_image.py index 47196a139..53f0199de 100644 --- a/Tests/test_image.py +++ b/Tests/test_image.py @@ -3,7 +3,6 @@ import shutil import sys from PIL import Image -from PIL._util import py3 from .helper import PillowTestCase, hopper, unittest @@ -80,20 +79,14 @@ class TestImage(PillowTestCase): im.size = (3, 4) def test_invalid_image(self): - if py3: - import io + import io - im = io.BytesIO(b"") - else: - import StringIO - - im = StringIO.StringIO("") + im = io.BytesIO(b"") self.assertRaises(IOError, Image.open, im) def test_bad_mode(self): self.assertRaises(ValueError, Image.open, "filename", "bad mode") - @unittest.skipUnless(Image.HAS_PATHLIB, "requires pathlib/pathlib2") def test_pathlib(self): from PIL.Image import Path diff --git a/Tests/test_image_access.py b/Tests/test_image_access.py index b06814cb9..4004ca5df 100644 --- a/Tests/test_image_access.py +++ b/Tests/test_image_access.py @@ -349,12 +349,8 @@ class TestEmbeddable(unittest.TestCase): int main(int argc, char* argv[]) { char *home = "%s"; -#if PY_MAJOR_VERSION >= 3 wchar_t *whome = Py_DecodeLocale(home, NULL); Py_SetPythonHome(whome); -#else - Py_SetPythonHome(home); -#endif Py_InitializeEx(0); Py_DECREF(PyImport_ImportModule("PIL.Image")); @@ -364,9 +360,7 @@ int main(int argc, char* argv[]) Py_DECREF(PyImport_ImportModule("PIL.Image")); Py_Finalize(); -#if PY_MAJOR_VERSION >= 3 PyMem_RawFree(whome); -#endif return 0; } diff --git a/Tests/test_image_getim.py b/Tests/test_image_getim.py index 3f0c46c46..f6908af4b 100644 --- a/Tests/test_image_getim.py +++ b/Tests/test_image_getim.py @@ -1,5 +1,3 @@ -from PIL._util import py3 - from .helper import PillowTestCase, hopper @@ -8,7 +6,5 @@ class TestImageGetIm(PillowTestCase): im = hopper() type_repr = repr(type(im.getim())) - if py3: - self.assertIn("PyCapsule", type_repr) - + self.assertIn("PyCapsule", type_repr) self.assertIsInstance(im.im.id, int) diff --git a/Tests/test_imagepath.py b/Tests/test_imagepath.py index ed65d47c1..35c78cd3b 100644 --- a/Tests/test_imagepath.py +++ b/Tests/test_imagepath.py @@ -2,7 +2,6 @@ import array import struct from PIL import Image, ImagePath -from PIL._util import py3 from .helper import PillowTestCase @@ -75,10 +74,7 @@ class TestImagePath(PillowTestCase): # This fails due to the invalid malloc above, # and segfaults for i in range(200000): - if py3: - x[i] = b"0" * 16 - else: - x[i] = "0" * 16 + x[i] = b"0" * 16 class evil: diff --git a/Tests/test_imagetk.py b/Tests/test_imagetk.py index c397c84be..808ebd392 100644 --- a/Tests/test_imagetk.py +++ b/Tests/test_imagetk.py @@ -1,15 +1,12 @@ from PIL import Image -from PIL._util import py3 from .helper import PillowTestCase, hopper, unittest try: from PIL import ImageTk - if py3: - import tkinter as tk - else: - import Tkinter as tk + import tkinter as tk + dir(ImageTk) HAS_TK = True except (OSError, ImportError): diff --git a/Tests/test_psdraw.py b/Tests/test_psdraw.py index 561df4ee6..0ef9acb06 100644 --- a/Tests/test_psdraw.py +++ b/Tests/test_psdraw.py @@ -1,5 +1,6 @@ import os import sys +from io import StringIO from PIL import Image, PSDraw @@ -47,10 +48,6 @@ class TestPsDraw(PillowTestCase): def test_stdout(self): # Temporarily redirect stdout - try: - from cStringIO import StringIO - except ImportError: - from io import StringIO old_stdout = sys.stdout sys.stdout = mystdout = StringIO() diff --git a/mp_compile.py b/mp_compile.py deleted file mode 100644 index ec73e927e..000000000 --- a/mp_compile.py +++ /dev/null @@ -1,93 +0,0 @@ -# A monkey patch of the base distutils.ccompiler to use parallel builds -# Tested on 2.7, looks to be identical to 3.3. -# Only applied on Python 2.7 because otherwise, it conflicts with Python's -# own newly-added support for parallel builds. - -from __future__ import print_function - -import os -import sys -from distutils.ccompiler import CCompiler -from multiprocessing import Pool, cpu_count - -try: - MAX_PROCS = int(os.environ.get("MAX_CONCURRENCY", min(4, cpu_count()))) -except NotImplementedError: - MAX_PROCS = None - - -# hideous monkeypatching. but. but. but. -def _mp_compile_one(tp): - (self, obj, build, cc_args, extra_postargs, pp_opts) = tp - try: - src, ext = build[obj] - except KeyError: - return - self._compile(obj, src, ext, cc_args, extra_postargs, pp_opts) - return - - -def _mp_compile( - self, - sources, - output_dir=None, - macros=None, - include_dirs=None, - debug=0, - extra_preargs=None, - extra_postargs=None, - depends=None, -): - """Compile one or more source files. - - see distutils.ccompiler.CCompiler.compile for comments. - """ - # A concrete compiler class can either override this method - # entirely or implement _compile(). - - macros, objects, extra_postargs, pp_opts, build = self._setup_compile( - output_dir, macros, include_dirs, sources, depends, extra_postargs - ) - cc_args = self._get_cc_args(pp_opts, debug, extra_preargs) - - pool = Pool(MAX_PROCS) - try: - print("Building using %d processes" % pool._processes) - except Exception: - pass - arr = [(self, obj, build, cc_args, extra_postargs, pp_opts) for obj in objects] - pool.map_async(_mp_compile_one, arr) - pool.close() - pool.join() - # Return *all* object filenames, not just the ones we just built. - return objects - - -def install(): - - fl_win = sys.platform.startswith("win") - fl_cygwin = sys.platform.startswith("cygwin") - - if fl_win or fl_cygwin: - # Windows barfs on multiprocessing installs - print("Single threaded build for Windows") - return - - if MAX_PROCS != 1: - # explicitly don't enable if environment says 1 processor - try: - # bug, only enable if we can make a Pool. see issue #790 and - # https://stackoverflow.com/questions/6033599/oserror-38-errno-38-with-multiprocessing - Pool(2) - CCompiler.compile = _mp_compile - except Exception as msg: - print("Exception installing mp_compile, proceeding without: %s" % msg) - else: - print( - "Single threaded build, not installing mp_compile: %s processes" % MAX_PROCS - ) - - -# We monkeypatch Python 2.7 -if sys.version_info.major < 3: - install() diff --git a/setup.py b/setup.py index 76bdfb159..547034f50 100755 --- a/setup.py +++ b/setup.py @@ -20,10 +20,6 @@ from distutils.command.build_ext import build_ext from setuptools import Extension, setup -# monkey patch import hook. Even though flake8 says it's not used, it is. -# comment this out to disable multi threaded builds. -import mp_compile - if sys.platform == "win32" and sys.version_info >= (3, 8): warnings.warn( "Pillow does not yet support Python {}.{} and does not yet provide " @@ -332,12 +328,6 @@ class pil_build_ext(build_ext): if self.debug: global DEBUG DEBUG = True - if sys.version_info.major >= 3 and not self.parallel: - # For Python 2.7, we monkeypatch distutils to have parallel - # builds. If --parallel (or -j) wasn't specified, we want to - # reproduce the same behavior as before, that is, auto-detect the - # number of jobs. - self.parallel = mp_compile.MAX_PROCS for x in self.feature: if getattr(self, "disable_%s" % x): setattr(self.feature, x, False) @@ -861,12 +851,11 @@ try: classifiers=[ "Development Status :: 6 - Mature", "License :: OSI Approved :: Historical Permission Notice and Disclaimer (HPND)", # noqa: E501 - "Programming Language :: Python :: 2", - "Programming Language :: Python :: 2.7", "Programming Language :: Python :: 3", "Programming Language :: Python :: 3.5", "Programming Language :: Python :: 3.6", "Programming Language :: Python :: 3.7", + "Programming Language :: Python :: 3 :: Only", "Programming Language :: Python :: Implementation :: CPython", "Programming Language :: Python :: Implementation :: PyPy", "Topic :: Multimedia :: Graphics", @@ -875,7 +864,7 @@ try: "Topic :: Multimedia :: Graphics :: Graphics Conversion", "Topic :: Multimedia :: Graphics :: Viewers", ], - python_requires=">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*", + python_requires=">=3.5", cmdclass={"build_ext": pil_build_ext}, ext_modules=[Extension("PIL._imaging", ["_imaging.c"])], include_package_data=True, diff --git a/src/PIL/EpsImagePlugin.py b/src/PIL/EpsImagePlugin.py index 219f2dbb8..6d8828951 100644 --- a/src/PIL/EpsImagePlugin.py +++ b/src/PIL/EpsImagePlugin.py @@ -42,15 +42,8 @@ gs_windows_binary = None if sys.platform.startswith("win"): import shutil - if hasattr(shutil, "which"): - which = shutil.which - else: - # Python 2 - import distutils.spawn - - which = distutils.spawn.find_executable for binary in ("gswin32c", "gswin64c", "gs"): - if which(binary) is not None: + if shutil.which(binary) is not None: gs_windows_binary = binary break else: @@ -378,9 +371,8 @@ def _save(im, fp, filename, eps=1): base_fp = fp wrapped_fp = False if fp != sys.stdout: - if sys.version_info.major > 2: - fp = io.TextIOWrapper(fp, encoding="latin-1") - wrapped_fp = True + fp = io.TextIOWrapper(fp, encoding="latin-1") + wrapped_fp = True try: if eps: diff --git a/src/PIL/Image.py b/src/PIL/Image.py index cca9c8a91..c73cb5eb8 100644 --- a/src/PIL/Image.py +++ b/src/PIL/Image.py @@ -25,6 +25,7 @@ # import atexit +import builtins import io import logging import math @@ -33,29 +34,15 @@ import os import struct import sys import warnings +from collections.abc import Callable, MutableMapping +from pathlib import Path # VERSION was removed in Pillow 6.0.0. # PILLOW_VERSION was removed in Pillow 7.0.0. # Use __version__ instead. from . import ImageMode, TiffTags, __version__, _plugins from ._binary import i8, i32le -from ._util import deferred_error, isPath, isStringType, py3 - -try: - import builtins -except ImportError: - import __builtin__ - - builtins = __builtin__ - - -try: - # Python 3 - from collections.abc import Callable, MutableMapping -except ImportError: - # Python 2.7 - from collections import Callable, MutableMapping - +from ._util import deferred_error, isPath, isStringType logger = logging.getLogger(__name__) @@ -134,18 +121,6 @@ try: except ImportError: cffi = None -try: - from pathlib import Path - - HAS_PATHLIB = True -except ImportError: - try: - from pathlib2 import Path - - HAS_PATHLIB = True - except ImportError: - HAS_PATHLIB = False - def isImageType(t): """ @@ -621,10 +596,8 @@ class Image(object): # object is gone. self.im = deferred_error(ValueError("Operation on closed image")) - if sys.version_info.major >= 3: - - def __del__(self): - self.__exit__() + def __del__(self): + self.__exit__() def _copy(self): self.load() @@ -1347,10 +1320,7 @@ class Image(object): self.load() try: - if py3: - return list(self.im.getpalette()) - else: - return [i8(c) for c in self.im.getpalette()] + return list(self.im.getpalette()) except ValueError: return None # no palette @@ -1701,10 +1671,7 @@ class Image(object): palette = ImagePalette.raw(data.rawmode, data.palette) else: if not isinstance(data, bytes): - if py3: - data = bytes(data) - else: - data = "".join(chr(x) for x in data) + data = bytes(data) palette = ImagePalette.raw(rawmode, data) self.mode = "PA" if "A" in self.mode else "P" self.palette = palette @@ -2036,7 +2003,7 @@ class Image(object): if isPath(fp): filename = fp open_fp = True - elif HAS_PATHLIB and isinstance(fp, Path): + elif isinstance(fp, Path): filename = str(fp) open_fp = True if not filename and hasattr(fp, "name") and isPath(fp.name): @@ -2747,7 +2714,7 @@ def open(fp, mode="r"): exclusive_fp = False filename = "" - if HAS_PATHLIB and isinstance(fp, Path): + if isinstance(fp, Path): filename = str(fp.resolve()) elif isPath(fp): filename = fp @@ -3311,11 +3278,6 @@ class Exif(MutableMapping): def __contains__(self, tag): return tag in self._data or (self._info is not None and tag in self._info) - if not py3: - - def has_key(self, tag): - return tag in self - def __setitem__(self, tag, value): if self._info is not None and tag in self._info: del self._info[tag] diff --git a/src/PIL/ImageFont.py b/src/PIL/ImageFont.py index 5cce9af8e..1453a290c 100644 --- a/src/PIL/ImageFont.py +++ b/src/PIL/ImageFont.py @@ -29,7 +29,7 @@ import os import sys from . import Image -from ._util import isDirectory, isPath, py3 +from ._util import isDirectory, isPath LAYOUT_BASIC = 0 LAYOUT_RAQM = 1 @@ -695,10 +695,7 @@ def load_path(filename): for directory in sys.path: if isDirectory(directory): if not isinstance(filename, str): - if py3: - filename = filename.decode("utf-8") - else: - filename = filename.encode("utf-8") + filename = filename.decode("utf-8") try: return load(os.path.join(directory, filename)) except IOError: diff --git a/src/PIL/ImageMath.py b/src/PIL/ImageMath.py index 392151c10..d7598b932 100644 --- a/src/PIL/ImageMath.py +++ b/src/PIL/ImageMath.py @@ -15,15 +15,9 @@ # See the README file for information on usage and redistribution. # +import builtins + from . import Image, _imagingmath -from ._util import py3 - -try: - import builtins -except ImportError: - import __builtin__ - - builtins = __builtin__ VERBOSE = 0 @@ -101,11 +95,6 @@ class _Operand(object): # an image is "true" if it contains at least one non-zero pixel return self.im.getbbox() is not None - if not py3: - # Provide __nonzero__ for pre-Py3k - __nonzero__ = __bool__ - del __bool__ - def __abs__(self): return self.apply("abs", self) @@ -152,13 +141,6 @@ class _Operand(object): def __rpow__(self, other): return self.apply("pow", other, self) - if not py3: - # Provide __div__ and __rdiv__ for pre-Py3k - __div__ = __truediv__ - __rdiv__ = __rtruediv__ - del __truediv__ - del __rtruediv__ - # bitwise def __invert__(self): return self.apply("invert", self) diff --git a/src/PIL/ImageQt.py b/src/PIL/ImageQt.py index da60cacd0..ce34b0a04 100644 --- a/src/PIL/ImageQt.py +++ b/src/PIL/ImageQt.py @@ -20,7 +20,7 @@ import sys from io import BytesIO from . import Image -from ._util import isPath, py3 +from ._util import isPath qt_versions = [["5", "PyQt5"], ["side2", "PySide2"]] @@ -125,10 +125,7 @@ def _toqclass_helper(im): # handle filename, if given instead of image name if hasattr(im, "toUtf8"): # FIXME - is this really the best way to do this? - if py3: - im = str(im.toUtf8(), "utf-8") - else: - im = unicode(im.toUtf8(), "utf-8") # noqa: F821 + im = str(im.toUtf8(), "utf-8") if isPath(im): im = Image.open(im) diff --git a/src/PIL/ImageShow.py b/src/PIL/ImageShow.py index ca622c525..6d3ca52d8 100644 --- a/src/PIL/ImageShow.py +++ b/src/PIL/ImageShow.py @@ -18,14 +18,10 @@ import os import subprocess import sys import tempfile +from shlex import quote from PIL import Image -if sys.version_info.major >= 3: - from shlex import quote -else: - from pipes import quote - _viewers = [] diff --git a/src/PIL/ImageTk.py b/src/PIL/ImageTk.py index fd480007a..769fae662 100644 --- a/src/PIL/ImageTk.py +++ b/src/PIL/ImageTk.py @@ -25,17 +25,11 @@ # See the README file for information on usage and redistribution. # -import sys +import tkinter from io import BytesIO from . import Image -if sys.version_info.major > 2: - import tkinter -else: - import Tkinter as tkinter - - # -------------------------------------------------------------------- # Check for Tkinter interface hooks diff --git a/src/PIL/PSDraw.py b/src/PIL/PSDraw.py index f37701ce9..baad510e1 100644 --- a/src/PIL/PSDraw.py +++ b/src/PIL/PSDraw.py @@ -18,7 +18,6 @@ import sys from . import EpsImagePlugin -from ._util import py3 ## # Simple Postscript graphics interface. @@ -36,7 +35,7 @@ class PSDraw(object): self.fp = fp def _fp_write(self, to_write): - if not py3 or self.fp == sys.stdout: + if self.fp == sys.stdout: self.fp.write(to_write) else: self.fp.write(bytes(to_write, "UTF-8")) diff --git a/src/PIL/PdfParser.py b/src/PIL/PdfParser.py index 0ec6bba14..14079f6b1 100644 --- a/src/PIL/PdfParser.py +++ b/src/PIL/PdfParser.py @@ -7,24 +7,14 @@ import re import time import zlib -from ._util import py3 - try: from UserDict import UserDict # Python 2.x except ImportError: UserDict = collections.UserDict # Python 3.x -if py3: # Python 3.x - - def make_bytes(s): - return s.encode("us-ascii") - - -else: # Python 2.x - - def make_bytes(s): # pragma: no cover - return s # pragma: no cover +def make_bytes(s): + return s.encode("us-ascii") # see 7.9.2.2 Text String Type on page 86 and D.3 PDFDocEncoding Character Set @@ -81,10 +71,8 @@ PDFDocEncoding = { def decode_text(b): if b[: len(codecs.BOM_UTF16_BE)] == codecs.BOM_UTF16_BE: return b[len(codecs.BOM_UTF16_BE) :].decode("utf_16_be") - elif py3: # Python 3.x + else: return "".join(PDFDocEncoding.get(byte, chr(byte)) for byte in b) - else: # Python 2.x - return u"".join(PDFDocEncoding.get(ord(byte), byte) for byte in b) class PdfFormatError(RuntimeError): @@ -252,16 +240,10 @@ class PdfName: def __bytes__(self): result = bytearray(b"/") for b in self.name: - if py3: # Python 3.x - if b in self.allowed_chars: - result.append(b) - else: - result.extend(make_bytes("#%02X" % b)) - else: # Python 2.x - if ord(b) in self.allowed_chars: - result.append(b) - else: - result.extend(b"#%02X" % ord(b)) + if b in self.allowed_chars: + result.append(b) + else: + result.extend(make_bytes("#%02X" % b)) return bytes(result) __str__ = __bytes__ @@ -324,23 +306,13 @@ class PdfDict(UserDict): out.extend(b"\n>>") return bytes(out) - if not py3: - __str__ = __bytes__ - class PdfBinary: def __init__(self, data): self.data = data - if py3: # Python 3.x - - def __bytes__(self): - return make_bytes("<%s>" % "".join("%02X" % b for b in self.data)) - - else: # Python 2.x - - def __str__(self): - return "<%s>" % "".join("%02X" % ord(b) for b in self.data) + def __bytes__(self): + return make_bytes("<%s>" % "".join("%02X" % b for b in self.data)) class PdfStream: @@ -382,9 +354,7 @@ def pdf_repr(x): return bytes(PdfDict(x)) elif isinstance(x, list): return bytes(PdfArray(x)) - elif (py3 and isinstance(x, str)) or ( - not py3 and isinstance(x, unicode) # noqa: F821 - ): + elif isinstance(x, str): return pdf_repr(encode_text(x)) elif isinstance(x, bytes): # XXX escape more chars? handle binary garbage diff --git a/src/PIL/PngImagePlugin.py b/src/PIL/PngImagePlugin.py index be237b3ee..c3a5dd627 100644 --- a/src/PIL/PngImagePlugin.py +++ b/src/PIL/PngImagePlugin.py @@ -38,7 +38,6 @@ import zlib from . import Image, ImageFile, ImagePalette from ._binary import i8, i16be as i16, i32be as i32, o16be as o16, o32be as o32 -from ._util import py3 # __version__ is deprecated and will be removed in a future version. Use # PIL.__version__ instead. @@ -450,9 +449,8 @@ class PngStream(ChunkStream): k = s v = b"" if k: - if py3: - k = k.decode("latin-1", "strict") - v = v.decode("latin-1", "replace") + k = k.decode("latin-1", "strict") + v = v.decode("latin-1", "replace") self.im_info[k] = self.im_text[k] = v self.check_text_memory(len(v)) @@ -487,9 +485,8 @@ class PngStream(ChunkStream): v = b"" if k: - if py3: - k = k.decode("latin-1", "strict") - v = v.decode("latin-1", "replace") + k = k.decode("latin-1", "strict") + v = v.decode("latin-1", "replace") self.im_info[k] = self.im_text[k] = v self.check_text_memory(len(v)) @@ -524,14 +521,13 @@ class PngStream(ChunkStream): return s else: return s - if py3: - try: - k = k.decode("latin-1", "strict") - lang = lang.decode("utf-8", "strict") - tk = tk.decode("utf-8", "strict") - v = v.decode("utf-8", "strict") - except UnicodeError: - return s + try: + k = k.decode("latin-1", "strict") + lang = lang.decode("utf-8", "strict") + tk = tk.decode("utf-8", "strict") + v = v.decode("utf-8", "strict") + except UnicodeError: + return s self.im_info[k] = self.im_text[k] = iTXt(v, lang, tk) self.check_text_memory(len(v)) diff --git a/src/PIL/SgiImagePlugin.py b/src/PIL/SgiImagePlugin.py index 99408fdc3..7de62c117 100644 --- a/src/PIL/SgiImagePlugin.py +++ b/src/PIL/SgiImagePlugin.py @@ -27,7 +27,6 @@ import struct from . import Image, ImageFile from ._binary import i8, i16be as i16, o8 -from ._util import py3 # __version__ is deprecated and will be removed in a future version. Use # PIL.__version__ instead. @@ -173,8 +172,7 @@ def _save(im, fp, filename): pinmax = 255 # Image name (79 characters max, truncated below in write) imgName = os.path.splitext(os.path.basename(filename))[0] - if py3: - imgName = imgName.encode("ascii", "ignore") + imgName = imgName.encode("ascii", "ignore") # Standard representation of pixel in the file colormap = 0 fp.write(struct.pack(">h", magicNumber)) diff --git a/src/PIL/TarIO.py b/src/PIL/TarIO.py index e180b802c..457d75d03 100644 --- a/src/PIL/TarIO.py +++ b/src/PIL/TarIO.py @@ -15,7 +15,6 @@ # import io -import sys from . import ContainerIO @@ -64,10 +63,8 @@ class TarIO(ContainerIO.ContainerIO): def __exit__(self, *args): self.close() - if sys.version_info.major >= 3: - - def __del__(self): - self.close() + def __del__(self): + self.close() def close(self): self.fh.close() diff --git a/src/PIL/TiffImagePlugin.py b/src/PIL/TiffImagePlugin.py index b0d465fe2..6d56df217 100644 --- a/src/PIL/TiffImagePlugin.py +++ b/src/PIL/TiffImagePlugin.py @@ -46,24 +46,15 @@ import io import itertools import os import struct -import sys import warnings +from collections.abc import MutableMapping from fractions import Fraction from numbers import Number, Rational from . import Image, ImageFile, ImagePalette, TiffTags from ._binary import i8, o8 -from ._util import py3 from .TiffTags import TYPES -try: - # Python 3 - from collections.abc import MutableMapping -except ImportError: - # Python 2.7 - from collections import MutableMapping - - # __version__ is deprecated and will be removed in a future version. Use # PIL.__version__ instead. __version__ = "1.3.5" @@ -531,18 +522,11 @@ class ImageFileDirectory_v2(MutableMapping): def __contains__(self, tag): return tag in self._tags_v2 or tag in self._tagdata - if not py3: - - def has_key(self, tag): - return tag in self - def __setitem__(self, tag, value): self._setitem(tag, value, self.legacy_api) def _setitem(self, tag, value, legacy_api): basetypes = (Number, bytes, str) - if not py3: - basetypes += (unicode,) # noqa: F821 info = TiffTags.lookup(tag) values = [value] if isinstance(value, basetypes) else value @@ -562,14 +546,10 @@ class ImageFileDirectory_v2(MutableMapping): elif all(isinstance(v, float) for v in values): self.tagtype[tag] = TiffTags.DOUBLE else: - if py3: - if all(isinstance(v, str) for v in values): - self.tagtype[tag] = TiffTags.ASCII - else: - # Never treat data as binary by default on Python 2. + if all(isinstance(v, str) for v in values): self.tagtype[tag] = TiffTags.ASCII - if self.tagtype[tag] == TiffTags.UNDEFINED and py3: + if self.tagtype[tag] == TiffTags.UNDEFINED: values = [ value.encode("ascii", "replace") if isinstance(value, str) else value ] @@ -689,8 +669,6 @@ class ImageFileDirectory_v2(MutableMapping): @_register_writer(2) def write_string(self, value): # remerge of https://github.com/python-pillow/Pillow/pull/1416 - if sys.version_info.major == 2: - value = value.decode("ascii", "replace") return b"" + value.encode("ascii", "replace") + b"\0" @_register_loader(5, 8) @@ -1132,7 +1110,7 @@ class TiffImageFile(ImageFile.ImageFile): try: fp = hasattr(self.fp, "fileno") and os.dup(self.fp.fileno()) # flush the file descriptor, prevents error on pypy 2.4+ - # should also eliminate the need for fp.tell for py3 + # should also eliminate the need for fp.tell # in _seek if hasattr(self.fp, "flush"): self.fp.flush() @@ -1602,13 +1580,10 @@ def _save(im, fp, filename): if tag in ifd.tagtype: types[tag] = ifd.tagtype[tag] - elif not ( - isinstance(value, (int, float, str, bytes)) - or (not py3 and isinstance(value, unicode)) # noqa: F821 - ): + elif not (isinstance(value, (int, float, str, bytes))): continue if tag not in atts and tag not in blocklist: - if isinstance(value, str if py3 else unicode): # noqa: F821 + if isinstance(value, str): atts[tag] = value.encode("ascii", "replace") + b"\0" elif isinstance(value, IFDRational): atts[tag] = float(value) diff --git a/src/PIL/WalImageFile.py b/src/PIL/WalImageFile.py index e2e1cd4f5..daa806a8b 100644 --- a/src/PIL/WalImageFile.py +++ b/src/PIL/WalImageFile.py @@ -21,16 +21,11 @@ # https://www.flipcode.com/archives/Quake_2_BSP_File_Format.shtml # and has been tested with a few sample files found using google. +import builtins + from . import Image from ._binary import i32le as i32 -try: - import builtins -except ImportError: - import __builtin__ - - builtins = __builtin__ - def open(filename): """ diff --git a/src/PIL/WmfImagePlugin.py b/src/PIL/WmfImagePlugin.py index 416af6fd7..be07a747b 100644 --- a/src/PIL/WmfImagePlugin.py +++ b/src/PIL/WmfImagePlugin.py @@ -23,7 +23,6 @@ from __future__ import print_function from . import Image, ImageFile from ._binary import i16le as word, i32le as dword, si16le as short, si32le as _long -from ._util import py3 # __version__ is deprecated and will be removed in a future version. Use # PIL.__version__ instead. @@ -31,9 +30,6 @@ __version__ = "0.2" _handler = None -if py3: - long = int - def register_handler(handler): """ diff --git a/src/PIL/_binary.py b/src/PIL/_binary.py index 53b1ca956..529b8c94b 100644 --- a/src/PIL/_binary.py +++ b/src/PIL/_binary.py @@ -13,24 +13,13 @@ from struct import pack, unpack_from -from ._util import py3 -if py3: - - def i8(c): - return c if c.__class__ is int else c[0] - - def o8(i): - return bytes((i & 255,)) +def i8(c): + return c if c.__class__ is int else c[0] -else: - - def i8(c): - return ord(c) - - def o8(i): - return chr(i & 255) +def o8(i): + return bytes((i & 255,)) # Input, le = little endian, be = big endian diff --git a/src/PIL/_tkinter_finder.py b/src/PIL/_tkinter_finder.py index d4f34196e..30493066a 100644 --- a/src/PIL/_tkinter_finder.py +++ b/src/PIL/_tkinter_finder.py @@ -1,11 +1,7 @@ """ Find compiled module linking to Tcl / Tk libraries """ import sys - -if sys.version_info.major > 2: - from tkinter import _tkinter as tk -else: - from Tkinter import tkinter as tk +from tkinter import _tkinter as tk if hasattr(sys, "pypy_find_executable"): # Tested with packages at https://bitbucket.org/pypy/pypy/downloads. diff --git a/src/PIL/_util.py b/src/PIL/_util.py index 59964c7ef..127e94d82 100644 --- a/src/PIL/_util.py +++ b/src/PIL/_util.py @@ -1,33 +1,24 @@ import os import sys -py3 = sys.version_info.major >= 3 py36 = sys.version_info[0:2] >= (3, 6) -if py3: - def isStringType(t): - return isinstance(t, str) +def isStringType(t): + return isinstance(t, str) - if py36: - from pathlib import Path - def isPath(f): - return isinstance(f, (bytes, str, Path)) +if py36: + from pathlib import Path - else: - - def isPath(f): - return isinstance(f, (bytes, str)) + def isPath(f): + return isinstance(f, (bytes, str, Path)) else: - def isStringType(t): - return isinstance(t, basestring) # noqa: F821 - def isPath(f): - return isinstance(f, basestring) # noqa: F821 + return isinstance(f, (bytes, str)) # Checks if an object is a string, and that it points to a directory. diff --git a/src/Tk/tkImaging.c b/src/Tk/tkImaging.c index bb0fd33a3..59801f58e 100644 --- a/src/Tk/tkImaging.c +++ b/src/Tk/tkImaging.c @@ -225,11 +225,7 @@ TkImaging_Init(Tcl_Interp* interp) #include /* Must be linked with 'psapi' library */ -#if PY_VERSION_HEX >= 0x03000000 #define TKINTER_PKG "tkinter" -#else -#define TKINTER_PKG "Tkinter" -#endif FARPROC _dfunc(HMODULE lib_handle, const char *func_name) { @@ -354,7 +350,6 @@ int load_tkinter_funcs(void) */ /* From module __file__ attribute to char *string for dlopen. */ -#if PY_VERSION_HEX >= 0x03000000 char *fname2char(PyObject *fname) { PyObject* bytes; @@ -364,9 +359,6 @@ char *fname2char(PyObject *fname) } return PyBytes_AsString(bytes); } -#else -#define fname2char(s) (PyString_AsString(s)) -#endif #include diff --git a/src/_imaging.c b/src/_imaging.c index 04520b1a1..a12fdfb15 100644 --- a/src/_imaging.c +++ b/src/_imaging.c @@ -84,8 +84,6 @@ #include "Imaging.h" -#include "py3.h" - #define _USE_MATH_DEFINES #include @@ -237,45 +235,13 @@ void ImagingSectionLeave(ImagingSectionCookie* cookie) int PyImaging_CheckBuffer(PyObject* buffer) { -#if PY_VERSION_HEX >= 0x03000000 return PyObject_CheckBuffer(buffer); -#else - return PyObject_CheckBuffer(buffer) || PyObject_CheckReadBuffer(buffer); -#endif } int PyImaging_GetBuffer(PyObject* buffer, Py_buffer *view) { /* must call check_buffer first! */ -#if PY_VERSION_HEX >= 0x03000000 return PyObject_GetBuffer(buffer, view, PyBUF_SIMPLE); -#else - /* Use new buffer protocol if available - (mmap doesn't support this in 2.7, go figure) */ - if (PyObject_CheckBuffer(buffer)) { - int success = PyObject_GetBuffer(buffer, view, PyBUF_SIMPLE); - if (!success) { return success; } - PyErr_Clear(); - } - - /* Pretend we support the new protocol; PyBuffer_Release happily ignores - calling bf_releasebuffer on objects that don't support it */ - view->buf = NULL; - view->len = 0; - view->readonly = 1; - view->format = NULL; - view->ndim = 0; - view->shape = NULL; - view->strides = NULL; - view->suboffsets = NULL; - view->itemsize = 0; - view->internal = NULL; - - Py_INCREF(buffer); - view->obj = buffer; - - return PyObject_AsReadBuffer(buffer, (void *) &view->buf, &view->len); -#endif } /* -------------------------------------------------------------------- */ @@ -416,11 +382,11 @@ getlist(PyObject* arg, Py_ssize_t* length, const char* wrong_length, int type) // on this switch. And 3 fewer loops to copy/paste. switch (type) { case TYPE_UINT8: - itemp = PyInt_AsLong(op); + itemp = PyLong_AsLong(op); list[i] = CLIP8(itemp); break; case TYPE_INT32: - itemp = PyInt_AsLong(op); + itemp = PyLong_AsLong(op); memcpy(list + i * sizeof(INT32), &itemp, sizeof(itemp)); break; case TYPE_FLOAT32: @@ -499,7 +465,7 @@ getpixel(Imaging im, ImagingAccess access, int x, int y) case IMAGING_TYPE_UINT8: switch (im->bands) { case 1: - return PyInt_FromLong(pixel.b[0]); + return PyLong_FromLong(pixel.b[0]); case 2: return Py_BuildValue("BB", pixel.b[0], pixel.b[1]); case 3: @@ -509,12 +475,12 @@ getpixel(Imaging im, ImagingAccess access, int x, int y) } break; case IMAGING_TYPE_INT32: - return PyInt_FromLong(pixel.i); + return PyLong_FromLong(pixel.i); case IMAGING_TYPE_FLOAT32: return PyFloat_FromDouble(pixel.f); case IMAGING_TYPE_SPECIAL: if (strncmp(im->mode, "I;16", 4) == 0) - return PyInt_FromLong(pixel.h); + return PyLong_FromLong(pixel.h); break; } @@ -543,16 +509,8 @@ getink(PyObject* color, Imaging im, char* ink) if (im->type == IMAGING_TYPE_UINT8 || im->type == IMAGING_TYPE_INT32 || im->type == IMAGING_TYPE_SPECIAL) { -#if PY_VERSION_HEX >= 0x03000000 if (PyLong_Check(color)) { r = PyLong_AsLongLong(color); -#else - if (PyInt_Check(color) || PyLong_Check(color)) { - if (PyInt_Check(color)) - r = PyInt_AS_LONG(color); - else - r = PyLong_AsLongLong(color); -#endif rIsInt = 1; } if (r == -1 && PyErr_Occurred()) { @@ -1129,16 +1087,16 @@ _getxy(PyObject* xy, int* x, int *y) goto badarg; value = PyTuple_GET_ITEM(xy, 0); - if (PyInt_Check(value)) - *x = PyInt_AS_LONG(value); + if (PyLong_Check(value)) + *x = PyLong_AS_LONG(value); else if (PyFloat_Check(value)) *x = (int) PyFloat_AS_DOUBLE(value); else goto badval; value = PyTuple_GET_ITEM(xy, 1); - if (PyInt_Check(value)) - *y = PyInt_AS_LONG(value); + if (PyLong_Check(value)) + *y = PyLong_AS_LONG(value); else if (PyFloat_Check(value)) *y = (int) PyFloat_AS_DOUBLE(value); else @@ -1255,7 +1213,7 @@ _histogram(ImagingObject* self, PyObject* args) list = PyList_New(h->bands * 256); for (i = 0; i < h->bands * 256; i++) { PyObject* item; - item = PyInt_FromLong(h->histogram[i]); + item = PyLong_FromLong(h->histogram[i]); if (item == NULL) { Py_DECREF(list); list = NULL; @@ -1524,7 +1482,7 @@ _putdata(ImagingObject* self, PyObject* args) /* Clipped data */ for (i = x = y = 0; i < n; i++) { op = PySequence_Fast_GET_ITEM(seq, i); - image->image8[y][x] = (UINT8) CLIP8(PyInt_AsLong(op)); + image->image8[y][x] = (UINT8) CLIP8(PyLong_AsLong(op)); if (++x >= (int) image->xsize){ x = 0, y++; } @@ -1635,7 +1593,7 @@ _putpalette(ImagingObject* self, PyObject* args) char* rawmode; UINT8* palette; Py_ssize_t palettesize; - if (!PyArg_ParseTuple(args, "s"PY_ARG_BYTES_LENGTH, &rawmode, &palette, &palettesize)) + if (!PyArg_ParseTuple(args, "sy#", &rawmode, &palette, &palettesize)) return NULL; if (strcmp(self->image->mode, "L") && strcmp(self->image->mode, "LA") && @@ -1698,7 +1656,7 @@ _putpalettealphas(ImagingObject* self, PyObject* args) int i; UINT8 *values; Py_ssize_t length; - if (!PyArg_ParseTuple(args, PY_ARG_BYTES_LENGTH, &values, &length)) + if (!PyArg_ParseTuple(args, "y#", &values, &length)) return NULL; if (!self->image->palette) { @@ -2136,7 +2094,7 @@ _getprojection(ImagingObject* self, PyObject* args) ImagingGetProjection(self->image, (unsigned char *)xprofile, (unsigned char *)yprofile); - result = Py_BuildValue(PY_ARG_BYTES_LENGTH PY_ARG_BYTES_LENGTH, + result = Py_BuildValue("y#y#", xprofile, (Py_ssize_t)self->image->xsize, yprofile, (Py_ssize_t)self->image->ysize); @@ -2414,7 +2372,7 @@ _font_new(PyObject* self_, PyObject* args) ImagingObject* imagep; unsigned char* glyphdata; Py_ssize_t glyphdata_length; - if (!PyArg_ParseTuple(args, "O!"PY_ARG_BYTES_LENGTH, + if (!PyArg_ParseTuple(args, "O!y#", &Imaging_Type, &imagep, &glyphdata, &glyphdata_length)) return NULL; @@ -2648,7 +2606,7 @@ _draw_ink(ImagingDrawObject* self, PyObject* args) if (!getink(color, self->image->image, (char*) &ink)) return NULL; - return PyInt_FromLong((int) ink); + return PyLong_FromLong((int) ink); } static PyObject* @@ -3356,13 +3314,13 @@ _getattr_size(ImagingObject* self, void* closure) static PyObject* _getattr_bands(ImagingObject* self, void* closure) { - return PyInt_FromLong(self->image->bands); + return PyLong_FromLong(self->image->bands); } static PyObject* _getattr_id(ImagingObject* self, void* closure) { - return PyInt_FromSsize_t((Py_ssize_t) self->image); + return PyLong_FromSsize_t((Py_ssize_t) self->image); } static PyObject* @@ -3575,17 +3533,17 @@ _get_stats(PyObject* self, PyObject* args) if ( ! d) return NULL; PyDict_SetItemString(d, "new_count", - PyInt_FromLong(arena->stats_new_count)); + PyLong_FromLong(arena->stats_new_count)); PyDict_SetItemString(d, "allocated_blocks", - PyInt_FromLong(arena->stats_allocated_blocks)); + PyLong_FromLong(arena->stats_allocated_blocks)); PyDict_SetItemString(d, "reused_blocks", - PyInt_FromLong(arena->stats_reused_blocks)); + PyLong_FromLong(arena->stats_reused_blocks)); PyDict_SetItemString(d, "reallocated_blocks", - PyInt_FromLong(arena->stats_reallocated_blocks)); + PyLong_FromLong(arena->stats_reallocated_blocks)); PyDict_SetItemString(d, "freed_blocks", - PyInt_FromLong(arena->stats_freed_blocks)); + PyLong_FromLong(arena->stats_freed_blocks)); PyDict_SetItemString(d, "blocks_cached", - PyInt_FromLong(arena->blocks_cached)); + PyLong_FromLong(arena->blocks_cached)); return d; } @@ -3613,7 +3571,7 @@ _get_alignment(PyObject* self, PyObject* args) if (!PyArg_ParseTuple(args, ":get_alignment")) return NULL; - return PyInt_FromLong(ImagingDefaultArena.alignment); + return PyLong_FromLong(ImagingDefaultArena.alignment); } static PyObject* @@ -3622,7 +3580,7 @@ _get_block_size(PyObject* self, PyObject* args) if (!PyArg_ParseTuple(args, ":get_block_size")) return NULL; - return PyInt_FromLong(ImagingDefaultArena.block_size); + return PyLong_FromLong(ImagingDefaultArena.block_size); } static PyObject* @@ -3631,7 +3589,7 @@ _get_blocks_max(PyObject* self, PyObject* args) if (!PyArg_ParseTuple(args, ":get_blocks_max")) return NULL; - return PyInt_FromLong(ImagingDefaultArena.blocks_max); + return PyLong_FromLong(ImagingDefaultArena.blocks_max); } static PyObject* @@ -3959,7 +3917,6 @@ setup_module(PyObject* m) { return 0; } -#if PY_VERSION_HEX >= 0x03000000 PyMODINIT_FUNC PyInit__imaging(void) { PyObject* m; @@ -3979,11 +3936,3 @@ PyInit__imaging(void) { return m; } -#else -PyMODINIT_FUNC -init_imaging(void) -{ - PyObject* m = Py_InitModule("_imaging", functions); - setup_module(m); -} -#endif diff --git a/src/_imagingcms.c b/src/_imagingcms.c index 2c9f3aa68..0b22ab695 100644 --- a/src/_imagingcms.c +++ b/src/_imagingcms.c @@ -32,7 +32,6 @@ http://www.cazabon.com\n\ #include "lcms2.h" #include "Imaging.h" -#include "py3.h" #define PYCMSVERSION "1.0.0 pil" @@ -122,13 +121,8 @@ cms_profile_fromstring(PyObject* self, PyObject* args) char* pProfile; Py_ssize_t nProfile; -#if PY_VERSION_HEX >= 0x03000000 if (!PyArg_ParseTuple(args, "y#:profile_frombytes", &pProfile, &nProfile)) return NULL; -#else - if (!PyArg_ParseTuple(args, "s#:profile_fromstring", &pProfile, &nProfile)) - return NULL; -#endif hProfile = cmsOpenProfileFromMem(pProfile, nProfile); if (!hProfile) { @@ -172,11 +166,7 @@ cms_profile_tobytes(PyObject* self, PyObject* args) return NULL; } -#if PY_VERSION_HEX >= 0x03000000 ret = PyBytes_FromStringAndSize(pProfile, (Py_ssize_t)nProfile); -#else - ret = PyString_FromStringAndSize(pProfile, (Py_ssize_t)nProfile); -#endif free(pProfile); return ret; @@ -592,7 +582,7 @@ cms_profile_is_intent_supported(CmsProfileObject *self, PyObject *args) /* printf("cmsIsIntentSupported(%p, %d, %d) => %d\n", self->profile, intent, direction, result); */ - return PyInt_FromLong(result != 0); + return PyLong_FromLong(result != 0); } #ifdef _WIN32 @@ -691,11 +681,7 @@ _profile_read_int_as_string(cmsUInt32Number nr) buf[3] = (char) (nr & 0xff); buf[4] = 0; -#if PY_VERSION_HEX >= 0x03000000 ret = PyUnicode_DecodeASCII(buf, 4, NULL); -#else - ret = PyString_FromStringAndSize(buf, 4); -#endif return ret; } @@ -898,7 +884,7 @@ _is_intent_supported(CmsProfileObject* self, int clut) || intent == INTENT_SATURATION || intent == INTENT_ABSOLUTE_COLORIMETRIC)) continue; - id = PyInt_FromLong((long) intent); + id = PyLong_FromLong((long) intent); entry = Py_BuildValue("(OOO)", _check_intent(clut, self->profile, intent, LCMS_USED_AS_INPUT) ? Py_True : Py_False, _check_intent(clut, self->profile, intent, LCMS_USED_AS_OUTPUT) ? Py_True : Py_False, @@ -1010,7 +996,7 @@ cms_profile_getattr_product_copyright(CmsProfileObject* self, void* closure) static PyObject* cms_profile_getattr_rendering_intent(CmsProfileObject* self, void* closure) { - return PyInt_FromLong(cmsGetHeaderRenderingIntent(self->profile)); + return PyLong_FromLong(cmsGetHeaderRenderingIntent(self->profile)); } static PyObject* @@ -1098,7 +1084,7 @@ cms_profile_getattr_version(CmsProfileObject* self, void* closure) static PyObject* cms_profile_getattr_icc_version(CmsProfileObject* self, void* closure) { - return PyInt_FromLong((long) cmsGetEncodedICCversion(self->profile)); + return PyLong_FromLong((long) cmsGetEncodedICCversion(self->profile)); } static PyObject* @@ -1115,7 +1101,7 @@ static PyObject* cms_profile_getattr_header_flags(CmsProfileObject* self, void* closure) { cmsUInt32Number flags = cmsGetHeaderFlags(self->profile); - return PyInt_FromLong(flags); + return PyLong_FromLong(flags); } static PyObject* @@ -1611,7 +1597,6 @@ setup_module(PyObject* m) { return 0; } -#if PY_VERSION_HEX >= 0x03000000 PyMODINIT_FUNC PyInit__imagingcms(void) { PyObject* m; @@ -1633,12 +1618,3 @@ PyInit__imagingcms(void) { return m; } -#else -PyMODINIT_FUNC -init_imagingcms(void) -{ - PyObject *m = Py_InitModule("_imagingcms", pyCMSdll_methods); - setup_module(m); - PyDateTime_IMPORT; -} -#endif diff --git a/src/_imagingft.c b/src/_imagingft.c index 7776e43f1..fb6a46d17 100644 --- a/src/_imagingft.c +++ b/src/_imagingft.c @@ -30,7 +30,6 @@ #include FT_SFNT_NAMES_H #define KEEP_PY_UNICODE -#include "py3.h" #if !defined(_MSC_VER) #include @@ -266,8 +265,7 @@ getfont(PyObject* self_, PyObject* args, PyObject* kw) return NULL; } - if (!PyArg_ParseTupleAndKeywords(args, kw, "etn|ns"PY_ARG_BYTES_LENGTH"n", - kwlist, + if (!PyArg_ParseTupleAndKeywords(args, kw, "etn|nsy#n", kwlist, Py_FileSystemDefaultEncoding, &filename, &size, &index, &encoding, &font_bytes, &font_bytes_size, &layout_engine)) { @@ -328,7 +326,7 @@ getfont(PyObject* self_, PyObject* args, PyObject* kw) static int font_getchar(PyObject* string, int index, FT_ULong* char_out) { -#if (PY_VERSION_HEX < 0x03030000) || (defined(PYPY_VERSION_NUM)) +#if (defined(PYPY_VERSION_NUM)) if (PyUnicode_Check(string)) { Py_UNICODE* p = PyUnicode_AS_UNICODE(string); int size = PyUnicode_GET_SIZE(string); @@ -337,16 +335,6 @@ font_getchar(PyObject* string, int index, FT_ULong* char_out) *char_out = p[index]; return 1; } -#if PY_VERSION_HEX < 0x03000000 - if (PyString_Check(string)) { - unsigned char* p = (unsigned char*) PyString_AS_STRING(string); - int size = PyString_GET_SIZE(string); - if (index >= size) - return 0; - *char_out = (unsigned char) p[index]; - return 1; - } -#endif #else if (PyUnicode_Check(string)) { if (index >= PyUnicode_GET_LENGTH(string)) @@ -375,7 +363,7 @@ text_layout_raqm(PyObject* string, FontObject* self, const char* dir, PyObject * goto failed; } -#if (PY_VERSION_HEX < 0x03030000) || (defined(PYPY_VERSION_NUM)) +#if (defined(PYPY_VERSION_NUM)) if (PyUnicode_Check(string)) { Py_UNICODE *text = PyUnicode_AS_UNICODE(string); Py_ssize_t size = PyUnicode_GET_SIZE(string); @@ -395,25 +383,6 @@ text_layout_raqm(PyObject* string, FontObject* self, const char* dir, PyObject * } } } -#if PY_VERSION_HEX < 0x03000000 - else if (PyString_Check(string)) { - char *text = PyString_AS_STRING(string); - int size = PyString_GET_SIZE(string); - if (! size) { - goto failed; - } - if (!(*p_raqm.set_text_utf8)(rq, text, size)) { - PyErr_SetString(PyExc_ValueError, "raqm_set_text_utf8() failed"); - goto failed; - } - if (lang) { - if (!(*p_raqm.set_language)(rq, lang, start, size)) { - PyErr_SetString(PyExc_ValueError, "raqm_set_language() failed"); - goto failed; - } - } - } -#endif #else if (PyUnicode_Check(string)) { Py_UCS4 *text = PyUnicode_AsUCS4Copy(string); @@ -479,11 +448,7 @@ text_layout_raqm(PyObject* string, FontObject* self, const char* dir, PyObject * Py_ssize_t size = 0; PyObject *bytes; -#if PY_VERSION_HEX >= 0x03000000 if (!PyUnicode_Check(item)) { -#else - if (!PyUnicode_Check(item) && !PyString_Check(item)) { -#endif PyErr_SetString(PyExc_TypeError, "expected a string"); goto failed; } @@ -495,12 +460,6 @@ text_layout_raqm(PyObject* string, FontObject* self, const char* dir, PyObject * feature = PyBytes_AS_STRING(bytes); size = PyBytes_GET_SIZE(bytes); } -#if PY_VERSION_HEX < 0x03000000 - else { - feature = PyString_AsString(item); - size = PyString_GET_SIZE(item); - } -#endif if (!(*p_raqm.add_font_feature)(rq, feature, size)) { PyErr_SetString(PyExc_ValueError, "raqm_add_font_feature() failed"); goto failed; @@ -581,11 +540,7 @@ text_layout_fallback(PyObject* string, FontObject* self, const char* dir, PyObje if (features != Py_None || dir != NULL || lang != NULL) { PyErr_SetString(PyExc_KeyError, "setting text direction, language or font features is not supported without libraqm"); } -#if PY_VERSION_HEX >= 0x03000000 if (!PyUnicode_Check(string)) { -#else - if (!PyUnicode_Check(string) && !PyString_Check(string)) { -#endif PyErr_SetString(PyExc_TypeError, "expected string"); return 0; } @@ -990,8 +945,7 @@ font_render(FontObject* self, PyObject* args) continue; if (master->namedstyle[j].strid == name.name_id) { - list_name = Py_BuildValue(PY_ARG_BYTES_LENGTH, - name.string, name.string_len); + list_name = Py_BuildValue("y#", name.string, name.string_len); PyList_SetItem(list_names, j, list_name); break; } @@ -1025,11 +979,11 @@ font_render(FontObject* self, PyObject* args) list_axis = PyDict_New(); PyDict_SetItemString(list_axis, "minimum", - PyInt_FromLong(axis.minimum / 65536)); + PyLong_FromLong(axis.minimum / 65536)); PyDict_SetItemString(list_axis, "default", - PyInt_FromLong(axis.def / 65536)); + PyLong_FromLong(axis.def / 65536)); PyDict_SetItemString(list_axis, "maximum", - PyInt_FromLong(axis.maximum / 65536)); + PyLong_FromLong(axis.maximum / 65536)); for (j = 0; j < name_count; j++) { error = FT_Get_Sfnt_Name(self->face, j, &name); @@ -1037,8 +991,7 @@ font_render(FontObject* self, PyObject* args) return geterror(error); if (name.name_id == axis.strid) { - axis_name = Py_BuildValue(PY_ARG_BYTES_LENGTH, - name.string, name.string_len); + axis_name = Py_BuildValue("y#", name.string, name.string_len); PyDict_SetItemString(list_axis, "name", axis_name); break; } @@ -1095,8 +1048,8 @@ font_render(FontObject* self, PyObject* args) item = PyList_GET_ITEM(axes, i); if (PyFloat_Check(item)) coord = PyFloat_AS_DOUBLE(item); - else if (PyInt_Check(item)) - coord = (float) PyInt_AS_LONG(item); + else if (PyLong_Check(item)) + coord = (float) PyLong_AS_LONG(item); else if (PyNumber_Check(item)) coord = PyFloat_AsDouble(item); else { @@ -1146,64 +1099,54 @@ static PyMethodDef font_methods[] = { static PyObject* font_getattr_family(FontObject* self, void* closure) { -#if PY_VERSION_HEX >= 0x03000000 if (self->face->family_name) return PyUnicode_FromString(self->face->family_name); -#else - if (self->face->family_name) - return PyString_FromString(self->face->family_name); -#endif Py_RETURN_NONE; } static PyObject* font_getattr_style(FontObject* self, void* closure) { -#if PY_VERSION_HEX >= 0x03000000 if (self->face->style_name) return PyUnicode_FromString(self->face->style_name); -#else - if (self->face->style_name) - return PyString_FromString(self->face->style_name); -#endif Py_RETURN_NONE; } static PyObject* font_getattr_ascent(FontObject* self, void* closure) { - return PyInt_FromLong(PIXEL(self->face->size->metrics.ascender)); + return PyLong_FromLong(PIXEL(self->face->size->metrics.ascender)); } static PyObject* font_getattr_descent(FontObject* self, void* closure) { - return PyInt_FromLong(-PIXEL(self->face->size->metrics.descender)); + return PyLong_FromLong(-PIXEL(self->face->size->metrics.descender)); } static PyObject* font_getattr_height(FontObject* self, void* closure) { - return PyInt_FromLong(PIXEL(self->face->size->metrics.height)); + return PyLong_FromLong(PIXEL(self->face->size->metrics.height)); } static PyObject* font_getattr_x_ppem(FontObject* self, void* closure) { - return PyInt_FromLong(self->face->size->metrics.x_ppem); + return PyLong_FromLong(self->face->size->metrics.x_ppem); } static PyObject* font_getattr_y_ppem(FontObject* self, void* closure) { - return PyInt_FromLong(self->face->size->metrics.y_ppem); + return PyLong_FromLong(self->face->size->metrics.y_ppem); } static PyObject* font_getattr_glyphs(FontObject* self, void* closure) { - return PyInt_FromLong(self->face->num_glyphs); + return PyLong_FromLong(self->face->num_glyphs); } static struct PyGetSetDef font_getsetters[] = { @@ -1271,11 +1214,7 @@ setup_module(PyObject* m) { FT_Library_Version(library, &major, &minor, &patch); -#if PY_VERSION_HEX >= 0x03000000 v = PyUnicode_FromFormat("%d.%d.%d", major, minor, patch); -#else - v = PyString_FromFormat("%d.%d.%d", major, minor, patch); -#endif PyDict_SetItemString(d, "freetype2_version", v); @@ -1286,7 +1225,6 @@ setup_module(PyObject* m) { return 0; } -#if PY_VERSION_HEX >= 0x03000000 PyMODINIT_FUNC PyInit__imagingft(void) { PyObject* m; @@ -1306,12 +1244,3 @@ PyInit__imagingft(void) { return m; } -#else -PyMODINIT_FUNC -init_imagingft(void) -{ - PyObject* m = Py_InitModule("_imagingft", _functions); - setup_module(m); -} -#endif - diff --git a/src/_imagingmath.c b/src/_imagingmath.c index ea9f103c6..bc66a581a 100644 --- a/src/_imagingmath.c +++ b/src/_imagingmath.c @@ -16,7 +16,6 @@ #include "Python.h" #include "Imaging.h" -#include "py3.h" #include "math.h" #include "float.h" @@ -215,7 +214,7 @@ static PyMethodDef _functions[] = { static void install(PyObject *d, char* name, void* value) { - PyObject *v = PyInt_FromSsize_t((Py_ssize_t) value); + PyObject *v = PyLong_FromSsize_t((Py_ssize_t) value); if (!v || PyDict_SetItemString(d, name, v)) PyErr_Clear(); Py_XDECREF(v); @@ -273,7 +272,6 @@ setup_module(PyObject* m) { return 0; } -#if PY_VERSION_HEX >= 0x03000000 PyMODINIT_FUNC PyInit__imagingmath(void) { PyObject* m; @@ -293,12 +291,3 @@ PyInit__imagingmath(void) { return m; } -#else -PyMODINIT_FUNC -init_imagingmath(void) -{ - PyObject* m = Py_InitModule("_imagingmath", _functions); - setup_module(m); -} -#endif - diff --git a/src/_imagingmorph.c b/src/_imagingmorph.c index fc8f246cc..050ae9f02 100644 --- a/src/_imagingmorph.c +++ b/src/_imagingmorph.c @@ -13,7 +13,6 @@ #include "Python.h" #include "Imaging.h" -#include "py3.h" #define LUT_SIZE (1<<9) @@ -273,7 +272,6 @@ static PyMethodDef functions[] = { {NULL, NULL, 0, NULL} }; -#if PY_VERSION_HEX >= 0x03000000 PyMODINIT_FUNC PyInit__imagingmorph(void) { PyObject* m; @@ -293,12 +291,3 @@ PyInit__imagingmorph(void) { return m; } -#else -PyMODINIT_FUNC -init_imagingmorph(void) -{ - PyObject* m = Py_InitModule("_imagingmorph", functions); - setup_module(m); -} -#endif - diff --git a/src/_imagingtk.c b/src/_imagingtk.c index d0295f317..bdf5e68d1 100644 --- a/src/_imagingtk.c +++ b/src/_imagingtk.c @@ -64,7 +64,6 @@ static PyMethodDef functions[] = { {NULL, NULL} /* sentinel */ }; -#if PY_VERSION_HEX >= 0x03000000 PyMODINIT_FUNC PyInit__imagingtk(void) { static PyModuleDef module_def = { @@ -78,12 +77,3 @@ PyInit__imagingtk(void) { m = PyModule_Create(&module_def); return (load_tkinter_funcs() == 0) ? m : NULL; } -#else -PyMODINIT_FUNC -init_imagingtk(void) -{ - Py_InitModule("_imagingtk", functions); - load_tkinter_funcs(); -} -#endif - diff --git a/src/_webp.c b/src/_webp.c index 66b6d3268..4581ef89d 100644 --- a/src/_webp.c +++ b/src/_webp.c @@ -1,7 +1,6 @@ #define PY_SSIZE_T_CLEAN #include #include "Imaging.h" -#include "py3.h" #include #include #include @@ -557,7 +556,7 @@ PyObject* WebPEncode_wrapper(PyObject* self, PyObject* args) Py_ssize_t xmp_size; size_t ret_size; - if (!PyArg_ParseTuple(args, PY_ARG_BYTES_LENGTH"iiifss#s#s#", + if (!PyArg_ParseTuple(args, "y#iiifss#s#s#", (char**)&rgb, &size, &width, &height, &lossless, &quality_factor, &mode, &icc_bytes, &icc_size, &exif_bytes, &exif_size, &xmp_bytes, &xmp_size)) { return NULL; @@ -754,11 +753,7 @@ PyObject* WebPDecode_wrapper(PyObject* self, PyObject* args) config.output.u.YUVA.y_size); } -#if PY_VERSION_HEX >= 0x03000000 pymode = PyUnicode_FromString(mode); -#else - pymode = PyString_FromString(mode); -#endif ret = Py_BuildValue("SiiSSS", bytes, config.output.width, config.output.height, pymode, NULL == icc_profile ? Py_None : icc_profile, @@ -848,7 +843,6 @@ static int setup_module(PyObject* m) { return 0; } -#if PY_VERSION_HEX >= 0x03000000 PyMODINIT_FUNC PyInit__webp(void) { PyObject* m; @@ -867,11 +861,3 @@ PyInit__webp(void) { return m; } -#else -PyMODINIT_FUNC -init_webp(void) -{ - PyObject* m = Py_InitModule("_webp", webpMethods); - setup_module(m); -} -#endif diff --git a/src/decode.c b/src/decode.c index 79133f48f..5ab6ca9d1 100644 --- a/src/decode.c +++ b/src/decode.c @@ -33,7 +33,6 @@ #include "Python.h" #include "Imaging.h" -#include "py3.h" #include "Gif.h" #include "Raw.h" @@ -122,7 +121,7 @@ _decode(ImagingDecoderObject* decoder, PyObject* args) int status; ImagingSectionCookie cookie; - if (!PyArg_ParseTuple(args, PY_ARG_BYTES_LENGTH, &buffer, &bufsize)) + if (!PyArg_ParseTuple(args, "y#", &buffer, &bufsize)) return NULL; if (!decoder->pulls_fd) { diff --git a/src/display.c b/src/display.c index efabf1c86..4c2faf9e0 100644 --- a/src/display.c +++ b/src/display.c @@ -26,7 +26,6 @@ #include "Python.h" #include "Imaging.h" -#include "py3.h" /* -------------------------------------------------------------------- */ /* Windows DIB support */ @@ -187,13 +186,8 @@ _frombytes(ImagingDisplayObject* display, PyObject* args) char* ptr; int bytes; -#if PY_VERSION_HEX >= 0x03000000 if (!PyArg_ParseTuple(args, "y#:frombytes", &ptr, &bytes)) return NULL; -#else - if (!PyArg_ParseTuple(args, "s#:fromstring", &ptr, &bytes)) - return NULL; -#endif if (display->dib->ysize * display->dib->linesize != bytes) { PyErr_SetString(PyExc_ValueError, "wrong size"); @@ -209,13 +203,8 @@ _frombytes(ImagingDisplayObject* display, PyObject* args) static PyObject* _tobytes(ImagingDisplayObject* display, PyObject* args) { -#if PY_VERSION_HEX >= 0x03000000 if (!PyArg_ParseTuple(args, ":tobytes")) return NULL; -#else - if (!PyArg_ParseTuple(args, ":tostring")) - return NULL; -#endif return PyBytes_FromStringAndSize( display->dib->bits, display->dib->ysize * display->dib->linesize @@ -741,7 +730,7 @@ PyImaging_DrawWmf(PyObject* self, PyObject* args) int datasize; int width, height; int x0, y0, x1, y1; - if (!PyArg_ParseTuple(args, PY_ARG_BYTES_LENGTH"(ii)(iiii):_load", &data, &datasize, + if (!PyArg_ParseTuple(args, "y#(ii)(iiii):_load", &data, &datasize, &width, &height, &x0, &x1, &y0, &y1)) return NULL; diff --git a/src/encode.c b/src/encode.c index 7dc1035c4..41ba124c4 100644 --- a/src/encode.c +++ b/src/encode.c @@ -26,7 +26,6 @@ #include "Python.h" #include "Imaging.h" -#include "py3.h" #include "Gif.h" #ifdef HAVE_UNISTD_H @@ -567,7 +566,7 @@ PyImaging_ZipEncoderNew(PyObject* self, PyObject* args) Py_ssize_t compress_type = -1; char* dictionary = NULL; Py_ssize_t dictionary_size = 0; - if (!PyArg_ParseTuple(args, "ss|nnn"PY_ARG_BYTES_LENGTH, &mode, &rawmode, + if (!PyArg_ParseTuple(args, "ss|nnny#", &mode, &rawmode, &optimize, &compress_level, &compress_type, &dictionary, &dictionary_size)) @@ -693,7 +692,7 @@ PyImaging_LibTiffEncoderNew(PyObject* self, PyObject* args) item = PyList_GetItem(tags, pos); // We already checked that tags is a 2-tuple list. key = PyTuple_GetItem(item, 0); - key_int = (int)PyInt_AsLong(key); + key_int = (int)PyLong_AsLong(key); value = PyTuple_GetItem(item, 1); status = 0; is_core_tag = 0; @@ -710,7 +709,7 @@ PyImaging_LibTiffEncoderNew(PyObject* self, PyObject* args) if (!is_core_tag) { PyObject *tag_type = PyDict_GetItem(types, key); if (tag_type) { - int type_int = PyInt_AsLong(tag_type); + int type_int = PyLong_AsLong(tag_type); if (type_int >= TIFF_BYTE && type_int <= TIFF_DOUBLE) { type = (TIFFDataType)type_int; } @@ -721,7 +720,7 @@ PyImaging_LibTiffEncoderNew(PyObject* self, PyObject* args) if (type == TIFF_NOTYPE) { // Autodetect type. Types should not be changed for backwards // compatibility. - if (PyInt_Check(value)) { + if (PyLong_Check(value)) { type = TIFF_LONG; } else if (PyFloat_Check(value)) { type = TIFF_DOUBLE; @@ -749,7 +748,7 @@ PyImaging_LibTiffEncoderNew(PyObject* self, PyObject* args) if (type == TIFF_NOTYPE) { // Autodetect type based on first item. Types should not be // changed for backwards compatibility. - if (PyInt_Check(PyTuple_GetItem(value,0))) { + if (PyLong_Check(PyTuple_GetItem(value,0))) { type = TIFF_LONG; } else if (PyFloat_Check(PyTuple_GetItem(value,0))) { type = TIFF_FLOAT; @@ -775,7 +774,7 @@ PyImaging_LibTiffEncoderNew(PyObject* self, PyObject* args) av = calloc(len, sizeof(UINT8)); if (av) { for (i=0;istate, (ttag_t) key_int, len, av); free(av); @@ -786,7 +785,7 @@ PyImaging_LibTiffEncoderNew(PyObject* self, PyObject* args) av = calloc(len, sizeof(UINT16)); if (av) { for (i=0;istate, (ttag_t) key_int, len, av); free(av); @@ -797,7 +796,7 @@ PyImaging_LibTiffEncoderNew(PyObject* self, PyObject* args) av = calloc(len, sizeof(UINT32)); if (av) { for (i=0;istate, (ttag_t) key_int, len, av); free(av); @@ -808,7 +807,7 @@ PyImaging_LibTiffEncoderNew(PyObject* self, PyObject* args) av = calloc(len, sizeof(INT8)); if (av) { for (i=0;istate, (ttag_t) key_int, len, av); free(av); @@ -819,7 +818,7 @@ PyImaging_LibTiffEncoderNew(PyObject* self, PyObject* args) av = calloc(len, sizeof(INT16)); if (av) { for (i=0;istate, (ttag_t) key_int, len, av); free(av); @@ -830,7 +829,7 @@ PyImaging_LibTiffEncoderNew(PyObject* self, PyObject* args) av = calloc(len, sizeof(INT32)); if (av) { for (i=0;istate, (ttag_t) key_int, len, av); free(av); @@ -862,19 +861,19 @@ PyImaging_LibTiffEncoderNew(PyObject* self, PyObject* args) if (type == TIFF_SHORT) { status = ImagingLibTiffSetField(&encoder->state, (ttag_t) key_int, - (UINT16)PyInt_AsLong(value)); + (UINT16)PyLong_AsLong(value)); } else if (type == TIFF_LONG) { status = ImagingLibTiffSetField(&encoder->state, (ttag_t) key_int, - (UINT32)PyInt_AsLong(value)); + (UINT32)PyLong_AsLong(value)); } else if (type == TIFF_SSHORT) { status = ImagingLibTiffSetField(&encoder->state, (ttag_t) key_int, - (INT16)PyInt_AsLong(value)); + (INT16)PyLong_AsLong(value)); } else if (type == TIFF_SLONG) { status = ImagingLibTiffSetField(&encoder->state, (ttag_t) key_int, - (INT32)PyInt_AsLong(value)); + (INT32)PyLong_AsLong(value)); } else if (type == TIFF_FLOAT) { status = ImagingLibTiffSetField(&encoder->state, (ttag_t) key_int, @@ -886,11 +885,11 @@ PyImaging_LibTiffEncoderNew(PyObject* self, PyObject* args) } else if (type == TIFF_BYTE) { status = ImagingLibTiffSetField(&encoder->state, (ttag_t) key_int, - (UINT8)PyInt_AsLong(value)); + (UINT8)PyLong_AsLong(value)); } else if (type == TIFF_SBYTE) { status = ImagingLibTiffSetField(&encoder->state, (ttag_t) key_int, - (INT8)PyInt_AsLong(value)); + (INT8)PyLong_AsLong(value)); } else if (type == TIFF_ASCII) { status = ImagingLibTiffSetField(&encoder->state, (ttag_t) key_int, @@ -984,7 +983,7 @@ static unsigned int* get_qtables_arrays(PyObject* qtables, int* qtablesLen) { } table_data = PySequence_Fast(table, "expected a sequence"); for (j = 0; j < DCTSIZE2; j++) { - qarrays[i * DCTSIZE2 + j] = PyInt_AS_LONG(PySequence_Fast_GET_ITEM(table_data, j)); + qarrays[i * DCTSIZE2 + j] = PyLong_AS_LONG(PySequence_Fast_GET_ITEM(table_data, j)); } Py_DECREF(table_data); } @@ -1024,7 +1023,7 @@ PyImaging_JpegEncoderNew(PyObject* self, PyObject* args) char* rawExif = NULL; Py_ssize_t rawExifLen = 0; - if (!PyArg_ParseTuple(args, "ss|nnnnnnnnO"PY_ARG_BYTES_LENGTH""PY_ARG_BYTES_LENGTH, + if (!PyArg_ParseTuple(args, "ss|nnnnnnnnOy#y#", &mode, &rawmode, &quality, &progressive, &smooth, &optimize, &streamtype, &xdpi, &ydpi, &subsampling, &qtables, &extra, &extra_size, @@ -1109,8 +1108,8 @@ j2k_decode_coord_tuple(PyObject *tuple, int *x, int *y) *x = *y = 0; if (tuple && PyTuple_Check(tuple) && PyTuple_GET_SIZE(tuple) == 2) { - *x = (int)PyInt_AsLong(PyTuple_GET_ITEM(tuple, 0)); - *y = (int)PyInt_AsLong(PyTuple_GET_ITEM(tuple, 1)); + *x = (int)PyLong_AsLong(PyTuple_GET_ITEM(tuple, 0)); + *y = (int)PyLong_AsLong(PyTuple_GET_ITEM(tuple, 1)); if (*x < 0) *x = 0; diff --git a/src/libImaging/codec_fd.c b/src/libImaging/codec_fd.c index 7bd4dadf8..5cde31cdc 100644 --- a/src/libImaging/codec_fd.c +++ b/src/libImaging/codec_fd.c @@ -1,6 +1,5 @@ #include "Python.h" #include "Imaging.h" -#include "../py3.h" Py_ssize_t @@ -72,7 +71,7 @@ _imaging_tell_pyFd(PyObject *fd) Py_ssize_t location; result = PyObject_CallMethod(fd, "tell", NULL); - location = PyInt_AsSsize_t(result); + location = PyLong_AsSsize_t(result); Py_DECREF(result); return location; diff --git a/src/map.c b/src/map.c index 099bb4b3e..a8fb69c6e 100644 --- a/src/map.c +++ b/src/map.c @@ -22,8 +22,6 @@ #include "Imaging.h" -#include "py3.h" - /* compatibility wrappers (defined in _imaging.c) */ extern int PyImaging_CheckBuffer(PyObject* buffer); extern int PyImaging_GetBuffer(PyObject* buffer, Py_buffer *view); diff --git a/src/path.c b/src/path.c index 5f0541b0b..f69755d16 100644 --- a/src/path.c +++ b/src/path.c @@ -31,8 +31,6 @@ #include -#include "py3.h" - /* compatibility wrappers (defined in _imaging.c) */ extern int PyImaging_CheckBuffer(PyObject* buffer); extern int PyImaging_GetBuffer(PyObject* buffer, Py_buffer *view); @@ -170,8 +168,8 @@ PyPath_Flatten(PyObject* data, double **pxy) PyObject *op = PyList_GET_ITEM(data, i); if (PyFloat_Check(op)) xy[j++] = PyFloat_AS_DOUBLE(op); - else if (PyInt_Check(op)) - xy[j++] = (float) PyInt_AS_LONG(op); + else if (PyLong_Check(op)) + xy[j++] = (float) PyLong_AS_LONG(op); else if (PyNumber_Check(op)) xy[j++] = PyFloat_AsDouble(op); else if (PyArg_ParseTuple(op, "dd", &x, &y)) { @@ -188,8 +186,8 @@ PyPath_Flatten(PyObject* data, double **pxy) PyObject *op = PyTuple_GET_ITEM(data, i); if (PyFloat_Check(op)) xy[j++] = PyFloat_AS_DOUBLE(op); - else if (PyInt_Check(op)) - xy[j++] = (float) PyInt_AS_LONG(op); + else if (PyLong_Check(op)) + xy[j++] = (float) PyLong_AS_LONG(op); else if (PyNumber_Check(op)) xy[j++] = PyFloat_AsDouble(op); else if (PyArg_ParseTuple(op, "dd", &x, &y)) { @@ -217,8 +215,8 @@ PyPath_Flatten(PyObject* data, double **pxy) } if (PyFloat_Check(op)) xy[j++] = PyFloat_AS_DOUBLE(op); - else if (PyInt_Check(op)) - xy[j++] = (float) PyInt_AS_LONG(op); + else if (PyLong_Check(op)) + xy[j++] = (float) PyLong_AS_LONG(op); else if (PyNumber_Check(op)) xy[j++] = PyFloat_AsDouble(op); else if (PyArg_ParseTuple(op, "dd", &x, &y)) { @@ -552,13 +550,8 @@ path_subscript(PyPathObject* self, PyObject* item) { int len = 4; Py_ssize_t start, stop, step, slicelength; -#if PY_VERSION_HEX >= 0x03020000 if (PySlice_GetIndicesEx(item, len, &start, &stop, &step, &slicelength) < 0) return NULL; -#else - if (PySlice_GetIndicesEx((PySliceObject*)item, len, &start, &stop, &step, &slicelength) < 0) - return NULL; -#endif if (slicelength <= 0) { double *xy = alloc_array(0); diff --git a/src/py3.h b/src/py3.h deleted file mode 100644 index 310583845..000000000 --- a/src/py3.h +++ /dev/null @@ -1,56 +0,0 @@ -/* - Python3 definition file to consistently map the code to Python 2 or - Python 3. - - PyInt and PyLong were merged into PyLong in Python 3, so all PyInt functions - are mapped to PyLong. - - PyString, on the other hand, was split into PyBytes and PyUnicode. We map - both back onto PyString, so use PyBytes or PyUnicode where appropriate. The - only exception to this is _imagingft.c, where PyUnicode is left alone. -*/ - -#if PY_VERSION_HEX >= 0x03000000 -#define PY_ARG_BYTES_LENGTH "y#" - -/* Map PyInt -> PyLong */ -#define PyInt_AsLong PyLong_AsLong -#define PyInt_Check PyLong_Check -#define PyInt_FromLong PyLong_FromLong -#define PyInt_AS_LONG PyLong_AS_LONG -#define PyInt_FromSsize_t PyLong_FromSsize_t -#define PyInt_AsSsize_t PyLong_AsSsize_t - -#else /* PY_VERSION_HEX < 0x03000000 */ -#define PY_ARG_BYTES_LENGTH "s#" - -#if !defined(KEEP_PY_UNICODE) -/* Map PyUnicode -> PyString */ -#undef PyUnicode_AsString -#undef PyUnicode_AS_STRING -#undef PyUnicode_Check -#undef PyUnicode_FromStringAndSize -#undef PyUnicode_FromString -#undef PyUnicode_FromFormat -#undef PyUnicode_DecodeFSDefault - -#define PyUnicode_AsString PyString_AsString -#define PyUnicode_AS_STRING PyString_AS_STRING -#define PyUnicode_Check PyString_Check -#define PyUnicode_FromStringAndSize PyString_FromStringAndSize -#define PyUnicode_FromString PyString_FromString -#define PyUnicode_FromFormat PyString_FromFormat -#define PyUnicode_DecodeFSDefault PyString_FromString -#endif - -/* Map PyBytes -> PyString */ -#define PyBytesObject PyStringObject -#define PyBytes_AsString PyString_AsString -#define PyBytes_AS_STRING PyString_AS_STRING -#define PyBytes_Check PyString_Check -#define PyBytes_AsStringAndSize PyString_AsStringAndSize -#define PyBytes_FromStringAndSize PyString_FromStringAndSize -#define PyBytes_FromString PyString_FromString -#define _PyBytes_Resize _PyString_Resize - -#endif /* PY_VERSION_HEX < 0x03000000 */ diff --git a/tox.ini b/tox.ini index 2dc920371..11bcec500 100644 --- a/tox.ini +++ b/tox.ini @@ -6,7 +6,7 @@ [tox] envlist = lint - py{27,35,36,37} + py{35,36,37} minversion = 1.9 [testenv] diff --git a/winbuild/appveyor_install_msys2_deps.sh b/winbuild/appveyor_install_msys2_deps.sh index 02b75e210..4cc01082d 100644 --- a/winbuild/appveyor_install_msys2_deps.sh +++ b/winbuild/appveyor_install_msys2_deps.sh @@ -2,15 +2,11 @@ mkdir /var/cache/pacman/pkg pacman -S --noconfirm mingw32/mingw-w64-i686-python3-pip \ - mingw32/mingw-w64-i686-python3-setuptools \ - mingw32/mingw-w64-i686-python3-pytest \ - mingw32/mingw-w64-i686-python3-pytest-cov \ - mingw32/mingw-w64-i686-python2-pip \ - mingw32/mingw-w64-i686-python2-setuptools \ - mingw32/mingw-w64-i686-python2-pytest \ - mingw32/mingw-w64-i686-python2-pytest-cov \ - mingw-w64-i686-libjpeg-turbo \ - mingw-w64-i686-libimagequant + mingw32/mingw-w64-i686-python3-setuptools \ + mingw32/mingw-w64-i686-python3-pytest \ + mingw32/mingw-w64-i686-python3-pytest-cov \ + mingw-w64-i686-libjpeg-turbo \ + mingw-w64-i686-libimagequant C:/msys64/mingw32/bin/python3 -m pip install --upgrade pip diff --git a/winbuild/appveyor_install_pypy2.cmd b/winbuild/appveyor_install_pypy2.cmd deleted file mode 100644 index fc56d0e56..000000000 --- a/winbuild/appveyor_install_pypy2.cmd +++ /dev/null @@ -1,3 +0,0 @@ -curl -fsSL -o pypy2.zip https://bitbucket.org/pypy/pypy/downloads/pypy2.7-v7.1.1-win32.zip -7z x pypy2.zip -oc:\ -c:\Python37\Scripts\virtualenv.exe -p c:\pypy2.7-v7.1.1-win32\pypy.exe c:\vp\pypy2 diff --git a/winbuild/build.py b/winbuild/build.py index 0617022dc..f4561d933 100755 --- a/winbuild/build.py +++ b/winbuild/build.py @@ -105,10 +105,7 @@ def build_one(py_ver, compiler, bit): args["executable"] = "%EXECUTABLE%" args["py_ver"] = py_ver - if "27" in py_ver: - args["tcl_ver"] = "85" - else: - args["tcl_ver"] = "86" + args["tcl_ver"] = "86" if compiler["vc_version"] == "2015": args["imaging_libs"] = " build_ext --add-imaging-libs=msvcrt" diff --git a/winbuild/config.py b/winbuild/config.py index 16a1d9cad..46c9f9b2b 100644 --- a/winbuild/config.py +++ b/winbuild/config.py @@ -4,8 +4,6 @@ SF_MIRROR = "http://iweb.dl.sourceforge.net" PILLOW_DEPENDS_DIR = "C:\\pillow-depends\\" pythons = { - "27": {"compiler": 7, "vc": 2010}, - "pypy2": {"compiler": 7, "vc": 2010}, "35": {"compiler": 7.1, "vc": 2015}, "36": {"compiler": 7.1, "vc": 2015}, "pypy3": {"compiler": 7.1, "vc": 2015}, @@ -131,7 +129,7 @@ compilers = { def pyversion_from_env(): py = os.environ["PYTHON"] - py_version = "27" + py_version = "35" for k in pythons: if k in py: py_version = k diff --git a/winbuild/get_pythons.py b/winbuild/get_pythons.py index e24bb65f7..a853fc6f7 100644 --- a/winbuild/get_pythons.py +++ b/winbuild/get_pythons.py @@ -3,7 +3,7 @@ import os from fetch import fetch if __name__ == "__main__": - for version in ["2.7.15", "3.4.4"]: + for version in ["3.4.4"]: for platform in ["", ".amd64"]: for extension in ["", ".asc"]: fetch( From 538d9e2e5d252bea23e12da08495945d776aed78 Mon Sep 17 00:00:00 2001 From: Hugo Date: Mon, 30 Sep 2019 17:56:31 +0300 Subject: [PATCH 02/59] Upgrade Python syntax with pyupgrade --py3-plus --- Tests/check_imaging_leaks.py | 3 -- Tests/createfontdatachunk.py | 2 - Tests/helper.py | 30 ++++++----- Tests/import_all.py | 2 - Tests/make_hash.py | 2 - Tests/test_bmp_reference.py | 4 +- Tests/test_color_lut.py | 2 - Tests/test_core_resources.py | 2 - Tests/test_features.py | 2 - Tests/test_file_eps.py | 2 +- Tests/test_file_gif.py | 3 +- Tests/test_file_libtiff.py | 4 +- Tests/test_file_pdf.py | 7 ++- Tests/test_file_tiff.py | 3 +- Tests/test_file_tiff_metadata.py | 4 +- Tests/test_file_webp_animated.py | 3 +- Tests/test_file_webp_metadata.py | 6 +-- Tests/test_font_leaks.py | 2 - Tests/test_image.py | 6 +-- Tests/test_image_access.py | 4 +- Tests/test_image_array.py | 2 +- Tests/test_image_resample.py | 2 - Tests/test_imageenhance.py | 2 +- Tests/test_imagefont.py | 7 ++- Tests/test_imagefontctl.py | 1 - Tests/test_imagemath.py | 4 +- Tests/test_imageops.py | 2 +- Tests/test_imageqt.py | 2 +- Tests/test_locale.py | 2 - Tests/test_main.py | 2 - Tests/test_mode_i16.py | 6 ++- Tests/test_numpy.py | 2 - Tests/test_pdfparser.py | 2 +- Tests/test_qt_image_toqimage.py | 2 +- Tests/threaded_save.py | 2 - Tests/versions.py | 2 - docs/conf.py | 15 +++--- docs/example/DdsImagePlugin.py | 8 +-- selftest.py | 1 - setup.py | 12 ++--- src/PIL/BdfFontFile.py | 1 - src/PIL/BlpImagePlugin.py | 2 +- src/PIL/BmpImagePlugin.py | 16 +++--- src/PIL/BufrStubImagePlugin.py | 2 +- src/PIL/ContainerIO.py | 2 +- src/PIL/CurImagePlugin.py | 3 -- src/PIL/DdsImagePlugin.py | 4 +- src/PIL/EpsImagePlugin.py | 8 +-- src/PIL/FitsStubImagePlugin.py | 2 +- src/PIL/FontFile.py | 3 +- src/PIL/FpxImagePlugin.py | 9 ++-- src/PIL/GdImageFile.py | 2 +- src/PIL/GifImagePlugin.py | 2 +- src/PIL/GimpGradientFile.py | 4 +- src/PIL/GimpPaletteFile.py | 2 +- src/PIL/GribStubImagePlugin.py | 2 +- src/PIL/Hdf5StubImagePlugin.py | 2 +- src/PIL/IcnsImagePlugin.py | 2 +- src/PIL/IcoImagePlugin.py | 2 +- src/PIL/Image.py | 16 +++--- src/PIL/ImageCms.py | 30 ++++++----- src/PIL/ImageDraw.py | 2 +- src/PIL/ImageDraw2.py | 8 +-- src/PIL/ImageEnhance.py | 2 +- src/PIL/ImageFile.py | 28 +++++------ src/PIL/ImageFilter.py | 5 +- src/PIL/ImageFont.py | 16 +++--- src/PIL/ImageMath.py | 2 +- src/PIL/ImageMode.py | 2 +- src/PIL/ImageMorph.py | 6 +-- src/PIL/ImageOps.py | 2 +- src/PIL/ImagePalette.py | 4 +- src/PIL/ImageSequence.py | 2 +- src/PIL/ImageShow.py | 13 ++--- src/PIL/ImageStat.py | 2 +- src/PIL/ImageTk.py | 6 +-- src/PIL/ImageWin.py | 8 +-- src/PIL/IptcImagePlugin.py | 9 ++-- src/PIL/JpegImagePlugin.py | 5 +- src/PIL/MicImagePlugin.py | 2 +- src/PIL/MpegImagePlugin.py | 2 +- src/PIL/MspImagePlugin.py | 8 +-- src/PIL/PSDraw.py | 8 +-- src/PIL/PaletteFile.py | 2 +- src/PIL/PalmImagePlugin.py | 4 +- src/PIL/PcfFontFile.py | 2 +- src/PIL/PcxImagePlugin.py | 2 +- src/PIL/PdfParser.py | 86 ++++++++++++++++---------------- src/PIL/PngImagePlugin.py | 14 +++--- src/PIL/PpmImagePlugin.py | 2 +- src/PIL/PsdImagePlugin.py | 2 +- src/PIL/PyAccess.py | 2 +- src/PIL/SgiImagePlugin.py | 4 +- src/PIL/SpiderImagePlugin.py | 5 +- src/PIL/TarIO.py | 4 +- src/PIL/TgaImagePlugin.py | 2 +- src/PIL/TiffImagePlugin.py | 35 ++++++------- src/PIL/TiffTags.py | 2 +- src/PIL/WalImageFile.py | 1 - src/PIL/WebPImagePlugin.py | 12 ++--- src/PIL/WmfImagePlugin.py | 6 +-- src/PIL/XbmImagePlugin.py | 2 +- src/PIL/_util.py | 2 +- src/PIL/features.py | 2 - winbuild/build.py | 10 ++-- winbuild/build_dep.py | 2 +- winbuild/config.py | 2 +- winbuild/test.py | 8 +-- 108 files changed, 287 insertions(+), 357 deletions(-) diff --git a/Tests/check_imaging_leaks.py b/Tests/check_imaging_leaks.py index 2b9a9605b..35157d1c7 100755 --- a/Tests/check_imaging_leaks.py +++ b/Tests/check_imaging_leaks.py @@ -1,7 +1,4 @@ #!/usr/bin/env python - -from __future__ import division - import sys from PIL import Image diff --git a/Tests/createfontdatachunk.py b/Tests/createfontdatachunk.py index 4d189dbad..c7055995e 100755 --- a/Tests/createfontdatachunk.py +++ b/Tests/createfontdatachunk.py @@ -1,6 +1,4 @@ #!/usr/bin/env python -from __future__ import print_function - import base64 import os diff --git a/Tests/helper.py b/Tests/helper.py index 87f034449..282482940 100644 --- a/Tests/helper.py +++ b/Tests/helper.py @@ -1,7 +1,6 @@ """ Helper functions. """ -from __future__ import print_function import logging import os @@ -77,10 +76,13 @@ class PillowTestCase(unittest.TestCase): def assert_deep_equal(self, a, b, msg=None): try: self.assertEqual( - len(a), len(b), msg or "got length %s, expected %s" % (len(a), len(b)) + len(a), + len(b), + msg or "got length {}, expected {}".format(len(a), len(b)), ) self.assertTrue( - all(x == y for x, y in zip(a, b)), msg or "got %s, expected %s" % (a, b) + all(x == y for x, y in zip(a, b)), + msg or "got {}, expected {}".format(a, b), ) except Exception: self.assertEqual(a, b, msg) @@ -88,20 +90,24 @@ class PillowTestCase(unittest.TestCase): def assert_image(self, im, mode, size, msg=None): if mode is not None: self.assertEqual( - im.mode, mode, msg or "got mode %r, expected %r" % (im.mode, mode) + im.mode, + mode, + msg or "got mode {!r}, expected {!r}".format(im.mode, mode), ) if size is not None: self.assertEqual( - im.size, size, msg or "got size %r, expected %r" % (im.size, size) + im.size, + size, + msg or "got size {!r}, expected {!r}".format(im.size, size), ) def assert_image_equal(self, a, b, msg=None): self.assertEqual( - a.mode, b.mode, msg or "got mode %r, expected %r" % (a.mode, b.mode) + a.mode, b.mode, msg or "got mode {!r}, expected {!r}".format(a.mode, b.mode) ) self.assertEqual( - a.size, b.size, msg or "got size %r, expected %r" % (a.size, b.size) + a.size, b.size, msg or "got size {!r}, expected {!r}".format(a.size, b.size) ) if a.tobytes() != b.tobytes(): if HAS_UPLOADER: @@ -122,10 +128,10 @@ class PillowTestCase(unittest.TestCase): def assert_image_similar(self, a, b, epsilon, msg=None): epsilon = float(epsilon) self.assertEqual( - a.mode, b.mode, msg or "got mode %r, expected %r" % (a.mode, b.mode) + a.mode, b.mode, msg or "got mode {!r}, expected {!r}".format(a.mode, b.mode) ) self.assertEqual( - a.size, b.size, msg or "got size %r, expected %r" % (a.size, b.size) + a.size, b.size, msg or "got size {!r}, expected {!r}".format(a.size, b.size) ) a, b = convert_to_comparable(a, b) @@ -228,12 +234,12 @@ class PillowTestCase(unittest.TestCase): def open_withImagemagick(self, f): if not imagemagick_available(): - raise IOError() + raise OSError() outfile = self.tempfile("temp.png") if command_succeeds([IMCONVERT, f, outfile]): return Image.open(outfile) - raise IOError() + raise OSError() @unittest.skipIf(sys.platform.startswith("win32"), "requires Unix or macOS") @@ -371,7 +377,7 @@ def distro(): return line.strip().split("=")[1] -class cached_property(object): +class cached_property: def __init__(self, func): self.func = func diff --git a/Tests/import_all.py b/Tests/import_all.py index 4dfacb291..33b07f9a2 100644 --- a/Tests/import_all.py +++ b/Tests/import_all.py @@ -1,5 +1,3 @@ -from __future__ import print_function - import glob import os import sys diff --git a/Tests/make_hash.py b/Tests/make_hash.py index bacb391fa..7199f8c7f 100644 --- a/Tests/make_hash.py +++ b/Tests/make_hash.py @@ -1,7 +1,5 @@ # brute-force search for access descriptor hash table -from __future__ import print_function - modes = [ "1", "L", diff --git a/Tests/test_bmp_reference.py b/Tests/test_bmp_reference.py index e6a75e2c3..2038ac540 100644 --- a/Tests/test_bmp_reference.py +++ b/Tests/test_bmp_reference.py @@ -1,5 +1,3 @@ -from __future__ import print_function - import os from PIL import Image @@ -109,4 +107,4 @@ class TestBmpReference(PillowTestCase): os.path.join(base, "g", "pal4rle.bmp"), ) if f not in unsupported: - self.fail("Unsupported Image %s: %s" % (f, msg)) + self.fail("Unsupported Image {}: {}".format(f, msg)) diff --git a/Tests/test_color_lut.py b/Tests/test_color_lut.py index ca82209c2..301a99a3f 100644 --- a/Tests/test_color_lut.py +++ b/Tests/test_color_lut.py @@ -1,5 +1,3 @@ -from __future__ import division - from array import array from PIL import Image, ImageFilter diff --git a/Tests/test_core_resources.py b/Tests/test_core_resources.py index eefb1a0ef..8f3cf7105 100644 --- a/Tests/test_core_resources.py +++ b/Tests/test_core_resources.py @@ -1,5 +1,3 @@ -from __future__ import division, print_function - import sys from PIL import Image diff --git a/Tests/test_features.py b/Tests/test_features.py index 64b0302ca..b8e02edea 100644 --- a/Tests/test_features.py +++ b/Tests/test_features.py @@ -1,5 +1,3 @@ -from __future__ import unicode_literals - import io from PIL import features diff --git a/Tests/test_file_eps.py b/Tests/test_file_eps.py index 3459310df..08d58b3ba 100644 --- a/Tests/test_file_eps.py +++ b/Tests/test_file_eps.py @@ -92,7 +92,7 @@ class TestFileEps(PillowTestCase): def test_iobase_object(self): # issue 479 image1 = Image.open(file1) - with io.open(self.tempfile("temp_iobase.eps"), "wb") as fh: + with open(self.tempfile("temp_iobase.eps"), "wb") as fh: image1.save(fh, "EPS") @unittest.skipUnless(HAS_GHOSTSCRIPT, "Ghostscript not available") diff --git a/Tests/test_file_gif.py b/Tests/test_file_gif.py index 4ff9727e1..8be614412 100644 --- a/Tests/test_file_gif.py +++ b/Tests/test_file_gif.py @@ -610,8 +610,7 @@ class TestFileGif(PillowTestCase): # Tests appending using a generator def imGenerator(ims): - for im in ims: - yield im + yield from ims im.save(out, save_all=True, append_images=imGenerator(ims)) diff --git a/Tests/test_file_libtiff.py b/Tests/test_file_libtiff.py index c1cce1936..881d9096c 100644 --- a/Tests/test_file_libtiff.py +++ b/Tests/test_file_libtiff.py @@ -1,5 +1,3 @@ -from __future__ import print_function - import distutils.version import io import itertools @@ -262,7 +260,7 @@ class TestFileLibTiff(LibTiffTestCase): tc(4.25, TiffTags.FLOAT, True), tc(4.25, TiffTags.DOUBLE, True), tc("custom tag value", TiffTags.ASCII, True), - tc(u"custom tag value", TiffTags.ASCII, True), + tc("custom tag value", TiffTags.ASCII, True), tc(b"custom tag value", TiffTags.BYTE, True), tc((4, 5, 6), TiffTags.SHORT, True), tc((123456789, 9, 34, 234, 219387, 92432323), TiffTags.LONG, True), diff --git a/Tests/test_file_pdf.py b/Tests/test_file_pdf.py index 25c2f6bf6..0158807f7 100644 --- a/Tests/test_file_pdf.py +++ b/Tests/test_file_pdf.py @@ -99,8 +99,7 @@ class TestFilePdf(PillowTestCase): # Test appending using a generator def imGenerator(ims): - for im in ims: - yield im + yield from ims im.save(outfile, save_all=True, append_images=imGenerator(ims)) @@ -207,7 +206,7 @@ class TestFilePdf(PillowTestCase): # append some info pdf.info.Title = "abc" pdf.info.Author = "def" - pdf.info.Subject = u"ghi\uABCD" + pdf.info.Subject = "ghi\uABCD" pdf.info.Keywords = "qw)e\\r(ty" pdf.info.Creator = "hopper()" pdf.start_writing() @@ -235,7 +234,7 @@ class TestFilePdf(PillowTestCase): self.assertEqual(pdf.info.Title, "abc") self.assertEqual(pdf.info.Producer, "PdfParser") self.assertEqual(pdf.info.Keywords, "qw)e\\r(ty") - self.assertEqual(pdf.info.Subject, u"ghi\uABCD") + self.assertEqual(pdf.info.Subject, "ghi\uABCD") self.assertIn(b"CreationDate", pdf.info) self.assertIn(b"ModDate", pdf.info) self.check_pdf_pages_consistency(pdf) diff --git a/Tests/test_file_tiff.py b/Tests/test_file_tiff.py index 9919bacf8..d3e39e8c6 100644 --- a/Tests/test_file_tiff.py +++ b/Tests/test_file_tiff.py @@ -518,8 +518,7 @@ class TestFileTiff(PillowTestCase): # Test appending using a generator def imGenerator(ims): - for im in ims: - yield im + yield from ims mp = io.BytesIO() im.save(mp, format="TIFF", save_all=True, append_images=imGenerator(ims)) diff --git a/Tests/test_file_tiff_metadata.py b/Tests/test_file_tiff_metadata.py index 170cac71e..c30d055ae 100644 --- a/Tests/test_file_tiff_metadata.py +++ b/Tests/test_file_tiff_metadata.py @@ -157,13 +157,13 @@ class TestFileTiffMetadata(PillowTestCase): self.assert_deep_equal( original[tag], value, - "%s didn't roundtrip, %s, %s" % (tag, original[tag], value), + "{} didn't roundtrip, {}, {}".format(tag, original[tag], value), ) else: self.assertEqual( original[tag], value, - "%s didn't roundtrip, %s, %s" % (tag, original[tag], value), + "{} didn't roundtrip, {}, {}".format(tag, original[tag], value), ) for tag, value in original.items(): diff --git a/Tests/test_file_webp_animated.py b/Tests/test_file_webp_animated.py index dec74d0d0..11ccbc329 100644 --- a/Tests/test_file_webp_animated.py +++ b/Tests/test_file_webp_animated.py @@ -91,8 +91,7 @@ class TestFileWebpAnimation(PillowTestCase): # Tests appending using a generator def imGenerator(ims): - for im in ims: - yield im + yield from ims temp_file2 = self.tempfile("temp_generator.webp") frame1.copy().save( diff --git a/Tests/test_file_webp_metadata.py b/Tests/test_file_webp_metadata.py index ae528e3bf..39d6f30c4 100644 --- a/Tests/test_file_webp_metadata.py +++ b/Tests/test_file_webp_metadata.py @@ -114,9 +114,9 @@ class TestFileWebpMetadata(PillowTestCase): if not _webp.HAVE_WEBPANIM: self.skipTest("WebP animation support not available") - iccp_data = "".encode("utf-8") - exif_data = "".encode("utf-8") - xmp_data = "".encode("utf-8") + iccp_data = b"" + exif_data = b"" + xmp_data = b"" temp_file = self.tempfile("temp.webp") frame1 = Image.open("Tests/images/anim_frame1.webp") diff --git a/Tests/test_font_leaks.py b/Tests/test_font_leaks.py index 14b368585..9f172c212 100644 --- a/Tests/test_font_leaks.py +++ b/Tests/test_font_leaks.py @@ -1,5 +1,3 @@ -from __future__ import division - import sys from PIL import Image, ImageDraw, ImageFont, features diff --git a/Tests/test_image.py b/Tests/test_image.py index 53f0199de..75b5e778a 100644 --- a/Tests/test_image.py +++ b/Tests/test_image.py @@ -106,7 +106,7 @@ class TestImage(PillowTestCase): def test_fp_name(self): temp_file = self.tempfile("temp.jpg") - class FP(object): + class FP: def write(a, b): pass @@ -588,11 +588,11 @@ class TestImage(PillowTestCase): try: im.load() self.assertFail() - except IOError as e: + except OSError as e: self.assertEqual(str(e), "buffer overrun when reading image file") -class MockEncoder(object): +class MockEncoder: pass diff --git a/Tests/test_image_access.py b/Tests/test_image_access.py index 4004ca5df..a15a8b75e 100644 --- a/Tests/test_image_access.py +++ b/Tests/test_image_access.py @@ -122,7 +122,7 @@ class TestImageGetPixel(AccessTest): self.assertEqual( im.getpixel((0, 0)), c, - "put/getpixel roundtrip failed for mode %s, color %s" % (mode, c), + "put/getpixel roundtrip failed for mode {}, color {}".format(mode, c), ) # check putpixel negative index @@ -151,7 +151,7 @@ class TestImageGetPixel(AccessTest): self.assertEqual( im.getpixel((0, 0)), c, - "initial color failed for mode %s, color %s " % (mode, c), + "initial color failed for mode {}, color {} ".format(mode, c), ) # check initial color negative index self.assertEqual( diff --git a/Tests/test_image_array.py b/Tests/test_image_array.py index 02e5c80f2..8277e42af 100644 --- a/Tests/test_image_array.py +++ b/Tests/test_image_array.py @@ -25,7 +25,7 @@ class TestImageArray(PillowTestCase): self.assertEqual(test("RGBX"), (3, (100, 128, 4), "|u1", 51200)) def test_fromarray(self): - class Wrapper(object): + class Wrapper: """ Class with API matching Image.fromarray """ def __init__(self, img, arr_params): diff --git a/Tests/test_image_resample.py b/Tests/test_image_resample.py index 7d1dc009d..b831b3e4a 100644 --- a/Tests/test_image_resample.py +++ b/Tests/test_image_resample.py @@ -1,5 +1,3 @@ -from __future__ import division, print_function - from contextlib import contextmanager from PIL import Image, ImageDraw diff --git a/Tests/test_imageenhance.py b/Tests/test_imageenhance.py index b2235853a..d0d994eee 100644 --- a/Tests/test_imageenhance.py +++ b/Tests/test_imageenhance.py @@ -35,7 +35,7 @@ class TestImageEnhance(PillowTestCase): self.assert_image_equal( im.getchannel("A"), original.getchannel("A"), - "Diff on %s: %s" % (op, amount), + "Diff on {}: {}".format(op, amount), ) def test_alpha(self): diff --git a/Tests/test_imagefont.py b/Tests/test_imagefont.py index 6a2d572a9..6daac701b 100644 --- a/Tests/test_imagefont.py +++ b/Tests/test_imagefont.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- import copy import distutils.version import os @@ -20,7 +19,7 @@ HAS_FREETYPE = features.check("freetype2") HAS_RAQM = features.check("raqm") -class SimplePatcher(object): +class SimplePatcher: def __init__(self, parent_obj, attr_name, value): self._parent_obj = parent_obj self._attr_name = attr_name @@ -462,7 +461,7 @@ class TestImageFont(PillowTestCase): # issue #2826 font = ImageFont.load_default() with self.assertRaises(UnicodeEncodeError): - font.getsize(u"’") + font.getsize("’") @unittest.skipIf( sys.version.startswith("2") or hasattr(sys, "pypy_translation_info"), @@ -470,7 +469,7 @@ class TestImageFont(PillowTestCase): ) def test_unicode_extended(self): # issue #3777 - text = u"A\u278A\U0001F12B" + text = "A\u278A\U0001F12B" target = "Tests/images/unicode_extended.png" ttf = ImageFont.truetype( diff --git a/Tests/test_imagefontctl.py b/Tests/test_imagefontctl.py index 5b88f94cc..84bf1c58f 100644 --- a/Tests/test_imagefontctl.py +++ b/Tests/test_imagefontctl.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- from PIL import Image, ImageDraw, ImageFont, features from .helper import PillowTestCase, unittest diff --git a/Tests/test_imagemath.py b/Tests/test_imagemath.py index da41b3a12..8d2b94226 100644 --- a/Tests/test_imagemath.py +++ b/Tests/test_imagemath.py @@ -1,5 +1,3 @@ -from __future__ import print_function - from PIL import Image, ImageMath from .helper import PillowTestCase @@ -7,7 +5,7 @@ from .helper import PillowTestCase def pixel(im): if hasattr(im, "im"): - return "%s %r" % (im.mode, im.getpixel((0, 0))) + return "{} {!r}".format(im.mode, im.getpixel((0, 0))) else: if isinstance(im, int): return int(im) # hack to deal with booleans diff --git a/Tests/test_imageops.py b/Tests/test_imageops.py index 2cdbbe02f..d0fd73689 100644 --- a/Tests/test_imageops.py +++ b/Tests/test_imageops.py @@ -11,7 +11,7 @@ except ImportError: class TestImageOps(PillowTestCase): - class Deformer(object): + class Deformer: def getmesh(self, im): x, y = im.size return [((0, 0, x, y), (0, 0, x, 0, x, y, y, 0))] diff --git a/Tests/test_imageqt.py b/Tests/test_imageqt.py index 9248291c3..8134dcbc4 100644 --- a/Tests/test_imageqt.py +++ b/Tests/test_imageqt.py @@ -15,7 +15,7 @@ else: test_case.skipTest("Qt bindings are not installed") -class PillowQtTestCase(object): +class PillowQtTestCase: def setUp(self): skip_if_qt_is_not_installed(self) diff --git a/Tests/test_locale.py b/Tests/test_locale.py index cbec8b965..14b2317ab 100644 --- a/Tests/test_locale.py +++ b/Tests/test_locale.py @@ -1,5 +1,3 @@ -from __future__ import print_function - import locale from PIL import Image diff --git a/Tests/test_main.py b/Tests/test_main.py index 847def834..9f3758256 100644 --- a/Tests/test_main.py +++ b/Tests/test_main.py @@ -1,5 +1,3 @@ -from __future__ import unicode_literals - import os import subprocess import sys diff --git a/Tests/test_mode_i16.py b/Tests/test_mode_i16.py index b1cf2a233..5b2ace010 100644 --- a/Tests/test_mode_i16.py +++ b/Tests/test_mode_i16.py @@ -20,7 +20,11 @@ class TestModeI16(PillowTestCase): self.assertEqual( p1, p2, - ("got %r from mode %s at %s, expected %r" % (p1, im1.mode, xy, p2)), + ( + "got {!r} from mode {} at {}, expected {!r}".format( + p1, im1.mode, xy, p2 + ) + ), ) def test_basic(self): diff --git a/Tests/test_numpy.py b/Tests/test_numpy.py index 872ecdbb6..358180f3f 100644 --- a/Tests/test_numpy.py +++ b/Tests/test_numpy.py @@ -1,5 +1,3 @@ -from __future__ import print_function - from PIL import Image from .helper import PillowTestCase, hopper, unittest diff --git a/Tests/test_pdfparser.py b/Tests/test_pdfparser.py index 0d4923480..d599c8264 100644 --- a/Tests/test_pdfparser.py +++ b/Tests/test_pdfparser.py @@ -22,7 +22,7 @@ class TestPdfParser(PillowTestCase): self.assertEqual(encode_text("abc"), b"\xFE\xFF\x00a\x00b\x00c") self.assertEqual(decode_text(b"\xFE\xFF\x00a\x00b\x00c"), "abc") self.assertEqual(decode_text(b"abc"), "abc") - self.assertEqual(decode_text(b"\x1B a \x1C"), u"\u02D9 a \u02DD") + self.assertEqual(decode_text(b"\x1B a \x1C"), "\u02D9 a \u02DD") def test_indirect_refs(self): self.assertEqual(IndirectReference(1, 2), IndirectReference(1, 2)) diff --git a/Tests/test_qt_image_toqimage.py b/Tests/test_qt_image_toqimage.py index 5115c1862..39d60d197 100644 --- a/Tests/test_qt_image_toqimage.py +++ b/Tests/test_qt_image_toqimage.py @@ -57,7 +57,7 @@ if ImageQt.qt_is_installed: class Example(QWidget): def __init__(self): - super(Example, self).__init__() + super().__init__() img = hopper().resize((1000, 1000)) diff --git a/Tests/threaded_save.py b/Tests/threaded_save.py index 11eb86779..d47c2cf99 100644 --- a/Tests/threaded_save.py +++ b/Tests/threaded_save.py @@ -1,5 +1,3 @@ -from __future__ import print_function - import io import queue import sys diff --git a/Tests/versions.py b/Tests/versions.py index 1ac226c9d..d6433b063 100644 --- a/Tests/versions.py +++ b/Tests/versions.py @@ -1,5 +1,3 @@ -from __future__ import print_function - from PIL import Image diff --git a/docs/conf.py b/docs/conf.py index a9ca91de7..66effdfb2 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- # # Pillow (PIL Fork) documentation build configuration file, created by # sphinx-quickstart on Sat Apr 4 07:54:11 2015. @@ -42,9 +41,9 @@ source_suffix = ".rst" master_doc = "index" # General information about the project. -project = u"Pillow (PIL Fork)" -copyright = u"1995-2011 Fredrik Lundh, 2010-2019 Alex Clark and Contributors" -author = u"Fredrik Lundh, Alex Clark and Contributors" +project = "Pillow (PIL Fork)" +copyright = "1995-2011 Fredrik Lundh, 2010-2019 Alex Clark and Contributors" +author = "Fredrik Lundh, Alex Clark and Contributors" # The version info for the project you're documenting, acts as replacement for # |version| and |release|, also used in various other places throughout the @@ -220,8 +219,8 @@ latex_documents = [ ( master_doc, "PillowPILFork.tex", - u"Pillow (PIL Fork) Documentation", - u"Alex Clark", + "Pillow (PIL Fork) Documentation", + "Alex Clark", "manual", ) ] @@ -252,7 +251,7 @@ latex_documents = [ # One entry per manual page. List of tuples # (source start file, name, description, authors, manual section). man_pages = [ - (master_doc, "pillowpilfork", u"Pillow (PIL Fork) Documentation", [author], 1) + (master_doc, "pillowpilfork", "Pillow (PIL Fork) Documentation", [author], 1) ] # If true, show URL addresses after external links. @@ -268,7 +267,7 @@ texinfo_documents = [ ( master_doc, "PillowPILFork", - u"Pillow (PIL Fork) Documentation", + "Pillow (PIL Fork) Documentation", author, "PillowPILFork", "Pillow is the friendly PIL fork by Alex Clark and Contributors.", diff --git a/docs/example/DdsImagePlugin.py b/docs/example/DdsImagePlugin.py index be493a316..45f63f164 100644 --- a/docs/example/DdsImagePlugin.py +++ b/docs/example/DdsImagePlugin.py @@ -212,10 +212,10 @@ class DdsImageFile(ImageFile.ImageFile): def _open(self): magic, header_size = struct.unpack("= 3 and option[2]: version = " (%s)" % option[2] - print("--- %s support available%s" % (option[1], version)) + print("--- {} support available{}".format(option[1], version)) else: print("*** %s support not available" % option[1]) all = 0 diff --git a/src/PIL/BdfFontFile.py b/src/PIL/BdfFontFile.py index fdf2c097e..13c79b460 100644 --- a/src/PIL/BdfFontFile.py +++ b/src/PIL/BdfFontFile.py @@ -17,7 +17,6 @@ # See the README file for information on usage and redistribution. # -from __future__ import print_function from . import FontFile, Image diff --git a/src/PIL/BlpImagePlugin.py b/src/PIL/BlpImagePlugin.py index 7b97964a8..8a9d656de 100644 --- a/src/PIL/BlpImagePlugin.py +++ b/src/PIL/BlpImagePlugin.py @@ -283,7 +283,7 @@ class _BLPBaseDecoder(ImageFile.PyDecoder): self._read_blp_header() self._load() except struct.error: - raise IOError("Truncated Blp file") + raise OSError("Truncated Blp file") return 0, 0 def _read_palette(self): diff --git a/src/PIL/BmpImagePlugin.py b/src/PIL/BmpImagePlugin.py index 8426e2497..2b46c97a8 100644 --- a/src/PIL/BmpImagePlugin.py +++ b/src/PIL/BmpImagePlugin.py @@ -148,7 +148,7 @@ class BmpImageFile(ImageFile.ImageFile): file_info["a_mask"], ) else: - raise IOError("Unsupported BMP header type (%d)" % file_info["header_size"]) + raise OSError("Unsupported BMP header type (%d)" % file_info["header_size"]) # ------------------ Special case : header is reported 40, which # ---------------------- is shorter than real size for bpp >= 16 @@ -163,12 +163,12 @@ class BmpImageFile(ImageFile.ImageFile): # ------------------------------- Check abnormal values for DOS attacks if file_info["width"] * file_info["height"] > 2 ** 31: - raise IOError("Unsupported BMP Size: (%dx%d)" % self.size) + raise OSError("Unsupported BMP Size: (%dx%d)" % self.size) # ---------------------- Check bit depth for unusual unsupported values self.mode, raw_mode = BIT2MODE.get(file_info["bits"], (None, None)) if self.mode is None: - raise IOError("Unsupported BMP pixel depth (%d)" % file_info["bits"]) + raise OSError("Unsupported BMP pixel depth (%d)" % file_info["bits"]) # ---------------- Process BMP with Bitfields compression (not palette) if file_info["compression"] == self.BITFIELDS: @@ -206,21 +206,21 @@ class BmpImageFile(ImageFile.ImageFile): ): raw_mode = MASK_MODES[(file_info["bits"], file_info["rgb_mask"])] else: - raise IOError("Unsupported BMP bitfields layout") + raise OSError("Unsupported BMP bitfields layout") else: - raise IOError("Unsupported BMP bitfields layout") + raise OSError("Unsupported BMP bitfields layout") elif file_info["compression"] == self.RAW: if file_info["bits"] == 32 and header == 22: # 32-bit .cur offset raw_mode, self.mode = "BGRA", "RGBA" else: - raise IOError("Unsupported BMP compression (%d)" % file_info["compression"]) + raise OSError("Unsupported BMP compression (%d)" % file_info["compression"]) # --------------- Once the header is processed, process the palette/LUT if self.mode == "P": # Paletted for 1, 4 and 8 bit images # ---------------------------------------------------- 1-bit images if not (0 < file_info["colors"] <= 65536): - raise IOError("Unsupported BMP Palette size (%d)" % file_info["colors"]) + raise OSError("Unsupported BMP Palette size (%d)" % file_info["colors"]) else: padding = file_info["palette_padding"] palette = read(padding * file_info["colors"]) @@ -309,7 +309,7 @@ def _save(im, fp, filename, bitmap_header=True): try: rawmode, bits, colors = SAVE[im.mode] except KeyError: - raise IOError("cannot write mode %s as BMP" % im.mode) + raise OSError("cannot write mode %s as BMP" % im.mode) info = im.encoderinfo diff --git a/src/PIL/BufrStubImagePlugin.py b/src/PIL/BufrStubImagePlugin.py index 56cac3bb1..48f21e1b3 100644 --- a/src/PIL/BufrStubImagePlugin.py +++ b/src/PIL/BufrStubImagePlugin.py @@ -60,7 +60,7 @@ class BufrStubImageFile(ImageFile.StubImageFile): def _save(im, fp, filename): if _handler is None or not hasattr("_handler", "save"): - raise IOError("BUFR save handler not installed") + raise OSError("BUFR save handler not installed") _handler.save(im, fp, filename) diff --git a/src/PIL/ContainerIO.py b/src/PIL/ContainerIO.py index 3cf9d82d2..9727601ab 100644 --- a/src/PIL/ContainerIO.py +++ b/src/PIL/ContainerIO.py @@ -21,7 +21,7 @@ import io -class ContainerIO(object): +class ContainerIO: def __init__(self, file, offset, length): """ Create file object. diff --git a/src/PIL/CurImagePlugin.py b/src/PIL/CurImagePlugin.py index 9e2d8c96f..054b00d27 100644 --- a/src/PIL/CurImagePlugin.py +++ b/src/PIL/CurImagePlugin.py @@ -15,9 +15,6 @@ # # See the README file for information on usage and redistribution. # - -from __future__ import print_function - from . import BmpImagePlugin, Image from ._binary import i8, i16le as i16, i32le as i32 diff --git a/src/PIL/DdsImagePlugin.py b/src/PIL/DdsImagePlugin.py index b2d508942..57769af78 100644 --- a/src/PIL/DdsImagePlugin.py +++ b/src/PIL/DdsImagePlugin.py @@ -106,10 +106,10 @@ class DdsImageFile(ImageFile.ImageFile): def _open(self): magic, header_size = struct.unpack("= xsize: diff --git a/src/PIL/GdImageFile.py b/src/PIL/GdImageFile.py index 2d492358c..d6113f8d7 100644 --- a/src/PIL/GdImageFile.py +++ b/src/PIL/GdImageFile.py @@ -87,4 +87,4 @@ def open(fp, mode="r"): try: return GdImageFile(fp) except SyntaxError: - raise IOError("cannot identify this image file") + raise OSError("cannot identify this image file") diff --git a/src/PIL/GifImagePlugin.py b/src/PIL/GifImagePlugin.py index 9d8e96fee..69ff2f66a 100644 --- a/src/PIL/GifImagePlugin.py +++ b/src/PIL/GifImagePlugin.py @@ -853,7 +853,7 @@ def getdata(im, offset=(0, 0), **params): """ - class Collector(object): + class Collector: data = [] def write(self, data): diff --git a/src/PIL/GimpGradientFile.py b/src/PIL/GimpGradientFile.py index f48e7f76e..851e24d62 100644 --- a/src/PIL/GimpGradientFile.py +++ b/src/PIL/GimpGradientFile.py @@ -60,7 +60,7 @@ def sphere_decreasing(middle, pos): SEGMENTS = [linear, curved, sine, sphere_increasing, sphere_decreasing] -class GradientFile(object): +class GradientFile: gradient = None @@ -132,7 +132,7 @@ class GimpGradientFile(GradientFile): cspace = int(s[12]) if cspace != 0: - raise IOError("cannot handle HSV colour space") + raise OSError("cannot handle HSV colour space") gradient.append((x0, x1, xm, rgb0, rgb1, segment)) diff --git a/src/PIL/GimpPaletteFile.py b/src/PIL/GimpPaletteFile.py index 2994bbeab..e3060ab8a 100644 --- a/src/PIL/GimpPaletteFile.py +++ b/src/PIL/GimpPaletteFile.py @@ -22,7 +22,7 @@ from ._binary import o8 # File handler for GIMP's palette format. -class GimpPaletteFile(object): +class GimpPaletteFile: rawmode = "RGB" diff --git a/src/PIL/GribStubImagePlugin.py b/src/PIL/GribStubImagePlugin.py index 8a24a9829..515c272f7 100644 --- a/src/PIL/GribStubImagePlugin.py +++ b/src/PIL/GribStubImagePlugin.py @@ -61,7 +61,7 @@ class GribStubImageFile(ImageFile.StubImageFile): def _save(im, fp, filename): if _handler is None or not hasattr("_handler", "save"): - raise IOError("GRIB save handler not installed") + raise OSError("GRIB save handler not installed") _handler.save(im, fp, filename) diff --git a/src/PIL/Hdf5StubImagePlugin.py b/src/PIL/Hdf5StubImagePlugin.py index a3ea12f99..362f2d399 100644 --- a/src/PIL/Hdf5StubImagePlugin.py +++ b/src/PIL/Hdf5StubImagePlugin.py @@ -60,7 +60,7 @@ class HDF5StubImageFile(ImageFile.StubImageFile): def _save(im, fp, filename): if _handler is None or not hasattr("_handler", "save"): - raise IOError("HDF5 save handler not installed") + raise OSError("HDF5 save handler not installed") _handler.save(im, fp, filename) diff --git a/src/PIL/IcnsImagePlugin.py b/src/PIL/IcnsImagePlugin.py index 75ea18b6b..03484cf0e 100644 --- a/src/PIL/IcnsImagePlugin.py +++ b/src/PIL/IcnsImagePlugin.py @@ -128,7 +128,7 @@ def read_png_or_jpeg2000(fobj, start_length, size): raise ValueError("Unsupported icon subimage format") -class IcnsFile(object): +class IcnsFile: SIZES = { (512, 512, 2): [(b"ic10", read_png_or_jpeg2000)], diff --git a/src/PIL/IcoImagePlugin.py b/src/PIL/IcoImagePlugin.py index 148e604f8..ff1d37d7d 100644 --- a/src/PIL/IcoImagePlugin.py +++ b/src/PIL/IcoImagePlugin.py @@ -86,7 +86,7 @@ def _accept(prefix): return prefix[:4] == _MAGIC -class IcoFile(object): +class IcoFile: def __init__(self, buf): """ Parse image from file-like object containing ico file data diff --git a/src/PIL/Image.py b/src/PIL/Image.py index c73cb5eb8..026b04491 100644 --- a/src/PIL/Image.py +++ b/src/PIL/Image.py @@ -55,7 +55,7 @@ class DecompressionBombError(Exception): pass -class _imaging_not_installed(object): +class _imaging_not_installed: # module placeholder def __getattr__(self, id): raise ImportError("The _imaging C module is not installed") @@ -427,7 +427,7 @@ def _getdecoder(mode, decoder_name, args, extra=()): decoder = getattr(core, decoder_name + "_decoder") return decoder(mode, *args + extra) except AttributeError: - raise IOError("decoder %s not available" % decoder_name) + raise OSError("decoder %s not available" % decoder_name) def _getencoder(mode, encoder_name, args, extra=()): @@ -448,7 +448,7 @@ def _getencoder(mode, encoder_name, args, extra=()): encoder = getattr(core, encoder_name + "_encoder") return encoder(mode, *args + extra) except AttributeError: - raise IOError("encoder %s not available" % encoder_name) + raise OSError("encoder %s not available" % encoder_name) # -------------------------------------------------------------------- @@ -459,7 +459,7 @@ def coerce_e(value): return value if isinstance(value, _E) else _E(value) -class _E(object): +class _E: def __init__(self, data): self.data = data @@ -500,7 +500,7 @@ def _getscaleoffset(expr): # Implementation wrapper -class Image(object): +class Image: """ This class represents an image object. To create :py:class:`~PIL.Image.Image` objects, use the appropriate factory @@ -2389,12 +2389,12 @@ class Image(object): # Abstract handlers. -class ImagePointHandler(object): +class ImagePointHandler: # used as a mixin by point transforms (for use with im.point) pass -class ImageTransformHandler(object): +class ImageTransformHandler: # used as a mixin by geometry transforms (for use with im.transform) pass @@ -2772,7 +2772,7 @@ def open(fp, mode="r"): fp.close() for message in accept_warnings: warnings.warn(message) - raise IOError("cannot identify image file %r" % (filename if filename else fp)) + raise OSError("cannot identify image file %r" % (filename if filename else fp)) # diff --git a/src/PIL/ImageCms.py b/src/PIL/ImageCms.py index ed4eefc0d..89a4426fa 100644 --- a/src/PIL/ImageCms.py +++ b/src/PIL/ImageCms.py @@ -15,8 +15,6 @@ # See the README file for information on usage and redistribution. See # below for the original description. -from __future__ import print_function - import sys from PIL import Image @@ -152,7 +150,7 @@ for flag in FLAGS.values(): # Profile. -class ImageCmsProfile(object): +class ImageCmsProfile: def __init__(self, profile): """ :param profile: Either a string representing a filename, @@ -374,7 +372,7 @@ def profileToProfile( imOut = None else: imOut = transform.apply(im) - except (IOError, TypeError, ValueError) as v: + except (OSError, TypeError, ValueError) as v: raise PyCMSError(v) return imOut @@ -398,7 +396,7 @@ def getOpenProfile(profileFilename): try: return ImageCmsProfile(profileFilename) - except (IOError, TypeError, ValueError) as v: + except (OSError, TypeError, ValueError) as v: raise PyCMSError(v) @@ -479,7 +477,7 @@ def buildTransform( return ImageCmsTransform( inputProfile, outputProfile, inMode, outMode, renderingIntent, flags=flags ) - except (IOError, TypeError, ValueError) as v: + except (OSError, TypeError, ValueError) as v: raise PyCMSError(v) @@ -590,7 +588,7 @@ def buildProofTransform( proofRenderingIntent, flags, ) - except (IOError, TypeError, ValueError) as v: + except (OSError, TypeError, ValueError) as v: raise PyCMSError(v) @@ -733,9 +731,9 @@ def getProfileName(profile): return (profile.profile.profile_description or "") + "\n" if not manufacturer or len(model) > 30: return model + "\n" - return "%s - %s\n" % (model, manufacturer) + return "{} - {}\n".format(model, manufacturer) - except (AttributeError, IOError, TypeError, ValueError) as v: + except (AttributeError, OSError, TypeError, ValueError) as v: raise PyCMSError(v) @@ -775,7 +773,7 @@ def getProfileInfo(profile): arr.append(elt) return "\r\n\r\n".join(arr) + "\r\n\r\n" - except (AttributeError, IOError, TypeError, ValueError) as v: + except (AttributeError, OSError, TypeError, ValueError) as v: raise PyCMSError(v) @@ -803,7 +801,7 @@ def getProfileCopyright(profile): if not isinstance(profile, ImageCmsProfile): profile = ImageCmsProfile(profile) return (profile.profile.copyright or "") + "\n" - except (AttributeError, IOError, TypeError, ValueError) as v: + except (AttributeError, OSError, TypeError, ValueError) as v: raise PyCMSError(v) @@ -831,7 +829,7 @@ def getProfileManufacturer(profile): if not isinstance(profile, ImageCmsProfile): profile = ImageCmsProfile(profile) return (profile.profile.manufacturer or "") + "\n" - except (AttributeError, IOError, TypeError, ValueError) as v: + except (AttributeError, OSError, TypeError, ValueError) as v: raise PyCMSError(v) @@ -860,7 +858,7 @@ def getProfileModel(profile): if not isinstance(profile, ImageCmsProfile): profile = ImageCmsProfile(profile) return (profile.profile.model or "") + "\n" - except (AttributeError, IOError, TypeError, ValueError) as v: + except (AttributeError, OSError, TypeError, ValueError) as v: raise PyCMSError(v) @@ -889,7 +887,7 @@ def getProfileDescription(profile): if not isinstance(profile, ImageCmsProfile): profile = ImageCmsProfile(profile) return (profile.profile.profile_description or "") + "\n" - except (AttributeError, IOError, TypeError, ValueError) as v: + except (AttributeError, OSError, TypeError, ValueError) as v: raise PyCMSError(v) @@ -928,7 +926,7 @@ def getDefaultIntent(profile): if not isinstance(profile, ImageCmsProfile): profile = ImageCmsProfile(profile) return profile.profile.rendering_intent - except (AttributeError, IOError, TypeError, ValueError) as v: + except (AttributeError, OSError, TypeError, ValueError) as v: raise PyCMSError(v) @@ -979,7 +977,7 @@ def isIntentSupported(profile, intent, direction): return 1 else: return -1 - except (AttributeError, IOError, TypeError, ValueError) as v: + except (AttributeError, OSError, TypeError, ValueError) as v: raise PyCMSError(v) diff --git a/src/PIL/ImageDraw.py b/src/PIL/ImageDraw.py index ed3383f05..65e4d7ac9 100644 --- a/src/PIL/ImageDraw.py +++ b/src/PIL/ImageDraw.py @@ -45,7 +45,7 @@ directly. """ -class ImageDraw(object): +class ImageDraw: def __init__(self, im, mode=None): """ Create a drawing instance. diff --git a/src/PIL/ImageDraw2.py b/src/PIL/ImageDraw2.py index 324d869f0..20b5fe4c4 100644 --- a/src/PIL/ImageDraw2.py +++ b/src/PIL/ImageDraw2.py @@ -19,25 +19,25 @@ from . import Image, ImageColor, ImageDraw, ImageFont, ImagePath -class Pen(object): +class Pen: def __init__(self, color, width=1, opacity=255): self.color = ImageColor.getrgb(color) self.width = width -class Brush(object): +class Brush: def __init__(self, color, opacity=255): self.color = ImageColor.getrgb(color) -class Font(object): +class Font: def __init__(self, color, file, size=12): # FIXME: add support for bitmap fonts self.color = ImageColor.getrgb(color) self.font = ImageFont.truetype(file, size) -class Draw(object): +class Draw: def __init__(self, image, size=None, color=None): if not hasattr(image, "im"): image = Image.new(image, size, color) diff --git a/src/PIL/ImageEnhance.py b/src/PIL/ImageEnhance.py index 534eb4f16..3b79d5c46 100644 --- a/src/PIL/ImageEnhance.py +++ b/src/PIL/ImageEnhance.py @@ -21,7 +21,7 @@ from . import Image, ImageFilter, ImageStat -class _Enhance(object): +class _Enhance: def enhance(self, factor): """ Returns an enhanced image. diff --git a/src/PIL/ImageFile.py b/src/PIL/ImageFile.py index 836e6318c..354159703 100644 --- a/src/PIL/ImageFile.py +++ b/src/PIL/ImageFile.py @@ -56,7 +56,7 @@ def raise_ioerror(error): message = ERRORS.get(error) if not message: message = "decoder error %d" % error - raise IOError(message + " when reading image file") + raise OSError(message + " when reading image file") # @@ -145,7 +145,7 @@ class ImageFile(Image.Image): pixel = Image.Image.load(self) if self.tile is None: - raise IOError("cannot load this image") + raise OSError("cannot load this image") if not self.tile: return pixel @@ -203,7 +203,7 @@ class ImageFile(Image.Image): # we might need to reload the palette data. if self.palette: self.palette.dirty = 1 - except (AttributeError, EnvironmentError, ImportError): + except (AttributeError, OSError, ImportError): self.map = None self.load_prepare() @@ -238,13 +238,13 @@ class ImageFile(Image.Image): if LOAD_TRUNCATED_IMAGES: break else: - raise IOError("image file is truncated") + raise OSError("image file is truncated") if not s: # truncated jpeg if LOAD_TRUNCATED_IMAGES: break else: - raise IOError( + raise OSError( "image file is truncated " "(%d bytes not processed)" % len(b) ) @@ -322,7 +322,7 @@ class StubImageFile(ImageFile): def load(self): loader = self._load() if loader is None: - raise IOError("cannot find loader for this %s file" % self.format) + raise OSError("cannot find loader for this %s file" % self.format) image = loader.load(self) assert image is not None # become the other object (!) @@ -334,7 +334,7 @@ class StubImageFile(ImageFile): raise NotImplementedError("StubImageFile subclass must implement _load") -class Parser(object): +class Parser: """ Incremental image parser. This class implements the standard feed/close consumer interface. @@ -411,7 +411,7 @@ class Parser(object): try: with io.BytesIO(self.data) as fp: im = Image.open(fp) - except IOError: + except OSError: # traceback.print_exc() pass # not enough data else: @@ -456,9 +456,9 @@ class Parser(object): self.feed(b"") self.data = self.decoder = None if not self.finished: - raise IOError("image was incomplete") + raise OSError("image was incomplete") if not self.image: - raise IOError("cannot parse this image") + raise OSError("cannot parse this image") if self.data: # incremental parsing not possible; reopen the file # not that we have all data @@ -514,7 +514,7 @@ def _save(im, fp, tile, bufsize=0): if s: break if s < 0: - raise IOError("encoder error %d when writing image file" % s) + raise OSError("encoder error %d when writing image file" % s) e.cleanup() else: # slight speedup: compress to real file object @@ -529,7 +529,7 @@ def _save(im, fp, tile, bufsize=0): else: s = e.encode_to_file(fh, bufsize) if s < 0: - raise IOError("encoder error %d when writing image file" % s) + raise OSError("encoder error %d when writing image file" % s) e.cleanup() if hasattr(fp, "flush"): fp.flush() @@ -559,7 +559,7 @@ def _safe_read(fp, size): return b"".join(data) -class PyCodecState(object): +class PyCodecState: def __init__(self): self.xsize = 0 self.ysize = 0 @@ -570,7 +570,7 @@ class PyCodecState(object): return (self.xoff, self.yoff, self.xoff + self.xsize, self.yoff + self.ysize) -class PyDecoder(object): +class PyDecoder: """ Python implementation of a format decoder. Override this class and add the decoding logic in the `decode` method. diff --git a/src/PIL/ImageFilter.py b/src/PIL/ImageFilter.py index fa4162b61..9cb62ad27 100644 --- a/src/PIL/ImageFilter.py +++ b/src/PIL/ImageFilter.py @@ -14,9 +14,6 @@ # # See the README file for information on usage and redistribution. # - -from __future__ import division - import functools try: @@ -25,7 +22,7 @@ except ImportError: # pragma: no cover numpy = None -class Filter(object): +class Filter: pass diff --git a/src/PIL/ImageFont.py b/src/PIL/ImageFont.py index 1453a290c..f1bd6242a 100644 --- a/src/PIL/ImageFont.py +++ b/src/PIL/ImageFont.py @@ -35,7 +35,7 @@ LAYOUT_BASIC = 0 LAYOUT_RAQM = 1 -class _imagingft_not_installed(object): +class _imagingft_not_installed: # module placeholder def __getattr__(self, id): raise ImportError("The _imagingft C module is not installed") @@ -63,7 +63,7 @@ except ImportError: # -------------------------------------------------------------------- -class ImageFont(object): +class ImageFont: "PIL font wrapper" def _load_pilfont(self, filename): @@ -79,7 +79,7 @@ class ImageFont(object): if image and image.mode in ("1", "L"): break else: - raise IOError("cannot find glyph data file") + raise OSError("cannot find glyph data file") self.file = fullname @@ -145,7 +145,7 @@ class ImageFont(object): # truetype factory function to create font objects. -class FreeTypeFont(object): +class FreeTypeFont: "FreeType font wrapper (requires _imagingft service)" def __init__(self, font=None, size=10, index=0, encoding="", layout_engine=None): @@ -542,7 +542,7 @@ class FreeTypeFont(object): raise NotImplementedError("FreeType 2.9.1 or greater is required") -class TransposedFont(object): +class TransposedFont: "Wrapper for writing rotated or mirrored text" def __init__(self, font, orientation=None): @@ -638,7 +638,7 @@ def truetype(font=None, size=10, index=0, encoding="", layout_engine=None): try: return freetype(font) - except IOError: + except OSError: if not isPath(font): raise ttf_filename = os.path.basename(font) @@ -698,9 +698,9 @@ def load_path(filename): filename = filename.decode("utf-8") try: return load(os.path.join(directory, filename)) - except IOError: + except OSError: pass - raise IOError("cannot find font file") + raise OSError("cannot find font file") def load_default(): diff --git a/src/PIL/ImageMath.py b/src/PIL/ImageMath.py index d7598b932..adbb94000 100644 --- a/src/PIL/ImageMath.py +++ b/src/PIL/ImageMath.py @@ -26,7 +26,7 @@ def _isconstant(v): return isinstance(v, (int, float)) -class _Operand(object): +class _Operand: """Wraps an image operand, providing standard operators""" def __init__(self, im): diff --git a/src/PIL/ImageMode.py b/src/PIL/ImageMode.py index 596be7b9d..988288329 100644 --- a/src/PIL/ImageMode.py +++ b/src/PIL/ImageMode.py @@ -17,7 +17,7 @@ _modes = None -class ModeDescriptor(object): +class ModeDescriptor: """Wrapper for mode strings.""" def __init__(self, mode, bands, basemode, basetype): diff --git a/src/PIL/ImageMorph.py b/src/PIL/ImageMorph.py index 61199234b..d1ec09eac 100644 --- a/src/PIL/ImageMorph.py +++ b/src/PIL/ImageMorph.py @@ -5,8 +5,6 @@ # # Copyright (c) 2014 Dov Grobgeld -from __future__ import print_function - import re from . import Image, _imagingmorph @@ -27,7 +25,7 @@ MIRROR_MATRIX = [ # fmt: on -class LutBuilder(object): +class LutBuilder: """A class for building a MorphLut from a descriptive language The input patterns is a list of a strings sequences like these:: @@ -178,7 +176,7 @@ class LutBuilder(object): return self.lut -class MorphOp(object): +class MorphOp: """A class for binary morphological operators""" def __init__(self, lut=None, op_name=None, patterns=None): diff --git a/src/PIL/ImageOps.py b/src/PIL/ImageOps.py index 5052cb74d..5a06adaf3 100644 --- a/src/PIL/ImageOps.py +++ b/src/PIL/ImageOps.py @@ -55,7 +55,7 @@ def _lut(image, lut): lut = lut + lut + lut return image.point(lut) else: - raise IOError("not supported for this image mode") + raise OSError("not supported for this image mode") # diff --git a/src/PIL/ImagePalette.py b/src/PIL/ImagePalette.py index 2d4f5cb6b..e0d439c98 100644 --- a/src/PIL/ImagePalette.py +++ b/src/PIL/ImagePalette.py @@ -21,7 +21,7 @@ import array from . import GimpGradientFile, GimpPaletteFile, ImageColor, PaletteFile -class ImagePalette(object): +class ImagePalette: """ Color palette for palette mapped images @@ -216,6 +216,6 @@ def load(filename): # traceback.print_exc() pass else: - raise IOError("cannot load palette") + raise OSError("cannot load palette") return lut # data, rawmode diff --git a/src/PIL/ImageSequence.py b/src/PIL/ImageSequence.py index f9be92d48..28a54c7b0 100644 --- a/src/PIL/ImageSequence.py +++ b/src/PIL/ImageSequence.py @@ -16,7 +16,7 @@ ## -class Iterator(object): +class Iterator: """ This class implements an iterator object that can be used to loop over an image sequence. diff --git a/src/PIL/ImageShow.py b/src/PIL/ImageShow.py index 6d3ca52d8..2999d2087 100644 --- a/src/PIL/ImageShow.py +++ b/src/PIL/ImageShow.py @@ -11,9 +11,6 @@ # # See the README file for information on usage and redistribution. # - -from __future__ import print_function - import os import subprocess import sys @@ -52,7 +49,7 @@ def show(image, title=None, **options): return 0 -class Viewer(object): +class Viewer: """Base class for viewers.""" # main api @@ -123,10 +120,8 @@ elif sys.platform == "darwin": # on darwin open returns immediately resulting in the temp # file removal while app is opening command = "open -a Preview.app" - command = "(%s %s; sleep 20; rm -f %s)&" % ( - command, - quote(file), - quote(file), + command = "({} {}; sleep 20; rm -f {})&".format( + command, quote(file), quote(file) ) return command @@ -166,7 +161,7 @@ else: def get_command(self, file, **options): command = self.get_command_ex(file, **options)[0] - return "(%s %s; rm -f %s)&" % (command, quote(file), quote(file)) + return "({} {}; rm -f {})&".format(command, quote(file), quote(file)) def show_file(self, file, **options): """Display given file""" diff --git a/src/PIL/ImageStat.py b/src/PIL/ImageStat.py index 9ba16fd85..50bafc972 100644 --- a/src/PIL/ImageStat.py +++ b/src/PIL/ImageStat.py @@ -26,7 +26,7 @@ import math import operator -class Stat(object): +class Stat: def __init__(self, image_or_list, mask=None): try: if mask: diff --git a/src/PIL/ImageTk.py b/src/PIL/ImageTk.py index 769fae662..31615b838 100644 --- a/src/PIL/ImageTk.py +++ b/src/PIL/ImageTk.py @@ -62,7 +62,7 @@ def _get_image_from_kw(kw): # PhotoImage -class PhotoImage(object): +class PhotoImage: """ A Tkinter-compatible photo image. This can be used everywhere Tkinter expects an image object. If the image is an RGBA @@ -203,7 +203,7 @@ class PhotoImage(object): # BitmapImage -class BitmapImage(object): +class BitmapImage: """ A Tkinter-compatible bitmap image. This can be used everywhere Tkinter expects an image object. @@ -293,7 +293,7 @@ def _show(image, title): tkinter.Label.__init__(self, master, image=self.image, bg="black", bd=0) if not tkinter._default_root: - raise IOError("tkinter not initialized") + raise OSError("tkinter not initialized") top = tkinter.Toplevel() if title: top.title(title) diff --git a/src/PIL/ImageWin.py b/src/PIL/ImageWin.py index ed2c18ec4..c04e70eb5 100644 --- a/src/PIL/ImageWin.py +++ b/src/PIL/ImageWin.py @@ -20,7 +20,7 @@ from . import Image -class HDC(object): +class HDC: """ Wraps an HDC integer. The resulting object can be passed to the :py:meth:`~PIL.ImageWin.Dib.draw` and :py:meth:`~PIL.ImageWin.Dib.expose` @@ -34,7 +34,7 @@ class HDC(object): return self.dc -class HWND(object): +class HWND: """ Wraps an HWND integer. The resulting object can be passed to the :py:meth:`~PIL.ImageWin.Dib.draw` and :py:meth:`~PIL.ImageWin.Dib.expose` @@ -48,7 +48,7 @@ class HWND(object): return self.wnd -class Dib(object): +class Dib: """ A Windows bitmap with the given mode and size. The mode can be one of "1", "L", "P", or "RGB". @@ -186,7 +186,7 @@ class Dib(object): return self.image.tobytes() -class Window(object): +class Window: """Create a Window with the given title size.""" def __init__(self, title="PIL", width=None, height=None): diff --git a/src/PIL/IptcImagePlugin.py b/src/PIL/IptcImagePlugin.py index aedf2e48c..042e96740 100644 --- a/src/PIL/IptcImagePlugin.py +++ b/src/PIL/IptcImagePlugin.py @@ -14,9 +14,6 @@ # # See the README file for information on usage and redistribution. # - -from __future__ import print_function - import os import tempfile @@ -75,7 +72,7 @@ class IptcImageFile(ImageFile.ImageFile): # field size size = i8(s[3]) if size > 132: - raise IOError("illegal field length in IPTC/NAA file") + raise OSError("illegal field length in IPTC/NAA file") elif size == 128: size = 0 elif size > 128: @@ -126,7 +123,7 @@ class IptcImageFile(ImageFile.ImageFile): try: compression = COMPRESSION[self.getint((3, 120))] except KeyError: - raise IOError("Unknown IPTC image compression") + raise OSError("Unknown IPTC image compression") # tile if tag == (8, 10): @@ -215,7 +212,7 @@ def getiptcinfo(im): return None # no properties # create an IptcImagePlugin object without initializing it - class FakeImage(object): + class FakeImage: pass im = FakeImage() diff --git a/src/PIL/JpegImagePlugin.py b/src/PIL/JpegImagePlugin.py index 020b95219..8c88a94c4 100644 --- a/src/PIL/JpegImagePlugin.py +++ b/src/PIL/JpegImagePlugin.py @@ -31,9 +31,6 @@ # # See the README file for information on usage and redistribution. # - -from __future__ import print_function - import array import io import struct @@ -618,7 +615,7 @@ def _save(im, fp, filename): try: rawmode = RAWMODE[im.mode] except KeyError: - raise IOError("cannot write mode %s as JPEG" % im.mode) + raise OSError("cannot write mode %s as JPEG" % im.mode) info = im.encoderinfo diff --git a/src/PIL/MicImagePlugin.py b/src/PIL/MicImagePlugin.py index b48905bda..add344f24 100644 --- a/src/PIL/MicImagePlugin.py +++ b/src/PIL/MicImagePlugin.py @@ -51,7 +51,7 @@ class MicImageFile(TiffImagePlugin.TiffImageFile): try: self.ole = olefile.OleFileIO(self.fp) - except IOError: + except OSError: raise SyntaxError("not an MIC file; invalid OLE file") # find ACI subfiles with Image members (maybe not the diff --git a/src/PIL/MpegImagePlugin.py b/src/PIL/MpegImagePlugin.py index 9c662fcc2..34d0922ab 100644 --- a/src/PIL/MpegImagePlugin.py +++ b/src/PIL/MpegImagePlugin.py @@ -26,7 +26,7 @@ __version__ = "0.1" # Bitstream parser -class BitStream(object): +class BitStream: def __init__(self, fp): self.fp = fp self.bits = 0 diff --git a/src/PIL/MspImagePlugin.py b/src/PIL/MspImagePlugin.py index 7315ab66e..5aa5f7974 100644 --- a/src/PIL/MspImagePlugin.py +++ b/src/PIL/MspImagePlugin.py @@ -122,7 +122,7 @@ class MspDecoder(ImageFile.PyDecoder): "<%dH" % (self.state.ysize), self.fd.read(self.state.ysize * 2) ) except struct.error: - raise IOError("Truncated MSP file in row map") + raise OSError("Truncated MSP file in row map") for x, rowlen in enumerate(rowmap): try: @@ -131,7 +131,7 @@ class MspDecoder(ImageFile.PyDecoder): continue row = self.fd.read(rowlen) if len(row) != rowlen: - raise IOError( + raise OSError( "Truncated MSP file, expected %d bytes on row %s", (rowlen, x) ) idx = 0 @@ -148,7 +148,7 @@ class MspDecoder(ImageFile.PyDecoder): idx += runcount except struct.error: - raise IOError("Corrupted MSP file in row %d" % x) + raise OSError("Corrupted MSP file in row %d" % x) self.set_as_raw(img.getvalue(), ("1", 0, 1)) @@ -165,7 +165,7 @@ Image.register_decoder("MSP", MspDecoder) def _save(im, fp, filename): if im.mode != "1": - raise IOError("cannot write mode %s as MSP" % im.mode) + raise OSError("cannot write mode %s as MSP" % im.mode) # create MSP header header = [0] * 16 diff --git a/src/PIL/PSDraw.py b/src/PIL/PSDraw.py index baad510e1..90bcad036 100644 --- a/src/PIL/PSDraw.py +++ b/src/PIL/PSDraw.py @@ -23,7 +23,7 @@ from . import EpsImagePlugin # Simple Postscript graphics interface. -class PSDraw(object): +class PSDraw: """ Sets up printing to the given file. If **fp** is omitted, :py:attr:`sys.stdout` is assumed. @@ -71,7 +71,7 @@ class PSDraw(object): """ if font not in self.isofont: # reencode font - self._fp_write("/PSDraw-%s ISOLatin1Encoding /%s E\n" % (font, font)) + self._fp_write("/PSDraw-{} ISOLatin1Encoding /{} E\n".format(font, font)) self.isofont[font] = 1 # rough self._fp_write("/F0 %d /PSDraw-%s F\n" % (size, font)) @@ -132,12 +132,12 @@ class PSDraw(object): y = ymax dx = (xmax - x) / 2 + box[0] dy = (ymax - y) / 2 + box[1] - self._fp_write("gsave\n%f %f translate\n" % (dx, dy)) + self._fp_write("gsave\n{:f} {:f} translate\n".format(dx, dy)) if (x, y) != im.size: # EpsImagePlugin._save prints the image at (0,0,xsize,ysize) sx = x / im.size[0] sy = y / im.size[1] - self._fp_write("%f %f scale\n" % (sx, sy)) + self._fp_write("{:f} {:f} scale\n".format(sx, sy)) EpsImagePlugin._save(im, self.fp, None, 0) self._fp_write("\ngrestore\n") diff --git a/src/PIL/PaletteFile.py b/src/PIL/PaletteFile.py index ab22d5f0c..73f1b4b27 100644 --- a/src/PIL/PaletteFile.py +++ b/src/PIL/PaletteFile.py @@ -19,7 +19,7 @@ from ._binary import o8 # File handler for Teragon-style palette files. -class PaletteFile(object): +class PaletteFile: rawmode = "RGB" diff --git a/src/PIL/PalmImagePlugin.py b/src/PIL/PalmImagePlugin.py index dd068d794..0f42d72d2 100644 --- a/src/PIL/PalmImagePlugin.py +++ b/src/PIL/PalmImagePlugin.py @@ -141,7 +141,7 @@ def _save(im, fp, filename): bpp = im.info["bpp"] im = im.point(lambda x, maxval=(1 << bpp) - 1: maxval - (x & maxval)) else: - raise IOError("cannot write mode %s as Palm" % im.mode) + raise OSError("cannot write mode %s as Palm" % im.mode) # we ignore the palette here im.mode = "P" @@ -157,7 +157,7 @@ def _save(im, fp, filename): else: - raise IOError("cannot write mode %s as Palm" % im.mode) + raise OSError("cannot write mode %s as Palm" % im.mode) # # make sure image data is available diff --git a/src/PIL/PcfFontFile.py b/src/PIL/PcfFontFile.py index 074124612..63735cecb 100644 --- a/src/PIL/PcfFontFile.py +++ b/src/PIL/PcfFontFile.py @@ -184,7 +184,7 @@ class PcfFontFile(FontFile.FontFile): nbitmaps = i32(fp.read(4)) if nbitmaps != len(metrics): - raise IOError("Wrong number of bitmaps") + raise OSError("Wrong number of bitmaps") offsets = [] for i in range(nbitmaps): diff --git a/src/PIL/PcxImagePlugin.py b/src/PIL/PcxImagePlugin.py index 397af8c10..f7995d6b2 100644 --- a/src/PIL/PcxImagePlugin.py +++ b/src/PIL/PcxImagePlugin.py @@ -107,7 +107,7 @@ class PcxImageFile(ImageFile.ImageFile): rawmode = "RGB;L" else: - raise IOError("unknown PCX mode") + raise OSError("unknown PCX mode") self.mode = mode self._size = bbox[2] - bbox[0], bbox[3] - bbox[1] diff --git a/src/PIL/PdfParser.py b/src/PIL/PdfParser.py index 14079f6b1..6e054af08 100644 --- a/src/PIL/PdfParser.py +++ b/src/PIL/PdfParser.py @@ -24,47 +24,47 @@ def encode_text(s): PDFDocEncoding = { - 0x16: u"\u0017", - 0x18: u"\u02D8", - 0x19: u"\u02C7", - 0x1A: u"\u02C6", - 0x1B: u"\u02D9", - 0x1C: u"\u02DD", - 0x1D: u"\u02DB", - 0x1E: u"\u02DA", - 0x1F: u"\u02DC", - 0x80: u"\u2022", - 0x81: u"\u2020", - 0x82: u"\u2021", - 0x83: u"\u2026", - 0x84: u"\u2014", - 0x85: u"\u2013", - 0x86: u"\u0192", - 0x87: u"\u2044", - 0x88: u"\u2039", - 0x89: u"\u203A", - 0x8A: u"\u2212", - 0x8B: u"\u2030", - 0x8C: u"\u201E", - 0x8D: u"\u201C", - 0x8E: u"\u201D", - 0x8F: u"\u2018", - 0x90: u"\u2019", - 0x91: u"\u201A", - 0x92: u"\u2122", - 0x93: u"\uFB01", - 0x94: u"\uFB02", - 0x95: u"\u0141", - 0x96: u"\u0152", - 0x97: u"\u0160", - 0x98: u"\u0178", - 0x99: u"\u017D", - 0x9A: u"\u0131", - 0x9B: u"\u0142", - 0x9C: u"\u0153", - 0x9D: u"\u0161", - 0x9E: u"\u017E", - 0xA0: u"\u20AC", + 0x16: "\u0017", + 0x18: "\u02D8", + 0x19: "\u02C7", + 0x1A: "\u02C6", + 0x1B: "\u02D9", + 0x1C: "\u02DD", + 0x1D: "\u02DB", + 0x1E: "\u02DA", + 0x1F: "\u02DC", + 0x80: "\u2022", + 0x81: "\u2020", + 0x82: "\u2021", + 0x83: "\u2026", + 0x84: "\u2014", + 0x85: "\u2013", + 0x86: "\u0192", + 0x87: "\u2044", + 0x88: "\u2039", + 0x89: "\u203A", + 0x8A: "\u2212", + 0x8B: "\u2030", + 0x8C: "\u201E", + 0x8D: "\u201C", + 0x8E: "\u201D", + 0x8F: "\u2018", + 0x90: "\u2019", + 0x91: "\u201A", + 0x92: "\u2122", + 0x93: "\uFB01", + 0x94: "\uFB02", + 0x95: "\u0141", + 0x96: "\u0152", + 0x97: "\u0160", + 0x98: "\u0178", + 0x99: "\u017D", + 0x9A: "\u0131", + 0x9B: "\u0142", + 0x9C: "\u0153", + 0x9D: "\u0161", + 0x9E: "\u017E", + 0xA0: "\u20AC", } @@ -235,7 +235,7 @@ class PdfName: def from_pdf_stream(cls, data): return cls(PdfParser.interpret_name(data)) - allowed_chars = set(range(33, 127)) - set(ord(c) for c in "#%/()<>[]{}") + allowed_chars = set(range(33, 127)) - {ord(c) for c in "#%/()<>[]{}"} def __bytes__(self): result = bytearray(b"/") @@ -441,7 +441,7 @@ class PdfParser: self.f.write(b"%PDF-1.4\n") def write_comment(self, s): - self.f.write(("%% %s\n" % (s,)).encode("utf-8")) + self.f.write(("% {}\n".format(s)).encode("utf-8")) def write_catalog(self): self.del_root() diff --git a/src/PIL/PngImagePlugin.py b/src/PIL/PngImagePlugin.py index c3a5dd627..49e0c358f 100644 --- a/src/PIL/PngImagePlugin.py +++ b/src/PIL/PngImagePlugin.py @@ -101,7 +101,7 @@ def _crc32(data, seed=0): # Support classes. Suitable for PNG and related formats like MNG etc. -class ChunkStream(object): +class ChunkStream: def __init__(self, fp): self.fp = fp @@ -179,7 +179,7 @@ class ChunkStream(object): try: cid, pos, length = self.read() except struct.error: - raise IOError("truncated PNG file") + raise OSError("truncated PNG file") if cid == endchunk: break @@ -211,7 +211,7 @@ class iTXt(str): return self -class PngInfo(object): +class PngInfo: """ PNG chunk container (for use with save(pnginfo=)) @@ -742,7 +742,7 @@ def putchunk(fp, cid, *data): fp.write(o32(crc)) -class _idat(object): +class _idat: # wrap output from the encoder in IDAT chunks def __init__(self, fp, chunk): @@ -795,7 +795,7 @@ def _save(im, fp, filename, chunk=putchunk): try: rawmode, mode = _OUTMODES[mode] except KeyError: - raise IOError("cannot write mode %s as PNG" % mode) + raise OSError("cannot write mode %s as PNG" % mode) # # write minimal PNG file @@ -870,7 +870,7 @@ def _save(im, fp, filename, chunk=putchunk): if "transparency" in im.encoderinfo: # don't bother with transparency if it's an RGBA # and it's in the info dict. It's probably just stale. - raise IOError("cannot use transparency for this mode") + raise OSError("cannot use transparency for this mode") else: if im.mode == "P" and im.im.getpalettemode() == "RGBA": alpha = im.im.getpalette("RGBA", "A") @@ -918,7 +918,7 @@ def _save(im, fp, filename, chunk=putchunk): def getchunks(im, **params): """Return a list of PNG chunks representing this image.""" - class collector(object): + class collector: data = [] def write(self, data): diff --git a/src/PIL/PpmImagePlugin.py b/src/PIL/PpmImagePlugin.py index c3e9eed6d..1ba46255b 100644 --- a/src/PIL/PpmImagePlugin.py +++ b/src/PIL/PpmImagePlugin.py @@ -139,7 +139,7 @@ def _save(im, fp, filename): elif im.mode == "RGBA": rawmode, head = "RGB", b"P6" else: - raise IOError("cannot write mode %s as PPM" % im.mode) + raise OSError("cannot write mode %s as PPM" % im.mode) fp.write(head + ("\n%d %d\n" % im.size).encode("ascii")) if head == b"P6": fp.write(b"255\n") diff --git a/src/PIL/PsdImagePlugin.py b/src/PIL/PsdImagePlugin.py index f72ad5f44..f2171bf6e 100644 --- a/src/PIL/PsdImagePlugin.py +++ b/src/PIL/PsdImagePlugin.py @@ -75,7 +75,7 @@ class PsdImageFile(ImageFile.ImageFile): mode, channels = MODES[(psd_mode, psd_bits)] if channels > psd_channels: - raise IOError("not enough channels") + raise OSError("not enough channels") self.mode = mode self._size = i32(s[18:]), i32(s[14:]) diff --git a/src/PIL/PyAccess.py b/src/PIL/PyAccess.py index 2ab06f93f..359a94919 100644 --- a/src/PIL/PyAccess.py +++ b/src/PIL/PyAccess.py @@ -40,7 +40,7 @@ ffi = FFI() ffi.cdef(defs) -class PyAccess(object): +class PyAccess: def __init__(self, img, readonly=False): vals = dict(img.im.unsafe_ptrs) self.readonly = readonly diff --git a/src/PIL/SgiImagePlugin.py b/src/PIL/SgiImagePlugin.py index 7de62c117..aa5e54386 100644 --- a/src/PIL/SgiImagePlugin.py +++ b/src/PIL/SgiImagePlugin.py @@ -163,7 +163,9 @@ def _save(im, fp, filename): # assert we've got the right number of bands. if len(im.getbands()) != z: raise ValueError( - "incorrect number of bands in SGI write: %s vs %s" % (z, len(im.getbands())) + "incorrect number of bands in SGI write: {} vs {}".format( + z, len(im.getbands()) + ) ) # Minimum Byte value diff --git a/src/PIL/SpiderImagePlugin.py b/src/PIL/SpiderImagePlugin.py index f1cae4d9f..68559729c 100644 --- a/src/PIL/SpiderImagePlugin.py +++ b/src/PIL/SpiderImagePlugin.py @@ -32,9 +32,6 @@ # Details about the Spider image format: # https://spider.wadsworth.org/spider_doc/spider/docs/image_doc.html # - -from __future__ import print_function - import os import struct import sys @@ -273,7 +270,7 @@ def _save(im, fp, filename): hdr = makeSpiderHeader(im) if len(hdr) < 256: - raise IOError("Error creating Spider header") + raise OSError("Error creating Spider header") # write the SPIDER header fp.writelines(hdr) diff --git a/src/PIL/TarIO.py b/src/PIL/TarIO.py index 457d75d03..06769846e 100644 --- a/src/PIL/TarIO.py +++ b/src/PIL/TarIO.py @@ -37,12 +37,12 @@ class TarIO(ContainerIO.ContainerIO): s = self.fh.read(512) if len(s) != 512: - raise IOError("unexpected end of tar file") + raise OSError("unexpected end of tar file") name = s[:100].decode("utf-8") i = name.find("\0") if i == 0: - raise IOError("cannot find subfile") + raise OSError("cannot find subfile") if i > 0: name = name[:i] diff --git a/src/PIL/TgaImagePlugin.py b/src/PIL/TgaImagePlugin.py index b1b351396..72cd2d61e 100644 --- a/src/PIL/TgaImagePlugin.py +++ b/src/PIL/TgaImagePlugin.py @@ -173,7 +173,7 @@ def _save(im, fp, filename): try: rawmode, bits, colormaptype, imagetype = SAVE[im.mode] except KeyError: - raise IOError("cannot write mode %s as TGA" % im.mode) + raise OSError("cannot write mode %s as TGA" % im.mode) if "rle" in im.encoderinfo: rle = im.encoderinfo["rle"] diff --git a/src/PIL/TiffImagePlugin.py b/src/PIL/TiffImagePlugin.py index 6d56df217..0fffbfb14 100644 --- a/src/PIL/TiffImagePlugin.py +++ b/src/PIL/TiffImagePlugin.py @@ -38,9 +38,6 @@ # # See the README file for information on usage and redistribution. # - -from __future__ import division, print_function - import distutils.version import io import itertools @@ -712,7 +709,7 @@ class ImageFileDirectory_v2(MutableMapping): def _ensure_read(self, fp, size): ret = fp.read(size) if len(ret) != size: - raise IOError( + raise OSError( "Corrupt EXIF data. " + "Expecting to read %d bytes but only got %d. " % (size, len(ret)) ) @@ -746,7 +743,7 @@ class ImageFileDirectory_v2(MutableMapping): offset, = self._unpack("L", data) if DEBUG: print( - "Tag Location: %s - Data Location: %s" % (here, offset), + "Tag Location: {} - Data Location: {}".format(here, offset), end=" ", ) fp.seek(offset) @@ -776,7 +773,7 @@ class ImageFileDirectory_v2(MutableMapping): print("- value:", self[tag]) self.next, = self._unpack("L", self._ensure_read(fp, 4)) - except IOError as msg: + except OSError as msg: warnings.warn(str(msg)) return @@ -795,7 +792,7 @@ class ImageFileDirectory_v2(MutableMapping): stripoffsets = len(entries) typ = self.tagtype.get(tag) if DEBUG: - print("Tag %s, Type: %s, Value: %s" % (tag, typ, value)) + print("Tag {}, Type: {}, Value: {}".format(tag, typ, value)) values = value if isinstance(value, tuple) else (value,) data = self._write_dispatch[typ](self, *values) if DEBUG: @@ -1060,7 +1057,7 @@ class TiffImageFile(ImageFile.ImageFile): def load(self): if self.use_load_libtiff: return self._load_libtiff() - return super(TiffImageFile, self).load() + return super().load() def load_end(self): if self._tile_orientation: @@ -1089,14 +1086,14 @@ class TiffImageFile(ImageFile.ImageFile): pixel = Image.Image.load(self) if self.tile is None: - raise IOError("cannot load this image") + raise OSError("cannot load this image") if not self.tile: return pixel self.load_prepare() if not len(self.tile) == 1: - raise IOError("Not exactly one tile") + raise OSError("Not exactly one tile") # (self._compression, (extents tuple), # 0, (rawmode, self._compression, fp)) @@ -1114,7 +1111,7 @@ class TiffImageFile(ImageFile.ImageFile): # in _seek if hasattr(self.fp, "flush"): self.fp.flush() - except IOError: + except OSError: # io.BytesIO have a fileno, but returns an IOError if # it doesn't use a file descriptor. fp = False @@ -1128,7 +1125,7 @@ class TiffImageFile(ImageFile.ImageFile): try: decoder.setimage(self.im, extents) except ValueError: - raise IOError("Couldn't set the image") + raise OSError("Couldn't set the image") close_self_fp = self._exclusive_fp and not self._is_animated if hasattr(self.fp, "getvalue"): @@ -1171,7 +1168,7 @@ class TiffImageFile(ImageFile.ImageFile): self.fp = None # might be shared if err < 0: - raise IOError(err) + raise OSError(err) return Image.Image.load(self) @@ -1179,7 +1176,7 @@ class TiffImageFile(ImageFile.ImageFile): """Setup this image object based on current tags""" if 0xBC01 in self.tag_v2: - raise IOError("Windows Media Photo files not yet supported") + raise OSError("Windows Media Photo files not yet supported") # extract relevant tags self._compression = COMPRESSION_INFO[self.tag_v2.get(COMPRESSION, 1)] @@ -1421,7 +1418,7 @@ def _save(im, fp, filename): try: rawmode, prefix, photo, format, bits, extra = SAVE_INFO[im.mode] except KeyError: - raise IOError("cannot write mode %s as TIFF" % im.mode) + raise OSError("cannot write mode %s as TIFF" % im.mode) ifd = ImageFileDirectory_v2(prefix=prefix) @@ -1616,7 +1613,7 @@ def _save(im, fp, filename): if s: break if s < 0: - raise IOError("encoder error %d when writing image file" % s) + raise OSError("encoder error %d when writing image file" % s) else: offset = ifd.save(fp) @@ -1664,9 +1661,9 @@ class AppendingTiffWriter: self.name = fn self.close_fp = True try: - self.f = io.open(fn, "w+b" if new else "r+b") - except IOError: - self.f = io.open(fn, "w+b") + self.f = open(fn, "w+b" if new else "r+b") + except OSError: + self.f = open(fn, "w+b") self.beginning = self.f.tell() self.setup() diff --git a/src/PIL/TiffTags.py b/src/PIL/TiffTags.py index c047f42b6..6cc9ff7f3 100644 --- a/src/PIL/TiffTags.py +++ b/src/PIL/TiffTags.py @@ -24,7 +24,7 @@ class TagInfo(namedtuple("_TagInfo", "value name type length enum")): __slots__ = [] def __new__(cls, value=None, name="unknown", type=None, length=None, enum=None): - return super(TagInfo, cls).__new__(cls, value, name, type, length, enum or {}) + return super().__new__(cls, value, name, type, length, enum or {}) def cvt_enum(self, value): # Using get will call hash(value), which can be expensive diff --git a/src/PIL/WalImageFile.py b/src/PIL/WalImageFile.py index daa806a8b..d5a5c8e67 100644 --- a/src/PIL/WalImageFile.py +++ b/src/PIL/WalImageFile.py @@ -1,4 +1,3 @@ -# encoding: utf-8 # # The Python Imaging Library. # $Id$ diff --git a/src/PIL/WebPImagePlugin.py b/src/PIL/WebPImagePlugin.py index 18eda6d18..eda685508 100644 --- a/src/PIL/WebPImagePlugin.py +++ b/src/PIL/WebPImagePlugin.py @@ -105,7 +105,7 @@ class WebPImageFile(ImageFile.ImageFile): def seek(self, frame): if not _webp.HAVE_WEBPANIM: - return super(WebPImageFile, self).seek(frame) + return super().seek(frame) # Perform some simple checks first if frame >= self._n_frames: @@ -168,11 +168,11 @@ class WebPImageFile(ImageFile.ImageFile): self.fp = BytesIO(data) self.tile = [("raw", (0, 0) + self.size, 0, self.rawmode)] - return super(WebPImageFile, self).load() + return super().load() def tell(self): if not _webp.HAVE_WEBPANIM: - return super(WebPImageFile, self).tell() + return super().tell() return self.__logical_frame @@ -233,7 +233,7 @@ def _save_all(im, fp, filename): or len(background) != 4 or not all(v >= 0 and v < 256 for v in background) ): - raise IOError( + raise OSError( "Background color is not an RGBA tuple clamped to (0-255): %s" % str(background) ) @@ -312,7 +312,7 @@ def _save_all(im, fp, filename): # Get the final output from the encoder data = enc.assemble(icc_profile, exif, xmp) if data is None: - raise IOError("cannot write file as WebP (encoder returned None)") + raise OSError("cannot write file as WebP (encoder returned None)") fp.write(data) @@ -346,7 +346,7 @@ def _save(im, fp, filename): xmp, ) if data is None: - raise IOError("cannot write file as WebP (encoder returned None)") + raise OSError("cannot write file as WebP (encoder returned None)") fp.write(data) diff --git a/src/PIL/WmfImagePlugin.py b/src/PIL/WmfImagePlugin.py index be07a747b..4818c00b0 100644 --- a/src/PIL/WmfImagePlugin.py +++ b/src/PIL/WmfImagePlugin.py @@ -19,8 +19,6 @@ # http://wvware.sourceforge.net/caolan/index.html # http://wvware.sourceforge.net/caolan/ora-wmf.html -from __future__ import print_function - from . import Image, ImageFile from ._binary import i16le as word, i32le as dword, si16le as short, si32le as _long @@ -44,7 +42,7 @@ def register_handler(handler): if hasattr(Image.core, "drawwmf"): # install default handler (windows only) - class WmfHandler(object): + class WmfHandler: def open(self, im): im.mode = "RGB" self.bbox = im.info["wmf_bbox"] @@ -154,7 +152,7 @@ class WmfStubImageFile(ImageFile.StubImageFile): def _save(im, fp, filename): if _handler is None or not hasattr(_handler, "save"): - raise IOError("WMF save handler not installed") + raise OSError("WMF save handler not installed") _handler.save(im, fp, filename) diff --git a/src/PIL/XbmImagePlugin.py b/src/PIL/XbmImagePlugin.py index bc825c3f3..22b8fe34e 100644 --- a/src/PIL/XbmImagePlugin.py +++ b/src/PIL/XbmImagePlugin.py @@ -73,7 +73,7 @@ class XbmImageFile(ImageFile.ImageFile): def _save(im, fp, filename): if im.mode != "1": - raise IOError("cannot write mode %s as XBM" % im.mode) + raise OSError("cannot write mode %s as XBM" % im.mode) fp.write(("#define im_width %d\n" % im.size[0]).encode("ascii")) fp.write(("#define im_height %d\n" % im.size[1]).encode("ascii")) diff --git a/src/PIL/_util.py b/src/PIL/_util.py index 127e94d82..95bb457af 100644 --- a/src/PIL/_util.py +++ b/src/PIL/_util.py @@ -26,7 +26,7 @@ def isDirectory(f): return isPath(f) and os.path.isdir(f) -class deferred_error(object): +class deferred_error: def __init__(self, ex): self.ex = ex diff --git a/src/PIL/features.py b/src/PIL/features.py index 9fd522368..a5ab108de 100644 --- a/src/PIL/features.py +++ b/src/PIL/features.py @@ -1,5 +1,3 @@ -from __future__ import print_function, unicode_literals - import collections import os import sys diff --git a/winbuild/build.py b/winbuild/build.py index f4561d933..caa63c437 100755 --- a/winbuild/build.py +++ b/winbuild/build.py @@ -53,10 +53,10 @@ def run_script(params): print(stderr.decode()) print("-- stdout --") print(trace.decode()) - print("Done with %s: %s" % (version, status)) + print("Done with {}: {}".format(version, status)) return (version, status, trace, stderr) except Exception as msg: - print("Error with %s: %s" % (version, str(msg))) + print("Error with {}: {}".format(version, str(msg))) return (version, -1, "", str(msg)) @@ -98,7 +98,7 @@ def build_one(py_ver, compiler, bit): if "PYTHON" in os.environ: args["python_path"] = "%PYTHON%" else: - args["python_path"] = "%s%s\\Scripts" % (VIRT_BASE, py_ver) + args["python_path"] = "{}{}\\Scripts".format(VIRT_BASE, py_ver) args["executable"] = "python.exe" if "EXECUTABLE" in os.environ: @@ -157,7 +157,7 @@ def main(op): scripts.append( ( - "%s%s" % (py_version, X64_EXT), + "{}{}".format(py_version, X64_EXT), "\n".join( [ header(op), @@ -171,7 +171,7 @@ def main(op): results = map(run_script, scripts) for (version, status, trace, err) in results: - print("Compiled %s: %s" % (version, status and "ERR" or "OK")) + print("Compiled {}: {}".format(version, status and "ERR" or "OK")) def run_one(op): diff --git a/winbuild/build_dep.py b/winbuild/build_dep.py index 487329db8..b5ece5f06 100644 --- a/winbuild/build_dep.py +++ b/winbuild/build_dep.py @@ -103,7 +103,7 @@ set CMAKE="cmake.exe" set INCLIB=%~dp0\depends set BUILD=%~dp0\build """ + "\n".join( - r"set %s=%%BUILD%%\%s" % (k.upper(), v["dir"]) + r"set {}=%BUILD%\{}".format(k.upper(), v["dir"]) for (k, v) in libs.items() if v["dir"] ) diff --git a/winbuild/config.py b/winbuild/config.py index 46c9f9b2b..e1f181210 100644 --- a/winbuild/config.py +++ b/winbuild/config.py @@ -136,7 +136,7 @@ def pyversion_from_env(): break if "64" in py: - py_version = "%s%s" % (py_version, X64_EXT) + py_version = "{}{}".format(py_version, X64_EXT) return py_version diff --git a/winbuild/test.py b/winbuild/test.py index 559ecdec1..a05a20b18 100755 --- a/winbuild/test.py +++ b/winbuild/test.py @@ -13,7 +13,7 @@ def test_one(params): try: print("Running: %s, %s" % params) command = [ - r"%s\%s%s\Scripts\python.exe" % (VIRT_BASE, python, architecture), + r"{}\{}{}\Scripts\python.exe".format(VIRT_BASE, python, architecture), "test-installed.py", "--processes=-0", "--process-timeout=30", @@ -22,10 +22,10 @@ def test_one(params): proc = subprocess.Popen(command, stdin=subprocess.PIPE, stdout=subprocess.PIPE) (trace, stderr) = proc.communicate() status = proc.returncode - print("Done with %s, %s -- %s" % (python, architecture, status)) + print("Done with {}, {} -- {}".format(python, architecture, status)) return (python, architecture, status, trace) except Exception as msg: - print("Error with %s, %s: %s" % (python, architecture, msg)) + print("Error with {}, {}: {}".format(python, architecture, msg)) return (python, architecture, -1, str(msg)) @@ -39,7 +39,7 @@ if __name__ == "__main__": results = map(test_one, matrix) for (python, architecture, status, trace) in results: - print("%s%s: %s" % (python, architecture, status and "ERR" or "PASS")) + print("{}{}: {}".format(python, architecture, status and "ERR" or "PASS")) res = all(status for (python, architecture, status, trace) in results) sys.exit(res) From 74d2767c575779cfd4c6bb9685d7adde953c9a5a Mon Sep 17 00:00:00 2001 From: Hugo Date: Wed, 2 Oct 2019 14:16:04 +0300 Subject: [PATCH 03/59] Remove duplicate line --- Tests/test_file_libtiff.py | 1 - 1 file changed, 1 deletion(-) diff --git a/Tests/test_file_libtiff.py b/Tests/test_file_libtiff.py index 881d9096c..f92fc3b9b 100644 --- a/Tests/test_file_libtiff.py +++ b/Tests/test_file_libtiff.py @@ -260,7 +260,6 @@ class TestFileLibTiff(LibTiffTestCase): tc(4.25, TiffTags.FLOAT, True), tc(4.25, TiffTags.DOUBLE, True), tc("custom tag value", TiffTags.ASCII, True), - tc("custom tag value", TiffTags.ASCII, True), tc(b"custom tag value", TiffTags.BYTE, True), tc((4, 5, 6), TiffTags.SHORT, True), tc((123456789, 9, 34, 234, 219387, 92432323), TiffTags.LONG, True), From 810b61e78f873cc973407c4010fc0579e712b8a2 Mon Sep 17 00:00:00 2001 From: Hugo Date: Wed, 2 Oct 2019 18:11:35 +0300 Subject: [PATCH 04/59] Reinstate and simplify parallel auto-detection --- setup.py | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/setup.py b/setup.py index c3e0dfc9f..c72e02966 100755 --- a/setup.py +++ b/setup.py @@ -326,6 +326,15 @@ class pil_build_ext(build_ext): if self.debug: global DEBUG DEBUG = True + if not self.parallel: + # If --parallel (or -j) wasn't specified, we want to reproduce the same + # behavior as before, that is, auto-detect the number of jobs. + try: + self.parallel = int( + os.environ.get("MAX_CONCURRENCY", min(4, os.cpu_count())) + ) + except TypeError: + self.parallel = None for x in self.feature: if getattr(self, "disable_%s" % x): setattr(self.feature, x, False) From 7e3156eb173c00d8cac0961d44e0d9c1f1dbcd8f Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Mon, 7 Oct 2019 20:09:16 +1100 Subject: [PATCH 05/59] Updated IFDRational operators --- src/PIL/TiffImagePlugin.py | 12 +++++------- 1 file changed, 5 insertions(+), 7 deletions(-) diff --git a/src/PIL/TiffImagePlugin.py b/src/PIL/TiffImagePlugin.py index 0fffbfb14..59d323fc2 100644 --- a/src/PIL/TiffImagePlugin.py +++ b/src/PIL/TiffImagePlugin.py @@ -358,10 +358,10 @@ class IFDRational(Rational): return delegate - """ a = ['add','radd', 'sub', 'rsub','div', 'rdiv', 'mul', 'rmul', - 'truediv', 'rtruediv', 'floordiv', - 'rfloordiv','mod','rmod', 'pow','rpow', 'pos', 'neg', - 'abs', 'trunc', 'lt', 'gt', 'le', 'ge', 'nonzero', + """ a = ['add','radd', 'sub', 'rsub', 'mul', 'rmul', + 'truediv', 'rtruediv', 'floordiv', 'rfloordiv', + 'mod','rmod', 'pow','rpow', 'pos', 'neg', + 'abs', 'trunc', 'lt', 'gt', 'le', 'ge', 'bool', 'ceil', 'floor', 'round'] print("\n".join("__%s__ = _delegate('__%s__')" % (s,s) for s in a)) """ @@ -370,8 +370,6 @@ class IFDRational(Rational): __radd__ = _delegate("__radd__") __sub__ = _delegate("__sub__") __rsub__ = _delegate("__rsub__") - __div__ = _delegate("__div__") - __rdiv__ = _delegate("__rdiv__") __mul__ = _delegate("__mul__") __rmul__ = _delegate("__rmul__") __truediv__ = _delegate("__truediv__") @@ -390,7 +388,7 @@ class IFDRational(Rational): __gt__ = _delegate("__gt__") __le__ = _delegate("__le__") __ge__ = _delegate("__ge__") - __nonzero__ = _delegate("__nonzero__") + __bool__ = _delegate("__bool__") __ceil__ = _delegate("__ceil__") __floor__ = _delegate("__floor__") __round__ = _delegate("__round__") From 865b17d5cf8682cc77889ba26db36f486a6cf6f3 Mon Sep 17 00:00:00 2001 From: Hugo Date: Mon, 7 Oct 2019 15:34:12 +0300 Subject: [PATCH 06/59] Remove Python 2-compatibility code --- Tests/test_file_png.py | 4 +--- Tests/test_imagefont.py | 5 +---- src/PIL/ImageQt.py | 6 +----- src/PIL/PdfParser.py | 12 ++---------- src/PIL/TiffImagePlugin.py | 2 +- 5 files changed, 6 insertions(+), 23 deletions(-) diff --git a/Tests/test_file_png.py b/Tests/test_file_png.py index 3f5f003ac..26c40ab44 100644 --- a/Tests/test_file_png.py +++ b/Tests/test_file_png.py @@ -448,9 +448,7 @@ class TestFilePng(PillowTestCase): self.assertIsInstance(im.info["Text"], str) def test_unicode_text(self): - # Check preservation of non-ASCII characters on Python 3 - # This cannot really be meaningfully tested on Python 2, - # since it didn't preserve charsets to begin with. + # Check preservation of non-ASCII characters def rt_text(value): im = Image.new("RGB", (32, 32)) diff --git a/Tests/test_imagefont.py b/Tests/test_imagefont.py index 6daac701b..d2e24836e 100644 --- a/Tests/test_imagefont.py +++ b/Tests/test_imagefont.py @@ -463,10 +463,7 @@ class TestImageFont(PillowTestCase): with self.assertRaises(UnicodeEncodeError): font.getsize("’") - @unittest.skipIf( - sys.version.startswith("2") or hasattr(sys, "pypy_translation_info"), - "requires CPython 3.3+", - ) + @unittest.skipIf(hasattr(sys, "pypy_translation_info"), "requires CPython") def test_unicode_extended(self): # issue #3777 text = "A\u278A\U0001F12B" diff --git a/src/PIL/ImageQt.py b/src/PIL/ImageQt.py index ce34b0a04..f19ad3df9 100644 --- a/src/PIL/ImageQt.py +++ b/src/PIL/ImageQt.py @@ -65,11 +65,7 @@ def fromqimage(im): im.save(buffer, "ppm") b = BytesIO() - try: - b.write(buffer.data()) - except TypeError: - # workaround for Python 2 - b.write(str(buffer.data())) + b.write(buffer.data()) buffer.close() b.seek(0) diff --git a/src/PIL/PdfParser.py b/src/PIL/PdfParser.py index 6e054af08..3267ee491 100644 --- a/src/PIL/PdfParser.py +++ b/src/PIL/PdfParser.py @@ -7,11 +7,6 @@ import re import time import zlib -try: - from UserDict import UserDict # Python 2.x -except ImportError: - UserDict = collections.UserDict # Python 3.x - def make_bytes(s): return s.encode("us-ascii") @@ -256,13 +251,10 @@ class PdfArray(list): __str__ = __bytes__ -class PdfDict(UserDict): +class PdfDict(collections.UserDict): def __setattr__(self, key, value): if key == "data": - if hasattr(UserDict, "__setattr__"): - UserDict.__setattr__(self, key, value) - else: - self.__dict__[key] = value + collections.UserDict.__setattr__(self, key, value) else: self[key.encode("us-ascii")] = value diff --git a/src/PIL/TiffImagePlugin.py b/src/PIL/TiffImagePlugin.py index 59d323fc2..1094a6072 100644 --- a/src/PIL/TiffImagePlugin.py +++ b/src/PIL/TiffImagePlugin.py @@ -1027,7 +1027,7 @@ class TiffImageFile(ImageFile.ImageFile): "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 + # reset buffered io handle in case fp # was passed to libtiff, invalidating the buffer self.fp.tell() self.fp.seek(self.__next) From 4382413bb4bd7986e24cc838ff41181416a2d28f Mon Sep 17 00:00:00 2001 From: Hugo Date: Mon, 7 Oct 2019 15:40:00 +0300 Subject: [PATCH 07/59] Remove redundant bytearray --- Tests/test_file_gif.py | 14 ++++++-------- Tests/test_lib_pack.py | 4 ++-- src/PIL/TiffImagePlugin.py | 2 +- 3 files changed, 9 insertions(+), 11 deletions(-) diff --git a/Tests/test_file_gif.py b/Tests/test_file_gif.py index 8be614412..68301a84f 100644 --- a/Tests/test_file_gif.py +++ b/Tests/test_file_gif.py @@ -71,9 +71,7 @@ class TestFileGif(PillowTestCase): def check(colors, size, expected_palette_length): # make an image with empty colors in the start of the palette range im = Image.frombytes( - "P", - (colors, colors), - bytes(bytearray(range(256 - colors, 256)) * colors), + "P", (colors, colors), bytes(range(256 - colors, 256)) * colors ) im = im.resize((size, size)) outfile = BytesIO() @@ -103,7 +101,7 @@ class TestFileGif(PillowTestCase): check(256, 511, 256) def test_optimize_full_l(self): - im = Image.frombytes("L", (16, 16), bytes(bytearray(range(256)))) + im = Image.frombytes("L", (16, 16), bytes(range(256))) test_file = BytesIO() im.save(test_file, "GIF", optimize=True) self.assertEqual(im.mode, "L") @@ -632,7 +630,7 @@ class TestFileGif(PillowTestCase): # that's > 128 items where the transparent color is actually # the top palette entry to trigger the bug. - data = bytes(bytearray(range(1, 254))) + data = bytes(range(1, 254)) palette = ImagePalette.ImagePalette("RGB", list(range(256)) * 3) im = Image.new("L", (253, 1)) @@ -680,7 +678,7 @@ class TestFileGif(PillowTestCase): im = hopper("P") im_l = Image.frombytes("L", im.size, im.tobytes()) - palette = bytes(bytearray(im.getpalette())) + palette = bytes(im.getpalette()) out = self.tempfile("temp.gif") im_l.save(out, palette=palette) @@ -695,7 +693,7 @@ class TestFileGif(PillowTestCase): # Forcing a non-straight grayscale palette. im = hopper("P") - palette = bytes(bytearray([255 - i // 3 for i in range(768)])) + palette = bytes([255 - i // 3 for i in range(768)]) out = self.tempfile("temp.gif") im.save(out, palette=palette) @@ -736,7 +734,7 @@ class TestFileGif(PillowTestCase): im.putpalette(ImagePalette.ImagePalette("RGB")) im.info = {"background": 0} - passed_palette = bytes(bytearray([255 - i // 3 for i in range(768)])) + passed_palette = bytes([255 - i // 3 for i in range(768)]) GifImagePlugin._FORCE_OPTIMIZE = True try: diff --git a/Tests/test_lib_pack.py b/Tests/test_lib_pack.py index 6178184bc..45958d2eb 100644 --- a/Tests/test_lib_pack.py +++ b/Tests/test_lib_pack.py @@ -18,7 +18,7 @@ class TestLibPack(PillowTestCase): if isinstance(data, int): data_len = data * len(pixels) - data = bytes(bytearray(range(1, data_len + 1))) + data = bytes(range(1, data_len + 1)) self.assertEqual(data, im.tobytes("raw", rawmode)) @@ -226,7 +226,7 @@ class TestLibUnpack(PillowTestCase): """ if isinstance(data, int): data_len = data * len(pixels) - data = bytes(bytearray(range(1, data_len + 1))) + data = bytes(range(1, data_len + 1)) im = Image.frombytes(mode, (len(pixels), 1), data, "raw", rawmode, 0, 1) diff --git a/src/PIL/TiffImagePlugin.py b/src/PIL/TiffImagePlugin.py index 1094a6072..a6fa412ca 100644 --- a/src/PIL/TiffImagePlugin.py +++ b/src/PIL/TiffImagePlugin.py @@ -1742,7 +1742,7 @@ class AppendingTiffWriter: # pad to 16 byte boundary padBytes = 16 - pos % 16 if 0 < padBytes < 16: - self.f.write(bytes(bytearray(padBytes))) + self.f.write(bytes(padBytes)) self.offsetOfNewPage = self.f.tell() def setEndian(self, endian): From 84e53e375799639153756a586ddceab3a94539ad Mon Sep 17 00:00:00 2001 From: Hugo Date: Tue, 8 Oct 2019 11:24:36 +0300 Subject: [PATCH 08/59] Simplify using subprocess.DEVNULL Co-Authored-By: Jon Dufresne --- Tests/helper.py | 9 ++++----- setup.py | 6 +++--- src/PIL/EpsImagePlugin.py | 3 +-- src/PIL/GifImagePlugin.py | 18 +++++++++--------- src/PIL/IcnsImagePlugin.py | 7 +++---- 5 files changed, 20 insertions(+), 23 deletions(-) diff --git a/Tests/helper.py b/Tests/helper.py index 302e1f6ac..2c9ec331e 100644 --- a/Tests/helper.py +++ b/Tests/helper.py @@ -321,11 +321,10 @@ def command_succeeds(cmd): Runs the command, which must be a list of strings. Returns True if the command succeeds, or False if an OSError was raised by subprocess.Popen. """ - with open(os.devnull, "wb") as f: - try: - subprocess.call(cmd, stdout=f, stderr=subprocess.STDOUT) - except OSError: - return False + try: + subprocess.call(cmd, stdout=subprocess.DEVNULL, stderr=subprocess.STDOUT) + except OSError: + return False return True diff --git a/setup.py b/setup.py index c72e02966..ff68b1e8b 100755 --- a/setup.py +++ b/setup.py @@ -164,10 +164,10 @@ def _find_library_dirs_ldconfig(): expr = r".* => (.*)" env = {} - null = open(os.devnull, "wb") try: - with null: - p = subprocess.Popen(args, stderr=null, stdout=subprocess.PIPE, env=env) + p = subprocess.Popen( + args, stderr=subprocess.DEVNULL, stdout=subprocess.PIPE, env=env + ) except OSError: # E.g. command not found return [] [data, _] = p.communicate() diff --git a/src/PIL/EpsImagePlugin.py b/src/PIL/EpsImagePlugin.py index 6d85da988..e38de58cf 100644 --- a/src/PIL/EpsImagePlugin.py +++ b/src/PIL/EpsImagePlugin.py @@ -57,8 +57,7 @@ def has_ghostscript(): return True if not sys.platform.startswith("win"): try: - with open(os.devnull, "wb") as devnull: - subprocess.check_call(["gs", "--version"], stdout=devnull) + subprocess.check_call(["gs", "--version"], stdout=subprocess.DEVNULL) return True except OSError: # No Ghostscript diff --git a/src/PIL/GifImagePlugin.py b/src/PIL/GifImagePlugin.py index 2024e025d..3079cc950 100644 --- a/src/PIL/GifImagePlugin.py +++ b/src/PIL/GifImagePlugin.py @@ -623,20 +623,20 @@ def _save_netpbm(im, fp, filename): with open(filename, "wb") as f: if im.mode != "RGB": - with open(os.devnull, "wb") as devnull: - subprocess.check_call(["ppmtogif", tempfile], stdout=f, stderr=devnull) + subprocess.check_call( + ["ppmtogif", tempfile], stdout=f, stderr=subprocess.DEVNULL + ) else: # Pipe ppmquant output into ppmtogif # "ppmquant 256 %s | ppmtogif > %s" % (tempfile, filename) quant_cmd = ["ppmquant", "256", tempfile] togif_cmd = ["ppmtogif"] - with open(os.devnull, "wb") as devnull: - quant_proc = subprocess.Popen( - quant_cmd, stdout=subprocess.PIPE, stderr=devnull - ) - togif_proc = subprocess.Popen( - togif_cmd, stdin=quant_proc.stdout, stdout=f, stderr=devnull - ) + quant_proc = subprocess.Popen( + quant_cmd, stdout=subprocess.PIPE, stderr=subprocess.DEVNULL + ) + togif_proc = subprocess.Popen( + togif_cmd, stdin=quant_proc.stdout, stdout=f, stderr=subprocess.DEVNULL + ) # Allow ppmquant to receive SIGPIPE if ppmtogif exits quant_proc.stdout.close() diff --git a/src/PIL/IcnsImagePlugin.py b/src/PIL/IcnsImagePlugin.py index efa7f3c2b..b80ac2179 100644 --- a/src/PIL/IcnsImagePlugin.py +++ b/src/PIL/IcnsImagePlugin.py @@ -336,10 +336,9 @@ def _save(im, fp, filename): # iconutil -c icns -o {} {} convert_cmd = ["iconutil", "-c", "icns", "-o", filename, iconset] - with open(os.devnull, "wb") as devnull: - convert_proc = subprocess.Popen( - convert_cmd, stdout=subprocess.PIPE, stderr=devnull - ) + convert_proc = subprocess.Popen( + convert_cmd, stdout=subprocess.PIPE, stderr=subprocess.DEVNULL + ) convert_proc.stdout.close() From 3a34081db5762b18e630d7b313dc75e4bebe0c31 Mon Sep 17 00:00:00 2001 From: Hugo Date: Tue, 8 Oct 2019 16:32:42 +0300 Subject: [PATCH 09/59] Simplify temporary directory cleanup Co-Authored-By: Jon Dufresne --- Tests/test_file_pdf.py | 5 +--- src/PIL/IcnsImagePlugin.py | 55 +++++++++++++++++++------------------- 2 files changed, 28 insertions(+), 32 deletions(-) diff --git a/Tests/test_file_pdf.py b/Tests/test_file_pdf.py index 0158807f7..3547a5938 100644 --- a/Tests/test_file_pdf.py +++ b/Tests/test_file_pdf.py @@ -162,13 +162,10 @@ class TestFilePdf(PillowTestCase): def test_pdf_append_fails_on_nonexistent_file(self): im = hopper("RGB") - temp_dir = tempfile.mkdtemp() - try: + with tempfile.TemporaryDirectory() as temp_dir: self.assertRaises( IOError, im.save, os.path.join(temp_dir, "nonexistent.pdf"), append=True ) - finally: - os.rmdir(temp_dir) def check_pdf_pages_consistency(self, pdf): pages_info = pdf.read_indirect(pdf.pages_ref) diff --git a/src/PIL/IcnsImagePlugin.py b/src/PIL/IcnsImagePlugin.py index b80ac2179..18e8403d1 100644 --- a/src/PIL/IcnsImagePlugin.py +++ b/src/PIL/IcnsImagePlugin.py @@ -314,41 +314,40 @@ def _save(im, fp, filename): fp.flush() # create the temporary set of pngs - iconset = tempfile.mkdtemp(".iconset") - provided_images = {im.width: im for im in im.encoderinfo.get("append_images", [])} - last_w = None - second_path = None - for w in [16, 32, 128, 256, 512]: - prefix = "icon_{}x{}".format(w, w) + with tempfile.TemporaryDirectory(".iconset") as iconset: + provided_images = { + im.width: im for im in im.encoderinfo.get("append_images", []) + } + last_w = None + second_path = None + for w in [16, 32, 128, 256, 512]: + prefix = "icon_{}x{}".format(w, w) - first_path = os.path.join(iconset, prefix + ".png") - if last_w == w: - shutil.copyfile(second_path, first_path) - else: - im_w = provided_images.get(w, im.resize((w, w), Image.LANCZOS)) - im_w.save(first_path) + first_path = os.path.join(iconset, prefix + ".png") + if last_w == w: + shutil.copyfile(second_path, first_path) + else: + im_w = provided_images.get(w, im.resize((w, w), Image.LANCZOS)) + im_w.save(first_path) - second_path = os.path.join(iconset, prefix + "@2x.png") - im_w2 = provided_images.get(w * 2, im.resize((w * 2, w * 2), Image.LANCZOS)) - im_w2.save(second_path) - last_w = w * 2 + second_path = os.path.join(iconset, prefix + "@2x.png") + im_w2 = provided_images.get(w * 2, im.resize((w * 2, w * 2), Image.LANCZOS)) + im_w2.save(second_path) + last_w = w * 2 - # iconutil -c icns -o {} {} + # iconutil -c icns -o {} {} - convert_cmd = ["iconutil", "-c", "icns", "-o", filename, iconset] - convert_proc = subprocess.Popen( - convert_cmd, stdout=subprocess.PIPE, stderr=subprocess.DEVNULL - ) + convert_cmd = ["iconutil", "-c", "icns", "-o", filename, iconset] + convert_proc = subprocess.Popen( + convert_cmd, stdout=subprocess.PIPE, stderr=subprocess.DEVNULL + ) - convert_proc.stdout.close() + convert_proc.stdout.close() - retcode = convert_proc.wait() + retcode = convert_proc.wait() - # remove the temporary files - shutil.rmtree(iconset) - - if retcode: - raise subprocess.CalledProcessError(retcode, convert_cmd) + if retcode: + raise subprocess.CalledProcessError(retcode, convert_cmd) Image.register_open(IcnsImageFile.format, IcnsImageFile, lambda x: x[:4] == b"icns") From 0caa48b179eb8d9b3baa26d1f4d9c44f3bd46101 Mon Sep 17 00:00:00 2001 From: Hugo Date: Tue, 8 Oct 2019 16:41:57 +0300 Subject: [PATCH 10/59] Remove redundant __future__ from docs Co-Authored-By: Jon Dufresne --- docs/handbook/tutorial.rst | 5 ----- 1 file changed, 5 deletions(-) diff --git a/docs/handbook/tutorial.rst b/docs/handbook/tutorial.rst index 16090b040..e57bc440f 100644 --- a/docs/handbook/tutorial.rst +++ b/docs/handbook/tutorial.rst @@ -18,7 +18,6 @@ in the :py:mod:`~PIL.Image` module:: If successful, this function returns an :py:class:`~PIL.Image.Image` object. You can now use instance attributes to examine the file contents:: - >>> from __future__ import print_function >>> print(im.format, im.size, im.mode) PPM (512, 512) RGB @@ -67,7 +66,6 @@ Convert files to JPEG :: - from __future__ import print_function import os, sys from PIL import Image @@ -89,7 +87,6 @@ Create JPEG thumbnails :: - from __future__ import print_function import os, sys from PIL import Image @@ -120,7 +117,6 @@ Identify Image Files :: - from __future__ import print_function import sys from PIL import Image @@ -513,7 +509,6 @@ This is only available for JPEG and MPO files. :: from PIL import Image - from __future__ import print_function im = Image.open(file) print("original =", im.mode, im.size) From e118de943d20690e9957dc902c9ec44e4754e784 Mon Sep 17 00:00:00 2001 From: Hugo Date: Tue, 8 Oct 2019 16:55:22 +0300 Subject: [PATCH 11/59] Remove redundant __ne__ method Co-Authored-By: Jon Dufresne --- src/PIL/Image.py | 4 ---- 1 file changed, 4 deletions(-) diff --git a/src/PIL/Image.py b/src/PIL/Image.py index 494be337a..6468fbddb 100644 --- a/src/PIL/Image.py +++ b/src/PIL/Image.py @@ -646,10 +646,6 @@ class Image: and self.tobytes() == other.tobytes() ) - def __ne__(self, other): - eq = self == other - return not eq - def __repr__(self): return "<%s.%s image mode=%s size=%dx%d at 0x%X>" % ( self.__class__.__module__, From 3e24c5fea42a84fe18b7e6c08e6316addd1bb127 Mon Sep 17 00:00:00 2001 From: Hugo Date: Tue, 8 Oct 2019 17:01:11 +0300 Subject: [PATCH 12/59] Replace isStringType(t) with isinstance(t, str) Co-Authored-By: Jon Dufresne --- Tests/test_util.py | 20 -------------------- src/PIL/Image.py | 8 ++++---- src/PIL/ImageCms.py | 3 +-- src/PIL/ImageDraw.py | 5 ++--- src/PIL/ImageOps.py | 3 +-- src/PIL/JpegImagePlugin.py | 5 ++--- src/PIL/_util.py | 4 ---- 7 files changed, 10 insertions(+), 38 deletions(-) diff --git a/Tests/test_util.py b/Tests/test_util.py index 5ec21a77c..cb2101e1d 100644 --- a/Tests/test_util.py +++ b/Tests/test_util.py @@ -4,26 +4,6 @@ from .helper import PillowTestCase, unittest class TestUtil(PillowTestCase): - def test_is_string_type(self): - # Arrange - color = "red" - - # Act - it_is = _util.isStringType(color) - - # Assert - self.assertTrue(it_is) - - def test_is_not_string_type(self): - # Arrange - color = (255, 0, 0) - - # Act - it_is_not = _util.isStringType(color) - - # Assert - self.assertFalse(it_is_not) - def test_is_path(self): # Arrange fp = "filename.ext" diff --git a/src/PIL/Image.py b/src/PIL/Image.py index 6468fbddb..26194b998 100644 --- a/src/PIL/Image.py +++ b/src/PIL/Image.py @@ -43,7 +43,7 @@ from pathlib import Path # Use __version__ instead. from . import ImageMode, TiffTags, __version__, _plugins from ._binary import i8, i32le -from ._util import deferred_error, isPath, isStringType +from ._util import deferred_error, isPath logger = logging.getLogger(__name__) @@ -1466,7 +1466,7 @@ class Image: raise ValueError("cannot determine region size; use 4-item box") box += (box[0] + size[0], box[1] + size[1]) - if isStringType(im): + if isinstance(im, str): from . import ImageColor im = ImageColor.getcolor(im, self.mode) @@ -2120,7 +2120,7 @@ class Image: """ self.load() - if isStringType(channel): + if isinstance(channel, str): try: channel = self.getbands().index(channel) except ValueError: @@ -2447,7 +2447,7 @@ def new(mode, size, color=0): # don't initialize return Image()._new(core.new(mode, size)) - if isStringType(color): + if isinstance(color, str): # css3-style specifier from . import ImageColor diff --git a/src/PIL/ImageCms.py b/src/PIL/ImageCms.py index 89a4426fa..d05b3102c 100644 --- a/src/PIL/ImageCms.py +++ b/src/PIL/ImageCms.py @@ -18,7 +18,6 @@ import sys from PIL import Image -from PIL._util import isStringType try: from PIL import _imagingcms @@ -159,7 +158,7 @@ class ImageCmsProfile: """ - if isStringType(profile): + if isinstance(profile, str): self._set(core.profile_open(profile), profile) elif hasattr(profile, "read"): self._set(core.profile_frombytes(profile.read())) diff --git a/src/PIL/ImageDraw.py b/src/PIL/ImageDraw.py index 65e4d7ac9..3ece20152 100644 --- a/src/PIL/ImageDraw.py +++ b/src/PIL/ImageDraw.py @@ -34,7 +34,6 @@ import math import numbers from . import Image, ImageColor -from ._util import isStringType """ @@ -107,13 +106,13 @@ class ImageDraw: ink = self.ink else: if ink is not None: - if isStringType(ink): + if isinstance(ink, str): ink = ImageColor.getcolor(ink, self.mode) if self.palette and not isinstance(ink, numbers.Number): ink = self.palette.getcolor(ink) ink = self.draw.draw_ink(ink) if fill is not None: - if isStringType(fill): + if isinstance(fill, str): fill = ImageColor.getcolor(fill, self.mode) if self.palette and not isinstance(fill, numbers.Number): fill = self.palette.getcolor(fill) diff --git a/src/PIL/ImageOps.py b/src/PIL/ImageOps.py index 5a06adaf3..3ffe50806 100644 --- a/src/PIL/ImageOps.py +++ b/src/PIL/ImageOps.py @@ -21,7 +21,6 @@ import functools import operator from . import Image -from ._util import isStringType # # helpers @@ -39,7 +38,7 @@ def _border(border): def _color(color, mode): - if isStringType(color): + if isinstance(color, str): from . import ImageColor color = ImageColor.getcolor(color, mode) diff --git a/src/PIL/JpegImagePlugin.py b/src/PIL/JpegImagePlugin.py index 22ec5ceb9..17cb0c412 100644 --- a/src/PIL/JpegImagePlugin.py +++ b/src/PIL/JpegImagePlugin.py @@ -41,7 +41,6 @@ import warnings from . import Image, ImageFile, TiffImagePlugin from ._binary import i8, i16be as i16, i32be as i32, o8 -from ._util import isStringType from .JpegPresets import presets # __version__ is deprecated and will be removed in a future version. Use @@ -638,7 +637,7 @@ def _save(im, fp, filename): else: if subsampling in presets: subsampling = presets[subsampling].get("subsampling", -1) - if isStringType(qtables) and qtables in presets: + if isinstance(qtables, str) and qtables in presets: qtables = presets[qtables].get("quantization") if subsampling == "4:4:4": @@ -659,7 +658,7 @@ def _save(im, fp, filename): def validate_qtables(qtables): if qtables is None: return qtables - if isStringType(qtables): + if isinstance(qtables, str): try: lines = [ int(num) diff --git a/src/PIL/_util.py b/src/PIL/_util.py index 95bb457af..755b4b272 100644 --- a/src/PIL/_util.py +++ b/src/PIL/_util.py @@ -4,10 +4,6 @@ import sys py36 = sys.version_info[0:2] >= (3, 6) -def isStringType(t): - return isinstance(t, str) - - if py36: from pathlib import Path From 23fa3c69790521873425bedf63319f0409bc9938 Mon Sep 17 00:00:00 2001 From: Hugo Date: Sat, 12 Oct 2019 14:16:10 +0300 Subject: [PATCH 13/59] Remove outdated OS scripts, point docs to Dockerfiles --- depends/alpine_Dockerfile | 43 --------------------------------------- depends/debian_8.2.sh | 18 ---------------- depends/fedora_23.sh | 18 ---------------- depends/freebsd_10.sh | 13 ------------ depends/ubuntu_12.04.sh | 17 ---------------- depends/ubuntu_14.04.sh | 16 --------------- docs/installation.rst | 7 +++---- 7 files changed, 3 insertions(+), 129 deletions(-) delete mode 100644 depends/alpine_Dockerfile delete mode 100755 depends/debian_8.2.sh delete mode 100755 depends/fedora_23.sh delete mode 100755 depends/freebsd_10.sh delete mode 100755 depends/ubuntu_12.04.sh delete mode 100755 depends/ubuntu_14.04.sh diff --git a/depends/alpine_Dockerfile b/depends/alpine_Dockerfile deleted file mode 100644 index 69bdf84f6..000000000 --- a/depends/alpine_Dockerfile +++ /dev/null @@ -1,43 +0,0 @@ -# This is a sample Dockerfile to build Pillow on Alpine Linux -# with all/most of the dependencies working. -# -# Tcl/Tk isn't detecting -# Freetype has different metrics so tests are failing. -# sudo and bash are required for the webp build script. - -FROM alpine -USER root - -RUN apk --no-cache add python \ - build-base \ - python-dev \ - py-pip \ - # Pillow dependencies - jpeg-dev \ - zlib-dev \ - freetype-dev \ - lcms2-dev \ - openjpeg-dev \ - tiff-dev \ - tk-dev \ - tcl-dev - -# install from pip, without webp -#RUN LIBRARY_PATH=/lib:/usr/lib /bin/sh -c "pip install Pillow" - -# install from git, run tests, including webp -RUN apk --no-cache add git \ - bash \ - sudo - -RUN git clone https://github.com/python-pillow/Pillow.git /Pillow -RUN pip install virtualenv && virtualenv /vpy && source /vpy/bin/activate && pip install nose - -RUN echo "#!/bin/bash" >> /test && \ - echo "source /vpy/bin/activate && cd /Pillow " >> test && \ - echo "pushd depends && ./install_webp.sh && ./install_imagequant.sh && popd" >> test && \ - echo "LIBRARY_PATH=/lib:/usr/lib make install && make test" >> test - -RUN chmod +x /test - -CMD ["/test"] diff --git a/depends/debian_8.2.sh b/depends/debian_8.2.sh deleted file mode 100755 index c4f72bf8e..000000000 --- a/depends/debian_8.2.sh +++ /dev/null @@ -1,18 +0,0 @@ -#!/bin/sh - -# -# Installs all of the dependencies for Pillow for Debian 8.2 -# for both system Pythons 2.7 and 3.4 -# -# Also works for Raspbian Jessie -# - -sudo apt-get -y install python-dev python-setuptools \ - python3-dev python-virtualenv cmake -sudo apt-get -y install libtiff5-dev libjpeg62-turbo-dev zlib1g-dev \ - libfreetype6-dev liblcms2-dev libwebp-dev tcl8.6-dev tk8.6-dev \ - python-tk python3-tk libharfbuzz-dev libfribidi-dev - -./install_openjpeg.sh -./install_imagequant.sh -./install_raqm.sh diff --git a/depends/fedora_23.sh b/depends/fedora_23.sh deleted file mode 100755 index 5bdcf7f17..000000000 --- a/depends/fedora_23.sh +++ /dev/null @@ -1,18 +0,0 @@ -#!/bin/sh - -# -# Installs all of the dependencies for Pillow for Fedora 23 -# for both system Pythons 2.7 and 3.4 -# -# note that Fedora does ship packages for Pillow as python-pillow - -# this is a workaround for -# "gcc: error: /usr/lib/rpm/redhat/redhat-hardened-cc1: No such file or directory" -# errors when compiling. -sudo dnf install redhat-rpm-config - -sudo dnf install python-devel python3-devel python-virtualenv make gcc - -sudo dnf install libtiff-devel libjpeg-devel zlib-devel freetype-devel \ - lcms2-devel libwebp-devel openjpeg2-devel tkinter python3-tkinter \ - tcl-devel tk-devel harfbuzz-devel fribidi-devel libraqm-devel \ No newline at end of file diff --git a/depends/freebsd_10.sh b/depends/freebsd_10.sh deleted file mode 100755 index 36d9c1069..000000000 --- a/depends/freebsd_10.sh +++ /dev/null @@ -1,13 +0,0 @@ -#!/bin/sh - -# -# Installs all of the dependencies for Pillow for Freebsd 10.x -# for both system Pythons 2.7 and 3.4 -# -sudo pkg install python2 python3 py27-pip py27-virtualenv wget cmake - -# Openjpeg fails badly using the openjpeg package. -# I can't find a python3.4 version of tkinter -sudo pkg install jpeg-turbo tiff webp lcms2 freetype2 harfbuzz fribidi py27-tkinter - -./install_raqm_cmake.sh diff --git a/depends/ubuntu_12.04.sh b/depends/ubuntu_12.04.sh deleted file mode 100755 index 9bfae43b0..000000000 --- a/depends/ubuntu_12.04.sh +++ /dev/null @@ -1,17 +0,0 @@ -#!/bin/sh - -# -# Installs all of the dependencies for Pillow for Ubuntu 12.04 -# for both system Pythons 2.7 and 3.2 -# - -sudo apt-get -y install python-dev python-setuptools \ - python3-dev python-virtualenv cmake -sudo apt-get install libtiff4-dev libjpeg8-dev zlib1g-dev \ - libfreetype6-dev liblcms2-dev tcl8.5-dev \ - tk8.5-dev python-tk python3-tk - - -./install_openjpeg.sh -./install_webp.sh -./install_imagequant.sh diff --git a/depends/ubuntu_14.04.sh b/depends/ubuntu_14.04.sh deleted file mode 100755 index f7d28fba7..000000000 --- a/depends/ubuntu_14.04.sh +++ /dev/null @@ -1,16 +0,0 @@ -#!/bin/sh - -# -# Installs all of the dependencies for Pillow for Ubuntu 14.04 -# for both system Pythons 2.7 and 3.4 -# -sudo apt-get update -sudo apt-get -y install python-dev python-setuptools \ - python3-dev python-virtualenv cmake -sudo apt-get -y install libtiff5-dev libjpeg8-dev zlib1g-dev \ - libfreetype6-dev liblcms2-dev libwebp-dev tcl8.6-dev tk8.6-dev \ - python-tk python3-tk libharfbuzz-dev libfribidi-dev - -./install_openjpeg.sh -./install_imagequant.sh -./install_raqm.sh diff --git a/docs/installation.rst b/docs/installation.rst index 35547cb55..baca3629b 100644 --- a/docs/installation.rst +++ b/docs/installation.rst @@ -123,10 +123,9 @@ External Libraries .. note:: - There are scripts to install the dependencies for some operating - systems included in the ``depends`` directory. Also see the - Dockerfiles in our `docker images repo - `_. + There are Dockerfiles in our `Docker images repo + `_ to install the + dependencies for some operating systems. Many of Pillow's features require external libraries: From 3dac6e2c6284d5c013c6b4dce03b70070198892d Mon Sep 17 00:00:00 2001 From: Hugo Date: Sat, 12 Oct 2019 21:40:11 +0300 Subject: [PATCH 14/59] Replace ImageShow.which() with stdlib Co-Authored-By: Jon Dufresne --- src/PIL/ImageShow.py | 17 ++++------------- 1 file changed, 4 insertions(+), 13 deletions(-) diff --git a/src/PIL/ImageShow.py b/src/PIL/ImageShow.py index 2999d2087..f7e809279 100644 --- a/src/PIL/ImageShow.py +++ b/src/PIL/ImageShow.py @@ -12,6 +12,7 @@ # See the README file for information on usage and redistribution. # import os +import shutil import subprocess import sys import tempfile @@ -145,16 +146,6 @@ else: # unixoids - def which(executable): - path = os.environ.get("PATH") - if not path: - return None - for dirname in path.split(os.pathsep): - filename = os.path.join(dirname, executable) - if os.path.isfile(filename) and os.access(filename, os.X_OK): - return filename - return None - class UnixViewer(Viewer): format = "PNG" options = {"compress_level": 1} @@ -183,7 +174,7 @@ else: command = executable = "display" return command, executable - if which("display"): + if shutil.which("display"): register(DisplayViewer) class EogViewer(UnixViewer): @@ -191,7 +182,7 @@ else: command = executable = "eog" return command, executable - if which("eog"): + if shutil.which("eog"): register(EogViewer) class XVViewer(UnixViewer): @@ -203,7 +194,7 @@ else: command += " -name %s" % quote(title) return command, executable - if which("xv"): + if shutil.which("xv"): register(XVViewer) if __name__ == "__main__": From 204803917c2df413246bda8fd7ca941602bed543 Mon Sep 17 00:00:00 2001 From: mixmastamyk Date: Sun, 27 Oct 2019 23:36:33 -0700 Subject: [PATCH 15/59] Fix mismatched name, add explanation. Mention why this information is not available in the EXIF tag specified for this purpose. --- src/PIL/JpegPresets.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/PIL/JpegPresets.py b/src/PIL/JpegPresets.py index 387844f8e..012bf81b0 100644 --- a/src/PIL/JpegPresets.py +++ b/src/PIL/JpegPresets.py @@ -33,7 +33,10 @@ Possible subsampling values are 0, 1 and 2 that correspond to 4:4:4, 4:2:2 and 4:2:0. You can get the subsampling of a JPEG with the -`JpegImagePlugin.get_subsampling(im)` function. +`JpegImagePlugin.get_sampling(im)` function. + +In JPEG compressed data a JPEG marker is used instead of an EXIF tag. +(ref.: https://www.exiv2.org/tags.html) Quantization tables From 47691906df10d0a7bfb485e97ba66475247189fd Mon Sep 17 00:00:00 2001 From: pwohlhart Date: Tue, 29 Oct 2019 10:12:03 -0700 Subject: [PATCH 16/59] Better error messaging in PIL.image.fromarray --- src/PIL/Image.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/PIL/Image.py b/src/PIL/Image.py index 474ca1e88..0c783a428 100644 --- a/src/PIL/Image.py +++ b/src/PIL/Image.py @@ -2633,7 +2633,7 @@ def fromarray(obj, mode=None): typekey = (1, 1) + shape[2:], arr["typestr"] mode, rawmode = _fromarray_typemap[typekey] except KeyError: - raise TypeError("Cannot handle this data type") + raise TypeError("Cannot handle this data type: %s, %s" % typekey) else: rawmode = mode if mode in ["1", "L", "I", "P", "F"]: From 9b20276c45d7fdae34f24d9a803846b725deba82 Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Wed, 30 Oct 2019 07:23:08 +1100 Subject: [PATCH 17/59] Allow for arr KeyError --- Tests/test_image_array.py | 5 +++++ src/PIL/Image.py | 3 +++ 2 files changed, 8 insertions(+) diff --git a/Tests/test_image_array.py b/Tests/test_image_array.py index 02e5c80f2..e2a79764e 100644 --- a/Tests/test_image_array.py +++ b/Tests/test_image_array.py @@ -52,3 +52,8 @@ class TestImageArray(PillowTestCase): self.assertEqual(test("RGB"), ("RGB", (128, 100), True)) self.assertEqual(test("RGBA"), ("RGBA", (128, 100), True)) self.assertEqual(test("RGBX"), ("RGBA", (128, 100), True)) + + # Test mode is None with no "typestr" in the array interface + with self.assertRaises(TypeError): + wrapped = Wrapper(test("L"), {"shape": (100, 128)}) + Image.fromarray(wrapped) diff --git a/src/PIL/Image.py b/src/PIL/Image.py index 0c783a428..4093c030c 100644 --- a/src/PIL/Image.py +++ b/src/PIL/Image.py @@ -2631,6 +2631,9 @@ def fromarray(obj, mode=None): if mode is None: try: typekey = (1, 1) + shape[2:], arr["typestr"] + except KeyError: + raise TypeError("Cannot handle this data type") + try: mode, rawmode = _fromarray_typemap[typekey] except KeyError: raise TypeError("Cannot handle this data type: %s, %s" % typekey) From b4f93cf140faeaab59a46924d01694f106d165e2 Mon Sep 17 00:00:00 2001 From: Hugo Date: Fri, 1 Nov 2019 13:54:19 +0200 Subject: [PATCH 18/59] Upgrade Python syntax with pyupgrade --py3-plus --- Tests/test_image_fromqimage.py | 2 +- Tests/test_imageops_usm.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Tests/test_image_fromqimage.py b/Tests/test_image_fromqimage.py index bf3a250f3..0d961572c 100644 --- a/Tests/test_image_fromqimage.py +++ b/Tests/test_image_fromqimage.py @@ -6,7 +6,7 @@ from .test_imageqt import PillowQtTestCase class TestFromQImage(PillowQtTestCase, PillowTestCase): def setUp(self): - super(TestFromQImage, self).setUp() + super().setUp() self.files_to_test = [ hopper(), Image.open("Tests/images/transparent.png"), diff --git a/Tests/test_imageops_usm.py b/Tests/test_imageops_usm.py index 4ed8a83a7..71e858681 100644 --- a/Tests/test_imageops_usm.py +++ b/Tests/test_imageops_usm.py @@ -5,7 +5,7 @@ from .helper import PillowTestCase class TestImageOpsUsm(PillowTestCase): def setUp(self): - super(TestImageOpsUsm, self).setUp() + super().setUp() self.im = Image.open("Tests/images/hopper.ppm") self.addCleanup(self.im.close) self.snakes = Image.open("Tests/images/color_snakes.png") From 6f88d8dd6bf8526c760f311820296833807d18b0 Mon Sep 17 00:00:00 2001 From: Hugo Date: Sat, 2 Nov 2019 20:02:07 +0200 Subject: [PATCH 19/59] black --target-version py35 --- src/PIL/ImageDraw.py | 4 ++-- src/PIL/ImageFilter.py | 2 +- tox.ini | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/PIL/ImageDraw.py b/src/PIL/ImageDraw.py index 3ece20152..c6e12150e 100644 --- a/src/PIL/ImageDraw.py +++ b/src/PIL/ImageDraw.py @@ -313,7 +313,7 @@ class ImageDraw: language=language, stroke_width=stroke_width, *args, - **kwargs + **kwargs, ) coord = coord[0] + offset[0], coord[1] + offset[1] except AttributeError: @@ -326,7 +326,7 @@ class ImageDraw: language, stroke_width, *args, - **kwargs + **kwargs, ) except TypeError: mask = font.getmask(text) diff --git a/src/PIL/ImageFilter.py b/src/PIL/ImageFilter.py index 9cb62ad27..6b0f5eb37 100644 --- a/src/PIL/ImageFilter.py +++ b/src/PIL/ImageFilter.py @@ -495,7 +495,7 @@ class Color3DLUT(MultibandFilter): r / (size1D - 1), g / (size2D - 1), b / (size3D - 1), - *values + *values, ) else: values = callback(*values) diff --git a/tox.ini b/tox.ini index 0bf833b81..07d75be64 100644 --- a/tox.ini +++ b/tox.ini @@ -24,7 +24,7 @@ deps = [testenv:lint] commands = - black --check --diff . + black --target-version py35 --check --diff . flake8 --statistics --count isort --check-only --diff check-manifest From 58e6d127b7631fcbab6e42263482b229c7316e35 Mon Sep 17 00:00:00 2001 From: Hugo Date: Sun, 3 Nov 2019 16:02:08 +0200 Subject: [PATCH 20/59] In Python 3, 'next' is renamed to '__next__' --- src/PIL/ImageSequence.py | 3 --- 1 file changed, 3 deletions(-) diff --git a/src/PIL/ImageSequence.py b/src/PIL/ImageSequence.py index 28a54c7b0..4e9f5c210 100644 --- a/src/PIL/ImageSequence.py +++ b/src/PIL/ImageSequence.py @@ -52,9 +52,6 @@ class Iterator: except EOFError: raise StopIteration - def next(self): - return self.__next__() - def all_frames(im, func=None): """ From 24b8501d4b01f80181820aea8ed23472474db79a Mon Sep 17 00:00:00 2001 From: Hugo van Kemenade Date: Mon, 4 Nov 2019 00:32:33 +0200 Subject: [PATCH 21/59] Remove handling for pre-3.3 wide/narrow builds --- src/PIL/Image.py | 16 ---------------- 1 file changed, 16 deletions(-) diff --git a/src/PIL/Image.py b/src/PIL/Image.py index 0374d1583..2d73d8f53 100644 --- a/src/PIL/Image.py +++ b/src/PIL/Image.py @@ -88,22 +88,6 @@ except ImportError as v: ) elif str(v).startswith("The _imaging extension"): warnings.warn(str(v), RuntimeWarning) - elif "Symbol not found: _PyUnicodeUCS2_" in str(v): - # should match _PyUnicodeUCS2_FromString and - # _PyUnicodeUCS2_AsLatin1String - warnings.warn( - "The _imaging extension was built for Python with UCS2 support; " - "recompile Pillow or build Python --without-wide-unicode. ", - RuntimeWarning, - ) - elif "Symbol not found: _PyUnicodeUCS4_" in str(v): - # should match _PyUnicodeUCS4_FromString and - # _PyUnicodeUCS4_AsLatin1String - warnings.warn( - "The _imaging extension was built for Python with UCS4 support; " - "recompile Pillow or build Python --with-wide-unicode. ", - RuntimeWarning, - ) # Fail here anyway. Don't let people run with a mostly broken Pillow. # see docs/porting.rst raise From b0d9fe6ce3bf9108a820d4a443292cecfc9e60d0 Mon Sep 17 00:00:00 2001 From: Hugo Date: Mon, 4 Nov 2019 09:06:09 +0200 Subject: [PATCH 22/59] Cache pip --- .github/workflows/test.yml | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 163250248..9288553d9 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -30,6 +30,20 @@ jobs: steps: - uses: actions/checkout@v1 + - name: Ubuntu cache + uses: actions/cache@preview + if: startsWith(matrix.os, 'ubuntu') + with: + path: ~/.cache/pip + key: ${{ github.workflow }}-${{ matrix.os }}-${{ matrix.python-version }} + + - name: macOS cache + uses: actions/cache@preview + if: startsWith(matrix.os, 'macOS') + with: + path: ~/Library/Caches/pip + key: ${{ github.workflow }}-${{ matrix.os }}-${{ matrix.python-version }} + - name: Set up Python ${{ matrix.python-version }} uses: actions/setup-python@v1 with: From 45497c33b893212d71c1dfed3874e0868ff23050 Mon Sep 17 00:00:00 2001 From: Hugo Date: Sun, 17 Nov 2019 13:24:37 +0200 Subject: [PATCH 23/59] Add hashFiles to cache key, and add restore-keys --- .github/workflows/test.yml | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 9288553d9..68d2731bb 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -31,18 +31,24 @@ jobs: - uses: actions/checkout@v1 - name: Ubuntu cache - uses: actions/cache@preview + uses: actions/cache@v1 if: startsWith(matrix.os, 'ubuntu') with: path: ~/.cache/pip - key: ${{ github.workflow }}-${{ matrix.os }}-${{ matrix.python-version }} + key: + ${{ matrix.os }}-${{ matrix.python-version }}-${{ hashFiles('**/.travis/*.sh') }} + restore-keys: | + ${{ matrix.os }}-${{ matrix.python-version }}- - name: macOS cache - uses: actions/cache@preview + uses: actions/cache@v1 if: startsWith(matrix.os, 'macOS') with: path: ~/Library/Caches/pip - key: ${{ github.workflow }}-${{ matrix.os }}-${{ matrix.python-version }} + key: + ${{ matrix.os }}-${{ matrix.python-version }}-${{ hashFiles('**/.travis/*.sh') }} + restore-keys: | + ${{ matrix.os }}-${{ matrix.python-version }}- - name: Set up Python ${{ matrix.python-version }} uses: actions/setup-python@v1 From 324f4856fabd42198547b0c131fdd9bcdcf05dc3 Mon Sep 17 00:00:00 2001 From: Hugo Date: Sun, 17 Nov 2019 13:51:42 +0200 Subject: [PATCH 24/59] Cache pip --- .github/workflows/test-windows.yml | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/.github/workflows/test-windows.yml b/.github/workflows/test-windows.yml index 075707bf6..61dab9d6b 100644 --- a/.github/workflows/test-windows.yml +++ b/.github/workflows/test-windows.yml @@ -31,6 +31,15 @@ jobs: steps: - uses: actions/checkout@v1 + - name: Cache + uses: actions/cache@v1 + with: + path: ~\AppData\Local\pip\Cache + key: + ${{ runner.os }}-${{ matrix.python-version }}-${{ hashFiles('**/.github/workflows/test-windows.yml') }} + restore-keys: | + ${{ runner.os }}-${{ matrix.python-version }}- + - name: Install PyPy if: "contains(matrix.python-version, 'pypy')" run: | From 1c9275c2498bb84bd3f659de4ddd18f17b214067 Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Tue, 19 Nov 2019 20:04:38 +1100 Subject: [PATCH 25/59] Updated CHANGES.rst [ci skip] --- CHANGES.rst | 3 +++ 1 file changed, 3 insertions(+) diff --git a/CHANGES.rst b/CHANGES.rst index eb96600ed..9952c1166 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -5,6 +5,9 @@ Changelog (Pillow) 7.0.0 (unreleased) ------------------ +- Remove deprecated __version__ from plugins #4197 + [hugovk, radarhere] + - Fixed freeing unallocated pointer when resizing with height too large #4116 [radarhere] From e05ef3fef37846316488f0360577a7a5f485ea6f Mon Sep 17 00:00:00 2001 From: Hugo Date: Tue, 19 Nov 2019 11:55:49 +0200 Subject: [PATCH 26/59] Include architecture in cache key --- .github/workflows/test-windows.yml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.github/workflows/test-windows.yml b/.github/workflows/test-windows.yml index 61dab9d6b..aa4761f68 100644 --- a/.github/workflows/test-windows.yml +++ b/.github/workflows/test-windows.yml @@ -36,8 +36,9 @@ jobs: with: path: ~\AppData\Local\pip\Cache key: - ${{ runner.os }}-${{ matrix.python-version }}-${{ hashFiles('**/.github/workflows/test-windows.yml') }} + ${{ runner.os }}-${{ matrix.python-version }}-${{ matrix.architecture }}-${{ hashFiles('**/.github/workflows/test-windows.yml') }} restore-keys: | + ${{ runner.os }}-${{ matrix.python-version }}-${{ matrix.architecture }}- ${{ runner.os }}-${{ matrix.python-version }}- - name: Install PyPy From 40f891dfd70b484f7257fee455bd96f82f61d4e3 Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Tue, 19 Nov 2019 21:20:02 +1100 Subject: [PATCH 27/59] Added UnidentifiedImageError --- Tests/test_file_gd.py | 4 ++-- Tests/test_image.py | 7 +++++-- docs/releasenotes/7.0.0.rst | 8 ++++---- src/PIL/GdImageFile.py | 4 ++-- src/PIL/Image.py | 6 ++++-- src/PIL/__init__.py | 4 ++++ 6 files changed, 21 insertions(+), 12 deletions(-) diff --git a/Tests/test_file_gd.py b/Tests/test_file_gd.py index 9208dcbff..0b7543a31 100644 --- a/Tests/test_file_gd.py +++ b/Tests/test_file_gd.py @@ -1,4 +1,4 @@ -from PIL import GdImageFile +from PIL import GdImageFile, UnidentifiedImageError from .helper import PillowTestCase @@ -17,4 +17,4 @@ class TestFileGd(PillowTestCase): def test_invalid_file(self): invalid_file = "Tests/images/flower.jpg" - self.assertRaises(IOError, GdImageFile.open, invalid_file) + self.assertRaises(UnidentifiedImageError, GdImageFile.open, invalid_file) diff --git a/Tests/test_image.py b/Tests/test_image.py index b494b17dc..90bf9d1cf 100644 --- a/Tests/test_image.py +++ b/Tests/test_image.py @@ -2,7 +2,7 @@ import os import shutil import tempfile -from PIL import Image +from PIL import Image, UnidentifiedImageError from PIL._util import py3 from .helper import PillowTestCase, hopper, is_win32, unittest @@ -48,6 +48,9 @@ class TestImage(PillowTestCase): Image.new(mode, (1, 1)) self.assertEqual(str(e.exception), "unrecognized image mode") + def test_exception_inheritance(self): + self.assertTrue(issubclass(UnidentifiedImageError, IOError)) + def test_sanity(self): im = Image.new("L", (100, 100)) @@ -88,7 +91,7 @@ class TestImage(PillowTestCase): import StringIO im = StringIO.StringIO("") - self.assertRaises(IOError, Image.open, im) + self.assertRaises(UnidentifiedImageError, Image.open, im) def test_bad_mode(self): self.assertRaises(ValueError, Image.open, "filename", "bad mode") diff --git a/docs/releasenotes/7.0.0.rst b/docs/releasenotes/7.0.0.rst index 374a8d2dd..6d64cc7f9 100644 --- a/docs/releasenotes/7.0.0.rst +++ b/docs/releasenotes/7.0.0.rst @@ -63,11 +63,11 @@ TODO API Additions ============= -TODO -^^^^ - -TODO +Custom unidentified image error +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +Pillow will now throw a custom UnidentifiedImageError when an image cannot be +identified. For backwards compatibility, this will inherit from IOError. Other Changes ============= diff --git a/src/PIL/GdImageFile.py b/src/PIL/GdImageFile.py index 80235bba6..54c88712c 100644 --- a/src/PIL/GdImageFile.py +++ b/src/PIL/GdImageFile.py @@ -23,7 +23,7 @@ # purposes only. -from . import ImageFile, ImagePalette +from . import ImageFile, ImagePalette, UnidentifiedImageError from ._binary import i8, i16be as i16, i32be as i32 ## @@ -82,4 +82,4 @@ def open(fp, mode="r"): try: return GdImageFile(fp) except SyntaxError: - raise IOError("cannot identify this image file") + raise UnidentifiedImageError("cannot identify this image file") diff --git a/src/PIL/Image.py b/src/PIL/Image.py index f50b319d2..b04b4af93 100644 --- a/src/PIL/Image.py +++ b/src/PIL/Image.py @@ -38,7 +38,7 @@ import warnings # VERSION was removed in Pillow 6.0.0. # PILLOW_VERSION was removed in Pillow 7.0.0. # Use __version__ instead. -from . import ImageMode, TiffTags, __version__, _plugins +from . import ImageMode, TiffTags, UnidentifiedImageError, __version__, _plugins from ._binary import i8, i32le from ._util import deferred_error, isPath, isStringType, py3 @@ -2794,7 +2794,9 @@ def open(fp, mode="r"): fp.close() for message in accept_warnings: warnings.warn(message) - raise IOError("cannot identify image file %r" % (filename if filename else fp)) + raise UnidentifiedImageError( + "cannot identify image file %r" % (filename if filename else fp) + ) # diff --git a/src/PIL/__init__.py b/src/PIL/__init__.py index fd02b40d9..e7f26488d 100644 --- a/src/PIL/__init__.py +++ b/src/PIL/__init__.py @@ -70,3 +70,7 @@ _plugins = [ "XpmImagePlugin", "XVThumbImagePlugin", ] + + +class UnidentifiedImageError(IOError): + pass From 699183c5dc63cc983c7656b307cf4b8fac726942 Mon Sep 17 00:00:00 2001 From: Andrew Murray <3112309+radarhere@users.noreply.github.com> Date: Tue, 19 Nov 2019 21:41:40 +1100 Subject: [PATCH 28/59] Highlighted classes Co-Authored-By: Hugo van Kemenade --- docs/releasenotes/7.0.0.rst | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/releasenotes/7.0.0.rst b/docs/releasenotes/7.0.0.rst index 6d64cc7f9..386a26757 100644 --- a/docs/releasenotes/7.0.0.rst +++ b/docs/releasenotes/7.0.0.rst @@ -66,8 +66,8 @@ API Additions Custom unidentified image error ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ -Pillow will now throw a custom UnidentifiedImageError when an image cannot be -identified. For backwards compatibility, this will inherit from IOError. +Pillow will now throw a custom ``UnidentifiedImageError`` when an image cannot be +identified. For backwards compatibility, this will inherit from ``IOError``. Other Changes ============= From 1ff3a501ef31552e1ef279b8554655977f1c2e0e Mon Sep 17 00:00:00 2001 From: Hugo Date: Wed, 20 Nov 2019 14:00:59 +0200 Subject: [PATCH 29/59] Use Codecov's GitHub action --- .codecov.yml | 2 -- .github/workflows/test-windows.yml | 10 ++++++---- .github/workflows/test.yml | 8 ++++++++ .travis/after_success.sh | 11 +++++++++-- 4 files changed, 23 insertions(+), 8 deletions(-) diff --git a/.codecov.yml b/.codecov.yml index a93095486..3e147d151 100644 --- a/.codecov.yml +++ b/.codecov.yml @@ -6,6 +6,4 @@ codecov: # https://docs.codecov.io/v4.3.6/docs/comparing-commits allow_coverage_offsets: true - token: 6dafc396-e7f5-4221-a38a-8b07a49fbdae - comment: off diff --git a/.github/workflows/test-windows.yml b/.github/workflows/test-windows.yml index 075707bf6..30f302d20 100644 --- a/.github/workflows/test-windows.yml +++ b/.github/workflows/test-windows.yml @@ -55,10 +55,9 @@ jobs: python-version: ${{ matrix.python-version }} architecture: ${{ matrix.architecture }} - - name: pip install wheel pytest pytest-cov codecov + - name: pip install wheel pytest pytest-cov run: | "%pythonLocation%\python.exe" -m pip install wheel pytest pytest-cov - pip install codecov shell: cmd - name: Fetch dependencies @@ -357,8 +356,11 @@ jobs: path: Tests/errors - name: Upload coverage - run: 'codecov --file "%GITHUB_WORKSPACE%\coverage.xml" --name "%pythonLocation%"' - shell: cmd + if: success() + uses: codecov/codecov-action@v1 + with: + token: ${{ secrets.CODECOV_TOKEN }} + name: ${{ runner.os }} Python ${{ matrix.python-version }} - name: Build wheel id: wheel diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 163250248..580aff824 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -71,5 +71,13 @@ jobs: run: | .travis/after_success.sh env: + MATRIX_OS: ${{ matrix.os }} CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }} COVERALLS_REPO_TOKEN: ${{ secrets.COVERALLS_REPO_TOKEN }} + + - name: Upload coverage + if: success() + uses: codecov/codecov-action@v1 + with: + token: ${{ secrets.CODECOV_TOKEN }} + name: ${{ matrix.os }} Python ${{ matrix.python-version }} diff --git a/.travis/after_success.sh b/.travis/after_success.sh index 721a469b6..ec4f027b4 100755 --- a/.travis/after_success.sh +++ b/.travis/after_success.sh @@ -1,7 +1,12 @@ #!/bin/bash # gather the coverage data -sudo apt-get -qq install lcov +if [[ "$MATRIX_OS" == "macOS-latest" ]]; then + brew install lcov +else + sudo apt-get -qq install lcov +fi + lcov --capture --directory . -b . --output-file coverage.info # filter to remove system headers lcov --remove coverage.info '/usr/*' -o coverage.filtered.info @@ -13,7 +18,9 @@ coverage report pip install codecov pip install coveralls-merge coveralls-merge coverage.c.json -codecov +if [[ $TRAVIS ]]; then + codecov +fi if [ "$TRAVIS_PYTHON_VERSION" == "3.7" ] && [ "$DOCKER" == "" ]; then # Coverage and quality reports on just the latest diff. From a1c6b5edf738f834cac268587eadf4c0d55df2e4 Mon Sep 17 00:00:00 2001 From: Hugo Date: Tue, 22 Oct 2019 16:37:04 +0300 Subject: [PATCH 30/59] Cover tests https://nedbatchelder.com/blog/201908/dont_omit_tests_from_coverage.html --- .appveyor.yml | 2 +- .github/workflows/test-windows.yml | 2 +- .travis/test.sh | 2 +- Tests/README.rst | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/.appveyor.yml b/.appveyor.yml index 3183c3d1b..edfc75101 100644 --- a/.appveyor.yml +++ b/.appveyor.yml @@ -77,7 +77,7 @@ test_script: - cd c:\pillow - '%PYTHON%\%PIP_DIR%\pip.exe install pytest pytest-cov' - c:\"Program Files (x86)"\"Windows Kits"\10\Debuggers\x86\gflags.exe /p /enable %PYTHON%\%EXECUTABLE% -- '%PYTHON%\%EXECUTABLE% -m pytest -vx --cov PIL --cov-report term --cov-report xml Tests' +- '%PYTHON%\%EXECUTABLE% -m pytest -vx --cov PIL --cov Tests --cov-report term --cov-report xml Tests' #- '%PYTHON%\%EXECUTABLE% test-installed.py -v -s %TEST_OPTIONS%' TODO TEST_OPTIONS with pytest? after_test: diff --git a/.github/workflows/test-windows.yml b/.github/workflows/test-windows.yml index 075707bf6..05a04ca02 100644 --- a/.github/workflows/test-windows.yml +++ b/.github/workflows/test-windows.yml @@ -346,7 +346,7 @@ jobs: rem Add libraqm.dll (copied to INCLIB) to PATH. path %INCLIB%;%PATH% cd /D %GITHUB_WORKSPACE% - %PYTHON%\python.exe -m pytest -vx --cov PIL --cov-report term --cov-report xml Tests + %PYTHON%\python.exe -m pytest -vx --cov PIL --cov Tests --cov-report term --cov-report xml Tests shell: cmd - name: Upload errors diff --git a/.travis/test.sh b/.travis/test.sh index a65e620b8..91dae9f8d 100755 --- a/.travis/test.sh +++ b/.travis/test.sh @@ -2,7 +2,7 @@ set -e -python -m pytest -v -x --cov PIL --cov-report term Tests +python -m pytest -v -x --cov PIL --cov Tests --cov-report term Tests # Docs if [ "$TRAVIS_PYTHON_VERSION" == "3.7" ]; then make doccheck; fi diff --git a/Tests/README.rst b/Tests/README.rst index da3297bce..9c959a4bf 100644 --- a/Tests/README.rst +++ b/Tests/README.rst @@ -27,6 +27,6 @@ Run all the tests from the root of the Pillow source distribution:: Or with coverage:: - pytest --cov PIL --cov-report term + pytest --cov PIL --cov Tests --cov-report term coverage html open htmlcov/index.html From cccc535f66a306059f919dbb1f64547adc152490 Mon Sep 17 00:00:00 2001 From: Hugo Date: Sun, 27 Oct 2019 12:20:54 +0200 Subject: [PATCH 31/59] Omit Tests/check_ files from coverage --- .coveragerc | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/.coveragerc b/.coveragerc index ea79190ae..e93a2a426 100644 --- a/.coveragerc +++ b/.coveragerc @@ -12,3 +12,7 @@ exclude_lines = # Don't complain about debug code if Image.DEBUG: if DEBUG: + +[run] +omit = + Tests/check_*.py From b33df562b8a212bf499ee304efc847bc4d476503 Mon Sep 17 00:00:00 2001 From: Hugo Date: Sun, 27 Oct 2019 18:23:28 +0200 Subject: [PATCH 32/59] Matches 'omit:' from .coveragerc --- .codecov.yml | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/.codecov.yml b/.codecov.yml index a93095486..033786a49 100644 --- a/.codecov.yml +++ b/.codecov.yml @@ -9,3 +9,7 @@ codecov: token: 6dafc396-e7f5-4221-a38a-8b07a49fbdae comment: off + +# Matches 'omit:' in .coveragerc +ignore: + - "Tests/check_*.py" From 106fc4085f19c4c40a7c482d5c57a4fb86143e71 Mon Sep 17 00:00:00 2001 From: Hugo Date: Mon, 18 Nov 2019 13:48:37 +0200 Subject: [PATCH 33/59] Remove redundant files --- Tests/bench_get.py | 23 ----------------------- 1 file changed, 23 deletions(-) delete mode 100644 Tests/bench_get.py diff --git a/Tests/bench_get.py b/Tests/bench_get.py deleted file mode 100644 index 8a54ff921..000000000 --- a/Tests/bench_get.py +++ /dev/null @@ -1,23 +0,0 @@ -import sys -import timeit - -from . import helper - -sys.path.insert(0, ".") - - -def bench(mode): - im = helper.hopper(mode) - get = im.im.getpixel - xy = 50, 50 # position shouldn't really matter - t0 = timeit.default_timer() - for _ in range(1000000): - get(xy) - print(mode, timeit.default_timer() - t0, "us") - - -bench("L") -bench("I") -bench("I;16") -bench("F") -bench("RGB") From 837d8ae984e5959633483191376b70d652e89379 Mon Sep 17 00:00:00 2001 From: nulano Date: Sun, 13 Oct 2019 00:11:48 +0100 Subject: [PATCH 34/59] fix support for extended unicode characters in PyPy --- Tests/test_imagefont.py | 2 +- src/_imagingft.c | 14 +------------- 2 files changed, 2 insertions(+), 14 deletions(-) diff --git a/Tests/test_imagefont.py b/Tests/test_imagefont.py index b7cfefacf..1c44e5221 100644 --- a/Tests/test_imagefont.py +++ b/Tests/test_imagefont.py @@ -463,7 +463,7 @@ class TestImageFont(PillowTestCase): with self.assertRaises(UnicodeEncodeError): font.getsize("’") - @unittest.skipIf(is_pypy(), "requires CPython") + @unittest.skipIf(is_pypy(), "failing on PyPy") def test_unicode_extended(self): # issue #3777 text = "A\u278A\U0001F12B" diff --git a/src/_imagingft.c b/src/_imagingft.c index 0a9c00a91..d89f9a758 100644 --- a/src/_imagingft.c +++ b/src/_imagingft.c @@ -326,24 +326,12 @@ getfont(PyObject* self_, PyObject* args, PyObject* kw) static int font_getchar(PyObject* string, int index, FT_ULong* char_out) { -#if (defined(PYPY_VERSION_NUM)) - if (PyUnicode_Check(string)) { - Py_UNICODE* p = PyUnicode_AS_UNICODE(string); - int size = PyUnicode_GET_SIZE(string); - if (index >= size) - return 0; - *char_out = p[index]; - return 1; - } -#else if (PyUnicode_Check(string)) { if (index >= PyUnicode_GET_LENGTH(string)) return 0; *char_out = PyUnicode_READ_CHAR(string, index); return 1; } -#endif - return 0; } @@ -363,7 +351,7 @@ text_layout_raqm(PyObject* string, FontObject* self, const char* dir, PyObject * goto failed; } -#if (defined(PYPY_VERSION_NUM)) +#if (defined(PYPY_VERSION_NUM) && (PYPY_VERSION_NUM < 0x07020000)) if (PyUnicode_Check(string)) { Py_UNICODE *text = PyUnicode_AS_UNICODE(string); Py_ssize_t size = PyUnicode_GET_SIZE(string); From 64317f8885b171f16938a6e8306ab068a8a97ce4 Mon Sep 17 00:00:00 2001 From: nulano Date: Tue, 15 Oct 2019 21:58:50 +0100 Subject: [PATCH 35/59] raqm now works with PyPy on Windows --- .github/workflows/test-windows.yml | 4 ---- src/_imagingft.c | 2 +- 2 files changed, 1 insertion(+), 5 deletions(-) diff --git a/.github/workflows/test-windows.yml b/.github/workflows/test-windows.yml index 075707bf6..af89d2d43 100644 --- a/.github/workflows/test-windows.yml +++ b/.github/workflows/test-windows.yml @@ -253,7 +253,6 @@ jobs: # for Raqm - name: Build dependencies / HarfBuzz - if: "!contains(matrix.python-version, 'pypy')" run: | set INCLUDE=C:\Program Files (x86)\Microsoft SDKs\Windows\V7.1A\Include set INCLIB=%GITHUB_WORKSPACE%\winbuild\depends\msvcr10-x32 @@ -274,7 +273,6 @@ jobs: # for Raqm - name: Build dependencies / FriBidi - if: "!contains(matrix.python-version, 'pypy')" run: | set INCLUDE=C:\Program Files (x86)\Microsoft SDKs\Windows\V7.1A\Include set INCLIB=%GITHUB_WORKSPACE%\winbuild\depends\msvcr10-x32 @@ -292,9 +290,7 @@ jobs: copy /Y /B *.lib %INCLIB% shell: cmd - # failing with PyPy3 - name: Build dependencies / Raqm - if: "!contains(matrix.python-version, 'pypy')" run: | set INCLUDE=C:\Program Files (x86)\Microsoft SDKs\Windows\V7.1A\Include set INCLIB=%GITHUB_WORKSPACE%\winbuild\depends\msvcr10-x32 diff --git a/src/_imagingft.c b/src/_imagingft.c index d89f9a758..62a4c283e 100644 --- a/src/_imagingft.c +++ b/src/_imagingft.c @@ -380,7 +380,7 @@ text_layout_raqm(PyObject* string, FontObject* self, const char* dir, PyObject * and raqm fails with empty strings */ goto failed; } - int set_text = (*p_raqm.set_text)(rq, (const uint32_t *)(text), size); + int set_text = (*p_raqm.set_text)(rq, text, size); PyMem_Free(text); if (!set_text) { PyErr_SetString(PyExc_ValueError, "raqm_set_text() failed"); From b0688d9aed1bc8d83ef33dc0cbf8287cd2733fa1 Mon Sep 17 00:00:00 2001 From: nulano Date: Wed, 20 Nov 2019 17:13:18 +0000 Subject: [PATCH 36/59] Use prebuilt GhostScript on AppVeyor --- .appveyor.yml | 3 +++ winbuild/build_dep.py | 28 ---------------------------- winbuild/config.py | 5 ----- 3 files changed, 3 insertions(+), 33 deletions(-) diff --git a/.appveyor.yml b/.appveyor.yml index 3183c3d1b..9ec837996 100644 --- a/.appveyor.yml +++ b/.appveyor.yml @@ -56,6 +56,9 @@ install: c:\pillow\winbuild\build_deps.cmd $host.SetShouldExit(0) } +- curl -fsSL -o gs950.exe https://github.com/ArtifexSoftware/ghostpdl-downloads/releases/download/gs950/gs950w32.exe +- gs950.exe /S +- path %path%;C:\Program Files (x86)\gs\gs9.50\bin build_script: - ps: | diff --git a/winbuild/build_dep.py b/winbuild/build_dep.py index c2ed77ca0..5762de5e5 100644 --- a/winbuild/build_dep.py +++ b/winbuild/build_dep.py @@ -300,33 +300,6 @@ endlocal ) -def build_ghostscript(compiler, bit): - script = ( - r""" -rem Build gs -setlocal -""" - + vc_setup(compiler, bit) - + r""" -set MSVC_VERSION=""" - + {"2010": "90", "2015": "14"}[compiler["vc_version"]] - + r""" -set RCOMP="C:\Program Files (x86)\Microsoft SDKs\Windows\v7.1A\Bin\RC.Exe" -cd /D %%GHOSTSCRIPT%% -""" - ) - if bit == 64: - script += r""" -set WIN64="" -""" - script += r""" -nmake -nologo -f psi/msvc.mak -copy /Y /B bin\ C:\Python27\ -endlocal -""" - return script % compiler - - def add_compiler(compiler, bit): script.append(setup_compiler(compiler)) script.append(nmake_libs(compiler, bit)) @@ -336,7 +309,6 @@ def add_compiler(compiler, bit): script.append(msbuild_freetype(compiler, bit)) script.append(build_lcms2(compiler)) script.append(nmake_openjpeg(compiler, bit)) - script.append(build_ghostscript(compiler, bit)) script.append(end_compiler()) diff --git a/winbuild/config.py b/winbuild/config.py index 826c6183c..0ecfaddeb 100644 --- a/winbuild/config.py +++ b/winbuild/config.py @@ -53,11 +53,6 @@ libs = { "filename": "lcms2-2.8.zip", "dir": "lcms2-2.8", }, - "ghostscript": { - "url": "https://github.com/ArtifexSoftware/ghostpdl-downloads/releases/download/gs927/ghostscript-9.27.tar.gz", # noqa: E501 - "filename": "ghostscript-9.27.tar.gz", - "dir": "ghostscript-9.27", - }, "tcl-8.5": { "url": SF_MIRROR + "/project/tcl/Tcl/8.5.19/tcl8519-src.zip", "filename": "tcl8519-src.zip", From fa40817efbbb7555c55210562e3304933a607c5e Mon Sep 17 00:00:00 2001 From: Hugo Date: Wed, 20 Nov 2019 19:22:38 +0200 Subject: [PATCH 37/59] GHA: Install lcov on macOS to report coverage --- .github/workflows/test.yml | 1 + .travis/after_success.sh | 7 ++++++- 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 163250248..06caa4316 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -71,5 +71,6 @@ jobs: run: | .travis/after_success.sh env: + MATRIX_OS: ${{ matrix.os }} CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }} COVERALLS_REPO_TOKEN: ${{ secrets.COVERALLS_REPO_TOKEN }} diff --git a/.travis/after_success.sh b/.travis/after_success.sh index 721a469b6..badf04484 100755 --- a/.travis/after_success.sh +++ b/.travis/after_success.sh @@ -1,7 +1,12 @@ #!/bin/bash # gather the coverage data -sudo apt-get -qq install lcov +if [[ "$MATRIX_OS" == "macOS-latest" ]]; then + brew install lcov +else + sudo apt-get -qq install lcov +fi + lcov --capture --directory . -b . --output-file coverage.info # filter to remove system headers lcov --remove coverage.info '/usr/*' -o coverage.filtered.info From d870f1ac2ee8de715c11cf7898e511e1861dbb18 Mon Sep 17 00:00:00 2001 From: Hugo Date: Wed, 20 Nov 2019 19:52:32 +0200 Subject: [PATCH 38/59] Revert --- .codecov.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.codecov.yml b/.codecov.yml index 3e147d151..a93095486 100644 --- a/.codecov.yml +++ b/.codecov.yml @@ -6,4 +6,6 @@ codecov: # https://docs.codecov.io/v4.3.6/docs/comparing-commits allow_coverage_offsets: true + token: 6dafc396-e7f5-4221-a38a-8b07a49fbdae + comment: off From 96bfc981335e23a32ff263ed9b78eb0e738d4cea Mon Sep 17 00:00:00 2001 From: nulano Date: Wed, 20 Nov 2019 18:20:01 +0000 Subject: [PATCH 39/59] use checkout action to fetch depends --- .github/workflows/test-windows.yml | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/.github/workflows/test-windows.yml b/.github/workflows/test-windows.yml index 075707bf6..38c7a55ef 100644 --- a/.github/workflows/test-windows.yml +++ b/.github/workflows/test-windows.yml @@ -31,6 +31,11 @@ jobs: steps: - uses: actions/checkout@v1 + - uses: actions/checkout@v1 + with: + repository: python-pillow/pillow-depends + ref: master + - name: Install PyPy if: "contains(matrix.python-version, 'pypy')" run: | @@ -75,12 +80,9 @@ jobs: Write-Host "::add-path::C:\Program Files (x86)\gs\gs9.50\bin" $env:PYTHON=$env:pythonLocation - curl -fsSL -o pillow-depends.zip https://github.com/python-pillow/pillow-depends/archive/master.zip - 7z x pillow-depends.zip -oc:\ - mv c:\pillow-depends-master c:\pillow-depends - xcopy c:\pillow-depends\*.zip $env:GITHUB_WORKSPACE\winbuild\ - xcopy c:\pillow-depends\*.tar.gz $env:GITHUB_WORKSPACE\winbuild\ - xcopy /s c:\pillow-depends\test_images\* $env:GITHUB_WORKSPACE\tests\images\ + xcopy ..\pillow-depends\*.zip $env:GITHUB_WORKSPACE\winbuild\ + xcopy ..\pillow-depends\*.tar.gz $env:GITHUB_WORKSPACE\winbuild\ + xcopy /s ..\pillow-depends\test_images\* $env:GITHUB_WORKSPACE\tests\images\ cd $env:GITHUB_WORKSPACE/winbuild/ python.exe $env:GITHUB_WORKSPACE\winbuild\build_dep.py env: From 416393885429240ea599411bd7386c3dc04e275c Mon Sep 17 00:00:00 2001 From: nulano Date: Wed, 20 Nov 2019 17:48:58 +0000 Subject: [PATCH 40/59] Test Python 3.8 on Windows with GitHub Actions --- .github/workflows/test-windows.yml | 2 +- winbuild/config.py | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/.github/workflows/test-windows.yml b/.github/workflows/test-windows.yml index 075707bf6..e5356b79f 100644 --- a/.github/workflows/test-windows.yml +++ b/.github/workflows/test-windows.yml @@ -9,7 +9,7 @@ jobs: strategy: fail-fast: false matrix: - python-version: ["3.5", "3.6", "3.7"] + python-version: ["3.5", "3.6", "3.7", "3.8"] architecture: ["x86", "x64"] include: - architecture: "x86" diff --git a/winbuild/config.py b/winbuild/config.py index 826c6183c..999e7e5b8 100644 --- a/winbuild/config.py +++ b/winbuild/config.py @@ -13,6 +13,7 @@ pythons = { "3.5": {"compiler": 7.1, "vc": 2015}, "3.6": {"compiler": 7.1, "vc": 2015}, "3.7": {"compiler": 7.1, "vc": 2015}, + "3.8": {"compiler": 7.1, "vc": 2015}, } VIRT_BASE = "c:/vp/" From bd6f00cf4394a77e608e53f6d04834dab3840a5f Mon Sep 17 00:00:00 2001 From: nulano Date: Wed, 20 Nov 2019 19:45:30 +0000 Subject: [PATCH 41/59] use cached binary dependency files --- .github/workflows/test-windows.yml | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/.github/workflows/test-windows.yml b/.github/workflows/test-windows.yml index 38c7a55ef..b854c0a36 100644 --- a/.github/workflows/test-windows.yml +++ b/.github/workflows/test-windows.yml @@ -68,14 +68,11 @@ jobs: - name: Fetch dependencies run: | - curl -fsSL -o nasm.zip https://www.nasm.us/pub/nasm/releasebuilds/2.14.02/win64/nasm-2.14.02-win64.zip - 7z x nasm.zip "-o$env:RUNNER_WORKSPACE\" + 7z x ..\pillow-depends\nasm-2.14.02-win64.zip "-o$env:RUNNER_WORKSPACE\" Write-Host "`#`#[add-path]$env:RUNNER_WORKSPACE\nasm-2.14.02" Write-Host "::add-path::$env:RUNNER_WORKSPACE\nasm-2.14.02" - # 32-bit should work on both platforms - curl -fsSL -o gs950.exe https://github.com/ArtifexSoftware/ghostpdl-downloads/releases/download/gs950/gs950w32.exe - ./gs950.exe /S + ..\pillow-depends\gs950w32.exe /S Write-Host "`#`#[add-path]C:\Program Files (x86)\gs\gs9.50\bin" Write-Host "::add-path::C:\Program Files (x86)\gs\gs9.50\bin" From 14f65284224d7c7a72d5bd0c35b7b85114d6817b Mon Sep 17 00:00:00 2001 From: nulano Date: Wed, 20 Nov 2019 21:11:51 +0000 Subject: [PATCH 42/59] add pypy to gha win --- .github/workflows/test-windows.yml | 3 ++- winbuild/config.py | 2 +- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/.github/workflows/test-windows.yml b/.github/workflows/test-windows.yml index e5356b79f..08d1573d7 100644 --- a/.github/workflows/test-windows.yml +++ b/.github/workflows/test-windows.yml @@ -9,7 +9,7 @@ jobs: strategy: fail-fast: false matrix: - python-version: ["3.5", "3.6", "3.7", "3.8"] + python-version: ["3.5", "3.6", "3.7", "3.8", "pypy3.6"] architecture: ["x86", "x64"] include: - architecture: "x86" @@ -324,6 +324,7 @@ jobs: set LIB=%INCLIB%;%PYTHON%\tcl set INCLUDE=%INCLIB%;%GITHUB_WORKSPACE%\depends\tcl86\include;%INCLUDE% call "C:\Program Files (x86)\Microsoft Visual Studio\2019\Enterprise\VC\Auxiliary\Build\vcvarsall.bat" ${{ matrix.platform-vcvars }} + set MSSdk=1 set DISTUTILS_USE_SDK=1 set py_vcruntime_redist=true %PYTHON%\python.exe setup.py build_ext install diff --git a/winbuild/config.py b/winbuild/config.py index 999e7e5b8..7f7fc7430 100644 --- a/winbuild/config.py +++ b/winbuild/config.py @@ -3,10 +3,10 @@ import os SF_MIRROR = "https://iweb.dl.sourceforge.net" pythons = { + "pypy3": {"compiler": 7.1, "vc": 2015}, # for AppVeyor "35": {"compiler": 7.1, "vc": 2015}, "36": {"compiler": 7.1, "vc": 2015}, - "pypy3": {"compiler": 7.1, "vc": 2015}, "37": {"compiler": 7.1, "vc": 2015}, "38": {"compiler": 7.1, "vc": 2015}, # for GitHub Actions From 33dabf986f1b14557f8005334c70327a1f5740aa Mon Sep 17 00:00:00 2001 From: Jon Dufresne Date: Wed, 20 Nov 2019 18:42:52 -0800 Subject: [PATCH 43/59] Import unittest from stdlib rather than helper.py The unittest in helper.py has not offered an interesting abstraction since dbe9f85c7d8f760756ebf8129195470700e63e3d so import from the more typical stdlib location. --- Tests/bench_cffi_access.py | 3 ++- Tests/check_fli_overflow.py | 4 +++- Tests/check_imaging_leaks.py | 4 +++- Tests/check_j2k_leaks.py | 3 ++- Tests/check_j2k_overflow.py | 4 +++- Tests/check_jpeg_leaks.py | 3 ++- Tests/check_large_memory.py | 3 ++- Tests/check_large_memory_numpy.py | 3 ++- Tests/check_libtiff_segfault.py | 4 +++- Tests/check_png_dos.py | 3 ++- Tests/test_color_lut.py | 3 ++- Tests/test_core_resources.py | 3 ++- Tests/test_features.py | 3 ++- Tests/test_file_eps.py | 3 ++- Tests/test_file_fpx.py | 4 +++- Tests/test_file_gif.py | 3 ++- Tests/test_file_icns.py | 3 ++- Tests/test_file_mic.py | 4 +++- Tests/test_file_msp.py | 3 ++- Tests/test_file_png.py | 3 ++- Tests/test_file_sun.py | 3 ++- Tests/test_file_tiff.py | 3 ++- Tests/test_file_webp.py | 4 +++- Tests/test_file_webp_alpha.py | 4 +++- Tests/test_font_leaks.py | 4 +++- Tests/test_image.py | 3 ++- Tests/test_image_access.py | 3 ++- Tests/test_image_resample.py | 3 ++- Tests/test_imagedraw.py | 3 ++- Tests/test_imagedraw2.py | 3 ++- Tests/test_imagefile.py | 3 ++- Tests/test_imagefont.py | 3 ++- Tests/test_imagefont_bitmap.py | 4 +++- Tests/test_imagefontctl.py | 4 +++- Tests/test_imageshow.py | 4 +++- Tests/test_imagetk.py | 4 +++- Tests/test_imagewin.py | 4 +++- Tests/test_lib_image.py | 4 +++- Tests/test_locale.py | 3 ++- Tests/test_main.py | 3 ++- Tests/test_map.py | 3 ++- Tests/test_numpy.py | 4 +++- Tests/test_pyroma.py | 4 +++- Tests/test_util.py | 4 +++- Tests/test_webp_leaks.py | 3 ++- 45 files changed, 108 insertions(+), 45 deletions(-) diff --git a/Tests/bench_cffi_access.py b/Tests/bench_cffi_access.py index 99c7006fa..1797d34fc 100644 --- a/Tests/bench_cffi_access.py +++ b/Tests/bench_cffi_access.py @@ -1,8 +1,9 @@ import time +import unittest from PIL import PyAccess -from .helper import PillowTestCase, hopper, unittest +from .helper import PillowTestCase, hopper # Not running this test by default. No DOS against Travis CI. diff --git a/Tests/check_fli_overflow.py b/Tests/check_fli_overflow.py index db6559f1e..fa6037c2e 100644 --- a/Tests/check_fli_overflow.py +++ b/Tests/check_fli_overflow.py @@ -1,6 +1,8 @@ +import unittest + from PIL import Image -from .helper import PillowTestCase, unittest +from .helper import PillowTestCase TEST_FILE = "Tests/images/fli_overflow.fli" diff --git a/Tests/check_imaging_leaks.py b/Tests/check_imaging_leaks.py index 34a570bb1..2c1793a4f 100755 --- a/Tests/check_imaging_leaks.py +++ b/Tests/check_imaging_leaks.py @@ -1,7 +1,9 @@ #!/usr/bin/env python +import unittest + from PIL import Image -from .helper import PillowTestCase, is_win32, unittest +from .helper import PillowTestCase, is_win32 min_iterations = 100 max_iterations = 10000 diff --git a/Tests/check_j2k_leaks.py b/Tests/check_j2k_leaks.py index d9c6c68b7..1635f1001 100755 --- a/Tests/check_j2k_leaks.py +++ b/Tests/check_j2k_leaks.py @@ -1,8 +1,9 @@ +import unittest from io import BytesIO from PIL import Image -from .helper import PillowTestCase, is_win32, unittest +from .helper import PillowTestCase, is_win32 # Limits for testing the leak mem_limit = 1024 * 1048576 diff --git a/Tests/check_j2k_overflow.py b/Tests/check_j2k_overflow.py index 3e6cf8d34..d5b6e455f 100644 --- a/Tests/check_j2k_overflow.py +++ b/Tests/check_j2k_overflow.py @@ -1,6 +1,8 @@ +import unittest + from PIL import Image -from .helper import PillowTestCase, unittest +from .helper import PillowTestCase class TestJ2kEncodeOverflow(PillowTestCase): diff --git a/Tests/check_jpeg_leaks.py b/Tests/check_jpeg_leaks.py index 8cb93f2dd..6b2801a21 100644 --- a/Tests/check_jpeg_leaks.py +++ b/Tests/check_jpeg_leaks.py @@ -1,6 +1,7 @@ +import unittest from io import BytesIO -from .helper import PillowTestCase, hopper, is_win32, unittest +from .helper import PillowTestCase, hopper, is_win32 iterations = 5000 diff --git a/Tests/check_large_memory.py b/Tests/check_large_memory.py index 5df476e0a..7fcaa4cf9 100644 --- a/Tests/check_large_memory.py +++ b/Tests/check_large_memory.py @@ -1,8 +1,9 @@ import sys +import unittest from PIL import Image -from .helper import PillowTestCase, unittest +from .helper import PillowTestCase # This test is not run automatically. # diff --git a/Tests/check_large_memory_numpy.py b/Tests/check_large_memory_numpy.py index 4653a6013..8e65dc1cb 100644 --- a/Tests/check_large_memory_numpy.py +++ b/Tests/check_large_memory_numpy.py @@ -1,8 +1,9 @@ import sys +import unittest from PIL import Image -from .helper import PillowTestCase, unittest +from .helper import PillowTestCase # This test is not run automatically. # diff --git a/Tests/check_libtiff_segfault.py b/Tests/check_libtiff_segfault.py index ae9a46d1b..a0263f725 100644 --- a/Tests/check_libtiff_segfault.py +++ b/Tests/check_libtiff_segfault.py @@ -1,6 +1,8 @@ +import unittest + from PIL import Image -from .helper import PillowTestCase, unittest +from .helper import PillowTestCase TEST_FILE = "Tests/images/libtiff_segfault.tif" diff --git a/Tests/check_png_dos.py b/Tests/check_png_dos.py index 5c78ce122..f133b2695 100644 --- a/Tests/check_png_dos.py +++ b/Tests/check_png_dos.py @@ -1,9 +1,10 @@ +import unittest import zlib from io import BytesIO from PIL import Image, ImageFile, PngImagePlugin -from .helper import PillowTestCase, unittest +from .helper import PillowTestCase TEST_FILE = "Tests/images/png_decompression_dos.png" diff --git a/Tests/test_color_lut.py b/Tests/test_color_lut.py index 301a99a3f..96461ca3a 100644 --- a/Tests/test_color_lut.py +++ b/Tests/test_color_lut.py @@ -1,8 +1,9 @@ +import unittest from array import array from PIL import Image, ImageFilter -from .helper import PillowTestCase, unittest +from .helper import PillowTestCase try: import numpy diff --git a/Tests/test_core_resources.py b/Tests/test_core_resources.py index 8d5fa5479..ac2970de2 100644 --- a/Tests/test_core_resources.py +++ b/Tests/test_core_resources.py @@ -1,8 +1,9 @@ import sys +import unittest from PIL import Image -from .helper import PillowTestCase, is_pypy, unittest +from .helper import PillowTestCase, is_pypy class TestCoreStats(PillowTestCase): diff --git a/Tests/test_features.py b/Tests/test_features.py index 96030bf56..eb51407a1 100644 --- a/Tests/test_features.py +++ b/Tests/test_features.py @@ -1,8 +1,9 @@ import io +import unittest from PIL import features -from .helper import PillowTestCase, unittest +from .helper import PillowTestCase try: from PIL import _webp diff --git a/Tests/test_file_eps.py b/Tests/test_file_eps.py index 82c510b18..0ab14e4eb 100644 --- a/Tests/test_file_eps.py +++ b/Tests/test_file_eps.py @@ -1,8 +1,9 @@ import io +import unittest from PIL import EpsImagePlugin, Image -from .helper import PillowTestCase, hopper, unittest +from .helper import PillowTestCase, hopper HAS_GHOSTSCRIPT = EpsImagePlugin.has_ghostscript() diff --git a/Tests/test_file_fpx.py b/Tests/test_file_fpx.py index 68412c8ca..7c985be30 100644 --- a/Tests/test_file_fpx.py +++ b/Tests/test_file_fpx.py @@ -1,4 +1,6 @@ -from .helper import PillowTestCase, unittest +import unittest + +from .helper import PillowTestCase try: from PIL import FpxImagePlugin diff --git a/Tests/test_file_gif.py b/Tests/test_file_gif.py index 3ae960075..bed25c890 100644 --- a/Tests/test_file_gif.py +++ b/Tests/test_file_gif.py @@ -1,8 +1,9 @@ +import unittest from io import BytesIO from PIL import GifImagePlugin, Image, ImageDraw, ImagePalette -from .helper import PillowTestCase, hopper, is_pypy, netpbm_available, unittest +from .helper import PillowTestCase, hopper, is_pypy, netpbm_available try: from PIL import _webp diff --git a/Tests/test_file_icns.py b/Tests/test_file_icns.py index 4d7f95ec3..e258601ce 100644 --- a/Tests/test_file_icns.py +++ b/Tests/test_file_icns.py @@ -1,9 +1,10 @@ import io import sys +import unittest from PIL import IcnsImagePlugin, Image -from .helper import PillowTestCase, unittest +from .helper import PillowTestCase # sample icon file TEST_FILE = "Tests/images/pillow.icns" diff --git a/Tests/test_file_mic.py b/Tests/test_file_mic.py index f9ea7712c..00f42fa4a 100644 --- a/Tests/test_file_mic.py +++ b/Tests/test_file_mic.py @@ -1,6 +1,8 @@ +import unittest + from PIL import Image, ImagePalette, features -from .helper import PillowTestCase, hopper, unittest +from .helper import PillowTestCase, hopper try: from PIL import MicImagePlugin diff --git a/Tests/test_file_msp.py b/Tests/test_file_msp.py index 5d512047b..d717ade16 100644 --- a/Tests/test_file_msp.py +++ b/Tests/test_file_msp.py @@ -1,8 +1,9 @@ import os +import unittest from PIL import Image, MspImagePlugin -from .helper import PillowTestCase, hopper, unittest +from .helper import PillowTestCase, hopper TEST_FILE = "Tests/images/hopper.msp" EXTRA_DIR = "Tests/images/picins" diff --git a/Tests/test_file_png.py b/Tests/test_file_png.py index 24279732d..c9c337e64 100644 --- a/Tests/test_file_png.py +++ b/Tests/test_file_png.py @@ -1,9 +1,10 @@ +import unittest import zlib from io import BytesIO from PIL import Image, ImageFile, PngImagePlugin -from .helper import PillowLeakTestCase, PillowTestCase, hopper, is_win32, unittest +from .helper import PillowLeakTestCase, PillowTestCase, hopper, is_win32 try: from PIL import _webp diff --git a/Tests/test_file_sun.py b/Tests/test_file_sun.py index 84d59e0c7..5fc171054 100644 --- a/Tests/test_file_sun.py +++ b/Tests/test_file_sun.py @@ -1,8 +1,9 @@ import os +import unittest from PIL import Image, SunImagePlugin -from .helper import PillowTestCase, hopper, unittest +from .helper import PillowTestCase, hopper EXTRA_DIR = "Tests/images/sunraster" diff --git a/Tests/test_file_tiff.py b/Tests/test_file_tiff.py index dec3f5488..a4346a7b5 100644 --- a/Tests/test_file_tiff.py +++ b/Tests/test_file_tiff.py @@ -1,12 +1,13 @@ import logging import os +import unittest from io import BytesIO import pytest from PIL import Image, TiffImagePlugin from PIL.TiffImagePlugin import RESOLUTION_UNIT, X_RESOLUTION, Y_RESOLUTION -from .helper import PillowTestCase, hopper, is_pypy, is_win32, unittest +from .helper import PillowTestCase, hopper, is_pypy, is_win32 logger = logging.getLogger(__name__) diff --git a/Tests/test_file_webp.py b/Tests/test_file_webp.py index 35ce3dba7..685f876e2 100644 --- a/Tests/test_file_webp.py +++ b/Tests/test_file_webp.py @@ -1,6 +1,8 @@ +import unittest + from PIL import Image, WebPImagePlugin -from .helper import PillowTestCase, hopper, unittest +from .helper import PillowTestCase, hopper try: from PIL import _webp diff --git a/Tests/test_file_webp_alpha.py b/Tests/test_file_webp_alpha.py index 9693f691f..e3c2b98b9 100644 --- a/Tests/test_file_webp_alpha.py +++ b/Tests/test_file_webp_alpha.py @@ -1,6 +1,8 @@ +import unittest + from PIL import Image -from .helper import PillowTestCase, hopper, unittest +from .helper import PillowTestCase, hopper try: from PIL import _webp diff --git a/Tests/test_font_leaks.py b/Tests/test_font_leaks.py index 06085392d..393ddb0fb 100644 --- a/Tests/test_font_leaks.py +++ b/Tests/test_font_leaks.py @@ -1,6 +1,8 @@ +import unittest + from PIL import Image, ImageDraw, ImageFont, features -from .helper import PillowLeakTestCase, is_win32, unittest +from .helper import PillowLeakTestCase, is_win32 @unittest.skipIf(is_win32(), "requires Unix or macOS") diff --git a/Tests/test_image.py b/Tests/test_image.py index 74cac4914..15cf435b0 100644 --- a/Tests/test_image.py +++ b/Tests/test_image.py @@ -1,10 +1,11 @@ import os import shutil import tempfile +import unittest from PIL import Image, UnidentifiedImageError -from .helper import PillowTestCase, hopper, is_win32, unittest +from .helper import PillowTestCase, hopper, is_win32 class TestImage(PillowTestCase): diff --git a/Tests/test_image_access.py b/Tests/test_image_access.py index cd65e8dd0..57bac753d 100644 --- a/Tests/test_image_access.py +++ b/Tests/test_image_access.py @@ -2,11 +2,12 @@ import ctypes import os import subprocess import sys +import unittest from distutils import ccompiler, sysconfig from PIL import Image -from .helper import PillowTestCase, hopper, is_win32, on_ci, unittest +from .helper import PillowTestCase, hopper, is_win32, on_ci # CFFI imports pycparser which doesn't support PYTHONOPTIMIZE=2 # https://github.com/eliben/pycparser/pull/198#issuecomment-317001670 diff --git a/Tests/test_image_resample.py b/Tests/test_image_resample.py index 26c65d544..00c8fa5f2 100644 --- a/Tests/test_image_resample.py +++ b/Tests/test_image_resample.py @@ -1,8 +1,9 @@ +import unittest from contextlib import contextmanager from PIL import Image, ImageDraw -from .helper import PillowTestCase, hopper, unittest +from .helper import PillowTestCase, hopper class TestImagingResampleVulnerability(PillowTestCase): diff --git a/Tests/test_imagedraw.py b/Tests/test_imagedraw.py index 75e658d65..50a774df2 100644 --- a/Tests/test_imagedraw.py +++ b/Tests/test_imagedraw.py @@ -1,8 +1,9 @@ import os.path +import unittest from PIL import Image, ImageColor, ImageDraw, ImageFont, features -from .helper import PillowTestCase, hopper, unittest +from .helper import PillowTestCase, hopper BLACK = (0, 0, 0) WHITE = (255, 255, 255) diff --git a/Tests/test_imagedraw2.py b/Tests/test_imagedraw2.py index 9ce472dd0..577727664 100644 --- a/Tests/test_imagedraw2.py +++ b/Tests/test_imagedraw2.py @@ -1,8 +1,9 @@ import os.path +import unittest from PIL import Image, ImageDraw2, features -from .helper import PillowTestCase, hopper, unittest +from .helper import PillowTestCase, hopper BLACK = (0, 0, 0) WHITE = (255, 255, 255) diff --git a/Tests/test_imagefile.py b/Tests/test_imagefile.py index dad408e93..228bbf929 100644 --- a/Tests/test_imagefile.py +++ b/Tests/test_imagefile.py @@ -1,8 +1,9 @@ +import unittest from io import BytesIO from PIL import EpsImagePlugin, Image, ImageFile -from .helper import PillowTestCase, fromstring, hopper, tostring, unittest +from .helper import PillowTestCase, fromstring, hopper, tostring try: from PIL import _webp diff --git a/Tests/test_imagefont.py b/Tests/test_imagefont.py index b7cfefacf..e34756a79 100644 --- a/Tests/test_imagefont.py +++ b/Tests/test_imagefont.py @@ -4,11 +4,12 @@ import os import re import shutil import sys +import unittest from io import BytesIO from PIL import Image, ImageDraw, ImageFont, features -from .helper import PillowTestCase, is_pypy, is_win32, unittest +from .helper import PillowTestCase, is_pypy, is_win32 FONT_PATH = "Tests/fonts/FreeMono.ttf" FONT_SIZE = 20 diff --git a/Tests/test_imagefont_bitmap.py b/Tests/test_imagefont_bitmap.py index b7be8f723..eed0c70b6 100644 --- a/Tests/test_imagefont_bitmap.py +++ b/Tests/test_imagefont_bitmap.py @@ -1,6 +1,8 @@ +import unittest + from PIL import Image, ImageDraw, ImageFont -from .helper import PillowTestCase, unittest +from .helper import PillowTestCase image_font_installed = True try: diff --git a/Tests/test_imagefontctl.py b/Tests/test_imagefontctl.py index 84bf1c58f..71efb897f 100644 --- a/Tests/test_imagefontctl.py +++ b/Tests/test_imagefontctl.py @@ -1,6 +1,8 @@ +import unittest + from PIL import Image, ImageDraw, ImageFont, features -from .helper import PillowTestCase, unittest +from .helper import PillowTestCase FONT_SIZE = 20 FONT_PATH = "Tests/fonts/DejaVuSans.ttf" diff --git a/Tests/test_imageshow.py b/Tests/test_imageshow.py index 225c8a6a9..858714e57 100644 --- a/Tests/test_imageshow.py +++ b/Tests/test_imageshow.py @@ -1,6 +1,8 @@ +import unittest + from PIL import Image, ImageShow -from .helper import PillowTestCase, hopper, is_win32, on_ci, on_github_actions, unittest +from .helper import PillowTestCase, hopper, is_win32, on_ci, on_github_actions class TestImageShow(PillowTestCase): diff --git a/Tests/test_imagetk.py b/Tests/test_imagetk.py index 808ebd392..048efd639 100644 --- a/Tests/test_imagetk.py +++ b/Tests/test_imagetk.py @@ -1,6 +1,8 @@ +import unittest + from PIL import Image -from .helper import PillowTestCase, hopper, unittest +from .helper import PillowTestCase, hopper try: from PIL import ImageTk diff --git a/Tests/test_imagewin.py b/Tests/test_imagewin.py index 2b2034187..1cd8c674e 100644 --- a/Tests/test_imagewin.py +++ b/Tests/test_imagewin.py @@ -1,6 +1,8 @@ +import unittest + from PIL import ImageWin -from .helper import PillowTestCase, hopper, is_win32, unittest +from .helper import PillowTestCase, hopper, is_win32 class TestImageWin(PillowTestCase): diff --git a/Tests/test_lib_image.py b/Tests/test_lib_image.py index 68e72bc4e..240783960 100644 --- a/Tests/test_lib_image.py +++ b/Tests/test_lib_image.py @@ -1,6 +1,8 @@ +import unittest + from PIL import Image -from .helper import PillowTestCase, unittest +from .helper import PillowTestCase class TestLibImage(PillowTestCase): diff --git a/Tests/test_locale.py b/Tests/test_locale.py index 69bce68cf..0b1f330ac 100644 --- a/Tests/test_locale.py +++ b/Tests/test_locale.py @@ -1,8 +1,9 @@ import locale +import unittest from PIL import Image -from .helper import PillowTestCase, unittest +from .helper import PillowTestCase # ref https://github.com/python-pillow/Pillow/issues/272 # on windows, in polish locale: diff --git a/Tests/test_main.py b/Tests/test_main.py index 15d4f7819..4f52149b6 100644 --- a/Tests/test_main.py +++ b/Tests/test_main.py @@ -1,9 +1,10 @@ import os import subprocess import sys +import unittest from unittest import TestCase -from .helper import is_pypy, is_win32, on_github_actions, unittest +from .helper import is_pypy, is_win32, on_github_actions class TestMain(TestCase): diff --git a/Tests/test_map.py b/Tests/test_map.py index 25e24e2fb..bdc3a7e2c 100644 --- a/Tests/test_map.py +++ b/Tests/test_map.py @@ -1,8 +1,9 @@ import sys +import unittest from PIL import Image -from .helper import PillowTestCase, is_win32, unittest +from .helper import PillowTestCase, is_win32 try: import numpy diff --git a/Tests/test_numpy.py b/Tests/test_numpy.py index 358180f3f..7de5c3bbd 100644 --- a/Tests/test_numpy.py +++ b/Tests/test_numpy.py @@ -1,6 +1,8 @@ +import unittest + from PIL import Image -from .helper import PillowTestCase, hopper, unittest +from .helper import PillowTestCase, hopper try: import numpy diff --git a/Tests/test_pyroma.py b/Tests/test_pyroma.py index 3455a502b..accad5451 100644 --- a/Tests/test_pyroma.py +++ b/Tests/test_pyroma.py @@ -1,6 +1,8 @@ +import unittest + from PIL import __version__ -from .helper import PillowTestCase, unittest +from .helper import PillowTestCase try: import pyroma diff --git a/Tests/test_util.py b/Tests/test_util.py index cb2101e1d..6a111b5b9 100644 --- a/Tests/test_util.py +++ b/Tests/test_util.py @@ -1,6 +1,8 @@ +import unittest + from PIL import _util -from .helper import PillowTestCase, unittest +from .helper import PillowTestCase class TestUtil(PillowTestCase): diff --git a/Tests/test_webp_leaks.py b/Tests/test_webp_leaks.py index 93a6c2db0..713fc161e 100644 --- a/Tests/test_webp_leaks.py +++ b/Tests/test_webp_leaks.py @@ -1,8 +1,9 @@ +import unittest from io import BytesIO from PIL import Image, features -from .helper import PillowLeakTestCase, unittest +from .helper import PillowLeakTestCase test_file = "Tests/images/hopper.webp" From 2f5eacf0f9f03aa2f12f20301bb57e949c8db72f Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Thu, 21 Nov 2019 18:55:58 +1100 Subject: [PATCH 44/59] Updated CHANGES.rst [ci skip] --- CHANGES.rst | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/CHANGES.rst b/CHANGES.rst index 9952c1166..3fb4c2e0c 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -5,6 +5,12 @@ Changelog (Pillow) 7.0.0 (unreleased) ------------------ +- Drop support for EOL Python 2.7 #4109 + [hugovk, radarhere, jdufresne] + +- Added UnidentifiedImageError #4182 + [radarhere, hugovk] + - Remove deprecated __version__ from plugins #4197 [hugovk, radarhere] From e77398096100b458b439c784ed1ec68e3aac137c Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Fri, 22 Nov 2019 10:12:52 +1100 Subject: [PATCH 45/59] Updated URL [ci skip] --- src/PIL/GbrImagePlugin.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/PIL/GbrImagePlugin.py b/src/PIL/GbrImagePlugin.py index 2de56aadf..292de435c 100644 --- a/src/PIL/GbrImagePlugin.py +++ b/src/PIL/GbrImagePlugin.py @@ -14,7 +14,7 @@ # See the README file for information on usage and redistribution. # # -# See https://github.com/GNOME/gimp/blob/master/devel-docs/gbr.txt for +# See https://github.com/GNOME/gimp/blob/mainline/devel-docs/gbr.txt for # format documentation. # # This code Interprets version 1 and 2 .gbr files. From 39a8108e347714ad70710e8447d37995e707a7d7 Mon Sep 17 00:00:00 2001 From: Hugo van Kemenade Date: Fri, 22 Nov 2019 07:39:38 +0200 Subject: [PATCH 46/59] Docs: remove 2.7 from CI targets Removed in https://github.com/python-pillow/Pillow/pull/4133 for https://github.com/python-pillow/Pillow/issues/3642. Also update macOS to reflect GitHub Actions. --- docs/installation.rst | 64 +++++++++++++++++++++---------------------- 1 file changed, 31 insertions(+), 33 deletions(-) diff --git a/docs/installation.rst b/docs/installation.rst index 5ace862f0..56689e0e5 100644 --- a/docs/installation.rst +++ b/docs/installation.rst @@ -381,40 +381,38 @@ Continuous Integration Targets These platforms are built and tested for every change. -+----------------------------------+-------------------------------+-----------------------+ -|**Operating system** |**Tested Python versions** |**Tested Architecture**| -+----------------------------------+-------------------------------+-----------------------+ -| Alpine | 2.7, 3.7 |x86-64 | -+----------------------------------+-------------------------------+-----------------------+ -| Arch | 2.7, 3.7 |x86-64 | -+----------------------------------+-------------------------------+-----------------------+ -| Amazon Linux 1 | 2.7, 3.6 |x86-64 | -+----------------------------------+-------------------------------+-----------------------+ -| Amazon Linux 2 | 2.7, 3.6 |x86-64 | -+----------------------------------+-------------------------------+-----------------------+ -| CentOS 6 | 2.7, 3.6 |x86-64 | -+----------------------------------+-------------------------------+-----------------------+ -| CentOS 7 | 2.7, 3.6 |x86-64 | -+----------------------------------+-------------------------------+-----------------------+ -| Debian 9 Stretch | 2.7, 3.5 |x86 | -+----------------------------------+-------------------------------+-----------------------+ -| Debian 10 Buster | 2.7, 3.7 |x86 | -+----------------------------------+-------------------------------+-----------------------+ -| Fedora 30 | 2.7, 3.7 |x86-64 | -+----------------------------------+-------------------------------+-----------------------+ -| Fedora 31 | 3.7 |x86-64 | -+----------------------------------+-------------------------------+-----------------------+ -| macOS 10.13 High Sierra* | 2.7, 3.5, 3.6, 3.7, 3.8 |x86-64 | -+----------------------------------+-------------------------------+-----------------------+ -| Ubuntu Linux 16.04 LTS | 2.7, 3.5, 3.6, 3.7, 3.8, |x86-64 | -| | PyPy, PyPy3 | | -+----------------------------------+-------------------------------+-----------------------+ -| Windows Server 2012 R2 | 2.7, 3.5, 3.6, 3.7, 3.8 |x86, x86-64 | -| +-------------------------------+-----------------------+ -| | PyPy, 3.7/MinGW |x86 | -+----------------------------------+-------------------------------+-----------------------+ ++----------------------------------+--------------------------+-----------------------+ +|**Operating system** |**Tested Python versions**|**Tested architecture**| ++----------------------------------+--------------------------+-----------------------+ +| Alpine | 3.7 |x86-64 | ++----------------------------------+--------------------------+-----------------------+ +| Arch | 3.7 |x86-64 | ++----------------------------------+--------------------------+-----------------------+ +| Amazon Linux 1 | 3.6 |x86-64 | ++----------------------------------+--------------------------+-----------------------+ +| Amazon Linux 2 | 3.6 |x86-64 | ++----------------------------------+--------------------------+-----------------------+ +| CentOS 6 | 3.6 |x86-64 | ++----------------------------------+--------------------------+-----------------------+ +| CentOS 7 | 3.6 |x86-64 | ++----------------------------------+--------------------------+-----------------------+ +| Debian 9 Stretch | 3.5 |x86 | ++----------------------------------+--------------------------+-----------------------+ +| Debian 10 Buster | 3.7 |x86 | ++----------------------------------+--------------------------+-----------------------+ +| Fedora 30 | 3.7 |x86-64 | ++----------------------------------+--------------------------+-----------------------+ +| Fedora 31 | 3.7 |x86-64 | ++----------------------------------+--------------------------+-----------------------+ +| macOS 10.14 Catalina | 3.5, 3.6, 3.7, 3.8 |x86-64 | ++----------------------------------+--------------------------+-----------------------+ +| Ubuntu Linux 16.04 LTS | 3.5, 3.6, 3.7, 3.8, PyPy3|x86-64 | ++----------------------------------+--------------------------+-----------------------+ +| Windows Server 2012 R2 | 3.5, 3.6, 3.7, 3.8 |x86, x86-64 | +| +--------------------------+-----------------------+ +| | PyPy3, 3.7/MinGW |x86 | ++----------------------------------+--------------------------+-----------------------+ -\* macOS CI is not run for every commit, but is run for every release. Other Platforms ^^^^^^^^^^^^^^^ From 0253bd59234a46797479908d38d11cc60b863789 Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Fri, 22 Nov 2019 19:44:06 +1100 Subject: [PATCH 47/59] Updated Tk Tcl to 8.6.10 --- winbuild/config.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/winbuild/config.py b/winbuild/config.py index 7f7fc7430..fad263755 100644 --- a/winbuild/config.py +++ b/winbuild/config.py @@ -71,15 +71,15 @@ libs = { "version": "8.5.19", }, "tcl-8.6": { - "url": SF_MIRROR + "/project/tcl/Tcl/8.6.9/tcl869-src.zip", - "filename": "tcl869-src.zip", + "url": SF_MIRROR + "/project/tcl/Tcl/8.6.10/tcl8610-src.zip", + "filename": "tcl8610-src.zip", "dir": "", }, "tk-8.6": { - "url": SF_MIRROR + "/project/tcl/Tcl/8.6.9/tk869-src.zip", - "filename": "tk869-src.zip", + "url": SF_MIRROR + "/project/tcl/Tcl/8.6.10/tk8610-src.zip", + "filename": "tk8610-src.zip", "dir": "", - "version": "8.6.9", + "version": "8.6.10", }, "webp": { "url": "http://downloads.webmproject.org/releases/webp/libwebp-1.0.3.tar.gz", From 0cce22c68dd59befb797583dec485dd12a553621 Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Sat, 23 Nov 2019 06:24:55 +1100 Subject: [PATCH 48/59] Updated CHANGES.rst [ci skip] --- CHANGES.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGES.rst b/CHANGES.rst index 3fb4c2e0c..d3922f328 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -23,7 +23,7 @@ Changelog (Pillow) - Corrected DdsImagePlugin setting info gamma #4171 [radarhere] -- Depends: Update libtiff to 4.1.0 #4195 +- Depends: Update libtiff to 4.1.0 #4195, Tk Tcl to 8.6.10 #4229 [radarhere] - Improve handling of file resources #3577 From c0048ad7de2b74e7b27a535ddff9c64484d859b2 Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Tue, 26 Nov 2019 07:03:23 +1100 Subject: [PATCH 49/59] Use context managers --- Tests/check_fli_overflow.py | 4 +- Tests/check_libtiff_segfault.py | 4 +- Tests/test_file_blp.py | 6 +- Tests/test_file_bmp.py | 82 +++--- Tests/test_file_cur.py | 15 +- Tests/test_file_dds.py | 95 +++--- Tests/test_file_eps.py | 27 +- Tests/test_file_ftex.py | 13 +- Tests/test_file_gif.py | 19 +- Tests/test_file_icns.py | 31 +- Tests/test_file_ico.py | 24 +- Tests/test_file_jpeg.py | 331 ++++++++++----------- Tests/test_file_jpeg2k.py | 116 ++++---- Tests/test_file_libtiff.py | 486 +++++++++++++++---------------- Tests/test_file_libtiff_small.py | 21 +- Tests/test_file_mcidas.py | 16 +- Tests/test_file_msp.py | 22 +- Tests/test_file_pcd.py | 4 +- Tests/test_file_pcx.py | 24 +- Tests/test_file_pdf.py | 4 +- Tests/test_file_pixar.py | 16 +- Tests/test_file_png.py | 270 ++++++++--------- Tests/test_file_ppm.py | 50 ++-- Tests/test_file_sgi.py | 50 ++-- Tests/test_file_spider.py | 12 +- Tests/test_file_sun.py | 14 +- Tests/test_file_tar.py | 10 +- Tests/test_file_tga.py | 156 +++++----- Tests/test_file_tiff.py | 167 +++++------ Tests/test_file_tiff_metadata.py | 38 ++- Tests/test_file_webp.py | 99 +++---- Tests/test_file_webp_alpha.py | 80 +++-- Tests/test_file_webp_animated.py | 153 +++++----- Tests/test_file_webp_lossless.py | 16 +- Tests/test_file_webp_metadata.py | 56 ++-- Tests/test_file_wmf.py | 12 +- Tests/test_file_xbm.py | 9 +- Tests/test_file_xpm.py | 14 +- Tests/test_file_xvthumb.py | 12 +- Tests/test_format_lab.py | 28 +- Tests/test_image.py | 38 +-- Tests/test_image_convert.py | 26 +- Tests/test_image_crop.py | 8 +- Tests/test_image_filter.py | 76 ++--- Tests/test_image_getextrema.py | 6 +- Tests/test_image_quantize.py | 9 +- Tests/test_image_resample.py | 28 +- Tests/test_image_rotate.py | 58 ++-- Tests/test_image_split.py | 4 +- Tests/test_image_transform.py | 8 +- Tests/test_imagechops.py | 142 ++++----- Tests/test_imagecms.py | 10 +- Tests/test_imagedraw.py | 282 +++++++++--------- Tests/test_imagefile.py | 132 +++++---- Tests/test_imagefont.py | 54 ++-- Tests/test_imagefontctl.py | 70 ++--- Tests/test_imagemorph.py | 4 +- Tests/test_imageops.py | 69 +++-- Tests/test_imagepalette.py | 5 +- Tests/test_imagetk.py | 22 +- Tests/test_numpy.py | 12 +- Tests/test_pickle.py | 42 +-- Tests/test_psdraw.py | 4 +- Tests/test_qt_image_toqimage.py | 4 +- Tests/test_shell_injection.py | 4 +- 65 files changed, 1837 insertions(+), 1886 deletions(-) diff --git a/Tests/check_fli_overflow.py b/Tests/check_fli_overflow.py index fa6037c2e..206a86007 100644 --- a/Tests/check_fli_overflow.py +++ b/Tests/check_fli_overflow.py @@ -11,8 +11,8 @@ class TestFliOverflow(PillowTestCase): def test_fli_overflow(self): # this should not crash with a malloc error or access violation - im = Image.open(TEST_FILE) - im.load() + with Image.open(TEST_FILE) as im: + im.load() if __name__ == "__main__": diff --git a/Tests/check_libtiff_segfault.py b/Tests/check_libtiff_segfault.py index a0263f725..b272c601c 100644 --- a/Tests/check_libtiff_segfault.py +++ b/Tests/check_libtiff_segfault.py @@ -14,8 +14,8 @@ class TestLibtiffSegfault(PillowTestCase): """ with self.assertRaises(IOError): - im = Image.open(TEST_FILE) - im.load() + with Image.open(TEST_FILE) as im: + im.load() if __name__ == "__main__": diff --git a/Tests/test_file_blp.py b/Tests/test_file_blp.py index 8dbd82986..1e8dff184 100644 --- a/Tests/test_file_blp.py +++ b/Tests/test_file_blp.py @@ -15,6 +15,6 @@ class TestFileBlp(PillowTestCase): self.assert_image_equal(im, target) def test_load_blp2_dxt1a(self): - im = Image.open("Tests/images/blp/blp2_dxt1a.blp") - target = Image.open("Tests/images/blp/blp2_dxt1a.png") - self.assert_image_equal(im, target) + with Image.open("Tests/images/blp/blp2_dxt1a.blp") as im: + with Image.open("Tests/images/blp/blp2_dxt1a.png") as target: + self.assert_image_equal(im, target) diff --git a/Tests/test_file_bmp.py b/Tests/test_file_bmp.py index e7f2c9315..76cd98aba 100644 --- a/Tests/test_file_bmp.py +++ b/Tests/test_file_bmp.py @@ -11,12 +11,12 @@ class TestFileBmp(PillowTestCase): im.save(outfile, "BMP") - reloaded = Image.open(outfile) - reloaded.load() - self.assertEqual(im.mode, reloaded.mode) - self.assertEqual(im.size, reloaded.size) - self.assertEqual(reloaded.format, "BMP") - self.assertEqual(reloaded.get_format_mimetype(), "image/bmp") + with Image.open(outfile) as reloaded: + reloaded.load() + self.assertEqual(im.mode, reloaded.mode) + self.assertEqual(im.size, reloaded.size) + self.assertEqual(reloaded.format, "BMP") + self.assertEqual(reloaded.get_format_mimetype(), "image/bmp") def test_sanity(self): self.roundtrip(hopper()) @@ -36,11 +36,10 @@ class TestFileBmp(PillowTestCase): im.save(output, "BMP") output.seek(0) - reloaded = Image.open(output) - - self.assertEqual(im.mode, reloaded.mode) - self.assertEqual(im.size, reloaded.size) - self.assertEqual(reloaded.format, "BMP") + with Image.open(output) as reloaded: + self.assertEqual(im.mode, reloaded.mode) + self.assertEqual(im.size, reloaded.size) + self.assertEqual(reloaded.format, "BMP") def test_dpi(self): dpi = (72, 72) @@ -57,17 +56,17 @@ class TestFileBmp(PillowTestCase): # Test for #1301 # Arrange outfile = self.tempfile("temp.jpg") - im = Image.open("Tests/images/hopper.bmp") + with Image.open("Tests/images/hopper.bmp") as im: - # Act - im.save(outfile, "JPEG", dpi=im.info["dpi"]) + # Act + im.save(outfile, "JPEG", dpi=im.info["dpi"]) - # Assert - reloaded = Image.open(outfile) - reloaded.load() - self.assertEqual(im.info["dpi"], reloaded.info["dpi"]) - self.assertEqual(im.size, reloaded.size) - self.assertEqual(reloaded.format, "JPEG") + # Assert + with Image.open(outfile) as reloaded: + reloaded.load() + self.assertEqual(im.info["dpi"], reloaded.info["dpi"]) + self.assertEqual(im.size, reloaded.size) + self.assertEqual(reloaded.format, "JPEG") def test_load_dpi_rounding(self): # Round up @@ -80,11 +79,10 @@ class TestFileBmp(PillowTestCase): def test_save_dpi_rounding(self): outfile = self.tempfile("temp.bmp") - im = Image.open("Tests/images/hopper.bmp") - - im.save(outfile, dpi=(72.2, 72.2)) - with Image.open(outfile) as reloaded: - self.assertEqual(reloaded.info["dpi"], (72, 72)) + with Image.open("Tests/images/hopper.bmp") as im: + im.save(outfile, dpi=(72.2, 72.2)) + with Image.open(outfile) as reloaded: + self.assertEqual(reloaded.info["dpi"], (72, 72)) im.save(outfile, dpi=(72.8, 72.8)) with Image.open(outfile) as reloaded: @@ -92,32 +90,32 @@ class TestFileBmp(PillowTestCase): def test_load_dib(self): # test for #1293, Imagegrab returning Unsupported Bitfields Format - im = Image.open("Tests/images/clipboard.dib") - self.assertEqual(im.format, "DIB") - self.assertEqual(im.get_format_mimetype(), "image/bmp") + with Image.open("Tests/images/clipboard.dib") as im: + self.assertEqual(im.format, "DIB") + self.assertEqual(im.get_format_mimetype(), "image/bmp") - target = Image.open("Tests/images/clipboard_target.png") - self.assert_image_equal(im, target) + with Image.open("Tests/images/clipboard_target.png") as target: + self.assert_image_equal(im, target) def test_save_dib(self): outfile = self.tempfile("temp.dib") - im = Image.open("Tests/images/clipboard.dib") - im.save(outfile) + with Image.open("Tests/images/clipboard.dib") as im: + im.save(outfile) - reloaded = Image.open(outfile) - self.assertEqual(reloaded.format, "DIB") - self.assertEqual(reloaded.get_format_mimetype(), "image/bmp") - self.assert_image_equal(im, reloaded) + with Image.open(outfile) as reloaded: + self.assertEqual(reloaded.format, "DIB") + self.assertEqual(reloaded.get_format_mimetype(), "image/bmp") + self.assert_image_equal(im, reloaded) def test_rgba_bitfields(self): # This test image has been manually hexedited # to change the bitfield compression in the header from XBGR to RGBA - im = Image.open("Tests/images/rgb32bf-rgba.bmp") + with Image.open("Tests/images/rgb32bf-rgba.bmp") as im: - # So before the comparing the image, swap the channels - b, g, r = im.split()[1:] - im = Image.merge("RGB", (r, g, b)) + # So before the comparing the image, swap the channels + b, g, r = im.split()[1:] + im = Image.merge("RGB", (r, g, b)) - target = Image.open("Tests/images/bmp/q/rgb32bf-xbgr.bmp") - self.assert_image_equal(im, target) + with Image.open("Tests/images/bmp/q/rgb32bf-xbgr.bmp") as target: + self.assert_image_equal(im, target) diff --git a/Tests/test_file_cur.py b/Tests/test_file_cur.py index 0b2f7a98c..246404bab 100644 --- a/Tests/test_file_cur.py +++ b/Tests/test_file_cur.py @@ -7,14 +7,13 @@ TEST_FILE = "Tests/images/deerstalker.cur" class TestFileCur(PillowTestCase): def test_sanity(self): - im = Image.open(TEST_FILE) - - self.assertEqual(im.size, (32, 32)) - self.assertIsInstance(im, CurImagePlugin.CurImageFile) - # Check some pixel colors to ensure image is loaded properly - self.assertEqual(im.getpixel((10, 1)), (0, 0, 0, 0)) - self.assertEqual(im.getpixel((11, 1)), (253, 254, 254, 1)) - self.assertEqual(im.getpixel((16, 16)), (84, 87, 86, 255)) + with Image.open(TEST_FILE) as im: + self.assertEqual(im.size, (32, 32)) + self.assertIsInstance(im, CurImagePlugin.CurImageFile) + # Check some pixel colors to ensure image is loaded properly + self.assertEqual(im.getpixel((10, 1)), (0, 0, 0, 0)) + 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): invalid_file = "Tests/images/flower.jpg" diff --git a/Tests/test_file_dds.py b/Tests/test_file_dds.py index 8ef90e86e..053d72568 100644 --- a/Tests/test_file_dds.py +++ b/Tests/test_file_dds.py @@ -17,73 +17,71 @@ class TestFileDds(PillowTestCase): def test_sanity_dxt1(self): """Check DXT1 images can be opened""" - target = Image.open(TEST_FILE_DXT1.replace(".dds", ".png")) + with Image.open(TEST_FILE_DXT1.replace(".dds", ".png")) as target: + target = target.convert("RGBA") + with Image.open(TEST_FILE_DXT1) as im: + im.load() - im = Image.open(TEST_FILE_DXT1) - im.load() + self.assertEqual(im.format, "DDS") + self.assertEqual(im.mode, "RGBA") + self.assertEqual(im.size, (256, 256)) - self.assertEqual(im.format, "DDS") - self.assertEqual(im.mode, "RGBA") - self.assertEqual(im.size, (256, 256)) - - self.assert_image_equal(target.convert("RGBA"), im) + self.assert_image_equal(im, target) def test_sanity_dxt5(self): """Check DXT5 images can be opened""" - target = Image.open(TEST_FILE_DXT5.replace(".dds", ".png")) - - im = Image.open(TEST_FILE_DXT5) - im.load() + with Image.open(TEST_FILE_DXT5) as im: + im.load() self.assertEqual(im.format, "DDS") self.assertEqual(im.mode, "RGBA") self.assertEqual(im.size, (256, 256)) - self.assert_image_equal(target, im) + with Image.open(TEST_FILE_DXT5.replace(".dds", ".png")) as target: + self.assert_image_equal(target, im) def test_sanity_dxt3(self): """Check DXT3 images can be opened""" - target = Image.open(TEST_FILE_DXT3.replace(".dds", ".png")) + with Image.open(TEST_FILE_DXT3.replace(".dds", ".png")) as target: + with Image.open(TEST_FILE_DXT3) as im: + im.load() - im = Image.open(TEST_FILE_DXT3) - im.load() + self.assertEqual(im.format, "DDS") + self.assertEqual(im.mode, "RGBA") + self.assertEqual(im.size, (256, 256)) - self.assertEqual(im.format, "DDS") - self.assertEqual(im.mode, "RGBA") - self.assertEqual(im.size, (256, 256)) - - self.assert_image_equal(target, im) + self.assert_image_equal(target, im) def test_dx10_bc7(self): """Check DX10 images can be opened""" - target = Image.open(TEST_FILE_DX10_BC7.replace(".dds", ".png")) + with Image.open(TEST_FILE_DX10_BC7) as im: + im.load() - im = Image.open(TEST_FILE_DX10_BC7) - im.load() + self.assertEqual(im.format, "DDS") + self.assertEqual(im.mode, "RGBA") + self.assertEqual(im.size, (256, 256)) - self.assertEqual(im.format, "DDS") - self.assertEqual(im.mode, "RGBA") - self.assertEqual(im.size, (256, 256)) - - self.assert_image_equal(target, im) + with Image.open(TEST_FILE_DX10_BC7.replace(".dds", ".png")) as target: + self.assert_image_equal(target, im) def test_dx10_bc7_unorm_srgb(self): """Check DX10 unsigned normalized integer images can be opened""" - target = Image.open(TEST_FILE_DX10_BC7_UNORM_SRGB.replace(".dds", ".png")) + with Image.open(TEST_FILE_DX10_BC7_UNORM_SRGB) as im: + im.load() - im = Image.open(TEST_FILE_DX10_BC7_UNORM_SRGB) - im.load() + self.assertEqual(im.format, "DDS") + self.assertEqual(im.mode, "RGBA") + self.assertEqual(im.size, (16, 16)) + self.assertEqual(im.info["gamma"], 1 / 2.2) - self.assertEqual(im.format, "DDS") - self.assertEqual(im.mode, "RGBA") - self.assertEqual(im.size, (16, 16)) - self.assertEqual(im.info["gamma"], 1 / 2.2) - - self.assert_image_equal(target, im) + with Image.open( + TEST_FILE_DX10_BC7_UNORM_SRGB.replace(".dds", ".png") + ) as target: + self.assert_image_equal(target, im) def test_unimplemented_dxgi_format(self): self.assertRaises( @@ -95,16 +93,17 @@ class TestFileDds(PillowTestCase): def test_uncompressed_rgb(self): """Check uncompressed RGB images can be opened""" - target = Image.open(TEST_FILE_UNCOMPRESSED_RGB.replace(".dds", ".png")) + with Image.open(TEST_FILE_UNCOMPRESSED_RGB) as im: + im.load() - im = Image.open(TEST_FILE_UNCOMPRESSED_RGB) - im.load() + self.assertEqual(im.format, "DDS") + self.assertEqual(im.mode, "RGBA") + self.assertEqual(im.size, (800, 600)) - self.assertEqual(im.format, "DDS") - self.assertEqual(im.mode, "RGBA") - self.assertEqual(im.size, (800, 600)) - - self.assert_image_equal(target, im) + with Image.open( + TEST_FILE_UNCOMPRESSED_RGB.replace(".dds", ".png") + ) as target: + self.assert_image_equal(target, im) def test__validate_true(self): """Check valid prefix""" @@ -145,8 +144,8 @@ class TestFileDds(PillowTestCase): img_file = f.read() def short_file(): - im = Image.open(BytesIO(img_file[:-100])) - im.load() + with Image.open(BytesIO(img_file[:-100])) as im: + im.load() self.assertRaises(IOError, short_file) diff --git a/Tests/test_file_eps.py b/Tests/test_file_eps.py index 0ab14e4eb..6fd201d48 100644 --- a/Tests/test_file_eps.py +++ b/Tests/test_file_eps.py @@ -68,8 +68,8 @@ class TestFileEps(PillowTestCase): self.assertEqual(cmyk_image.mode, "RGB") if "jpeg_decoder" in dir(Image.core): - target = Image.open("Tests/images/pil_sample_rgb.jpg") - self.assert_image_similar(cmyk_image, target, 10) + with Image.open("Tests/images/pil_sample_rgb.jpg") as target: + self.assert_image_similar(cmyk_image, target, 10) @unittest.skipUnless(HAS_GHOSTSCRIPT, "Ghostscript not available") def test_showpage(self): @@ -100,12 +100,13 @@ class TestFileEps(PillowTestCase): with open(file1, "rb") as f: img_bytes = io.BytesIO(f.read()) - img = Image.open(img_bytes) - img.load() + with Image.open(img_bytes) as img: + img.load() - image1_scale1_compare = Image.open(file1_compare).convert("RGB") - image1_scale1_compare.load() - self.assert_image_similar(img, image1_scale1_compare, 5) + with Image.open(file1_compare) as image1_scale1_compare: + image1_scale1_compare = image1_scale1_compare.convert("RGB") + image1_scale1_compare.load() + self.assert_image_similar(img, image1_scale1_compare, 5) def test_image_mode_not_supported(self): im = hopper("RGBA") @@ -122,14 +123,16 @@ class TestFileEps(PillowTestCase): # Zero bounding box with Image.open(file1) as image1_scale1: image1_scale1.load() - image1_scale1_compare = Image.open(file1_compare).convert("RGB") + with Image.open(file1_compare) as image1_scale1_compare: + image1_scale1_compare = image1_scale1_compare.convert("RGB") image1_scale1_compare.load() self.assert_image_similar(image1_scale1, image1_scale1_compare, 5) # Non-Zero bounding box with Image.open(file2) as image2_scale1: image2_scale1.load() - image2_scale1_compare = Image.open(file2_compare).convert("RGB") + with Image.open(file2_compare) as image2_scale1_compare: + image2_scale1_compare = image2_scale1_compare.convert("RGB") image2_scale1_compare.load() self.assert_image_similar(image2_scale1, image2_scale1_compare, 10) @@ -143,14 +146,16 @@ class TestFileEps(PillowTestCase): # Zero bounding box with Image.open(file1) as image1_scale2: image1_scale2.load(scale=2) - image1_scale2_compare = Image.open(file1_compare_scale2).convert("RGB") + with Image.open(file1_compare_scale2) as image1_scale2_compare: + image1_scale2_compare = image1_scale2_compare.convert("RGB") image1_scale2_compare.load() self.assert_image_similar(image1_scale2, image1_scale2_compare, 5) # Non-Zero bounding box with Image.open(file2) as image2_scale2: image2_scale2.load(scale=2) - image2_scale2_compare = Image.open(file2_compare_scale2).convert("RGB") + with Image.open(file2_compare_scale2) as image2_scale2_compare: + image2_scale2_compare = image2_scale2_compare.convert("RGB") image2_scale2_compare.load() self.assert_image_similar(image2_scale2, image2_scale2_compare, 10) diff --git a/Tests/test_file_ftex.py b/Tests/test_file_ftex.py index 7d30042ca..335a96e83 100644 --- a/Tests/test_file_ftex.py +++ b/Tests/test_file_ftex.py @@ -5,12 +5,11 @@ from .helper import PillowTestCase class TestFileFtex(PillowTestCase): def test_load_raw(self): - im = Image.open("Tests/images/ftex_uncompressed.ftu") - target = Image.open("Tests/images/ftex_uncompressed.png") - - self.assert_image_equal(im, target) + with Image.open("Tests/images/ftex_uncompressed.ftu") as im: + with Image.open("Tests/images/ftex_uncompressed.png") as target: + self.assert_image_equal(im, target) def test_load_dxt1(self): - im = Image.open("Tests/images/ftex_dxt1.ftc") - target = Image.open("Tests/images/ftex_dxt1.png") - self.assert_image_similar(im, target.convert("RGBA"), 15) + with Image.open("Tests/images/ftex_dxt1.ftc") as im: + with Image.open("Tests/images/ftex_dxt1.png") as target: + self.assert_image_similar(im, target.convert("RGBA"), 15) diff --git a/Tests/test_file_gif.py b/Tests/test_file_gif.py index bed25c890..b17459a8b 100644 --- a/Tests/test_file_gif.py +++ b/Tests/test_file_gif.py @@ -94,13 +94,14 @@ class TestFileGif(PillowTestCase): outfile = BytesIO() im.save(outfile, "GIF") outfile.seek(0) - reloaded = Image.open(outfile) + with Image.open(outfile) as reloaded: + # check palette length + palette_length = max( + i + 1 for i, v in enumerate(reloaded.histogram()) if v + ) + self.assertEqual(expected_palette_length, palette_length) - # check palette length - palette_length = max(i + 1 for i, v in enumerate(reloaded.histogram()) if v) - self.assertEqual(expected_palette_length, palette_length) - - self.assert_image_equal(im.convert("RGB"), reloaded.convert("RGB")) + self.assert_image_equal(im.convert("RGB"), reloaded.convert("RGB")) # These do optimize the palette check(128, 511, 128) @@ -554,9 +555,9 @@ class TestFileGif(PillowTestCase): self.assertEqual(reread.info["background"], im.info["background"]) if HAVE_WEBP and _webp.HAVE_WEBPANIM: - im = Image.open("Tests/images/hopper.webp") - self.assertIsInstance(im.info["background"], tuple) - im.save(out) + with Image.open("Tests/images/hopper.webp") as im: + self.assertIsInstance(im.info["background"], tuple) + im.save(out) def test_comment(self): with Image.open(TEST_GIF) as im: diff --git a/Tests/test_file_icns.py b/Tests/test_file_icns.py index e258601ce..6b6543d8d 100644 --- a/Tests/test_file_icns.py +++ b/Tests/test_file_icns.py @@ -27,32 +27,31 @@ class TestFileIcns(PillowTestCase): @unittest.skipIf(sys.platform != "darwin", "requires macOS") def test_save(self): - im = Image.open(TEST_FILE) - temp_file = self.tempfile("temp.icns") - im.save(temp_file) - reread = Image.open(temp_file) + with Image.open(TEST_FILE) as im: + im.save(temp_file) - self.assertEqual(reread.mode, "RGBA") - self.assertEqual(reread.size, (1024, 1024)) - self.assertEqual(reread.format, "ICNS") + with Image.open(temp_file) as reread: + self.assertEqual(reread.mode, "RGBA") + self.assertEqual(reread.size, (1024, 1024)) + self.assertEqual(reread.format, "ICNS") @unittest.skipIf(sys.platform != "darwin", "requires macOS") def test_save_append_images(self): - im = Image.open(TEST_FILE) - temp_file = self.tempfile("temp.icns") provided_im = Image.new("RGBA", (32, 32), (255, 0, 0, 128)) - im.save(temp_file, append_images=[provided_im]) - reread = Image.open(temp_file) - self.assert_image_similar(reread, im, 1) + with Image.open(TEST_FILE) as im: + im.save(temp_file, append_images=[provided_im]) - reread = Image.open(temp_file) - reread.size = (16, 16, 2) - reread.load() - self.assert_image_equal(reread, provided_im) + with Image.open(temp_file) as reread: + self.assert_image_similar(reread, im, 1) + + with Image.open(temp_file) as reread: + reread.size = (16, 16, 2) + reread.load() + self.assert_image_equal(reread, provided_im) def test_sizes(self): # Check that we can load all of the sizes, and that the final pixel diff --git a/Tests/test_file_ico.py b/Tests/test_file_ico.py index ac6b19041..d8bb9630f 100644 --- a/Tests/test_file_ico.py +++ b/Tests/test_file_ico.py @@ -27,23 +27,23 @@ class TestFileIco(PillowTestCase): # the default image output.seek(0) - reloaded = Image.open(output) - self.assertEqual(reloaded.info["sizes"], {(32, 32), (64, 64)}) + with Image.open(output) as reloaded: + self.assertEqual(reloaded.info["sizes"], {(32, 32), (64, 64)}) - self.assertEqual(im.mode, reloaded.mode) - self.assertEqual((64, 64), reloaded.size) - self.assertEqual(reloaded.format, "ICO") - self.assert_image_equal(reloaded, hopper().resize((64, 64), Image.LANCZOS)) + 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)) # the other one output.seek(0) - reloaded = Image.open(output) - reloaded.size = (32, 32) + with Image.open(output) as reloaded: + reloaded.size = (32, 32) - self.assertEqual(im.mode, reloaded.mode) - self.assertEqual((32, 32), reloaded.size) - self.assertEqual(reloaded.format, "ICO") - self.assert_image_equal(reloaded, hopper().resize((32, 32), Image.LANCZOS)) + 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)) def test_incorrect_size(self): with Image.open(TEST_ICO_FILE) as im: diff --git a/Tests/test_file_jpeg.py b/Tests/test_file_jpeg.py index 35f2c0940..e43b27f52 100644 --- a/Tests/test_file_jpeg.py +++ b/Tests/test_file_jpeg.py @@ -44,12 +44,12 @@ class TestFileJpeg(PillowTestCase): # internal version number self.assertRegex(Image.core.jpeglib_version, r"\d+\.\d+$") - im = Image.open(TEST_FILE) - im.load() - self.assertEqual(im.mode, "RGB") - self.assertEqual(im.size, (128, 128)) - self.assertEqual(im.format, "JPEG") - self.assertEqual(im.get_format_mimetype(), "image/jpeg") + with Image.open(TEST_FILE) as im: + im.load() + self.assertEqual(im.mode, "RGB") + self.assertEqual(im.size, (128, 128)) + self.assertEqual(im.format, "JPEG") + self.assertEqual(im.get_format_mimetype(), "image/jpeg") def test_app(self): # Test APP/COM reader (@PIL135) @@ -66,30 +66,34 @@ class TestFileJpeg(PillowTestCase): # Test CMYK handling. Thanks to Tim and Charlie for test data, # Michael for getting me to look one more time. f = "Tests/images/pil_sample_cmyk.jpg" - im = Image.open(f) - # the source image has red pixels in the upper left corner. - c, m, y, k = [x / 255.0 for x in im.getpixel((0, 0))] - self.assertEqual(c, 0.0) - self.assertGreater(m, 0.8) - self.assertGreater(y, 0.8) - self.assertEqual(k, 0.0) - # the opposite corner is black - c, m, y, k = [x / 255.0 for x in im.getpixel((im.size[0] - 1, im.size[1] - 1))] - self.assertGreater(k, 0.9) - # roundtrip, and check again - im = self.roundtrip(im) - c, m, y, k = [x / 255.0 for x in im.getpixel((0, 0))] - self.assertEqual(c, 0.0) - self.assertGreater(m, 0.8) - self.assertGreater(y, 0.8) - self.assertEqual(k, 0.0) - c, m, y, k = [x / 255.0 for x in im.getpixel((im.size[0] - 1, im.size[1] - 1))] - self.assertGreater(k, 0.9) + with Image.open(f) as im: + # the source image has red pixels in the upper left corner. + c, m, y, k = [x / 255.0 for x in im.getpixel((0, 0))] + self.assertEqual(c, 0.0) + self.assertGreater(m, 0.8) + self.assertGreater(y, 0.8) + self.assertEqual(k, 0.0) + # the opposite corner is black + c, m, y, k = [ + x / 255.0 for x in im.getpixel((im.size[0] - 1, im.size[1] - 1)) + ] + self.assertGreater(k, 0.9) + # roundtrip, and check again + im = self.roundtrip(im) + c, m, y, k = [x / 255.0 for x in im.getpixel((0, 0))] + self.assertEqual(c, 0.0) + self.assertGreater(m, 0.8) + self.assertGreater(y, 0.8) + self.assertEqual(k, 0.0) + c, m, y, k = [ + x / 255.0 for x in im.getpixel((im.size[0] - 1, im.size[1] - 1)) + ] + self.assertGreater(k, 0.9) def test_dpi(self): def test(xdpi, ydpi=None): - im = Image.open(TEST_FILE) - im = self.roundtrip(im, dpi=(xdpi, ydpi or xdpi)) + with Image.open(TEST_FILE) as im: + im = self.roundtrip(im, dpi=(xdpi, ydpi or xdpi)) return im.info.get("dpi") self.assertEqual(test(72), (72, 72)) @@ -140,18 +144,18 @@ class TestFileJpeg(PillowTestCase): # https://github.com/python-pillow/Pillow/issues/148 # Sometimes the meta data on the icc_profile block is bigger than # Image.MAXBLOCK or the image size. - im = Image.open("Tests/images/icc_profile_big.jpg") - f = self.tempfile("temp.jpg") - icc_profile = im.info["icc_profile"] - # Should not raise IOError for image with icc larger than image size. - im.save( - f, - format="JPEG", - progressive=True, - quality=95, - icc_profile=icc_profile, - optimize=True, - ) + with Image.open("Tests/images/icc_profile_big.jpg") as im: + f = self.tempfile("temp.jpg") + icc_profile = im.info["icc_profile"] + # Should not raise IOError for image with icc larger than image size. + im.save( + f, + format="JPEG", + progressive=True, + quality=95, + icc_profile=icc_profile, + optimize=True, + ) def test_optimize(self): im1 = self.roundtrip(hopper()) @@ -339,17 +343,17 @@ class TestFileJpeg(PillowTestCase): def test_quality_keep(self): # RGB - im = Image.open("Tests/images/hopper.jpg") - f = self.tempfile("temp.jpg") - im.save(f, quality="keep") + with Image.open("Tests/images/hopper.jpg") as im: + f = self.tempfile("temp.jpg") + im.save(f, quality="keep") # Grayscale - im = Image.open("Tests/images/hopper_gray.jpg") - f = self.tempfile("temp.jpg") - im.save(f, quality="keep") + with Image.open("Tests/images/hopper_gray.jpg") as im: + f = self.tempfile("temp.jpg") + im.save(f, quality="keep") # CMYK - im = Image.open("Tests/images/pil_sample_cmyk.jpg") - f = self.tempfile("temp.jpg") - im.save(f, quality="keep") + with Image.open("Tests/images/pil_sample_cmyk.jpg") as im: + f = self.tempfile("temp.jpg") + im.save(f, quality="keep") def test_junk_jpeg_header(self): # https://github.com/python-pillow/Pillow/issues/630 @@ -365,10 +369,10 @@ class TestFileJpeg(PillowTestCase): def test_truncated_jpeg_should_read_all_the_data(self): filename = "Tests/images/truncated_jpeg.jpg" ImageFile.LOAD_TRUNCATED_IMAGES = True - im = Image.open(filename) - im.load() - ImageFile.LOAD_TRUNCATED_IMAGES = False - self.assertIsNotNone(im.getbbox()) + with Image.open(filename) as im: + im.load() + ImageFile.LOAD_TRUNCATED_IMAGES = False + self.assertIsNotNone(im.getbbox()) def test_truncated_jpeg_throws_IOError(self): filename = "Tests/images/truncated_jpeg.jpg" @@ -381,106 +385,106 @@ class TestFileJpeg(PillowTestCase): im.load() def _n_qtables_helper(self, n, test_file): - im = Image.open(test_file) - f = self.tempfile("temp.jpg") - im.save(f, qtables=[[n] * 64] * n) - im = Image.open(f) - self.assertEqual(len(im.quantization), n) - reloaded = self.roundtrip(im, qtables="keep") - self.assertEqual(im.quantization, reloaded.quantization) + with Image.open(test_file) as im: + f = self.tempfile("temp.jpg") + im.save(f, qtables=[[n] * 64] * n) + with Image.open(f) as im: + self.assertEqual(len(im.quantization), n) + reloaded = self.roundtrip(im, qtables="keep") + self.assertEqual(im.quantization, reloaded.quantization) def test_qtables(self): - im = Image.open("Tests/images/hopper.jpg") - qtables = im.quantization - reloaded = self.roundtrip(im, qtables=qtables, subsampling=0) - self.assertEqual(im.quantization, reloaded.quantization) - self.assert_image_similar(im, self.roundtrip(im, qtables="web_low"), 30) - self.assert_image_similar(im, self.roundtrip(im, qtables="web_high"), 30) - self.assert_image_similar(im, self.roundtrip(im, qtables="keep"), 30) + with Image.open("Tests/images/hopper.jpg") as im: + qtables = im.quantization + reloaded = self.roundtrip(im, qtables=qtables, subsampling=0) + self.assertEqual(im.quantization, reloaded.quantization) + self.assert_image_similar(im, self.roundtrip(im, qtables="web_low"), 30) + self.assert_image_similar(im, self.roundtrip(im, qtables="web_high"), 30) + self.assert_image_similar(im, self.roundtrip(im, qtables="keep"), 30) - # valid bounds for baseline qtable - bounds_qtable = [int(s) for s in ("255 1 " * 32).split(None)] - self.roundtrip(im, qtables=[bounds_qtable]) + # valid bounds for baseline qtable + bounds_qtable = [int(s) for s in ("255 1 " * 32).split(None)] + self.roundtrip(im, qtables=[bounds_qtable]) - # values from wizard.txt in jpeg9-a src package. - standard_l_qtable = [ - int(s) - for s in """ - 16 11 10 16 24 40 51 61 - 12 12 14 19 26 58 60 55 - 14 13 16 24 40 57 69 56 - 14 17 22 29 51 87 80 62 - 18 22 37 56 68 109 103 77 - 24 35 55 64 81 104 113 92 - 49 64 78 87 103 121 120 101 - 72 92 95 98 112 100 103 99 - """.split( - None + # values from wizard.txt in jpeg9-a src package. + standard_l_qtable = [ + int(s) + for s in """ + 16 11 10 16 24 40 51 61 + 12 12 14 19 26 58 60 55 + 14 13 16 24 40 57 69 56 + 14 17 22 29 51 87 80 62 + 18 22 37 56 68 109 103 77 + 24 35 55 64 81 104 113 92 + 49 64 78 87 103 121 120 101 + 72 92 95 98 112 100 103 99 + """.split( + None + ) + ] + + standard_chrominance_qtable = [ + int(s) + for s in """ + 17 18 24 47 99 99 99 99 + 18 21 26 66 99 99 99 99 + 24 26 56 99 99 99 99 99 + 47 66 99 99 99 99 99 99 + 99 99 99 99 99 99 99 99 + 99 99 99 99 99 99 99 99 + 99 99 99 99 99 99 99 99 + 99 99 99 99 99 99 99 99 + """.split( + None + ) + ] + # list of qtable lists + self.assert_image_similar( + im, + self.roundtrip( + im, qtables=[standard_l_qtable, standard_chrominance_qtable] + ), + 30, ) - ] - standard_chrominance_qtable = [ - int(s) - for s in """ - 17 18 24 47 99 99 99 99 - 18 21 26 66 99 99 99 99 - 24 26 56 99 99 99 99 99 - 47 66 99 99 99 99 99 99 - 99 99 99 99 99 99 99 99 - 99 99 99 99 99 99 99 99 - 99 99 99 99 99 99 99 99 - 99 99 99 99 99 99 99 99 - """.split( - None + # tuple of qtable lists + self.assert_image_similar( + im, + self.roundtrip( + im, qtables=(standard_l_qtable, standard_chrominance_qtable) + ), + 30, ) - ] - # list of qtable lists - self.assert_image_similar( - im, - self.roundtrip( - im, qtables=[standard_l_qtable, standard_chrominance_qtable] - ), - 30, - ) - # tuple of qtable lists - self.assert_image_similar( - im, - self.roundtrip( - im, qtables=(standard_l_qtable, standard_chrominance_qtable) - ), - 30, - ) + # dict of qtable lists + self.assert_image_similar( + im, + self.roundtrip( + im, qtables={0: standard_l_qtable, 1: standard_chrominance_qtable} + ), + 30, + ) - # dict of qtable lists - self.assert_image_similar( - im, - self.roundtrip( - im, qtables={0: standard_l_qtable, 1: standard_chrominance_qtable} - ), - 30, - ) + self._n_qtables_helper(1, "Tests/images/hopper_gray.jpg") + self._n_qtables_helper(1, "Tests/images/pil_sample_rgb.jpg") + self._n_qtables_helper(2, "Tests/images/pil_sample_rgb.jpg") + self._n_qtables_helper(3, "Tests/images/pil_sample_rgb.jpg") + self._n_qtables_helper(1, "Tests/images/pil_sample_cmyk.jpg") + self._n_qtables_helper(2, "Tests/images/pil_sample_cmyk.jpg") + self._n_qtables_helper(3, "Tests/images/pil_sample_cmyk.jpg") + self._n_qtables_helper(4, "Tests/images/pil_sample_cmyk.jpg") - self._n_qtables_helper(1, "Tests/images/hopper_gray.jpg") - self._n_qtables_helper(1, "Tests/images/pil_sample_rgb.jpg") - self._n_qtables_helper(2, "Tests/images/pil_sample_rgb.jpg") - self._n_qtables_helper(3, "Tests/images/pil_sample_rgb.jpg") - self._n_qtables_helper(1, "Tests/images/pil_sample_cmyk.jpg") - self._n_qtables_helper(2, "Tests/images/pil_sample_cmyk.jpg") - self._n_qtables_helper(3, "Tests/images/pil_sample_cmyk.jpg") - self._n_qtables_helper(4, "Tests/images/pil_sample_cmyk.jpg") + # not a sequence + self.assertRaises(ValueError, self.roundtrip, im, qtables="a") + # sequence wrong length + self.assertRaises(ValueError, self.roundtrip, im, qtables=[]) + # sequence wrong length + self.assertRaises(ValueError, self.roundtrip, im, qtables=[1, 2, 3, 4, 5]) - # not a sequence - self.assertRaises(ValueError, self.roundtrip, im, qtables="a") - # sequence wrong length - self.assertRaises(ValueError, self.roundtrip, im, qtables=[]) - # sequence wrong length - self.assertRaises(ValueError, self.roundtrip, im, qtables=[1, 2, 3, 4, 5]) - - # qtable entry not a sequence - self.assertRaises(ValueError, self.roundtrip, im, qtables=[1]) - # qtable entry has wrong number of items - self.assertRaises(ValueError, self.roundtrip, im, qtables=[[1, 2, 3, 4]]) + # qtable entry not a sequence + self.assertRaises(ValueError, self.roundtrip, im, qtables=[1]) + # qtable entry has wrong number of items + self.assertRaises(ValueError, self.roundtrip, im, qtables=[[1, 2, 3, 4]]) @unittest.skipUnless(djpeg_available(), "djpeg not available") def test_load_djpeg(self): @@ -490,12 +494,11 @@ class TestFileJpeg(PillowTestCase): @unittest.skipUnless(cjpeg_available(), "cjpeg not available") def test_save_cjpeg(self): - img = Image.open(TEST_FILE) - - tempfile = self.tempfile("temp.jpg") - JpegImagePlugin._save_cjpeg(img, 0, tempfile) - # Default save quality is 75%, so a tiny bit of difference is alright - self.assert_image_similar(img, Image.open(tempfile), 17) + with Image.open(TEST_FILE) as img: + tempfile = self.tempfile("temp.jpg") + JpegImagePlugin._save_cjpeg(img, 0, tempfile) + # Default save quality is 75%, so a tiny bit of difference is alright + self.assert_image_similar(img, Image.open(tempfile), 17) def test_no_duplicate_0x1001_tag(self): # Arrange @@ -512,12 +515,11 @@ class TestFileJpeg(PillowTestCase): f = self.tempfile("temp.jpeg") im.save(f, quality=100, optimize=True) - reloaded = Image.open(f) - - # none of these should crash - reloaded.save(f, quality="keep") - reloaded.save(f, quality="keep", progressive=True) - reloaded.save(f, quality="keep", optimize=True) + with Image.open(f) as reloaded: + # none of these should crash + reloaded.save(f, quality="keep") + 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 """ @@ -547,15 +549,15 @@ class TestFileJpeg(PillowTestCase): def test_save_tiff_with_dpi(self): # Arrange outfile = self.tempfile("temp.tif") - im = Image.open("Tests/images/hopper.tif") + with Image.open("Tests/images/hopper.tif") as im: - # Act - im.save(outfile, "JPEG", dpi=im.info["dpi"]) + # Act + im.save(outfile, "JPEG", dpi=im.info["dpi"]) - # Assert - reloaded = Image.open(outfile) - reloaded.load() - self.assertEqual(im.info["dpi"], reloaded.info["dpi"]) + # Assert + with Image.open(outfile) as reloaded: + reloaded.load() + self.assertEqual(im.info["dpi"], reloaded.info["dpi"]) def test_load_dpi_rounding(self): # Round up @@ -568,13 +570,14 @@ class TestFileJpeg(PillowTestCase): def test_save_dpi_rounding(self): outfile = self.tempfile("temp.jpg") - im = Image.open("Tests/images/hopper.jpg") + with Image.open("Tests/images/hopper.jpg") as im: + im.save(outfile, dpi=(72.2, 72.2)) - im.save(outfile, dpi=(72.2, 72.2)) - with Image.open(outfile) as reloaded: - self.assertEqual(reloaded.info["dpi"], (72, 72)) + with Image.open(outfile) as reloaded: + self.assertEqual(reloaded.info["dpi"], (72, 72)) im.save(outfile, dpi=(72.8, 72.8)) + with Image.open(outfile) as reloaded: self.assertEqual(reloaded.info["dpi"], (73, 73)) diff --git a/Tests/test_file_jpeg2k.py b/Tests/test_file_jpeg2k.py index dac1d0ec0..37ce726db 100644 --- a/Tests/test_file_jpeg2k.py +++ b/Tests/test_file_jpeg2k.py @@ -33,13 +33,13 @@ class TestFileJpeg2k(PillowTestCase): # Internal version number self.assertRegex(Image.core.jp2klib_version, r"\d+\.\d+\.\d+$") - im = Image.open("Tests/images/test-card-lossless.jp2") - px = im.load() - self.assertEqual(px[0, 0], (0, 0, 0)) - self.assertEqual(im.mode, "RGB") - self.assertEqual(im.size, (640, 480)) - self.assertEqual(im.format, "JPEG2000") - self.assertEqual(im.get_format_mimetype(), "image/jp2") + with Image.open("Tests/images/test-card-lossless.jp2") as im: + px = im.load() + self.assertEqual(px[0, 0], (0, 0, 0)) + self.assertEqual(im.mode, "RGB") + self.assertEqual(im.size, (640, 480)) + self.assertEqual(im.format, "JPEG2000") + self.assertEqual(im.get_format_mimetype(), "image/jp2") def test_jpf(self): with Image.open("Tests/images/balloon.jpf") as im: @@ -54,24 +54,24 @@ class TestFileJpeg2k(PillowTestCase): def test_bytesio(self): with open("Tests/images/test-card-lossless.jp2", "rb") as f: data = BytesIO(f.read()) - im = Image.open(data) - im.load() - self.assert_image_similar(im, test_card, 1.0e-3) + with Image.open(data) as im: + im.load() + self.assert_image_similar(im, test_card, 1.0e-3) # These two test pre-written JPEG 2000 files that were not written with # PIL (they were made using Adobe Photoshop) def test_lossless(self): - im = Image.open("Tests/images/test-card-lossless.jp2") - im.load() - outfile = self.tempfile("temp_test-card.png") - im.save(outfile) + with Image.open("Tests/images/test-card-lossless.jp2") as im: + im.load() + outfile = self.tempfile("temp_test-card.png") + im.save(outfile) self.assert_image_similar(im, test_card, 1.0e-3) def test_lossy_tiled(self): - im = Image.open("Tests/images/test-card-lossy-tiled.jp2") - im.load() - self.assert_image_similar(im, test_card, 2.0) + with Image.open("Tests/images/test-card-lossy-tiled.jp2") as im: + im.load() + self.assert_image_similar(im, test_card, 2.0) def test_lossless_rt(self): im = self.roundtrip(test_card) @@ -110,10 +110,10 @@ class TestFileJpeg2k(PillowTestCase): self.assert_image_equal(im, test_card) def test_reduce(self): - im = Image.open("Tests/images/test-card-lossless.jp2") - im.reduce = 2 - im.load() - self.assertEqual(im.size, (160, 120)) + with Image.open("Tests/images/test-card-lossless.jp2") as im: + im.reduce = 2 + im.load() + self.assertEqual(im.size, (160, 120)) def test_layers_type(self): outfile = self.tempfile("temp_layers.jp2") @@ -132,64 +132,58 @@ class TestFileJpeg2k(PillowTestCase): ) out.seek(0) - im = Image.open(out) - im.layers = 1 - im.load() - self.assert_image_similar(im, test_card, 13) + with Image.open(out) as im: + im.layers = 1 + im.load() + self.assert_image_similar(im, test_card, 13) out.seek(0) - im = Image.open(out) - im.layers = 3 - im.load() - self.assert_image_similar(im, test_card, 0.4) + with Image.open(out) as im: + im.layers = 3 + im.load() + self.assert_image_similar(im, test_card, 0.4) def test_rgba(self): # Arrange - j2k = Image.open("Tests/images/rgb_trns_ycbc.j2k") - jp2 = Image.open("Tests/images/rgb_trns_ycbc.jp2") + with Image.open("Tests/images/rgb_trns_ycbc.j2k") as j2k: + with Image.open("Tests/images/rgb_trns_ycbc.jp2") as jp2: - # Act - j2k.load() - jp2.load() + # Act + j2k.load() + jp2.load() - # Assert - self.assertEqual(j2k.mode, "RGBA") - self.assertEqual(jp2.mode, "RGBA") + # Assert + self.assertEqual(j2k.mode, "RGBA") + self.assertEqual(jp2.mode, "RGBA") def test_16bit_monochrome_has_correct_mode(self): + with Image.open("Tests/images/16bit.cropped.j2k") as j2k: + j2k.load() + self.assertEqual(j2k.mode, "I;16") - j2k = Image.open("Tests/images/16bit.cropped.j2k") - jp2 = Image.open("Tests/images/16bit.cropped.jp2") - - j2k.load() - jp2.load() - - self.assertEqual(j2k.mode, "I;16") - self.assertEqual(jp2.mode, "I;16") + with Image.open("Tests/images/16bit.cropped.jp2") as jp2: + jp2.load() + self.assertEqual(jp2.mode, "I;16") def test_16bit_monochrome_jp2_like_tiff(self): - - tiff_16bit = Image.open("Tests/images/16bit.cropped.tif") - jp2 = Image.open("Tests/images/16bit.cropped.jp2") - self.assert_image_similar(jp2, tiff_16bit, 1e-3) + with Image.open("Tests/images/16bit.cropped.tif") as tiff_16bit: + with Image.open("Tests/images/16bit.cropped.jp2") as jp2: + self.assert_image_similar(jp2, tiff_16bit, 1e-3) def test_16bit_monochrome_j2k_like_tiff(self): - - tiff_16bit = Image.open("Tests/images/16bit.cropped.tif") - j2k = Image.open("Tests/images/16bit.cropped.j2k") - self.assert_image_similar(j2k, tiff_16bit, 1e-3) + with Image.open("Tests/images/16bit.cropped.tif") as tiff_16bit: + with Image.open("Tests/images/16bit.cropped.j2k") as j2k: + self.assert_image_similar(j2k, tiff_16bit, 1e-3) def test_16bit_j2k_roundtrips(self): - - j2k = Image.open("Tests/images/16bit.cropped.j2k") - im = self.roundtrip(j2k) - self.assert_image_equal(im, j2k) + with Image.open("Tests/images/16bit.cropped.j2k") as j2k: + im = self.roundtrip(j2k) + self.assert_image_equal(im, j2k) def test_16bit_jp2_roundtrips(self): - - jp2 = Image.open("Tests/images/16bit.cropped.jp2") - im = self.roundtrip(jp2) - self.assert_image_equal(im, jp2) + with Image.open("Tests/images/16bit.cropped.jp2") as jp2: + im = self.roundtrip(jp2) + self.assert_image_equal(im, jp2) def test_unbound_local(self): # prepatch, a malformed jp2 file could cause an UnboundLocalError diff --git a/Tests/test_file_libtiff.py b/Tests/test_file_libtiff.py index dba053e60..c26c503c4 100644 --- a/Tests/test_file_libtiff.py +++ b/Tests/test_file_libtiff.py @@ -47,25 +47,23 @@ class TestFileLibTiff(LibTiffTestCase): """Test the ordinary file path load path""" test_file = "Tests/images/hopper_g4_500.tif" - im = Image.open(test_file) - - self.assertEqual(im.size, (500, 500)) - self._assert_noerr(im) + with Image.open(test_file) as im: + self.assertEqual(im.size, (500, 500)) + self._assert_noerr(im) def test_g4_large(self): test_file = "Tests/images/pport_g4.tif" - im = Image.open(test_file) - self._assert_noerr(im) + with Image.open(test_file) as im: + self._assert_noerr(im) def test_g4_tiff_file(self): """Testing the string load path""" test_file = "Tests/images/hopper_g4_500.tif" with open(test_file, "rb") as f: - im = Image.open(f) - - self.assertEqual(im.size, (500, 500)) - self._assert_noerr(im) + with Image.open(f) as im: + self.assertEqual(im.size, (500, 500)) + self._assert_noerr(im) def test_g4_tiff_bytesio(self): """Testing the stringio loading code path""" @@ -74,10 +72,9 @@ class TestFileLibTiff(LibTiffTestCase): with open(test_file, "rb") as f: s.write(f.read()) s.seek(0) - im = Image.open(s) - - self.assertEqual(im.size, (500, 500)) - self._assert_noerr(im) + with Image.open(s) as im: + self.assertEqual(im.size, (500, 500)) + self._assert_noerr(im) def test_g4_non_disk_file_object(self): """Testing loading from non-disk non-BytesIO file object""" @@ -87,56 +84,51 @@ class TestFileLibTiff(LibTiffTestCase): s.write(f.read()) s.seek(0) r = io.BufferedReader(s) - im = Image.open(r) - - self.assertEqual(im.size, (500, 500)) - self._assert_noerr(im) + with Image.open(r) as im: + self.assertEqual(im.size, (500, 500)) + self._assert_noerr(im) def test_g4_eq_png(self): """ Checking that we're actually getting the data that we expect""" - png = Image.open("Tests/images/hopper_bw_500.png") - g4 = Image.open("Tests/images/hopper_g4_500.tif") - - self.assert_image_equal(g4, png) + with Image.open("Tests/images/hopper_bw_500.png") as png: + with Image.open("Tests/images/hopper_g4_500.tif") as g4: + self.assert_image_equal(g4, png) # see https://github.com/python-pillow/Pillow/issues/279 def test_g4_fillorder_eq_png(self): """ Checking that we're actually getting the data that we expect""" - png = Image.open("Tests/images/g4-fillorder-test.png") - g4 = Image.open("Tests/images/g4-fillorder-test.tif") - - self.assert_image_equal(g4, png) + with Image.open("Tests/images/g4-fillorder-test.png") as png: + with Image.open("Tests/images/g4-fillorder-test.tif") as g4: + self.assert_image_equal(g4, png) def test_g4_write(self): """Checking to see that the saved image is the same as what we wrote""" test_file = "Tests/images/hopper_g4_500.tif" - orig = Image.open(test_file) + with Image.open(test_file) as orig: + out = self.tempfile("temp.tif") + rot = orig.transpose(Image.ROTATE_90) + self.assertEqual(rot.size, (500, 500)) + rot.save(out) - out = self.tempfile("temp.tif") - rot = orig.transpose(Image.ROTATE_90) - self.assertEqual(rot.size, (500, 500)) - rot.save(out) + with Image.open(out) as reread: + self.assertEqual(reread.size, (500, 500)) + self._assert_noerr(reread) + self.assert_image_equal(reread, rot) + self.assertEqual(reread.info["compression"], "group4") - reread = Image.open(out) - self.assertEqual(reread.size, (500, 500)) - self._assert_noerr(reread) - self.assert_image_equal(reread, rot) - self.assertEqual(reread.info["compression"], "group4") + self.assertEqual(reread.info["compression"], orig.info["compression"]) - self.assertEqual(reread.info["compression"], orig.info["compression"]) - - self.assertNotEqual(orig.tobytes(), reread.tobytes()) + self.assertNotEqual(orig.tobytes(), reread.tobytes()) def test_adobe_deflate_tiff(self): test_file = "Tests/images/tiff_adobe_deflate.tif" - im = Image.open(test_file) + with Image.open(test_file) as im: + self.assertEqual(im.mode, "RGB") + self.assertEqual(im.size, (278, 374)) + self.assertEqual(im.tile[0][:3], ("libtiff", (0, 0, 278, 374), 0)) + im.load() - self.assertEqual(im.mode, "RGB") - self.assertEqual(im.size, (278, 374)) - self.assertEqual(im.tile[0][:3], ("libtiff", (0, 0, 278, 374), 0)) - im.load() - - self.assert_image_equal_tofile(im, "Tests/images/tiff_adobe_deflate.png") + self.assert_image_equal_tofile(im, "Tests/images/tiff_adobe_deflate.png") def test_write_metadata(self): """ Test metadata writing through libtiff """ @@ -204,44 +196,44 @@ class TestFileLibTiff(LibTiffTestCase): # Exclude ones that have special meaning # that we're already testing them - im = Image.open("Tests/images/hopper_g4.tif") - for tag in im.tag_v2: - try: - del core_items[tag] - except KeyError: - pass + with Image.open("Tests/images/hopper_g4.tif") as im: + for tag in im.tag_v2: + try: + del core_items[tag] + except KeyError: + pass - # Type codes: - # 2: "ascii", - # 3: "short", - # 4: "long", - # 5: "rational", - # 12: "double", - # Type: dummy value - values = { - 2: "test", - 3: 1, - 4: 2 ** 20, - 5: TiffImagePlugin.IFDRational(100, 1), - 12: 1.05, - } + # Type codes: + # 2: "ascii", + # 3: "short", + # 4: "long", + # 5: "rational", + # 12: "double", + # Type: dummy value + values = { + 2: "test", + 3: 1, + 4: 2 ** 20, + 5: TiffImagePlugin.IFDRational(100, 1), + 12: 1.05, + } - new_ifd = TiffImagePlugin.ImageFileDirectory_v2() - for tag, info in core_items.items(): - if info.length == 1: - new_ifd[tag] = values[info.type] - if info.length == 0: - new_ifd[tag] = tuple(values[info.type] for _ in range(3)) - else: - new_ifd[tag] = tuple(values[info.type] for _ in range(info.length)) + new_ifd = TiffImagePlugin.ImageFileDirectory_v2() + for tag, info in core_items.items(): + if info.length == 1: + new_ifd[tag] = values[info.type] + if info.length == 0: + new_ifd[tag] = tuple(values[info.type] for _ in range(3)) + else: + new_ifd[tag] = tuple(values[info.type] for _ in range(info.length)) - # Extra samples really doesn't make sense in this application. - del new_ifd[338] + # Extra samples really doesn't make sense in this application. + del new_ifd[338] - out = self.tempfile("temp.tif") - TiffImagePlugin.WRITE_LIBTIFF = True + out = self.tempfile("temp.tif") + TiffImagePlugin.WRITE_LIBTIFF = True - im.save(out, tiffinfo=new_ifd) + im.save(out, tiffinfo=new_ifd) TiffImagePlugin.WRITE_LIBTIFF = False @@ -342,62 +334,58 @@ class TestFileLibTiff(LibTiffTestCase): self.assertEqual(reloaded.info["dpi"], (72.0, 72.0)) def test_g3_compression(self): - i = Image.open("Tests/images/hopper_g4_500.tif") - out = self.tempfile("temp.tif") - i.save(out, compression="group3") + with Image.open("Tests/images/hopper_g4_500.tif") as i: + out = self.tempfile("temp.tif") + i.save(out, compression="group3") - reread = Image.open(out) - self.assertEqual(reread.info["compression"], "group3") - self.assert_image_equal(reread, i) + with Image.open(out) as reread: + self.assertEqual(reread.info["compression"], "group3") + self.assert_image_equal(reread, i) def test_little_endian(self): - im = Image.open("Tests/images/16bit.deflate.tif") - self.assertEqual(im.getpixel((0, 0)), 480) - self.assertEqual(im.mode, "I;16") + with Image.open("Tests/images/16bit.deflate.tif") as im: + self.assertEqual(im.getpixel((0, 0)), 480) + self.assertEqual(im.mode, "I;16") - b = im.tobytes() - # Bytes are in image native order (little endian) - self.assertEqual(b[0], ord(b"\xe0")) - self.assertEqual(b[1], ord(b"\x01")) + b = im.tobytes() + # Bytes are in image native order (little endian) + self.assertEqual(b[0], ord(b"\xe0")) + self.assertEqual(b[1], ord(b"\x01")) - out = self.tempfile("temp.tif") - # out = "temp.le.tif" - im.save(out) - reread = Image.open(out) - - self.assertEqual(reread.info["compression"], im.info["compression"]) - self.assertEqual(reread.getpixel((0, 0)), 480) + out = self.tempfile("temp.tif") + # out = "temp.le.tif" + im.save(out) + with Image.open(out) as reread: + self.assertEqual(reread.info["compression"], im.info["compression"]) + self.assertEqual(reread.getpixel((0, 0)), 480) # UNDONE - libtiff defaults to writing in native endian, so # on big endian, we'll get back mode = 'I;16B' here. def test_big_endian(self): - im = Image.open("Tests/images/16bit.MM.deflate.tif") + with Image.open("Tests/images/16bit.MM.deflate.tif") as im: + self.assertEqual(im.getpixel((0, 0)), 480) + self.assertEqual(im.mode, "I;16B") - self.assertEqual(im.getpixel((0, 0)), 480) - self.assertEqual(im.mode, "I;16B") + b = im.tobytes() - b = im.tobytes() + # Bytes are in image native order (big endian) + self.assertEqual(b[0], ord(b"\x01")) + self.assertEqual(b[1], ord(b"\xe0")) - # Bytes are in image native order (big endian) - self.assertEqual(b[0], ord(b"\x01")) - self.assertEqual(b[1], ord(b"\xe0")) - - out = self.tempfile("temp.tif") - im.save(out) - reread = Image.open(out) - - self.assertEqual(reread.info["compression"], im.info["compression"]) - self.assertEqual(reread.getpixel((0, 0)), 480) + out = self.tempfile("temp.tif") + im.save(out) + with Image.open(out) as reread: + self.assertEqual(reread.info["compression"], im.info["compression"]) + self.assertEqual(reread.getpixel((0, 0)), 480) def test_g4_string_info(self): """Tests String data in info directory""" test_file = "Tests/images/hopper_g4_500.tif" - orig = Image.open(test_file) + with Image.open(test_file) as orig: + out = self.tempfile("temp.tif") - out = self.tempfile("temp.tif") - - orig.tag[269] = "temp.tif" - orig.save(out) + orig.tag[269] = "temp.tif" + orig.save(out) with Image.open(out) as reread: self.assertEqual("temp.tif", reread.tag_v2[269]) @@ -407,16 +395,16 @@ class TestFileLibTiff(LibTiffTestCase): """ Are we generating the same interpretation of the image as Imagemagick is? """ TiffImagePlugin.READ_LIBTIFF = True - im = Image.open("Tests/images/12bit.cropped.tif") - im.load() - TiffImagePlugin.READ_LIBTIFF = False - # to make the target -- - # convert 12bit.cropped.tif -depth 16 tmp.tif - # convert tmp.tif -evaluate RightShift 4 12in16bit2.tif - # imagemagick will auto scale so that a 12bit FFF is 16bit FFF0, - # so we need to unshift so that the integer values are the same. + with Image.open("Tests/images/12bit.cropped.tif") as im: + im.load() + TiffImagePlugin.READ_LIBTIFF = False + # to make the target -- + # convert 12bit.cropped.tif -depth 16 tmp.tif + # convert tmp.tif -evaluate RightShift 4 12in16bit2.tif + # imagemagick will auto scale so that a 12bit FFF is 16bit FFF0, + # so we need to unshift so that the integer values are the same. - self.assert_image_equal_tofile(im, "Tests/images/12in16bit.tif") + self.assert_image_equal_tofile(im, "Tests/images/12in16bit.tif") def test_blur(self): # test case from irc, how to do blur on b/w image @@ -424,16 +412,16 @@ class TestFileLibTiff(LibTiffTestCase): from PIL import ImageFilter out = self.tempfile("temp.tif") - im = Image.open("Tests/images/pport_g4.tif") - im = im.convert("L") + with Image.open("Tests/images/pport_g4.tif") as im: + im = im.convert("L") im = im.filter(ImageFilter.GaussianBlur(4)) im.save(out, compression="tiff_adobe_deflate") - im2 = Image.open(out) - im2.load() + with Image.open(out) as im2: + im2.load() - self.assert_image_equal(im, im2) + self.assert_image_equal(im, im2) def test_compressions(self): # Test various tiff compressions and assert similar image content but reduced @@ -446,18 +434,18 @@ class TestFileLibTiff(LibTiffTestCase): for compression in ("packbits", "tiff_lzw"): im.save(out, compression=compression) size_compressed = os.path.getsize(out) - im2 = Image.open(out) - self.assert_image_equal(im, im2) + with Image.open(out) as im2: + self.assert_image_equal(im, im2) im.save(out, compression="jpeg") size_jpeg = os.path.getsize(out) - im2 = Image.open(out) - self.assert_image_similar(im, im2, 30) + with Image.open(out) as im2: + self.assert_image_similar(im, im2, 30) im.save(out, compression="jpeg", quality=30) size_jpeg_30 = os.path.getsize(out) - im3 = Image.open(out) - self.assert_image_similar(im2, im3, 30) + with Image.open(out) as im3: + self.assert_image_similar(im2, im3, 30) self.assertGreater(size_raw, size_compressed) self.assertGreater(size_compressed, size_jpeg) @@ -479,8 +467,8 @@ class TestFileLibTiff(LibTiffTestCase): out = self.tempfile("temp.tif") im.save(out, compression="tiff_adobe_deflate") - im2 = Image.open(out) - self.assert_image_equal(im, im2) + with Image.open(out) as im2: + self.assert_image_equal(im, im2) def xtest_bw_compression_w_rgb(self): """ This test passes, but when running all tests causes a failure due @@ -543,10 +531,10 @@ class TestFileLibTiff(LibTiffTestCase): def test__next(self): TiffImagePlugin.READ_LIBTIFF = True - im = Image.open("Tests/images/hopper.tif") - self.assertFalse(im.tag.next) - im.load() - self.assertFalse(im.tag.next) + with Image.open("Tests/images/hopper.tif") as im: + self.assertFalse(im.tag.next) + im.load() + self.assertFalse(im.tag.next) def test_4bit(self): # Arrange @@ -555,13 +543,13 @@ class TestFileLibTiff(LibTiffTestCase): # Act TiffImagePlugin.READ_LIBTIFF = True - im = Image.open(test_file) - TiffImagePlugin.READ_LIBTIFF = False + with Image.open(test_file) as im: + TiffImagePlugin.READ_LIBTIFF = False - # Assert - self.assertEqual(im.size, (128, 128)) - self.assertEqual(im.mode, "L") - self.assert_image_similar(im, original, 7.3) + # Assert + self.assertEqual(im.size, (128, 128)) + self.assertEqual(im.mode, "L") + self.assert_image_similar(im, original, 7.3) def test_gray_semibyte_per_pixel(self): test_files = ( @@ -586,15 +574,15 @@ class TestFileLibTiff(LibTiffTestCase): ) original = hopper("L") for epsilon, group in test_files: - im = Image.open(group[0]) - self.assertEqual(im.size, (128, 128)) - self.assertEqual(im.mode, "L") - self.assert_image_similar(im, original, epsilon) + with Image.open(group[0]) as im: + self.assertEqual(im.size, (128, 128)) + self.assertEqual(im.mode, "L") + self.assert_image_similar(im, original, epsilon) for file in group[1:]: - im2 = Image.open(file) - self.assertEqual(im2.size, (128, 128)) - self.assertEqual(im2.mode, "L") - self.assert_image_equal(im, im2) + with Image.open(file) as im2: + self.assertEqual(im2.size, (128, 128)) + self.assertEqual(im2.mode, "L") + self.assert_image_equal(im, im2) def test_save_bytesio(self): # PR 1011 @@ -612,8 +600,8 @@ class TestFileLibTiff(LibTiffTestCase): pilim.save(buffer_io, format="tiff", compression=compression) buffer_io.seek(0) - pilim_load = Image.open(buffer_io) - self.assert_image_similar(pilim, pilim_load, 0) + with Image.open(buffer_io) as pilim_load: + self.assert_image_similar(pilim, pilim_load, 0) save_bytesio() save_bytesio("raw") @@ -625,12 +613,12 @@ class TestFileLibTiff(LibTiffTestCase): def test_crashing_metadata(self): # issue 1597 - im = Image.open("Tests/images/rdf.tif") - out = self.tempfile("temp.tif") + with Image.open("Tests/images/rdf.tif") as im: + out = self.tempfile("temp.tif") - TiffImagePlugin.WRITE_LIBTIFF = True - # this shouldn't crash - im.save(out, format="TIFF") + TiffImagePlugin.WRITE_LIBTIFF = True + # this shouldn't crash + im.save(out, format="TIFF") TiffImagePlugin.WRITE_LIBTIFF = False def test_page_number_x_0(self): @@ -696,44 +684,50 @@ class TestFileLibTiff(LibTiffTestCase): # Created with ImageMagick: convert hopper.jpg hopper_jpg.tif # Contains JPEGTables (347) tag infile = "Tests/images/hopper_jpg.tif" - im = Image.open(infile) - - # Act / Assert - # Should not raise UnicodeDecodeError or anything else - im.save(outfile) + with Image.open(infile) as im: + # Act / Assert + # Should not raise UnicodeDecodeError or anything else + im.save(outfile) def test_16bit_RGB_tiff(self): - im = Image.open("Tests/images/tiff_16bit_RGB.tiff") + with Image.open("Tests/images/tiff_16bit_RGB.tiff") as im: + self.assertEqual(im.mode, "RGB") + self.assertEqual(im.size, (100, 40)) + self.assertEqual( + im.tile, + [ + ( + "libtiff", + (0, 0, 100, 40), + 0, + ("RGB;16N", "tiff_adobe_deflate", False, 8), + ) + ], + ) + im.load() - self.assertEqual(im.mode, "RGB") - self.assertEqual(im.size, (100, 40)) - self.assertEqual( - im.tile, - [ - ( - "libtiff", - (0, 0, 100, 40), - 0, - ("RGB;16N", "tiff_adobe_deflate", False, 8), - ) - ], - ) - im.load() - - self.assert_image_equal_tofile(im, "Tests/images/tiff_16bit_RGB_target.png") + self.assert_image_equal_tofile(im, "Tests/images/tiff_16bit_RGB_target.png") def test_16bit_RGBa_tiff(self): - im = Image.open("Tests/images/tiff_16bit_RGBa.tiff") + with Image.open("Tests/images/tiff_16bit_RGBa.tiff") as im: + self.assertEqual(im.mode, "RGBA") + self.assertEqual(im.size, (100, 40)) + self.assertEqual( + im.tile, + [ + ( + "libtiff", + (0, 0, 100, 40), + 0, + ("RGBa;16N", "tiff_lzw", False, 38236), + ) + ], + ) + im.load() - self.assertEqual(im.mode, "RGBA") - self.assertEqual(im.size, (100, 40)) - self.assertEqual( - im.tile, - [("libtiff", (0, 0, 100, 40), 0, ("RGBa;16N", "tiff_lzw", False, 38236))], - ) - im.load() - - self.assert_image_equal_tofile(im, "Tests/images/tiff_16bit_RGBa_target.png") + self.assert_image_equal_tofile( + im, "Tests/images/tiff_16bit_RGBa_target.png" + ) def test_gimp_tiff(self): # Read TIFF JPEG images from GIMP [@PIL168] @@ -743,82 +737,79 @@ class TestFileLibTiff(LibTiffTestCase): self.skipTest("jpeg support not available") filename = "Tests/images/pil168.tif" - im = Image.open(filename) + with Image.open(filename) as im: + self.assertEqual(im.mode, "RGB") + self.assertEqual(im.size, (256, 256)) + self.assertEqual( + im.tile, + [("libtiff", (0, 0, 256, 256), 0, ("RGB", "jpeg", False, 5122))], + ) + im.load() - self.assertEqual(im.mode, "RGB") - self.assertEqual(im.size, (256, 256)) - self.assertEqual( - im.tile, [("libtiff", (0, 0, 256, 256), 0, ("RGB", "jpeg", False, 5122))] - ) - im.load() - - self.assert_image_equal_tofile(im, "Tests/images/pil168.png") + self.assert_image_equal_tofile(im, "Tests/images/pil168.png") def test_sampleformat(self): # https://github.com/python-pillow/Pillow/issues/1466 - im = Image.open("Tests/images/copyleft.tiff") - self.assertEqual(im.mode, "RGB") + with Image.open("Tests/images/copyleft.tiff") as im: + self.assertEqual(im.mode, "RGB") - self.assert_image_equal_tofile(im, "Tests/images/copyleft.png", mode="RGB") + self.assert_image_equal_tofile(im, "Tests/images/copyleft.png", mode="RGB") def test_lzw(self): - im = Image.open("Tests/images/hopper_lzw.tif") - - self.assertEqual(im.mode, "RGB") - self.assertEqual(im.size, (128, 128)) - self.assertEqual(im.format, "TIFF") - im2 = hopper() - self.assert_image_similar(im, im2, 5) + with Image.open("Tests/images/hopper_lzw.tif") as im: + self.assertEqual(im.mode, "RGB") + self.assertEqual(im.size, (128, 128)) + self.assertEqual(im.format, "TIFF") + im2 = hopper() + self.assert_image_similar(im, im2, 5) def test_strip_cmyk_jpeg(self): infile = "Tests/images/tiff_strip_cmyk_jpeg.tif" - im = Image.open(infile) - - self.assert_image_similar_tofile(im, "Tests/images/pil_sample_cmyk.jpg", 0.5) + with Image.open(infile) as im: + self.assert_image_similar_tofile( + im, "Tests/images/pil_sample_cmyk.jpg", 0.5 + ) def test_strip_cmyk_16l_jpeg(self): infile = "Tests/images/tiff_strip_cmyk_16l_jpeg.tif" - im = Image.open(infile) - - self.assert_image_similar_tofile(im, "Tests/images/pil_sample_cmyk.jpg", 0.5) + with Image.open(infile) as im: + self.assert_image_similar_tofile( + im, "Tests/images/pil_sample_cmyk.jpg", 0.5 + ) def test_strip_ycbcr_jpeg_2x2_sampling(self): infile = "Tests/images/tiff_strip_ycbcr_jpeg_2x2_sampling.tif" - im = Image.open(infile) - - self.assert_image_similar_tofile(im, "Tests/images/flower.jpg", 0.5) + with Image.open(infile) as im: + self.assert_image_similar_tofile(im, "Tests/images/flower.jpg", 0.5) def test_strip_ycbcr_jpeg_1x1_sampling(self): infile = "Tests/images/tiff_strip_ycbcr_jpeg_1x1_sampling.tif" - im = Image.open(infile) - - self.assert_image_equal_tofile(im, "Tests/images/flower2.jpg") + with Image.open(infile) as im: + self.assert_image_equal_tofile(im, "Tests/images/flower2.jpg") def test_tiled_cmyk_jpeg(self): infile = "Tests/images/tiff_tiled_cmyk_jpeg.tif" - im = Image.open(infile) - - self.assert_image_similar_tofile(im, "Tests/images/pil_sample_cmyk.jpg", 0.5) + with Image.open(infile) as im: + self.assert_image_similar_tofile( + im, "Tests/images/pil_sample_cmyk.jpg", 0.5 + ) def test_tiled_ycbcr_jpeg_1x1_sampling(self): infile = "Tests/images/tiff_tiled_ycbcr_jpeg_1x1_sampling.tif" - im = Image.open(infile) - - self.assert_image_equal_tofile(im, "Tests/images/flower2.jpg") + with Image.open(infile) as im: + self.assert_image_equal_tofile(im, "Tests/images/flower2.jpg") def test_tiled_ycbcr_jpeg_2x2_sampling(self): infile = "Tests/images/tiff_tiled_ycbcr_jpeg_2x2_sampling.tif" - im = Image.open(infile) - - self.assert_image_similar_tofile(im, "Tests/images/flower.jpg", 0.5) + with Image.open(infile) as im: + self.assert_image_similar_tofile(im, "Tests/images/flower.jpg", 0.5) def test_old_style_jpeg(self): infile = "Tests/images/old-style-jpeg-compression.tif" - im = Image.open(infile) - - self.assert_image_equal_tofile( - im, "Tests/images/old-style-jpeg-compression.png" - ) + with Image.open(infile) as im: + self.assert_image_equal_tofile( + im, "Tests/images/old-style-jpeg-compression.png" + ) def test_no_rows_per_strip(self): # This image does not have a RowsPerStrip TIFF tag @@ -828,13 +819,12 @@ class TestFileLibTiff(LibTiffTestCase): self.assertEqual(im.size, (950, 975)) def test_orientation(self): - base_im = Image.open("Tests/images/g4_orientation_1.tif") + with Image.open("Tests/images/g4_orientation_1.tif") as base_im: + for i in range(2, 9): + with Image.open("Tests/images/g4_orientation_" + str(i) + ".tif") as im: + im.load() - for i in range(2, 9): - im = Image.open("Tests/images/g4_orientation_" + str(i) + ".tif") - im.load() - - self.assert_image_similar(base_im, im, 0.7) + self.assert_image_similar(base_im, im, 0.7) def test_sampleformat_not_corrupted(self): # Assert that a TIFF image with SampleFormat=UINT tag is not corrupted diff --git a/Tests/test_file_libtiff_small.py b/Tests/test_file_libtiff_small.py index 2eabc60fd..a4e74896f 100644 --- a/Tests/test_file_libtiff_small.py +++ b/Tests/test_file_libtiff_small.py @@ -20,10 +20,9 @@ class TestFileLibTiffSmall(LibTiffTestCase): test_file = "Tests/images/hopper_g4.tif" with open(test_file, "rb") as f: - im = Image.open(f) - - self.assertEqual(im.size, (128, 128)) - self._assert_noerr(im) + with Image.open(f) as im: + self.assertEqual(im.size, (128, 128)) + self._assert_noerr(im) def test_g4_hopper_bytesio(self): """Testing the bytesio loading code path""" @@ -32,16 +31,14 @@ class TestFileLibTiffSmall(LibTiffTestCase): with open(test_file, "rb") as f: s.write(f.read()) s.seek(0) - im = Image.open(s) - - self.assertEqual(im.size, (128, 128)) - self._assert_noerr(im) + with Image.open(s) as im: + self.assertEqual(im.size, (128, 128)) + self._assert_noerr(im) def test_g4_hopper(self): """The 128x128 lena image failed for some reason.""" test_file = "Tests/images/hopper_g4.tif" - im = Image.open(test_file) - - self.assertEqual(im.size, (128, 128)) - self._assert_noerr(im) + with Image.open(test_file) as im: + self.assertEqual(im.size, (128, 128)) + self._assert_noerr(im) diff --git a/Tests/test_file_mcidas.py b/Tests/test_file_mcidas.py index acc4ddb91..ed2653cd3 100644 --- a/Tests/test_file_mcidas.py +++ b/Tests/test_file_mcidas.py @@ -17,12 +17,12 @@ class TestFileMcIdas(PillowTestCase): saved_file = "Tests/images/cmx3g8_wv_1998.260_0745_mcidas.png" # Act - im = Image.open(test_file) - im.load() + with Image.open(test_file) as im: + im.load() - # Assert - self.assertEqual(im.format, "MCIDAS") - self.assertEqual(im.mode, "I") - self.assertEqual(im.size, (1800, 400)) - im2 = Image.open(saved_file) - self.assert_image_equal(im, im2) + # Assert + self.assertEqual(im.format, "MCIDAS") + self.assertEqual(im.mode, "I") + self.assertEqual(im.size, (1800, 400)) + with Image.open(saved_file) as im2: + self.assert_image_equal(im, im2) diff --git a/Tests/test_file_msp.py b/Tests/test_file_msp.py index d717ade16..51189b83b 100644 --- a/Tests/test_file_msp.py +++ b/Tests/test_file_msp.py @@ -16,11 +16,11 @@ class TestFileMsp(PillowTestCase): hopper("1").save(test_file) - im = Image.open(test_file) - im.load() - self.assertEqual(im.mode, "1") - self.assertEqual(im.size, (128, 128)) - self.assertEqual(im.format, "MSP") + with Image.open(test_file) as im: + im.load() + self.assertEqual(im.mode, "1") + self.assertEqual(im.size, (128, 128)) + self.assertEqual(im.format, "MSP") def test_invalid_file(self): invalid_file = "Tests/images/flower.jpg" @@ -38,16 +38,16 @@ class TestFileMsp(PillowTestCase): def test_open_windows_v1(self): # Arrange # Act - im = Image.open(TEST_FILE) + with Image.open(TEST_FILE) as im: - # Assert - self.assert_image_equal(im, hopper("1")) - self.assertIsInstance(im, MspImagePlugin.MspImageFile) + # Assert + self.assert_image_equal(im, hopper("1")) + self.assertIsInstance(im, MspImagePlugin.MspImageFile) def _assert_file_image_equal(self, source_path, target_path): with Image.open(source_path) as im: - target = Image.open(target_path) - self.assert_image_equal(im, target) + with Image.open(target_path) as target: + self.assert_image_equal(im, target) @unittest.skipIf(not os.path.exists(EXTRA_DIR), "Extra image files not installed") def test_open_windows_v2(self): diff --git a/Tests/test_file_pcd.py b/Tests/test_file_pcd.py index b23328ba5..55d753fc3 100644 --- a/Tests/test_file_pcd.py +++ b/Tests/test_file_pcd.py @@ -5,8 +5,8 @@ from .helper import PillowTestCase class TestFilePcd(PillowTestCase): def test_load_raw(self): - im = Image.open("Tests/images/hopper.pcd") - im.load() # should not segfault. + with Image.open("Tests/images/hopper.pcd") as im: + im.load() # should not segfault. # Note that this image was created with a resized hopper # image, which was then converted to pcd with imagemagick diff --git a/Tests/test_file_pcx.py b/Tests/test_file_pcx.py index eb2c7d611..780739422 100644 --- a/Tests/test_file_pcx.py +++ b/Tests/test_file_pcx.py @@ -7,13 +7,12 @@ class TestFilePcx(PillowTestCase): def _roundtrip(self, im): f = self.tempfile("temp.pcx") im.save(f) - im2 = Image.open(f) - - self.assertEqual(im2.mode, im.mode) - self.assertEqual(im2.size, im.size) - self.assertEqual(im2.format, "PCX") - self.assertEqual(im2.get_format_mimetype(), "image/x-pcx") - self.assert_image_equal(im2, im) + with Image.open(f) as im2: + self.assertEqual(im2.mode, im.mode) + self.assertEqual(im2.size, im.size) + self.assertEqual(im2.format, "PCX") + self.assertEqual(im2.get_format_mimetype(), "image/x-pcx") + self.assert_image_equal(im2, im) def test_sanity(self): for mode in ("1", "L", "P", "RGB"): @@ -42,13 +41,12 @@ class TestFilePcx(PillowTestCase): # Check reading of files where xmin/xmax is not zero. test_file = "Tests/images/pil184.pcx" - im = Image.open(test_file) + with Image.open(test_file) as im: + self.assertEqual(im.size, (447, 144)) + self.assertEqual(im.tile[0][1], (0, 0, 447, 144)) - self.assertEqual(im.size, (447, 144)) - self.assertEqual(im.tile[0][1], (0, 0, 447, 144)) - - # Make sure all pixels are either 0 or 255. - self.assertEqual(im.histogram()[0] + im.histogram()[255], 447 * 144) + # Make sure all pixels are either 0 or 255. + self.assertEqual(im.histogram()[0] + im.histogram()[255], 447 * 144) def test_1px_width(self): im = Image.new("L", (1, 256)) diff --git a/Tests/test_file_pdf.py b/Tests/test_file_pdf.py index 4b5897589..9b8b789b9 100644 --- a/Tests/test_file_pdf.py +++ b/Tests/test_file_pdf.py @@ -107,8 +107,8 @@ class TestFilePdf(PillowTestCase): self.assertGreater(os.path.getsize(outfile), 0) # Append JPEG images - jpeg = Image.open("Tests/images/flower.jpg") - jpeg.save(outfile, save_all=True, append_images=[jpeg.copy()]) + with Image.open("Tests/images/flower.jpg") as jpeg: + jpeg.save(outfile, save_all=True, append_images=[jpeg.copy()]) self.assertTrue(os.path.isfile(outfile)) self.assertGreater(os.path.getsize(outfile), 0) diff --git a/Tests/test_file_pixar.py b/Tests/test_file_pixar.py index c744932d4..eae2fabba 100644 --- a/Tests/test_file_pixar.py +++ b/Tests/test_file_pixar.py @@ -7,15 +7,15 @@ TEST_FILE = "Tests/images/hopper.pxr" class TestFilePixar(PillowTestCase): def test_sanity(self): - im = Image.open(TEST_FILE) - im.load() - self.assertEqual(im.mode, "RGB") - self.assertEqual(im.size, (128, 128)) - self.assertEqual(im.format, "PIXAR") - self.assertIsNone(im.get_format_mimetype()) + with Image.open(TEST_FILE) as im: + im.load() + self.assertEqual(im.mode, "RGB") + self.assertEqual(im.size, (128, 128)) + self.assertEqual(im.format, "PIXAR") + self.assertIsNone(im.get_format_mimetype()) - im2 = hopper() - self.assert_image_similar(im, im2, 4.8) + im2 = hopper() + self.assert_image_similar(im, im2, 4.8) def test_invalid_file(self): invalid_file = "Tests/images/flower.jpg" diff --git a/Tests/test_file_png.py b/Tests/test_file_png.py index c9c337e64..4cd613785 100644 --- a/Tests/test_file_png.py +++ b/Tests/test_file_png.py @@ -91,10 +91,10 @@ class TestFilePng(PillowTestCase): for mode in ["1", "L", "P", "RGB", "I", "I;16"]: im = hopper(mode) im.save(test_file) - reloaded = Image.open(test_file) - if mode == "I;16": - reloaded = reloaded.convert(mode) - self.assert_image_equal(reloaded, im) + with Image.open(test_file) as reloaded: + if mode == "I;16": + reloaded = reloaded.convert(mode) + self.assert_image_equal(reloaded, im) def test_invalid_file(self): invalid_file = "Tests/images/flower.jpg" @@ -195,27 +195,24 @@ class TestFilePng(PillowTestCase): def test_interlace(self): test_file = "Tests/images/pil123p.png" - im = Image.open(test_file) + with Image.open(test_file) as im: + self.assert_image(im, "P", (162, 150)) + self.assertTrue(im.info.get("interlace")) - self.assert_image(im, "P", (162, 150)) - self.assertTrue(im.info.get("interlace")) - - im.load() + im.load() test_file = "Tests/images/pil123rgba.png" - im = Image.open(test_file) + with Image.open(test_file) as im: + self.assert_image(im, "RGBA", (162, 150)) + self.assertTrue(im.info.get("interlace")) - self.assert_image(im, "RGBA", (162, 150)) - self.assertTrue(im.info.get("interlace")) - - im.load() + im.load() def test_load_transparent_p(self): test_file = "Tests/images/pil123p.png" - im = Image.open(test_file) - - self.assert_image(im, "P", (162, 150)) - im = im.convert("RGBA") + with Image.open(test_file) as im: + self.assert_image(im, "P", (162, 150)) + im = im.convert("RGBA") self.assert_image(im, "RGBA", (162, 150)) # image has 124 unique alpha values @@ -223,11 +220,11 @@ class TestFilePng(PillowTestCase): def test_load_transparent_rgb(self): test_file = "Tests/images/rgb_trns.png" - im = Image.open(test_file) - self.assertEqual(im.info["transparency"], (0, 255, 52)) + with Image.open(test_file) as im: + self.assertEqual(im.info["transparency"], (0, 255, 52)) - self.assert_image(im, "RGB", (64, 64)) - im = im.convert("RGBA") + self.assert_image(im, "RGB", (64, 64)) + im = im.convert("RGBA") self.assert_image(im, "RGBA", (64, 64)) # image has 876 transparent pixels @@ -235,21 +232,20 @@ class TestFilePng(PillowTestCase): def test_save_p_transparent_palette(self): in_file = "Tests/images/pil123p.png" - im = Image.open(in_file) + with Image.open(in_file) as im: + # 'transparency' contains a byte string with the opacity for + # each palette entry + self.assertEqual(len(im.info["transparency"]), 256) - # 'transparency' contains a byte string with the opacity for - # each palette entry - self.assertEqual(len(im.info["transparency"]), 256) - - test_file = self.tempfile("temp.png") - im.save(test_file) + test_file = self.tempfile("temp.png") + im.save(test_file) # check if saved image contains same transparency - im = Image.open(test_file) - self.assertEqual(len(im.info["transparency"]), 256) + with Image.open(test_file) as im: + self.assertEqual(len(im.info["transparency"]), 256) - self.assert_image(im, "P", (162, 150)) - im = im.convert("RGBA") + self.assert_image(im, "P", (162, 150)) + im = im.convert("RGBA") self.assert_image(im, "RGBA", (162, 150)) # image has 124 unique alpha values @@ -257,21 +253,20 @@ class TestFilePng(PillowTestCase): def test_save_p_single_transparency(self): in_file = "Tests/images/p_trns_single.png" - im = Image.open(in_file) + with Image.open(in_file) as im: + # pixel value 164 is full transparent + self.assertEqual(im.info["transparency"], 164) + self.assertEqual(im.getpixel((31, 31)), 164) - # pixel value 164 is full transparent - self.assertEqual(im.info["transparency"], 164) - self.assertEqual(im.getpixel((31, 31)), 164) - - test_file = self.tempfile("temp.png") - im.save(test_file) + test_file = self.tempfile("temp.png") + im.save(test_file) # check if saved image contains same transparency - im = Image.open(test_file) - self.assertEqual(im.info["transparency"], 164) - self.assertEqual(im.getpixel((31, 31)), 164) - self.assert_image(im, "P", (64, 64)) - im = im.convert("RGBA") + with Image.open(test_file) as im: + self.assertEqual(im.info["transparency"], 164) + self.assertEqual(im.getpixel((31, 31)), 164) + self.assert_image(im, "P", (64, 64)) + im = im.convert("RGBA") self.assert_image(im, "RGBA", (64, 64)) self.assertEqual(im.getpixel((31, 31)), (0, 255, 52, 0)) @@ -290,30 +285,30 @@ class TestFilePng(PillowTestCase): im.save(test_file) # check if saved image contains same transparency - im = Image.open(test_file) - self.assertEqual(len(im.info["transparency"]), 256) - self.assert_image(im, "P", (10, 10)) - im = im.convert("RGBA") + with Image.open(test_file) as im: + self.assertEqual(len(im.info["transparency"]), 256) + self.assert_image(im, "P", (10, 10)) + im = im.convert("RGBA") self.assert_image(im, "RGBA", (10, 10)) self.assertEqual(im.getcolors(), [(100, (0, 0, 0, 0))]) def test_save_greyscale_transparency(self): for mode, num_transparent in {"1": 1994, "L": 559, "I": 559}.items(): in_file = "Tests/images/" + mode.lower() + "_trns.png" - im = Image.open(in_file) - self.assertEqual(im.mode, mode) - self.assertEqual(im.info["transparency"], 255) + with Image.open(in_file) as im: + self.assertEqual(im.mode, mode) + self.assertEqual(im.info["transparency"], 255) - im_rgba = im.convert("RGBA") + im_rgba = im.convert("RGBA") self.assertEqual(im_rgba.getchannel("A").getcolors()[0][0], num_transparent) test_file = self.tempfile("temp.png") im.save(test_file) - test_im = Image.open(test_file) - self.assertEqual(test_im.mode, mode) - self.assertEqual(test_im.info["transparency"], 255) - self.assert_image_equal(im, test_im) + with Image.open(test_file) as test_im: + self.assertEqual(test_im.mode, mode) + self.assertEqual(test_im.info["transparency"], 255) + self.assert_image_equal(im, test_im) test_im_rgba = test_im.convert("RGBA") self.assertEqual( @@ -322,22 +317,20 @@ class TestFilePng(PillowTestCase): def test_save_rgb_single_transparency(self): in_file = "Tests/images/caption_6_33_22.png" - im = Image.open(in_file) - - test_file = self.tempfile("temp.png") - im.save(test_file) + with Image.open(in_file) as im: + test_file = self.tempfile("temp.png") + im.save(test_file) def test_load_verify(self): # Check open/load/verify exception (@PIL150) - im = Image.open(TEST_PNG_FILE) + with Image.open(TEST_PNG_FILE) as im: + # Assert that there is no unclosed file warning + self.assert_warning(None, im.verify) - # Assert that there is no unclosed file warning - self.assert_warning(None, im.verify) - - im = Image.open(TEST_PNG_FILE) - im.load() - self.assertRaises(RuntimeError, im.verify) + with Image.open(TEST_PNG_FILE) as im: + im.load() + self.assertRaises(RuntimeError, im.verify) def test_verify_struct_error(self): # Check open/load/verify exception (#1755) @@ -350,9 +343,9 @@ class TestFilePng(PillowTestCase): with open(TEST_PNG_FILE, "rb") as f: test_file = f.read()[:offset] - im = Image.open(BytesIO(test_file)) - self.assertIsNotNone(im.fp) - self.assertRaises((IOError, SyntaxError), im.verify) + with Image.open(BytesIO(test_file)) as im: + self.assertIsNotNone(im.fp) + self.assertRaises((IOError, SyntaxError), im.verify) def test_verify_ignores_crc_error(self): # check ignores crc errors in ancillary chunks @@ -386,9 +379,8 @@ class TestFilePng(PillowTestCase): def test_roundtrip_dpi(self): # Check dpi roundtripping - im = Image.open(TEST_PNG_FILE) - - im = roundtrip(im, dpi=(100, 100)) + with Image.open(TEST_PNG_FILE) as im: + im = roundtrip(im, dpi=(100, 100)) self.assertEqual(im.info["dpi"], (100, 100)) def test_load_dpi_rounding(self): @@ -401,9 +393,8 @@ class TestFilePng(PillowTestCase): self.assertEqual(im.info["dpi"], (72, 72)) def test_save_dpi_rounding(self): - im = Image.open(TEST_PNG_FILE) - - im = roundtrip(im, dpi=(72.2, 72.2)) + with Image.open(TEST_PNG_FILE) as im: + im = roundtrip(im, dpi=(72.2, 72.2)) self.assertEqual(im.info["dpi"], (72, 72)) im = roundtrip(im, dpi=(72.8, 72.8)) @@ -412,13 +403,12 @@ class TestFilePng(PillowTestCase): def test_roundtrip_text(self): # Check text roundtripping - im = Image.open(TEST_PNG_FILE) + with Image.open(TEST_PNG_FILE) as im: + info = PngImagePlugin.PngInfo() + info.add_text("TXT", "VALUE") + info.add_text("ZIP", "VALUE", zip=True) - info = PngImagePlugin.PngInfo() - info.add_text("TXT", "VALUE") - info.add_text("ZIP", "VALUE", zip=True) - - im = roundtrip(im, pnginfo=info) + im = roundtrip(im, pnginfo=info) self.assertEqual(im.info, {"TXT": "VALUE", "ZIP": "VALUE"}) self.assertEqual(im.text, {"TXT": "VALUE", "ZIP": "VALUE"}) @@ -480,11 +470,11 @@ class TestFilePng(PillowTestCase): # Independent file sample provided by Sebastian Spaeth. test_file = "Tests/images/caption_6_33_22.png" - im = Image.open(test_file) - self.assertEqual(im.info["transparency"], (248, 248, 248)) + with Image.open(test_file) as im: + self.assertEqual(im.info["transparency"], (248, 248, 248)) - # check saving transparency by default - im = roundtrip(im) + # check saving transparency by default + im = roundtrip(im) self.assertEqual(im.info["transparency"], (248, 248, 248)) im = roundtrip(im, transparency=(0, 1, 2)) @@ -498,10 +488,10 @@ class TestFilePng(PillowTestCase): f = self.tempfile("temp.png") im.save(f) - im2 = Image.open(f) - self.assertIn("transparency", im2.info) + with Image.open(f) as im2: + self.assertIn("transparency", im2.info) - self.assert_image_equal(im2.convert("RGBA"), im.convert("RGBA")) + self.assert_image_equal(im2.convert("RGBA"), im.convert("RGBA")) def test_trns_null(self): # Check reading images with null tRNS value, issue #1239 @@ -521,36 +511,35 @@ class TestFilePng(PillowTestCase): self.assertEqual(im.info["icc_profile"], expected_icc) def test_discard_icc_profile(self): - im = Image.open("Tests/images/icc_profile.png") - - im = roundtrip(im, icc_profile=None) + with Image.open("Tests/images/icc_profile.png") as im: + im = roundtrip(im, icc_profile=None) self.assertNotIn("icc_profile", im.info) def test_roundtrip_icc_profile(self): - im = Image.open("Tests/images/icc_profile.png") - expected_icc = im.info["icc_profile"] + with Image.open("Tests/images/icc_profile.png") as im: + expected_icc = im.info["icc_profile"] - im = roundtrip(im) + im = roundtrip(im) self.assertEqual(im.info["icc_profile"], expected_icc) def test_roundtrip_no_icc_profile(self): - im = Image.open("Tests/images/icc_profile_none.png") - self.assertIsNone(im.info["icc_profile"]) + with Image.open("Tests/images/icc_profile_none.png") as im: + self.assertIsNone(im.info["icc_profile"]) - im = roundtrip(im) + im = roundtrip(im) self.assertNotIn("icc_profile", im.info) def test_repr_png(self): im = hopper() - repr_png = Image.open(BytesIO(im._repr_png_())) - self.assertEqual(repr_png.format, "PNG") - self.assert_image_equal(im, repr_png) + with Image.open(BytesIO(im._repr_png_())) as repr_png: + self.assertEqual(repr_png.format, "PNG") + self.assert_image_equal(im, repr_png) def test_chunk_order(self): - im = Image.open("Tests/images/icc_profile.png") - test_file = self.tempfile("temp.png") - im.convert("P").save(test_file, dpi=(100, 100)) + with Image.open("Tests/images/icc_profile.png") as im: + test_file = self.tempfile("temp.png") + im.convert("P").save(test_file, dpi=(100, 100)) chunks = self.get_chunks(test_file) @@ -575,61 +564,58 @@ class TestFilePng(PillowTestCase): self.assertEqual(len(chunks), 3) def test_textual_chunks_after_idat(self): - im = Image.open("Tests/images/hopper.png") - self.assertIn("comment", im.text.keys()) - for k, v in { - "date:create": "2014-09-04T09:37:08+03:00", - "date:modify": "2014-09-04T09:37:08+03:00", - }.items(): - self.assertEqual(im.text[k], v) + with Image.open("Tests/images/hopper.png") as im: + self.assertIn("comment", im.text.keys()) + for k, v in { + "date:create": "2014-09-04T09:37:08+03:00", + "date:modify": "2014-09-04T09:37:08+03:00", + }.items(): + self.assertEqual(im.text[k], v) # Raises a SyntaxError in load_end - im = Image.open("Tests/images/broken_data_stream.png") - with self.assertRaises(IOError): - self.assertIsInstance(im.text, dict) + with Image.open("Tests/images/broken_data_stream.png") as im: + with self.assertRaises(IOError): + self.assertIsInstance(im.text, dict) # Raises a UnicodeDecodeError in load_end - im = Image.open("Tests/images/truncated_image.png") - # The file is truncated - self.assertRaises(IOError, lambda: im.text) - ImageFile.LOAD_TRUNCATED_IMAGES = True - self.assertIsInstance(im.text, dict) - ImageFile.LOAD_TRUNCATED_IMAGES = False + with Image.open("Tests/images/truncated_image.png") as im: + # The file is truncated + self.assertRaises(IOError, lambda: im.text) + ImageFile.LOAD_TRUNCATED_IMAGES = True + self.assertIsInstance(im.text, dict) + ImageFile.LOAD_TRUNCATED_IMAGES = False # Raises an EOFError in load_end - im = Image.open("Tests/images/hopper_idat_after_image_end.png") - self.assertEqual(im.text, {"TXT": "VALUE", "ZIP": "VALUE"}) + with Image.open("Tests/images/hopper_idat_after_image_end.png") as im: + self.assertEqual(im.text, {"TXT": "VALUE", "ZIP": "VALUE"}) def test_exif(self): - im = Image.open("Tests/images/exif.png") - exif = im._getexif() + with Image.open("Tests/images/exif.png") as im: + exif = im._getexif() self.assertEqual(exif[274], 1) def test_exif_save(self): - im = Image.open("Tests/images/exif.png") - - test_file = self.tempfile("temp.png") - im.save(test_file) + with Image.open("Tests/images/exif.png") as im: + test_file = self.tempfile("temp.png") + im.save(test_file) with Image.open(test_file) as reloaded: exif = reloaded._getexif() self.assertEqual(exif[274], 1) def test_exif_from_jpg(self): - im = Image.open("Tests/images/pil_sample_rgb.jpg") - - test_file = self.tempfile("temp.png") - im.save(test_file) + with Image.open("Tests/images/pil_sample_rgb.jpg") as im: + test_file = self.tempfile("temp.png") + im.save(test_file) with Image.open(test_file) as reloaded: exif = reloaded._getexif() self.assertEqual(exif[305], "Adobe Photoshop CS Macintosh") def test_exif_argument(self): - im = Image.open(TEST_PNG_FILE) - - test_file = self.tempfile("temp.png") - im.save(test_file, exif=b"exifstring") + with Image.open(TEST_PNG_FILE) as im: + test_file = self.tempfile("temp.png") + im.save(test_file, exif=b"exifstring") with Image.open(test_file) as reloaded: self.assertEqual(reloaded.info["exif"], b"Exif\x00\x00exifstring") @@ -638,12 +624,12 @@ class TestFilePng(PillowTestCase): HAVE_WEBP and _webp.HAVE_WEBPANIM, "WebP support not installed with animation" ) def test_apng(self): - im = Image.open("Tests/images/iss634.apng") - self.assertEqual(im.get_format_mimetype(), "image/apng") + with Image.open("Tests/images/iss634.apng") as im: + self.assertEqual(im.get_format_mimetype(), "image/apng") - # This also tests reading unknown PNG chunks (fcTL and fdAT) in load_end - expected = Image.open("Tests/images/iss634.webp") - self.assert_image_similar(im, expected, 0.23) + # This also tests reading unknown PNG chunks (fcTL and fdAT) in load_end + with Image.open("Tests/images/iss634.webp") as expected: + self.assert_image_similar(im, expected, 0.23) @unittest.skipIf(is_win32(), "requires Unix or macOS") diff --git a/Tests/test_file_ppm.py b/Tests/test_file_ppm.py index 226687fa3..cd3719c18 100644 --- a/Tests/test_file_ppm.py +++ b/Tests/test_file_ppm.py @@ -8,42 +8,42 @@ test_file = "Tests/images/hopper.ppm" class TestFilePpm(PillowTestCase): def test_sanity(self): - im = Image.open(test_file) - im.load() - self.assertEqual(im.mode, "RGB") - self.assertEqual(im.size, (128, 128)) - self.assertEqual(im.format, "PPM") - self.assertEqual(im.get_format_mimetype(), "image/x-portable-pixmap") + with Image.open(test_file) as im: + im.load() + self.assertEqual(im.mode, "RGB") + self.assertEqual(im.size, (128, 128)) + self.assertEqual(im.format, "PPM") + self.assertEqual(im.get_format_mimetype(), "image/x-portable-pixmap") def test_16bit_pgm(self): - im = Image.open("Tests/images/16_bit_binary.pgm") - im.load() - self.assertEqual(im.mode, "I") - self.assertEqual(im.size, (20, 100)) - self.assertEqual(im.get_format_mimetype(), "image/x-portable-graymap") + with Image.open("Tests/images/16_bit_binary.pgm") as im: + im.load() + self.assertEqual(im.mode, "I") + self.assertEqual(im.size, (20, 100)) + self.assertEqual(im.get_format_mimetype(), "image/x-portable-graymap") - tgt = Image.open("Tests/images/16_bit_binary_pgm.png") - self.assert_image_equal(im, tgt) + with Image.open("Tests/images/16_bit_binary_pgm.png") as tgt: + self.assert_image_equal(im, tgt) def test_16bit_pgm_write(self): - im = Image.open("Tests/images/16_bit_binary.pgm") - im.load() + with Image.open("Tests/images/16_bit_binary.pgm") as im: + im.load() - f = self.tempfile("temp.pgm") - im.save(f, "PPM") + f = self.tempfile("temp.pgm") + im.save(f, "PPM") - reloaded = Image.open(f) - self.assert_image_equal(im, reloaded) + with Image.open(f) as reloaded: + self.assert_image_equal(im, reloaded) def test_pnm(self): - im = Image.open("Tests/images/hopper.pnm") - self.assert_image_similar(im, hopper(), 0.0001) + with Image.open("Tests/images/hopper.pnm") as im: + self.assert_image_similar(im, hopper(), 0.0001) - f = self.tempfile("temp.pnm") - im.save(f) + f = self.tempfile("temp.pnm") + im.save(f) - reloaded = Image.open(f) - self.assert_image_equal(im, reloaded) + with Image.open(f) as reloaded: + self.assert_image_equal(im, reloaded) def test_truncated_file(self): path = self.tempfile("temp.pgm") diff --git a/Tests/test_file_sgi.py b/Tests/test_file_sgi.py index ff3aea1d5..2cb510c6f 100644 --- a/Tests/test_file_sgi.py +++ b/Tests/test_file_sgi.py @@ -9,50 +9,50 @@ class TestFileSgi(PillowTestCase): # convert hopper.ppm -compress None sgi:hopper.rgb test_file = "Tests/images/hopper.rgb" - im = Image.open(test_file) - self.assert_image_equal(im, hopper()) - self.assertEqual(im.get_format_mimetype(), "image/rgb") + with Image.open(test_file) as im: + self.assert_image_equal(im, hopper()) + self.assertEqual(im.get_format_mimetype(), "image/rgb") def test_rgb16(self): test_file = "Tests/images/hopper16.rgb" - im = Image.open(test_file) - self.assert_image_equal(im, hopper()) + with Image.open(test_file) as im: + self.assert_image_equal(im, hopper()) def test_l(self): # Created with ImageMagick # convert hopper.ppm -monochrome -compress None sgi:hopper.bw test_file = "Tests/images/hopper.bw" - im = Image.open(test_file) - self.assert_image_similar(im, hopper("L"), 2) - self.assertEqual(im.get_format_mimetype(), "image/sgi") + with Image.open(test_file) as im: + self.assert_image_similar(im, hopper("L"), 2) + self.assertEqual(im.get_format_mimetype(), "image/sgi") def test_rgba(self): # Created with ImageMagick: # convert transparent.png -compress None transparent.sgi test_file = "Tests/images/transparent.sgi" - im = Image.open(test_file) - target = Image.open("Tests/images/transparent.png") - self.assert_image_equal(im, target) - self.assertEqual(im.get_format_mimetype(), "image/sgi") + with Image.open(test_file) as im: + with Image.open("Tests/images/transparent.png") as target: + self.assert_image_equal(im, target) + self.assertEqual(im.get_format_mimetype(), "image/sgi") def test_rle(self): # Created with ImageMagick: # convert hopper.ppm hopper.sgi test_file = "Tests/images/hopper.sgi" - im = Image.open(test_file) - target = Image.open("Tests/images/hopper.rgb") - self.assert_image_equal(im, target) + with Image.open(test_file) as im: + with Image.open("Tests/images/hopper.rgb") as target: + self.assert_image_equal(im, target) def test_rle16(self): test_file = "Tests/images/tv16.sgi" - im = Image.open(test_file) - target = Image.open("Tests/images/tv.rgb") - self.assert_image_equal(im, target) + with Image.open(test_file) as im: + with Image.open("Tests/images/tv.rgb") as target: + self.assert_image_equal(im, target) def test_invalid_file(self): invalid_file = "Tests/images/flower.jpg" @@ -63,8 +63,8 @@ class TestFileSgi(PillowTestCase): def roundtrip(img): out = self.tempfile("temp.sgi") img.save(out, format="sgi") - reloaded = Image.open(out) - self.assert_image_equal(img, reloaded) + with Image.open(out) as reloaded: + self.assert_image_equal(img, reloaded) for mode in ("L", "RGB", "RGBA"): roundtrip(hopper(mode)) @@ -75,12 +75,12 @@ class TestFileSgi(PillowTestCase): def test_write16(self): test_file = "Tests/images/hopper16.rgb" - im = Image.open(test_file) - out = self.tempfile("temp.sgi") - im.save(out, format="sgi", bpc=2) + with Image.open(test_file) as im: + out = self.tempfile("temp.sgi") + im.save(out, format="sgi", bpc=2) - reloaded = Image.open(out) - self.assert_image_equal(im, reloaded) + with Image.open(out) as reloaded: + self.assert_image_equal(im, reloaded) def test_unsupported_mode(self): im = hopper("LA") diff --git a/Tests/test_file_spider.py b/Tests/test_file_spider.py index 5940c2ff2..4f617ce51 100644 --- a/Tests/test_file_spider.py +++ b/Tests/test_file_spider.py @@ -64,10 +64,10 @@ class TestImageSpider(PillowTestCase): # Assert fp.seek(0) - reloaded = Image.open(fp) - self.assertEqual(reloaded.mode, "F") - self.assertEqual(reloaded.size, (128, 128)) - self.assertEqual(reloaded.format, "SPIDER") + with Image.open(fp) as reloaded: + self.assertEqual(reloaded.mode, "F") + self.assertEqual(reloaded.size, (128, 128)) + self.assertEqual(reloaded.format, "SPIDER") def test_isSpiderImage(self): self.assertTrue(SpiderImagePlugin.isSpiderImage(TEST_FILE)) @@ -143,5 +143,5 @@ class TestImageSpider(PillowTestCase): im.save(data, format="SPIDER") data.seek(0) - im2 = Image.open(data) - self.assert_image_equal(im, im2) + with Image.open(data) as im2: + self.assert_image_equal(im, im2) diff --git a/Tests/test_file_sun.py b/Tests/test_file_sun.py index 5fc171054..835aead79 100644 --- a/Tests/test_file_sun.py +++ b/Tests/test_file_sun.py @@ -15,20 +15,20 @@ class TestFileSun(PillowTestCase): test_file = "Tests/images/hopper.ras" # Act - im = Image.open(test_file) + with Image.open(test_file) as im: - # Assert - self.assertEqual(im.size, (128, 128)) + # Assert + self.assertEqual(im.size, (128, 128)) - self.assert_image_similar(im, hopper(), 5) # visually verified + self.assert_image_similar(im, hopper(), 5) # visually verified invalid_file = "Tests/images/flower.jpg" self.assertRaises(SyntaxError, SunImagePlugin.SunImageFile, invalid_file) def test_im1(self): - im = Image.open("Tests/images/sunraster.im1") - target = Image.open("Tests/images/sunraster.im1.png") - self.assert_image_equal(im, target) + with Image.open("Tests/images/sunraster.im1") as im: + with Image.open("Tests/images/sunraster.im1.png") as target: + self.assert_image_equal(im, target) @unittest.skipIf(not os.path.exists(EXTRA_DIR), "Extra image files not installed") def test_others(self): diff --git a/Tests/test_file_tar.py b/Tests/test_file_tar.py index f381eef7e..a77e4e84c 100644 --- a/Tests/test_file_tar.py +++ b/Tests/test_file_tar.py @@ -22,11 +22,11 @@ class TestFileTar(PillowTestCase): ]: if codec in codecs: with TarIO.TarIO(TEST_TAR_FILE, test_path) as tar: - im = Image.open(tar) - im.load() - self.assertEqual(im.mode, "RGB") - self.assertEqual(im.size, (128, 128)) - self.assertEqual(im.format, format) + with Image.open(tar) as im: + im.load() + self.assertEqual(im.mode, "RGB") + self.assertEqual(im.size, (128, 128)) + self.assertEqual(im.format, format) @unittest.skipIf(is_pypy(), "Requires CPython") def test_unclosed_file(self): diff --git a/Tests/test_file_tga.py b/Tests/test_file_tga.py index bd5c32c5b..01664cd6e 100644 --- a/Tests/test_file_tga.py +++ b/Tests/test_file_tga.py @@ -24,52 +24,57 @@ class TestFileTga(PillowTestCase): ) for png_path in png_paths: - reference_im = Image.open(png_path) - self.assertEqual(reference_im.mode, mode) + with Image.open(png_path) as reference_im: + self.assertEqual(reference_im.mode, mode) - path_no_ext = os.path.splitext(png_path)[0] - for origin, rle in product(self._ORIGINS, (True, False)): - tga_path = "{}_{}_{}.tga".format( - path_no_ext, origin, "rle" if rle else "raw" - ) - - original_im = Image.open(tga_path) - self.assertEqual(original_im.format, "TGA") - self.assertEqual(original_im.get_format_mimetype(), "image/x-tga") - if rle: - self.assertEqual(original_im.info["compression"], "tga_rle") - self.assertEqual( - original_im.info["orientation"], - self._ORIGIN_TO_ORIENTATION[origin], - ) - if mode == "P": - self.assertEqual( - original_im.getpalette(), reference_im.getpalette() + path_no_ext = os.path.splitext(png_path)[0] + for origin, rle in product(self._ORIGINS, (True, False)): + tga_path = "{}_{}_{}.tga".format( + path_no_ext, origin, "rle" if rle else "raw" ) - self.assert_image_equal(original_im, reference_im) + with Image.open(tga_path) as original_im: + self.assertEqual(original_im.format, "TGA") + self.assertEqual( + original_im.get_format_mimetype(), "image/x-tga" + ) + if rle: + self.assertEqual( + original_im.info["compression"], "tga_rle" + ) + self.assertEqual( + original_im.info["orientation"], + self._ORIGIN_TO_ORIENTATION[origin], + ) + if mode == "P": + self.assertEqual( + original_im.getpalette(), reference_im.getpalette() + ) - # Generate a new test name every time so the - # test will not fail with permission error - # on Windows. - out = self.tempfile("temp.tga") + self.assert_image_equal(original_im, reference_im) - original_im.save(out, rle=rle) - saved_im = Image.open(out) - if rle: - self.assertEqual( - saved_im.info["compression"], - original_im.info["compression"], - ) - self.assertEqual( - saved_im.info["orientation"], original_im.info["orientation"] - ) - if mode == "P": - self.assertEqual( - saved_im.getpalette(), original_im.getpalette() - ) + # Generate a new test name every time so the + # test will not fail with permission error + # on Windows. + out = self.tempfile("temp.tga") - self.assert_image_equal(saved_im, original_im) + original_im.save(out, rle=rle) + with Image.open(out) as saved_im: + if rle: + self.assertEqual( + saved_im.info["compression"], + original_im.info["compression"], + ) + self.assertEqual( + saved_im.info["orientation"], + original_im.info["orientation"], + ) + if mode == "P": + self.assertEqual( + saved_im.getpalette(), original_im.getpalette() + ) + + self.assert_image_equal(saved_im, original_im) def test_id_field(self): # tga file with id field @@ -93,29 +98,27 @@ class TestFileTga(PillowTestCase): def test_save(self): test_file = "Tests/images/tga_id_field.tga" - im = Image.open(test_file) + with Image.open(test_file) as im: + out = self.tempfile("temp.tga") - out = self.tempfile("temp.tga") + # Save + im.save(out) + with Image.open(out) as test_im: + self.assertEqual(test_im.size, (100, 100)) + self.assertEqual(test_im.info["id_section"], im.info["id_section"]) - # Save - im.save(out) - with Image.open(out) as test_im: - self.assertEqual(test_im.size, (100, 100)) - self.assertEqual(test_im.info["id_section"], im.info["id_section"]) - - # RGBA save - im.convert("RGBA").save(out) + # RGBA save + im.convert("RGBA").save(out) with Image.open(out) as test_im: self.assertEqual(test_im.size, (100, 100)) def test_save_id_section(self): test_file = "Tests/images/rgb32rle.tga" - im = Image.open(test_file) + with Image.open(test_file) as im: + out = self.tempfile("temp.tga") - out = self.tempfile("temp.tga") - - # Check there is no id section - im.save(out) + # Check there is no id section + im.save(out) with Image.open(out) as test_im: self.assertNotIn("id_section", test_im.info) @@ -140,24 +143,23 @@ class TestFileTga(PillowTestCase): def test_save_orientation(self): test_file = "Tests/images/rgb32rle.tga" - im = Image.open(test_file) - self.assertEqual(im.info["orientation"], -1) - out = self.tempfile("temp.tga") + with Image.open(test_file) as im: + self.assertEqual(im.info["orientation"], -1) - im.save(out, orientation=1) + im.save(out, orientation=1) with Image.open(out) as test_im: self.assertEqual(test_im.info["orientation"], 1) def test_save_rle(self): test_file = "Tests/images/rgb32rle.tga" - im = Image.open(test_file) - self.assertEqual(im.info["compression"], "tga_rle") + with Image.open(test_file) as im: + self.assertEqual(im.info["compression"], "tga_rle") - out = self.tempfile("temp.tga") + out = self.tempfile("temp.tga") - # Save - im.save(out) + # Save + im.save(out) with Image.open(out) as test_im: self.assertEqual(test_im.size, (199, 199)) self.assertEqual(test_im.info["compression"], "tga_rle") @@ -173,11 +175,11 @@ class TestFileTga(PillowTestCase): self.assertEqual(test_im.size, (199, 199)) test_file = "Tests/images/tga_id_field.tga" - im = Image.open(test_file) - self.assertNotIn("compression", im.info) + with Image.open(test_file) as im: + self.assertNotIn("compression", im.info) - # Save with compression - im.save(out, compression="tga_rle") + # Save with compression + im.save(out, compression="tga_rle") with Image.open(out) as test_im: self.assertEqual(test_im.info["compression"], "tga_rle") @@ -186,15 +188,15 @@ class TestFileTga(PillowTestCase): num_transparent = 559 in_file = "Tests/images/la.tga" - im = Image.open(in_file) - self.assertEqual(im.mode, "LA") - self.assertEqual(im.getchannel("A").getcolors()[0][0], num_transparent) + with Image.open(in_file) as im: + self.assertEqual(im.mode, "LA") + self.assertEqual(im.getchannel("A").getcolors()[0][0], num_transparent) - out = self.tempfile("temp.tga") - im.save(out) + out = self.tempfile("temp.tga") + im.save(out) - test_im = Image.open(out) - self.assertEqual(test_im.mode, "LA") - self.assertEqual(test_im.getchannel("A").getcolors()[0][0], num_transparent) + with Image.open(out) as test_im: + self.assertEqual(test_im.mode, "LA") + self.assertEqual(test_im.getchannel("A").getcolors()[0][0], num_transparent) - self.assert_image_equal(im, test_im) + self.assert_image_equal(im, test_im) diff --git a/Tests/test_file_tiff.py b/Tests/test_file_tiff.py index a4346a7b5..2353fb34e 100644 --- a/Tests/test_file_tiff.py +++ b/Tests/test_file_tiff.py @@ -72,22 +72,20 @@ class TestFileTiff(PillowTestCase): # Read RGBa images from macOS [@PIL136] filename = "Tests/images/pil136.tiff" - im = Image.open(filename) + with Image.open(filename) as im: + self.assertEqual(im.mode, "RGBA") + self.assertEqual(im.size, (55, 43)) + self.assertEqual(im.tile, [("raw", (0, 0, 55, 43), 8, ("RGBa", 0, 1))]) + im.load() - self.assertEqual(im.mode, "RGBA") - self.assertEqual(im.size, (55, 43)) - self.assertEqual(im.tile, [("raw", (0, 0, 55, 43), 8, ("RGBa", 0, 1))]) - im.load() - - self.assert_image_similar_tofile(im, "Tests/images/pil136.png", 1) + self.assert_image_similar_tofile(im, "Tests/images/pil136.png", 1) def test_wrong_bits_per_sample(self): - im = Image.open("Tests/images/tiff_wrong_bits_per_sample.tiff") - - self.assertEqual(im.mode, "RGBA") - self.assertEqual(im.size, (52, 53)) - self.assertEqual(im.tile, [("raw", (0, 0, 52, 53), 160, ("RGBA", 0, 1))]) - im.load() + with Image.open("Tests/images/tiff_wrong_bits_per_sample.tiff") as im: + self.assertEqual(im.mode, "RGBA") + self.assertEqual(im.size, (52, 53)) + self.assertEqual(im.tile, [("raw", (0, 0, 52, 53), 160, ("RGBA", 0, 1))]) + im.load() def test_set_legacy_api(self): ifd = TiffImagePlugin.ImageFileDirectory_v2() @@ -149,23 +147,22 @@ class TestFileTiff(PillowTestCase): def test_save_dpi_rounding(self): outfile = self.tempfile("temp.tif") - im = Image.open("Tests/images/hopper.tif") + with Image.open("Tests/images/hopper.tif") as im: + for dpi in (72.2, 72.8): + im.save(outfile, dpi=(dpi, dpi)) - for dpi in (72.2, 72.8): - im.save(outfile, dpi=(dpi, dpi)) - - reloaded = Image.open(outfile) - reloaded.load() - self.assertEqual((round(dpi), round(dpi)), reloaded.info["dpi"]) + with Image.open(outfile) as reloaded: + reloaded.load() + self.assertEqual((round(dpi), round(dpi)), reloaded.info["dpi"]) def test_save_setting_missing_resolution(self): b = BytesIO() Image.open("Tests/images/10ct_32bit_128.tiff").save( b, format="tiff", resolution=123.45 ) - im = Image.open(b) - self.assertEqual(float(im.tag_v2[X_RESOLUTION]), 123.45) - self.assertEqual(float(im.tag_v2[Y_RESOLUTION]), 123.45) + with Image.open(b) as im: + self.assertEqual(float(im.tag_v2[X_RESOLUTION]), 123.45) + self.assertEqual(float(im.tag_v2[Y_RESOLUTION]), 123.45) def test_invalid_file(self): invalid_file = "Tests/images/flower.jpg" @@ -192,55 +189,53 @@ class TestFileTiff(PillowTestCase): self.assertRaises(IOError, im.save, outfile) def test_little_endian(self): - im = Image.open("Tests/images/16bit.cropped.tif") - self.assertEqual(im.getpixel((0, 0)), 480) - self.assertEqual(im.mode, "I;16") + with Image.open("Tests/images/16bit.cropped.tif") as im: + self.assertEqual(im.getpixel((0, 0)), 480) + self.assertEqual(im.mode, "I;16") - b = im.tobytes() + b = im.tobytes() # Bytes are in image native order (little endian) self.assertEqual(b[0], ord(b"\xe0")) self.assertEqual(b[1], ord(b"\x01")) def test_big_endian(self): - im = Image.open("Tests/images/16bit.MM.cropped.tif") - self.assertEqual(im.getpixel((0, 0)), 480) - self.assertEqual(im.mode, "I;16B") - - b = im.tobytes() + with Image.open("Tests/images/16bit.MM.cropped.tif") as im: + self.assertEqual(im.getpixel((0, 0)), 480) + self.assertEqual(im.mode, "I;16B") + b = im.tobytes() # Bytes are in image native order (big endian) self.assertEqual(b[0], ord(b"\x01")) self.assertEqual(b[1], ord(b"\xe0")) def test_16bit_s(self): - im = Image.open("Tests/images/16bit.s.tif") - im.load() - self.assertEqual(im.mode, "I") - self.assertEqual(im.getpixel((0, 0)), 32767) - self.assertEqual(im.getpixel((0, 1)), 0) + with Image.open("Tests/images/16bit.s.tif") as im: + im.load() + self.assertEqual(im.mode, "I") + self.assertEqual(im.getpixel((0, 0)), 32767) + self.assertEqual(im.getpixel((0, 1)), 0) def test_12bit_rawmode(self): """ Are we generating the same interpretation of the image as Imagemagick is? """ - im = Image.open("Tests/images/12bit.cropped.tif") + with Image.open("Tests/images/12bit.cropped.tif") as im: + # to make the target -- + # convert 12bit.cropped.tif -depth 16 tmp.tif + # convert tmp.tif -evaluate RightShift 4 12in16bit2.tif + # imagemagick will auto scale so that a 12bit FFF is 16bit FFF0, + # so we need to unshift so that the integer values are the same. - # to make the target -- - # convert 12bit.cropped.tif -depth 16 tmp.tif - # convert tmp.tif -evaluate RightShift 4 12in16bit2.tif - # imagemagick will auto scale so that a 12bit FFF is 16bit FFF0, - # so we need to unshift so that the integer values are the same. - - self.assert_image_equal_tofile(im, "Tests/images/12in16bit.tif") + self.assert_image_equal_tofile(im, "Tests/images/12in16bit.tif") def test_32bit_float(self): # Issue 614, specific 32-bit float format path = "Tests/images/10ct_32bit_128.tiff" - im = Image.open(path) - im.load() + with Image.open(path) as im: + im.load() - self.assertEqual(im.getpixel((0, 0)), -0.4526388943195343) - self.assertEqual(im.getextrema(), (-3.140936851501465, 3.140684127807617)) + self.assertEqual(im.getpixel((0, 0)), -0.4526388943195343) + self.assertEqual(im.getextrema(), (-3.140936851501465, 3.140684127807617)) def test_unknown_pixel_mode(self): self.assertRaises( @@ -292,10 +287,10 @@ class TestFileTiff(PillowTestCase): self.assertEqual(im.convert("RGB").getpixel((0, 0)), (0, 0, 255)) def test_multipage_last_frame(self): - im = Image.open("Tests/images/multipage-lastframe.tif") - im.load() - self.assertEqual(im.size, (20, 20)) - self.assertEqual(im.convert("RGB").getpixel((0, 0)), (0, 0, 255)) + with Image.open("Tests/images/multipage-lastframe.tif") as im: + im.load() + self.assertEqual(im.size, (20, 20)) + self.assertEqual(im.convert("RGB").getpixel((0, 0)), (0, 0, 255)) def test___str__(self): filename = "Tests/images/pil136.tiff" @@ -411,10 +406,10 @@ class TestFileTiff(PillowTestCase): def test_4bit(self): test_file = "Tests/images/hopper_gray_4bpp.tif" original = hopper("L") - im = Image.open(test_file) - self.assertEqual(im.size, (128, 128)) - self.assertEqual(im.mode, "L") - self.assert_image_similar(im, original, 7.3) + with Image.open(test_file) as im: + self.assertEqual(im.size, (128, 128)) + self.assertEqual(im.mode, "L") + self.assert_image_similar(im, original, 7.3) def test_gray_semibyte_per_pixel(self): test_files = ( @@ -439,15 +434,15 @@ class TestFileTiff(PillowTestCase): ) original = hopper("L") for epsilon, group in test_files: - im = Image.open(group[0]) - self.assertEqual(im.size, (128, 128)) - self.assertEqual(im.mode, "L") - self.assert_image_similar(im, original, epsilon) - for file in group[1:]: - im2 = Image.open(file) - self.assertEqual(im2.size, (128, 128)) - self.assertEqual(im2.mode, "L") - self.assert_image_equal(im, im2) + with Image.open(group[0]) as im: + self.assertEqual(im.size, (128, 128)) + self.assertEqual(im.mode, "L") + self.assert_image_similar(im, original, epsilon) + for file in group[1:]: + with Image.open(file) as im2: + self.assertEqual(im2.size, (128, 128)) + self.assertEqual(im2.mode, "L") + self.assert_image_equal(im, im2) def test_with_underscores(self): kwargs = {"resolution_unit": "inch", "x_resolution": 72, "y_resolution": 36} @@ -467,29 +462,26 @@ class TestFileTiff(PillowTestCase): # Test an image of all '0' values pixel_value = 0x1234 infile = "Tests/images/uint16_1_4660.tif" - im = Image.open(infile) - self.assertEqual(im.getpixel((0, 0)), pixel_value) + with Image.open(infile) as im: + self.assertEqual(im.getpixel((0, 0)), pixel_value) - tmpfile = self.tempfile("temp.tif") - im.save(tmpfile) + tmpfile = self.tempfile("temp.tif") + im.save(tmpfile) - reloaded = Image.open(tmpfile) - - self.assert_image_equal(im, reloaded) + with Image.open(tmpfile) as reloaded: + self.assert_image_equal(im, reloaded) def test_strip_raw(self): infile = "Tests/images/tiff_strip_raw.tif" - im = Image.open(infile) - - self.assert_image_equal_tofile(im, "Tests/images/tiff_adobe_deflate.png") + with Image.open(infile) as im: + self.assert_image_equal_tofile(im, "Tests/images/tiff_adobe_deflate.png") def test_strip_planar_raw(self): # gdal_translate -of GTiff -co INTERLEAVE=BAND \ # tiff_strip_raw.tif tiff_strip_planar_raw.tiff infile = "Tests/images/tiff_strip_planar_raw.tif" - im = Image.open(infile) - - self.assert_image_equal_tofile(im, "Tests/images/tiff_adobe_deflate.png") + with Image.open(infile) as im: + self.assert_image_equal_tofile(im, "Tests/images/tiff_adobe_deflate.png") def test_strip_planar_raw_with_overviews(self): # gdaladdo tiff_strip_planar_raw2.tif 2 4 8 16 @@ -502,9 +494,8 @@ class TestFileTiff(PillowTestCase): # -co BLOCKYSIZE=32 -co INTERLEAVE=BAND \ # tiff_tiled_raw.tif tiff_tiled_planar_raw.tiff infile = "Tests/images/tiff_tiled_planar_raw.tif" - im = Image.open(infile) - - self.assert_image_equal_tofile(im, "Tests/images/tiff_adobe_deflate.png") + with Image.open(infile) as im: + self.assert_image_equal_tofile(im, "Tests/images/tiff_adobe_deflate.png") def test_palette(self): for mode in ["P", "PA"]: @@ -513,8 +504,8 @@ class TestFileTiff(PillowTestCase): im = hopper(mode) im.save(outfile) - reloaded = Image.open(outfile) - self.assert_image_equal(im.convert("RGB"), reloaded.convert("RGB")) + with Image.open(outfile) as reloaded: + self.assert_image_equal(im.convert("RGB"), reloaded.convert("RGB")) def test_tiff_save_all(self): mp = BytesIO() @@ -532,8 +523,8 @@ class TestFileTiff(PillowTestCase): im.copy().save(mp, format="TIFF", save_all=True, append_images=ims) mp.seek(0, os.SEEK_SET) - reread = Image.open(mp) - self.assertEqual(reread.n_frames, 3) + with Image.open(mp) as reread: + self.assertEqual(reread.n_frames, 3) # Test appending using a generator def imGenerator(ims): @@ -543,8 +534,8 @@ class TestFileTiff(PillowTestCase): im.save(mp, format="TIFF", save_all=True, append_images=imGenerator(ims)) mp.seek(0, os.SEEK_SET) - reread = Image.open(mp) - self.assertEqual(reread.n_frames, 3) + with Image.open(mp) as reread: + self.assertEqual(reread.n_frames, 3) def test_saving_icc_profile(self): # Tests saving TIFF with icc_profile set. diff --git a/Tests/test_file_tiff_metadata.py b/Tests/test_file_tiff_metadata.py index 7393a147c..bf8d6e933 100644 --- a/Tests/test_file_tiff_metadata.py +++ b/Tests/test_file_tiff_metadata.py @@ -130,14 +130,13 @@ class TestFileTiffMetadata(PillowTestCase): def test_write_metadata(self): """ Test metadata writing through the python code """ - img = Image.open("Tests/images/hopper.tif") - - f = self.tempfile("temp.tiff") - img.save(f, tiffinfo=img.tag) - - with Image.open(f) as loaded: + with Image.open("Tests/images/hopper.tif") as img: + f = self.tempfile("temp.tiff") + img.save(f, tiffinfo=img.tag) original = img.tag_v2.named() + + with Image.open(f) as loaded: reloaded = loaded.tag_v2.named() for k, v in original.items(): @@ -187,10 +186,10 @@ class TestFileTiffMetadata(PillowTestCase): def test_iccprofile(self): # https://github.com/python-pillow/Pillow/issues/1462 - im = Image.open("Tests/images/hopper.iccprofile.tif") out = self.tempfile("temp.tiff") + with Image.open("Tests/images/hopper.iccprofile.tif") as im: + im.save(out) - im.save(out) with Image.open(out) as reloaded: self.assertNotIsInstance(im.info["icc_profile"], tuple) self.assertEqual(im.info["icc_profile"], reloaded.info["icc_profile"]) @@ -205,14 +204,14 @@ class TestFileTiffMetadata(PillowTestCase): self.assertTrue(im.info["icc_profile"]) def test_iccprofile_save_png(self): - im = Image.open("Tests/images/hopper.iccprofile.tif") - outfile = self.tempfile("temp.png") - im.save(outfile) + with Image.open("Tests/images/hopper.iccprofile.tif") as im: + outfile = self.tempfile("temp.png") + im.save(outfile) def test_iccprofile_binary_save_png(self): - im = Image.open("Tests/images/hopper.iccprofile_binary.tif") - outfile = self.tempfile("temp.png") - im.save(outfile) + with Image.open("Tests/images/hopper.iccprofile_binary.tif") as im: + outfile = self.tempfile("temp.png") + im.save(outfile) def test_exif_div_zero(self): im = hopper() @@ -241,12 +240,11 @@ class TestFileTiffMetadata(PillowTestCase): self.assertIn(33432, info) def test_PhotoshopInfo(self): - im = Image.open("Tests/images/issue_2278.tif") - - self.assertEqual(len(im.tag_v2[34377]), 1) - self.assertIsInstance(im.tag_v2[34377][0], bytes) - out = self.tempfile("temp.tiff") - im.save(out) + with Image.open("Tests/images/issue_2278.tif") as im: + self.assertEqual(len(im.tag_v2[34377]), 1) + self.assertIsInstance(im.tag_v2[34377][0], bytes) + out = self.tempfile("temp.tiff") + im.save(out) with Image.open(out) as reloaded: self.assertEqual(len(reloaded.tag_v2[34377]), 1) self.assertIsInstance(reloaded.tag_v2[34377][0], bytes) diff --git a/Tests/test_file_webp.py b/Tests/test_file_webp.py index 685f876e2..a2fdbc333 100644 --- a/Tests/test_file_webp.py +++ b/Tests/test_file_webp.py @@ -41,19 +41,18 @@ class TestFileWebp(PillowTestCase): Does it have the bits we expect? """ - image = Image.open("Tests/images/hopper.webp") + with Image.open("Tests/images/hopper.webp") as image: + self.assertEqual(image.mode, self.rgb_mode) + self.assertEqual(image.size, (128, 128)) + self.assertEqual(image.format, "WEBP") + image.load() + image.getdata() - self.assertEqual(image.mode, self.rgb_mode) - self.assertEqual(image.size, (128, 128)) - self.assertEqual(image.format, "WEBP") - image.load() - image.getdata() - - # generated with: - # dwebp -ppm ../../Tests/images/hopper.webp -o hopper_webp_bits.ppm - self.assert_image_similar_tofile( - image, "Tests/images/hopper_webp_bits.ppm", 1.0 - ) + # generated with: + # dwebp -ppm ../../Tests/images/hopper.webp -o hopper_webp_bits.ppm + self.assert_image_similar_tofile( + image, "Tests/images/hopper_webp_bits.ppm", 1.0 + ) def test_write_rgb(self): """ @@ -64,26 +63,25 @@ class TestFileWebp(PillowTestCase): temp_file = self.tempfile("temp.webp") hopper(self.rgb_mode).save(temp_file) - image = Image.open(temp_file) + with Image.open(temp_file) as image: + self.assertEqual(image.mode, self.rgb_mode) + self.assertEqual(image.size, (128, 128)) + self.assertEqual(image.format, "WEBP") + image.load() + image.getdata() - self.assertEqual(image.mode, self.rgb_mode) - self.assertEqual(image.size, (128, 128)) - self.assertEqual(image.format, "WEBP") - image.load() - image.getdata() + # generated with: dwebp -ppm temp.webp -o hopper_webp_write.ppm + self.assert_image_similar_tofile( + image, "Tests/images/hopper_webp_write.ppm", 12.0 + ) - # generated with: dwebp -ppm temp.webp -o hopper_webp_write.ppm - self.assert_image_similar_tofile( - image, "Tests/images/hopper_webp_write.ppm", 12.0 - ) - - # This test asserts that the images are similar. If the average pixel - # difference between the two images is less than the epsilon value, - # then we're going to accept that it's a reasonable lossy version of - # the image. The old lena images for WebP are showing ~16 on - # Ubuntu, the jpegs are showing ~18. - target = hopper(self.rgb_mode) - self.assert_image_similar(image, target, 12.0) + # This test asserts that the images are similar. If the average pixel + # difference between the two images is less than the epsilon value, + # then we're going to accept that it's a reasonable lossy version of + # the image. The old lena images for WebP are showing ~16 on + # Ubuntu, the jpegs are showing ~18. + target = hopper(self.rgb_mode) + self.assert_image_similar(image, target, 12.0) def test_write_unsupported_mode_L(self): """ @@ -93,17 +91,16 @@ class TestFileWebp(PillowTestCase): temp_file = self.tempfile("temp.webp") hopper("L").save(temp_file) - image = Image.open(temp_file) + with Image.open(temp_file) as image: + self.assertEqual(image.mode, self.rgb_mode) + self.assertEqual(image.size, (128, 128)) + self.assertEqual(image.format, "WEBP") - self.assertEqual(image.mode, self.rgb_mode) - self.assertEqual(image.size, (128, 128)) - self.assertEqual(image.format, "WEBP") + image.load() + image.getdata() + target = hopper("L").convert(self.rgb_mode) - image.load() - image.getdata() - target = hopper("L").convert(self.rgb_mode) - - self.assert_image_similar(image, target, 10.0) + self.assert_image_similar(image, target, 10.0) def test_write_unsupported_mode_P(self): """ @@ -113,17 +110,16 @@ class TestFileWebp(PillowTestCase): temp_file = self.tempfile("temp.webp") hopper("P").save(temp_file) - image = Image.open(temp_file) + with Image.open(temp_file) as image: + self.assertEqual(image.mode, self.rgb_mode) + self.assertEqual(image.size, (128, 128)) + self.assertEqual(image.format, "WEBP") - self.assertEqual(image.mode, self.rgb_mode) - self.assertEqual(image.size, (128, 128)) - self.assertEqual(image.format, "WEBP") + image.load() + image.getdata() + target = hopper("P").convert(self.rgb_mode) - image.load() - image.getdata() - target = hopper("P").convert(self.rgb_mode) - - self.assert_image_similar(image, target, 50.0) + self.assert_image_similar(image, target, 50.0) def test_WebPEncode_with_invalid_args(self): """ @@ -145,10 +141,9 @@ class TestFileWebp(PillowTestCase): def test_no_resource_warning(self): file_path = "Tests/images/hopper.webp" - image = Image.open(file_path) - - temp_file = self.tempfile("temp.webp") - self.assert_warning(None, image.save, temp_file) + with Image.open(file_path) as image: + temp_file = self.tempfile("temp.webp") + self.assert_warning(None, image.save, temp_file) def test_file_pointer_could_be_reused(self): file_path = "Tests/images/hopper.webp" diff --git a/Tests/test_file_webp_alpha.py b/Tests/test_file_webp_alpha.py index e3c2b98b9..85a1e3d2f 100644 --- a/Tests/test_file_webp_alpha.py +++ b/Tests/test_file_webp_alpha.py @@ -26,18 +26,17 @@ class TestFileWebpAlpha(PillowTestCase): # Generated with `cwebp transparent.png -o transparent.webp` file_path = "Tests/images/transparent.webp" - image = Image.open(file_path) + with Image.open(file_path) as image: + self.assertEqual(image.mode, "RGBA") + self.assertEqual(image.size, (200, 150)) + self.assertEqual(image.format, "WEBP") + image.load() + image.getdata() - self.assertEqual(image.mode, "RGBA") - self.assertEqual(image.size, (200, 150)) - self.assertEqual(image.format, "WEBP") - image.load() - image.getdata() + image.tobytes() - image.tobytes() - - target = Image.open("Tests/images/transparent.png") - self.assert_image_similar(image, target, 20.0) + with Image.open("Tests/images/transparent.png") as target: + self.assert_image_similar(image, target, 20.0) def test_write_lossless_rgb(self): """ @@ -56,16 +55,16 @@ class TestFileWebpAlpha(PillowTestCase): pil_image.save(temp_file, lossless=True) - image = Image.open(temp_file) - image.load() + with Image.open(temp_file) as image: + image.load() - self.assertEqual(image.mode, "RGBA") - self.assertEqual(image.size, pil_image.size) - self.assertEqual(image.format, "WEBP") - image.load() - image.getdata() + self.assertEqual(image.mode, "RGBA") + self.assertEqual(image.size, pil_image.size) + self.assertEqual(image.format, "WEBP") + image.load() + image.getdata() - self.assert_image_equal(image, pil_image) + self.assert_image_equal(image, pil_image) def test_write_rgba(self): """ @@ -81,21 +80,21 @@ class TestFileWebpAlpha(PillowTestCase): if _webp.WebPDecoderBuggyAlpha(self): return - image = Image.open(temp_file) - image.load() + with Image.open(temp_file) as image: + image.load() - self.assertEqual(image.mode, "RGBA") - self.assertEqual(image.size, (10, 10)) - self.assertEqual(image.format, "WEBP") - image.load() - image.getdata() + self.assertEqual(image.mode, "RGBA") + self.assertEqual(image.size, (10, 10)) + self.assertEqual(image.format, "WEBP") + image.load() + image.getdata() - # 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: - self.assert_image_similar(image, pil_image, 1.0) + # 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: + self.assert_image_similar(image, pil_image, 1.0) def test_write_unsupported_mode_PA(self): """ @@ -107,15 +106,14 @@ class TestFileWebpAlpha(PillowTestCase): file_path = "Tests/images/transparent.gif" with Image.open(file_path) as im: im.save(temp_file) - image = Image.open(temp_file) + with Image.open(temp_file) as image: + self.assertEqual(image.mode, "RGBA") + self.assertEqual(image.size, (200, 150)) + self.assertEqual(image.format, "WEBP") - self.assertEqual(image.mode, "RGBA") - self.assertEqual(image.size, (200, 150)) - self.assertEqual(image.format, "WEBP") + image.load() + image.getdata() + with Image.open(file_path) as im: + target = im.convert("RGBA") - image.load() - image.getdata() - with Image.open(file_path) as im: - target = im.convert("RGBA") - - self.assert_image_similar(image, target, 25.0) + self.assert_image_similar(image, target, 25.0) diff --git a/Tests/test_file_webp_animated.py b/Tests/test_file_webp_animated.py index 834080db8..bf425d079 100644 --- a/Tests/test_file_webp_animated.py +++ b/Tests/test_file_webp_animated.py @@ -48,18 +48,18 @@ class TestFileWebpAnimation(PillowTestCase): temp_file = self.tempfile("temp.webp") orig.save(temp_file, save_all=True) - im = Image.open(temp_file) - self.assertEqual(im.n_frames, orig.n_frames) + with Image.open(temp_file) as im: + self.assertEqual(im.n_frames, orig.n_frames) - # Compare first and last frames to the original animated GIF - orig.load() - im.load() - self.assert_image_similar(im, orig.convert("RGBA"), 25.0) - orig.seek(orig.n_frames - 1) - im.seek(im.n_frames - 1) - orig.load() - im.load() - self.assert_image_similar(im, orig.convert("RGBA"), 25.0) + # Compare first and last frames to the original animated GIF + orig.load() + im.load() + self.assert_image_similar(im, orig.convert("RGBA"), 25.0) + orig.seek(orig.n_frames - 1) + im.seek(im.n_frames - 1) + orig.load() + im.load() + self.assert_image_similar(im, orig.convert("RGBA"), 25.0) def test_write_animation_RGB(self): """ @@ -68,39 +68,38 @@ class TestFileWebpAnimation(PillowTestCase): """ def check(temp_file): - im = Image.open(temp_file) - self.assertEqual(im.n_frames, 2) + with Image.open(temp_file) as im: + self.assertEqual(im.n_frames, 2) - # Compare first frame to original - im.load() - self.assert_image_equal(im, frame1.convert("RGBA")) + # Compare first frame to original + im.load() + self.assert_image_equal(im, frame1.convert("RGBA")) - # Compare second frame to original - im.seek(1) - im.load() - self.assert_image_equal(im, frame2.convert("RGBA")) + # Compare second frame to original + im.seek(1) + im.load() + self.assert_image_equal(im, frame2.convert("RGBA")) - frame1 = Image.open("Tests/images/anim_frame1.webp") - frame2 = Image.open("Tests/images/anim_frame2.webp") + with Image.open("Tests/images/anim_frame1.webp") as frame1: + with Image.open("Tests/images/anim_frame2.webp") as frame2: + temp_file1 = self.tempfile("temp.webp") + frame1.copy().save( + temp_file1, save_all=True, append_images=[frame2], lossless=True + ) + check(temp_file1) - temp_file1 = self.tempfile("temp.webp") - frame1.copy().save( - temp_file1, save_all=True, append_images=[frame2], lossless=True - ) - check(temp_file1) + # Tests appending using a generator + def imGenerator(ims): + yield from ims - # Tests appending using a generator - def imGenerator(ims): - yield from ims - - temp_file2 = self.tempfile("temp_generator.webp") - frame1.copy().save( - temp_file2, - save_all=True, - append_images=imGenerator([frame2]), - lossless=True, - ) - check(temp_file2) + temp_file2 = self.tempfile("temp_generator.webp") + frame1.copy().save( + temp_file2, + save_all=True, + append_images=imGenerator([frame2]), + lossless=True, + ) + check(temp_file2) def test_timestamp_and_duration(self): """ @@ -110,27 +109,27 @@ class TestFileWebpAnimation(PillowTestCase): durations = [0, 10, 20, 30, 40] temp_file = self.tempfile("temp.webp") - frame1 = Image.open("Tests/images/anim_frame1.webp") - frame2 = Image.open("Tests/images/anim_frame2.webp") - frame1.save( - temp_file, - save_all=True, - append_images=[frame2, frame1, frame2, frame1], - duration=durations, - ) + with Image.open("Tests/images/anim_frame1.webp") as frame1: + with Image.open("Tests/images/anim_frame2.webp") as frame2: + frame1.save( + temp_file, + save_all=True, + append_images=[frame2, frame1, frame2, frame1], + duration=durations, + ) - im = Image.open(temp_file) - self.assertEqual(im.n_frames, 5) - self.assertTrue(im.is_animated) + with Image.open(temp_file) as im: + self.assertEqual(im.n_frames, 5) + self.assertTrue(im.is_animated) - # Check that timestamps and durations match original values specified - ts = 0 - for frame in range(im.n_frames): - im.seek(frame) - im.load() - self.assertEqual(im.info["duration"], durations[frame]) - self.assertEqual(im.info["timestamp"], ts) - ts += durations[frame] + # Check that timestamps and durations match original values specified + ts = 0 + for frame in range(im.n_frames): + im.seek(frame) + im.load() + self.assertEqual(im.info["duration"], durations[frame]) + self.assertEqual(im.info["timestamp"], ts) + ts += durations[frame] def test_seeking(self): """ @@ -141,24 +140,24 @@ class TestFileWebpAnimation(PillowTestCase): dur = 33 temp_file = self.tempfile("temp.webp") - frame1 = Image.open("Tests/images/anim_frame1.webp") - frame2 = Image.open("Tests/images/anim_frame2.webp") - frame1.save( - temp_file, - save_all=True, - append_images=[frame2, frame1, frame2, frame1], - duration=dur, - ) + with Image.open("Tests/images/anim_frame1.webp") as frame1: + with Image.open("Tests/images/anim_frame2.webp") as frame2: + frame1.save( + temp_file, + save_all=True, + append_images=[frame2, frame1, frame2, frame1], + duration=dur, + ) - im = Image.open(temp_file) - self.assertEqual(im.n_frames, 5) - self.assertTrue(im.is_animated) + with Image.open(temp_file) as im: + self.assertEqual(im.n_frames, 5) + self.assertTrue(im.is_animated) - # Traverse frames in reverse, checking timestamps and durations - ts = dur * (im.n_frames - 1) - for frame in reversed(range(im.n_frames)): - im.seek(frame) - im.load() - self.assertEqual(im.info["duration"], dur) - self.assertEqual(im.info["timestamp"], ts) - ts -= dur + # Traverse frames in reverse, checking timestamps and durations + ts = dur * (im.n_frames - 1) + for frame in reversed(range(im.n_frames)): + im.seek(frame) + im.load() + self.assertEqual(im.info["duration"], dur) + self.assertEqual(im.info["timestamp"], ts) + ts -= dur diff --git a/Tests/test_file_webp_lossless.py b/Tests/test_file_webp_lossless.py index 2eff41529..5d184a766 100644 --- a/Tests/test_file_webp_lossless.py +++ b/Tests/test_file_webp_lossless.py @@ -26,13 +26,13 @@ class TestFileWebpLossless(PillowTestCase): hopper(self.rgb_mode).save(temp_file, lossless=True) - image = Image.open(temp_file) - image.load() + with Image.open(temp_file) as image: + image.load() - self.assertEqual(image.mode, self.rgb_mode) - self.assertEqual(image.size, (128, 128)) - self.assertEqual(image.format, "WEBP") - image.load() - image.getdata() + self.assertEqual(image.mode, self.rgb_mode) + self.assertEqual(image.size, (128, 128)) + self.assertEqual(image.format, "WEBP") + image.load() + image.getdata() - self.assert_image_equal(image, hopper(self.rgb_mode)) + self.assert_image_equal(image, hopper(self.rgb_mode)) diff --git a/Tests/test_file_webp_metadata.py b/Tests/test_file_webp_metadata.py index e3acf745d..06c780299 100644 --- a/Tests/test_file_webp_metadata.py +++ b/Tests/test_file_webp_metadata.py @@ -42,17 +42,15 @@ class TestFileWebpMetadata(PillowTestCase): def test_write_exif_metadata(self): file_path = "Tests/images/flower.jpg" - image = Image.open(file_path) - expected_exif = image.info["exif"] - test_buffer = BytesIO() + with Image.open(file_path) as image: + expected_exif = image.info["exif"] - image.save(test_buffer, "webp", exif=expected_exif) + image.save(test_buffer, "webp", exif=expected_exif) test_buffer.seek(0) - webp_image = Image.open(test_buffer) - - webp_exif = webp_image.info.get("exif", None) + with Image.open(test_buffer) as webp_image: + webp_exif = webp_image.info.get("exif", None) self.assertTrue(webp_exif) if webp_exif: self.assertEqual(webp_exif, expected_exif, "WebP EXIF didn't match") @@ -74,17 +72,15 @@ class TestFileWebpMetadata(PillowTestCase): def test_write_icc_metadata(self): file_path = "Tests/images/flower2.jpg" - image = Image.open(file_path) - expected_icc_profile = image.info["icc_profile"] - test_buffer = BytesIO() + with Image.open(file_path) as image: + expected_icc_profile = image.info["icc_profile"] - image.save(test_buffer, "webp", icc_profile=expected_icc_profile) + image.save(test_buffer, "webp", icc_profile=expected_icc_profile) test_buffer.seek(0) - webp_image = Image.open(test_buffer) - - webp_icc_profile = webp_image.info.get("icc_profile", None) + with Image.open(test_buffer) as webp_image: + webp_icc_profile = webp_image.info.get("icc_profile", None) self.assertTrue(webp_icc_profile) if webp_icc_profile: @@ -94,17 +90,15 @@ class TestFileWebpMetadata(PillowTestCase): def test_read_no_exif(self): file_path = "Tests/images/flower.jpg" - image = Image.open(file_path) - self.assertIn("exif", image.info) - test_buffer = BytesIO() + with Image.open(file_path) as image: + self.assertIn("exif", image.info) - image.save(test_buffer, "webp") + image.save(test_buffer, "webp") test_buffer.seek(0) - webp_image = Image.open(test_buffer) - - self.assertFalse(webp_image._getexif()) + with Image.open(test_buffer) as webp_image: + self.assertFalse(webp_image._getexif()) def test_write_animated_metadata(self): if not _webp.HAVE_WEBPANIM: @@ -115,16 +109,16 @@ class TestFileWebpMetadata(PillowTestCase): xmp_data = b"" temp_file = self.tempfile("temp.webp") - frame1 = Image.open("Tests/images/anim_frame1.webp") - frame2 = Image.open("Tests/images/anim_frame2.webp") - frame1.save( - temp_file, - save_all=True, - append_images=[frame2, frame1, frame2], - icc_profile=iccp_data, - exif=exif_data, - xmp=xmp_data, - ) + with Image.open("Tests/images/anim_frame1.webp") as frame1: + with Image.open("Tests/images/anim_frame2.webp") as frame2: + frame1.save( + temp_file, + save_all=True, + append_images=[frame2, frame1, frame2], + icc_profile=iccp_data, + exif=exif_data, + xmp=xmp_data, + ) with Image.open(temp_file) as image: self.assertIn("icc_profile", image.info) diff --git a/Tests/test_file_wmf.py b/Tests/test_file_wmf.py index c07bbf60a..01a802bfb 100644 --- a/Tests/test_file_wmf.py +++ b/Tests/test_file_wmf.py @@ -12,9 +12,9 @@ class TestFileWmf(PillowTestCase): # Currently, support for WMF/EMF is Windows-only im.load() # Compare to reference rendering - imref = Image.open("Tests/images/drawing_emf_ref.png") - imref.load() - self.assert_image_similar(im, imref, 0) + with Image.open("Tests/images/drawing_emf_ref.png") as imref: + imref.load() + self.assert_image_similar(im, imref, 0) # Test basic WMF open and rendering with Image.open("Tests/images/drawing.wmf") as im: @@ -22,9 +22,9 @@ class TestFileWmf(PillowTestCase): # Currently, support for WMF/EMF is Windows-only im.load() # Compare to reference rendering - imref = Image.open("Tests/images/drawing_wmf_ref.png") - imref.load() - self.assert_image_similar(im, imref, 2.0) + with Image.open("Tests/images/drawing_wmf_ref.png") as imref: + imref.load() + self.assert_image_similar(im, imref, 2.0) def test_register_handler(self): class TestHandler: diff --git a/Tests/test_file_xbm.py b/Tests/test_file_xbm.py index 5a1eb54bc..972c5abe0 100644 --- a/Tests/test_file_xbm.py +++ b/Tests/test_file_xbm.py @@ -30,11 +30,10 @@ static char basic_bits[] = { class TestFileXbm(PillowTestCase): def test_pil151(self): - im = Image.open(BytesIO(PIL151)) - - im.load() - self.assertEqual(im.mode, "1") - self.assertEqual(im.size, (32, 32)) + with Image.open(BytesIO(PIL151)) as im: + im.load() + self.assertEqual(im.mode, "1") + self.assertEqual(im.size, (32, 32)) def test_open(self): # Arrange diff --git a/Tests/test_file_xpm.py b/Tests/test_file_xpm.py index a4ea8c491..38ecbc3de 100644 --- a/Tests/test_file_xpm.py +++ b/Tests/test_file_xpm.py @@ -7,14 +7,14 @@ TEST_FILE = "Tests/images/hopper.xpm" class TestFileXpm(PillowTestCase): def test_sanity(self): - im = Image.open(TEST_FILE) - im.load() - self.assertEqual(im.mode, "P") - self.assertEqual(im.size, (128, 128)) - self.assertEqual(im.format, "XPM") + with Image.open(TEST_FILE) as im: + im.load() + self.assertEqual(im.mode, "P") + self.assertEqual(im.size, (128, 128)) + self.assertEqual(im.format, "XPM") - # large error due to quantization->44 colors. - self.assert_image_similar(im.convert("RGB"), hopper("RGB"), 60) + # large error due to quantization->44 colors. + self.assert_image_similar(im.convert("RGB"), hopper("RGB"), 60) def test_invalid_file(self): invalid_file = "Tests/images/flower.jpg" diff --git a/Tests/test_file_xvthumb.py b/Tests/test_file_xvthumb.py index f8b6d3531..3b63ab36f 100644 --- a/Tests/test_file_xvthumb.py +++ b/Tests/test_file_xvthumb.py @@ -8,14 +8,14 @@ TEST_FILE = "Tests/images/hopper.p7" class TestFileXVThumb(PillowTestCase): def test_open(self): # Act - im = Image.open(TEST_FILE) + with Image.open(TEST_FILE) as im: - # Assert - self.assertEqual(im.format, "XVThumb") + # Assert + self.assertEqual(im.format, "XVThumb") - # Create a Hopper image with a similar XV palette - im_hopper = hopper().quantize(palette=im) - self.assert_image_similar(im, im_hopper, 9) + # Create a Hopper image with a similar XV palette + im_hopper = hopper().quantize(palette=im) + self.assert_image_similar(im, im_hopper, 9) def test_unexpected_eof(self): # Test unexpected EOF reading XV thumbnail file diff --git a/Tests/test_format_lab.py b/Tests/test_format_lab.py index a98c20579..08e9ad068 100644 --- a/Tests/test_format_lab.py +++ b/Tests/test_format_lab.py @@ -5,21 +5,21 @@ from .helper import PillowTestCase class TestFormatLab(PillowTestCase): def test_white(self): - i = Image.open("Tests/images/lab.tif") + with Image.open("Tests/images/lab.tif") as i: + i.load() - i.load() + self.assertEqual(i.mode, "LAB") - self.assertEqual(i.mode, "LAB") + self.assertEqual(i.getbands(), ("L", "A", "B")) - self.assertEqual(i.getbands(), ("L", "A", "B")) + k = i.getpixel((0, 0)) + + L = i.getdata(0) + a = i.getdata(1) + b = i.getdata(2) - k = i.getpixel((0, 0)) self.assertEqual(k, (255, 128, 128)) - L = i.getdata(0) - a = i.getdata(1) - b = i.getdata(2) - self.assertEqual(list(L), [255] * 100) self.assertEqual(list(a), [128] * 100) self.assertEqual(list(b), [128] * 100) @@ -27,15 +27,13 @@ class TestFormatLab(PillowTestCase): def test_green(self): # l= 50 (/100), a = -100 (-128 .. 128) b=0 in PS # == RGB: 0, 152, 117 - i = Image.open("Tests/images/lab-green.tif") - - k = i.getpixel((0, 0)) + with Image.open("Tests/images/lab-green.tif") as i: + k = i.getpixel((0, 0)) self.assertEqual(k, (128, 28, 128)) def test_red(self): # l= 50 (/100), a = 100 (-128 .. 128) b=0 in PS # == RGB: 255, 0, 124 - i = Image.open("Tests/images/lab-red.tif") - - k = i.getpixel((0, 0)) + with Image.open("Tests/images/lab-red.tif") as i: + k = i.getpixel((0, 0)) self.assertEqual(k, (128, 228, 128)) diff --git a/Tests/test_image.py b/Tests/test_image.py index 15cf435b0..83da76b96 100644 --- a/Tests/test_image.py +++ b/Tests/test_image.py @@ -98,14 +98,14 @@ class TestImage(PillowTestCase): self.assertEqual(im.mode, "P") self.assertEqual(im.size, (10, 10)) - im = Image.open(Path("Tests/images/hopper.jpg")) - self.assertEqual(im.mode, "RGB") - self.assertEqual(im.size, (128, 128)) + with Image.open(Path("Tests/images/hopper.jpg")) as im: + self.assertEqual(im.mode, "RGB") + self.assertEqual(im.size, (128, 128)) - temp_file = self.tempfile("temp.jpg") - if os.path.exists(temp_file): - os.remove(temp_file) - im.save(Path(temp_file)) + temp_file = self.tempfile("temp.jpg") + if os.path.exists(temp_file): + os.remove(temp_file) + im.save(Path(temp_file)) def test_fp_name(self): temp_file = self.tempfile("temp.jpg") @@ -127,8 +127,8 @@ class TestImage(PillowTestCase): with tempfile.TemporaryFile() as fp: im.save(fp, "JPEG") fp.seek(0) - reloaded = Image.open(fp) - self.assert_image_similar(im, reloaded, 20) + with Image.open(fp) as reloaded: + self.assert_image_similar(im, reloaded, 20) def test_unknown_extension(self): im = hopper() @@ -150,9 +150,9 @@ class TestImage(PillowTestCase): temp_file = self.tempfile("temp.bmp") shutil.copy("Tests/images/rgb32bf-rgba.bmp", temp_file) - im = Image.open(temp_file) - self.assertTrue(im.readonly) - im.save(temp_file) + with Image.open(temp_file) as im: + self.assertTrue(im.readonly) + im.save(temp_file) def test_dump(self): im = Image.new("L", (10, 10)) @@ -365,8 +365,8 @@ class TestImage(PillowTestCase): # Assert self.assertEqual(im.size, (512, 512)) - im2 = Image.open("Tests/images/effect_mandelbrot.png") - self.assert_image_equal(im, im2) + with Image.open("Tests/images/effect_mandelbrot.png") as im2: + self.assert_image_equal(im, im2) def test_effect_mandelbrot_bad_arguments(self): # Arrange @@ -407,8 +407,8 @@ class TestImage(PillowTestCase): # Assert self.assertEqual(im.size, (128, 128)) - im3 = Image.open("Tests/images/effect_spread.png") - self.assert_image_similar(im2, im3, 110) + with Image.open("Tests/images/effect_spread.png") as im3: + self.assert_image_similar(im2, im3, 110) def test_check_size(self): # Checking that the _check_size function throws value errors @@ -475,7 +475,8 @@ class TestImage(PillowTestCase): self.assertEqual(im.mode, mode) self.assertEqual(im.getpixel((0, 0)), 0) self.assertEqual(im.getpixel((255, 255)), 255) - target = Image.open(target_file).convert(mode) + with Image.open(target_file) as target: + target = target.convert(mode) self.assert_image_equal(im, target) def test_radial_gradient_wrong_mode(self): @@ -499,7 +500,8 @@ class TestImage(PillowTestCase): self.assertEqual(im.mode, mode) self.assertEqual(im.getpixel((0, 0)), 255) self.assertEqual(im.getpixel((128, 128)), 0) - target = Image.open(target_file).convert(mode) + with Image.open(target_file) as target: + target = target.convert(mode) self.assert_image_equal(im, target) def test_register_extensions(self): diff --git a/Tests/test_image_convert.py b/Tests/test_image_convert.py index 80fa9f513..5bbe3abfa 100644 --- a/Tests/test_image_convert.py +++ b/Tests/test_image_convert.py @@ -53,16 +53,16 @@ class TestImageConvert(PillowTestCase): self.assertEqual(orig, converted) def test_8bit(self): - im = Image.open("Tests/images/hopper.jpg") - self._test_float_conversion(im.convert("L")) + with Image.open("Tests/images/hopper.jpg") as im: + self._test_float_conversion(im.convert("L")) def test_16bit(self): - im = Image.open("Tests/images/16bit.cropped.tif") - self._test_float_conversion(im) + with Image.open("Tests/images/16bit.cropped.tif") as im: + self._test_float_conversion(im) def test_16bit_workaround(self): - im = Image.open("Tests/images/16bit.cropped.tif") - self._test_float_conversion(im.convert("I")) + with Image.open("Tests/images/16bit.cropped.tif") as im: + self._test_float_conversion(im.convert("I")) def test_rgba_p(self): im = hopper("RGBA") @@ -210,13 +210,13 @@ class TestImageConvert(PillowTestCase): # Assert self.assertEqual(converted_im.mode, mode) self.assertEqual(converted_im.size, im.size) - target = Image.open("Tests/images/hopper-XYZ.png") - if converted_im.mode == "RGB": - self.assert_image_similar(converted_im, target, 3) - self.assertEqual(converted_im.info["transparency"], (105, 54, 4)) - else: - self.assert_image_similar(converted_im, target.getchannel(0), 1) - self.assertEqual(converted_im.info["transparency"], 105) + with Image.open("Tests/images/hopper-XYZ.png") as target: + if converted_im.mode == "RGB": + self.assert_image_similar(converted_im, target, 3) + self.assertEqual(converted_im.info["transparency"], (105, 54, 4)) + else: + self.assert_image_similar(converted_im, target.getchannel(0), 1) + self.assertEqual(converted_im.info["transparency"], 105) matrix_convert("RGB") matrix_convert("L") diff --git a/Tests/test_image_crop.py b/Tests/test_image_crop.py index 6a604f494..480ab2b8e 100644 --- a/Tests/test_image_crop.py +++ b/Tests/test_image_crop.py @@ -76,13 +76,13 @@ class TestImageCrop(PillowTestCase): test_img = "Tests/images/bmp/g/pal8-0.bmp" extents = (1, 1, 10, 10) # works prepatch - img = Image.open(test_img) - img2 = img.crop(extents) + with Image.open(test_img) as img: + img2 = img.crop(extents) img2.load() # fail prepatch - img = Image.open(test_img) - img = img.crop(extents) + with Image.open(test_img) as img: + img = img.crop(extents) img.load() def test_crop_zero(self): diff --git a/Tests/test_image_filter.py b/Tests/test_image_filter.py index bbd10e6e5..50d2ebbe8 100644 --- a/Tests/test_image_filter.py +++ b/Tests/test_image_filter.py @@ -99,45 +99,45 @@ class TestImageFilter(PillowTestCase): self.assertRaises(ValueError, lambda: ImageFilter.Kernel((3, 3), (0, 0))) def test_consistency_3x3(self): - source = Image.open("Tests/images/hopper.bmp") - reference = Image.open("Tests/images/hopper_emboss.bmp") - kernel = ImageFilter.Kernel( # noqa: E127 - (3, 3), - # fmt: off - (-1, -1, 0, - -1, 0, 1, - 0, 1, 1), - # fmt: on - 0.3, - ) - source = source.split() * 2 - reference = reference.split() * 2 + with Image.open("Tests/images/hopper.bmp") as source: + with Image.open("Tests/images/hopper_emboss.bmp") as reference: + kernel = ImageFilter.Kernel( # noqa: E127 + (3, 3), + # fmt: off + (-1, -1, 0, + -1, 0, 1, + 0, 1, 1), + # fmt: on + 0.3, + ) + source = source.split() * 2 + reference = reference.split() * 2 - for mode in ["L", "LA", "RGB", "CMYK"]: - self.assert_image_equal( - Image.merge(mode, source[: len(mode)]).filter(kernel), - Image.merge(mode, reference[: len(mode)]), - ) + for mode in ["L", "LA", "RGB", "CMYK"]: + self.assert_image_equal( + Image.merge(mode, source[: len(mode)]).filter(kernel), + Image.merge(mode, reference[: len(mode)]), + ) def test_consistency_5x5(self): - source = Image.open("Tests/images/hopper.bmp") - reference = Image.open("Tests/images/hopper_emboss_more.bmp") - kernel = ImageFilter.Kernel( # noqa: E127 - (5, 5), - # fmt: off - (-1, -1, -1, -1, 0, - -1, -1, -1, 0, 1, - -1, -1, 0, 1, 1, - -1, 0, 1, 1, 1, - 0, 1, 1, 1, 1), - # fmt: on - 0.3, - ) - source = source.split() * 2 - reference = reference.split() * 2 + with Image.open("Tests/images/hopper.bmp") as source: + with Image.open("Tests/images/hopper_emboss_more.bmp") as reference: + kernel = ImageFilter.Kernel( # noqa: E127 + (5, 5), + # fmt: off + (-1, -1, -1, -1, 0, + -1, -1, -1, 0, 1, + -1, -1, 0, 1, 1, + -1, 0, 1, 1, 1, + 0, 1, 1, 1, 1), + # fmt: on + 0.3, + ) + source = source.split() * 2 + reference = reference.split() * 2 - for mode in ["L", "LA", "RGB", "CMYK"]: - self.assert_image_equal( - Image.merge(mode, source[: len(mode)]).filter(kernel), - Image.merge(mode, reference[: len(mode)]), - ) + for mode in ["L", "LA", "RGB", "CMYK"]: + self.assert_image_equal( + Image.merge(mode, source[: len(mode)]).filter(kernel), + Image.merge(mode, reference[: len(mode)]), + ) diff --git a/Tests/test_image_getextrema.py b/Tests/test_image_getextrema.py index 1944b041c..228eb82c8 100644 --- a/Tests/test_image_getextrema.py +++ b/Tests/test_image_getextrema.py @@ -19,7 +19,7 @@ class TestImageGetExtrema(PillowTestCase): self.assertEqual(extrema("I;16"), (0, 255)) def test_true_16(self): - im = Image.open("Tests/images/16_bit_noise.tif") - self.assertEqual(im.mode, "I;16") - extrema = im.getextrema() + with Image.open("Tests/images/16_bit_noise.tif") as im: + self.assertEqual(im.mode, "I;16") + extrema = im.getextrema() self.assertEqual(extrema, (106, 285)) diff --git a/Tests/test_image_quantize.py b/Tests/test_image_quantize.py index 2be5b74fc..ee5a09899 100644 --- a/Tests/test_image_quantize.py +++ b/Tests/test_image_quantize.py @@ -42,21 +42,24 @@ class TestImageQuantize(PillowTestCase): self.assertEqual(image.quantize().convert().mode, "RGBA") def test_quantize(self): - image = Image.open("Tests/images/caption_6_33_22.png").convert("RGB") + with Image.open("Tests/images/caption_6_33_22.png") as image: + image = image.convert("RGB") converted = image.quantize() self.assert_image(converted, "P", converted.size) self.assert_image_similar(converted.convert("RGB"), image, 1) def test_quantize_no_dither(self): image = hopper() - palette = Image.open("Tests/images/caption_6_33_22.png").convert("P") + with Image.open("Tests/images/caption_6_33_22.png") as palette: + palette = palette.convert("P") converted = image.quantize(dither=0, palette=palette) self.assert_image(converted, "P", converted.size) def test_quantize_dither_diff(self): image = hopper() - palette = Image.open("Tests/images/caption_6_33_22.png").convert("P") + with Image.open("Tests/images/caption_6_33_22.png") as palette: + palette = palette.convert("P") dither = image.quantize(dither=1, palette=palette) nodither = image.quantize(dither=0, palette=palette) diff --git a/Tests/test_image_resample.py b/Tests/test_image_resample.py index 00c8fa5f2..3bb941438 100644 --- a/Tests/test_image_resample.py +++ b/Tests/test_image_resample.py @@ -450,25 +450,25 @@ class CoreResampleBoxTest(PillowTestCase): return tiled def test_tiles(self): - im = Image.open("Tests/images/flower.jpg") - self.assertEqual(im.size, (480, 360)) - dst_size = (251, 188) - reference = im.resize(dst_size, Image.BICUBIC) + with Image.open("Tests/images/flower.jpg") as im: + self.assertEqual(im.size, (480, 360)) + dst_size = (251, 188) + reference = im.resize(dst_size, Image.BICUBIC) - for tiles in [(1, 1), (3, 3), (9, 7), (100, 100)]: - tiled = self.resize_tiled(im, dst_size, *tiles) - self.assert_image_similar(reference, tiled, 0.01) + for tiles in [(1, 1), (3, 3), (9, 7), (100, 100)]: + tiled = self.resize_tiled(im, dst_size, *tiles) + self.assert_image_similar(reference, tiled, 0.01) def test_subsample(self): # This test shows advantages of the subpixel resizing # after supersampling (e.g. during JPEG decoding). - im = Image.open("Tests/images/flower.jpg") - self.assertEqual(im.size, (480, 360)) - dst_size = (48, 36) - # Reference is cropped image resized to destination - reference = im.crop((0, 0, 473, 353)).resize(dst_size, Image.BICUBIC) - # Image.BOX emulates supersampling (480 / 8 = 60, 360 / 8 = 45) - supersampled = im.resize((60, 45), Image.BOX) + with Image.open("Tests/images/flower.jpg") as im: + self.assertEqual(im.size, (480, 360)) + dst_size = (48, 36) + # Reference is cropped image resized to destination + reference = im.crop((0, 0, 473, 353)).resize(dst_size, Image.BICUBIC) + # Image.BOX emulates supersampling (480 / 8 = 60, 360 / 8 = 45) + supersampled = im.resize((60, 45), Image.BOX) with_box = supersampled.resize(dst_size, Image.BICUBIC, (0, 0, 59.125, 44.125)) without_box = supersampled.resize(dst_size, Image.BICUBIC) diff --git a/Tests/test_image_rotate.py b/Tests/test_image_rotate.py index 9c62e7362..b758531d4 100644 --- a/Tests/test_image_rotate.py +++ b/Tests/test_image_rotate.py @@ -24,8 +24,8 @@ class TestImageRotate(PillowTestCase): def test_angle(self): for angle in (0, 90, 180, 270): - im = Image.open("Tests/images/test-card.png") - self.rotate(im, im.mode, angle) + with Image.open("Tests/images/test-card.png") as im: + self.rotate(im, im.mode, angle) def test_zero(self): for angle in (0, 45, 90, 180, 270): @@ -38,43 +38,43 @@ class TestImageRotate(PillowTestCase): # >>> im = im.rotate(45, resample=Image.BICUBIC, expand=True) # >>> im.save('Tests/images/hopper_45.png') - target = Image.open("Tests/images/hopper_45.png") - for (resample, epsilon) in ( - (Image.NEAREST, 10), - (Image.BILINEAR, 5), - (Image.BICUBIC, 0), - ): - im = hopper() - im = im.rotate(45, resample=resample, expand=True) - self.assert_image_similar(im, target, epsilon) + with Image.open("Tests/images/hopper_45.png") as target: + for (resample, epsilon) in ( + (Image.NEAREST, 10), + (Image.BILINEAR, 5), + (Image.BICUBIC, 0), + ): + im = hopper() + im = im.rotate(45, resample=resample, expand=True) + self.assert_image_similar(im, target, epsilon) def test_center_0(self): im = hopper() - target = Image.open("Tests/images/hopper_45.png") - target_origin = target.size[1] / 2 - target = target.crop((0, target_origin, 128, target_origin + 128)) - im = im.rotate(45, center=(0, 0), resample=Image.BICUBIC) + with Image.open("Tests/images/hopper_45.png") as target: + target_origin = target.size[1] / 2 + target = target.crop((0, target_origin, 128, target_origin + 128)) + self.assert_image_similar(im, target, 15) def test_center_14(self): im = hopper() - target = Image.open("Tests/images/hopper_45.png") - target_origin = target.size[1] / 2 - 14 - target = target.crop((6, target_origin, 128 + 6, target_origin + 128)) - im = im.rotate(45, center=(14, 14), resample=Image.BICUBIC) - self.assert_image_similar(im, target, 10) + with Image.open("Tests/images/hopper_45.png") as target: + target_origin = target.size[1] / 2 - 14 + target = target.crop((6, target_origin, 128 + 6, target_origin + 128)) + + self.assert_image_similar(im, target, 10) def test_translate(self): im = hopper() - target = Image.open("Tests/images/hopper_45.png") - target_origin = (target.size[1] / 2 - 64) - 5 - target = target.crop( - (target_origin, target_origin, target_origin + 128, target_origin + 128) - ) + with Image.open("Tests/images/hopper_45.png") as target: + target_origin = (target.size[1] / 2 - 64) - 5 + target = target.crop( + (target_origin, target_origin, target_origin + 128, target_origin + 128) + ) im = im.rotate(45, translate=(5, 5), resample=Image.BICUBIC) @@ -102,15 +102,15 @@ class TestImageRotate(PillowTestCase): def test_rotate_no_fill(self): im = Image.new("RGB", (100, 100), "green") - target = Image.open("Tests/images/rotate_45_no_fill.png") im = im.rotate(45) - self.assert_image_equal(im, target) + with Image.open("Tests/images/rotate_45_no_fill.png") as target: + self.assert_image_equal(im, target) def test_rotate_with_fill(self): im = Image.new("RGB", (100, 100), "green") - target = Image.open("Tests/images/rotate_45_with_fill.png") im = im.rotate(45, fillcolor="white") - self.assert_image_equal(im, target) + with Image.open("Tests/images/rotate_45_with_fill.png") as target: + self.assert_image_equal(im, target) def test_alpha_rotate_no_fill(self): # Alpha images are handled differently internally diff --git a/Tests/test_image_split.py b/Tests/test_image_split.py index a19878aae..63918d073 100644 --- a/Tests/test_image_split.py +++ b/Tests/test_image_split.py @@ -53,8 +53,8 @@ class TestImageSplit(PillowTestCase): def split_open(mode): hopper(mode).save(test_file) - im = Image.open(test_file) - return len(im.split()) + with Image.open(test_file) as im: + return len(im.split()) self.assertEqual(split_open("1"), 1) self.assertEqual(split_open("L"), 1) diff --git a/Tests/test_image_transform.py b/Tests/test_image_transform.py index 35f64bcc9..781ba7923 100644 --- a/Tests/test_image_transform.py +++ b/Tests/test_image_transform.py @@ -23,11 +23,11 @@ class TestImageTransform(PillowTestCase): def test_info(self): comment = b"File written by Adobe Photoshop\xa8 4.0" - im = Image.open("Tests/images/hopper.gif") - self.assertEqual(im.info["comment"], comment) + with Image.open("Tests/images/hopper.gif") as im: + self.assertEqual(im.info["comment"], comment) - transform = ImageTransform.ExtentTransform((0, 0, 0, 0)) - new_im = im.transform((100, 100), transform) + transform = ImageTransform.ExtentTransform((0, 0, 0, 0)) + new_im = im.transform((100, 100), transform) self.assertEqual(new_im.info["comment"], comment) def test_extent(self): diff --git a/Tests/test_imagechops.py b/Tests/test_imagechops.py index 6f42a28df..ad1fee3e5 100644 --- a/Tests/test_imagechops.py +++ b/Tests/test_imagechops.py @@ -45,11 +45,11 @@ class TestImageChops(PillowTestCase): def test_add(self): # Arrange - im1 = Image.open("Tests/images/imagedraw_ellipse_RGB.png") - im2 = Image.open("Tests/images/imagedraw_floodfill_RGB.png") + with Image.open("Tests/images/imagedraw_ellipse_RGB.png") as im1: + with Image.open("Tests/images/imagedraw_floodfill_RGB.png") as im2: - # Act - new = ImageChops.add(im1, im2) + # Act + new = ImageChops.add(im1, im2) # Assert self.assertEqual(new.getbbox(), (25, 25, 76, 76)) @@ -57,11 +57,11 @@ class TestImageChops(PillowTestCase): def test_add_scale_offset(self): # Arrange - im1 = Image.open("Tests/images/imagedraw_ellipse_RGB.png") - im2 = Image.open("Tests/images/imagedraw_floodfill_RGB.png") + with Image.open("Tests/images/imagedraw_ellipse_RGB.png") as im1: + with Image.open("Tests/images/imagedraw_floodfill_RGB.png") as im2: - # Act - new = ImageChops.add(im1, im2, scale=2.5, offset=100) + # Act + new = ImageChops.add(im1, im2, scale=2.5, offset=100) # Assert self.assertEqual(new.getbbox(), (0, 0, 100, 100)) @@ -79,11 +79,11 @@ class TestImageChops(PillowTestCase): def test_add_modulo(self): # Arrange - im1 = Image.open("Tests/images/imagedraw_ellipse_RGB.png") - im2 = Image.open("Tests/images/imagedraw_floodfill_RGB.png") + with Image.open("Tests/images/imagedraw_ellipse_RGB.png") as im1: + with Image.open("Tests/images/imagedraw_floodfill_RGB.png") as im2: - # Act - new = ImageChops.add_modulo(im1, im2) + # Act + new = ImageChops.add_modulo(im1, im2) # Assert self.assertEqual(new.getbbox(), (25, 25, 76, 76)) @@ -101,11 +101,11 @@ class TestImageChops(PillowTestCase): def test_blend(self): # Arrange - im1 = Image.open("Tests/images/imagedraw_ellipse_RGB.png") - im2 = Image.open("Tests/images/imagedraw_floodfill_RGB.png") + with Image.open("Tests/images/imagedraw_ellipse_RGB.png") as im1: + with Image.open("Tests/images/imagedraw_floodfill_RGB.png") as im2: - # Act - new = ImageChops.blend(im1, im2, 0.5) + # Act + new = ImageChops.blend(im1, im2, 0.5) # Assert self.assertEqual(new.getbbox(), (25, 25, 76, 76)) @@ -125,33 +125,33 @@ class TestImageChops(PillowTestCase): def test_darker_image(self): # Arrange - im1 = Image.open("Tests/images/imagedraw_chord_RGB.png") - im2 = Image.open("Tests/images/imagedraw_outline_chord_RGB.png") + with Image.open("Tests/images/imagedraw_chord_RGB.png") as im1: + with Image.open("Tests/images/imagedraw_outline_chord_RGB.png") as im2: - # Act - new = ImageChops.darker(im1, im2) + # Act + new = ImageChops.darker(im1, im2) - # Assert - self.assert_image_equal(new, im2) + # Assert + self.assert_image_equal(new, im2) def test_darker_pixel(self): # Arrange im1 = hopper() - im2 = Image.open("Tests/images/imagedraw_chord_RGB.png") + with Image.open("Tests/images/imagedraw_chord_RGB.png") as im2: - # Act - new = ImageChops.darker(im1, im2) + # Act + new = ImageChops.darker(im1, im2) # Assert self.assertEqual(new.getpixel((50, 50)), (240, 166, 0)) def test_difference(self): # Arrange - im1 = Image.open("Tests/images/imagedraw_arc_end_le_start.png") - im2 = Image.open("Tests/images/imagedraw_arc_no_loops.png") + with Image.open("Tests/images/imagedraw_arc_end_le_start.png") as im1: + with Image.open("Tests/images/imagedraw_arc_no_loops.png") as im2: - # Act - new = ImageChops.difference(im1, im2) + # Act + new = ImageChops.difference(im1, im2) # Assert self.assertEqual(new.getbbox(), (25, 25, 76, 76)) @@ -159,10 +159,10 @@ class TestImageChops(PillowTestCase): def test_difference_pixel(self): # Arrange im1 = hopper() - im2 = Image.open("Tests/images/imagedraw_polygon_kite_RGB.png") + with Image.open("Tests/images/imagedraw_polygon_kite_RGB.png") as im2: - # Act - new = ImageChops.difference(im1, im2) + # Act + new = ImageChops.difference(im1, im2) # Assert self.assertEqual(new.getpixel((50, 50)), (240, 166, 128)) @@ -179,10 +179,10 @@ class TestImageChops(PillowTestCase): def test_invert(self): # Arrange - im = Image.open("Tests/images/imagedraw_floodfill_RGB.png") + with Image.open("Tests/images/imagedraw_floodfill_RGB.png") as im: - # Act - new = ImageChops.invert(im) + # Act + new = ImageChops.invert(im) # Assert self.assertEqual(new.getbbox(), (0, 0, 100, 100)) @@ -191,11 +191,11 @@ class TestImageChops(PillowTestCase): def test_lighter_image(self): # Arrange - im1 = Image.open("Tests/images/imagedraw_chord_RGB.png") - im2 = Image.open("Tests/images/imagedraw_outline_chord_RGB.png") + with Image.open("Tests/images/imagedraw_chord_RGB.png") as im1: + with Image.open("Tests/images/imagedraw_outline_chord_RGB.png") as im2: - # Act - new = ImageChops.lighter(im1, im2) + # Act + new = ImageChops.lighter(im1, im2) # Assert self.assert_image_equal(new, im1) @@ -203,10 +203,10 @@ class TestImageChops(PillowTestCase): def test_lighter_pixel(self): # Arrange im1 = hopper() - im2 = Image.open("Tests/images/imagedraw_chord_RGB.png") + with Image.open("Tests/images/imagedraw_chord_RGB.png") as im2: - # Act - new = ImageChops.lighter(im1, im2) + # Act + new = ImageChops.lighter(im1, im2) # Assert self.assertEqual(new.getpixel((50, 50)), (255, 255, 127)) @@ -226,11 +226,11 @@ class TestImageChops(PillowTestCase): def test_multiply_green(self): # Arrange - im = Image.open("Tests/images/imagedraw_floodfill_RGB.png") - green = Image.new("RGB", im.size, "green") + with Image.open("Tests/images/imagedraw_floodfill_RGB.png") as im: + green = Image.new("RGB", im.size, "green") - # Act - new = ImageChops.multiply(im, green) + # Act + new = ImageChops.multiply(im, green) # Assert self.assertEqual(new.getbbox(), (25, 25, 76, 76)) @@ -252,12 +252,12 @@ class TestImageChops(PillowTestCase): def test_offset(self): # Arrange - im = Image.open("Tests/images/imagedraw_ellipse_RGB.png") xoffset = 45 yoffset = 20 + with Image.open("Tests/images/imagedraw_ellipse_RGB.png") as im: - # Act - new = ImageChops.offset(im, xoffset, yoffset) + # Act + new = ImageChops.offset(im, xoffset, yoffset) # Assert self.assertEqual(new.getbbox(), (0, 45, 100, 96)) @@ -271,11 +271,11 @@ class TestImageChops(PillowTestCase): def test_screen(self): # Arrange - im1 = Image.open("Tests/images/imagedraw_ellipse_RGB.png") - im2 = Image.open("Tests/images/imagedraw_floodfill_RGB.png") + with Image.open("Tests/images/imagedraw_ellipse_RGB.png") as im1: + with Image.open("Tests/images/imagedraw_floodfill_RGB.png") as im2: - # Act - new = ImageChops.screen(im1, im2) + # Act + new = ImageChops.screen(im1, im2) # Assert self.assertEqual(new.getbbox(), (25, 25, 76, 76)) @@ -283,11 +283,11 @@ class TestImageChops(PillowTestCase): def test_subtract(self): # Arrange - im1 = Image.open("Tests/images/imagedraw_chord_RGB.png") - im2 = Image.open("Tests/images/imagedraw_outline_chord_RGB.png") + with Image.open("Tests/images/imagedraw_chord_RGB.png") as im1: + with Image.open("Tests/images/imagedraw_outline_chord_RGB.png") as im2: - # Act - new = ImageChops.subtract(im1, im2) + # Act + new = ImageChops.subtract(im1, im2) # Assert self.assertEqual(new.getbbox(), (25, 50, 76, 76)) @@ -296,11 +296,11 @@ class TestImageChops(PillowTestCase): def test_subtract_scale_offset(self): # Arrange - im1 = Image.open("Tests/images/imagedraw_chord_RGB.png") - im2 = Image.open("Tests/images/imagedraw_outline_chord_RGB.png") + with Image.open("Tests/images/imagedraw_chord_RGB.png") as im1: + with Image.open("Tests/images/imagedraw_outline_chord_RGB.png") as im2: - # Act - new = ImageChops.subtract(im1, im2, scale=2.5, offset=100) + # Act + new = ImageChops.subtract(im1, im2, scale=2.5, offset=100) # Assert self.assertEqual(new.getbbox(), (0, 0, 100, 100)) @@ -309,21 +309,21 @@ class TestImageChops(PillowTestCase): def test_subtract_clip(self): # Arrange im1 = hopper() - im2 = Image.open("Tests/images/imagedraw_chord_RGB.png") + with Image.open("Tests/images/imagedraw_chord_RGB.png") as im2: - # Act - new = ImageChops.subtract(im1, im2) + # Act + new = ImageChops.subtract(im1, im2) # Assert self.assertEqual(new.getpixel((50, 50)), (0, 0, 127)) def test_subtract_modulo(self): # Arrange - im1 = Image.open("Tests/images/imagedraw_chord_RGB.png") - im2 = Image.open("Tests/images/imagedraw_outline_chord_RGB.png") + with Image.open("Tests/images/imagedraw_chord_RGB.png") as im1: + with Image.open("Tests/images/imagedraw_outline_chord_RGB.png") as im2: - # Act - new = ImageChops.subtract_modulo(im1, im2) + # Act + new = ImageChops.subtract_modulo(im1, im2) # Assert self.assertEqual(new.getbbox(), (25, 50, 76, 76)) @@ -333,10 +333,10 @@ class TestImageChops(PillowTestCase): def test_subtract_modulo_no_clip(self): # Arrange im1 = hopper() - im2 = Image.open("Tests/images/imagedraw_chord_RGB.png") + with Image.open("Tests/images/imagedraw_chord_RGB.png") as im2: - # Act - new = ImageChops.subtract_modulo(im1, im2) + # Act + new = ImageChops.subtract_modulo(im1, im2) # Assert self.assertEqual(new.getpixel((50, 50)), (241, 167, 127)) diff --git a/Tests/test_imagecms.py b/Tests/test_imagecms.py index 97f81fb3f..98b1a80ab 100644 --- a/Tests/test_imagecms.py +++ b/Tests/test_imagecms.py @@ -229,18 +229,16 @@ class TestImageCms(PillowTestCase): # i.save('temp.lab.tif') # visually verified vs PS. - target = Image.open("Tests/images/hopper.Lab.tif") - - self.assert_image_similar(i, target, 3.5) + with Image.open("Tests/images/hopper.Lab.tif") as target: + self.assert_image_similar(i, target, 3.5) def test_lab_srgb(self): psRGB = ImageCms.createProfile("sRGB") pLab = ImageCms.createProfile("LAB") t = ImageCms.buildTransform(pLab, psRGB, "LAB", "RGB") - img = Image.open("Tests/images/hopper.Lab.tif") - - img_srgb = ImageCms.applyTransform(img, t) + with Image.open("Tests/images/hopper.Lab.tif") as img: + img_srgb = ImageCms.applyTransform(img, t) # img_srgb.save('temp.srgb.tif') # visually verified vs ps. diff --git a/Tests/test_imagedraw.py b/Tests/test_imagedraw.py index 50a774df2..d3791e0b1 100644 --- a/Tests/test_imagedraw.py +++ b/Tests/test_imagedraw.py @@ -157,12 +157,12 @@ class TestImageDraw(PillowTestCase): def test_bitmap(self): # Arrange - small = Image.open("Tests/images/pil123rgba.png").resize((50, 50)) im = Image.new("RGB", (W, H)) draw = ImageDraw.Draw(im) + with Image.open("Tests/images/pil123rgba.png").resize((50, 50)) as small: - # Act - draw.bitmap((10, 10), small) + # Act + draw.bitmap((10, 10), small) # Assert self.assert_image_equal(im, Image.open("Tests/images/imagedraw_bitmap.png")) @@ -523,8 +523,8 @@ class TestImageDraw(PillowTestCase): # Assert expected = "Tests/images/imagedraw_floodfill_" + mode + ".png" - im_floodfill = Image.open(expected) - self.assert_image_equal(im, im_floodfill) + with Image.open(expected) as im_floodfill: + self.assert_image_equal(im, im_floodfill) # Test that using the same colour does not change the image ImageDraw.floodfill(im, centre_point, red) @@ -603,152 +603,166 @@ class TestImageDraw(PillowTestCase): return img, ImageDraw.Draw(img) def test_square(self): - expected = Image.open(os.path.join(IMAGES_PATH, "square.png")) - expected.load() - img, draw = self.create_base_image_draw((10, 10)) - draw.polygon([(2, 2), (2, 7), (7, 7), (7, 2)], BLACK) - self.assert_image_equal(img, expected, "square as normal polygon failed") - img, draw = self.create_base_image_draw((10, 10)) - draw.polygon([(7, 7), (7, 2), (2, 2), (2, 7)], BLACK) - self.assert_image_equal(img, expected, "square as inverted polygon failed") - img, draw = self.create_base_image_draw((10, 10)) - draw.rectangle((2, 2, 7, 7), BLACK) - self.assert_image_equal(img, expected, "square as normal rectangle failed") - img, draw = self.create_base_image_draw((10, 10)) - draw.rectangle((7, 7, 2, 2), BLACK) - self.assert_image_equal(img, expected, "square as inverted rectangle failed") + with Image.open(os.path.join(IMAGES_PATH, "square.png")) as expected: + expected.load() + img, draw = self.create_base_image_draw((10, 10)) + draw.polygon([(2, 2), (2, 7), (7, 7), (7, 2)], BLACK) + self.assert_image_equal(img, expected, "square as normal polygon failed") + img, draw = self.create_base_image_draw((10, 10)) + draw.polygon([(7, 7), (7, 2), (2, 2), (2, 7)], BLACK) + self.assert_image_equal(img, expected, "square as inverted polygon failed") + img, draw = self.create_base_image_draw((10, 10)) + draw.rectangle((2, 2, 7, 7), BLACK) + self.assert_image_equal(img, expected, "square as normal rectangle failed") + img, draw = self.create_base_image_draw((10, 10)) + draw.rectangle((7, 7, 2, 2), BLACK) + self.assert_image_equal( + img, expected, "square as inverted rectangle failed" + ) def test_triangle_right(self): - expected = Image.open(os.path.join(IMAGES_PATH, "triangle_right.png")) - expected.load() - img, draw = self.create_base_image_draw((20, 20)) - draw.polygon([(3, 5), (17, 5), (10, 12)], BLACK) - self.assert_image_equal(img, expected, "triangle right failed") + with Image.open(os.path.join(IMAGES_PATH, "triangle_right.png")) as expected: + expected.load() + img, draw = self.create_base_image_draw((20, 20)) + draw.polygon([(3, 5), (17, 5), (10, 12)], BLACK) + self.assert_image_equal(img, expected, "triangle right failed") def test_line_horizontal(self): - expected = Image.open( + with Image.open( os.path.join(IMAGES_PATH, "line_horizontal_w2px_normal.png") - ) - expected.load() - img, draw = self.create_base_image_draw((20, 20)) - draw.line((5, 5, 14, 5), BLACK, 2) - self.assert_image_equal( - img, expected, "line straight horizontal normal 2px wide failed" - ) - expected = Image.open( + ) as expected: + expected.load() + img, draw = self.create_base_image_draw((20, 20)) + draw.line((5, 5, 14, 5), BLACK, 2) + self.assert_image_equal( + img, expected, "line straight horizontal normal 2px wide failed" + ) + with Image.open( os.path.join(IMAGES_PATH, "line_horizontal_w2px_inverted.png") - ) - expected.load() - img, draw = self.create_base_image_draw((20, 20)) - draw.line((14, 5, 5, 5), BLACK, 2) - self.assert_image_equal( - img, expected, "line straight horizontal inverted 2px wide failed" - ) - expected = Image.open(os.path.join(IMAGES_PATH, "line_horizontal_w3px.png")) - expected.load() - img, draw = self.create_base_image_draw((20, 20)) - draw.line((5, 5, 14, 5), BLACK, 3) - self.assert_image_equal( - img, expected, "line straight horizontal normal 3px wide failed" - ) - img, draw = self.create_base_image_draw((20, 20)) - draw.line((14, 5, 5, 5), BLACK, 3) - self.assert_image_equal( - img, expected, "line straight horizontal inverted 3px wide failed" - ) - expected = Image.open(os.path.join(IMAGES_PATH, "line_horizontal_w101px.png")) - expected.load() - img, draw = self.create_base_image_draw((200, 110)) - draw.line((5, 55, 195, 55), BLACK, 101) - self.assert_image_equal( - img, expected, "line straight horizontal 101px wide failed" - ) + ) as expected: + expected.load() + img, draw = self.create_base_image_draw((20, 20)) + draw.line((14, 5, 5, 5), BLACK, 2) + self.assert_image_equal( + img, expected, "line straight horizontal inverted 2px wide failed" + ) + with Image.open( + os.path.join(IMAGES_PATH, "line_horizontal_w3px.png") + ) as expected: + expected.load() + img, draw = self.create_base_image_draw((20, 20)) + draw.line((5, 5, 14, 5), BLACK, 3) + self.assert_image_equal( + img, expected, "line straight horizontal normal 3px wide failed" + ) + img, draw = self.create_base_image_draw((20, 20)) + draw.line((14, 5, 5, 5), BLACK, 3) + self.assert_image_equal( + img, expected, "line straight horizontal inverted 3px wide failed" + ) + with Image.open( + os.path.join(IMAGES_PATH, "line_horizontal_w101px.png") + ) as expected: + expected.load() + img, draw = self.create_base_image_draw((200, 110)) + draw.line((5, 55, 195, 55), BLACK, 101) + self.assert_image_equal( + img, expected, "line straight horizontal 101px wide failed" + ) def test_line_h_s1_w2(self): self.skipTest("failing") - expected = Image.open( + with Image.open( os.path.join(IMAGES_PATH, "line_horizontal_slope1px_w2px.png") - ) - expected.load() - img, draw = self.create_base_image_draw((20, 20)) - draw.line((5, 5, 14, 6), BLACK, 2) - self.assert_image_equal( - img, expected, "line horizontal 1px slope 2px wide failed" - ) + ) as expected: + expected.load() + img, draw = self.create_base_image_draw((20, 20)) + draw.line((5, 5, 14, 6), BLACK, 2) + self.assert_image_equal( + img, expected, "line horizontal 1px slope 2px wide failed" + ) def test_line_vertical(self): - expected = Image.open( + with Image.open( os.path.join(IMAGES_PATH, "line_vertical_w2px_normal.png") - ) - expected.load() - img, draw = self.create_base_image_draw((20, 20)) - draw.line((5, 5, 5, 14), BLACK, 2) - self.assert_image_equal( - img, expected, "line straight vertical normal 2px wide failed" - ) - expected = Image.open( + ) as expected: + expected.load() + img, draw = self.create_base_image_draw((20, 20)) + draw.line((5, 5, 5, 14), BLACK, 2) + self.assert_image_equal( + img, expected, "line straight vertical normal 2px wide failed" + ) + with Image.open( os.path.join(IMAGES_PATH, "line_vertical_w2px_inverted.png") - ) - expected.load() - img, draw = self.create_base_image_draw((20, 20)) - draw.line((5, 14, 5, 5), BLACK, 2) - self.assert_image_equal( - img, expected, "line straight vertical inverted 2px wide failed" - ) - expected = Image.open(os.path.join(IMAGES_PATH, "line_vertical_w3px.png")) - expected.load() - img, draw = self.create_base_image_draw((20, 20)) - draw.line((5, 5, 5, 14), BLACK, 3) - self.assert_image_equal( - img, expected, "line straight vertical normal 3px wide failed" - ) - img, draw = self.create_base_image_draw((20, 20)) - draw.line((5, 14, 5, 5), BLACK, 3) - self.assert_image_equal( - img, expected, "line straight vertical inverted 3px wide failed" - ) - expected = Image.open(os.path.join(IMAGES_PATH, "line_vertical_w101px.png")) - expected.load() - img, draw = self.create_base_image_draw((110, 200)) - draw.line((55, 5, 55, 195), BLACK, 101) - self.assert_image_equal( - img, expected, "line straight vertical 101px wide failed" - ) - expected = Image.open( + ) as expected: + expected.load() + img, draw = self.create_base_image_draw((20, 20)) + draw.line((5, 14, 5, 5), BLACK, 2) + self.assert_image_equal( + img, expected, "line straight vertical inverted 2px wide failed" + ) + with Image.open( + os.path.join(IMAGES_PATH, "line_vertical_w3px.png") + ) as expected: + expected.load() + img, draw = self.create_base_image_draw((20, 20)) + draw.line((5, 5, 5, 14), BLACK, 3) + self.assert_image_equal( + img, expected, "line straight vertical normal 3px wide failed" + ) + img, draw = self.create_base_image_draw((20, 20)) + draw.line((5, 14, 5, 5), BLACK, 3) + self.assert_image_equal( + img, expected, "line straight vertical inverted 3px wide failed" + ) + with Image.open( + os.path.join(IMAGES_PATH, "line_vertical_w101px.png") + ) as expected: + expected.load() + img, draw = self.create_base_image_draw((110, 200)) + draw.line((55, 5, 55, 195), BLACK, 101) + self.assert_image_equal( + img, expected, "line straight vertical 101px wide failed" + ) + with Image.open( os.path.join(IMAGES_PATH, "line_vertical_slope1px_w2px.png") - ) - expected.load() - img, draw = self.create_base_image_draw((20, 20)) - draw.line((5, 5, 6, 14), BLACK, 2) - self.assert_image_equal( - img, expected, "line vertical 1px slope 2px wide failed" - ) + ) as expected: + expected.load() + img, draw = self.create_base_image_draw((20, 20)) + draw.line((5, 5, 6, 14), BLACK, 2) + self.assert_image_equal( + img, expected, "line vertical 1px slope 2px wide failed" + ) def test_line_oblique_45(self): - expected = Image.open(os.path.join(IMAGES_PATH, "line_oblique_45_w3px_a.png")) - expected.load() - img, draw = self.create_base_image_draw((20, 20)) - draw.line((5, 5, 14, 14), BLACK, 3) - self.assert_image_equal( - img, expected, "line oblique 45 normal 3px wide A failed" - ) - img, draw = self.create_base_image_draw((20, 20)) - draw.line((14, 14, 5, 5), BLACK, 3) - self.assert_image_equal( - img, expected, "line oblique 45 inverted 3px wide A failed" - ) - expected = Image.open(os.path.join(IMAGES_PATH, "line_oblique_45_w3px_b.png")) - expected.load() - img, draw = self.create_base_image_draw((20, 20)) - draw.line((14, 5, 5, 14), BLACK, 3) - self.assert_image_equal( - img, expected, "line oblique 45 normal 3px wide B failed" - ) - img, draw = self.create_base_image_draw((20, 20)) - draw.line((5, 14, 14, 5), BLACK, 3) - self.assert_image_equal( - img, expected, "line oblique 45 inverted 3px wide B failed" - ) + with Image.open( + os.path.join(IMAGES_PATH, "line_oblique_45_w3px_a.png") + ) as expected: + expected.load() + img, draw = self.create_base_image_draw((20, 20)) + draw.line((5, 5, 14, 14), BLACK, 3) + self.assert_image_equal( + img, expected, "line oblique 45 normal 3px wide A failed" + ) + img, draw = self.create_base_image_draw((20, 20)) + draw.line((14, 14, 5, 5), BLACK, 3) + self.assert_image_equal( + img, expected, "line oblique 45 inverted 3px wide A failed" + ) + with Image.open( + os.path.join(IMAGES_PATH, "line_oblique_45_w3px_b.png") + ) as expected: + expected.load() + img, draw = self.create_base_image_draw((20, 20)) + draw.line((14, 5, 5, 14), BLACK, 3) + self.assert_image_equal( + img, expected, "line oblique 45 normal 3px wide B failed" + ) + img, draw = self.create_base_image_draw((20, 20)) + draw.line((5, 14, 14, 5), BLACK, 3) + self.assert_image_equal( + img, expected, "line oblique 45 inverted 3px wide B failed" + ) def test_wide_line_dot(self): # Test drawing a wide "line" from one point to another just draws diff --git a/Tests/test_imagefile.py b/Tests/test_imagefile.py index 228bbf929..50c7d337b 100644 --- a/Tests/test_imagefile.py +++ b/Tests/test_imagefile.py @@ -128,33 +128,31 @@ class TestImageFile(PillowTestCase): if "zip_encoder" not in codecs: self.skipTest("PNG (zlib) encoder not available") - im = Image.open("Tests/images/truncated_image.png") - - ImageFile.LOAD_TRUNCATED_IMAGES = True - try: - im.load() - finally: - ImageFile.LOAD_TRUNCATED_IMAGES = False + with Image.open("Tests/images/truncated_image.png") as im: + ImageFile.LOAD_TRUNCATED_IMAGES = True + try: + im.load() + finally: + ImageFile.LOAD_TRUNCATED_IMAGES = False def test_broken_datastream_with_errors(self): if "zip_encoder" not in codecs: self.skipTest("PNG (zlib) encoder not available") - im = Image.open("Tests/images/broken_data_stream.png") - with self.assertRaises(IOError): - im.load() + with Image.open("Tests/images/broken_data_stream.png") as im: + with self.assertRaises(IOError): + im.load() def test_broken_datastream_without_errors(self): if "zip_encoder" not in codecs: self.skipTest("PNG (zlib) encoder not available") - im = Image.open("Tests/images/broken_data_stream.png") - - ImageFile.LOAD_TRUNCATED_IMAGES = True - try: - im.load() - finally: - ImageFile.LOAD_TRUNCATED_IMAGES = False + with Image.open("Tests/images/broken_data_stream.png") as im: + ImageFile.LOAD_TRUNCATED_IMAGES = True + try: + im.load() + finally: + ImageFile.LOAD_TRUNCATED_IMAGES = False class MockPyDecoder(ImageFile.PyDecoder): @@ -246,19 +244,19 @@ class TestPyDecoder(PillowTestCase): self.assertIsNone(im.get_format_mimetype()) def test_exif_jpeg(self): - im = Image.open("Tests/images/exif-72dpi-int.jpg") # Little endian - exif = im.getexif() - self.assertNotIn(258, exif) - self.assertIn(40960, exif) - self.assertEqual(exif[40963], 450) - self.assertEqual(exif[11], "gThumb 3.0.1") + with Image.open("Tests/images/exif-72dpi-int.jpg") as im: # Little endian + exif = im.getexif() + self.assertNotIn(258, exif) + self.assertIn(40960, exif) + self.assertEqual(exif[40963], 450) + self.assertEqual(exif[11], "gThumb 3.0.1") - out = self.tempfile("temp.jpg") - exif[258] = 8 - del exif[40960] - exif[40963] = 455 - exif[11] = "Pillow test" - im.save(out, exif=exif) + out = self.tempfile("temp.jpg") + exif[258] = 8 + del exif[40960] + exif[40963] = 455 + exif[11] = "Pillow test" + im.save(out, exif=exif) with Image.open(out) as reloaded: reloaded_exif = reloaded.getexif() self.assertEqual(reloaded_exif[258], 8) @@ -266,19 +264,19 @@ class TestPyDecoder(PillowTestCase): self.assertEqual(reloaded_exif[40963], 455) self.assertEqual(exif[11], "Pillow test") - im = Image.open("Tests/images/no-dpi-in-exif.jpg") # Big endian - exif = im.getexif() - self.assertNotIn(258, exif) - self.assertIn(40962, exif) - self.assertEqual(exif[40963], 200) - self.assertEqual(exif[305], "Adobe Photoshop CC 2017 (Macintosh)") + with Image.open("Tests/images/no-dpi-in-exif.jpg") as im: # Big endian + exif = im.getexif() + self.assertNotIn(258, exif) + self.assertIn(40962, exif) + self.assertEqual(exif[40963], 200) + self.assertEqual(exif[305], "Adobe Photoshop CC 2017 (Macintosh)") - out = self.tempfile("temp.jpg") - exif[258] = 8 - del exif[34665] - exif[40963] = 455 - exif[305] = "Pillow test" - im.save(out, exif=exif) + out = self.tempfile("temp.jpg") + exif[258] = 8 + del exif[34665] + exif[40963] = 455 + exif[305] = "Pillow test" + im.save(out, exif=exif) with Image.open(out) as reloaded: reloaded_exif = reloaded.getexif() self.assertEqual(reloaded_exif[258], 8) @@ -291,38 +289,38 @@ class TestPyDecoder(PillowTestCase): "WebP support not installed with animation", ) def test_exif_webp(self): - im = Image.open("Tests/images/hopper.webp") - exif = im.getexif() - self.assertEqual(exif, {}) + with Image.open("Tests/images/hopper.webp") as im: + exif = im.getexif() + self.assertEqual(exif, {}) - out = self.tempfile("temp.webp") - exif[258] = 8 - exif[40963] = 455 - exif[305] = "Pillow test" + out = self.tempfile("temp.webp") + exif[258] = 8 + exif[40963] = 455 + exif[305] = "Pillow test" - def check_exif(): - with Image.open(out) as reloaded: - reloaded_exif = reloaded.getexif() - self.assertEqual(reloaded_exif[258], 8) - self.assertEqual(reloaded_exif[40963], 455) - self.assertEqual(exif[305], "Pillow test") + def check_exif(): + with Image.open(out) as reloaded: + reloaded_exif = reloaded.getexif() + self.assertEqual(reloaded_exif[258], 8) + self.assertEqual(reloaded_exif[40963], 455) + self.assertEqual(exif[305], "Pillow test") - im.save(out, exif=exif) - check_exif() - im.save(out, exif=exif, save_all=True) - check_exif() + im.save(out, exif=exif) + check_exif() + im.save(out, exif=exif, save_all=True) + check_exif() def test_exif_png(self): - im = Image.open("Tests/images/exif.png") - exif = im.getexif() - self.assertEqual(exif, {274: 1}) + with Image.open("Tests/images/exif.png") as im: + exif = im.getexif() + self.assertEqual(exif, {274: 1}) - out = self.tempfile("temp.png") - exif[258] = 8 - del exif[274] - exif[40963] = 455 - exif[305] = "Pillow test" - im.save(out, exif=exif) + out = self.tempfile("temp.png") + exif[258] = 8 + del exif[274] + exif[40963] = 455 + exif[305] = "Pillow test" + im.save(out, exif=exif) with Image.open(out) as reloaded: reloaded_exif = reloaded.getexif() diff --git a/Tests/test_imagefont.py b/Tests/test_imagefont.py index 5a15eb535..1777e2978 100644 --- a/Tests/test_imagefont.py +++ b/Tests/test_imagefont.py @@ -179,10 +179,10 @@ class TestImageFont(PillowTestCase): draw.rectangle((10, 10, 10 + size[0], 10 + size[1])) target = "Tests/images/rectangle_surrounding_text.png" - target_img = Image.open(target) + with Image.open(target) as target_img: - # Epsilon ~.5 fails with FreeType 2.7 - self.assert_image_similar(im, target_img, self.metrics["textsize"]) + # Epsilon ~.5 fails with FreeType 2.7 + self.assert_image_similar(im, target_img, self.metrics["textsize"]) def test_render_multiline(self): im = Image.new(mode="RGB", size=(300, 100)) @@ -196,12 +196,12 @@ class TestImageFont(PillowTestCase): y += line_spacing target = "Tests/images/multiline_text.png" - target_img = Image.open(target) + with Image.open(target) as target_img: - # some versions of freetype have different horizontal spacing. - # setting a tight epsilon, I'm showing the original test failure - # at epsilon = ~38. - self.assert_image_similar(im, target_img, self.metrics["multiline"]) + # some versions of freetype have different horizontal spacing. + # setting a tight epsilon, I'm showing the original test failure + # at epsilon = ~38. + self.assert_image_similar(im, target_img, self.metrics["multiline"]) def test_render_multiline_text(self): ttf = self.get_font() @@ -213,10 +213,10 @@ class TestImageFont(PillowTestCase): draw.text((0, 0), TEST_TEXT, font=ttf) target = "Tests/images/multiline_text.png" - target_img = Image.open(target) + with Image.open(target) as target_img: - # Epsilon ~.5 fails with FreeType 2.7 - self.assert_image_similar(im, target_img, self.metrics["multiline"]) + # Epsilon ~.5 fails with FreeType 2.7 + self.assert_image_similar(im, target_img, self.metrics["multiline"]) # Test that text() can pass on additional arguments # to multiline_text() @@ -232,10 +232,10 @@ class TestImageFont(PillowTestCase): draw.multiline_text((0, 0), TEST_TEXT, font=ttf, align=align) target = "Tests/images/multiline_text" + ext + ".png" - target_img = Image.open(target) + with Image.open(target) as target_img: - # Epsilon ~.5 fails with FreeType 2.7 - self.assert_image_similar(im, target_img, self.metrics["multiline"]) + # Epsilon ~.5 fails with FreeType 2.7 + self.assert_image_similar(im, target_img, self.metrics["multiline"]) def test_unknown_align(self): im = Image.new(mode="RGB", size=(300, 100)) @@ -297,10 +297,10 @@ class TestImageFont(PillowTestCase): draw.multiline_text((0, 0), TEST_TEXT, font=ttf, spacing=10) target = "Tests/images/multiline_text_spacing.png" - target_img = Image.open(target) + with Image.open(target) as target_img: - # Epsilon ~.5 fails with FreeType 2.7 - self.assert_image_similar(im, target_img, self.metrics["multiline"]) + # Epsilon ~.5 fails with FreeType 2.7 + self.assert_image_similar(im, target_img, self.metrics["multiline"]) def test_rotated_transposed_font(self): img_grey = Image.new("L", (100, 100)) @@ -432,14 +432,14 @@ class TestImageFont(PillowTestCase): draw = ImageDraw.Draw(im) target = "Tests/images/default_font.png" - target_img = Image.open(target) + with Image.open(target) as target_img: - # Act - default_font = ImageFont.load_default() - draw.text((10, 10), txt, font=default_font) + # Act + default_font = ImageFont.load_default() + draw.text((10, 10), txt, font=default_font) - # Assert - self.assert_image_equal(im, target_img) + # Assert + self.assert_image_equal(im, target_img) def test_getsize_empty(self): # issue #2614 @@ -703,8 +703,8 @@ class TestImageFont(PillowTestCase): d = ImageDraw.Draw(im) d.text((10, 10), "Text", font=font, fill="black") - expected = Image.open(path) - self.assert_image_similar(im, expected, epsilon) + with Image.open(path) as expected: + self.assert_image_similar(im, expected, epsilon) font = ImageFont.truetype("Tests/fonts/AdobeVFPrototype.ttf", 36) _check_text(font, "Tests/images/variation_adobe.png", 11) @@ -733,8 +733,8 @@ class TestImageFont(PillowTestCase): d = ImageDraw.Draw(im) d.text((10, 10), "Text", font=font, fill="black") - expected = Image.open(path) - self.assert_image_similar(im, expected, epsilon) + with Image.open(path) as expected: + self.assert_image_similar(im, expected, epsilon) font = ImageFont.truetype("Tests/fonts/AdobeVFPrototype.ttf", 36) font.set_variation_by_axes([500, 50]) diff --git a/Tests/test_imagefontctl.py b/Tests/test_imagefontctl.py index 71efb897f..c1f976dfa 100644 --- a/Tests/test_imagefontctl.py +++ b/Tests/test_imagefontctl.py @@ -25,9 +25,8 @@ class TestImagecomplextext(PillowTestCase): draw.text((0, 0), "اهلا عمان", font=ttf, fill=500) target = "Tests/images/test_text.png" - target_img = Image.open(target) - - self.assert_image_similar(im, target_img, 0.5) + with Image.open(target) as target_img: + self.assert_image_similar(im, target_img, 0.5) def test_y_offset(self): ttf = ImageFont.truetype("Tests/fonts/NotoNastaliqUrdu-Regular.ttf", FONT_SIZE) @@ -37,9 +36,8 @@ class TestImagecomplextext(PillowTestCase): draw.text((0, 0), "العالم العربي", font=ttf, fill=500) target = "Tests/images/test_y_offset.png" - target_img = Image.open(target) - - self.assert_image_similar(im, target_img, 1.7) + with Image.open(target) as target_img: + self.assert_image_similar(im, target_img, 1.7) def test_complex_unicode_text(self): ttf = ImageFont.truetype(FONT_PATH, FONT_SIZE) @@ -49,9 +47,8 @@ class TestImagecomplextext(PillowTestCase): draw.text((0, 0), "السلام عليكم", font=ttf, fill=500) target = "Tests/images/test_complex_unicode_text.png" - target_img = Image.open(target) - - self.assert_image_similar(im, target_img, 0.5) + with Image.open(target) as target_img: + self.assert_image_similar(im, target_img, 0.5) ttf = ImageFont.truetype("Tests/fonts/KhmerOSBattambang-Regular.ttf", FONT_SIZE) @@ -60,9 +57,8 @@ class TestImagecomplextext(PillowTestCase): draw.text((0, 0), "លោកុប្បត្តិ", font=ttf, fill=500) target = "Tests/images/test_complex_unicode_text2.png" - target_img = Image.open(target) - - self.assert_image_similar(im, target_img, 2.3) + with Image.open(target) as target_img: + self.assert_image_similar(im, target_img, 2.3) def test_text_direction_rtl(self): ttf = ImageFont.truetype(FONT_PATH, FONT_SIZE) @@ -72,9 +68,8 @@ class TestImagecomplextext(PillowTestCase): draw.text((0, 0), "English عربي", font=ttf, fill=500, direction="rtl") target = "Tests/images/test_direction_rtl.png" - target_img = Image.open(target) - - self.assert_image_similar(im, target_img, 0.5) + with Image.open(target) as target_img: + self.assert_image_similar(im, target_img, 0.5) def test_text_direction_ltr(self): ttf = ImageFont.truetype(FONT_PATH, FONT_SIZE) @@ -84,9 +79,8 @@ class TestImagecomplextext(PillowTestCase): draw.text((0, 0), "سلطنة عمان Oman", font=ttf, fill=500, direction="ltr") target = "Tests/images/test_direction_ltr.png" - target_img = Image.open(target) - - self.assert_image_similar(im, target_img, 0.5) + with Image.open(target) as target_img: + self.assert_image_similar(im, target_img, 0.5) def test_text_direction_rtl2(self): ttf = ImageFont.truetype(FONT_PATH, FONT_SIZE) @@ -96,9 +90,8 @@ class TestImagecomplextext(PillowTestCase): draw.text((0, 0), "Oman سلطنة عمان", font=ttf, fill=500, direction="rtl") target = "Tests/images/test_direction_ltr.png" - target_img = Image.open(target) - - self.assert_image_similar(im, target_img, 0.5) + with Image.open(target) as target_img: + self.assert_image_similar(im, target_img, 0.5) def test_text_direction_ttb(self): ttf = ImageFont.truetype("Tests/fonts/NotoSansJP-Regular.otf", FONT_SIZE) @@ -112,9 +105,8 @@ class TestImagecomplextext(PillowTestCase): self.skipTest("libraqm 0.7 or greater not available") target = "Tests/images/test_direction_ttb.png" - target_img = Image.open(target) - - self.assert_image_similar(im, target_img, 1.15) + with Image.open(target) as target_img: + self.assert_image_similar(im, target_img, 1.15) def test_text_direction_ttb_stroke(self): ttf = ImageFont.truetype("Tests/fonts/NotoSansJP-Regular.otf", 50) @@ -136,9 +128,8 @@ class TestImagecomplextext(PillowTestCase): self.skipTest("libraqm 0.7 or greater not available") target = "Tests/images/test_direction_ttb_stroke.png" - target_img = Image.open(target) - - self.assert_image_similar(im, target_img, 12.4) + with Image.open(target) as target_img: + self.assert_image_similar(im, target_img, 12.4) def test_ligature_features(self): ttf = ImageFont.truetype(FONT_PATH, FONT_SIZE) @@ -147,9 +138,8 @@ class TestImagecomplextext(PillowTestCase): draw = ImageDraw.Draw(im) draw.text((0, 0), "filling", font=ttf, fill=500, features=["-liga"]) target = "Tests/images/test_ligature_features.png" - target_img = Image.open(target) - - self.assert_image_similar(im, target_img, 0.5) + with Image.open(target) as target_img: + self.assert_image_similar(im, target_img, 0.5) liga_size = ttf.getsize("fi", features=["-liga"]) self.assertEqual(liga_size, (13, 19)) @@ -162,9 +152,8 @@ class TestImagecomplextext(PillowTestCase): draw.text((0, 0), "TeToAV", font=ttf, fill=500, features=["-kern"]) target = "Tests/images/test_kerning_features.png" - target_img = Image.open(target) - - self.assert_image_similar(im, target_img, 0.5) + with Image.open(target) as target_img: + self.assert_image_similar(im, target_img, 0.5) def test_arabictext_features(self): ttf = ImageFont.truetype(FONT_PATH, FONT_SIZE) @@ -180,9 +169,8 @@ class TestImagecomplextext(PillowTestCase): ) target = "Tests/images/test_arabictext_features.png" - target_img = Image.open(target) - - self.assert_image_similar(im, target_img, 0.5) + with Image.open(target) as target_img: + self.assert_image_similar(im, target_img, 0.5) def test_x_max_and_y_offset(self): ttf = ImageFont.truetype("Tests/fonts/ArefRuqaa-Regular.ttf", 40) @@ -192,9 +180,8 @@ class TestImagecomplextext(PillowTestCase): draw.text((0, 0), "لح", font=ttf, fill=500) target = "Tests/images/test_x_max_and_y_offset.png" - target_img = Image.open(target) - - self.assert_image_similar(im, target_img, 0.5) + with Image.open(target) as target_img: + self.assert_image_similar(im, target_img, 0.5) def test_language(self): ttf = ImageFont.truetype(FONT_PATH, FONT_SIZE) @@ -204,6 +191,5 @@ class TestImagecomplextext(PillowTestCase): draw.text((0, 0), "абвг", font=ttf, fill=500, language="sr") target = "Tests/images/test_language.png" - target_img = Image.open(target) - - self.assert_image_similar(im, target_img, 0.5) + with Image.open(target) as target_img: + self.assert_image_similar(im, target_img, 0.5) diff --git a/Tests/test_imagemorph.py b/Tests/test_imagemorph.py index 1492872b6..c9fd37c54 100644 --- a/Tests/test_imagemorph.py +++ b/Tests/test_imagemorph.py @@ -51,8 +51,8 @@ class MorphTests(PillowTestCase): self.assertEqual(self.img_to_string(A), self.img_string_normalize(Bstring)) def test_str_to_img(self): - im = Image.open("Tests/images/morph_a.png") - self.assert_image_equal(self.A, im) + with Image.open("Tests/images/morph_a.png") as im: + self.assert_image_equal(self.A, im) def create_lut(self): for op in ("corner", "dilation4", "dilation8", "erosion4", "erosion8", "edge"): diff --git a/Tests/test_imageops.py b/Tests/test_imageops.py index d0fd73689..ea50a4c76 100644 --- a/Tests/test_imageops.py +++ b/Tests/test_imageops.py @@ -106,10 +106,10 @@ class TestImageOps(PillowTestCase): new_im = ImageOps.pad(im, new_size, color=color, centering=centering) self.assertEqual(new_im.size, new_size) - target = Image.open( + with Image.open( "Tests/images/imageops_pad_" + label + "_" + str(i) + ".jpg" - ) - self.assert_image_similar(new_im, target, 6) + ) as target: + self.assert_image_similar(new_im, target, 6) def test_pil163(self): # Division by zero in equalize if < 255 pixels in image (@PIL163) @@ -140,8 +140,8 @@ class TestImageOps(PillowTestCase): # Test the colorizing function with 2-color functionality # Open test image (256px by 10px, black to white) - im = Image.open("Tests/images/bw_gradient.png") - im = im.convert("L") + with Image.open("Tests/images/bw_gradient.png") as im: + im = im.convert("L") # Create image with original 2-color functionality im_test = ImageOps.colorize(im, "red", "green") @@ -173,8 +173,8 @@ class TestImageOps(PillowTestCase): # Test the colorizing function with 2-color functionality and offset # Open test image (256px by 10px, black to white) - im = Image.open("Tests/images/bw_gradient.png") - im = im.convert("L") + with Image.open("Tests/images/bw_gradient.png") as im: + im = im.convert("L") # Create image with original 2-color functionality with offsets im_test = ImageOps.colorize( @@ -208,8 +208,8 @@ class TestImageOps(PillowTestCase): # Test the colorizing function with 3-color functionality and offset # Open test image (256px by 10px, black to white) - im = Image.open("Tests/images/bw_gradient.png") - im = im.convert("L") + with Image.open("Tests/images/bw_gradient.png") as im: + im = im.convert("L") # Create image with new three color functionality with offsets im_test = ImageOps.colorize( @@ -261,27 +261,36 @@ class TestImageOps(PillowTestCase): if HAVE_WEBP and _webp.HAVE_WEBPANIM: exts.append(".webp") for ext in exts: - base_im = Image.open("Tests/images/hopper" + ext) + with Image.open("Tests/images/hopper" + ext) as base_im: - orientations = [base_im] - for i in range(2, 9): - im = Image.open("Tests/images/hopper_orientation_" + str(i) + ext) - orientations.append(im) - for i, orientation_im in enumerate(orientations): - for im in [orientation_im, orientation_im.copy()]: # ImageFile # Image - if i == 0: - self.assertNotIn("exif", im.info) - else: - original_exif = im.info["exif"] - transposed_im = ImageOps.exif_transpose(im) - self.assert_image_similar(base_im, transposed_im, 17) - if i == 0: - self.assertNotIn("exif", im.info) - else: - self.assertNotEqual(transposed_im.info["exif"], original_exif) + def check(orientation_im): + for im in [ + orientation_im, + orientation_im.copy(), + ]: # ImageFile # Image + if orientation_im is base_im: + self.assertNotIn("exif", im.info) + else: + original_exif = im.info["exif"] + transposed_im = ImageOps.exif_transpose(im) + self.assert_image_similar(base_im, transposed_im, 17) + if orientation_im is base_im: + self.assertNotIn("exif", im.info) + else: + self.assertNotEqual( + transposed_im.info["exif"], original_exif + ) - self.assertNotIn(0x0112, transposed_im.getexif()) + self.assertNotIn(0x0112, transposed_im.getexif()) - # Repeat the operation, to test that it does not keep transposing - transposed_im2 = ImageOps.exif_transpose(transposed_im) - self.assert_image_equal(transposed_im2, transposed_im) + # Repeat the operation + # to test that it does not keep transposing + transposed_im2 = ImageOps.exif_transpose(transposed_im) + self.assert_image_equal(transposed_im2, transposed_im) + + check(base_im) + for i in range(2, 9): + with Image.open( + "Tests/images/hopper_orientation_" + str(i) + ext + ) as orientation_im: + check(orientation_im) diff --git a/Tests/test_imagepalette.py b/Tests/test_imagepalette.py index 1297712ef..933fc9923 100644 --- a/Tests/test_imagepalette.py +++ b/Tests/test_imagepalette.py @@ -128,9 +128,8 @@ class TestImagePalette(PillowTestCase): img.putpalette(b"\xFF\x00\x00\x00\xFF\x00\x00\x00\xFF") # RGB img.save(outfile, format="PNG") - reloaded = Image.open(outfile) - - self.assert_image_equal(img, reloaded) + with Image.open(outfile) as reloaded: + self.assert_image_equal(img, reloaded) def test_invalid_palette(self): self.assertRaises(IOError, ImagePalette.load, "Tests/images/hopper.jpg") diff --git a/Tests/test_imagetk.py b/Tests/test_imagetk.py index 048efd639..5ee994dc7 100644 --- a/Tests/test_imagetk.py +++ b/Tests/test_imagetk.py @@ -31,19 +31,19 @@ class TestImageTk(PillowTestCase): def test_kw(self): TEST_JPG = "Tests/images/hopper.jpg" TEST_PNG = "Tests/images/hopper.png" - im1 = Image.open(TEST_JPG) - im2 = Image.open(TEST_PNG) - with open(TEST_PNG, "rb") as fp: - data = fp.read() - kw = {"file": TEST_JPG, "data": data} + with Image.open(TEST_JPG) as im1: + with Image.open(TEST_PNG) as im2: + with open(TEST_PNG, "rb") as fp: + data = fp.read() + kw = {"file": TEST_JPG, "data": data} - # Test "file" - im = ImageTk._get_image_from_kw(kw) - self.assert_image_equal(im, im1) + # Test "file" + im = ImageTk._get_image_from_kw(kw) + self.assert_image_equal(im, im1) - # Test "data" - im = ImageTk._get_image_from_kw(kw) - self.assert_image_equal(im, im2) + # Test "data" + im = ImageTk._get_image_from_kw(kw) + self.assert_image_equal(im, im2) # Test no relevant entry im = ImageTk._get_image_from_kw(kw) diff --git a/Tests/test_numpy.py b/Tests/test_numpy.py index 7de5c3bbd..d3c4ce11d 100644 --- a/Tests/test_numpy.py +++ b/Tests/test_numpy.py @@ -101,9 +101,9 @@ class TestNumpy(PillowTestCase): self.assert_deep_equal(px[x, y], np[y, x]) def test_16bit(self): - img = Image.open("Tests/images/16bit.cropped.tif") - np_img = numpy.array(img) - self._test_img_equals_nparray(img, np_img) + with Image.open("Tests/images/16bit.cropped.tif") as img: + np_img = numpy.array(img) + self._test_img_equals_nparray(img, np_img) self.assertEqual(np_img.dtype, numpy.dtype(" Date: Sat, 30 Nov 2019 10:08:32 +1100 Subject: [PATCH 50/59] Handle broken Photoshop data --- Tests/images/photoshop-200dpi-broken.jpg | Bin 0 -> 10953 bytes Tests/test_file_jpeg.py | 5 +++++ src/PIL/JpegImagePlugin.py | 5 ++++- 3 files changed, 9 insertions(+), 1 deletion(-) create mode 100644 Tests/images/photoshop-200dpi-broken.jpg diff --git a/Tests/images/photoshop-200dpi-broken.jpg b/Tests/images/photoshop-200dpi-broken.jpg new file mode 100644 index 0000000000000000000000000000000000000000..a574872f267caea4bb2c0b185990f01a473f84de GIT binary patch literal 10953 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 literal 0 HcmV?d00001 diff --git a/Tests/test_file_jpeg.py b/Tests/test_file_jpeg.py index 35f2c0940..04851d82c 100644 --- a/Tests/test_file_jpeg.py +++ b/Tests/test_file_jpeg.py @@ -656,6 +656,11 @@ class TestFileJpeg(PillowTestCase): }, ) + # Test that the image can still load, even with broken Photoshop data + # This image had the APP13 length hexedited to be smaller + with Image.open("Tests/images/photoshop-200dpi-broken.jpg") as im_broken: + self.assert_image_equal(im_broken, im) + # This image does not contain a Photoshop header string with Image.open("Tests/images/app13.jpg") as im: self.assertNotIn("photoshop", im.info) diff --git a/src/PIL/JpegImagePlugin.py b/src/PIL/JpegImagePlugin.py index ea2f795a7..4286e1ae7 100644 --- a/src/PIL/JpegImagePlugin.py +++ b/src/PIL/JpegImagePlugin.py @@ -109,7 +109,10 @@ def APP(self, marker): while blocks[offset : offset + 4] == b"8BIM": offset += 4 # resource code - code = i16(blocks, offset) + try: + code = i16(blocks, offset) + except struct.error: + break offset += 2 # resource name (usually empty) name_len = i8(blocks[offset]) From 47b2ae9a634842b1a49ee9a4e15249eef0544a48 Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Sat, 30 Nov 2019 11:05:36 +1100 Subject: [PATCH 51/59] Raise a specific exception if no data is found for an MPO frame --- Tests/images/sugarshack_no_data.mpo | Bin 0 -> 120198 bytes Tests/test_file_mpo.py | 7 +++++++ src/PIL/MpoImagePlugin.py | 5 ++++- 3 files changed, 11 insertions(+), 1 deletion(-) create mode 100644 Tests/images/sugarshack_no_data.mpo diff --git a/Tests/images/sugarshack_no_data.mpo b/Tests/images/sugarshack_no_data.mpo new file mode 100644 index 0000000000000000000000000000000000000000..d94bad53b1f813d180da0a38f9509eb8a2fc4085 GIT binary patch literal 120198 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 y`1P$7zJ)4hg6^ct`UVN>+~EFJ9t literal 0 HcmV?d00001 diff --git a/Tests/test_file_mpo.py b/Tests/test_file_mpo.py index 9c8a2b468..cdcf2b041 100644 --- a/Tests/test_file_mpo.py +++ b/Tests/test_file_mpo.py @@ -113,6 +113,13 @@ class TestFileMpo(PillowTestCase): self.assertEqual(mpinfo[45056], b"0100") self.assertEqual(mpinfo[45057], 2) + def test_mp_no_data(self): + # This image has been manually hexedited to have the second frame + # beyond the end of the file + with Image.open("Tests/images/sugarshack_no_data.mpo") as im: + with self.assertRaises(ValueError): + im.seek(1) + def test_mp_attribute(self): for test_file in test_files: with Image.open(test_file) as im: diff --git a/src/PIL/MpoImagePlugin.py b/src/PIL/MpoImagePlugin.py index a63e76fe7..e97176d57 100644 --- a/src/PIL/MpoImagePlugin.py +++ b/src/PIL/MpoImagePlugin.py @@ -82,7 +82,10 @@ class MpoImageFile(JpegImagePlugin.JpegImageFile): self.offset = self.__mpoffsets[frame] self.fp.seek(self.offset + 2) # skip SOI marker - if i16(self.fp.read(2)) == 0xFFE1: # APP1 + segment = self.fp.read(2) + if not segment: + raise ValueError("No data found for frame") + if i16(segment) == 0xFFE1: # APP1 n = i16(self.fp.read(2)) - 2 self.info["exif"] = ImageFile._safe_read(self.fp, n) From 6745a193c22895d308d4f2450c0358104a181c26 Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Mon, 2 Dec 2019 00:00:04 +1100 Subject: [PATCH 52/59] Updated CHANGES.rst [ci skip] --- CHANGES.rst | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/CHANGES.rst b/CHANGES.rst index d3922f328..5af03f281 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -5,6 +5,15 @@ Changelog (Pillow) 7.0.0 (unreleased) ------------------ +- Handle broken Photoshop data #4239 + [radarhere] + +- Raise a specific exception if no data is found for an MPO frame #4240 + [radarhere] + +- Fix Unicode support for PyPy #4145 + [nulano] + - Drop support for EOL Python 2.7 #4109 [hugovk, radarhere, jdufresne] From ebed90c228e4c88b8bf7303a267e2714a5e3cd57 Mon Sep 17 00:00:00 2001 From: Hugo Date: Mon, 2 Dec 2019 15:26:51 +0200 Subject: [PATCH 53/59] Remove redundant files --- Tests/import_all.py | 14 --------- Tests/make_hash.py | 66 ------------------------------------------ Tests/threaded_save.py | 57 ------------------------------------ Tests/versions.py | 26 ----------------- 4 files changed, 163 deletions(-) delete mode 100644 Tests/import_all.py delete mode 100644 Tests/make_hash.py delete mode 100644 Tests/threaded_save.py delete mode 100644 Tests/versions.py diff --git a/Tests/import_all.py b/Tests/import_all.py deleted file mode 100644 index 33b07f9a2..000000000 --- a/Tests/import_all.py +++ /dev/null @@ -1,14 +0,0 @@ -import glob -import os -import sys -import traceback - -sys.path.insert(0, ".") - -for file in glob.glob("src/PIL/*.py"): - module = os.path.basename(file)[:-3] - try: - exec("from PIL import " + module) - except (ImportError, SyntaxError): - print("===", "failed to import", module) - traceback.print_exc() diff --git a/Tests/make_hash.py b/Tests/make_hash.py deleted file mode 100644 index 7199f8c7f..000000000 --- a/Tests/make_hash.py +++ /dev/null @@ -1,66 +0,0 @@ -# brute-force search for access descriptor hash table - -modes = [ - "1", - "L", - "LA", - "La", - "I", - "I;16", - "I;16L", - "I;16B", - "I;32L", - "I;32B", - "F", - "P", - "PA", - "RGB", - "RGBA", - "RGBa", - "RGBX", - "CMYK", - "YCbCr", - "LAB", - "HSV", -] - - -def hash(s, i): - # djb2 hash: multiply by 33 and xor character - for c in s: - i = (((i << 5) + i) ^ ord(c)) & 0xFFFFFFFF - return i - - -def check(size, i0): - h = [None] * size - for m in modes: - i = hash(m, i0) - i = i % size - if h[i]: - return 0 - h[i] = m - return h - - -min_start = 0 - -# 1) find the smallest table size with no collisions -for min_size in range(len(modes), 16384): - if check(min_size, 0): - print(len(modes), "modes fit in", min_size, "slots") - break - -# 2) see if we can do better with a different initial value -for i0 in range(65556): - for size in range(1, min_size): - if check(size, i0): - if size < min_size: - print(len(modes), "modes fit in", size, "slots with start", i0) - min_size = size - min_start = i0 - -print() - -print("#define ACCESS_TABLE_SIZE", min_size) -print("#define ACCESS_TABLE_HASH", min_start) diff --git a/Tests/threaded_save.py b/Tests/threaded_save.py deleted file mode 100644 index d47c2cf99..000000000 --- a/Tests/threaded_save.py +++ /dev/null @@ -1,57 +0,0 @@ -import io -import queue -import sys -import threading -import time - -from PIL import Image - -test_format = sys.argv[1] if len(sys.argv) > 1 else "PNG" - -im = Image.open("Tests/images/hopper.ppm") -im.load() - -queue = queue.Queue() - -result = [] - - -class Worker(threading.Thread): - def run(self): - while True: - im = queue.get() - if im is None: - queue.task_done() - sys.stdout.write("x") - break - f = io.BytesIO() - im.save(f, test_format, optimize=1) - data = f.getvalue() - result.append(len(data)) - im = Image.open(io.BytesIO(data)) - im.load() - sys.stdout.write(".") - queue.task_done() - - -t0 = time.time() - -threads = 20 -jobs = 100 - -for i in range(threads): - w = Worker() - w.start() - -for i in range(jobs): - queue.put(im) - -for i in range(threads): - queue.put(None) - -queue.join() - -print() -print(time.time() - t0) -print(len(result), sum(result)) -print(result) diff --git a/Tests/versions.py b/Tests/versions.py deleted file mode 100644 index d6433b063..000000000 --- a/Tests/versions.py +++ /dev/null @@ -1,26 +0,0 @@ -from PIL import Image - - -def version(module, version): - v = getattr(module.core, version + "_version", None) - if v: - print(version, v) - - -version(Image, "jpeglib") -version(Image, "zlib") -version(Image, "libtiff") - -try: - from PIL import ImageFont -except ImportError: - pass -else: - version(ImageFont, "freetype2") - -try: - from PIL import ImageCms -except ImportError: - pass -else: - version(ImageCms, "littlecms") From ce382e7858c32ef5e336509fd9e2375dc5412a1a Mon Sep 17 00:00:00 2001 From: Hugo Date: Mon, 2 Dec 2019 15:37:20 +0200 Subject: [PATCH 54/59] Omit from coverage --- .codecov.yml | 3 +++ .coveragerc | 3 +++ 2 files changed, 6 insertions(+) diff --git a/.codecov.yml b/.codecov.yml index 033786a49..d348345dc 100644 --- a/.codecov.yml +++ b/.codecov.yml @@ -12,4 +12,7 @@ comment: off # Matches 'omit:' in .coveragerc ignore: + - "Tests/32bit_segfault_check.py" + - "Tests/bench_cffi_access.py" - "Tests/check_*.py" + - "Tests/createfontdatachunk.py" diff --git a/.coveragerc b/.coveragerc index e93a2a426..f1f095806 100644 --- a/.coveragerc +++ b/.coveragerc @@ -15,4 +15,7 @@ exclude_lines = [run] omit = + Tests/32bit_segfault_check.py + Tests/bench_cffi_access.py Tests/check_*.py + Tests/createfontdatachunk.py From af37cf087392e1e6b8b4a80d5481dd9ef293daeb Mon Sep 17 00:00:00 2001 From: Hugo Date: Wed, 20 Nov 2019 12:11:59 +0200 Subject: [PATCH 55/59] Python 3.5 and 3.6 are tested by GHA, remove from AppVeyor --- .appveyor.yml | 4 ---- 1 file changed, 4 deletions(-) diff --git a/.appveyor.yml b/.appveyor.yml index 553ccdff1..2e3fd0a58 100644 --- a/.appveyor.yml +++ b/.appveyor.yml @@ -15,10 +15,6 @@ environment: matrix: - PYTHON: C:/Python38 - PYTHON: C:/Python38-x64 - - PYTHON: C:/Python37 - - PYTHON: C:/Python37-x64 - - PYTHON: C:/Python36 - - PYTHON: C:/Python36-x64 - PYTHON: C:/Python35 - PYTHON: C:/Python35-x64 - PYTHON: C:/msys64/mingw32 From a4a6a9e83a111e2234eb44092fbe326af4e89358 Mon Sep 17 00:00:00 2001 From: Alexander Date: Wed, 4 Dec 2019 22:47:15 +0300 Subject: [PATCH 56/59] Add La mode packing and unpacking --- Tests/test_lib_pack.py | 6 ++++++ src/libImaging/Pack.c | 3 +++ src/libImaging/Unpack.c | 4 +++- 3 files changed, 12 insertions(+), 1 deletion(-) diff --git a/Tests/test_lib_pack.py b/Tests/test_lib_pack.py index 45958d2eb..91479e343 100644 --- a/Tests/test_lib_pack.py +++ b/Tests/test_lib_pack.py @@ -44,6 +44,9 @@ class TestLibPack(PillowTestCase): self.assert_pack("LA", "LA", 2, (1, 2), (3, 4), (5, 6)) self.assert_pack("LA", "LA;L", 2, (1, 4), (2, 5), (3, 6)) + def test_La(self): + self.assert_pack("La", "La", 2, (1, 2), (3, 4), (5, 6)) + def test_P(self): self.assert_pack("P", "P;1", b"\xe4", 1, 1, 1, 0, 0, 255, 0, 0) self.assert_pack("P", "P;2", b"\xe4", 3, 2, 1, 0) @@ -269,6 +272,9 @@ class TestLibUnpack(PillowTestCase): self.assert_unpack("LA", "LA", 2, (1, 2), (3, 4), (5, 6)) self.assert_unpack("LA", "LA;L", 2, (1, 4), (2, 5), (3, 6)) + def test_La(self): + self.assert_unpack("La", "La", 2, (1, 2), (3, 4), (5, 6)) + def test_P(self): self.assert_unpack("P", "P;1", b"\xe4", 1, 1, 1, 0, 0, 1, 0, 0) self.assert_unpack("P", "P;2", b"\xe4", 3, 2, 1, 0) diff --git a/src/libImaging/Pack.c b/src/libImaging/Pack.c index eaa276af4..a239464d4 100644 --- a/src/libImaging/Pack.c +++ b/src/libImaging/Pack.c @@ -555,6 +555,9 @@ static struct { {"LA", "LA", 16, packLA}, {"LA", "LA;L", 16, packLAL}, + /* greyscale w. alpha premultiplied */ + {"La", "La", 16, packLA}, + /* palette */ {"P", "P;1", 1, pack1}, {"P", "P;2", 2, packP2}, diff --git a/src/libImaging/Unpack.c b/src/libImaging/Unpack.c index ab0c8dc60..adf2dd277 100644 --- a/src/libImaging/Unpack.c +++ b/src/libImaging/Unpack.c @@ -1306,6 +1306,9 @@ static struct { /* greyscale w. alpha */ {"LA", "LA", 16, unpackLA}, {"LA", "LA;L", 16, unpackLAL}, + + /* greyscale w. alpha premultiplied */ + {"La", "La", 16, unpackLA}, /* palette */ {"P", "P;1", 1, unpackP1}, @@ -1384,7 +1387,6 @@ static struct { {"RGBX", "RGBX;16N", 64, unpackRGBA16B}, #endif - /* true colour w. alpha premultiplied */ {"RGBa", "RGBa", 32, copy4}, {"RGBa", "BGRa", 32, unpackBGRA}, From 578d9ec0165c49ad6a01f4c87fd179a284d03ff1 Mon Sep 17 00:00:00 2001 From: Hugo Date: Sat, 7 Dec 2019 00:20:41 +0200 Subject: [PATCH 57/59] Update macOS to reflect GitHub Actions --- docs/installation.rst | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/docs/installation.rst b/docs/installation.rst index 56689e0e5..36ed805fe 100644 --- a/docs/installation.rst +++ b/docs/installation.rst @@ -404,7 +404,9 @@ These platforms are built and tested for every change. +----------------------------------+--------------------------+-----------------------+ | Fedora 31 | 3.7 |x86-64 | +----------------------------------+--------------------------+-----------------------+ -| macOS 10.14 Catalina | 3.5, 3.6, 3.7, 3.8 |x86-64 | +| macOS 10.13 High Sierra | 3.5, 3.6, 3.7, 3.8 |x86-64 | ++----------------------------------+--------------------------+-----------------------+ +| macOS 10.15 Catalina | PyPy3 |x86-64 | +----------------------------------+--------------------------+-----------------------+ | Ubuntu Linux 16.04 LTS | 3.5, 3.6, 3.7, 3.8, PyPy3|x86-64 | +----------------------------------+--------------------------+-----------------------+ From 9a9092773f9c7b6b7f144578071ad5baa60bc14f Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Sat, 7 Dec 2019 22:13:29 +1100 Subject: [PATCH 58/59] Removed references to Python 2.7 [ci skip] --- docs/installation.rst | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/docs/installation.rst b/docs/installation.rst index 36ed805fe..01ec0fe68 100644 --- a/docs/installation.rst +++ b/docs/installation.rst @@ -95,14 +95,13 @@ Pillow can be installed on FreeBSD via the official Ports or Packages systems: **Packages**:: - $ pkg install py27-pillow + $ pkg install py36-pillow .. note:: The `Pillow FreeBSD port `_ and packages - are tested by the ports team with all supported FreeBSD versions - and against Python 2.7 and 3.x. + are tested by the ports team with all supported FreeBSD versions. Building From Source @@ -174,8 +173,6 @@ Many of Pillow's features require external libraries: * Libimagequant is licensed GPLv3, which is more restrictive than the Pillow license, therefore we will not be distributing binaries with libimagequant support enabled. - * Windows support: Libimagequant requires VS2015/MSVC 19 to compile, - so it is unlikely to work with Python 2.7 on Windows. * **libraqm** provides complex text layout support. From 6f9f1c1d2b161a63f6fab051aca8d79c5d8da642 Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Sat, 7 Dec 2019 22:18:51 +1100 Subject: [PATCH 59/59] Removed outdated documentation [ci skip] --- depends/README.rst | 21 --------------------- 1 file changed, 21 deletions(-) diff --git a/depends/README.rst b/depends/README.rst index 069d2b81f..ce88fa47b 100644 --- a/depends/README.rst +++ b/depends/README.rst @@ -7,24 +7,3 @@ build & install non-packaged dependencies; useful for testing with Travis CI. ``install_extra_test_images.sh`` can be used to install additional test images that are used for Travis CI and AppVeyor. - -The other scripts can be used to install all of the dependencies for -the listed operating systems/distros. The ``ubuntu_14.04.sh`` and -``debian_8.2.sh`` scripts have been tested on bare AWS images and will -install all required dependencies for the system Python 2.7 and 3.4 -for all of the optional dependencies. Git may also be required prior -to running the script to actually download Pillow. - -e.g.:: - - $ sudo apt-get install git - $ git clone https://github.com/python-pillow/Pillow.git - $ cd Pillow/depends - $ ./debian_8.2.sh - $ cd .. - $ git checkout [branch or tag] - $ virtualenv -p /usr/bin/python2.7 ~/vpy27 - $ source ~/vpy27/bin/activate - $ make install - $ make test -