diff --git a/.appveyor.yml b/.appveyor.yml index 95d816a98..0cf10597a 100644 --- a/.appveyor.yml +++ b/.appveyor.yml @@ -18,9 +18,13 @@ environment: PIP_DIR: bin VENV: YES - PYTHON: C:/Python27-x64 - - PYTHON: C:/Python34 + - PYTHON: C:/Python37 - PYTHON: C:/Python27 - - PYTHON: C:/Python34-x64 + - PYTHON: C:/Python37-x64 + - PYTHON: C:/Python36 + - PYTHON: C:/Python36-x64 + - PYTHON: C:/Python35 + - PYTHON: C:/Python35-x64 - PYTHON: C:/msys64/mingw32 EXECUTABLE: bin/python3 PIP_DIR: bin diff --git a/.gitattributes b/.gitattributes index 983b58729..2cf25ab1a 100644 --- a/.gitattributes +++ b/.gitattributes @@ -1,2 +1,3 @@ +*.eps binary *.ppm binary *.container binary diff --git a/.gitignore b/.gitignore index de970d726..08955e34f 100644 --- a/.gitignore +++ b/.gitignore @@ -62,6 +62,9 @@ pip-delete-this-directory.txt *.log *.pot +# viewdoc output +.long-description.html + # Vim cruft .*.swp diff --git a/.readthedocs.yml b/.readthedocs.yml new file mode 100644 index 000000000..73e1f8213 --- /dev/null +++ b/.readthedocs.yml @@ -0,0 +1,2 @@ +python: + pip_install: true diff --git a/.travis.yml b/.travis.yml index 36a956e95..9550eb8f2 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,74 +1,102 @@ dist: xenial -sudo: required language: python cache: pip notifications: irc: "chat.freenode.net#pil" -# Run slow PyPy* first, to give them a headstart and reduce waiting time. +# 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. # Then run the remainder, with fastest Docker jobs last. matrix: fast_finish: true include: - - python: "pypy" - dist: trusty - - python: "pypy3" - dist: trusty + - python: "3.6" + name: "Lint" + env: LINT="true" + - python: "pypy2.7-6.0" + name: "PyPy2 Xenial" + dist: xenial + - python: "pypy3.5-6.0" + name: "PyPy3 Xenial" + dist: xenial - python: '3.7' + name: "3.7 Xenial" - python: '2.7' + name: "2.7 Xenial" - python: '2.7' + name: "2.7 Trusty" dist: trusty - python: "2.7_with_system_site_packages" # For PyQt4 + name: "2.7_with_system_site_packages Xenial" + services: xvfb - python: "2.7_with_system_site_packages" # For PyQt4 + name: "2.7_with_system_site_packages Trusty" dist: trusty - python: '3.6' + name: "3.6 Xenial" - python: '3.6' + name: "3.6 Trusty PYTHONOPTIMIZE=1" dist: trusty env: PYTHONOPTIMIZE=1 - python: '3.5' + name: "3.5 Xenial" - python: '3.5' + name: "3.5 Trusty PYTHONOPTIMIZE=2" dist: trusty env: PYTHONOPTIMIZE=2 - - python: '3.4' - dist: trusty - - env: DOCKER="alpine" DOCKER_TAG="pytest" - - env: DOCKER="arch" DOCKER_TAG="pytest" # contains PyQt5 - - env: DOCKER="ubuntu-trusty-x86" DOCKER_TAG="pytest" - - env: DOCKER="ubuntu-xenial-amd64" DOCKER_TAG="pytest" - - env: DOCKER="debian-stretch-x86" DOCKER_TAG="pytest" - - env: DOCKER="centos-6-amd64" DOCKER_TAG="pytest" - - env: DOCKER="centos-7-amd64" DOCKER_TAG="pytest" - - env: DOCKER="amazon-1-amd64" DOCKER_TAG="pytest" - - env: DOCKER="amazon-2-amd64" DOCKER_TAG="pytest" - - env: DOCKER="fedora-26-amd64" DOCKER_TAG="pytest" - - env: DOCKER="fedora-27-amd64" DOCKER_TAG="pytest" + - python: "3.8-dev" + name: "3.8-dev Xenial" + - env: DOCKER="alpine" DOCKER_TAG="master" + - env: DOCKER="arch" DOCKER_TAG="master" # contains PyQt5 + - env: DOCKER="ubuntu-trusty-x86" DOCKER_TAG="master" + - env: DOCKER="ubuntu-xenial-amd64" DOCKER_TAG="master" + - env: DOCKER="debian-stretch-x86" DOCKER_TAG="master" + - env: DOCKER="centos-6-amd64" DOCKER_TAG="master" + - env: DOCKER="centos-7-amd64" DOCKER_TAG="master" + - env: DOCKER="amazon-1-amd64" DOCKER_TAG="master" + - env: DOCKER="amazon-2-amd64" DOCKER_TAG="master" + - env: DOCKER="fedora-28-amd64" DOCKER_TAG="master" + - env: DOCKER="fedora-29-amd64" DOCKER_TAG="master" services: - docker -install: - - if [ "$DOCKER" == "" ]; then .travis/install.sh; fi - before_install: - if [ "$DOCKER" ]; then travis_retry docker pull pythonpillow/$DOCKER:$DOCKER_TAG; fi +install: + - | + if [ "$LINT" == "true" ]; then + pip install tox + elif [ "$DOCKER" == "" ]; then + .travis/install.sh; + fi + before_script: # Qt needs a display for some of the tests, and it's only run on the system site packages install - - "export DISPLAY=:99.0" - - "sh -e /etc/init.d/xvfb start" +- | + if [ "$TRAVIS_JOB_NAME" == "2.7_with_system_site_packages Trusty" ]; then + export DISPLAY=:99.0 + sh -e /etc/init.d/xvfb start + fi script: - - | - if [ "$DOCKER" == "" ]; then - .travis/script.sh - else - # the Pillow user in the docker container is UID 1000 - sudo chown -R 1000 $TRAVIS_BUILD_DIR - docker run -v $TRAVIS_BUILD_DIR:/Pillow pythonpillow/$DOCKER:$DOCKER_TAG - fi +- | + if [ "$LINT" == "true" ]; then + tox -e lint + elif [ "$DOCKER" == "" ]; then + .travis/script.sh + elif [ "$DOCKER" ]; then + # the Pillow user in the docker container is UID 1000 + sudo chown -R 1000 $TRAVIS_BUILD_DIR + docker run -v $TRAVIS_BUILD_DIR:/Pillow pythonpillow/$DOCKER:$DOCKER_TAG + fi after_success: - - .travis/after_success.sh +- | + if [ "$LINT" == "" ]; then + .travis/after_success.sh + fi diff --git a/.travis/after_success.sh b/.travis/after_success.sh index c215f4219..ad1aeffa3 100755 --- a/.travis/after_success.sh +++ b/.travis/after_success.sh @@ -15,15 +15,6 @@ pip install coveralls-merge coveralls-merge coverage.c.json codecov -if [ "$DOCKER" == "" ]; then - pip install pyflakes pycodestyle - pyflakes *.py | tee >(wc -l) - pyflakes src/PIL/*.py | tee >(wc -l) - pyflakes Tests/*.py | tee >(wc -l) - pycodestyle --statistics --count src/PIL/*.py - pycodestyle --statistics --count Tests/*.py -fi - if [ "$TRAVIS_PYTHON_VERSION" == "2.7" ] && [ "$DOCKER" == "" ]; then # Coverage and quality reports on just the latest diff. # (Installation is very slow on Py3, so just do it for Py2.) diff --git a/CHANGES.rst b/CHANGES.rst index 4cb7320ea..e1c08d8bd 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -2,9 +2,201 @@ Changelog (Pillow) ================== -5.3.0 (unreleased) +6.0.0 (unreleased) ------------------ +- Remove unnecessary unittest.main() boilerplate from test files #3631 + [jdufresne] + +- Exif: Seek to IFD offset #3584 + [radarhere] + +- Deprecate PIL.*ImagePlugin.__version__ attributes #3628 + [jdufresne] + +- Docs: Add note about ImageDraw operations that exceed image bounds #3620 + [radarhere] + +- Allow for unknown PNG chunks after image data #3558 + [radarhere] + +- Changed EPS subprocess stdin from devnull to None #3611 + [radarhere] + +- Fix possible integer overflow #3609 + [cgohlke] + +- Catch BaseException for resource cleanup handlers #3574 + [jdufresne] + +- Improve pytest configuration to allow specific tests as CLI args #3579 + [jdufresne] + +- Drop support for Python 3.4 #3596 + [hugovk] + +- Remove deprecated PIL.OleFileIO #3598 + [hugovk] + +- Remove deprecated ImageOps undocumented functions #3599 + [hugovk] + +- Depends: Update libwebp to 1.0.2 #3602 + [radarhere] + +- Detect MIME types #3525 + [radarhere] + +5.4.1 (2019-01-06) +------------------ + +- File closing: Only close __fp if not fp #3540 + [radarhere] + +- Fix build for Termux #3529 + [pslacerda] + +- PNG: Detect MIME types #3525 + [radarhere] + +- PNG: Handle IDAT chunks after image end #3532 + [radarhere] + +5.4.0 (2019-01-01) +------------------ + +- Docs: Improved ImageChops documentation #3522 + [radarhere] + +- Allow RGB and RGBA values for P image putpixel #3519 + [radarhere] + +- Add APNG extension to PNG plugin #3501 + [pirate486743186, radarhere] + +- Lookup ld.so.cache instead of hardcoding search paths #3245 + [pslacerda] + +- Added custom string TIFF tags #3513 + [radarhere] + +- Improve setup.py configuration #3395 + [diorcety] + +- Read textual chunks located after IDAT chunks for PNG #3506 + [radarhere] + +- Performance: Don't try to hash value if enum is empty #3503 + [Glandos] + +- Added custom int and float TIFF tags #3350 + [radarhere] + +- Fixes for issues reported by static code analysis #3393 + [frenzymadness] + +- GIF: Wait until mode is normalized to copy im.info into encoderinfo #3187 + [radarhere] + +- Docs: Add page of deprecations and removals #3486 + [hugovk] + +- Travis CI: Upgrade PyPy from 5.8.0 to 6.0 #3488 + [hugovk] + +- Travis CI: Allow lint job to fail #3467 + [hugovk] + +- Resolve __fp when closing and deleting #3261 + [radarhere] + +- Close exclusive fp before discarding #3461 + [radarhere] + +- Updated open files documentation #3490 + [radarhere] + +- Added libjpeg_turbo to check_feature #3493 + [radarhere] + +- Change color table index background to tuple when saving as WebP #3471 + [radarhere] + +- Allow arbitrary number of comment extension subblocks #3479 + [radarhere] + +- Ensure previous FLI frame is loaded before seeking to the next #3478 + [radarhere] + +- ImageShow improvements #3450 + [radarhere] + +- Depends: Update libimagequant to 2.12.2 #3442, libtiff to 4.0.10 #3458, libwebp to 1.0.1 #3468, Tk Tcl to 8.6.9 #3465 + [radarhere] + +- Check quality_layers type #3464 + [radarhere] + +- Add context manager, __del__ and close methods to TarIO #3455 + [radarhere] + +- Test: Do not play sound when running screencapture command #3454 + [radarhere] + +- Close exclusive fp on open exception #3456 + [radarhere] + +- Only close existing fp in WebP if fp is exclusive #3418 + [radarhere] + +- Docs: Re-add the downloads badge #3443 + [hugovk] + +- Added negative index to PixelAccess #3406 + [Nazime] + +- Change tuple background to global color table index when saving as GIF #3385 + [radarhere] + +- Test: Improved ImageGrab tests #3424 + [radarhere] + +- Flake8 fixes #3422, #3440 + [radarhere, hugovk] + +- Only ask for YCbCr->RGB libtiff conversion for jpeg-compressed tiffs #3417 + [kkopachev] + +- Optimise ImageOps.fit by combining resize and crop #3409 + [homm] + +5.3.0 (2018-10-01) +------------------ + +- Changed Image size property to be read-only by default #3203 + [radarhere] + +- Add warnings if image file identification fails due to lack of WebP support #3169 + [radarhere, hugovk] + +- Hide the Ghostscript progress dialog popup on Windows #3378 + [hugovk] + +- Adding support to reading tiled and YcbCr jpeg tiffs through libtiff #3227 + [kkopachev] + +- Fixed None as TIFF compression argument #3310 + [radarhere] + +- Changed GIF seek to remove previous info items #3324 + [radarhere] + +- Improved PDF document info #3274 + [radarhere] + +- Add line width parameter to rectangle and ellipse-based shapes #3094 + [hugovk, radarhere] + - Fixed decompression bomb check in _crop #3313 [dinkolubina, hugovk] @@ -3850,7 +4042,7 @@ Pre-fork (1.1.3 final released) + Made setup.py look for old versions of zlib. For some back- - ground, see: http://www.gzip.org/zlib/advisory-2002-03-11.txt + ground, see: https://zlib.net/advisory-2002-03-11.txt (1.1.3c2 released) diff --git a/LICENSE b/LICENSE index 80456a753..c106eeb1a 100644 --- a/LICENSE +++ b/LICENSE @@ -5,7 +5,7 @@ The Python Imaging Library (PIL) is Pillow is the friendly PIL fork. It is - Copyright © 2010-2018 by Alex Clark and contributors + Copyright © 2010-2019 by Alex Clark and contributors Like PIL, Pillow is licensed under the open source PIL Software License: diff --git a/MANIFEST.in b/MANIFEST.in index 40b2ef5d7..809d0d667 100644 --- a/MANIFEST.in +++ b/MANIFEST.in @@ -22,6 +22,7 @@ exclude .coveragerc exclude .codecov.yml exclude .editorconfig exclude .landscape.yaml +exclude .readthedocs.yml exclude .travis exclude .travis/* exclude tox.ini diff --git a/README.rst b/README.rst index b88a103b0..381859c18 100644 --- a/README.rst +++ b/README.rst @@ -16,7 +16,7 @@ Pillow is the friendly PIL fork by `Alex Clark and Contributors 5.2.0" Pillow - $ git push -``` + ```bash + git clone https://github.com/python-pillow/pillow-wheels + cd pillow-wheels + git submodule init + git submodule update Pillow + cd Pillow + git fetch --all + git checkout [[release tag]] + cd .. + git commit -m "Pillow -> 5.2.0" Pillow + git push + ``` * [ ] Download distributions from the [Pillow Wheel Builder container](http://a365fff413fe338398b6-1c8a9b3114517dc5fe17b7c3f8c63a43.r19.cf2.rackcdn.com/). - + ```bash + wget -m -A 'Pillow-*' \ + http://a365fff413fe338398b6-1c8a9b3114517dc5fe17b7c3f8c63a43.r19.cf2.rackcdn.com + ``` ## Publicize Release @@ -106,4 +109,4 @@ Released as needed privately to individual vendors for critical security-related ## Documentation -* [ ] Make sure the default version for Read the Docs is the latest release version, i.e. ``5.2.0`` rather than ``latest`` e.g. https://pillow.readthedocs.io/en/5.2.x/ +* [ ] Make sure the default version for Read the Docs is the latest tagged release e.g. `d2d43879` (5.4.0) diff --git a/Tests/README.rst b/Tests/README.rst index 44f6f4792..d64a34bd7 100644 --- a/Tests/README.rst +++ b/Tests/README.rst @@ -13,28 +13,20 @@ Install:: Execution --------- -**If Pillow has been built in-place** - To run an individual test:: - python Tests/test_image.py + pytest Tests/test_image.py -Run all the tests from the root of the Pillow source distribution:: +Or:: - pytest -vx Tests - -Or with coverage:: - - pytest -vx --cov PIL --cov-report term Tests - coverage html - open htmlcov/index.html - -**If Pillow has been installed** - -To run an individual test:: - - pytest -k Tests/test_image.py + pytest -k test_image.py Run all the tests from the root of the Pillow source distribution:: pytest + +Or with coverage:: + + pytest --cov PIL --cov-report term + coverage html + open htmlcov/index.html diff --git a/Tests/__init__.py b/Tests/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/Tests/bench_cffi_access.py b/Tests/bench_cffi_access.py index 300017168..79427dca3 100644 --- a/Tests/bench_cffi_access.py +++ b/Tests/bench_cffi_access.py @@ -1,4 +1,4 @@ -from helper import unittest, PillowTestCase, hopper +from .helper import unittest, PillowTestCase, hopper # Not running this test by default. No DOS against Travis CI. diff --git a/Tests/bench_get.py b/Tests/bench_get.py index e9afe1af5..68ac2c9a2 100644 --- a/Tests/bench_get.py +++ b/Tests/bench_get.py @@ -1,4 +1,4 @@ -import helper +from . import helper import timeit import sys diff --git a/Tests/check_fli_overflow.py b/Tests/check_fli_overflow.py index 9b370da3c..3f7c58015 100644 --- a/Tests/check_fli_overflow.py +++ b/Tests/check_fli_overflow.py @@ -1,4 +1,4 @@ -from helper import unittest, PillowTestCase +from .helper import unittest, PillowTestCase from PIL import Image TEST_FILE = "Tests/images/fli_overflow.fli" diff --git a/Tests/check_imaging_leaks.py b/Tests/check_imaging_leaks.py index 0c41b6da1..7fa0663e8 100755 --- a/Tests/check_imaging_leaks.py +++ b/Tests/check_imaging_leaks.py @@ -1,7 +1,7 @@ #!/usr/bin/env python from __future__ import division -from helper import unittest, PillowTestCase +from .helper import unittest, PillowTestCase import sys from PIL import Image diff --git a/Tests/check_j2k_leaks.py b/Tests/check_j2k_leaks.py index 5fafccbc0..d87b7f041 100755 --- a/Tests/check_j2k_leaks.py +++ b/Tests/check_j2k_leaks.py @@ -1,4 +1,4 @@ -from helper import unittest, PillowTestCase +from .helper import unittest, PillowTestCase import sys from PIL import Image from io import BytesIO diff --git a/Tests/check_j2k_overflow.py b/Tests/check_j2k_overflow.py index 1dd8db69f..f456ebb32 100644 --- a/Tests/check_j2k_overflow.py +++ b/Tests/check_j2k_overflow.py @@ -1,5 +1,5 @@ from PIL import Image -from helper import unittest, PillowTestCase +from .helper import unittest, PillowTestCase class TestJ2kEncodeOverflow(PillowTestCase): diff --git a/Tests/check_jpeg_leaks.py b/Tests/check_jpeg_leaks.py index c85f6f030..97a5650e0 100644 --- a/Tests/check_jpeg_leaks.py +++ b/Tests/check_jpeg_leaks.py @@ -1,4 +1,4 @@ -from helper import unittest, PillowTestCase, hopper +from .helper import unittest, PillowTestCase, hopper from io import BytesIO import sys diff --git a/Tests/check_large_memory.py b/Tests/check_large_memory.py index ef0cd1f80..24d687ea6 100644 --- a/Tests/check_large_memory.py +++ b/Tests/check_large_memory.py @@ -1,6 +1,6 @@ import sys -from helper import unittest, PillowTestCase +from .helper import unittest, PillowTestCase # This test is not run automatically. # @@ -21,7 +21,7 @@ class LargeMemoryTest(PillowTestCase): def _write_png(self, xdim, ydim): f = self.tempfile('temp.png') - im = Image.new('L', (xdim, ydim), (0)) + im = Image.new('L', (xdim, ydim), 0) im.save(f) def test_large(self): diff --git a/Tests/check_large_memory_numpy.py b/Tests/check_large_memory_numpy.py index e48d98367..b66988fd5 100644 --- a/Tests/check_large_memory_numpy.py +++ b/Tests/check_large_memory_numpy.py @@ -1,6 +1,6 @@ import sys -from helper import unittest, PillowTestCase +from .helper import unittest, PillowTestCase # This test is not run automatically. # diff --git a/Tests/check_libtiff_segfault.py b/Tests/check_libtiff_segfault.py index 6611648a5..f8c4a3090 100644 --- a/Tests/check_libtiff_segfault.py +++ b/Tests/check_libtiff_segfault.py @@ -1,4 +1,4 @@ -from helper import unittest, PillowTestCase +from .helper import unittest, PillowTestCase from PIL import Image TEST_FILE = "Tests/images/libtiff_segfault.tif" diff --git a/Tests/check_png_dos.py b/Tests/check_png_dos.py index 0bbaf6a4d..9a446bf84 100644 --- a/Tests/check_png_dos.py +++ b/Tests/check_png_dos.py @@ -1,4 +1,4 @@ -from helper import unittest, PillowTestCase +from .helper import unittest, PillowTestCase from PIL import Image, PngImagePlugin, ImageFile from io import BytesIO import zlib @@ -36,7 +36,7 @@ class TestPngDos(PillowTestCase): def test_dos_total_memory(self): im = Image.new('L', (1, 1)) - compressed_data = zlib.compress('a'*1024*1023) + compressed_data = zlib.compress(b'a'*1024*1023) info = PngImagePlugin.PngInfo() diff --git a/Tests/createfontdatachunk.py b/Tests/createfontdatachunk.py index 720fd0067..bace38a23 100755 --- a/Tests/createfontdatachunk.py +++ b/Tests/createfontdatachunk.py @@ -2,7 +2,6 @@ from __future__ import print_function import base64 import os -import sys if __name__ == "__main__": # create font data chunk for embedding @@ -10,7 +9,9 @@ if __name__ == "__main__": print(" f._load_pilfont_data(") print(" # %s" % os.path.basename(font)) print(" BytesIO(base64.decodestring(b'''") - base64.encode(open(font + ".pil", "rb"), sys.stdout) + with open(font + ".pil", "rb") as fp: + print(base64.b64encode(fp.read()).decode()) print("''')), Image.open(BytesIO(base64.decodestring(b'''") - base64.encode(open(font + ".pbm", "rb"), sys.stdout) + with open(font + ".pbm", "rb") as fp: + print(base64.b64encode(fp.read()).decode()) print("'''))))") diff --git a/Tests/helper.py b/Tests/helper.py index 8fb34848b..b47604a60 100644 --- a/Tests/helper.py +++ b/Tests/helper.py @@ -53,10 +53,6 @@ class PillowTestCase(unittest.TestCase): # holds last result object passed to run method: self.currentResult = None - # Nicer output for --verbose - def __str__(self): - return self.__class__.__name__ + "." + self._testMethodName - def run(self, result=None): self.currentResult = result # remember result for use later unittest.TestCase.run(self, result) # call superclass run method @@ -84,7 +80,7 @@ class PillowTestCase(unittest.TestCase): self.assertTrue( all(x == y for x, y in zip(a, b)), msg or "got %s, expected %s" % (a, b)) - except: + except Exception: self.assertEqual(a, b, msg) def assert_image(self, im, mode, size, msg=None): @@ -110,7 +106,7 @@ class PillowTestCase(unittest.TestCase): try: url = test_image_results.upload(a, b) logger.error("Url for test images: %s" % url) - except Exception as msg: + except Exception: pass self.fail(msg or "got different content") @@ -149,7 +145,7 @@ class PillowTestCase(unittest.TestCase): try: url = test_image_results.upload(a, b) logger.error("Url for test images: %s" % url) - except: + except Exception: pass raise e @@ -163,7 +159,6 @@ class PillowTestCase(unittest.TestCase): def assert_warning(self, warn_class, func, *args, **kwargs): import warnings - result = None with warnings.catch_warnings(record=True) as w: # Cause all warnings to always be triggered. warnings.simplefilter("always") @@ -175,7 +170,7 @@ class PillowTestCase(unittest.TestCase): if warn_class is None: self.assertEqual(len(w), 0, "Expected no warnings, got %s" % - list(v.category for v in w)) + [v.category for v in w]) else: self.assertGreaterEqual(len(w), 1) found = False @@ -187,10 +182,10 @@ class PillowTestCase(unittest.TestCase): return result def assert_all_same(self, items, msg=None): - self.assertTrue(items.count(items[0]) == len(items), msg) + self.assertEqual(items.count(items[0]), len(items), msg) def assert_not_all_same(self, items, msg=None): - self.assertFalse(items.count(items[0]) == len(items), msg) + self.assertNotEqual(items.count(items[0]), len(items), msg) def assert_tuple_approx_equal(self, actuals, targets, threshold, msg): """Tests if actuals has values within threshold from targets""" diff --git a/Tests/images/a_fli.png b/Tests/images/a_fli.png new file mode 100644 index 000000000..93c3f1b12 Binary files /dev/null and b/Tests/images/a_fli.png differ diff --git a/Tests/images/balloon.jpf b/Tests/images/balloon.jpf new file mode 100644 index 000000000..767eab5dd Binary files /dev/null and b/Tests/images/balloon.jpf differ diff --git a/Tests/images/exif-ifd-offset.jpg b/Tests/images/exif-ifd-offset.jpg new file mode 100644 index 000000000..e5dfc6807 Binary files /dev/null and b/Tests/images/exif-ifd-offset.jpg differ diff --git a/Tests/images/hopper_idat_after_image_end.png b/Tests/images/hopper_idat_after_image_end.png new file mode 100644 index 000000000..70b4a6400 Binary files /dev/null and b/Tests/images/hopper_idat_after_image_end.png differ diff --git a/Tests/images/hopper_unknown_pixel_mode.tif b/Tests/images/hopper_unknown_pixel_mode.tif new file mode 100644 index 000000000..89a8c5e17 Binary files /dev/null and b/Tests/images/hopper_unknown_pixel_mode.tif differ diff --git a/Tests/images/hopper_zero_comment_subblocks.gif b/Tests/images/hopper_zero_comment_subblocks.gif new file mode 100644 index 000000000..5f482c042 Binary files /dev/null and b/Tests/images/hopper_zero_comment_subblocks.gif differ diff --git a/Tests/images/imagedraw_arc_width.png b/Tests/images/imagedraw_arc_width.png new file mode 100644 index 000000000..ff3f1f0b2 Binary files /dev/null and b/Tests/images/imagedraw_arc_width.png differ diff --git a/Tests/images/imagedraw_arc_width_fill.png b/Tests/images/imagedraw_arc_width_fill.png new file mode 100644 index 000000000..9572a6059 Binary files /dev/null and b/Tests/images/imagedraw_arc_width_fill.png differ diff --git a/Tests/images/imagedraw_chord_width.png b/Tests/images/imagedraw_chord_width.png new file mode 100644 index 000000000..33a59b487 Binary files /dev/null and b/Tests/images/imagedraw_chord_width.png differ diff --git a/Tests/images/imagedraw_chord_width_fill.png b/Tests/images/imagedraw_chord_width_fill.png new file mode 100644 index 000000000..809c3ea1c Binary files /dev/null and b/Tests/images/imagedraw_chord_width_fill.png differ diff --git a/Tests/images/imagedraw_ellipse_width.png b/Tests/images/imagedraw_ellipse_width.png new file mode 100644 index 000000000..ec0ca6731 Binary files /dev/null and b/Tests/images/imagedraw_ellipse_width.png differ diff --git a/Tests/images/imagedraw_ellipse_width_fill.png b/Tests/images/imagedraw_ellipse_width_fill.png new file mode 100644 index 000000000..9b7be6029 Binary files /dev/null and b/Tests/images/imagedraw_ellipse_width_fill.png differ diff --git a/Tests/images/imagedraw_pieslice_width.png b/Tests/images/imagedraw_pieslice_width.png new file mode 100644 index 000000000..3bd69222c Binary files /dev/null and b/Tests/images/imagedraw_pieslice_width.png differ diff --git a/Tests/images/imagedraw_pieslice_width_fill.png b/Tests/images/imagedraw_pieslice_width_fill.png new file mode 100644 index 000000000..c5a34e0f3 Binary files /dev/null and b/Tests/images/imagedraw_pieslice_width_fill.png differ diff --git a/Tests/images/imagedraw_rectangle_width.png b/Tests/images/imagedraw_rectangle_width.png new file mode 100644 index 000000000..e39659921 Binary files /dev/null and b/Tests/images/imagedraw_rectangle_width.png differ diff --git a/Tests/images/imagedraw_rectangle_width_fill.png b/Tests/images/imagedraw_rectangle_width_fill.png new file mode 100644 index 000000000..d5243c608 Binary files /dev/null and b/Tests/images/imagedraw_rectangle_width_fill.png differ diff --git a/Tests/images/iss634.apng b/Tests/images/iss634.apng new file mode 100644 index 000000000..89e025906 Binary files /dev/null and b/Tests/images/iss634.apng differ diff --git a/Tests/images/sugarshack_ifd_offset.mpo b/Tests/images/sugarshack_ifd_offset.mpo new file mode 100644 index 000000000..2dcac876f Binary files /dev/null and b/Tests/images/sugarshack_ifd_offset.mpo differ diff --git a/Tests/images/tiff_strip_cmyk_jpeg.tif b/Tests/images/tiff_strip_cmyk_jpeg.tif new file mode 100644 index 000000000..0207d27c7 Binary files /dev/null and b/Tests/images/tiff_strip_cmyk_jpeg.tif differ diff --git a/Tests/images/tiff_strip_planar_raw.tif b/Tests/images/tiff_strip_planar_raw.tif new file mode 100644 index 000000000..ab8b3c3f3 Binary files /dev/null and b/Tests/images/tiff_strip_planar_raw.tif differ diff --git a/Tests/images/tiff_strip_planar_raw_with_overviews.tif b/Tests/images/tiff_strip_planar_raw_with_overviews.tif new file mode 100644 index 000000000..e032c5c36 Binary files /dev/null and b/Tests/images/tiff_strip_planar_raw_with_overviews.tif differ diff --git a/Tests/images/tiff_strip_raw.tif b/Tests/images/tiff_strip_raw.tif new file mode 100644 index 000000000..81bb42ce7 Binary files /dev/null and b/Tests/images/tiff_strip_raw.tif differ diff --git a/Tests/images/tiff_strip_ycbcr_jpeg_1x1_sampling.tif b/Tests/images/tiff_strip_ycbcr_jpeg_1x1_sampling.tif new file mode 100644 index 000000000..ca8b634bb Binary files /dev/null and b/Tests/images/tiff_strip_ycbcr_jpeg_1x1_sampling.tif differ diff --git a/Tests/images/tiff_strip_ycbcr_jpeg_2x2_sampling.tif b/Tests/images/tiff_strip_ycbcr_jpeg_2x2_sampling.tif new file mode 100644 index 000000000..c3207e451 Binary files /dev/null and b/Tests/images/tiff_strip_ycbcr_jpeg_2x2_sampling.tif differ diff --git a/Tests/images/tiff_tiled_cmyk_jpeg.tif b/Tests/images/tiff_tiled_cmyk_jpeg.tif new file mode 100644 index 000000000..0cc27b69c Binary files /dev/null and b/Tests/images/tiff_tiled_cmyk_jpeg.tif differ diff --git a/Tests/images/tiff_tiled_planar_raw.tif b/Tests/images/tiff_tiled_planar_raw.tif new file mode 100644 index 000000000..2e3ecc811 Binary files /dev/null and b/Tests/images/tiff_tiled_planar_raw.tif differ diff --git a/Tests/images/tiff_tiled_raw.tif b/Tests/images/tiff_tiled_raw.tif new file mode 100644 index 000000000..25803c395 Binary files /dev/null and b/Tests/images/tiff_tiled_raw.tif differ diff --git a/Tests/images/tiff_tiled_ycbcr_jpeg_1x1_sampling.tif b/Tests/images/tiff_tiled_ycbcr_jpeg_1x1_sampling.tif new file mode 100644 index 000000000..75ce833a1 Binary files /dev/null and b/Tests/images/tiff_tiled_ycbcr_jpeg_1x1_sampling.tif differ diff --git a/Tests/images/tiff_tiled_ycbcr_jpeg_2x2_sampling.tif b/Tests/images/tiff_tiled_ycbcr_jpeg_2x2_sampling.tif new file mode 100644 index 000000000..ff8b4a409 Binary files /dev/null and b/Tests/images/tiff_tiled_ycbcr_jpeg_2x2_sampling.tif differ diff --git a/Tests/test_000_sanity.py b/Tests/test_000_sanity.py index 67aff8ecc..321a6b3ce 100644 --- a/Tests/test_000_sanity.py +++ b/Tests/test_000_sanity.py @@ -1,4 +1,4 @@ -from helper import unittest, PillowTestCase +from .helper import PillowTestCase import PIL import PIL.Image @@ -9,7 +9,7 @@ class TestSanity(PillowTestCase): def test_sanity(self): # Make sure we have the binary extension - im = PIL.Image.core.new("L", (100, 100)) + PIL.Image.core.new("L", (100, 100)) self.assertEqual(PIL.Image.VERSION[:3], '1.1') @@ -19,12 +19,8 @@ class TestSanity(PillowTestCase): self.assertEqual(len(im.tobytes()), 1300) # Create images in all remaining major modes. - im = PIL.Image.new("L", (100, 100)) - im = PIL.Image.new("P", (100, 100)) - im = PIL.Image.new("RGB", (100, 100)) - im = PIL.Image.new("I", (100, 100)) - im = PIL.Image.new("F", (100, 100)) - - -if __name__ == '__main__': - unittest.main() + PIL.Image.new("L", (100, 100)) + PIL.Image.new("P", (100, 100)) + PIL.Image.new("RGB", (100, 100)) + PIL.Image.new("I", (100, 100)) + PIL.Image.new("F", (100, 100)) diff --git a/Tests/test_binary.py b/Tests/test_binary.py index 7e22e0f59..bf9ba1e5f 100644 --- a/Tests/test_binary.py +++ b/Tests/test_binary.py @@ -1,4 +1,4 @@ -from helper import unittest, PillowTestCase +from .helper import PillowTestCase from PIL import _binary @@ -22,7 +22,3 @@ class TestBinary(PillowTestCase): self.assertEqual(_binary.o16be(65535), b'\xff\xff') self.assertEqual(_binary.o32be(65535), b'\x00\x00\xff\xff') - - -if __name__ == '__main__': - unittest.main() diff --git a/Tests/test_bmp_reference.py b/Tests/test_bmp_reference.py index af25f7162..0e32c93dd 100644 --- a/Tests/test_bmp_reference.py +++ b/Tests/test_bmp_reference.py @@ -1,5 +1,5 @@ from __future__ import print_function -from helper import unittest, PillowTestCase +from .helper import PillowTestCase from PIL import Image import os @@ -17,11 +17,15 @@ class TestBmpReference(PillowTestCase): """ These shouldn't crash/dos, but they shouldn't return anything either """ for f in self.get_files('b'): - try: - im = Image.open(f) - im.load() - except Exception: # as msg: - pass + def open(f): + try: + im = Image.open(f) + im.load() + except Exception: # as msg: + pass + + # Assert that there is no unclosed file warning + self.assert_warning(None, open, f) def test_questionable(self): """ These shouldn't crash/dos, but it's not well defined that these @@ -99,7 +103,3 @@ class TestBmpReference(PillowTestCase): os.path.join(base, 'g', 'pal4rle.bmp')) if f not in unsupported: self.fail("Unsupported Image %s: %s" % (f, msg)) - - -if __name__ == '__main__': - unittest.main() diff --git a/Tests/test_box_blur.py b/Tests/test_box_blur.py index 2787dfc0d..b9939c5f2 100644 --- a/Tests/test_box_blur.py +++ b/Tests/test_box_blur.py @@ -1,4 +1,4 @@ -from helper import unittest, PillowTestCase +from .helper import PillowTestCase from PIL import Image, ImageFilter @@ -215,7 +215,3 @@ class TestBoxBlur(PillowTestCase): passes=3, delta=1, ) - - -if __name__ == '__main__': - unittest.main() diff --git a/Tests/test_color_lut.py b/Tests/test_color_lut.py index ec370f70a..97035c793 100644 --- a/Tests/test_color_lut.py +++ b/Tests/test_color_lut.py @@ -3,7 +3,7 @@ from __future__ import division from array import array from PIL import Image, ImageFilter -from helper import unittest, PillowTestCase +from .helper import unittest, PillowTestCase try: import numpy @@ -294,6 +294,8 @@ class TestColorLut3DFilter(PillowTestCase): lut = ImageFilter.Color3DLUT((2, 2, 2), [(0, 1, 2, 3)] * 8, channels=4) + self.assertEqual(tuple(lut.size), (2, 2, 2)) + self.assertEqual(lut.table, list(range(4)) * 8) @unittest.skipIf(numpy is None, "Numpy is not installed") def test_numpy_sources(self): @@ -517,7 +519,3 @@ class TestTransformColorLut3D(PillowTestCase): self.assertEqual(lut.table[0:16], [ 0.0, 0.0, 0.0, 0.5, 0.25, 0.0, 0.0, 0.5, 0.0, 0.0, 0.0, 0.5, 0.0, 0.16, 0.0, 0.5]) - - -if __name__ == '__main__': - unittest.main() diff --git a/Tests/test_core_resources.py b/Tests/test_core_resources.py index 11f26d38e..fe9f5ccc4 100644 --- a/Tests/test_core_resources.py +++ b/Tests/test_core_resources.py @@ -2,7 +2,7 @@ from __future__ import division, print_function import sys -from helper import unittest, PillowTestCase +from .helper import unittest, PillowTestCase from PIL import Image diff --git a/Tests/test_decompression_bomb.py b/Tests/test_decompression_bomb.py index 0a30e25f1..bc0bab525 100644 --- a/Tests/test_decompression_bomb.py +++ b/Tests/test_decompression_bomb.py @@ -1,4 +1,4 @@ -from helper import unittest, PillowTestCase, hopper +from .helper import PillowTestCase, hopper from PIL import Image @@ -83,7 +83,3 @@ class TestDecompressionCrop(PillowTestCase): for value in error_values: with self.assertRaises(Image.DecompressionBombError): im.crop(value) - - -if __name__ == '__main__': - unittest.main() diff --git a/Tests/test_features.py b/Tests/test_features.py index 54d668d2f..15b5982ce 100644 --- a/Tests/test_features.py +++ b/Tests/test_features.py @@ -1,11 +1,11 @@ -from helper import unittest, PillowTestCase +from .helper import unittest, PillowTestCase from PIL import features try: from PIL import _webp HAVE_WEBP = True -except: +except ImportError: HAVE_WEBP = False @@ -23,20 +23,20 @@ class TestFeatures(PillowTestCase): self.assertEqual(features.check_feature(feature), features.check(feature)) - @unittest.skipUnless(HAVE_WEBP, True) - def check_webp_transparency(self): + @unittest.skipUnless(HAVE_WEBP, "WebP not available") + def test_webp_transparency(self): self.assertEqual(features.check('transp_webp'), not _webp.WebPDecoderBuggyAlpha()) self.assertEqual(features.check('transp_webp'), _webp.HAVE_TRANSPARENCY) - @unittest.skipUnless(HAVE_WEBP, True) - def check_webp_mux(self): + @unittest.skipUnless(HAVE_WEBP, "WebP not available") + def test_webp_mux(self): self.assertEqual(features.check('webp_mux'), _webp.HAVE_WEBPMUX) - @unittest.skipUnless(HAVE_WEBP, True) - def check_webp_anim(self): + @unittest.skipUnless(HAVE_WEBP, "WebP not available") + def test_webp_anim(self): self.assertEqual(features.check('webp_anim'), _webp.HAVE_WEBPANIM) @@ -63,7 +63,3 @@ class TestFeatures(PillowTestCase): module = "unsupported_module" # Act / Assert self.assertRaises(ValueError, features.check_module, module) - - -if __name__ == '__main__': - unittest.main() diff --git a/Tests/test_file_blp.py b/Tests/test_file_blp.py index e0530cb1f..59951a890 100644 --- a/Tests/test_file_blp.py +++ b/Tests/test_file_blp.py @@ -1,6 +1,6 @@ from PIL import Image -from helper import PillowTestCase, unittest +from .helper import PillowTestCase class TestFileBlp(PillowTestCase): @@ -18,7 +18,3 @@ class TestFileBlp(PillowTestCase): im = Image.open("Tests/images/blp/blp2_dxt1a.blp") target = Image.open("Tests/images/blp/blp2_dxt1a.png") self.assert_image_equal(im, target) - - -if __name__ == "__main__": - unittest.main() diff --git a/Tests/test_file_bmp.py b/Tests/test_file_bmp.py index bfd97016f..c51e2bb4e 100644 --- a/Tests/test_file_bmp.py +++ b/Tests/test_file_bmp.py @@ -1,4 +1,4 @@ -from helper import unittest, PillowTestCase, hopper +from .helper import PillowTestCase, hopper from PIL import Image, BmpImagePlugin import io @@ -75,7 +75,3 @@ class TestFileBmp(PillowTestCase): im = BmpImagePlugin.DibImageFile('Tests/images/clipboard.dib') target = Image.open('Tests/images/clipboard_target.png') self.assert_image_equal(im, target) - - -if __name__ == '__main__': - unittest.main() diff --git a/Tests/test_file_bufrstub.py b/Tests/test_file_bufrstub.py index 08980a996..307029136 100644 --- a/Tests/test_file_bufrstub.py +++ b/Tests/test_file_bufrstub.py @@ -1,4 +1,4 @@ -from helper import unittest, PillowTestCase, hopper +from .helper import PillowTestCase, hopper from PIL import BufrStubImagePlugin, Image @@ -40,7 +40,3 @@ class TestFileBufrStub(PillowTestCase): # Act / Assert: stub cannot save without an implemented handler self.assertRaises(IOError, im.save, tmpfile) - - -if __name__ == '__main__': - unittest.main() diff --git a/Tests/test_file_container.py b/Tests/test_file_container.py index 55228be0c..8ca3310e5 100644 --- a/Tests/test_file_container.py +++ b/Tests/test_file_container.py @@ -1,4 +1,4 @@ -from helper import unittest, PillowTestCase, hopper +from .helper import PillowTestCase, hopper from PIL import Image from PIL import ContainerIO @@ -123,7 +123,3 @@ class TestFileContainer(PillowTestCase): # Assert self.assertEqual(data, expected) - - -if __name__ == '__main__': - unittest.main() diff --git a/Tests/test_file_cur.py b/Tests/test_file_cur.py index 23055a0ad..734c330e6 100644 --- a/Tests/test_file_cur.py +++ b/Tests/test_file_cur.py @@ -1,4 +1,4 @@ -from helper import unittest, PillowTestCase +from .helper import PillowTestCase from PIL import Image, CurImagePlugin @@ -26,9 +26,6 @@ class TestFileCur(PillowTestCase): no_cursors_file = "Tests/images/no_cursors.cur" cur = CurImagePlugin.CurImageFile(TEST_FILE) + cur.fp.close() with open(no_cursors_file, "rb") as cur.fp: self.assertRaises(TypeError, cur._open) - - -if __name__ == '__main__': - unittest.main() diff --git a/Tests/test_file_dcx.py b/Tests/test_file_dcx.py index 28ebb91dc..0da364648 100644 --- a/Tests/test_file_dcx.py +++ b/Tests/test_file_dcx.py @@ -1,4 +1,4 @@ -from helper import unittest, PillowTestCase, hopper +from .helper import PillowTestCase, hopper from PIL import Image, DcxImagePlugin @@ -20,6 +20,12 @@ class TestFileDcx(PillowTestCase): orig = hopper() self.assert_image_equal(im, orig) + def test_unclosed_file(self): + def open(): + im = Image.open(TEST_FILE) + im.load() + self.assert_warning(None, open) + def test_invalid_file(self): with open("Tests/images/flower.jpg", "rb") as fp: self.assertRaises(SyntaxError, @@ -58,7 +64,3 @@ class TestFileDcx(PillowTestCase): # Act / Assert self.assertRaises(EOFError, im.seek, frame) - - -if __name__ == '__main__': - unittest.main() diff --git a/Tests/test_file_dds.py b/Tests/test_file_dds.py index ec975f7ec..af2524c4a 100644 --- a/Tests/test_file_dds.py +++ b/Tests/test_file_dds.py @@ -1,6 +1,6 @@ from io import BytesIO -from helper import unittest, PillowTestCase +from .helper import PillowTestCase from PIL import Image, DdsImagePlugin TEST_FILE_DXT1 = "Tests/images/dxt1-rgb-4bbp-noalpha_MipMaps-1.dds" @@ -110,7 +110,3 @@ class TestFileDds(PillowTestCase): im.load() self.assertRaises(IOError, short_file) - - -if __name__ == '__main__': - unittest.main() diff --git a/Tests/test_file_eps.py b/Tests/test_file_eps.py index 66fedee90..1f6025d69 100644 --- a/Tests/test_file_eps.py +++ b/Tests/test_file_eps.py @@ -1,8 +1,10 @@ -from helper import unittest, PillowTestCase, hopper +from .helper import unittest, PillowTestCase, hopper from PIL import Image, EpsImagePlugin import io +HAS_GHOSTSCRIPT = EpsImagePlugin.has_ghostscript() + # Our two EPS test files (they are identical except for their bounding boxes) file1 = "Tests/images/zero_bb.eps" file2 = "Tests/images/non_zero_bb.eps" @@ -20,10 +22,7 @@ file3 = "Tests/images/binary_preview_map.eps" class TestFileEps(PillowTestCase): - def setUp(self): - if not EpsImagePlugin.has_ghostscript(): - self.skipTest("Ghostscript not available") - + @unittest.skipUnless(HAS_GHOSTSCRIPT, "Ghostscript not available") def test_sanity(self): # Regular scale image1 = Image.open(file1) @@ -57,6 +56,7 @@ class TestFileEps(PillowTestCase): self.assertRaises(SyntaxError, EpsImagePlugin.EpsImageFile, invalid_file) + @unittest.skipUnless(HAS_GHOSTSCRIPT, "Ghostscript not available") def test_cmyk(self): cmyk_image = Image.open("Tests/images/pil_sample_cmyk.eps") @@ -71,6 +71,7 @@ class TestFileEps(PillowTestCase): target = Image.open('Tests/images/pil_sample_rgb.jpg') self.assert_image_similar(cmyk_image, target, 10) + @unittest.skipUnless(HAS_GHOSTSCRIPT, "Ghostscript not available") def test_showpage(self): # See https://github.com/python-pillow/Pillow/issues/2615 plot_image = Image.open("Tests/images/reqd_showpage.eps") @@ -81,18 +82,21 @@ class TestFileEps(PillowTestCase): # fonts could be slightly different self.assert_image_similar(plot_image, target, 6) + @unittest.skipUnless(HAS_GHOSTSCRIPT, "Ghostscript not available") def test_file_object(self): # issue 479 image1 = Image.open(file1) with open(self.tempfile('temp_file.eps'), 'wb') as fh: image1.save(fh, 'EPS') + @unittest.skipUnless(HAS_GHOSTSCRIPT, "Ghostscript not available") def test_iobase_object(self): # issue 479 image1 = Image.open(file1) with io.open(self.tempfile('temp_iobase.eps'), 'wb') as fh: image1.save(fh, 'EPS') + @unittest.skipUnless(HAS_GHOSTSCRIPT, "Ghostscript not available") def test_bytesio_object(self): with open(file1, 'rb') as f: img_bytes = io.BytesIO(f.read()) @@ -109,6 +113,7 @@ class TestFileEps(PillowTestCase): tmpfile = self.tempfile('temp.eps') self.assertRaises(ValueError, im.save, tmpfile) + @unittest.skipUnless(HAS_GHOSTSCRIPT, "Ghostscript not available") def test_render_scale1(self): # We need png support for these render test codecs = dir(Image.core) @@ -129,6 +134,7 @@ class TestFileEps(PillowTestCase): image2_scale1_compare.load() self.assert_image_similar(image2_scale1, image2_scale1_compare, 10) + @unittest.skipUnless(HAS_GHOSTSCRIPT, "Ghostscript not available") def test_render_scale2(self): # We need png support for these render test codecs = dir(Image.core) @@ -149,6 +155,7 @@ class TestFileEps(PillowTestCase): image2_scale2_compare.load() self.assert_image_similar(image2_scale2, image2_scale2_compare, 10) + @unittest.skipUnless(HAS_GHOSTSCRIPT, "Ghostscript not available") def test_resize(self): # Arrange image1 = Image.open(file1) @@ -166,6 +173,7 @@ class TestFileEps(PillowTestCase): self.assertEqual(image2.size, new_size) self.assertEqual(image3.size, new_size) + @unittest.skipUnless(HAS_GHOSTSCRIPT, "Ghostscript not available") def test_thumbnail(self): # Issue #619 # Arrange @@ -228,13 +236,12 @@ class TestFileEps(PillowTestCase): "Tests/images/illuCS6_no_preview.eps", "Tests/images/illuCS6_preview.eps"] - # Act + # Act / Assert for filename in FILES: img = Image.open(filename) + self.assertEqual(img.mode, "RGB") - # Assert - self.assertEqual(img.mode, "RGB") - + @unittest.skipUnless(HAS_GHOSTSCRIPT, "Ghostscript not available") def test_emptyline(self): # Test file includes an empty line in the header data emptyline_file = "Tests/images/zero_bb_emptyline.eps" @@ -244,7 +251,3 @@ class TestFileEps(PillowTestCase): self.assertEqual(image.mode, "RGB") self.assertEqual(image.size, (460, 352)) self.assertEqual(image.format, "EPS") - - -if __name__ == '__main__': - unittest.main() diff --git a/Tests/test_file_fitsstub.py b/Tests/test_file_fitsstub.py index d74e983ce..6cb2de110 100644 --- a/Tests/test_file_fitsstub.py +++ b/Tests/test_file_fitsstub.py @@ -1,4 +1,4 @@ -from helper import unittest, PillowTestCase +from .helper import PillowTestCase from PIL import FitsStubImagePlugin, Image @@ -44,7 +44,3 @@ class TestFileFitsStub(PillowTestCase): self.assertRaises( IOError, FitsStubImagePlugin._save, im, dummy_fp, dummy_filename) - - -if __name__ == '__main__': - unittest.main() diff --git a/Tests/test_file_fli.py b/Tests/test_file_fli.py index 142af3cec..f67f0ada1 100644 --- a/Tests/test_file_fli.py +++ b/Tests/test_file_fli.py @@ -1,4 +1,4 @@ -from helper import unittest, PillowTestCase +from .helper import PillowTestCase from PIL import Image, FliImagePlugin @@ -27,6 +27,12 @@ class TestFileFli(PillowTestCase): self.assertEqual(im.info["duration"], 71) self.assertTrue(im.is_animated) + def test_unclosed_file(self): + def open(): + im = Image.open(static_test_file) + im.load() + self.assert_warning(None, open) + def test_tell(self): # Arrange im = Image.open(static_test_file) @@ -85,6 +91,9 @@ class TestFileFli(PillowTestCase): layer_number = im.tell() self.assertEqual(layer_number, 1) + def test_seek(self): + im = Image.open(animated_test_file) + im.seek(50) -if __name__ == '__main__': - unittest.main() + expected = Image.open("Tests/images/a_fli.png") + self.assert_image_equal(im, expected) diff --git a/Tests/test_file_fpx.py b/Tests/test_file_fpx.py index 441a3e635..7283ba088 100644 --- a/Tests/test_file_fpx.py +++ b/Tests/test_file_fpx.py @@ -1,4 +1,4 @@ -from helper import unittest, PillowTestCase +from .helper import unittest, PillowTestCase try: from PIL import FpxImagePlugin @@ -21,7 +21,3 @@ class TestFileFpx(PillowTestCase): ole_file = "Tests/images/test-ole-file.doc" self.assertRaises(SyntaxError, FpxImagePlugin.FpxImageFile, ole_file) - - -if __name__ == '__main__': - unittest.main() diff --git a/Tests/test_file_ftex.py b/Tests/test_file_ftex.py index 6b43244c9..07e29d7e0 100644 --- a/Tests/test_file_ftex.py +++ b/Tests/test_file_ftex.py @@ -1,4 +1,4 @@ -from helper import unittest, PillowTestCase +from .helper import PillowTestCase from PIL import Image @@ -14,7 +14,3 @@ class TestFileFtex(PillowTestCase): 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) - - -if __name__ == '__main__': - unittest.main() diff --git a/Tests/test_file_gbr.py b/Tests/test_file_gbr.py index 346605bd4..1eb66264d 100644 --- a/Tests/test_file_gbr.py +++ b/Tests/test_file_gbr.py @@ -1,4 +1,4 @@ -from helper import unittest, PillowTestCase +from .helper import PillowTestCase from PIL import Image, GbrImagePlugin @@ -17,7 +17,3 @@ class TestFileGbr(PillowTestCase): target = Image.open('Tests/images/gbr.png') self.assert_image_equal(target, im) - - -if __name__ == '__main__': - unittest.main() diff --git a/Tests/test_file_gd.py b/Tests/test_file_gd.py index b303369b4..67c9fba7b 100644 --- a/Tests/test_file_gd.py +++ b/Tests/test_file_gd.py @@ -1,4 +1,4 @@ -from helper import unittest, PillowTestCase +from .helper import PillowTestCase from PIL import GdImageFile @@ -20,7 +20,3 @@ class TestFileGd(PillowTestCase): invalid_file = "Tests/images/flower.jpg" self.assertRaises(IOError, GdImageFile.open, invalid_file) - - -if __name__ == '__main__': - unittest.main() diff --git a/Tests/test_file_gif.py b/Tests/test_file_gif.py index 086a0f5d0..6a4b14d40 100644 --- a/Tests/test_file_gif.py +++ b/Tests/test_file_gif.py @@ -1,9 +1,15 @@ -from helper import unittest, PillowTestCase, hopper, netpbm_available +from .helper import unittest, PillowTestCase, hopper, netpbm_available from PIL import Image, ImagePalette, GifImagePlugin from io import BytesIO +try: + from PIL import _webp + HAVE_WEBP = True +except ImportError: + HAVE_WEBP = False + codecs = dir(Image.core) # sample gif stream @@ -27,6 +33,12 @@ class TestFileGif(PillowTestCase): self.assertEqual(im.format, "GIF") self.assertEqual(im.info["version"], b"GIF89a") + def test_unclosed_file(self): + def open(): + im = Image.open(TEST_GIF) + im.load() + self.assert_warning(None, open) + def test_invalid_file(self): invalid_file = "Tests/images/flower.jpg" @@ -134,13 +146,15 @@ class TestFileGif(PillowTestCase): # Multiframe image im = Image.open("Tests/images/dispose_bgnd.gif") + info = im.info.copy() + out = self.tempfile('temp.gif') im.save(out, save_all=True) reread = Image.open(out) for header in important_headers: self.assertEqual( - im.info[header], + info[header], reread.info[header] ) @@ -207,6 +221,15 @@ class TestFileGif(PillowTestCase): except EOFError: self.assertEqual(framecount, 5) + def test_seek_info(self): + im = Image.open("Tests/images/iss634.gif") + info = im.info.copy() + + im.seek(1) + im.seek(0) + + self.assertEqual(im.info, info) + def test_n_frames(self): for path, n_frames in [ [TEST_GIF, 1], @@ -400,6 +423,11 @@ 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) + def test_comment(self): im = Image.open(TEST_GIF) self.assertEqual(im.info['comment'], @@ -413,6 +441,23 @@ class TestFileGif(PillowTestCase): self.assertEqual(reread.info['comment'], im.info['comment']) + def test_comment_over_255(self): + out = self.tempfile('temp.gif') + im = Image.new('L', (100, 100), '#000') + comment = b"Test comment text" + while len(comment) < 256: + comment += comment + im.info['comment'] = comment + im.save(out) + reread = Image.open(out) + + self.assertEqual(reread.info['comment'], comment) + + def test_zero_comment_subblocks(self): + im = Image.open('Tests/images/hopper_zero_comment_subblocks.gif') + expected = Image.open(TEST_GIF) + self.assert_image_equal(im, expected) + def test_version(self): out = self.tempfile('temp.gif') @@ -491,6 +536,27 @@ class TestFileGif(PillowTestCase): self.assertEqual(reloaded.info['transparency'], 253) + def test_rgb_transparency(self): + out = self.tempfile('temp.gif') + + # Single frame + im = Image.new('RGB', (1, 1)) + im.info['transparency'] = (255, 0, 0) + self.assert_warning(UserWarning, im.save, out) + + reloaded = Image.open(out) + self.assertNotIn('transparency', reloaded.info) + + # Multiple frames + im = Image.new('RGB', (1, 1)) + im.info['transparency'] = b"" + ims = [Image.new('RGB', (1, 1))] + self.assert_warning(UserWarning, + im.save, out, save_all=True, append_images=ims) + + reloaded = Image.open(out) + self.assertNotIn('transparency', reloaded.info) + def test_bbox(self): out = self.tempfile('temp.gif') @@ -588,7 +654,3 @@ class TestFileGif(PillowTestCase): self.assertEqual(im.tile[0][3][0], 11) # LZW bits # codec error prepatch im.load() - - -if __name__ == '__main__': - unittest.main() diff --git a/Tests/test_file_gimpgradient.py b/Tests/test_file_gimpgradient.py index b29f6f13b..c89440239 100644 --- a/Tests/test_file_gimpgradient.py +++ b/Tests/test_file_gimpgradient.py @@ -1,4 +1,4 @@ -from helper import unittest, PillowTestCase +from .helper import PillowTestCase from PIL import GimpGradientFile @@ -119,7 +119,3 @@ class TestImage(PillowTestCase): # load returns raw palette information self.assertEqual(len(palette[0]), 1024) self.assertEqual(palette[1], "RGBA") - - -if __name__ == '__main__': - unittest.main() diff --git a/Tests/test_file_gimppalette.py b/Tests/test_file_gimppalette.py index 4ee5323bc..1ebac44e7 100644 --- a/Tests/test_file_gimppalette.py +++ b/Tests/test_file_gimppalette.py @@ -1,4 +1,4 @@ -from helper import unittest, PillowTestCase +from .helper import PillowTestCase from PIL.GimpPaletteFile import GimpPaletteFile @@ -28,7 +28,3 @@ class TestImage(PillowTestCase): # Assert self.assertEqual(mode, "RGB") - - -if __name__ == '__main__': - unittest.main() diff --git a/Tests/test_file_gribstub.py b/Tests/test_file_gribstub.py index b3a6f1a5a..79e826945 100644 --- a/Tests/test_file_gribstub.py +++ b/Tests/test_file_gribstub.py @@ -1,4 +1,4 @@ -from helper import unittest, PillowTestCase, hopper +from .helper import PillowTestCase, hopper from PIL import GribStubImagePlugin, Image @@ -40,7 +40,3 @@ class TestFileGribStub(PillowTestCase): # Act / Assert: stub cannot save without an implemented handler self.assertRaises(IOError, im.save, tmpfile) - - -if __name__ == '__main__': - unittest.main() diff --git a/Tests/test_file_hdf5stub.py b/Tests/test_file_hdf5stub.py index 6cddd8d7b..598ef0c5c 100644 --- a/Tests/test_file_hdf5stub.py +++ b/Tests/test_file_hdf5stub.py @@ -1,4 +1,4 @@ -from helper import unittest, PillowTestCase +from .helper import PillowTestCase from PIL import Hdf5StubImagePlugin, Image @@ -44,7 +44,3 @@ class TestFileHdf5Stub(PillowTestCase): self.assertRaises( IOError, Hdf5StubImagePlugin._save, im, dummy_fp, dummy_filename) - - -if __name__ == '__main__': - unittest.main() diff --git a/Tests/test_file_icns.py b/Tests/test_file_icns.py index 06a7e39bb..ac60731f9 100644 --- a/Tests/test_file_icns.py +++ b/Tests/test_file_icns.py @@ -1,4 +1,4 @@ -from helper import unittest, PillowTestCase +from .helper import unittest, PillowTestCase from PIL import Image, IcnsImagePlugin @@ -17,7 +17,10 @@ class TestFileIcns(PillowTestCase): # Loading this icon by default should result in the largest size # (512x512@2x) being loaded im = Image.open(TEST_FILE) - im.load() + + # Assert that there is no unclosed file warning + self.assert_warning(None, im.load) + self.assertEqual(im.mode, "RGBA") self.assertEqual(im.size, (1024, 1024)) self.assertEqual(im.format, "ICNS") @@ -64,6 +67,10 @@ class TestFileIcns(PillowTestCase): self.assertEqual(im2.mode, 'RGBA') self.assertEqual(im2.size, (wr, hr)) + # Check that we cannot load an incorrect size + with self.assertRaises(ValueError): + im.size = (1, 1) + def test_older_icon(self): # This icon was made with Icon Composer rather than iconutil; it still # uses PNG rather than JP2, however (since it was made on 10.9). @@ -114,7 +121,3 @@ class TestFileIcns(PillowTestCase): with io.BytesIO(b'invalid\n') as fp: self.assertRaises(SyntaxError, IcnsImagePlugin.IcnsFile, fp) - - -if __name__ == '__main__': - unittest.main() diff --git a/Tests/test_file_ico.py b/Tests/test_file_ico.py index 20f21db57..b0d33e33f 100644 --- a/Tests/test_file_ico.py +++ b/Tests/test_file_ico.py @@ -1,4 +1,4 @@ -from helper import unittest, PillowTestCase, hopper +from .helper import PillowTestCase, hopper import io from PIL import Image, IcoImagePlugin @@ -28,7 +28,7 @@ class TestFileIco(PillowTestCase): # the default image output.seek(0) reloaded = Image.open(output) - self.assertEqual(reloaded.info['sizes'], set([(32, 32), (64, 64)])) + self.assertEqual(reloaded.info['sizes'], {(32, 32), (64, 64)}) self.assertEqual(im.mode, reloaded.mode) self.assertEqual((64, 64), reloaded.size) @@ -47,6 +47,11 @@ class TestFileIco(PillowTestCase): self.assert_image_equal(reloaded, hopper().resize((32, 32), Image.LANCZOS)) + def test_incorrect_size(self): + im = Image.open(TEST_ICO_FILE) + with self.assertRaises(ValueError): + im.size = (1, 1) + def test_save_256x256(self): """Issue #2264 https://github.com/python-pillow/Pillow/issues/2264""" # Arrange @@ -76,8 +81,4 @@ class TestFileIco(PillowTestCase): # Assert self.assertEqual( im_saved.info['sizes'], - set([(16, 16), (24, 24), (32, 32), (48, 48)])) - - -if __name__ == '__main__': - unittest.main() + {(16, 16), (24, 24), (32, 32), (48, 48)}) diff --git a/Tests/test_file_im.py b/Tests/test_file_im.py index c99924767..8e774ce0a 100644 --- a/Tests/test_file_im.py +++ b/Tests/test_file_im.py @@ -1,4 +1,4 @@ -from helper import unittest, PillowTestCase, hopper +from .helper import PillowTestCase, hopper from PIL import Image, ImImagePlugin @@ -15,6 +15,12 @@ class TestFileIm(PillowTestCase): self.assertEqual(im.size, (128, 128)) self.assertEqual(im.format, "IM") + def test_unclosed_file(self): + def open(): + im = Image.open(TEST_IM) + im.load() + self.assert_warning(None, open) + def test_tell(self): # Arrange im = Image.open(TEST_IM) @@ -63,7 +69,3 @@ class TestFileIm(PillowTestCase): def test_number(self): self.assertEqual(1.2, ImImagePlugin.number("1.2")) - - -if __name__ == '__main__': - unittest.main() diff --git a/Tests/test_file_iptc.py b/Tests/test_file_iptc.py index e08d994a2..83b735464 100644 --- a/Tests/test_file_iptc.py +++ b/Tests/test_file_iptc.py @@ -1,4 +1,4 @@ -from helper import unittest, PillowTestCase, hopper +from .helper import PillowTestCase, hopper from PIL import Image, IptcImagePlugin @@ -69,7 +69,3 @@ class TestFileIptc(PillowTestCase): # Assert self.assertEqual(mystdout.getvalue(), "61 62 63 \n") - - -if __name__ == '__main__': - unittest.main() diff --git a/Tests/test_file_jpeg.py b/Tests/test_file_jpeg.py index 1485651c7..cbe894f34 100644 --- a/Tests/test_file_jpeg.py +++ b/Tests/test_file_jpeg.py @@ -1,5 +1,5 @@ -from helper import unittest, PillowTestCase, hopper -from helper import djpeg_available, cjpeg_available +from .helper import unittest, PillowTestCase, hopper +from .helper import djpeg_available, cjpeg_available from io import BytesIO import os @@ -581,6 +581,15 @@ class TestFileJpeg(PillowTestCase): # OSError for unidentified image. self.assertEqual(im.info.get("dpi"), (72, 72)) + def test_ifd_offset_exif(self): + # Arrange + # This image has been manually hexedited to have an IFD offset of 10, + # in contrast to normal 8 + im = Image.open("Tests/images/exif-ifd-offset.jpg") + + # Act / Assert + self.assertEqual(im._getexif()[306], '2017:03:13 23:03:09') + @unittest.skipUnless(sys.platform.startswith('win32'), "Windows only") class TestFileCloseW32(PillowTestCase): @@ -602,7 +611,3 @@ class TestFileCloseW32(PillowTestCase): self.assertTrue(fp.closed) # this should not fail, as load should have closed the file. os.remove(tmpfile) - - -if __name__ == '__main__': - unittest.main() diff --git a/Tests/test_file_jpeg2k.py b/Tests/test_file_jpeg2k.py index be49c99bf..4b34354e2 100644 --- a/Tests/test_file_jpeg2k.py +++ b/Tests/test_file_jpeg2k.py @@ -1,4 +1,4 @@ -from helper import unittest, PillowTestCase +from .helper import PillowTestCase from PIL import Image, Jpeg2KImagePlugin from io import BytesIO @@ -39,6 +39,12 @@ class TestFileJpeg2k(PillowTestCase): 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): + im = Image.open('Tests/images/balloon.jpf') + self.assertEqual(im.format, 'JPEG2000') + self.assertEqual(im.get_format_mimetype(), 'image/jpx') def test_invalid_file(self): invalid_file = "Tests/images/flower.jpg" @@ -105,6 +111,22 @@ class TestFileJpeg2k(PillowTestCase): im.load() self.assertEqual(im.size, (160, 120)) + def test_layers_type(self): + outfile = self.tempfile('temp_layers.jp2') + for quality_layers in [ + [100, 50, 10], + (100, 50, 10), + None + ]: + test_card.save(outfile, quality_layers=quality_layers) + + for quality_layers in [ + 'quality_layers', + ('100', '50', '10') + ]: + self.assertRaises(ValueError, test_card.save, outfile, + quality_layers=quality_layers) + def test_layers(self): out = BytesIO() test_card.save(out, 'JPEG2000', quality_layers=[100, 50, 10], @@ -188,7 +210,3 @@ class TestFileJpeg2k(PillowTestCase): # Assert self.assertEqual(p.image.size, (640, 480)) - - -if __name__ == '__main__': - unittest.main() diff --git a/Tests/test_file_libtiff.py b/Tests/test_file_libtiff.py index b5e103e39..56564ebde 100644 --- a/Tests/test_file_libtiff.py +++ b/Tests/test_file_libtiff.py @@ -1,5 +1,5 @@ from __future__ import print_function -from helper import unittest, PillowTestCase, hopper +from .helper import PillowTestCase, hopper from PIL import features from PIL._util import py3 @@ -8,6 +8,7 @@ import io import logging import itertools import os +import distutils.version from PIL import Image, TiffImagePlugin, TiffTags @@ -194,7 +195,7 @@ class TestFileLibTiff(LibTiffTestCase): im = Image.open('Tests/images/hopper_g4.tif') for tag in im.tag_v2: try: - del(core_items[tag]) + del core_items[tag] except KeyError: pass @@ -204,7 +205,7 @@ class TestFileLibTiff(LibTiffTestCase): # 4: "long", # 5: "rational", # 12: "double", - # type: dummy value + # Type: dummy value values = {2: 'test', 3: 1, 4: 2**20, @@ -222,7 +223,7 @@ class TestFileLibTiff(LibTiffTestCase): for _ in range(info.length)) # Extra samples really doesn't make sense in this application. - del(new_ifd[338]) + del new_ifd[338] out = self.tempfile("temp.tif") TiffImagePlugin.WRITE_LIBTIFF = True @@ -231,6 +232,37 @@ class TestFileLibTiff(LibTiffTestCase): TiffImagePlugin.WRITE_LIBTIFF = False + def test_custom_metadata(self): + custom = { + 37000: 4, + 37001: 4.2, + 37002: 'custom tag value', + 37003: u'custom tag value', + 37004: b'custom tag value' + } + + libtiff_version = TiffImagePlugin._libtiff_version() + + libtiffs = [False] + if distutils.version.StrictVersion(libtiff_version) >= \ + distutils.version.StrictVersion("4.0"): + libtiffs.append(True) + + for libtiff in libtiffs: + TiffImagePlugin.WRITE_LIBTIFF = libtiff + + im = hopper() + + out = self.tempfile("temp.tif") + im.save(out, tiffinfo=custom) + TiffImagePlugin.WRITE_LIBTIFF = False + + reloaded = Image.open(out) + for tag, value in custom.items(): + if libtiff and isinstance(value, bytes): + value = value.decode() + self.assertEqual(reloaded.tag_v2[tag], value) + def test_int_dpi(self): # issue #1765 im = hopper('RGB') @@ -415,7 +447,7 @@ class TestFileLibTiff(LibTiffTestCase): im = Image.open('Tests/images/multipage.tiff') frames = im.n_frames self.assertEqual(frames, 3) - for idx in range(frames): + for _ in range(frames): im.seek(0) # Should not raise ValueError: I/O operation on closed file im.load() @@ -496,7 +528,7 @@ class TestFileLibTiff(LibTiffTestCase): pilim_load = Image.open(buffer_io) self.assert_image_similar(pilim, pilim_load, 0) - # save_bytesio() + save_bytesio() save_bytesio('raw') save_bytesio("packbits") save_bytesio("tiff_lzw") @@ -545,11 +577,11 @@ class TestFileLibTiff(LibTiffTestCase): def test_read_icc(self): with Image.open("Tests/images/hopper.iccprofile.tif") as img: icc = img.info.get('icc_profile') - self.assertNotEqual(icc, None) + self.assertIsNotNone(icc) TiffImagePlugin.READ_LIBTIFF = True with Image.open("Tests/images/hopper.iccprofile.tif") as img: icc_libtiff = img.info.get('icc_profile') - self.assertNotEqual(icc_libtiff, None) + self.assertIsNotNone(icc_libtiff) TiffImagePlugin.READ_LIBTIFF = False self.assertEqual(icc, icc_libtiff) @@ -633,6 +665,38 @@ class TestFileLibTiff(LibTiffTestCase): 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) -if __name__ == '__main__': - unittest.main() + 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) + + 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") + + 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) + + 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") + + 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) diff --git a/Tests/test_file_libtiff_small.py b/Tests/test_file_libtiff_small.py index c402673d8..6b379718e 100644 --- a/Tests/test_file_libtiff_small.py +++ b/Tests/test_file_libtiff_small.py @@ -1,8 +1,6 @@ -from helper import unittest - from PIL import Image -from test_file_libtiff import LibTiffTestCase +from .test_file_libtiff import LibTiffTestCase class TestFileLibTiffSmall(LibTiffTestCase): @@ -46,7 +44,3 @@ class TestFileLibTiffSmall(LibTiffTestCase): self.assertEqual(im.size, (128, 128)) self._assert_noerr(im) - - -if __name__ == '__main__': - unittest.main() diff --git a/Tests/test_file_mcidas.py b/Tests/test_file_mcidas.py index 491d8ea03..e273faad9 100644 --- a/Tests/test_file_mcidas.py +++ b/Tests/test_file_mcidas.py @@ -1,4 +1,4 @@ -from helper import unittest, PillowTestCase +from .helper import PillowTestCase from PIL import Image, McIdasImagePlugin @@ -28,7 +28,3 @@ class TestFileMcIdas(PillowTestCase): self.assertEqual(im.size, (1800, 400)) im2 = Image.open(saved_file) self.assert_image_equal(im, im2) - - -if __name__ == '__main__': - unittest.main() diff --git a/Tests/test_file_mic.py b/Tests/test_file_mic.py index f4059f9c9..3a7daa459 100644 --- a/Tests/test_file_mic.py +++ b/Tests/test_file_mic.py @@ -1,4 +1,4 @@ -from helper import unittest, PillowTestCase, hopper +from .helper import unittest, PillowTestCase, hopper from PIL import Image, ImagePalette, features @@ -64,7 +64,3 @@ class TestFileMic(PillowTestCase): ole_file = "Tests/images/test-ole-file.doc" self.assertRaises(SyntaxError, MicImagePlugin.MicImageFile, ole_file) - - -if __name__ == '__main__': - unittest.main() diff --git a/Tests/test_file_mpo.py b/Tests/test_file_mpo.py index 70bb9b105..45172472a 100644 --- a/Tests/test_file_mpo.py +++ b/Tests/test_file_mpo.py @@ -1,4 +1,4 @@ -from helper import unittest, PillowTestCase +from .helper import PillowTestCase from io import BytesIO from PIL import Image @@ -31,6 +31,12 @@ class TestFileMpo(PillowTestCase): self.assertEqual(im.size, (640, 480)) self.assertEqual(im.format, "MPO") + def test_unclosed_file(self): + def open(): + im = Image.open(test_files[0]) + im.load() + self.assert_warning(None, open) + def test_app(self): for test_file in test_files: # Test APP/COM reader (@PIL135) @@ -56,6 +62,14 @@ class TestFileMpo(PillowTestCase): self.assertEqual(mpinfo[45056], b'0100') self.assertEqual(mpinfo[45057], 2) + def test_mp_offset(self): + # This image has been manually hexedited to have an IFD offset of 10 + # in APP2 data, in contrast to normal 8 + im = Image.open("Tests/images/sugarshack_ifd_offset.mpo") + mpinfo = im._getmp() + self.assertEqual(mpinfo[45056], b'0100') + self.assertEqual(mpinfo[45057], 2) + def test_mp_attribute(self): for test_file in test_files: im = Image.open(test_file) @@ -136,7 +150,3 @@ class TestFileMpo(PillowTestCase): self.assertEqual(im.tell(), 1) jpg1 = self.frame_roundtrip(im) self.assert_image_similar(im, jpg1, 30) - - -if __name__ == '__main__': - unittest.main() diff --git a/Tests/test_file_msp.py b/Tests/test_file_msp.py index 4aac88092..66af65fcd 100644 --- a/Tests/test_file_msp.py +++ b/Tests/test_file_msp.py @@ -1,4 +1,4 @@ -from helper import unittest, PillowTestCase, hopper +from .helper import unittest, PillowTestCase, hopper from PIL import Image, MspImagePlugin @@ -78,7 +78,3 @@ class TestFileMsp(PillowTestCase): # Act/Assert self.assertRaises(IOError, im.save, filename) - - -if __name__ == '__main__': - unittest.main() diff --git a/Tests/test_file_palm.py b/Tests/test_file_palm.py index b97a9b19e..a6491634a 100644 --- a/Tests/test_file_palm.py +++ b/Tests/test_file_palm.py @@ -1,4 +1,4 @@ -from helper import unittest, PillowTestCase, hopper, imagemagick_available +from .helper import PillowTestCase, hopper, imagemagick_available import os.path @@ -52,7 +52,3 @@ class TestFilePalm(PillowTestCase): # Act / Assert self.assertRaises(IOError, self.helper_save_as_palm, mode) - - -if __name__ == '__main__': - unittest.main() diff --git a/Tests/test_file_pcd.py b/Tests/test_file_pcd.py index 06fd33043..7296a303f 100644 --- a/Tests/test_file_pcd.py +++ b/Tests/test_file_pcd.py @@ -1,4 +1,4 @@ -from helper import unittest, PillowTestCase +from .helper import PillowTestCase from PIL import Image @@ -16,7 +16,3 @@ class TestFilePcd(PillowTestCase): # target = hopper().resize((768,512)) # self.assert_image_similar(im, target, 10) - - -if __name__ == '__main__': - unittest.main() diff --git a/Tests/test_file_pcx.py b/Tests/test_file_pcx.py index 415827e49..9e42a86f7 100644 --- a/Tests/test_file_pcx.py +++ b/Tests/test_file_pcx.py @@ -1,4 +1,4 @@ -from helper import unittest, PillowTestCase, hopper +from .helper import PillowTestCase, hopper from PIL import Image, ImageFile, PcxImagePlugin @@ -128,7 +128,3 @@ class TestFilePcx(PillowTestCase): for x in range(5): px[x, 3] = 0 self._test_buffer_overflow(im) - - -if __name__ == '__main__': - unittest.main() diff --git a/Tests/test_file_pdf.py b/Tests/test_file_pdf.py index f012fb9d8..7b024426b 100644 --- a/Tests/test_file_pdf.py +++ b/Tests/test_file_pdf.py @@ -1,9 +1,10 @@ -from helper import unittest, PillowTestCase, hopper +from .helper import PillowTestCase, hopper from PIL import Image, PdfParser import io import os import os.path import tempfile +import time class TestFilePdf(PillowTestCase): @@ -187,8 +188,13 @@ class TestFilePdf(PillowTestCase): # open it, check pages and info with PdfParser.PdfParser(pdf_filename, mode="r+b") as pdf: self.assertEqual(len(pdf.pages), 1) - self.assertEqual(len(pdf.info), 1) + self.assertEqual(len(pdf.info), 4) + self.assertEqual(pdf.info.Title, os.path.splitext( + os.path.basename(pdf_filename) + )[0]) self.assertEqual(pdf.info.Producer, "PdfParser") + self.assertIn(b"CreationDate", pdf.info) + self.assertIn(b"ModDate", pdf.info) self.check_pdf_pages_consistency(pdf) # append some info @@ -203,8 +209,10 @@ class TestFilePdf(PillowTestCase): # open it again, check pages and info again with PdfParser.PdfParser(pdf_filename) as pdf: self.assertEqual(len(pdf.pages), 1) - self.assertEqual(len(pdf.info), 6) + self.assertEqual(len(pdf.info), 8) self.assertEqual(pdf.info.Title, "abc") + self.assertIn(b"CreationDate", pdf.info) + self.assertIn(b"ModDate", pdf.info) self.check_pdf_pages_consistency(pdf) # append two images @@ -216,29 +224,36 @@ class TestFilePdf(PillowTestCase): # open the PDF again, check pages and info again with PdfParser.PdfParser(pdf_filename) as pdf: self.assertEqual(len(pdf.pages), 3) - self.assertEqual(len(pdf.info), 6) + self.assertEqual(len(pdf.info), 8) self.assertEqual(PdfParser.decode_text(pdf.info[b"Title"]), "abc") 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.assertIn(b"CreationDate", pdf.info) + self.assertIn(b"ModDate", pdf.info) self.check_pdf_pages_consistency(pdf) def test_pdf_info(self): # make a PDF file pdf_filename = self.helper_save_as_pdf( "RGB", title="title", author="author", subject="subject", - keywords="keywords", creator="creator", producer="producer") + keywords="keywords", creator="creator", producer="producer", + creationDate=time.strptime("2000", "%Y"), + modDate=time.strptime("2001", "%Y")) # open it, check pages and info with PdfParser.PdfParser(pdf_filename) as pdf: - self.assertEqual(len(pdf.info), 6) + self.assertEqual(len(pdf.info), 8) self.assertEqual(pdf.info.Title, "title") self.assertEqual(pdf.info.Author, "author") self.assertEqual(pdf.info.Subject, "subject") self.assertEqual(pdf.info.Keywords, "keywords") self.assertEqual(pdf.info.Creator, "creator") self.assertEqual(pdf.info.Producer, "producer") + self.assertEqual(pdf.info.CreationDate, + time.strptime("2000", "%Y")) + self.assertEqual(pdf.info.ModDate, time.strptime("2001", "%Y")) self.check_pdf_pages_consistency(pdf) def test_pdf_append_to_bytesio(self): @@ -251,7 +266,3 @@ class TestFilePdf(PillowTestCase): f = io.BytesIO(f.getvalue()) im.save(f, format="PDF", append=True) self.assertGreater(len(f.getvalue()), initial_size) - - -if __name__ == '__main__': - unittest.main() diff --git a/Tests/test_file_pixar.py b/Tests/test_file_pixar.py index 3b00c710e..3b998c0b5 100644 --- a/Tests/test_file_pixar.py +++ b/Tests/test_file_pixar.py @@ -1,4 +1,4 @@ -from helper import hopper, unittest, PillowTestCase +from .helper import hopper, PillowTestCase from PIL import Image, PixarImagePlugin @@ -24,7 +24,3 @@ class TestFilePixar(PillowTestCase): self.assertRaises( SyntaxError, PixarImagePlugin.PixarImageFile, invalid_file) - - -if __name__ == '__main__': - unittest.main() diff --git a/Tests/test_file_png.py b/Tests/test_file_png.py index e9dcd5203..2b80bf357 100644 --- a/Tests/test_file_png.py +++ b/Tests/test_file_png.py @@ -1,4 +1,4 @@ -from helper import unittest, PillowTestCase, PillowLeakTestCase, hopper +from .helper import unittest, PillowTestCase, PillowLeakTestCase, hopper from PIL import Image, ImageFile, PngImagePlugin from PIL._util import py3 @@ -6,6 +6,12 @@ from io import BytesIO import zlib import sys +try: + from PIL import _webp + HAVE_WEBP = True +except ImportError: + HAVE_WEBP = False + codecs = dir(Image.core) @@ -80,21 +86,22 @@ class TestFilePng(PillowTestCase): self.assertEqual(im.mode, "RGB") self.assertEqual(im.size, (128, 128)) self.assertEqual(im.format, "PNG") + self.assertEqual(im.get_format_mimetype(), 'image/png') hopper("1").save(test_file) - im = Image.open(test_file) + Image.open(test_file) hopper("L").save(test_file) - im = Image.open(test_file) + Image.open(test_file) hopper("P").save(test_file) - im = Image.open(test_file) + Image.open(test_file) hopper("RGB").save(test_file) - im = Image.open(test_file) + Image.open(test_file) hopper("I").save(test_file) - im = Image.open(test_file) + Image.open(test_file) def test_invalid_file(self): invalid_file = "Tests/images/flower.jpg" @@ -327,7 +334,9 @@ class TestFilePng(PillowTestCase): # Check open/load/verify exception (@PIL150) im = Image.open(TEST_PNG_FILE) - im.verify() + + # Assert that there is no unclosed file warning + self.assert_warning(None, im.verify) im = Image.open(TEST_PNG_FILE) im.load() @@ -555,6 +564,42 @@ class TestFilePng(PillowTestCase): chunks = PngImagePlugin.getchunks(im) 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) + + # Raises a SyntaxError in load_end + im = Image.open("Tests/images/broken_data_stream.png") + 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 + + # 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'}) + + @unittest.skipUnless(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') + + # 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) + @unittest.skipIf(sys.platform.startswith('win32'), "requires Unix or macOS") class TestTruncatedPngPLeaks(PillowLeakTestCase): @@ -581,7 +626,3 @@ class TestTruncatedPngPLeaks(PillowLeakTestCase): self._test_leak(core) finally: ImageFile.LOAD_TRUNCATED_IMAGES = False - - -if __name__ == '__main__': - unittest.main() diff --git a/Tests/test_file_ppm.py b/Tests/test_file_ppm.py index 937a9dc32..47f8b845e 100644 --- a/Tests/test_file_ppm.py +++ b/Tests/test_file_ppm.py @@ -1,4 +1,4 @@ -from helper import unittest, PillowTestCase +from .helper import PillowTestCase from PIL import Image @@ -49,7 +49,3 @@ class TestFilePpm(PillowTestCase): with self.assertRaises(IOError): Image.open('Tests/images/negative_size.ppm') - - -if __name__ == '__main__': - unittest.main() diff --git a/Tests/test_file_psd.py b/Tests/test_file_psd.py index afc69694d..3b8a7add6 100644 --- a/Tests/test_file_psd.py +++ b/Tests/test_file_psd.py @@ -1,4 +1,4 @@ -from helper import hopper, unittest, PillowTestCase +from .helper import hopper, PillowTestCase from PIL import Image, PsdImagePlugin @@ -76,7 +76,3 @@ class TestImagePsd(PillowTestCase): im = Image.open("Tests/images/hopper_merged.psd") self.assertNotIn("icc_profile", im.info) - - -if __name__ == '__main__': - unittest.main() diff --git a/Tests/test_file_sgi.py b/Tests/test_file_sgi.py index aa33c9d93..1ad70e24f 100644 --- a/Tests/test_file_sgi.py +++ b/Tests/test_file_sgi.py @@ -1,4 +1,4 @@ -from helper import unittest, PillowTestCase, hopper +from .helper import PillowTestCase, hopper from PIL import Image, SgiImagePlugin @@ -12,6 +12,7 @@ class TestFileSgi(PillowTestCase): im = Image.open(test_file) self.assert_image_equal(im, hopper()) + self.assertEqual(im.get_format_mimetype(), 'image/rgb') def test_rgb16(self): test_file = "Tests/images/hopper16.rgb" @@ -26,6 +27,7 @@ class TestFileSgi(PillowTestCase): im = Image.open(test_file) self.assert_image_similar(im, hopper('L'), 2) + self.assertEqual(im.get_format_mimetype(), 'image/sgi') def test_rgba(self): # Created with ImageMagick: @@ -35,6 +37,7 @@ class TestFileSgi(PillowTestCase): 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') def test_rle(self): # Created with ImageMagick: @@ -86,7 +89,3 @@ class TestFileSgi(PillowTestCase): out = self.tempfile('temp.sgi') self.assertRaises(ValueError, im.save, out, format='sgi') - - -if __name__ == '__main__': - unittest.main() diff --git a/Tests/test_file_spider.py b/Tests/test_file_spider.py index b54b92e04..f160272fd 100644 --- a/Tests/test_file_spider.py +++ b/Tests/test_file_spider.py @@ -1,4 +1,4 @@ -from helper import unittest, PillowTestCase, hopper +from .helper import PillowTestCase, hopper from PIL import Image from PIL import ImageSequence @@ -18,6 +18,12 @@ class TestImageSpider(PillowTestCase): self.assertEqual(im.size, (128, 128)) self.assertEqual(im.format, "SPIDER") + def test_unclosed_file(self): + def open(): + im = Image.open(TEST_FILE) + im.load() + self.assert_warning(None, open) + def test_save(self): # Arrange temp = self.tempfile('temp.spider') @@ -113,7 +119,3 @@ class TestImageSpider(PillowTestCase): for i, frame in enumerate(ImageSequence.Iterator(im)): if i > 1: self.fail("Non-stack DOS file test failed") - - -if __name__ == '__main__': - unittest.main() diff --git a/Tests/test_file_sun.py b/Tests/test_file_sun.py index 61cfdf367..65c00eea2 100644 --- a/Tests/test_file_sun.py +++ b/Tests/test_file_sun.py @@ -1,4 +1,4 @@ -from helper import unittest, PillowTestCase, hopper +from .helper import unittest, PillowTestCase, hopper from PIL import Image, SunImagePlugin @@ -45,7 +45,3 @@ class TestFileSun(PillowTestCase): # im.save(target_file) with Image.open(target_path) as target: self.assert_image_equal(im, target) - - -if __name__ == '__main__': - unittest.main() diff --git a/Tests/test_file_tar.py b/Tests/test_file_tar.py index 9e02ab1a5..cd2b95778 100644 --- a/Tests/test_file_tar.py +++ b/Tests/test_file_tar.py @@ -1,4 +1,4 @@ -from helper import unittest, PillowTestCase +from .helper import PillowTestCase from PIL import Image, TarIO @@ -15,22 +15,22 @@ class TestFileTar(PillowTestCase): self.skipTest("neither jpeg nor zip support available") def test_sanity(self): - if "zip_decoder" in codecs: - tar = TarIO.TarIO(TEST_TAR_FILE, 'hopper.png') - im = Image.open(tar) - im.load() - self.assertEqual(im.mode, "RGB") - self.assertEqual(im.size, (128, 128)) - self.assertEqual(im.format, "PNG") + for codec, test_path, format in [ + ['zip_decoder', 'hopper.png', 'PNG'], + ['jpeg_decoder', 'hopper.jpg', 'JPEG'] + ]: + if codec in codecs: + tar = TarIO.TarIO(TEST_TAR_FILE, test_path) + im = Image.open(tar) + im.load() + self.assertEqual(im.mode, "RGB") + self.assertEqual(im.size, (128, 128)) + self.assertEqual(im.format, format) - if "jpeg_decoder" in codecs: - tar = TarIO.TarIO(TEST_TAR_FILE, 'hopper.jpg') - im = Image.open(tar) - im.load() - self.assertEqual(im.mode, "RGB") - self.assertEqual(im.size, (128, 128)) - self.assertEqual(im.format, "JPEG") + def test_close(self): + tar = TarIO.TarIO(TEST_TAR_FILE, 'hopper.jpg') + tar.close() - -if __name__ == '__main__': - unittest.main() + def test_contextmanager(self): + with TarIO.TarIO(TEST_TAR_FILE, 'hopper.jpg'): + pass diff --git a/Tests/test_file_tga.py b/Tests/test_file_tga.py index 77695f2d1..0dbfb309c 100644 --- a/Tests/test_file_tga.py +++ b/Tests/test_file_tga.py @@ -2,7 +2,7 @@ import os from glob import glob from itertools import product -from helper import unittest, PillowTestCase +from .helper import PillowTestCase from PIL import Image @@ -201,7 +201,3 @@ class TestFileTga(PillowTestCase): test_im.getchannel("A").getcolors()[0][0], num_transparent) self.assert_image_equal(im, test_im) - - -if __name__ == '__main__': - unittest.main() diff --git a/Tests/test_file_tiff.py b/Tests/test_file_tiff.py index cbfc63d74..9a4104b78 100644 --- a/Tests/test_file_tiff.py +++ b/Tests/test_file_tiff.py @@ -2,7 +2,7 @@ import logging from io import BytesIO import sys -from helper import unittest, PillowTestCase, hopper +from .helper import unittest, PillowTestCase, hopper from PIL import Image, TiffImagePlugin from PIL._util import py3 @@ -26,19 +26,25 @@ class TestFileTiff(PillowTestCase): self.assertEqual(im.format, "TIFF") hopper("1").save(filename) - im = Image.open(filename) + Image.open(filename) hopper("L").save(filename) - im = Image.open(filename) + Image.open(filename) hopper("P").save(filename) - im = Image.open(filename) + Image.open(filename) hopper("RGB").save(filename) - im = Image.open(filename) + Image.open(filename) hopper("I").save(filename) - im = Image.open(filename) + Image.open(filename) + + def test_unclosed_file(self): + def open(): + im = Image.open("Tests/images/multipage.tiff") + im.load() + self.assert_warning(None, open) def test_mac_tiff(self): # Read RGBa images from macOS [@PIL136] @@ -69,6 +75,14 @@ class TestFileTiff(PillowTestCase): self.assertEqual(str(e.exception), "Not allowing setting of legacy api") + def test_size(self): + filename = "Tests/images/pil168.tif" + im = Image.open(filename) + + def set_size(): + im.size = (256, 256) + self.assert_warning(DeprecationWarning, set_size) + def test_xyres_tiff(self): filename = "Tests/images/pil168.tif" im = Image.open(filename) @@ -206,6 +220,10 @@ class TestFileTiff(PillowTestCase): self.assertEqual( im.getextrema(), (-3.140936851501465, 3.140684127807617)) + def test_unknown_pixel_mode(self): + self.assertRaises( + IOError, Image.open, 'Tests/images/hopper_unknown_pixel_mode.tif') + def test_n_frames(self): for path, n_frames in [ ['Tests/images/multipage-lastframe.tif', 1], @@ -413,6 +431,40 @@ class TestFileTiff(PillowTestCase): 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") + + 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") + + def test_strip_planar_raw_with_overviews(self): + # gdaladdo tiff_strip_planar_raw2.tif 2 4 8 16 + infile = "Tests/images/tiff_strip_planar_raw_with_overviews.tif" + im = Image.open(infile) + + self.assert_image_equal_tofile(im, + "Tests/images/tiff_adobe_deflate.png") + + def test_tiled_planar_raw(self): + # gdal_translate -of GTiff -co TILED=YES -co BLOCKXSIZE=32 \ + # -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") + def test_tiff_save_all(self): import io import os @@ -513,7 +565,3 @@ class TestFileTiffW32(PillowTestCase): # this should not fail, as load should have closed the file pointer, # and close should have closed the mmap os.remove(tmpfile) - - -if __name__ == '__main__': - unittest.main() diff --git a/Tests/test_file_tiff_metadata.py b/Tests/test_file_tiff_metadata.py index d1e2b577e..14ec3ab6c 100644 --- a/Tests/test_file_tiff_metadata.py +++ b/Tests/test_file_tiff_metadata.py @@ -1,7 +1,7 @@ import io import struct -from helper import unittest, PillowTestCase, hopper +from .helper import PillowTestCase, hopper from PIL import Image, TiffImagePlugin, TiffTags from PIL.TiffImagePlugin import _limit_rational, IFDRational @@ -135,9 +135,9 @@ class TestFileTiffMetadata(PillowTestCase): for k, v in original.items(): if isinstance(v, IFDRational): original[k] = IFDRational(*_limit_rational(v, 2**31)) - if isinstance(v, tuple) and isinstance(v[0], IFDRational): - original[k] = tuple([IFDRational(*_limit_rational(elt, 2**31)) - for elt in v]) + elif isinstance(v, tuple) and isinstance(v[0], IFDRational): + original[k] = tuple(IFDRational(*_limit_rational(elt, 2**31)) + for elt in v) ignored = ['StripByteCounts', 'RowsPerStrip', 'PageNumber', 'StripOffsets'] @@ -247,7 +247,3 @@ class TestFileTiffMetadata(PillowTestCase): # Should not raise ValueError. self.assert_warning(UserWarning, lambda: ifd[277]) - - -if __name__ == '__main__': - unittest.main() diff --git a/Tests/test_file_wal.py b/Tests/test_file_wal.py index 13b1e3a2f..1e0a835d2 100644 --- a/Tests/test_file_wal.py +++ b/Tests/test_file_wal.py @@ -1,4 +1,4 @@ -from helper import unittest, PillowTestCase +from .helper import PillowTestCase from PIL import WalImageFile @@ -17,7 +17,3 @@ class TestFileWal(PillowTestCase): self.assertEqual(im.format_description, "Quake2 Texture") self.assertEqual(im.mode, "P") self.assertEqual(im.size, (128, 128)) - - -if __name__ == '__main__': - unittest.main() diff --git a/Tests/test_file_webp.py b/Tests/test_file_webp.py index fa01cf93e..7e6fad93c 100644 --- a/Tests/test_file_webp.py +++ b/Tests/test_file_webp.py @@ -1,6 +1,6 @@ -from helper import unittest, PillowTestCase, hopper +from .helper import unittest, PillowTestCase, hopper -from PIL import Image +from PIL import Image, WebPImagePlugin try: from PIL import _webp @@ -9,13 +9,25 @@ except ImportError: HAVE_WEBP = False +class TestUnsupportedWebp(PillowTestCase): + def test_unsupported(self): + if HAVE_WEBP: + WebPImagePlugin.SUPPORTED = False + + file_path = "Tests/images/hopper.webp" + self.assert_warning( + UserWarning, + lambda: self.assertRaises(IOError, Image.open, file_path) + ) + + if HAVE_WEBP: + WebPImagePlugin.SUPPORTED = True + + +@unittest.skipIf(not HAVE_WEBP, "WebP support not installed") class TestFileWebp(PillowTestCase): def setUp(self): - if not HAVE_WEBP: - self.skipTest('WebP support not installed') - return - self.rgb_mode = "RGB" def test_version(self): @@ -135,6 +147,28 @@ class TestFileWebp(PillowTestCase): 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" + with open(file_path, 'rb') as blob: + Image.open(blob).load() + Image.open(blob).load() -if __name__ == '__main__': - unittest.main() + @unittest.skipUnless(HAVE_WEBP and _webp.HAVE_WEBPANIM, + "WebP save all not available") + def test_background_from_gif(self): + im = Image.open("Tests/images/chi.gif") + original_value = im.convert("RGB").getpixel((1, 1)) + + # Save as WEBP + out_webp = self.tempfile("temp.webp") + im.save(out_webp, save_all=True) + + # Save as GIF + out_gif = self.tempfile("temp.gif") + Image.open(out_webp).save(out_gif) + + reread = Image.open(out_gif) + reread_value = reread.convert("RGB").getpixel((1, 1)) + difference = sum([abs(original_value[i] - reread_value[i]) + for i in range(0, 3)]) + self.assertLess(difference, 5) diff --git a/Tests/test_file_webp_alpha.py b/Tests/test_file_webp_alpha.py index 60a324d18..c868fa1df 100644 --- a/Tests/test_file_webp_alpha.py +++ b/Tests/test_file_webp_alpha.py @@ -1,22 +1,17 @@ -from helper import unittest, PillowTestCase, hopper +from .helper import unittest, PillowTestCase, hopper from PIL import Image try: from PIL import _webp except ImportError: - pass - # Skip in setUp() + _webp = None +@unittest.skipIf(_webp is None, "WebP support not installed") class TestFileWebpAlpha(PillowTestCase): def setUp(self): - try: - from PIL import _webp - except ImportError: - self.skipTest('WebP support not installed') - if _webp.WebPDecoderBuggyAlpha(self): self.skipTest("Buggy early version of WebP installed, " "not testing transparency") @@ -120,7 +115,3 @@ class TestFileWebpAlpha(PillowTestCase): target = Image.open(file_path).convert("RGBA") self.assert_image_similar(image, target, 25.0) - - -if __name__ == '__main__': - unittest.main() diff --git a/Tests/test_file_webp_animated.py b/Tests/test_file_webp_animated.py index 6b3dc1622..c751545c7 100644 --- a/Tests/test_file_webp_animated.py +++ b/Tests/test_file_webp_animated.py @@ -1,4 +1,4 @@ -from helper import unittest, PillowTestCase +from .helper import PillowTestCase from PIL import Image @@ -151,7 +151,3 @@ class TestFileWebpAnimation(PillowTestCase): self.assertEqual(im.info["duration"], dur) self.assertEqual(im.info["timestamp"], ts) ts -= dur - - -if __name__ == '__main__': - unittest.main() diff --git a/Tests/test_file_webp_lossless.py b/Tests/test_file_webp_lossless.py index 4c35dad73..528c9177d 100644 --- a/Tests/test_file_webp_lossless.py +++ b/Tests/test_file_webp_lossless.py @@ -1,4 +1,4 @@ -from helper import unittest, PillowTestCase, hopper +from .helper import PillowTestCase, hopper from PIL import Image @@ -16,7 +16,7 @@ class TestFileWebpLossless(PillowTestCase): self.skipTest('WebP support not installed') return - if (_webp.WebPDecoderVersion() < 0x0200): + if _webp.WebPDecoderVersion() < 0x0200: self.skipTest('lossless not included') self.rgb_mode = "RGB" @@ -36,7 +36,3 @@ class TestFileWebpLossless(PillowTestCase): image.getdata() self.assert_image_equal(image, hopper(self.rgb_mode)) - - -if __name__ == '__main__': - unittest.main() diff --git a/Tests/test_file_webp_metadata.py b/Tests/test_file_webp_metadata.py index c04443f46..402b6ce42 100644 --- a/Tests/test_file_webp_metadata.py +++ b/Tests/test_file_webp_metadata.py @@ -1,4 +1,4 @@ -from helper import unittest, PillowTestCase +from .helper import PillowTestCase from PIL import Image @@ -133,7 +133,3 @@ class TestFileWebpMetadata(PillowTestCase): self.assertEqual(iccp_data, image.info.get('icc_profile', None)) self.assertEqual(exif_data, image.info.get('exif', None)) self.assertEqual(xmp_data, image.info.get('xmp', None)) - - -if __name__ == '__main__': - unittest.main() diff --git a/Tests/test_file_wmf.py b/Tests/test_file_wmf.py index 1a15a514f..146888491 100644 --- a/Tests/test_file_wmf.py +++ b/Tests/test_file_wmf.py @@ -1,4 +1,4 @@ -from helper import unittest, PillowTestCase, hopper +from .helper import PillowTestCase, hopper from PIL import Image from PIL import WmfImagePlugin @@ -51,7 +51,3 @@ class TestFileWmf(PillowTestCase): for ext in [".wmf", ".emf"]: tmpfile = self.tempfile("temp"+ext) self.assertRaises(IOError, im.save, tmpfile) - - -if __name__ == '__main__': - unittest.main() diff --git a/Tests/test_file_xbm.py b/Tests/test_file_xbm.py index 398dae98c..fbcb30e90 100644 --- a/Tests/test_file_xbm.py +++ b/Tests/test_file_xbm.py @@ -1,4 +1,4 @@ -from helper import unittest, PillowTestCase +from .helper import PillowTestCase from PIL import Image @@ -60,7 +60,3 @@ class TestFileXbm(PillowTestCase): # Assert self.assertEqual(im.mode, '1') self.assertEqual(im.size, (128, 128)) - - -if __name__ == '__main__': - unittest.main() diff --git a/Tests/test_file_xpm.py b/Tests/test_file_xpm.py index 4fa3f743f..b57cda2e3 100644 --- a/Tests/test_file_xpm.py +++ b/Tests/test_file_xpm.py @@ -1,4 +1,4 @@ -from helper import unittest, PillowTestCase, hopper +from .helper import PillowTestCase, hopper from PIL import Image, XpmImagePlugin @@ -33,7 +33,3 @@ class TestFileXpm(PillowTestCase): # Assert self.assertEqual(len(data), 16384) - - -if __name__ == '__main__': - unittest.main() diff --git a/Tests/test_file_xvthumb.py b/Tests/test_file_xvthumb.py index d0256cabf..11672ebae 100644 --- a/Tests/test_file_xvthumb.py +++ b/Tests/test_file_xvthumb.py @@ -1,4 +1,4 @@ -from helper import hopper, unittest, PillowTestCase +from .helper import hopper, PillowTestCase from PIL import Image, XVThumbImagePlugin @@ -34,7 +34,3 @@ class TestFileXVThumb(PillowTestCase): # Act / Assert self.assertRaises(SyntaxError, XVThumbImagePlugin.XVThumbImageFile, invalid_file) - - -if __name__ == '__main__': - unittest.main() diff --git a/Tests/test_font_bdf.py b/Tests/test_font_bdf.py index 7c8fe579e..be49e818e 100644 --- a/Tests/test_font_bdf.py +++ b/Tests/test_font_bdf.py @@ -1,4 +1,4 @@ -from helper import unittest, PillowTestCase +from .helper import PillowTestCase from PIL import FontFile, BdfFontFile @@ -18,7 +18,3 @@ class TestFontBdf(PillowTestCase): def test_invalid_file(self): with open("Tests/images/flower.jpg", "rb") as fp: self.assertRaises(SyntaxError, BdfFontFile.BdfFontFile, fp) - - -if __name__ == '__main__': - unittest.main() diff --git a/Tests/test_font_leaks.py b/Tests/test_font_leaks.py index f1ce44e6d..119012bc5 100644 --- a/Tests/test_font_leaks.py +++ b/Tests/test_font_leaks.py @@ -1,5 +1,5 @@ from __future__ import division -from helper import unittest, PillowLeakTestCase +from .helper import unittest, PillowLeakTestCase import sys from PIL import Image, features, ImageDraw, ImageFont @@ -31,7 +31,3 @@ class TestDefaultFontLeak(TestTTypeFontLeak): def test_leak(self): default_font = ImageFont.load_default() self._test_font(default_font) - - -if __name__ == '__main__': - unittest.main() diff --git a/Tests/test_font_pcf.py b/Tests/test_font_pcf.py index 20869505f..d092634f3 100644 --- a/Tests/test_font_pcf.py +++ b/Tests/test_font_pcf.py @@ -1,4 +1,4 @@ -from helper import unittest, PillowTestCase +from .helper import PillowTestCase from PIL import Image, FontFile, PcfFontFile from PIL import ImageFont, ImageDraw @@ -79,7 +79,3 @@ class TestFontPcf(PillowTestCase): # accept bytes instances in Py3. if py3: self._test_high_characters(message.encode('latin1')) - - -if __name__ == '__main__': - unittest.main() diff --git a/Tests/test_format_hsv.py b/Tests/test_format_hsv.py index d7820400e..3e1c00c9e 100644 --- a/Tests/test_format_hsv.py +++ b/Tests/test_format_hsv.py @@ -1,4 +1,4 @@ -from helper import unittest, PillowTestCase, hopper +from .helper import PillowTestCase, hopper from PIL import Image from PIL._util import py3 @@ -13,15 +13,11 @@ class TestFormatHSV(PillowTestCase): return float(i)/255.0 def str_to_float(self, i): - return float(ord(i))/255.0 - def to_int(self, f): - return int(f*255.0) - def tuple_to_ints(self, tp): x, y, z = tp - return (int(x*255.0), int(y*255.0), int(z*255.0)) + return int(x*255.0), int(y*255.0), int(z*255.0) def test_sanity(self): Image.new('HSV', (100, 100)) @@ -133,7 +129,3 @@ class TestFormatHSV(PillowTestCase): self.assert_image_similar(converted.getchannel(2), comparable.getchannel(2), 3, "B conversion is wrong") - - -if __name__ == '__main__': - unittest.main() diff --git a/Tests/test_format_lab.py b/Tests/test_format_lab.py index a243afe62..8a096e672 100644 --- a/Tests/test_format_lab.py +++ b/Tests/test_format_lab.py @@ -1,4 +1,4 @@ -from helper import unittest, PillowTestCase +from .helper import PillowTestCase from PIL import Image @@ -40,7 +40,3 @@ class TestFormatLab(PillowTestCase): k = i.getpixel((0, 0)) self.assertEqual(k, (128, 228, 128)) - - -if __name__ == '__main__': - unittest.main() diff --git a/Tests/test_image.py b/Tests/test_image.py index 7222e389b..330048057 100644 --- a/Tests/test_image.py +++ b/Tests/test_image.py @@ -1,4 +1,4 @@ -from helper import unittest, PillowTestCase, hopper +from .helper import unittest, PillowTestCase, hopper from PIL import Image from PIL._util import py3 @@ -56,9 +56,8 @@ class TestImage(PillowTestCase): self.assertEqual(im.width, 1) self.assertEqual(im.height, 2) - im.size = (3, 4) - self.assertEqual(im.width, 3) - self.assertEqual(im.height, 4) + with self.assertRaises(AttributeError): + im.size = (3, 4) def test_invalid_image(self): if py3: @@ -140,7 +139,6 @@ class TestImage(PillowTestCase): # Act/Assert # Shouldn't cause AttributeError (#774) self.assertFalse(item is None) - self.assertFalse(item == None) self.assertFalse(item == num) def test_expand_x(self): @@ -287,9 +285,9 @@ class TestImage(PillowTestCase): source.alpha_composite, over, (0, 0), "invalid destination") self.assertRaises(ValueError, - source.alpha_composite, over, (0)) + source.alpha_composite, over, 0) self.assertRaises(ValueError, - source.alpha_composite, over, (0, 0), (0)) + source.alpha_composite, over, (0, 0), 0) self.assertRaises(ValueError, source.alpha_composite, over, (0, -1)) self.assertRaises(ValueError, @@ -517,7 +515,7 @@ class TestImage(PillowTestCase): self.assertEqual(new_im.palette.tobytes(), palette_result.tobytes()) else: - self.assertEqual(new_im.palette, None) + self.assertIsNone(new_im.palette) _make_new(im, im_p, im_p.palette) _make_new(im_p, im, None) @@ -562,7 +560,3 @@ class TestRegistry(PillowTestCase): 'DoesNotExist', ('args',), extra=('extra',)) - - -if __name__ == '__main__': - unittest.main() diff --git a/Tests/test_image_access.py b/Tests/test_image_access.py index a7e39a499..937584cff 100644 --- a/Tests/test_image_access.py +++ b/Tests/test_image_access.py @@ -1,4 +1,4 @@ -from helper import unittest, PillowTestCase, hopper, on_appveyor +from .helper import unittest, PillowTestCase, hopper, on_appveyor from PIL import Image import sys @@ -64,6 +64,44 @@ class TestImagePutPixel(AccessTest): self.assert_image_equal(im1, im2) + def test_sanity_negative_index(self): + im1 = hopper() + im2 = Image.new(im1.mode, im1.size, 0) + + width, height = im1.size + self.assertEqual(im1.getpixel((0, 0)), im1.getpixel((-width, -height))) + self.assertEqual(im1.getpixel((-1, -1)), + im1.getpixel((width-1, height-1))) + + for y in range(-1, -im1.size[1]-1, -1): + for x in range(-1, -im1.size[0]-1, -1): + pos = x, y + im2.putpixel(pos, im1.getpixel(pos)) + + self.assert_image_equal(im1, im2) + + im2 = Image.new(im1.mode, im1.size, 0) + im2.readonly = 1 + + for y in range(-1, -im1.size[1]-1, -1): + for x in range(-1, -im1.size[0]-1, -1): + pos = x, y + im2.putpixel(pos, im1.getpixel(pos)) + + self.assertFalse(im2.readonly) + self.assert_image_equal(im1, im2) + + im2 = Image.new(im1.mode, im1.size, 0) + + pix1 = im1.load() + pix2 = im2.load() + + for y in range(-1, -im1.size[1]-1, -1): + for x in range(-1, -im1.size[0]-1, -1): + pix2[x, y] = pix1[x, y] + + self.assert_image_equal(im1, im2) + class TestImageGetPixel(AccessTest): @staticmethod @@ -85,23 +123,43 @@ class TestImageGetPixel(AccessTest): im.getpixel((0, 0)), c, "put/getpixel roundtrip failed for mode %s, color %s" % (mode, c)) + # check putpixel negative index + im.putpixel((-1, -1), c) + self.assertEqual( + im.getpixel((-1, -1)), c, + "put/getpixel roundtrip negative index failed" + " for mode %s, color %s" % (mode, c)) + # Check 0 im = Image.new(mode, (0, 0), None) with self.assertRaises(IndexError): im.putpixel((0, 0), c) with self.assertRaises(IndexError): im.getpixel((0, 0)) + # Check 0 negative index + with self.assertRaises(IndexError): + im.putpixel((-1, -1), c) + with self.assertRaises(IndexError): + im.getpixel((-1, -1)) # check initial color im = Image.new(mode, (1, 1), c) self.assertEqual( im.getpixel((0, 0)), c, "initial color failed for mode %s, color %s " % (mode, c)) + # check initial color negative index + self.assertEqual( + im.getpixel((-1, -1)), c, + "initial color failed with negative index" + "for mode %s, color %s " % (mode, c)) # Check 0 im = Image.new(mode, (0, 0), c) with self.assertRaises(IndexError): im.getpixel((0, 0)) + # Check 0 negative index + with self.assertRaises(IndexError): + im.getpixel((-1, -1)) def test_basic(self): for mode in ("1", "L", "LA", "I", "I;16", "I;16B", "F", @@ -117,6 +175,12 @@ class TestImageGetPixel(AccessTest): self.check(mode, 2**15+1) self.check(mode, 2**16-1) + def test_p_putpixel_rgb_rgba(self): + for color in [(255, 0, 0), (255, 0, 0, 255)]: + im = Image.new("P", (1, 1), 0) + im.putpixel((0, 0), color) + self.assertEqual(im.convert("RGB").getpixel((0, 0)), (255, 0, 0)) + @unittest.skipIf(cffi is None, "No cffi") class TestCffiPutPixel(TestImagePutPixel): @@ -236,13 +300,18 @@ class TestCffi(AccessTest): # pixels can contain garbage if image is released self.assertEqual(px[i, 0], 0) + def test_p_putpixel_rgb_rgba(self): + for color in [(255, 0, 0), (255, 0, 0, 255)]: + im = Image.new("P", (1, 1), 0) + access = PyAccess.new(im, False) + access.putpixel((0, 0), color) + self.assertEqual(im.convert("RGB").getpixel((0, 0)), (255, 0, 0)) + class TestEmbeddable(unittest.TestCase): @unittest.skipIf(not sys.platform.startswith('win32') or - sys.version_info[:2] == (3, 4) or - on_appveyor(), # failing on appveyor when run from - # subprocess, not from shell - "requires Python 2.7 or >=3.5 for Windows") + on_appveyor(), + "Failing on AppVeyor when run from subprocess, not from shell") def test_embeddable(self): import subprocess import ctypes @@ -297,7 +366,3 @@ int main(int argc, char* argv[]) process = subprocess.Popen(['embed_pil.exe'], env=env) process.communicate() self.assertEqual(process.returncode, 0) - - -if __name__ == '__main__': - unittest.main() diff --git a/Tests/test_image_array.py b/Tests/test_image_array.py index 7a86a3e54..cf104217a 100644 --- a/Tests/test_image_array.py +++ b/Tests/test_image_array.py @@ -1,4 +1,4 @@ -from helper import unittest, PillowTestCase, hopper +from .helper import PillowTestCase, hopper from PIL import Image @@ -55,7 +55,3 @@ 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)) - - -if __name__ == '__main__': - unittest.main() diff --git a/Tests/test_image_convert.py b/Tests/test_image_convert.py index 1b3815d80..1ba1794eb 100644 --- a/Tests/test_image_convert.py +++ b/Tests/test_image_convert.py @@ -1,4 +1,4 @@ -from helper import unittest, PillowTestCase, hopper +from .helper import PillowTestCase, hopper from PIL import Image @@ -88,7 +88,7 @@ class TestImageConvert(PillowTestCase): # Assert self.assertNotIn('transparency', im_rgba.info) # https://github.com/python-pillow/Pillow/issues/2702 - self.assertEqual(im_rgba.palette, None) + self.assertIsNone(im_rgba.palette) def test_trns_l(self): im = hopper('L') @@ -230,7 +230,3 @@ class TestImageConvert(PillowTestCase): # Assert # No change self.assert_image_equal(converted_im, im) - - -if __name__ == '__main__': - unittest.main() diff --git a/Tests/test_image_copy.py b/Tests/test_image_copy.py index bb1246a73..19679fe22 100644 --- a/Tests/test_image_copy.py +++ b/Tests/test_image_copy.py @@ -1,4 +1,4 @@ -from helper import unittest, PillowTestCase, hopper +from .helper import PillowTestCase, hopper from PIL import Image @@ -40,7 +40,3 @@ class TestImageCopy(PillowTestCase): out = im.copy() self.assertEqual(out.mode, im.mode) self.assertEqual(out.size, im.size) - - -if __name__ == '__main__': - unittest.main() diff --git a/Tests/test_image_crop.py b/Tests/test_image_crop.py index fe92dd865..45123a631 100644 --- a/Tests/test_image_crop.py +++ b/Tests/test_image_crop.py @@ -1,4 +1,4 @@ -from helper import unittest, PillowTestCase, hopper +from .helper import PillowTestCase, hopper from PIL import Image @@ -102,7 +102,3 @@ class TestImageCrop(PillowTestCase): cropped = im.crop((10, 10, 20, 20)) self.assertEqual(cropped.size, (10, 10)) self.assertEqual(cropped.getdata()[2], (0, 0, 0)) - - -if __name__ == '__main__': - unittest.main() diff --git a/Tests/test_image_draft.py b/Tests/test_image_draft.py index 18982fc99..9ebd68945 100644 --- a/Tests/test_image_draft.py +++ b/Tests/test_image_draft.py @@ -1,4 +1,4 @@ -from helper import unittest, PillowTestCase, fromstring, tostring +from .helper import PillowTestCase, fromstring, tostring from PIL import Image @@ -69,7 +69,3 @@ class TestImageDraft(PillowTestCase): im = self.draft_roundtrip('L', (128, 128), None, (64, 64)) im.draft(None, (64, 64)) im.load() - - -if __name__ == '__main__': - unittest.main() diff --git a/Tests/test_image_filter.py b/Tests/test_image_filter.py index 6936a84f0..a91387f81 100644 --- a/Tests/test_image_filter.py +++ b/Tests/test_image_filter.py @@ -1,4 +1,4 @@ -from helper import unittest, PillowTestCase, hopper +from .helper import PillowTestCase, hopper from PIL import Image, ImageFilter @@ -106,7 +106,7 @@ class TestImageFilter(PillowTestCase): def test_consistency_3x3(self): source = Image.open("Tests/images/hopper.bmp") reference = Image.open("Tests/images/hopper_emboss.bmp") - kernel = ImageFilter.Kernel((3, 3), + kernel = ImageFilter.Kernel((3, 3), # noqa: E127 (-1, -1, 0, -1, 0, 1, 0, 1, 1), .3) @@ -122,7 +122,7 @@ class TestImageFilter(PillowTestCase): def test_consistency_5x5(self): source = Image.open("Tests/images/hopper.bmp") reference = Image.open("Tests/images/hopper_emboss_more.bmp") - kernel = ImageFilter.Kernel((5, 5), + kernel = ImageFilter.Kernel((5, 5), # noqa: E127 (-1, -1, -1, -1, 0, -1, -1, -1, 0, 1, -1, -1, 0, 1, 1, @@ -136,7 +136,3 @@ class TestImageFilter(PillowTestCase): Image.merge(mode, source[:len(mode)]).filter(kernel), Image.merge(mode, reference[:len(mode)]), ) - - -if __name__ == '__main__': - unittest.main() diff --git a/Tests/test_image_frombytes.py b/Tests/test_image_frombytes.py index 2d48bb6b8..3b224e71a 100644 --- a/Tests/test_image_frombytes.py +++ b/Tests/test_image_frombytes.py @@ -1,4 +1,4 @@ -from helper import unittest, PillowTestCase, hopper +from .helper import PillowTestCase, hopper from PIL import Image @@ -13,7 +13,3 @@ class TestImageFromBytes(PillowTestCase): def test_not_implemented(self): self.assertRaises(NotImplementedError, Image.fromstring) - - -if __name__ == '__main__': - unittest.main() diff --git a/Tests/test_image_fromqimage.py b/Tests/test_image_fromqimage.py index 2e5d95aa7..8a29a2715 100644 --- a/Tests/test_image_fromqimage.py +++ b/Tests/test_image_fromqimage.py @@ -1,5 +1,5 @@ -from helper import unittest, PillowTestCase, hopper -from test_imageqt import PillowQtTestCase +from .helper import PillowTestCase, hopper +from .test_imageqt import PillowQtTestCase from PIL import ImageQt, Image @@ -42,7 +42,3 @@ class TestFromQImage(PillowQtTestCase, PillowTestCase): def test_sanity_p(self): for im in self.files_to_test: self.roundtrip(im.convert('P')) - - -if __name__ == '__main__': - unittest.main() diff --git a/Tests/test_image_getbands.py b/Tests/test_image_getbands.py index 5eecbf044..6d79bf280 100644 --- a/Tests/test_image_getbands.py +++ b/Tests/test_image_getbands.py @@ -1,4 +1,4 @@ -from helper import unittest, PillowTestCase +from .helper import PillowTestCase from PIL import Image @@ -18,7 +18,3 @@ class TestImageGetBands(PillowTestCase): Image.new("CMYK", (1, 1)).getbands(), ("C", "M", "Y", "K")) self.assertEqual( Image.new("YCbCr", (1, 1)).getbands(), ("Y", "Cb", "Cr")) - - -if __name__ == '__main__': - unittest.main() diff --git a/Tests/test_image_getbbox.py b/Tests/test_image_getbbox.py index f29032143..9bf39752b 100644 --- a/Tests/test_image_getbbox.py +++ b/Tests/test_image_getbbox.py @@ -1,4 +1,4 @@ -from helper import unittest, PillowTestCase, hopper +from .helper import PillowTestCase, hopper from PIL import Image @@ -37,7 +37,3 @@ class TestImageGetBbox(PillowTestCase): im.paste(255, (-10, -10, 110, 110)) self.assertEqual(im.getbbox(), (0, 0, 100, 100)) - - -if __name__ == '__main__': - unittest.main() diff --git a/Tests/test_image_getcolors.py b/Tests/test_image_getcolors.py index ca7a9d93d..aa2a40976 100644 --- a/Tests/test_image_getcolors.py +++ b/Tests/test_image_getcolors.py @@ -1,4 +1,4 @@ -from helper import unittest, PillowTestCase, hopper +from .helper import PillowTestCase, hopper class TestImageGetColors(PillowTestCase): @@ -65,7 +65,3 @@ class TestImageGetColors(PillowTestCase): A = im.getcolors(maxcolors=16) A.sort() self.assertEqual(A, expected) - - -if __name__ == '__main__': - unittest.main() diff --git a/Tests/test_image_getdata.py b/Tests/test_image_getdata.py index ef07844df..fe8f9adcd 100644 --- a/Tests/test_image_getdata.py +++ b/Tests/test_image_getdata.py @@ -1,4 +1,4 @@ -from helper import unittest, PillowTestCase, hopper +from .helper import PillowTestCase, hopper class TestImageGetData(PillowTestCase): @@ -23,11 +23,7 @@ class TestImageGetData(PillowTestCase): self.assertEqual(getdata("L"), (16, 960, 960)) self.assertEqual(getdata("I"), (16, 960, 960)) self.assertEqual(getdata("F"), (16.0, 960, 960)) - self.assertEqual(getdata("RGB"), (((11, 13, 52), 960, 960))) + self.assertEqual(getdata("RGB"), ((11, 13, 52), 960, 960)) self.assertEqual(getdata("RGBA"), ((11, 13, 52, 255), 960, 960)) self.assertEqual(getdata("CMYK"), ((244, 242, 203, 0), 960, 960)) self.assertEqual(getdata("YCbCr"), ((16, 147, 123), 960, 960)) - - -if __name__ == '__main__': - unittest.main() diff --git a/Tests/test_image_getextrema.py b/Tests/test_image_getextrema.py index 9783141a3..1689744af 100644 --- a/Tests/test_image_getextrema.py +++ b/Tests/test_image_getextrema.py @@ -1,5 +1,5 @@ from PIL import Image -from helper import unittest, PillowTestCase, hopper +from .helper import PillowTestCase, hopper class TestImageGetExtrema(PillowTestCase): @@ -19,7 +19,7 @@ class TestImageGetExtrema(PillowTestCase): self.assertEqual( extrema("RGBA"), ((0, 255), (0, 255), (0, 255), (255, 255))) self.assertEqual( - extrema("CMYK"), (((0, 255), (0, 255), (0, 255), (0, 0)))) + extrema("CMYK"), ((0, 255), (0, 255), (0, 255), (0, 0))) self.assertEqual(extrema("I;16"), (0, 255)) def test_true_16(self): @@ -27,7 +27,3 @@ class TestImageGetExtrema(PillowTestCase): self.assertEqual(im.mode, 'I;16') extrema = im.getextrema() self.assertEqual(extrema, (106, 285)) - - -if __name__ == '__main__': - unittest.main() diff --git a/Tests/test_image_getim.py b/Tests/test_image_getim.py index 1452e584e..6d3682caf 100644 --- a/Tests/test_image_getim.py +++ b/Tests/test_image_getim.py @@ -1,4 +1,4 @@ -from helper import unittest, PillowTestCase, hopper +from .helper import PillowTestCase, hopper from PIL._util import py3 @@ -12,7 +12,3 @@ class TestImageGetIm(PillowTestCase): self.assertIn("PyCapsule", type_repr) self.assertIsInstance(im.im.id, int) - - -if __name__ == '__main__': - unittest.main() diff --git a/Tests/test_image_getpalette.py b/Tests/test_image_getpalette.py index 01a6ac7ad..98f8142dc 100644 --- a/Tests/test_image_getpalette.py +++ b/Tests/test_image_getpalette.py @@ -1,4 +1,4 @@ -from helper import unittest, PillowTestCase, hopper +from .helper import PillowTestCase, hopper class TestImageGetPalette(PillowTestCase): @@ -18,7 +18,3 @@ class TestImageGetPalette(PillowTestCase): self.assertIsNone(palette("RGBA")) self.assertIsNone(palette("CMYK")) self.assertIsNone(palette("YCbCr")) - - -if __name__ == '__main__': - unittest.main() diff --git a/Tests/test_image_getprojection.py b/Tests/test_image_getprojection.py index 9d3f2d9ed..85d40e859 100644 --- a/Tests/test_image_getprojection.py +++ b/Tests/test_image_getprojection.py @@ -1,4 +1,4 @@ -from helper import unittest, PillowTestCase, hopper +from .helper import PillowTestCase, hopper from PIL import Image @@ -30,7 +30,3 @@ class TestImageGetProjection(PillowTestCase): im.paste(255, (2, 4, 8, 6)) self.assertEqual(im.getprojection()[0], [0, 0, 1, 1, 1, 1, 1, 1, 0, 0]) self.assertEqual(im.getprojection()[1], [0, 0, 0, 0, 1, 1, 0, 0, 0, 0]) - - -if __name__ == '__main__': - unittest.main() diff --git a/Tests/test_image_histogram.py b/Tests/test_image_histogram.py index 892e89328..0a023cd69 100644 --- a/Tests/test_image_histogram.py +++ b/Tests/test_image_histogram.py @@ -1,4 +1,4 @@ -from helper import unittest, PillowTestCase, hopper +from .helper import PillowTestCase, hopper class TestImageHistogram(PillowTestCase): @@ -18,7 +18,3 @@ class TestImageHistogram(PillowTestCase): self.assertEqual(histogram("RGBA"), (1024, 0, 16384)) self.assertEqual(histogram("CMYK"), (1024, 0, 16384)) self.assertEqual(histogram("YCbCr"), (768, 0, 1908)) - - -if __name__ == '__main__': - unittest.main() diff --git a/Tests/test_image_load.py b/Tests/test_image_load.py index 88b6e9b5c..d8f323eb8 100644 --- a/Tests/test_image_load.py +++ b/Tests/test_image_load.py @@ -1,4 +1,4 @@ -from helper import unittest, PillowTestCase, hopper +from .helper import PillowTestCase, hopper from PIL import Image @@ -28,7 +28,3 @@ class TestImageLoad(PillowTestCase): os.fstat(fn) self.assertRaises(OSError, os.fstat, fn) - - -if __name__ == '__main__': - unittest.main() diff --git a/Tests/test_image_mode.py b/Tests/test_image_mode.py index 0596af397..26266611d 100644 --- a/Tests/test_image_mode.py +++ b/Tests/test_image_mode.py @@ -1,4 +1,4 @@ -from helper import unittest, PillowTestCase, hopper +from .helper import PillowTestCase, hopper from PIL import Image @@ -51,7 +51,3 @@ class TestImageMode(PillowTestCase): check("RGBX", "RGB", "L", 4, ("R", "G", "B", "X")) check("CMYK", "RGB", "L", 4, ("C", "M", "Y", "K")) check("YCbCr", "RGB", "L", 3, ("Y", "Cb", "Cr")) - - -if __name__ == '__main__': - unittest.main() diff --git a/Tests/test_image_paste.py b/Tests/test_image_paste.py index e782008a7..5586e8618 100644 --- a/Tests/test_image_paste.py +++ b/Tests/test_image_paste.py @@ -1,4 +1,4 @@ -from helper import unittest, PillowTestCase, cached_property +from .helper import PillowTestCase, cached_property from PIL import Image @@ -250,7 +250,3 @@ class TestImagingPaste(PillowTestCase): im.copy().paste(im2) im.copy().paste(im2, (0, 0)) - - -if __name__ == '__main__': - unittest.main() diff --git a/Tests/test_image_point.py b/Tests/test_image_point.py index 977e98e83..90498fcff 100644 --- a/Tests/test_image_point.py +++ b/Tests/test_image_point.py @@ -1,4 +1,4 @@ -from helper import unittest, PillowTestCase, hopper +from .helper import PillowTestCase, hopper class TestImagePoint(PillowTestCase): @@ -38,7 +38,3 @@ class TestImagePoint(PillowTestCase): def test_f_mode(self): im = hopper('F') self.assertRaises(ValueError, im.point, None) - - -if __name__ == '__main__': - unittest.main() diff --git a/Tests/test_image_putalpha.py b/Tests/test_image_putalpha.py index 823e0612f..7b66b8833 100644 --- a/Tests/test_image_putalpha.py +++ b/Tests/test_image_putalpha.py @@ -1,4 +1,4 @@ -from helper import unittest, PillowTestCase +from .helper import PillowTestCase from PIL import Image @@ -44,7 +44,3 @@ class TestImagePutAlpha(PillowTestCase): self.assertFalse(im.readonly) self.assertEqual(im.mode, 'RGBA') self.assertEqual(im.getpixel((0, 0)), (1, 2, 3, 4)) - - -if __name__ == '__main__': - unittest.main() diff --git a/Tests/test_image_putdata.py b/Tests/test_image_putdata.py index 2008c1307..1b57e38b7 100644 --- a/Tests/test_image_putdata.py +++ b/Tests/test_image_putdata.py @@ -1,4 +1,4 @@ -from helper import unittest, PillowTestCase, hopper +from .helper import PillowTestCase, hopper from array import array import sys @@ -83,7 +83,3 @@ class TestImagePutData(PillowTestCase): im.putdata(arr) self.assertEqual(len(im.getdata()), len(arr)) - - -if __name__ == '__main__': - unittest.main() diff --git a/Tests/test_image_putpalette.py b/Tests/test_image_putpalette.py index e173f0000..34b585f55 100644 --- a/Tests/test_image_putpalette.py +++ b/Tests/test_image_putpalette.py @@ -1,4 +1,4 @@ -from helper import unittest, PillowTestCase, hopper +from .helper import PillowTestCase, hopper from PIL import ImagePalette @@ -28,7 +28,3 @@ class TestImagePutPalette(PillowTestCase): im.putpalette(ImagePalette.random()) im.putpalette(ImagePalette.sepia()) im.putpalette(ImagePalette.wedge()) - - -if __name__ == '__main__': - unittest.main() diff --git a/Tests/test_image_quantize.py b/Tests/test_image_quantize.py index 5b10d2de3..2f0b65758 100644 --- a/Tests/test_image_quantize.py +++ b/Tests/test_image_quantize.py @@ -1,4 +1,4 @@ -from helper import unittest, PillowTestCase, hopper +from .helper import PillowTestCase, hopper from PIL import Image @@ -46,7 +46,3 @@ class TestImageQuantize(PillowTestCase): converted = image.quantize() self.assert_image(converted, 'P', converted.size) self.assert_image_similar(converted.convert('RGB'), image, 1) - - -if __name__ == '__main__': - unittest.main() diff --git a/Tests/test_image_resample.py b/Tests/test_image_resample.py index 1b7081d55..9ab8280ed 100644 --- a/Tests/test_image_resample.py +++ b/Tests/test_image_resample.py @@ -2,7 +2,7 @@ from __future__ import division, print_function from contextlib import contextmanager -from helper import unittest, PillowTestCase, hopper +from .helper import unittest, PillowTestCase, hopper from PIL import Image, ImageDraw @@ -196,7 +196,7 @@ class TestImagingCoreResampleAccuracy(PillowTestCase): class CoreResampleConsistencyTest(PillowTestCase): def make_case(self, mode, fill): im = Image.new(mode, (512, 9), fill) - return (im.resize((9, 512), Image.LANCZOS), im.load()[0, 0]) + return im.resize((9, 512), Image.LANCZOS), im.load()[0, 0] def run_case(self, case): channel, color = case @@ -453,7 +453,7 @@ class CoreResampleBoxTest(PillowTestCase): # error with box should be much smaller than without self.assert_image_similar(reference, with_box, 6) - with self.assertRaisesRegex(AssertionError, "difference 29\."): + with self.assertRaisesRegex(AssertionError, r"difference 29\."): self.assert_image_similar(reference, without_box, 5) def test_formats(self): @@ -496,7 +496,7 @@ class CoreResampleBoxTest(PillowTestCase): try: res = im.resize(size, Image.LANCZOS, box) self.assertEqual(res.size, size) - with self.assertRaisesRegex(AssertionError, "difference \d"): + with self.assertRaisesRegex(AssertionError, r"difference \d"): # check that the difference at least that much self.assert_image_similar(res, im.crop(box), 20) except AssertionError: @@ -544,7 +544,3 @@ class CoreResampleBoxTest(PillowTestCase): except AssertionError: print('>>>', size, box, flt) raise - - -if __name__ == '__main__': - unittest.main() diff --git a/Tests/test_image_resize.py b/Tests/test_image_resize.py index 535f1d77e..c47c7317b 100644 --- a/Tests/test_image_resize.py +++ b/Tests/test_image_resize.py @@ -3,7 +3,7 @@ Tests for resize functionality. """ from itertools import permutations -from helper import unittest, PillowTestCase, hopper +from .helper import PillowTestCase, hopper from PIL import Image @@ -115,7 +115,3 @@ class TestImageResize(PillowTestCase): # Test unknown resampling filter im = hopper() self.assertRaises(ValueError, im.resize, (10, 10), "unknown") - - -if __name__ == '__main__': - unittest.main() diff --git a/Tests/test_image_rotate.py b/Tests/test_image_rotate.py index e788e722f..05043cd06 100644 --- a/Tests/test_image_rotate.py +++ b/Tests/test_image_rotate.py @@ -1,4 +1,4 @@ -from helper import unittest, PillowTestCase, hopper +from .helper import PillowTestCase, hopper from PIL import Image @@ -123,7 +123,3 @@ class TestImageRotate(PillowTestCase): im = im.rotate(45, expand=1, fillcolor=(255, 0, 0, 255)) corner = im.getpixel((0, 0)) self.assertEqual(corner, (255, 0, 0, 255)) - - -if __name__ == '__main__': - unittest.main() diff --git a/Tests/test_image_split.py b/Tests/test_image_split.py index 6f312ff80..2d97dabc2 100644 --- a/Tests/test_image_split.py +++ b/Tests/test_image_split.py @@ -1,4 +1,4 @@ -from helper import unittest, PillowTestCase, hopper +from .helper import PillowTestCase, hopper from PIL import Image @@ -59,7 +59,3 @@ class TestImageSplit(PillowTestCase): self.assertEqual(split_open("RGB"), 3) if 'zip_encoder' in codecs: self.assertEqual(split_open("RGBA"), 4) - - -if __name__ == '__main__': - unittest.main() diff --git a/Tests/test_image_thumbnail.py b/Tests/test_image_thumbnail.py index 6b92dbb24..d03ea4141 100644 --- a/Tests/test_image_thumbnail.py +++ b/Tests/test_image_thumbnail.py @@ -1,4 +1,4 @@ -from helper import unittest, PillowTestCase, hopper +from .helper import PillowTestCase, hopper class TestImageThumbnail(PillowTestCase): @@ -35,7 +35,3 @@ class TestImageThumbnail(PillowTestCase): im = hopper().resize((128, 128)) im.thumbnail((100, 100)) self.assert_image(im, im.mode, (100, 100)) - - -if __name__ == '__main__': - unittest.main() diff --git a/Tests/test_image_tobitmap.py b/Tests/test_image_tobitmap.py index f93fce6a8..c5c06ba01 100644 --- a/Tests/test_image_tobitmap.py +++ b/Tests/test_image_tobitmap.py @@ -1,4 +1,4 @@ -from helper import unittest, PillowTestCase, hopper, fromstring +from .helper import PillowTestCase, hopper, fromstring class TestImageToBitmap(PillowTestCase): @@ -13,7 +13,3 @@ class TestImageToBitmap(PillowTestCase): self.assertIsInstance(bitmap, bytes) self.assert_image_equal(im1, fromstring(bitmap)) - - -if __name__ == '__main__': - unittest.main() diff --git a/Tests/test_image_tobytes.py b/Tests/test_image_tobytes.py index f5a738695..2bfee2da3 100644 --- a/Tests/test_image_tobytes.py +++ b/Tests/test_image_tobytes.py @@ -1,4 +1,4 @@ -from helper import unittest, PillowTestCase, hopper +from .helper import PillowTestCase, hopper class TestImageToBytes(PillowTestCase): @@ -6,7 +6,3 @@ class TestImageToBytes(PillowTestCase): def test_sanity(self): data = hopper().tobytes() self.assertIsInstance(data, bytes) - - -if __name__ == '__main__': - unittest.main() diff --git a/Tests/test_image_transform.py b/Tests/test_image_transform.py index add8cc5e7..ae1cf6e3d 100644 --- a/Tests/test_image_transform.py +++ b/Tests/test_image_transform.py @@ -1,6 +1,6 @@ import math -from helper import unittest, PillowTestCase, hopper +from .helper import PillowTestCase, hopper from PIL import Image @@ -152,7 +152,7 @@ class TestImageTransform(PillowTestCase): ] # Yeah. Watch some JIT optimize this out. - pattern = None + pattern = None # noqa: F841 self.test_mesh() @@ -271,7 +271,3 @@ class TestImageTransformAffine(PillowTestCase): class TestImageTransformPerspective(TestImageTransformAffine): # Repeat all tests for AFFINE transformations with PERSPECTIVE transform = Image.PERSPECTIVE - - -if __name__ == '__main__': - unittest.main() diff --git a/Tests/test_image_transpose.py b/Tests/test_image_transpose.py index a6b1191db..8ffb9e9bf 100644 --- a/Tests/test_image_transpose.py +++ b/Tests/test_image_transpose.py @@ -1,5 +1,5 @@ -import helper -from helper import unittest, PillowTestCase +from . import helper +from .helper import PillowTestCase from PIL.Image import (FLIP_LEFT_RIGHT, FLIP_TOP_BOTTOM, ROTATE_90, ROTATE_180, ROTATE_270, TRANSPOSE, TRANSVERSE) @@ -146,7 +146,3 @@ class TestImageTranspose(PillowTestCase): im.transpose(TRANSVERSE), transpose(ROTATE_270, FLIP_TOP_BOTTOM)) self.assert_image_equal( im.transpose(TRANSVERSE), transpose(ROTATE_180, TRANSPOSE)) - - -if __name__ == '__main__': - unittest.main() diff --git a/Tests/test_imagechops.py b/Tests/test_imagechops.py index 06febc6d2..8aa784cdd 100644 --- a/Tests/test_imagechops.py +++ b/Tests/test_imagechops.py @@ -1,4 +1,4 @@ -from helper import unittest, PillowTestCase, hopper +from .helper import PillowTestCase, hopper from PIL import Image from PIL import ImageChops @@ -373,7 +373,3 @@ class TestImageChops(PillowTestCase): table(ImageChops.logical_or, 0, 255), (0, 255, 255, 255)) self.assertEqual( table(ImageChops.logical_xor, 0, 255), (0, 255, 255, 0)) - - -if __name__ == '__main__': - unittest.main() diff --git a/Tests/test_imagecms.py b/Tests/test_imagecms.py index dc6c85f05..cdd6f00c3 100644 --- a/Tests/test_imagecms.py +++ b/Tests/test_imagecms.py @@ -1,4 +1,4 @@ -from helper import unittest, PillowTestCase, hopper +from .helper import PillowTestCase, hopper import datetime from PIL import Image, ImageMode @@ -10,7 +10,7 @@ try: from PIL import ImageCms from PIL.ImageCms import ImageCmsProfile ImageCms.core.profile_open -except ImportError as v: +except ImportError: # Skipped via setUp() pass @@ -294,7 +294,14 @@ class TestImageCms(PillowTestCase): p.blue_primary, ((0.14306641366715667, 0.06060790921083026, 0.7140960805782015), (0.15588475410450106, 0.06603820408959558, 0.06060790921083026))) - assert_truncated_tuple_equal(p.chromatic_adaptation, (((1.04791259765625, 0.0229339599609375, -0.050201416015625), (0.02960205078125, 0.9904632568359375, -0.0170745849609375), (-0.009246826171875, 0.0150604248046875, 0.7517852783203125)), ((1.0267159024652783, 0.022470062342089134, 0.0229339599609375), (0.02951378324103937, 0.9875098886387147, 0.9904632568359375), (-0.012205438066465256, 0.01987915407854985, 0.0150604248046875)))) + assert_truncated_tuple_equal( + p.chromatic_adaptation, + (((1.04791259765625, 0.0229339599609375, -0.050201416015625), + (0.02960205078125, 0.9904632568359375, -0.0170745849609375), + (-0.009246826171875, 0.0150604248046875, 0.7517852783203125)), + ((1.0267159024652783, 0.022470062342089134, 0.0229339599609375), + (0.02951378324103937, 0.9875098886387147, 0.9904632568359375), + (-0.012205438066465256, 0.01987915407854985, 0.0150604248046875)))) self.assertIsNone(p.chromaticity) self.assertEqual(p.clut, { 0: (False, False, True), @@ -402,10 +409,10 @@ class TestImageCms(PillowTestCase): def create_test_image(): # set up test image with something interesting in the tested aux # channel. - nine_grid_deltas = [ + nine_grid_deltas = [ # noqa: E128 (-1, -1), (-1, 0), (-1, 1), - ( 0, -1), ( 0, 0), ( 0, 1), - ( 1, -1), ( 1, 0), ( 1, 1), + (0, -1), (0, 0), (0, 1), + (1, -1), (1, 0), (1, 1), ] chans = [] bands = ImageMode.getmode(mode).bands @@ -421,7 +428,11 @@ class TestImageCms(PillowTestCase): ) channel_data = Image.new(channel_type, channel_pattern.size) for delta in nine_grid_deltas: - channel_data.paste(channel_pattern, tuple(paste_offset[c] + delta[c]*channel_pattern.size[c] for c in range(2))) + channel_data.paste( + channel_pattern, + tuple(paste_offset[c] + delta[c] * channel_pattern.size[c] + for c in range(2)), + ) chans.append(channel_data) return Image.merge(mode, chans) @@ -503,7 +514,3 @@ class TestImageCms(PillowTestCase): self.assert_image_equal(test_image.convert(dst_format[2]), reference_image) - - -if __name__ == '__main__': - unittest.main() diff --git a/Tests/test_imagecolor.py b/Tests/test_imagecolor.py index 1ea37544b..cb9c9843c 100644 --- a/Tests/test_imagecolor.py +++ b/Tests/test_imagecolor.py @@ -1,4 +1,4 @@ -from helper import unittest, PillowTestCase +from .helper import PillowTestCase from PIL import Image from PIL import ImageColor @@ -192,7 +192,3 @@ class TestImageColor(PillowTestCase): self.assertEqual( (162, 33), ImageColor.getcolor("rgba(0, 255, 115, 33)", "LA")) Image.new("LA", (1, 1), "white") - - -if __name__ == '__main__': - unittest.main() diff --git a/Tests/test_imagedraw.py b/Tests/test_imagedraw.py index 5a945e8b2..bceb0e3d4 100644 --- a/Tests/test_imagedraw.py +++ b/Tests/test_imagedraw.py @@ -1,6 +1,6 @@ import os.path -from helper import PillowTestCase, hopper, unittest +from .helper import PillowTestCase, hopper from PIL import Image, ImageColor, ImageDraw BLACK = (0, 0, 0) @@ -46,7 +46,7 @@ class TestImageDraw(PillowTestCase): im = Image.open("Tests/images/chi.gif") draw = ImageDraw.Draw(im) - draw.line(((0, 0)), fill=(0, 0, 0)) + draw.line((0, 0), fill=(0, 0, 0)) def test_mode_mismatch(self): im = hopper("RGB").copy() @@ -102,6 +102,30 @@ class TestImageDraw(PillowTestCase): self.assert_image_similar( im, Image.open("Tests/images/imagedraw_arc_no_loops.png"), 1) + def test_arc_width(self): + # Arrange + im = Image.new("RGB", (W, H)) + draw = ImageDraw.Draw(im) + expected = "Tests/images/imagedraw_arc_width.png" + + # Act + draw.arc(BBOX1, 10, 260, width=5) + + # Assert + self.assert_image_similar(im, Image.open(expected), 1) + + def test_arc_width_fill(self): + # Arrange + im = Image.new("RGB", (W, H)) + draw = ImageDraw.Draw(im) + expected = "Tests/images/imagedraw_arc_width_fill.png" + + # Act + draw.arc(BBOX1, 10, 260, fill="yellow", width=5) + + # Assert + self.assert_image_similar(im, Image.open(expected), 1) + def test_bitmap(self): # Arrange small = Image.open("Tests/images/pil123rgba.png").resize((50, 50)) @@ -137,6 +161,30 @@ class TestImageDraw(PillowTestCase): self.helper_chord(mode, BBOX2, 0, 180) self.helper_chord(mode, BBOX2, 0.5, 180.4) + def test_chord_width(self): + # Arrange + im = Image.new("RGB", (W, H)) + draw = ImageDraw.Draw(im) + expected = "Tests/images/imagedraw_chord_width.png" + + # Act + draw.chord(BBOX1, 10, 260, outline="yellow", width=5) + + # Assert + self.assert_image_similar(im, Image.open(expected), 1) + + def test_chord_width_fill(self): + # Arrange + im = Image.new("RGB", (W, H)) + draw = ImageDraw.Draw(im) + expected = "Tests/images/imagedraw_chord_width_fill.png" + + # Act + draw.chord(BBOX1, 10, 260, fill="red", outline="yellow", width=5) + + # Assert + self.assert_image_similar(im, Image.open(expected), 1) + def helper_ellipse(self, mode, bbox): # Arrange im = Image.new(mode, (W, H)) @@ -179,6 +227,30 @@ class TestImageDraw(PillowTestCase): draw.ellipse(bbox, fill="green", outline="blue") self.assert_image_equal(im, im.transpose(Image.FLIP_LEFT_RIGHT)) + def test_ellipse_width(self): + # Arrange + im = Image.new("RGB", (W, H)) + draw = ImageDraw.Draw(im) + expected = "Tests/images/imagedraw_ellipse_width.png" + + # Act + draw.ellipse(BBOX1, outline="blue", width=5) + + # Assert + self.assert_image_similar(im, Image.open(expected), 1) + + def test_ellipse_width_fill(self): + # Arrange + im = Image.new("RGB", (W, H)) + draw = ImageDraw.Draw(im) + expected = "Tests/images/imagedraw_ellipse_width_fill.png" + + # Act + draw.ellipse(BBOX1, fill="green", outline="blue", width=5) + + # Assert + self.assert_image_similar(im, Image.open(expected), 1) + def helper_line(self, points): # Arrange im = Image.new("RGB", (W, H)) @@ -259,6 +331,30 @@ class TestImageDraw(PillowTestCase): self.helper_pieslice(BBOX2, -90, 45) self.helper_pieslice(BBOX2, -90.5, 45.4) + def test_pieslice_width(self): + # Arrange + im = Image.new("RGB", (W, H)) + draw = ImageDraw.Draw(im) + expected = "Tests/images/imagedraw_pieslice_width.png" + + # Act + draw.pieslice(BBOX1, 10, 260, outline="blue", width=5) + + # Assert + self.assert_image_similar(im, Image.open(expected), 1) + + def test_pieslice_width_fill(self): + # Arrange + im = Image.new("RGB", (W, H)) + draw = ImageDraw.Draw(im) + expected = "Tests/images/imagedraw_pieslice_width_fill.png" + + # Act + draw.pieslice(BBOX1, 10, 260, fill="white", outline="blue", width=5) + + # Assert + self.assert_image_similar(im, Image.open(expected), 1) + def helper_point(self, points): # Arrange im = Image.new("RGB", (W, H)) @@ -343,6 +439,30 @@ class TestImageDraw(PillowTestCase): # Assert self.assert_image_similar(im, Image.open(expected), 1) + def test_rectangle_width(self): + # Arrange + im = Image.new("RGB", (W, H)) + draw = ImageDraw.Draw(im) + expected = "Tests/images/imagedraw_rectangle_width.png" + + # Act + draw.rectangle(BBOX1, outline="green", width=5) + + # Assert + self.assert_image_equal(im, Image.open(expected)) + + def test_rectangle_width_fill(self): + # Arrange + im = Image.new("RGB", (W, H)) + draw = ImageDraw.Draw(im) + expected = "Tests/images/imagedraw_rectangle_width_fill.png" + + # Act + draw.rectangle(BBOX1, fill="blue", outline="green", width=5) + + # Assert + self.assert_image_equal(im, Image.open(expected)) + def test_floodfill(self): red = ImageColor.getrgb("red") @@ -423,7 +543,7 @@ class TestImageDraw(PillowTestCase): for y in range(0, size[1]): if (x + y) % 2 == 0: img.putpixel((x, y), background2) - return (img, ImageDraw.Draw(img)) + return img, ImageDraw.Draw(img) def test_square(self): expected = Image.open(os.path.join(IMAGES_PATH, 'square.png')) @@ -642,7 +762,3 @@ class TestImageDraw(PillowTestCase): expected = ("Tests/images/imagedraw_outline" "_{}_{}.png".format(operation, mode)) self.assert_image_similar(im, Image.open(expected), 1) - - -if __name__ == '__main__': - unittest.main() diff --git a/Tests/test_imagedraw2.py b/Tests/test_imagedraw2.py index c5faeb616..97033c8a7 100644 --- a/Tests/test_imagedraw2.py +++ b/Tests/test_imagedraw2.py @@ -1,6 +1,6 @@ import os.path -from helper import PillowTestCase, hopper, unittest +from .helper import PillowTestCase, hopper, unittest from PIL import Image, ImageDraw2, features BLACK = (0, 0, 0) @@ -223,7 +223,3 @@ class TestImageDraw(PillowTestCase): # Assert self.assert_image_equal(im, im2) - - -if __name__ == "__main__": - unittest.main() diff --git a/Tests/test_imageenhance.py b/Tests/test_imageenhance.py index 54288f4db..0e4e8c4f3 100644 --- a/Tests/test_imageenhance.py +++ b/Tests/test_imageenhance.py @@ -1,4 +1,4 @@ -from helper import unittest, PillowTestCase, hopper +from .helper import PillowTestCase, hopper from PIL import Image from PIL import ImageEnhance @@ -48,7 +48,3 @@ class TestImageEnhance(PillowTestCase): self._check_alpha( getattr(ImageEnhance, op)(original).enhance(amount), original, op, amount) - - -if __name__ == '__main__': - unittest.main() diff --git a/Tests/test_imagefile.py b/Tests/test_imagefile.py index 837e81d30..5853fb28f 100644 --- a/Tests/test_imagefile.py +++ b/Tests/test_imagefile.py @@ -1,4 +1,4 @@ -from helper import unittest, PillowTestCase, hopper, fromstring, tostring +from .helper import PillowTestCase, hopper, fromstring, tostring from io import BytesIO @@ -144,7 +144,7 @@ class TestImageFile(PillowTestCase): class MockPyDecoder(ImageFile.PyDecoder): def decode(self, buffer): # eof - return (-1, 0) + return -1, 0 xoff, yoff, xsize, ysize = 10, 20, 100, 100 @@ -154,7 +154,7 @@ class MockImageFile(ImageFile.ImageFile): def _open(self): self.rawmode = 'RGBA' self.mode = 'RGBA' - self.size = (200, 200) + self._size = (200, 200) self.tile = [("MOCK", (xoff, yoff, xoff+xsize, yoff+ysize), 32, None)] @@ -204,7 +204,7 @@ class TestPyDecoder(PillowTestCase): im = MockImageFile(buf) im.tile = [("MOCK", (xoff, yoff, -10, yoff+ysize), 32, None)] - d = self.get_decoder() + self.get_decoder() self.assertRaises(ValueError, im.load) @@ -218,7 +218,7 @@ class TestPyDecoder(PillowTestCase): im.tile = [ ("MOCK", (xoff, yoff, xoff+xsize + 100, yoff+ysize), 32, None) ] - d = self.get_decoder() + self.get_decoder() self.assertRaises(ValueError, im.load) @@ -233,7 +233,3 @@ class TestPyDecoder(PillowTestCase): im = MockImageFile(buf) self.assertIsNone(im.format) self.assertIsNone(im.get_format_mimetype()) - - -if __name__ == '__main__': - unittest.main() diff --git a/Tests/test_imagefont.py b/Tests/test_imagefont.py index 82108638c..be8667211 100644 --- a/Tests/test_imagefont.py +++ b/Tests/test_imagefont.py @@ -1,5 +1,5 @@ # -*- coding: utf-8 -*- -from helper import unittest, PillowTestCase +from .helper import unittest, PillowTestCase from PIL import Image, ImageDraw, ImageFont, features from io import BytesIO @@ -529,7 +529,3 @@ class TestImageFont(PillowTestCase): @unittest.skipUnless(HAS_RAQM, "Raqm not Available") class TestImageFont_RaqmLayout(TestImageFont): LAYOUT_ENGINE = ImageFont.LAYOUT_RAQM - - -if __name__ == '__main__': - unittest.main() diff --git a/Tests/test_imagefont_bitmap.py b/Tests/test_imagefont_bitmap.py index 8376728a2..eb44957e4 100644 --- a/Tests/test_imagefont_bitmap.py +++ b/Tests/test_imagefont_bitmap.py @@ -1,4 +1,4 @@ -from helper import unittest, PillowTestCase +from .helper import unittest, PillowTestCase from PIL import Image, ImageFont, ImageDraw @@ -34,7 +34,3 @@ class TestImageFontBitmap(PillowTestCase): draw_outline.text((0, size_final[1] - size_outline[1]), text, fill=(0, 0, 0), font=font_outline) self.assert_image_similar(im_bitmap, im_outline, 20) - - -if __name__ == '__main__': - unittest.main() diff --git a/Tests/test_imagefontctl.py b/Tests/test_imagefontctl.py index 04432b14f..d23f6d86f 100644 --- a/Tests/test_imagefontctl.py +++ b/Tests/test_imagefontctl.py @@ -1,5 +1,5 @@ # -*- coding: utf-8 -*- -from helper import unittest, PillowTestCase +from .helper import unittest, PillowTestCase from PIL import Image, ImageDraw, ImageFont, features @@ -130,9 +130,3 @@ class TestImagecomplextext(PillowTestCase): target_img = Image.open(target) self.assert_image_similar(im, target_img, .5) - - -if __name__ == '__main__': - unittest.main() - -# End of file diff --git a/Tests/test_imagegrab.py b/Tests/test_imagegrab.py index 87a6956f6..a2e7a028d 100644 --- a/Tests/test_imagegrab.py +++ b/Tests/test_imagegrab.py @@ -1,17 +1,32 @@ -from helper import unittest, PillowTestCase, on_appveyor +from .helper import PillowTestCase import sys +import subprocess try: from PIL import ImageGrab class TestImageGrab(PillowTestCase): - @unittest.skipIf(on_appveyor(), "Test fails on appveyor") def test_grab(self): im = ImageGrab.grab() self.assert_image(im, im.mode, im.size) + def test_grabclipboard(self): + if sys.platform == "darwin": + subprocess.call(['screencapture', '-cx']) + else: + p = subprocess.Popen(['powershell', '-command', '-'], + stdin=subprocess.PIPE) + p.stdin.write(b'''[Reflection.Assembly]::LoadWithPartialName("System.Drawing") +[Reflection.Assembly]::LoadWithPartialName("System.Windows.Forms") +$bmp = New-Object Drawing.Bitmap 200, 200 +[Windows.Forms.Clipboard]::SetImage($bmp)''') + p.communicate() + + im = ImageGrab.grabclipboard() + self.assert_image(im, im.mode, im.size) + except ImportError: class TestImageGrab(PillowTestCase): def test_skip(self): @@ -38,7 +53,3 @@ class TestImageGrabImport(PillowTestCase): self.assertIsInstance(exception, ImportError) self.assertEqual(str(exception), "ImageGrab is macOS and Windows only") - - -if __name__ == '__main__': - unittest.main() diff --git a/Tests/test_imagemath.py b/Tests/test_imagemath.py index 2329b747e..8273b63c1 100644 --- a/Tests/test_imagemath.py +++ b/Tests/test_imagemath.py @@ -1,5 +1,5 @@ from __future__ import print_function -from helper import unittest, PillowTestCase +from .helper import PillowTestCase from PIL import Image from PIL import ImageMath @@ -18,7 +18,7 @@ A = Image.new("L", (1, 1), 1) B = Image.new("L", (1, 1), 2) Z = Image.new("L", (1, 1), 0) # Z for zero F = Image.new("F", (1, 1), 3) -I = Image.new("I", (1, 1), 4) +I = Image.new("I", (1, 1), 4) # noqa: E741 A2 = A.resize((2, 2)) B2 = B.resize((2, 2)) @@ -182,7 +182,3 @@ class TestImageMath(PillowTestCase): pixel(ImageMath.eval("notequal(B, A)", A=A, B=B)), "I 1") self.assertEqual( pixel(ImageMath.eval("notequal(A, Z)", A=A, Z=Z)), "I 1") - - -if __name__ == '__main__': - unittest.main() diff --git a/Tests/test_imagemorph.py b/Tests/test_imagemorph.py index dceadebf4..0cf15bd6c 100644 --- a/Tests/test_imagemorph.py +++ b/Tests/test_imagemorph.py @@ -1,5 +1,5 @@ # Test the ImageMorphology functionality -from helper import unittest, PillowTestCase, hopper +from .helper import PillowTestCase, hopper from PIL import Image, ImageMorph, _imagingmorph @@ -321,7 +321,3 @@ class MorphTests(PillowTestCase): # Should not raise _imagingmorph.match(bytes(lut), iml.im.id) - - -if __name__ == '__main__': - unittest.main() diff --git a/Tests/test_imageops.py b/Tests/test_imageops.py index 70b1659d9..c5e48c431 100644 --- a/Tests/test_imageops.py +++ b/Tests/test_imageops.py @@ -1,4 +1,4 @@ -from helper import unittest, PillowTestCase, hopper +from .helper import PillowTestCase, hopper from PIL import ImageOps from PIL import Image @@ -218,7 +218,3 @@ class TestImageOps(PillowTestCase): (0, 127, 0), threshold=1, msg='white test pixel incorrect') - - -if __name__ == '__main__': - unittest.main() diff --git a/Tests/test_imageops_usm.py b/Tests/test_imageops_usm.py index 20758e9f8..a867e5430 100644 --- a/Tests/test_imageops_usm.py +++ b/Tests/test_imageops_usm.py @@ -1,7 +1,6 @@ -from helper import unittest, PillowTestCase +from .helper import PillowTestCase from PIL import Image -from PIL import ImageOps from PIL import ImageFilter im = Image.open("Tests/images/hopper.ppm") @@ -10,31 +9,6 @@ snakes = Image.open("Tests/images/color_snakes.png") class TestImageOpsUsm(PillowTestCase): - def test_ops_api(self): - - i = self.assert_warning(DeprecationWarning, - ImageOps.gaussian_blur, im, 2.0) - self.assertEqual(i.mode, "RGB") - self.assertEqual(i.size, (128, 128)) - - i = self.assert_warning(DeprecationWarning, ImageOps.box_blur, im, 1) - self.assertEqual(i.mode, "RGB") - self.assertEqual(i.size, (128, 128)) - - i = self.assert_warning(DeprecationWarning, ImageOps.gblur, im, 2.0) - self.assertEqual(i.mode, "RGB") - self.assertEqual(i.size, (128, 128)) - - i = self.assert_warning(DeprecationWarning, - ImageOps.unsharp_mask, im, 2.0, 125, 8) - self.assertEqual(i.mode, "RGB") - self.assertEqual(i.size, (128, 128)) - - i = self.assert_warning(DeprecationWarning, - ImageOps.usm, im, 2.0, 125, 8) - self.assertEqual(i.mode, "RGB") - self.assertEqual(i.size, (128, 128)) - def test_filter_api(self): test_filter = ImageFilter.GaussianBlur(2.0) @@ -99,7 +73,3 @@ class TestImageOpsUsm(PillowTestCase): self.assertTrue(236 <= gp(8, 5)[2] <= 239) self.assertTrue(236 <= gp(8, 6)[2] <= 239) self.assertTrue(236 <= gp(8, 7)[1] <= 239) - - -if __name__ == '__main__': - unittest.main() diff --git a/Tests/test_imagepalette.py b/Tests/test_imagepalette.py index 3b7087d7a..e4b5b7f72 100644 --- a/Tests/test_imagepalette.py +++ b/Tests/test_imagepalette.py @@ -1,4 +1,4 @@ -from helper import unittest, PillowTestCase +from .helper import PillowTestCase from PIL import ImagePalette, Image @@ -134,7 +134,3 @@ class TestImagePalette(PillowTestCase): def test_invalid_palette(self): self.assertRaises(IOError, ImagePalette.load, "Tests/images/hopper.jpg") - - -if __name__ == '__main__': - unittest.main() diff --git a/Tests/test_imagepath.py b/Tests/test_imagepath.py index 8df71121d..8cf88b7c1 100644 --- a/Tests/test_imagepath.py +++ b/Tests/test_imagepath.py @@ -1,4 +1,4 @@ -from helper import unittest, PillowTestCase +from .helper import PillowTestCase from PIL import ImagePath, Image from PIL._util import py3 @@ -94,7 +94,3 @@ class evil: def __setitem__(self, i, x): self.corrupt[i] = struct.unpack("dd", x) - - -if __name__ == '__main__': - unittest.main() diff --git a/Tests/test_imageqt.py b/Tests/test_imageqt.py index cecb1b5ee..2ded37c09 100644 --- a/Tests/test_imageqt.py +++ b/Tests/test_imageqt.py @@ -1,4 +1,4 @@ -from helper import unittest, PillowTestCase, hopper +from .helper import PillowTestCase, hopper from PIL import ImageQt @@ -78,7 +78,3 @@ class TestImageQt(PillowQtTestCase, PillowTestCase): def test_image(self): for mode in ('1', 'RGB', 'RGBA', 'L', 'P'): ImageQt.ImageQt(hopper(mode)) - - -if __name__ == '__main__': - unittest.main() diff --git a/Tests/test_imagesequence.py b/Tests/test_imagesequence.py index 500a2e60a..9fbf3fed8 100644 --- a/Tests/test_imagesequence.py +++ b/Tests/test_imagesequence.py @@ -1,4 +1,4 @@ -from helper import unittest, PillowTestCase, hopper +from .helper import PillowTestCase, hopper from PIL import Image, ImageSequence, TiffImagePlugin @@ -69,7 +69,3 @@ class TestImageSequence(PillowTestCase): im.seek(0) color2 = im.getpalette()[0:3] self.assertEqual(color1, color2) - - -if __name__ == '__main__': - unittest.main() diff --git a/Tests/test_imageshow.py b/Tests/test_imageshow.py index da91e35c7..899c057d6 100644 --- a/Tests/test_imageshow.py +++ b/Tests/test_imageshow.py @@ -1,4 +1,4 @@ -from helper import unittest, PillowTestCase, hopper +from .helper import PillowTestCase, hopper from PIL import Image from PIL import ImageShow @@ -14,6 +14,9 @@ class TestImageShow(PillowTestCase): # Test registering a viewer that is not a class ImageShow.register("not a class") + # Restore original state + ImageShow._viewers.pop() + def test_show(self): class TestViewer: methodCalled = False @@ -28,6 +31,9 @@ class TestImageShow(PillowTestCase): self.assertTrue(ImageShow.show(im)) self.assertTrue(viewer.methodCalled) + # Restore original state + ImageShow._viewers.pop(0) + def test_viewer(self): viewer = ImageShow.Viewer() @@ -35,6 +41,6 @@ class TestImageShow(PillowTestCase): self.assertRaises(NotImplementedError, viewer.get_command, None) - -if __name__ == '__main__': - unittest.main() + def test_viewers(self): + for viewer in ImageShow._viewers: + viewer.get_command('test.jpg') diff --git a/Tests/test_imagestat.py b/Tests/test_imagestat.py index 77eb0aac1..c2580a1b1 100644 --- a/Tests/test_imagestat.py +++ b/Tests/test_imagestat.py @@ -1,4 +1,4 @@ -from helper import unittest, PillowTestCase, hopper +from .helper import PillowTestCase, hopper from PIL import Image from PIL import ImageStat @@ -55,7 +55,3 @@ class TestImageStat(PillowTestCase): self.assertEqual(st.rms[0], 128) self.assertEqual(st.var[0], 0) self.assertEqual(st.stddev[0], 0) - - -if __name__ == '__main__': - unittest.main() diff --git a/Tests/test_imagetk.py b/Tests/test_imagetk.py index 14ce74eb1..a6a4dd4ea 100644 --- a/Tests/test_imagetk.py +++ b/Tests/test_imagetk.py @@ -1,4 +1,4 @@ -from helper import unittest, PillowTestCase, hopper +from .helper import unittest, PillowTestCase, hopper from PIL import Image from PIL._util import py3 @@ -11,23 +11,22 @@ try: import Tkinter as tk dir(ImageTk) HAS_TK = True -except (OSError, ImportError) as v: +except (OSError, ImportError): # Skipped via setUp() HAS_TK = False TK_MODES = ('1', 'L', 'P', 'RGB', 'RGBA') +@unittest.skipIf(not HAS_TK, "Tk not installed") class TestImageTk(PillowTestCase): def setUp(self): - if not HAS_TK: - self.skipTest("Tk not installed") try: # setup tk tk.Frame() # root = tk.Tk() - except (tk.TclError) as v: + except tk.TclError as v: self.skipTest("TCL Error: %s" % v) def test_kw(self): @@ -88,7 +87,3 @@ class TestImageTk(PillowTestCase): # reloaded = ImageTk.getimage(im_tk) # self.assert_image_equal(reloaded, im) - - -if __name__ == '__main__': - unittest.main() diff --git a/Tests/test_imagewin.py b/Tests/test_imagewin.py index 70bf28247..16d681f2c 100644 --- a/Tests/test_imagewin.py +++ b/Tests/test_imagewin.py @@ -1,4 +1,4 @@ -from helper import unittest, PillowTestCase, hopper +from .helper import unittest, PillowTestCase, hopper from PIL import ImageWin import sys @@ -106,7 +106,3 @@ class TestImageWinDib(PillowTestCase): # Assert # Confirm they're the same self.assertEqual(dib1.tobytes(), dib2.tobytes()) - - -if __name__ == '__main__': - unittest.main() diff --git a/Tests/test_imagewin_pointers.py b/Tests/test_imagewin_pointers.py index dc78b655c..64f921916 100644 --- a/Tests/test_imagewin_pointers.py +++ b/Tests/test_imagewin_pointers.py @@ -1,4 +1,4 @@ -from helper import unittest, PillowTestCase, hopper +from .helper import PillowTestCase, hopper from PIL import Image, ImageWin import sys @@ -107,6 +107,3 @@ if sys.platform.startswith('win32'): DeleteDC(hdc) Image.open(BytesIO(bitmap)).save(opath) - -if __name__ == '__main__': - unittest.main() diff --git a/Tests/test_lib_image.py b/Tests/test_lib_image.py index aefee2e08..466c43f88 100644 --- a/Tests/test_lib_image.py +++ b/Tests/test_lib_image.py @@ -1,4 +1,4 @@ -from helper import unittest, PillowTestCase +from .helper import unittest, PillowTestCase from PIL import Image diff --git a/Tests/test_lib_pack.py b/Tests/test_lib_pack.py index 2fb7da281..543d151ac 100644 --- a/Tests/test_lib_pack.py +++ b/Tests/test_lib_pack.py @@ -1,6 +1,6 @@ import sys -from helper import unittest, PillowTestCase +from .helper import PillowTestCase from PIL import Image @@ -17,7 +17,7 @@ class TestLibPack(PillowTestCase): for x, pixel in enumerate(pixels): im.putpixel((x, 0), pixel) - if isinstance(data, (int)): + if isinstance(data, int): data_len = data * len(pixels) data = bytes(bytearray(range(1, data_len + 1))) @@ -217,7 +217,7 @@ class TestLibUnpack(PillowTestCase): """ data - either raw bytes with data or just number of bytes in rawmode. """ - if isinstance(data, (int)): + if isinstance(data, int): data_len = data * len(pixels) data = bytes(bytearray(range(1, data_len + 1))) @@ -478,10 +478,6 @@ class TestLibUnpack(PillowTestCase): "YCbCr", "YCbCrK", 4, (1, 2, 3), (5, 6, 7), (9, 10, 11)) self.assert_unpack( "YCbCr", "YCbCrX", 4, (1, 2, 3), (5, 6, 7), (9, 10, 11)) - self.assert_unpack( - "YCbCr", "YCbCrXX", 5, (1, 2, 3), (6, 7, 8), (11, 12, 13)) - self.assert_unpack( - "YCbCr", "YCbCrXXX", 6, (1, 2, 3), (7, 8, 9), (13, 14, 15)) def test_LAB(self): self.assert_unpack( @@ -624,7 +620,3 @@ class TestLibUnpack(PillowTestCase): self.assertRaises(ValueError, self.assert_unpack, "L", "L", 0, 0) self.assertRaises(ValueError, self.assert_unpack, "RGB", "RGB", 2, 0) self.assertRaises(ValueError, self.assert_unpack, "CMYK", "CMYK", 2, 0) - - -if __name__ == '__main__': - unittest.main() diff --git a/Tests/test_locale.py b/Tests/test_locale.py index 5aef8427b..d40019e59 100644 --- a/Tests/test_locale.py +++ b/Tests/test_locale.py @@ -1,5 +1,5 @@ from __future__ import print_function -from helper import unittest, PillowTestCase +from .helper import unittest, PillowTestCase from PIL import Image @@ -32,7 +32,3 @@ class TestLocale(PillowTestCase): except locale.Error: unittest.skip('Polish locale not available') Image.open(path) - - -if __name__ == '__main__': - unittest.main() diff --git a/Tests/test_map.py b/Tests/test_map.py index 8e3916d27..2eeb1fc5f 100644 --- a/Tests/test_map.py +++ b/Tests/test_map.py @@ -1,4 +1,4 @@ -from helper import PillowTestCase, unittest +from .helper import PillowTestCase, unittest import sys from PIL import Image @@ -23,7 +23,3 @@ class TestMap(PillowTestCase): im.load() Image.MAX_IMAGE_PIXELS = max_pixels - - -if __name__ == '__main__': - unittest.main() diff --git a/Tests/test_mode_i16.py b/Tests/test_mode_i16.py index d51847199..80730a312 100644 --- a/Tests/test_mode_i16.py +++ b/Tests/test_mode_i16.py @@ -1,4 +1,4 @@ -from helper import unittest, PillowTestCase, hopper +from .helper import PillowTestCase, hopper from PIL import Image @@ -105,7 +105,3 @@ class TestModeI16(PillowTestCase): self.verify(im.convert("I;16B")) self.verify(im.convert("I;16B").convert("L")) self.verify(im.convert("I;16B").convert("I")) - - -if __name__ == '__main__': - unittest.main() diff --git a/Tests/test_numpy.py b/Tests/test_numpy.py index 03643ac1e..c9c3b0f1e 100644 --- a/Tests/test_numpy.py +++ b/Tests/test_numpy.py @@ -1,6 +1,6 @@ from __future__ import print_function -from helper import PillowTestCase, hopper, unittest +from .helper import PillowTestCase, hopper, unittest from PIL import Image try: @@ -208,7 +208,3 @@ class TestNumpy(PillowTestCase): # Act/Assert self.assert_warning(None, lambda: array(im)) - - -if __name__ == '__main__': - unittest.main() diff --git a/Tests/test_pdfparser.py b/Tests/test_pdfparser.py index b7373842e..da69d258d 100644 --- a/Tests/test_pdfparser.py +++ b/Tests/test_pdfparser.py @@ -1,8 +1,9 @@ -from helper import unittest, PillowTestCase +from .helper import PillowTestCase from PIL.PdfParser import IndirectObjectDef, IndirectReference, PdfBinary, \ PdfDict, PdfFormatError, PdfName, PdfParser, \ PdfStream, decode_text, encode_text, pdf_repr +import time class TestPdfParser(PillowTestCase): @@ -80,6 +81,19 @@ class TestPdfParser(PillowTestCase): self.assertIsInstance(s, PdfStream) self.assertEqual(s.dictionary.Name, "value") self.assertEqual(s.decode(), b"abcde") + for name in ["CreationDate", "ModDate"]: + for date, value in { + b"20180729214124": "20180729214124", + b"D:20180729214124": "20180729214124", + b"D:2018072921": "20180729210000", + b"D:20180729214124Z": "20180729214124", + b"D:20180729214124+08'00'": "20180729134124", + b"D:20180729214124-05'00'": "20180730024124" + }.items(): + d = PdfParser.get_value( + b"<>", 0)[0] + self.assertEqual( + time.strftime("%Y%m%d%H%M%S", getattr(d, name)), value) def test_pdf_repr(self): self.assertEqual(bytes(IndirectReference(1, 2)), b"1 2 R") @@ -108,7 +122,3 @@ class TestPdfParser(PillowTestCase): self.assertEqual(pdf_repr([123, True, {"a": PdfName(b"b")}]), b"[ 123 true <<\n/a /b\n>> ]") self.assertEqual(pdf_repr(PdfBinary(b"\x90\x1F\xA0")), b"<901FA0>") - - -if __name__ == '__main__': - unittest.main() diff --git a/Tests/test_pickle.py b/Tests/test_pickle.py index bdfd3582d..46958c085 100644 --- a/Tests/test_pickle.py +++ b/Tests/test_pickle.py @@ -1,4 +1,4 @@ -from helper import unittest, PillowTestCase +from .helper import PillowTestCase from PIL import Image @@ -91,7 +91,3 @@ class TestPickle(PillowTestCase): for protocol in range(0, cPickle.HIGHEST_PROTOCOL + 1): self.helper_pickle_string(cPickle, protocol, mode="L") self.helper_pickle_file(cPickle, protocol, mode="L") - - -if __name__ == '__main__': - unittest.main() diff --git a/Tests/test_psdraw.py b/Tests/test_psdraw.py index 17fa3662b..73ef5e2b2 100644 --- a/Tests/test_psdraw.py +++ b/Tests/test_psdraw.py @@ -1,4 +1,4 @@ -from helper import unittest, PillowTestCase +from .helper import PillowTestCase from PIL import Image, PSDraw import os @@ -61,7 +61,3 @@ class TestPsDraw(PillowTestCase): sys.stdout = old_stdout self.assertNotEqual(mystdout.getvalue(), "") - - -if __name__ == '__main__': - unittest.main() diff --git a/Tests/test_pyroma.py b/Tests/test_pyroma.py index cf5fc361f..3c4e3d9a2 100644 --- a/Tests/test_pyroma.py +++ b/Tests/test_pyroma.py @@ -1,23 +1,16 @@ -from helper import unittest, PillowTestCase +from .helper import unittest, PillowTestCase from PIL import __version__ try: import pyroma except ImportError: - # Skip via setUp() - pass + pyroma = None +@unittest.skipIf(pyroma is None, "Pyroma is not installed") class TestPyroma(PillowTestCase): - def setUp(self): - try: - import pyroma - assert pyroma # Ignore warning - except ImportError: - self.skipTest("ImportError") - def test_pyroma(self): # Arrange data = pyroma.projectdata.get_data(".") @@ -35,7 +28,3 @@ class TestPyroma(PillowTestCase): else: # Should have a perfect score self.assertEqual(rating, (10, [])) - - -if __name__ == '__main__': - unittest.main() diff --git a/Tests/test_qt_image_fromqpixmap.py b/Tests/test_qt_image_fromqpixmap.py index 0127f77e2..358f1573d 100644 --- a/Tests/test_qt_image_fromqpixmap.py +++ b/Tests/test_qt_image_fromqpixmap.py @@ -1,5 +1,5 @@ -from helper import unittest, PillowTestCase, hopper -from test_imageqt import PillowQPixmapTestCase +from .helper import PillowTestCase, hopper +from .test_imageqt import PillowQPixmapTestCase from PIL import ImageQt @@ -25,7 +25,3 @@ class TestFromQPixmap(PillowQPixmapTestCase, PillowTestCase): def test_sanity_p(self): self.roundtrip(hopper('P')) - - -if __name__ == '__main__': - unittest.main() diff --git a/Tests/test_qt_image_toqimage.py b/Tests/test_qt_image_toqimage.py index 28871d201..c1aa64b57 100644 --- a/Tests/test_qt_image_toqimage.py +++ b/Tests/test_qt_image_toqimage.py @@ -1,5 +1,5 @@ -from helper import unittest, PillowTestCase, hopper -from test_imageqt import PillowQtTestCase +from .helper import PillowTestCase, hopper +from .test_imageqt import PillowQtTestCase from PIL import ImageQt, Image @@ -70,8 +70,8 @@ class TestToQImage(PillowQtTestCase, PillowTestCase): def test_segfault(self): app = QApplication([]) ex = Example() - assert(app) # Silence warning - assert(ex) # Silence warning + assert app # Silence warning + assert ex # Silence warning if ImageQt.qt_is_installed: @@ -86,12 +86,8 @@ if ImageQt.qt_is_installed: pixmap1 = QtGui.QPixmap.fromImage(qimage) - hbox = QHBoxLayout(self) + QHBoxLayout(self) # hbox lbl = QLabel(self) # Segfault in the problem lbl.setPixmap(pixmap1.copy()) - - -if __name__ == '__main__': - unittest.main() diff --git a/Tests/test_qt_image_toqpixmap.py b/Tests/test_qt_image_toqpixmap.py index 5de7810f5..9bb7183b7 100644 --- a/Tests/test_qt_image_toqpixmap.py +++ b/Tests/test_qt_image_toqpixmap.py @@ -1,5 +1,5 @@ -from helper import unittest, PillowTestCase, hopper -from test_imageqt import PillowQPixmapTestCase +from .helper import PillowTestCase, hopper +from .test_imageqt import PillowQPixmapTestCase from PIL import ImageQt @@ -19,7 +19,3 @@ class TestToQPixmap(PillowQPixmapTestCase, PillowTestCase): # Test saving the file tempfile = self.tempfile('temp_{}.png'.format(mode)) data.save(tempfile) - - -if __name__ == '__main__': - unittest.main() diff --git a/Tests/test_shell_injection.py b/Tests/test_shell_injection.py index 9e489f77c..77fd67f01 100644 --- a/Tests/test_shell_injection.py +++ b/Tests/test_shell_injection.py @@ -1,5 +1,5 @@ -from helper import unittest, PillowTestCase -from helper import djpeg_available, cjpeg_available, netpbm_available +from .helper import unittest, PillowTestCase +from .helper import djpeg_available, cjpeg_available, netpbm_available import sys import shutil @@ -51,7 +51,3 @@ class TestShellInjection(PillowTestCase): def test_save_netpbm_filename_l_mode(self): im = Image.open(TEST_GIF).convert("L") self.assert_save_filename_check(im, GifImagePlugin._save_netpbm) - - -if __name__ == '__main__': - unittest.main() diff --git a/Tests/test_tiff_ifdrational.py b/Tests/test_tiff_ifdrational.py index bd767b931..fae4d7ed6 100644 --- a/Tests/test_tiff_ifdrational.py +++ b/Tests/test_tiff_ifdrational.py @@ -1,4 +1,4 @@ -from helper import unittest, PillowTestCase, hopper +from .helper import PillowTestCase, hopper from PIL import TiffImagePlugin, Image from PIL.TiffImagePlugin import IFDRational @@ -30,7 +30,7 @@ class Test_IFDRational(PillowTestCase): self._test_equal(1, 2, IFDRational(1, 2)) def test_nonetype(self): - " Fails if the _delegate function doesn't return a valid function" + # Fails if the _delegate function doesn't return a valid function xres = IFDRational(72) yres = IFDRational(72) @@ -58,7 +58,3 @@ class Test_IFDRational(PillowTestCase): reloaded = Image.open(out) self.assertEqual(float(IFDRational(301, 1)), float(reloaded.tag_v2[282])) - - -if __name__ == '__main__': - unittest.main() diff --git a/Tests/test_uploader.py b/Tests/test_uploader.py index e157867cb..e40e7fb86 100644 --- a/Tests/test_uploader.py +++ b/Tests/test_uploader.py @@ -1,4 +1,4 @@ -from helper import unittest, PillowTestCase, hopper +from .helper import PillowTestCase, hopper class TestUploader(PillowTestCase): @@ -11,7 +11,3 @@ class TestUploader(PillowTestCase): result = hopper('P').convert('RGB') target = hopper('RGB') self.assert_image_similar(result, target, 0) - - -if __name__ == '__main__': - unittest.main() diff --git a/Tests/test_util.py b/Tests/test_util.py index 2316d3d65..4471b75bd 100644 --- a/Tests/test_util.py +++ b/Tests/test_util.py @@ -1,4 +1,4 @@ -from helper import unittest, PillowTestCase +from .helper import PillowTestCase from PIL import _util @@ -30,7 +30,7 @@ class TestUtil(PillowTestCase): fp = "filename.ext" # Act - it_is = _util.isStringType(fp) + it_is = _util.isPath(fp) # Assert self.assertTrue(it_is) @@ -74,7 +74,3 @@ class TestUtil(PillowTestCase): # Assert self.assertRaises(ValueError, lambda: thing.some_attr) - - -if __name__ == '__main__': - unittest.main() diff --git a/Tests/test_webp_leaks.py b/Tests/test_webp_leaks.py index c542ed811..03befd507 100644 --- a/Tests/test_webp_leaks.py +++ b/Tests/test_webp_leaks.py @@ -1,4 +1,4 @@ -from helper import unittest, PillowLeakTestCase +from .helper import unittest, PillowLeakTestCase from PIL import Image, features from io import BytesIO @@ -20,7 +20,3 @@ class TestWebPLeaks(PillowLeakTestCase): im.load() self._test_leak(core) - - -if __name__ == '__main__': - unittest.main() diff --git a/Tests/versions.py b/Tests/versions.py index abc1a3be9..835865b37 100644 --- a/Tests/versions.py +++ b/Tests/versions.py @@ -10,6 +10,7 @@ def version(module, version): version(Image, "jpeglib") version(Image, "zlib") +version(Image, "libtiff") try: from PIL import ImageFont diff --git a/depends/README.rst b/depends/README.rst index 779e956f4..069d2b81f 100644 --- a/depends/README.rst +++ b/depends/README.rst @@ -1,9 +1,12 @@ Depends ======= -``install_openjpeg.sh``, ``install_webp.sh`` and ``install_imagequant.sh`` can -be used to download, build & install non-packaged dependencies; useful for -testing with Travis CI. +``install_openjpeg.sh``, ``install_webp.sh``, ``install_imagequant.sh``, +``install_raqm.sh`` and ``install_raqm_cmake.sh`` can be used to download, +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 diff --git a/depends/diffcover-install.sh b/depends/diffcover-install.sh index 850d368f8..a0b462b56 100755 --- a/depends/diffcover-install.sh +++ b/depends/diffcover-install.sh @@ -1,7 +1,8 @@ +#!/usr/bin/env bash # Fetch the remote master branch before running diff-cover on Travis CI. # https://github.com/Bachmann1234/diff-cover#troubleshooting git fetch origin master:refs/remotes/origin/master # CFLAGS=-O0 means build with no optimisation. # Makes build much quicker for lxml and other dependencies. -time CFLAGS=-O0 pip install --use-wheel diff_cover +time CFLAGS=-O0 pip install diff_cover diff --git a/depends/diffcover-run.sh b/depends/diffcover-run.sh index 02efab6ae..b007494e9 100755 --- a/depends/diffcover-run.sh +++ b/depends/diffcover-run.sh @@ -1,4 +1,5 @@ +#!/usr/bin/env bash coverage xml diff-cover coverage.xml diff-quality --violation=pyflakes -diff-quality --violation=pep8 +diff-quality --violation=pycodestyle diff --git a/depends/download-and-extract.sh b/depends/download-and-extract.sh index 7cc905e85..d9608e782 100755 --- a/depends/download-and-extract.sh +++ b/depends/download-and-extract.sh @@ -1,5 +1,5 @@ #!/bin/sh -# Usage: ./download-and-extract.sh something.tar.gz https://example.com/something.tar.gz +# Usage: ./download-and-extract.sh something https://example.com/something.tar.gz archive=$1 url=$2 diff --git a/depends/install_imagequant.sh b/depends/install_imagequant.sh index 56dfabf8f..e284bb0e4 100755 --- a/depends/install_imagequant.sh +++ b/depends/install_imagequant.sh @@ -1,7 +1,7 @@ #!/bin/bash # install libimagequant -archive=libimagequant-2.12.1 +archive=libimagequant-2.12.2 ./download-and-extract.sh $archive https://raw.githubusercontent.com/python-pillow/pillow-depends/master/$archive.tar.gz diff --git a/depends/install_webp.sh b/depends/install_webp.sh index e27fedc51..ead5637ee 100755 --- a/depends/install_webp.sh +++ b/depends/install_webp.sh @@ -1,7 +1,7 @@ #!/bin/bash # install webp -archive=libwebp-1.0.0 +archive=libwebp-1.0.2 ./download-and-extract.sh $archive https://raw.githubusercontent.com/python-pillow/pillow-depends/master/$archive.tar.gz diff --git a/docs/COPYING b/docs/COPYING index 754527885..a1e258129 100644 --- a/docs/COPYING +++ b/docs/COPYING @@ -5,7 +5,7 @@ The Python Imaging Library (PIL) is Pillow is the friendly PIL fork. It is - Copyright © 2010-2018 by Alex Clark and contributors + Copyright © 2010-2019 by Alex Clark and contributors Like PIL, Pillow is licensed under the open source PIL Software License: diff --git a/docs/conf.py b/docs/conf.py index ba0a552b3..2c25588a0 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -15,12 +15,16 @@ # If extensions (or modules to document with autodoc) are in another directory, # add these directories to sys.path here. If the directory is relative to the # documentation root, use os.path.abspath to make it absolute, like shown here. -#sys.path.insert(0, os.path.abspath('.')) +# sys.path.insert(0, os.path.abspath('.')) + +import sphinx_rtd_theme + +import PIL # -- General configuration ------------------------------------------------ # If your documentation needs a minimal Sphinx version, state it here. -#needs_sphinx = '1.0' +# needs_sphinx = '1.0' # Add any Sphinx extension module names here, as strings. They can be # extensions coming with Sphinx (named 'sphinx.ext.*') or your custom @@ -37,14 +41,14 @@ templates_path = ['_templates'] source_suffix = '.rst' # The encoding of source files. -#source_encoding = 'utf-8-sig' +# source_encoding = 'utf-8-sig' # The master toctree document. master_doc = 'index' # General information about the project. project = u'Pillow (PIL Fork)' -copyright = u'1995-2011 Fredrik Lundh, 2010-2018 Alex Clark and Contributors' +copyright = u'1995-2011 Fredrik Lundh, 2010-2019 Alex Clark and Contributors' author = u'Fredrik Lundh, Alex Clark and Contributors' # The version info for the project you're documenting, acts as replacement for @@ -52,7 +56,6 @@ author = u'Fredrik Lundh, Alex Clark and Contributors' # built documents. # # The short X.Y version. -import PIL version = PIL.__version__ # The full version, including alpha/beta/rc tags. release = PIL.__version__ @@ -66,9 +69,9 @@ language = None # There are two options for replacing |today|: either, you set today to some # non-false value, then it is used: -#today = '' +# today = '' # Else, today_fmt is used as the format for a strftime call. -#today_fmt = '%B %d, %Y' +# today_fmt = '%B %d, %Y' # List of patterns, relative to source directory, that match files and # directories to ignore when looking for source files. @@ -76,27 +79,27 @@ exclude_patterns = ['_build'] # The reST default role (used for this markup: `text`) to use for all # documents. -#default_role = None +# default_role = None # If true, '()' will be appended to :func: etc. cross-reference text. -#add_function_parentheses = True +# add_function_parentheses = True # If true, the current module name will be prepended to all description # unit titles (such as .. function::). -#add_module_names = True +# add_module_names = True # If true, sectionauthor and moduleauthor directives will be shown in the # output. They are ignored by default. -#show_authors = False +# show_authors = False # The name of the Pygments (syntax highlighting) style to use. pygments_style = 'sphinx' # A list of ignored prefixes for module index sorting. -#modindex_common_prefix = [] +# modindex_common_prefix = [] # If true, keep warnings as "system message" paragraphs in the built documents. -#keep_warnings = False +# keep_warnings = False # If true, `todo` and `todoList` produce output, else they produce nothing. todo_include_todos = False @@ -107,98 +110,97 @@ todo_include_todos = False # The theme to use for HTML and HTML Help pages. See the documentation for # a list of builtin themes. -import sphinx_rtd_theme html_theme = "sphinx_rtd_theme" html_theme_path = [sphinx_rtd_theme.get_html_theme_path()] # Theme options are theme-specific and customize the look and feel of a theme # further. For a list of options available for each theme, see the # documentation. -#html_theme_options = {} +# html_theme_options = {} # Add any paths that contain custom themes here, relative to this directory. -#html_theme_path = [] +# html_theme_path = [] # The name for this set of Sphinx documents. If None, it defaults to # " v documentation". -#html_title = None +# html_title = None # A shorter title for the navigation bar. Default is the same as html_title. -#html_short_title = None +# html_short_title = None # The name of an image file (relative to this directory) to place at the top # of the sidebar. -#html_logo = None +# html_logo = None # The name of an image file (within the static path) to use as favicon of the # docs. This file should be a Windows icon file (.ico) being 16x16 or 32x32 # pixels large. -#html_favicon = None +# html_favicon = None # Add any paths that contain custom static files (such as style sheets) here, # relative to this directory. They are copied after the builtin static files, # so a file named "default.css" will overwrite the builtin "default.css". -html_static_path = ['_static'] +html_static_path = ['_static', 'resources'] # Add any extra paths that contain custom files (such as robots.txt or # .htaccess) here, relative to this directory. These files are copied # directly to the root of the documentation. -#html_extra_path = [] +# html_extra_path = [] # If not '', a 'Last updated on:' timestamp is inserted at every page bottom, # using the given strftime format. -#html_last_updated_fmt = '%b %d, %Y' +# html_last_updated_fmt = '%b %d, %Y' # If true, SmartyPants will be used to convert quotes and dashes to # typographically correct entities. -#html_use_smartypants = True +# html_use_smartypants = True # Custom sidebar templates, maps document names to template names. -#html_sidebars = {} +# html_sidebars = {} # Additional templates that should be rendered to pages, maps page names to # template names. -#html_additional_pages = {} +# html_additional_pages = {} # If false, no module index is generated. -#html_domain_indices = True +# html_domain_indices = True # If false, no index is generated. -#html_use_index = True +# html_use_index = True # If true, the index is split into individual pages for each letter. -#html_split_index = False +# html_split_index = False # If true, links to the reST sources are added to the pages. -#html_show_sourcelink = True +# html_show_sourcelink = True # If true, "Created using Sphinx" is shown in the HTML footer. Default is True. -#html_show_sphinx = True +# html_show_sphinx = True # If true, "(C) Copyright ..." is shown in the HTML footer. Default is True. -#html_show_copyright = True +# html_show_copyright = True # If true, an OpenSearch description file will be output, and all pages will # contain a tag referring to it. The value of this option must be the # base URL from which the finished HTML is served. -#html_use_opensearch = '' +# html_use_opensearch = '' # This is the file name suffix for HTML files (e.g. ".xhtml"). -#html_file_suffix = None +# html_file_suffix = None # Language to be used for generating the HTML full-text search index. # Sphinx supports the following languages: # 'da', 'de', 'en', 'es', 'fi', 'fr', 'hu', 'it', 'ja' # 'nl', 'no', 'pt', 'ro', 'ru', 'sv', 'tr' -#html_search_language = 'en' +# html_search_language = 'en' # A dictionary with options for the search language support, empty by default. # Now only 'ja' uses this config value -#html_search_options = {'type': 'default'} +# html_search_options = {'type': 'default'} # The name of a javascript file (relative to the configuration directory) that # implements a search results scorer. If empty, the default will be used. -#html_search_scorer = 'scorer.js' +# html_search_scorer = 'scorer.js' # Output file base name for HTML help builder. htmlhelp_basename = 'PillowPILForkdoc' @@ -206,17 +208,17 @@ htmlhelp_basename = 'PillowPILForkdoc' # -- Options for LaTeX output --------------------------------------------- latex_elements = { -# The paper size ('letterpaper' or 'a4paper'). -#'papersize': 'letterpaper', + # The paper size ('letterpaper' or 'a4paper'). + # 'papersize': 'letterpaper', -# The font size ('10pt', '11pt' or '12pt'). -#'pointsize': '10pt', + # The font size ('10pt', '11pt' or '12pt'). + # 'pointsize': '10pt', -# Additional stuff for the LaTeX preamble. -#'preamble': '', + # Additional stuff for the LaTeX preamble. + # 'preamble': '', -# Latex figure (float) alignment -#'figure_align': 'htbp', + # Latex figure (float) alignment + # 'figure_align': 'htbp', } # Grouping the document tree into LaTeX files. List of tuples @@ -229,23 +231,23 @@ latex_documents = [ # The name of an image file (relative to this directory) to place at the top of # the title page. -#latex_logo = None +# latex_logo = None # For "manual" documents, if this is true, then toplevel headings are parts, # not chapters. -#latex_use_parts = False +# latex_use_parts = False # If true, show page references after internal links. -#latex_show_pagerefs = False +# latex_show_pagerefs = False # If true, show URL addresses after external links. -#latex_show_urls = False +# latex_show_urls = False # Documents to append as an appendix to all manuals. -#latex_appendices = [] +# latex_appendices = [] # If false, no module index is generated. -#latex_domain_indices = True +# latex_domain_indices = True # -- Options for manual page output --------------------------------------- @@ -258,7 +260,7 @@ man_pages = [ ] # If true, show URL addresses after external links. -#man_show_urls = False +# man_show_urls = False # -- Options for Texinfo output ------------------------------------------- @@ -268,18 +270,23 @@ man_pages = [ # dir menu entry, description, category) texinfo_documents = [ (master_doc, 'PillowPILFork', u'Pillow (PIL Fork) Documentation', - author, 'PillowPILFork', 'Pillow is the friendly PIL fork by Alex Clark and Contributors.', + author, 'PillowPILFork', + 'Pillow is the friendly PIL fork by Alex Clark and Contributors.', 'Miscellaneous'), ] # Documents to append as an appendix to all manuals. -#texinfo_appendices = [] +# texinfo_appendices = [] # If false, no module index is generated. -#texinfo_domain_indices = True +# texinfo_domain_indices = True # How to display URL addresses: 'footnote', 'no', or 'inline'. -#texinfo_show_urls = 'footnote' +# texinfo_show_urls = 'footnote' # If true, do not generate a @detailmenu in the "Top" node's menu. -#texinfo_no_detailmenu = False +# texinfo_no_detailmenu = False + + +def setup(app): + app.add_javascript('js/script.js') diff --git a/docs/deprecations.rst b/docs/deprecations.rst new file mode 100644 index 000000000..b8131ac05 --- /dev/null +++ b/docs/deprecations.rst @@ -0,0 +1,94 @@ +.. _deprecations: + +Deprecations and removals +========================= + +This page lists Pillow features that are deprecated, or have been removed in +past major releases, and gives the alternatives to use instead. + +Deprecated features +------------------- + +Below are features which are considered deprecated. Where appropriate, +a ``DeprecationWarning`` is issued. + +PIL.*ImagePlugin.__version__ attributes +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +.. deprecated:: 6.0.0 + +The version constants of individual plugins have been deprecated and will be removed in +a future version. Use ``PIL.__version__`` instead. + +=============================== ================================= ================================== +Deprecated Deprecated Deprecated +=============================== ================================= ================================== +``BmpImagePlugin.__version__`` ``Jpeg2KImagePlugin.__version__`` ``PngImagePlugin.__version__`` +``CurImagePlugin.__version__`` ``JpegImagePlugin.__version__`` ``PpmImagePlugin.__version__`` +``DcxImagePlugin.__version__`` ``McIdasImagePlugin.__version__`` ``PsdImagePlugin.__version__`` +``EpsImagePlugin.__version__`` ``MicImagePlugin.__version__`` ``SgiImagePlugin.__version__`` +``FliImagePlugin.__version__`` ``MpegImagePlugin.__version__`` ``SunImagePlugin.__version__`` +``FpxImagePlugin.__version__`` ``MpoImagePlugin.__version__`` ``TgaImagePlugin.__version__`` +``GdImageFile.__version__`` ``MspImagePlugin.__version__`` ``TiffImagePlugin.__version__`` +``GifImagePlugin.__version__`` ``PalmImagePlugin.__version__`` ``WmfImagePlugin.__version__`` +``IcoImagePlugin.__version__`` ``PcdImagePlugin.__version__`` ``XbmImagePlugin.__version__`` +``ImImagePlugin.__version__`` ``PcxImagePlugin.__version__`` ``XpmImagePlugin.__version__`` +``ImtImagePlugin.__version__`` ``PdfImagePlugin.__version__`` ``XVThumbImagePlugin.__version__`` +``IptcImagePlugin.__version__`` ``PixarImagePlugin.__version__`` +=============================== ================================= ================================== + +Setting the size of TIFF images +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +.. deprecated:: 5.3.0 + +Setting the image size of a TIFF image (eg. ``im.size = (256, 256)``) issues +a ``DeprecationWarning``: + +.. code-block:: none + + Setting the size of a TIFF image directly is deprecated, and will + be removed in a future version. Use the resize method instead. + +PILLOW_VERSION and VERSION constants +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +.. deprecated:: 5.2.0 + +Two version constants – ``VERSION`` (the old PIL version, always 1.1.7) and +``PILLOW_VERSION`` – have been deprecated and will be removed in the next +major release. Use ``__version__`` instead. + +Removed features +---------------- + +Deprecated features are only removed in major releases after an appropriate +period of deprecation has passed. + +Undocumented ImageOps functions +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +*Removed in version 6.0.0.* + +Several undocumented functions in ``ImageOps`` have been removed. Use the equivalents +in ``ImageFilter`` instead: + +========================== ============================ +Removed Use instead +========================== ============================ +``ImageOps.box_blur`` ``ImageFilter.BoxBlur`` +``ImageOps.gaussian_blur`` ``ImageFilter.GaussianBlur`` +``ImageOps.gblur`` ``ImageFilter.GaussianBlur`` +``ImageOps.usm`` ``ImageFilter.UnsharpMask`` +``ImageOps.unsharp_mask`` ``ImageFilter.UnsharpMask`` +========================== ============================ + +PIL.OleFileIO +~~~~~~~~~~~~~ + +*Removed in version 6.0.0.* + +PIL.OleFileIO was removed as a vendored file and in Pillow 4.0.0 (2017-01) in favour of +the upstream olefile Python package, and replaced with an ``ImportError`` in 5.0.0 +(2018-01). The deprecated file has now been removed from Pillow. If needed, install from +PyPI (eg. ``pip install olefile``). diff --git a/docs/example/DdsImagePlugin.py b/docs/example/DdsImagePlugin.py index 71a5b50ea..631dc2d61 100644 --- a/docs/example/DdsImagePlugin.py +++ b/docs/example/DdsImagePlugin.py @@ -221,11 +221,11 @@ class DdsImageFile(ImageFile.ImageFile): header = BytesIO(header_bytes) flags, height, width = struct.unpack("<3I", header.read(12)) - self.size = (width, height) + self._size = (width, height) self.mode = "RGBA" pitch, depth, mipmaps = struct.unpack("<3I", header.read(12)) - reserved = struct.unpack("<11I", header.read(44)) + struct.unpack("<11I", header.read(44)) # reserved # pixel format pfsize, pfflags = struct.unpack("<2I", header.read(8)) @@ -235,10 +235,8 @@ class DdsImageFile(ImageFile.ImageFile): if fourcc == b"DXT1": self.decoder = "DXT1" - codec = _dxt1 elif fourcc == b"DXT5": self.decoder = "DXT5" - codec = _dxt5 else: raise NotImplementedError("Unimplemented pixel format %r" % fourcc) diff --git a/docs/handbook/image-file-formats.rst b/docs/handbook/image-file-formats.rst index eb50ff23d..705438d4a 100644 --- a/docs/handbook/image-file-formats.rst +++ b/docs/handbook/image-file-formats.rst @@ -482,7 +482,7 @@ The :py:meth:`~PIL.Image.Image.open` method sets the following This key is omitted if the image is not a transparent palette image. -``Open`` also sets ``Image.text`` to a list of the values of the +``Open`` also sets ``Image.text`` to a dictionary of the values of the ``tEXt``, ``zTXt``, and ``iTXt`` chunks of the PNG image. Individual compressed chunks are limited to a decompressed size of ``PngImagePlugin.MAX_TEXT_CHUNK``, by default 1MB, to prevent @@ -751,7 +751,7 @@ The :py:meth:`~PIL.Image.Image.save` method supports the following options: **method** Quality/speed trade-off (0=fast, 6=slower-better). Defaults to 0. -**icc_procfile** +**icc_profile** The ICC Profile to include in the saved file. Only supported if the system WebP library was built with webpmux support. @@ -1029,7 +1029,8 @@ The :py:meth:`~PIL.Image.Image.save` method can take the following keyword argum saved in the PDF. **title** - The document’s title. + The document’s title. If not appending to an existing PDF file, this will + default to the filename. .. versionadded:: 5.1.0 @@ -1061,6 +1062,18 @@ The :py:meth:`~PIL.Image.Image.save` method can take the following keyword argum .. versionadded:: 5.1.0 +**creationDate** + The creation date of the document. If not appending to an existing PDF + file, this will default to the current time. + + .. versionadded:: 5.3.0 + +**modDate** + The modification date of the document. If not appending to an existing PDF + file, this will default to the current time. + + .. versionadded:: 5.3.0 + XV Thumbnails ^^^^^^^^^^^^^ diff --git a/docs/handbook/tutorial.rst b/docs/handbook/tutorial.rst index 52d98f694..13a7deba6 100644 --- a/docs/handbook/tutorial.rst +++ b/docs/handbook/tutorial.rst @@ -41,10 +41,10 @@ example, let’s display the image we just loaded:: .. note:: The standard version of :py:meth:`~PIL.Image.Image.show` is not very - efficient, since it saves the image to a temporary file and calls the - :command:`xv` utility to display the image. If you don’t have :command:`xv` - installed, it won’t even work. When it does work though, it is very handy - for debugging and tests. + efficient, since it saves the image to a temporary file and calls a utility + to display the image. If you don’t have an appropriate utility installed, + it won’t even work. When it does work though, it is very handy for + debugging and tests. The following sections provide an overview of the different functions provided in this library. @@ -183,20 +183,11 @@ Rolling an image part1 = image.crop((0, 0, delta, ysize)) part2 = image.crop((delta, 0, xsize, ysize)) - part1.load() - part2.load() - image.paste(part2, (0, 0, xsize-delta, ysize)) image.paste(part1, (xsize-delta, 0, xsize, ysize)) + image.paste(part2, (0, 0, xsize-delta, ysize)) return image -Note that when pasting it back from the :py:meth:`~PIL.Image.Image.crop` -operation, :py:meth:`~PIL.Image.Image.load` is called first. This is because -cropping is a lazy operation. If :py:meth:`~PIL.Image.Image.load` was not -called, then the crop operation would not be performed until the images were -used in the paste commands. This would mean that ``part1`` would be cropped from -the version of ``image`` already modified by the first paste. - For more advanced tricks, the paste method can also take a transparency mask as an optional argument. In this mask, the value 255 indicates that the pasted image is opaque in that position (that is, the pasted image should be used as diff --git a/docs/handbook/writing-your-own-file-decoder.rst b/docs/handbook/writing-your-own-file-decoder.rst index 107e25f36..2e68656ca 100644 --- a/docs/handbook/writing-your-own-file-decoder.rst +++ b/docs/handbook/writing-your-own-file-decoder.rst @@ -69,7 +69,7 @@ true color. header = string.split(header) # size in pixels (width, height) - self.size = int(header[1]), int(header[2]) + self._size = int(header[1]), int(header[2]) # mode setting bits = int(header[3]) diff --git a/docs/index.rst b/docs/index.rst index 9106efe8d..b300dc16d 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -10,11 +10,11 @@ Pillow is the friendly PIL fork by `Alex Clark and Contributors = 5.2.0 | | | | Yes | | | Yes | Yes | Yes | Yes | +|Pillow 5.2.x - 5.4.x| | | | Yes | | | Yes | Yes | Yes | Yes | ++--------------------+-------+-------+-------+-------+-------+-------+-------+-------+-------+-------+ +|Pillow >= 6.0.0 | | | | Yes | | | | Yes | Yes | Yes | +--------------------+-------+-------+-------+-------+-------+-------+-------+-------+-------+-------+ Basic Installation @@ -165,12 +167,12 @@ Many of Pillow's features require external libraries: * **libimagequant** provides improved color quantization - * Pillow has been tested with libimagequant **2.6-2.12.1** + * Pillow has been tested with libimagequant **2.6-2.12.2** * 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 VS2013/MSVC 18 to compile, - so it is unlikely to work with any Python prior to 3.5 on Windows. + so it is unlikely to work with Python 2.7 on Windows. * **libraqm** provides complex text layout support. @@ -390,22 +392,22 @@ These platforms are built and tested for every change. +----------------------------------+-------------------------------+-----------------------+ | Debian Stretch | 2.7 |x86 | +----------------------------------+-------------------------------+-----------------------+ -| Fedora 25 | 2.7 |x86-64 | +| Fedora 28 | 2.7 |x86-64 | +----------------------------------+-------------------------------+-----------------------+ -| Fedora 26 | 2.7 |x86-64 | +| Fedora 29 | 2.7 |x86-64 | +----------------------------------+-------------------------------+-----------------------+ -| Mac OS X 10.10 Yosemite* | 2.7, 3.4, 3.5, 3.6, 3.7 |x86-64 | +| Mac OS X 10.10 Yosemite* | 2.7, 3.5, 3.6, 3.7 |x86-64 | +----------------------------------+-------------------------------+-----------------------+ -| Ubuntu Linux 16.04 LTS | 2.7 |x86-64 | +| Ubuntu Linux 16.04 LTS | 2.7, 3.5, 3.6, 3.7, |x86-64 | +| | PyPy, PyPy3 | | +----------------------------------+-------------------------------+-----------------------+ -| Ubuntu Linux 14.04 LTS | 2.7, 3.4, 3.5, 3.6, 3.7, |x86-64 | -| | pypy, pypy3 | | +| Ubuntu Linux 14.04 LTS | 2.7, 3.5, 3.6 |x86-64 | | +-------------------------------+-----------------------+ | | 2.7 |x86 | +----------------------------------+-------------------------------+-----------------------+ -| Windows Server 2012 R2 | 2.7, 3.4 |x86, x86-64 | +| Windows Server 2012 R2 | 2.7, 3.5, 3.6, 3.7 |x86, x86-64 | | +-------------------------------+-----------------------+ -| | pypy, 3.5/mingw |x86 | +| | PyPy, 3.7/MinGW |x86 | +----------------------------------+-------------------------------+-----------------------+ \* Mac OS X CI is not run for every commit, but is run for every release. @@ -423,11 +425,15 @@ These platforms have been reported to work at the versions mentioned. +----------------------------------+------------------------------+--------------------------------+-----------------------+ |**Operating system** |**Tested Python versions** |**Latest tested Pillow version**|**Tested processors** | +----------------------------------+------------------------------+--------------------------------+-----------------------+ +| macOS 10.14 Mojave | 2.7, 3.4, 3.5, 3.6, 3.7 | 5.4.1 |x86-64 | ++----------------------------------+------------------------------+--------------------------------+-----------------------+ | macOS 10.13 High Sierra | 2.7, 3.4, 3.5, 3.6 | 4.2.1 |x86-64 | +----------------------------------+------------------------------+--------------------------------+-----------------------+ | macOS 10.12 Sierra | 2.7, 3.4, 3.5, 3.6 | 4.1.1 |x86-64 | +----------------------------------+------------------------------+--------------------------------+-----------------------+ -| Mac OS X 10.11 El Capitan | 2.7, 3.3, 3.4, 3.5 | 4.1.0 |x86-64 | +| Mac OS X 10.11 El Capitan | 2.7, 3.4, 3.5, 3.6, 3.7 | 5.4.1 |x86-64 | +| +------------------------------+--------------------------------+ + +| | 3.3 | 4.1.0 | | +----------------------------------+------------------------------+--------------------------------+-----------------------+ | Mac OS X 10.9 Mavericks | 2.7, 3.2, 3.3, 3.4 | 3.0.0 |x86-64 | +----------------------------------+------------------------------+--------------------------------+-----------------------+ @@ -474,8 +480,6 @@ These platforms have been reported to work at the versions mentioned. Old Versions ------------ -You can download old distributions from `PyPI -`_. Only the latest major -releases for Python 2.x and 3.x are visible, but all releases are -available by direct URL access -e.g. https://pypi.org/project/Pillow/1.0/. +You can download old distributions from the `release history at PyPI +`_ and by direct URL access +eg. https://pypi.org/project/Pillow/1.0/. diff --git a/docs/reference/ImageChops.rst b/docs/reference/ImageChops.rst index 2e4e21f19..6c8f11253 100644 --- a/docs/reference/ImageChops.rst +++ b/docs/reference/ImageChops.rst @@ -34,6 +34,7 @@ operations in this module). .. autofunction:: PIL.ImageChops.lighter .. autofunction:: PIL.ImageChops.logical_and .. autofunction:: PIL.ImageChops.logical_or +.. autofunction:: PIL.ImageChops.logical_xor .. autofunction:: PIL.ImageChops.multiply .. py:method:: PIL.ImageChops.offset(image, xoffset, yoffset=None) diff --git a/docs/reference/ImageDraw.rst b/docs/reference/ImageDraw.rst index 7e5192499..7c24bae93 100644 --- a/docs/reference/ImageDraw.rst +++ b/docs/reference/ImageDraw.rst @@ -37,7 +37,8 @@ Coordinates ^^^^^^^^^^^ The graphics interface uses the same coordinate system as PIL itself, with (0, -0) in the upper left corner. +0) in the upper left corner. Any pixels drawn outside of the image bounds will +be discarded. Colors ^^^^^^ @@ -126,7 +127,7 @@ Methods :returns: An image font. -.. py:method:: PIL.ImageDraw.ImageDraw.arc(xy, start, end, fill=None) +.. py:method:: PIL.ImageDraw.ImageDraw.arc(xy, start, end, fill=None, width=0) Draws an arc (a portion of a circle outline) between the start and end angles, inside the given bounding box. @@ -138,6 +139,9 @@ Methods 3 o'clock, increasing clockwise. :param end: Ending angle, in degrees. :param fill: Color to use for the arc. + :param width: The line width, in pixels. + + .. versionadded:: 5.3.0 .. py:method:: PIL.ImageDraw.ImageDraw.bitmap(xy, bitmap, fill=None) @@ -150,7 +154,7 @@ Methods To paste pixel data into an image, use the :py:meth:`~PIL.Image.Image.paste` method on the image itself. -.. py:method:: PIL.ImageDraw.ImageDraw.chord(xy, start, end, fill=None, outline=None) +.. py:method:: PIL.ImageDraw.ImageDraw.chord(xy, start, end, fill=None, outline=None, width=0) Same as :py:meth:`~PIL.ImageDraw.ImageDraw.arc`, but connects the end points with a straight line. @@ -160,8 +164,11 @@ Methods where ``x1 >= x0`` and ``y1 >= y0``. :param outline: Color to use for the outline. :param fill: Color to use for the fill. + :param width: The line width, in pixels. -.. py:method:: PIL.ImageDraw.ImageDraw.ellipse(xy, fill=None, outline=None) + .. versionadded:: 5.3.0 + +.. py:method:: PIL.ImageDraw.ImageDraw.ellipse(xy, fill=None, outline=None, width=0) Draws an ellipse inside the given bounding box. @@ -170,6 +177,9 @@ Methods where ``x1 >= x0`` and ``y1 >= y0``. :param outline: Color to use for the outline. :param fill: Color to use for the fill. + :param width: The line width, in pixels. + + .. versionadded:: 5.3.0 .. py:method:: PIL.ImageDraw.ImageDraw.line(xy, fill=None, width=0, joint=None) @@ -188,7 +198,7 @@ Methods .. versionadded:: 5.3.0 -.. py:method:: PIL.ImageDraw.ImageDraw.pieslice(xy, start, end, fill=None, outline=None) +.. py:method:: PIL.ImageDraw.ImageDraw.pieslice(xy, start, end, fill=None, outline=None, width=0) Same as arc, but also draws straight lines between the end points and the center of the bounding box. @@ -201,6 +211,9 @@ Methods :param end: Ending angle, in degrees. :param fill: Color to use for the fill. :param outline: Color to use for the outline. + :param width: The line width, in pixels. + + .. versionadded:: 5.3.0 .. py:method:: PIL.ImageDraw.ImageDraw.point(xy, fill=None) @@ -223,7 +236,7 @@ Methods :param outline: Color to use for the outline. :param fill: Color to use for the fill. -.. py:method:: PIL.ImageDraw.ImageDraw.rectangle(xy, fill=None, outline=None) +.. py:method:: PIL.ImageDraw.ImageDraw.rectangle(xy, fill=None, outline=None, width=0) Draws a rectangle. @@ -232,6 +245,9 @@ Methods is just outside the drawn rectangle. :param outline: Color to use for the outline. :param fill: Color to use for the fill. + :param width: The line width, in pixels. + + .. versionadded:: 5.3.0 .. py:method:: PIL.ImageDraw.ImageDraw.shape(shape, fill=None, outline=None) diff --git a/docs/reference/PixelAccess.rst b/docs/reference/PixelAccess.rst index 5389dab33..8a8569922 100644 --- a/docs/reference/PixelAccess.rst +++ b/docs/reference/PixelAccess.rst @@ -28,6 +28,13 @@ Results in the following:: (23, 24, 68) (0, 0, 0) +Access using negative indexes is also possible. + +.. code-block:: python + + px[-1,-1] = (0,0,0) + print (px[-1,-1]) + :py:class:`PixelAccess` Class @@ -58,7 +65,8 @@ Results in the following:: Modifies the pixel at x,y. The color is given as a single numerical value for single band images, and a tuple for - multi-band images + multi-band images. In addition to this, RGB and RGBA tuples + are accepted for P images. :param xy: The pixel coordinate, given as (x, y). :param color: The pixel value according to its mode. e.g. tuple (r, g, b) for RGB mode) diff --git a/docs/reference/PyAccess.rst b/docs/reference/PyAccess.rst index 8bd8af9ff..6a492cd86 100644 --- a/docs/reference/PyAccess.rst +++ b/docs/reference/PyAccess.rst @@ -29,6 +29,13 @@ Results in the following:: (23, 24, 68) (0, 0, 0) +Access using negative indexes is also possible. + +.. code-block:: python + + px[-1,-1] = (0,0,0) + print (px[-1,-1]) + :py:class:`PyAccess` Class diff --git a/docs/reference/block_allocator.rst b/docs/reference/block_allocator.rst index e70f9667c..400f236dc 100644 --- a/docs/reference/block_allocator.rst +++ b/docs/reference/block_allocator.rst @@ -8,7 +8,7 @@ Historically there have been two image allocators in Pillow: ``ImagingAllocateBlock`` and ``ImagingAllocateArray``. The first works for images smaller than 16MB of data and allocates one large chunk of memory of ``im->linesize * im->ysize`` bytes. The second works for -large images and make one allocation for each scan line of size +large images and makes one allocation for each scan line of size ``im->linesize`` bytes. This makes for a very sharp transition between one allocation and potentially thousands of small allocations, leading to unpredictable performance penalties around the transition. diff --git a/docs/reference/open_files.rst b/docs/reference/open_files.rst index a31a1f37e..511eb97bf 100644 --- a/docs/reference/open_files.rst +++ b/docs/reference/open_files.rst @@ -4,7 +4,7 @@ File Handling in Pillow ======================= When opening a file as an image, Pillow requires a filename, -pathlib.Path object, or a file-like object. Pillow uses the filename +pathlib.Path object, or a file-like object. Pillow uses the filename or Path to open a file, so for the rest of this article, they will all be treated as a file-like object. @@ -30,10 +30,10 @@ and may fail:: im5 = Image.open(f) im5.load() # FAILS, closed file -The documentation specifies that the file will be closed after the -``Image.Image.load()`` method is called. This is an aspirational -specification rather than an accurate reflection of the state of the -code. +If a filename or a path-like object is passed to Pillow, then the resulting +file object opened by Pillow may also be closed by Pillow after the +``Image.Image.load()`` method is called, provided the associated image does not +have multiple frames. Pillow cannot in general close and reopen a file, so any access to that file needs to be prior to the close. @@ -41,12 +41,6 @@ that file needs to be prior to the close. Issues ------ -The current open file handling is inconsistent at best: - -* Most of the image plugins do not close the input file. -* Multi-frame images behave badly when seeking through the file, as - it's legal to seek backward in the file until the last image is - read, and then it's not. * Using the file context manager to provide a file-like object to Pillow is dangerous unless the context of the image is limited to the context of the file. @@ -54,20 +48,20 @@ The current open file handling is inconsistent at best: Image Lifecycle --------------- -* ``Image.open()`` called. Path-like objects are opened as a - file. Metadata is read from the open file. The file is left open for - further usage. +* ``Image.open()`` Path-like objects are opened as a file. Metadata is read + from the open file. The file is left open for further usage. -* ``Image.Image.load()`` when the pixel data from the image is +* ``Image.Image.load()`` When the pixel data from the image is required, ``load()`` is called. The current frame is read into memory. The image can now be used independently of the underlying image file. -* ``Image.Image.seek()`` in the case of multi-frame images - (e.g. multipage TIFF and animated GIF) the image file left open so - that seek can load the appropriate frame. When the last frame is - read, the image file is closed (at least in some image plugins), and - no more seeks can occur. + If a filename or a path-like object was passed to ``Image.open()``, then + the file object was opened by Pillow and is considered to be used exclusively + by Pillow. So if the image is a single-frame image, the file will + be closed in this method after the frame is read. If the image is a + multi-frame image, (e.g. multipage TIFF and animated GIF) the image file is + left open so that ``Image.Image.seek()`` can load the appropriate frame. * ``Image.Image.close()`` Closes the file pointer and destroys the core image object. This is used in the Pillow context manager @@ -77,16 +71,13 @@ Image Lifecycle ... # image operations here. -The lifecycle of a single frame image is relatively simple. The file +The lifecycle of a single-frame image is relatively simple. The file must remain open until the ``load()`` or ``close()`` function is called. Multi-frame images are more complicated. The ``load()`` method is not -a terminal method, so it should not close the underlying file. The -current behavior of ``seek()`` closing the underlying file on -accessing the last frame is presumably a heuristic for closing the -file after iterating through the entire sequence. In general, Pillow -does not know if there are going to be any requests for additional +a terminal method, so it should not close the underlying file. In general, +Pillow does not know if there are going to be any requests for additional data until the caller has explicitly closed the image. @@ -124,4 +115,3 @@ Proposed File Handling * Users of the library should call ``Image.Image.close()`` on any multi-frame image to ensure that the underlying file is closed. - diff --git a/docs/releasenotes/4.3.0.rst b/docs/releasenotes/4.3.0.rst index 827754230..6fa554e23 100644 --- a/docs/releasenotes/4.3.0.rst +++ b/docs/releasenotes/4.3.0.rst @@ -9,7 +9,7 @@ Deprecations Several undocumented functions in ImageOps have been deprecated: ``gaussian_blur``, ``gblur``, ``unsharp_mask``, ``usm`` and -``box_blur``. Use the equivalent operations in ImageFilter +``box_blur``. Use the equivalent operations in ``ImageFilter`` instead. These functions will be removed in a future release. TIFF Metadata Changes diff --git a/docs/releasenotes/5.3.0.rst b/docs/releasenotes/5.3.0.rst index a5591b98d..cce671c32 100644 --- a/docs/releasenotes/5.3.0.rst +++ b/docs/releasenotes/5.3.0.rst @@ -1,9 +1,42 @@ 5.3.0 ----- +API Changes +=========== + +Image size +^^^^^^^^^^ + +If you attempt to set the size of an image directly, e.g. +``im.size = (100, 100)``, you will now receive an ``AttributeError``. This is +not about removing existing functionality, but instead about raising an +explicit error to prevent later consequences. The ``resize`` method is the +correct way to change an image's size. + +The exceptions to this are: + +* The ICO and ICNS image formats, which use ``im.size = (100, 100)`` to select a subimage. +* The TIFF image format, which now has a ``DeprecationWarning`` for this action, as direct image size setting was previously necessary to work around an issue with tile extents. + + API Additions ============= +Added line width parameter to rectangle and ellipse-based shapes +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +An optional line ``width`` parameter has been added to ``ImageDraw.Draw.arc``, +``chord``, ``ellipse``, ``pieslice`` and ``rectangle``. + +Curved joints for line sequences +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +``ImageDraw.Draw.line`` draws a line, or lines, between points. Previously, +when multiple points are given, for a larger ``width``, the joints between +these lines looked unsightly. There is now an additional optional argument, +``joint``, defaulting to ``None``. When it is set to ``curved``, the joints +between the lines will become rounded. + ImageOps.colorize ^^^^^^^^^^^^^^^^^ @@ -13,11 +46,22 @@ Now it supports three-color mapping with the optional ``mid`` parameter, and the positions for all three color arguments can each be optionally specified (``blackpoint``, ``whitepoint`` and ``midpoint``). For example, with all optional arguments:: - ImageOps.colorize(im, black=(32, 37, 79), white='white', mid=(59, 101, 175), + + ImageOps.colorize(im, black=(32, 37, 79), white='white', mid=(59, 101, 175), blackpoint=15, whitepoint=240, midpoint=100) +ImageOps.pad +^^^^^^^^^^^^ +While ``ImageOps.fit`` allows users to crop images to a requested aspect ratio +and size, new method ``ImageOps.pad`` pads images to fill a requested aspect +ratio and size, filling new space with a provided ``color`` and positioning the +image within the new area through a ``centering`` argument. Other Changes ============= +Added support for reading tiled TIFF images through LibTIFF. Compressed TIFF +images are now read through LibTIFF. + +RGB WebP images are now read as RGB mode, rather than RGBX. diff --git a/docs/releasenotes/5.4.0.rst b/docs/releasenotes/5.4.0.rst new file mode 100644 index 000000000..6d7277c70 --- /dev/null +++ b/docs/releasenotes/5.4.0.rst @@ -0,0 +1,67 @@ +5.4.0 +----- + +API Changes +=========== + +APNG extension to PNG plugin +^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +Animated Portable Network Graphics (APNG) images are not fully supported but +can be opened via the PNG plugin to get some basic info:: + + im = Image.open("image.apng") + print(im.mode) # "RGBA" + print(im.size) # (245, 245) + im.show() # Shows a single frame + +Check for libjpeg-turbo +^^^^^^^^^^^^^^^^^^^^^^^ + +You can check if Pillow has been built against the libjpeg-turbo version of the +libjpeg library:: + + from PIL import features + features.check_feature("libjpeg_turbo") # True or False + +Negative indexes in pixel access +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +When accessing individual image pixels, negative indexes are now also accepted. +For example, to get or set the farthest pixel in the lower right of an image:: + + px = im.load() + print(px[-1, -1]) + px[-1, -1] = (0, 0, 0) + + +New custom TIFF tags +^^^^^^^^^^^^^^^^^^^^ + +TIFF images can now be saved with custom integer, float and string TIFF tags:: + + im = Image.new("RGB", (200, 100)) + custom = { + 37000: 4, + 37001: 4.2, + 37002: "custom tag value", + 37003: u"custom tag value", + 37004: b"custom tag value", + } + im.save("output.tif", tiffinfo=custom) + + im2 = Image.open("output.tif") + print(im2.tag_v2[37000]) # 4 + print(im2.tag_v2[37002]) # "custom tag value" + print(im2.tag_v2[37004]) # b"custom tag value" + +Other Changes +============= + +ImageOps.fit +^^^^^^^^^^^^ + +Now uses one resize operation with ``box`` parameter internally +instead of a crop and scale operations sequence. +This improves the performance and accuracy of cropping since +the ``box`` parameter accepts float values. diff --git a/docs/releasenotes/5.4.1.rst b/docs/releasenotes/5.4.1.rst new file mode 100644 index 000000000..78f483db6 --- /dev/null +++ b/docs/releasenotes/5.4.1.rst @@ -0,0 +1,36 @@ +5.4.1 +----- + +This release fixes regressions in 5.4.0. + +Installation on Termux +^^^^^^^^^^^^^^^^^^^^^^ + +A change to the way Pillow detects libraries during installed prevented +installation on Termux, which does not have ``/sbin/ldconfig``. This is now +fixed. + +PNG: Handle IDAT chunks after image end +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +Some PNG images have multiple IDAT chunks. In some cases, Pillow will stop +reading image data before the IDAT chunks finish. A regression caused an +``EOFError`` exception when previously there was none. This is now fixed, and +file reading continues in case there are subsequent text chunks. + +PNG: MIME type +^^^^^^^^^^^^^^ + +The addition of limited APNG support to the PNG plugin also overwrote the MIME +type for PNG files, causing "image/apng" to be returned as the MIME type of +both APNG and PNG files. This has been fixed so the MIME type of PNG files is +"image/png". + +File closing +^^^^^^^^^^^^ + +A regression caused an unsupported image file to report a +``ValueError: seek of closed file`` exception instead of an ``OSError``. This +has been fixed by ensuring that image plugins only close their internal ``__fp`` +if they are not the same as ``ImageFile``'s ``fp``, allowing each to manage their own +file pointers. diff --git a/docs/releasenotes/6.0.0.rst b/docs/releasenotes/6.0.0.rst new file mode 100644 index 000000000..e1fa83cce --- /dev/null +++ b/docs/releasenotes/6.0.0.rst @@ -0,0 +1,90 @@ +6.0.0 +----- + +Backwards Incompatible Changes +============================== + +Python 3.4 dropped +^^^^^^^^^^^^^^^^^^ + +Python 3.4 is EOL since 2019-03-16 and no longer supported. We will not be creating +binaries, testing, or retaining compatibility with this version. The final version of +Pillow for Python 3.4 is 5.4.1. + +Removed deprecated PIL.OleFileIO +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +PIL.OleFileIO was removed as a vendored file and in Pillow 4.0.0 (2017-01) in favour of +the upstream olefile Python package, and replaced with an ``ImportError``. The +deprecated file has now been removed from Pillow. If needed, install from PyPI (eg. +``pip install olefile``). + +Removed deprecated ImageOps functions +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +Several undocumented functions in ``ImageOps`` were deprecated in Pillow 4.3.0 (2017-10) +and have now been removed: ``gaussian_blur``, ``gblur``, ``unsharp_mask``, ``usm`` and +``box_blur``. Use the equivalent operations in ``ImageFilter`` instead. + +API Changes +=========== + +Deprecations +^^^^^^^^^^^^ + +These version constants have been deprecated and will be removed in a future +version. + +* ``BmpImagePlugin.__version__`` +* ``CurImagePlugin.__version__`` +* ``DcxImagePlugin.__version__`` +* ``EpsImagePlugin.__version__`` +* ``FliImagePlugin.__version__`` +* ``FpxImagePlugin.__version__`` +* ``GdImageFile.__version__`` +* ``GifImagePlugin.__version__`` +* ``IcoImagePlugin.__version__`` +* ``ImImagePlugin.__version__`` +* ``ImtImagePlugin.__version__`` +* ``IptcImagePlugin.__version__`` +* ``Jpeg2KImagePlugin.__version__`` +* ``JpegImagePlugin.__version__`` +* ``McIdasImagePlugin.__version__`` +* ``MicImagePlugin.__version__`` +* ``MpegImagePlugin.__version__`` +* ``MpoImagePlugin.__version__`` +* ``MspImagePlugin.__version__`` +* ``PalmImagePlugin.__version__`` +* ``PcdImagePlugin.__version__`` +* ``PcxImagePlugin.__version__`` +* ``PdfImagePlugin.__version__`` +* ``PixarImagePlugin.__version__`` +* ``PngImagePlugin.__version__`` +* ``PpmImagePlugin.__version__`` +* ``PsdImagePlugin.__version__`` +* ``SgiImagePlugin.__version__`` +* ``SunImagePlugin.__version__`` +* ``TgaImagePlugin.__version__`` +* ``TiffImagePlugin.__version__`` +* ``WmfImagePlugin.__version__`` +* ``XbmImagePlugin.__version__`` +* ``XpmImagePlugin.__version__`` +* ``XVThumbImagePlugin.__version__`` + +Use ``PIL.__version__`` instead. + +API Additions +============= + +TODO +^^^^ + +TODO + +Other Changes +============= + +TODO +^^^^ + +TODO diff --git a/docs/releasenotes/index.rst b/docs/releasenotes/index.rst index fc8d686eb..9a088375b 100644 --- a/docs/releasenotes/index.rst +++ b/docs/releasenotes/index.rst @@ -6,6 +6,9 @@ Release Notes .. toctree:: :maxdepth: 2 + 6.0.0 + 5.4.1 + 5.4.0 5.3.0 5.2.0 5.1.0 diff --git a/docs/resources/js/script.js b/docs/resources/js/script.js new file mode 100644 index 000000000..3bc216c2d --- /dev/null +++ b/docs/resources/js/script.js @@ -0,0 +1,60 @@ +jQuery(document).ready(function ($) { + setTimeout(function () { + var sectionID = 'base'; + var search = function ($section, $sidebarItem) { + $section.children('.section, .function, .method').each(function () { + if ($(this).hasClass('section')) { + sectionID = $(this).attr('id'); + search($(this), $sidebarItem.parent().find('[href="#'+sectionID+'"]')); + } else { + var $dt = $(this).children('dt'); + var id = $dt.attr('id'); + if (id === undefined) { + return; + } + + var $functionsUL = $sidebarItem.siblings('[data-sectionID='+sectionID+']'); + if (!$functionsUL.length) { + $functionsUL = $('