diff --git a/.github/CONTRIBUTING.md b/.github/CONTRIBUTING.md index 676f4374b..b3d456659 100644 --- a/.github/CONTRIBUTING.md +++ b/.github/CONTRIBUTING.md @@ -34,6 +34,4 @@ The best reproductions are self-contained scripts with minimal dependencies. If ## Security vulnerabilities -To report sensitive vulnerability information, please use the [Tidelift security contact](https://tidelift.com/security). Tidelift will coordinate the fix and disclosure. - -If your organisation/employer is a distributor of Pillow and would like advance notification of security-related bugs, please let us know your preferred contact method. +Please see our [security policy](https://github.com/python-pillow/Pillow/blob/master/.github/SECURITY.md). diff --git a/.github/FUNDING.yml b/.github/FUNDING.yml new file mode 100644 index 000000000..ca04afe02 --- /dev/null +++ b/.github/FUNDING.yml @@ -0,0 +1 @@ +tidelift: pypi/pillow diff --git a/.github/ISSUE_TEMPLATE.md b/.github/ISSUE_TEMPLATE.md deleted file mode 100644 index 6cea87df2..000000000 --- a/.github/ISSUE_TEMPLATE.md +++ /dev/null @@ -1,19 +0,0 @@ -### What did you do? - -### What did you expect to happen? - -### What actually happened? - -### What are your OS, Python and Pillow versions? - -* OS: -* Python: -* Pillow: - -Please include **code** that reproduces the issue and whenever possible, an **image** that demonstrates the issue. Please upload images to GitHub, not to third-party file hosting sites. If necessary, add the image to a zip or tar archive. - -The best reproductions are self-contained scripts with minimal dependencies. If you are using a framework such as plone, Django, or buildout, try to replicate the issue just using Pillow. - -```python -code goes here -``` diff --git a/.github/ISSUE_TEMPLATE/ISSUE_REPORT.md b/.github/ISSUE_TEMPLATE/ISSUE_REPORT.md new file mode 100644 index 000000000..115f6135d --- /dev/null +++ b/.github/ISSUE_TEMPLATE/ISSUE_REPORT.md @@ -0,0 +1,59 @@ +--- +name: Issue report +about: Create a report to help us improve Pillow +--- + + + +### What did you do? + +### What did you expect to happen? + +### What actually happened? + +### What are your OS, Python and Pillow versions? + +* OS: +* Python: +* Pillow: + + + +```python +code goes here +``` diff --git a/.github/SECURITY.md b/.github/SECURITY.md new file mode 100644 index 000000000..c6369fdef --- /dev/null +++ b/.github/SECURITY.md @@ -0,0 +1,5 @@ +# Security policy + +To report sensitive vulnerability information, please use the [Tidelift security contact](https://tidelift.com/security). Tidelift will coordinate the fix and disclosure. + +If your organisation/employer is a distributor of Pillow and would like advance notification of security-related bugs, please let us know your preferred contact method. diff --git a/.gitignore b/.gitignore index 861b801b5..ef7520c0d 100644 --- a/.gitignore +++ b/.gitignore @@ -67,6 +67,9 @@ docs/_build/ \#*# .#* +#VS Code +.vscode + #Komodo *.komodoproject diff --git a/.travis.yml b/.travis.yml index 68d1d840c..43fc23613 100644 --- a/.travis.yml +++ b/.travis.yml @@ -16,9 +16,9 @@ matrix: - python: "3.6" name: "Lint" env: LINT="true" - - python: "pypy2.7-6.0" + - python: "pypy" name: "PyPy2 Xenial" - - python: "pypy3.5-6.0" + - python: "pypy3" name: "PyPy3 Xenial" - python: '3.7' name: "3.7 Xenial" @@ -44,8 +44,8 @@ matrix: - 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" + - env: DOCKER="fedora-30-amd64" DOCKER_TAG="master" services: - docker diff --git a/.travis/script.sh b/.travis/script.sh index d6e02f01d..ae2e8aad4 100755 --- a/.travis/script.sh +++ b/.travis/script.sh @@ -7,7 +7,7 @@ make clean make install-coverage python selftest.py -python -m pytest -vx --cov PIL --cov-report term Tests +python -m pytest -v -x --cov PIL --cov-report term Tests pushd /tmp/check-manifest && check-manifest --ignore ".coveragerc,.editorconfig,*.yml,*.yaml,tox.ini" && popd diff --git a/CHANGES.rst b/CHANGES.rst index cb276b0bf..fcddd8da0 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -2,6 +2,96 @@ Changelog (Pillow) ================== +6.1.0 (unreleased) +------------------ + +- Fixed crash when loading non-font bytes #3912 + [radarhere] + +- Fix SPARC memory alignment issues in Pack/Unpack functions #3858 + [kulikjak] + +- Added CMYK;16B and CMYK;16N unpackers #3913 + [radarhere] + +- Fixed bugs in calculating text size #3864 + [radarhere] + +- Add __main__.py to output basic format and support information #3870 + [jdufresne] + +- Added variation font support #3802 + [radarhere] + +- Do not down-convert if image is LA when showing with PNG format #3869 + [radarhere] + +- Improve handling of PSD frames #3759 + [radarhere] + +- Improved ICO and ICNS loading #3897 + [radarhere] + +- Changed Preview application path so that it is no longer static #3896 + [radarhere] + +- Corrected ttb text positioning #3856 + [radarhere] + +- Handle unexpected ICO image sizes #3836 + [radarhere] + +- Fixed bits value for RGB;16N unpackers #3837 + [kkopachev] + +- Travis CI: Add Fedora 30, remove Fedora 28 #3821 + [hugovk] + +- Added reading of CMYK;16L TIFF images #3817 + [radarhere] + +- Fixed dimensions of 1-bit PDFs #3827 + [radarhere] + +- Fixed opening mmap image through Path on Windows #3825 + [radarhere] + +- Fixed ImageDraw arc gaps #3824 + [radarhere] + +- Expand GIF to include frames with extents outside the image size #3822 + [radarhere] + +- Fixed ImageTk getimage #3814 + [radarhere] + +- Fixed bug in decoding large images #3791 + [radarhere] + +- Fixed reading APP13 marker without Photoshop data #3771 + [radarhere] + +- Added option to include layered windows in ImageGrab.grab on Windows #3808 + [radarhere] + +- Detect libimagequant when installed by pacman on MingW #3812 + [radarhere] + +- Fixed raqm layout bug #3787 + [radarhere] + +- Fixed loading font with non-Unicode path on Windows #3785 + [radarhere] + +- Travis CI: Upgrade PyPy from 6.0.0 to 7.1.1 #3783 + [hugovk, johnthagen] + +- Depends: Updated openjpeg to 2.3.1 #3794, raqm to 0.7.0 #3877, libimagequant to 2.12.3 #3889 + [radarhere] + +- Fix numpy bool bug #3790 + [radarhere] + 6.0.0 (2019-04-01) ------------------ @@ -1408,7 +1498,7 @@ Changelog (Pillow) - Test: Faster assert_image_similar #2279 [homm] -- Removed depreciated internal "stretch" method #2276 +- Removed deprecated internal "stretch" method #2276 [homm] - Removed the handles_eof flag in decode.c #2223 diff --git a/README.rst b/README.rst index a5de420e7..ddbd12f16 100644 --- a/README.rst +++ b/README.rst @@ -4,7 +4,7 @@ Pillow Python Imaging Library (Fork) ----------------------------- -Pillow is the friendly PIL fork by `Alex Clark and Contributors `_. PIL is the Python Imaging Library by Fredrik Lundh and Contributors. +Pillow is the friendly PIL fork by `Alex Clark and Contributors `_. PIL is the Python Imaging Library by Fredrik Lundh and Contributors. As of 2019, Pillow development is `supported by Tidelift `_. .. start-badges diff --git a/Tests/32bit_segfault_check.py b/Tests/32bit_segfault_check.py index a601f762e..59a22c9d6 100755 --- a/Tests/32bit_segfault_check.py +++ b/Tests/32bit_segfault_check.py @@ -4,5 +4,5 @@ from PIL import Image import sys -if sys.maxsize < 2**32: - im = Image.new('L', (999999, 999999), 0) +if sys.maxsize < 2 ** 32: + im = Image.new("L", (999999, 999999), 0) diff --git a/Tests/bench_cffi_access.py b/Tests/bench_cffi_access.py index 79427dca3..249db3c9a 100644 --- a/Tests/bench_cffi_access.py +++ b/Tests/bench_cffi_access.py @@ -26,18 +26,21 @@ def timer(func, label, *args): starttime = time.time() for x in range(iterations): func(*args) - if time.time()-starttime > 10: - print("%s: breaking at %s iterations, %.6f per iteration" % ( - label, x+1, (time.time()-starttime)/(x+1.0))) + if time.time() - starttime > 10: + print( + "%s: breaking at %s iterations, %.6f per iteration" + % (label, x + 1, (time.time() - starttime) / (x + 1.0)) + ) break - if x == iterations-1: + if x == iterations - 1: endtime = time.time() - print("%s: %.4f s %.6f per iteration" % ( - label, endtime-starttime, (endtime-starttime)/(x+1.0))) + print( + "%s: %.4f s %.6f per iteration" + % (label, endtime - starttime, (endtime - starttime) / (x + 1.0)) + ) class BenchCffiAccess(PillowTestCase): - def test_direct(self): im = hopper() im.load() @@ -48,11 +51,11 @@ class BenchCffiAccess(PillowTestCase): self.assertEqual(caccess[(0, 0)], access[(0, 0)]) print("Size: %sx%s" % im.size) - timer(iterate_get, 'PyAccess - get', im.size, access) - timer(iterate_set, 'PyAccess - set', im.size, access) - timer(iterate_get, 'C-api - get', im.size, caccess) - timer(iterate_set, 'C-api - set', im.size, caccess) + timer(iterate_get, "PyAccess - get", im.size, access) + timer(iterate_set, "PyAccess - set", im.size, access) + timer(iterate_get, "C-api - get", im.size, caccess) + timer(iterate_set, "C-api - set", im.size, caccess) -if __name__ == '__main__': +if __name__ == "__main__": unittest.main() diff --git a/Tests/bench_get.py b/Tests/bench_get.py index 68ac2c9a2..12d7d06fc 100644 --- a/Tests/bench_get.py +++ b/Tests/bench_get.py @@ -2,6 +2,7 @@ from . import helper import timeit import sys + sys.path.insert(0, ".") diff --git a/Tests/check_fli_overflow.py b/Tests/check_fli_overflow.py index 3f7c58015..6b16f0371 100644 --- a/Tests/check_fli_overflow.py +++ b/Tests/check_fli_overflow.py @@ -12,5 +12,5 @@ class TestFliOverflow(PillowTestCase): im.load() -if __name__ == '__main__': +if __name__ == "__main__": unittest.main() diff --git a/Tests/check_icns_dos.py b/Tests/check_icns_dos.py index d4c3cf7cb..aaa13ddd2 100644 --- a/Tests/check_icns_dos.py +++ b/Tests/check_icns_dos.py @@ -6,7 +6,6 @@ from PIL._util import py3 from io import BytesIO if py3: - Image.open(BytesIO(bytes('icns\x00\x00\x00\x10hang\x00\x00\x00\x00', - 'latin-1'))) + Image.open(BytesIO(bytes("icns\x00\x00\x00\x10hang\x00\x00\x00\x00", "latin-1"))) else: - Image.open(BytesIO(bytes('icns\x00\x00\x00\x10hang\x00\x00\x00\x00'))) + Image.open(BytesIO(bytes("icns\x00\x00\x00\x10hang\x00\x00\x00\x00"))) diff --git a/Tests/check_imaging_leaks.py b/Tests/check_imaging_leaks.py index 7fa0663e8..e616f20f2 100755 --- a/Tests/check_imaging_leaks.py +++ b/Tests/check_imaging_leaks.py @@ -9,11 +9,11 @@ min_iterations = 100 max_iterations = 10000 -@unittest.skipIf(sys.platform.startswith('win32'), "requires Unix or macOS") +@unittest.skipIf(sys.platform.startswith("win32"), "requires Unix or macOS") class TestImagingLeaks(PillowTestCase): - def _get_mem_usage(self): from resource import getpagesize, getrusage, RUSAGE_SELF + mem = getrusage(RUSAGE_SELF).ru_maxrss return mem * getpagesize() / 1024 / 1024 @@ -25,20 +25,22 @@ class TestImagingLeaks(PillowTestCase): if i < min_iterations: mem_limit = mem + 1 continue - msg = 'memory usage limit exceeded after %d iterations' % (i + 1) + msg = "memory usage limit exceeded after %d iterations" % (i + 1) self.assertLessEqual(mem, mem_limit, msg) def test_leak_putdata(self): - im = Image.new('RGB', (25, 25)) - self._test_leak(min_iterations, max_iterations, - im.putdata, im.getdata()) + im = Image.new("RGB", (25, 25)) + self._test_leak(min_iterations, max_iterations, im.putdata, im.getdata()) def test_leak_getlist(self): - im = Image.new('P', (25, 25)) - self._test_leak(min_iterations, max_iterations, - # Pass a new list at each iteration. - lambda: im.point(range(256))) + im = Image.new("P", (25, 25)) + self._test_leak( + min_iterations, + max_iterations, + # Pass a new list at each iteration. + lambda: im.point(range(256)), + ) -if __name__ == '__main__': +if __name__ == "__main__": unittest.main() diff --git a/Tests/check_j2k_dos.py b/Tests/check_j2k_dos.py index 4ea31cec2..e036f5dbd 100644 --- a/Tests/check_j2k_dos.py +++ b/Tests/check_j2k_dos.py @@ -6,10 +6,16 @@ from PIL._util import py3 from io import BytesIO if py3: - Image.open(BytesIO(bytes( - '\x00\x00\x00\x0cjP\x20\x20\x0d\x0a\x87\x0a\x00\x00\x00\x00hang', - 'latin-1'))) + Image.open( + BytesIO( + bytes( + "\x00\x00\x00\x0cjP\x20\x20\x0d\x0a\x87\x0a\x00\x00\x00\x00hang", + "latin-1", + ) + ) + ) else: - Image.open(BytesIO(bytes( - '\x00\x00\x00\x0cjP\x20\x20\x0d\x0a\x87\x0a\x00\x00\x00\x00hang'))) + Image.open( + BytesIO(bytes("\x00\x00\x00\x0cjP\x20\x20\x0d\x0a\x87\x0a\x00\x00\x00\x00hang")) + ) diff --git a/Tests/check_j2k_leaks.py b/Tests/check_j2k_leaks.py index d87b7f041..19aabc81b 100755 --- a/Tests/check_j2k_leaks.py +++ b/Tests/check_j2k_leaks.py @@ -4,21 +4,22 @@ from PIL import Image from io import BytesIO # Limits for testing the leak -mem_limit = 1024*1048576 -stack_size = 8*1048576 -iterations = int((mem_limit/stack_size)*2) +mem_limit = 1024 * 1048576 +stack_size = 8 * 1048576 +iterations = int((mem_limit / stack_size) * 2) codecs = dir(Image.core) test_file = "Tests/images/rgb_trns_ycbc.jp2" -@unittest.skipIf(sys.platform.startswith('win32'), "requires Unix or macOS") +@unittest.skipIf(sys.platform.startswith("win32"), "requires Unix or macOS") class TestJpegLeaks(PillowTestCase): def setUp(self): if "jpeg2k_encoder" not in codecs or "jpeg2k_decoder" not in codecs: - self.skipTest('JPEG 2000 support not available') + self.skipTest("JPEG 2000 support not available") def test_leak_load(self): from resource import setrlimit, RLIMIT_AS, RLIMIT_STACK + setrlimit(RLIMIT_STACK, (stack_size, stack_size)) setrlimit(RLIMIT_AS, (mem_limit, mem_limit)) for _ in range(iterations): @@ -27,6 +28,7 @@ class TestJpegLeaks(PillowTestCase): def test_leak_save(self): from resource import setrlimit, RLIMIT_AS, RLIMIT_STACK + setrlimit(RLIMIT_STACK, (stack_size, stack_size)) setrlimit(RLIMIT_AS, (mem_limit, mem_limit)) for _ in range(iterations): @@ -38,5 +40,5 @@ class TestJpegLeaks(PillowTestCase): test_output.read() -if __name__ == '__main__': +if __name__ == "__main__": unittest.main() diff --git a/Tests/check_j2k_overflow.py b/Tests/check_j2k_overflow.py index f456ebb32..32bbdbf42 100644 --- a/Tests/check_j2k_overflow.py +++ b/Tests/check_j2k_overflow.py @@ -5,11 +5,11 @@ from .helper import unittest, PillowTestCase class TestJ2kEncodeOverflow(PillowTestCase): def test_j2k_overflow(self): - im = Image.new('RGBA', (1024, 131584)) - target = self.tempfile('temp.jpc') + im = Image.new("RGBA", (1024, 131584)) + target = self.tempfile("temp.jpc") with self.assertRaises(IOError): im.save(target) -if __name__ == '__main__': +if __name__ == "__main__": unittest.main() diff --git a/Tests/check_jpeg_leaks.py b/Tests/check_jpeg_leaks.py index 97a5650e0..a6fd00280 100644 --- a/Tests/check_jpeg_leaks.py +++ b/Tests/check_jpeg_leaks.py @@ -14,7 +14,7 @@ valgrind --tool=massif python test-installed.py -s -v Tests/check_jpeg_leaks.py """ -@unittest.skipIf(sys.platform.startswith('win32'), "requires Unix or macOS") +@unittest.skipIf(sys.platform.startswith("win32"), "requires Unix or macOS") class TestJpegLeaks(PillowTestCase): """ @@ -74,9 +74,11 @@ post-patch: """ def test_qtables_leak(self): - im = hopper('RGB') + im = hopper("RGB") - standard_l_qtable = [int(s) for s in """ + standard_l_qtable = [ + int(s) + for s in """ 16 11 10 16 24 40 51 61 12 12 14 19 26 58 60 55 14 13 16 24 40 57 69 56 @@ -85,9 +87,14 @@ post-patch: 24 35 55 64 81 104 113 92 49 64 78 87 103 121 120 101 72 92 95 98 112 100 103 99 - """.split(None)] + """.split( + None + ) + ] - standard_chrominance_qtable = [int(s) for s in """ + standard_chrominance_qtable = [ + int(s) + for s in """ 17 18 24 47 99 99 99 99 18 21 26 66 99 99 99 99 24 26 56 99 99 99 99 99 @@ -96,10 +103,12 @@ post-patch: 99 99 99 99 99 99 99 99 99 99 99 99 99 99 99 99 99 99 99 99 99 99 99 99 - """.split(None)] + """.split( + None + ) + ] - qtables = [standard_l_qtable, - standard_chrominance_qtable] + qtables = [standard_l_qtable, standard_chrominance_qtable] for _ in range(iterations): test_output = BytesIO() @@ -161,8 +170,8 @@ post patch: 0 11.33 """ - im = hopper('RGB') - exif = b'12345678'*4096 + im = hopper("RGB") + exif = b"12345678" * 4096 for _ in range(iterations): test_output = BytesIO() @@ -195,12 +204,12 @@ base case: 0 +----------------------------------------------------------------------->Gi 0 7.882 """ - im = hopper('RGB') + im = hopper("RGB") for _ in range(iterations): test_output = BytesIO() im.save(test_output, "JPEG") -if __name__ == '__main__': +if __name__ == "__main__": unittest.main() diff --git a/Tests/check_large_memory.py b/Tests/check_large_memory.py index 24d687ea6..7e0e83088 100644 --- a/Tests/check_large_memory.py +++ b/Tests/check_large_memory.py @@ -12,16 +12,21 @@ from .helper import unittest, PillowTestCase # 2.7 and 3.2. from PIL import Image + +try: + import numpy +except ImportError: + numpy = None + YDIM = 32769 XDIM = 48000 -@unittest.skipIf(sys.maxsize <= 2**32, "requires 64-bit system") +@unittest.skipIf(sys.maxsize <= 2 ** 32, "requires 64-bit system") class LargeMemoryTest(PillowTestCase): - def _write_png(self, xdim, ydim): - f = self.tempfile('temp.png') - im = Image.new('L', (xdim, ydim), 0) + f = self.tempfile("temp.png") + im = Image.new("L", (xdim, ydim), 0) im.save(f) def test_large(self): @@ -32,6 +37,11 @@ class LargeMemoryTest(PillowTestCase): """failed prepatch""" self._write_png(XDIM, XDIM) + @unittest.skipIf(numpy is None, "Numpy is not installed") + def test_size_greater_than_int(self): + arr = numpy.ndarray(shape=(16394, 16394)) + Image.fromarray(arr) -if __name__ == '__main__': + +if __name__ == "__main__": unittest.main() diff --git a/Tests/check_large_memory_numpy.py b/Tests/check_large_memory_numpy.py index b66988fd5..3607bcb97 100644 --- a/Tests/check_large_memory_numpy.py +++ b/Tests/check_large_memory_numpy.py @@ -11,6 +11,7 @@ from .helper import unittest, PillowTestCase # Raspberry Pis). from PIL import Image + try: import numpy as np except ImportError: @@ -20,14 +21,13 @@ YDIM = 32769 XDIM = 48000 -@unittest.skipIf(sys.maxsize <= 2**32, "requires 64-bit system") +@unittest.skipIf(sys.maxsize <= 2 ** 32, "requires 64-bit system") class LargeMemoryNumpyTest(PillowTestCase): - def _write_png(self, xdim, ydim): dtype = np.uint8 a = np.zeros((xdim, ydim), dtype=dtype) - f = self.tempfile('temp.png') - im = Image.fromarray(a, 'L') + f = self.tempfile("temp.png") + im = Image.fromarray(a, "L") im.save(f) def test_large(self): @@ -39,5 +39,5 @@ class LargeMemoryNumpyTest(PillowTestCase): self._write_png(XDIM, XDIM) -if __name__ == '__main__': +if __name__ == "__main__": unittest.main() diff --git a/Tests/check_libtiff_segfault.py b/Tests/check_libtiff_segfault.py index f8c4a3090..99e3056bf 100644 --- a/Tests/check_libtiff_segfault.py +++ b/Tests/check_libtiff_segfault.py @@ -15,5 +15,5 @@ class TestLibtiffSegfault(PillowTestCase): im.load() -if __name__ == '__main__': +if __name__ == "__main__": unittest.main() diff --git a/Tests/check_png_dos.py b/Tests/check_png_dos.py index 9a446bf84..edcb0d952 100644 --- a/Tests/check_png_dos.py +++ b/Tests/check_png_dos.py @@ -17,10 +17,10 @@ class TestPngDos(PillowTestCase): ImageFile.LOAD_TRUNCATED_IMAGES = False for s in im.text.values(): - self.assertLess(len(s), 1024*1024, "Text chunk larger than 1M") + self.assertLess(len(s), 1024 * 1024, "Text chunk larger than 1M") for s in im.info.values(): - self.assertLess(len(s), 1024*1024, "Text chunk larger than 1M") + self.assertLess(len(s), 1024 * 1024, "Text chunk larger than 1M") def test_dos_text(self): @@ -32,20 +32,20 @@ class TestPngDos(PillowTestCase): return for s in im.text.values(): - self.assertLess(len(s), 1024*1024, "Text chunk larger than 1M") + self.assertLess(len(s), 1024 * 1024, "Text chunk larger than 1M") def test_dos_total_memory(self): - im = Image.new('L', (1, 1)) - compressed_data = zlib.compress(b'a'*1024*1023) + im = Image.new("L", (1, 1)) + compressed_data = zlib.compress(b"a" * 1024 * 1023) info = PngImagePlugin.PngInfo() for x in range(64): - info.add_text('t%s' % x, compressed_data, zip=True) - info.add_itxt('i%s' % x, compressed_data, zip=True) + info.add_text("t%s" % x, compressed_data, zip=True) + info.add_itxt("i%s" % x, compressed_data, zip=True) b = BytesIO() - im.save(b, 'PNG', pnginfo=info) + im.save(b, "PNG", pnginfo=info) b.seek(0) try: @@ -57,9 +57,10 @@ class TestPngDos(PillowTestCase): total_len = 0 for txt in im2.text.values(): total_len += len(txt) - self.assertLess(total_len, 64*1024*1024, - "Total text chunks greater than 64M") + self.assertLess( + total_len, 64 * 1024 * 1024, "Total text chunks greater than 64M" + ) -if __name__ == '__main__': +if __name__ == "__main__": unittest.main() diff --git a/Tests/fonts/AdobeVFPrototype.ttf b/Tests/fonts/AdobeVFPrototype.ttf new file mode 100644 index 000000000..64f5ea8e1 Binary files /dev/null and b/Tests/fonts/AdobeVFPrototype.ttf differ diff --git a/Tests/fonts/ArefRuqaa-Regular.ttf b/Tests/fonts/ArefRuqaa-Regular.ttf new file mode 100644 index 000000000..940cb58f4 Binary files /dev/null and b/Tests/fonts/ArefRuqaa-Regular.ttf differ diff --git a/Tests/fonts/KhmerOSBattambang-Regular.ttf b/Tests/fonts/KhmerOSBattambang-Regular.ttf new file mode 100755 index 000000000..b812c0af1 Binary files /dev/null and b/Tests/fonts/KhmerOSBattambang-Regular.ttf differ diff --git a/Tests/fonts/LICENSE.txt b/Tests/fonts/LICENSE.txt index ee9daee59..0e0baaabb 100644 --- a/Tests/fonts/LICENSE.txt +++ b/Tests/fonts/LICENSE.txt @@ -1,13 +1,13 @@ -NotoNastaliqUrdu-Regular.ttf: +NotoNastaliqUrdu-Regular.ttf, from https://github.com/googlei18n/noto-fonts +NotoSansJP-Thin.otf, from https://www.google.com/get/noto/help/cjk/ +AdobeVFPrototype.ttf, from https://github.com/adobe-fonts/adobe-variable-font-prototype +TINY5x3GX.ttf, from http://velvetyne.fr/fonts/tiny +ArefRuqaa-Regular.ttf, from https://github.com/google/fonts/tree/master/ofl/arefruqaa -(from https://github.com/googlei18n/noto-fonts) - -All Noto fonts are published under the SIL Open Font License (OFL) v1.1 (http://scripts.sil.org/cms/scripts/page.php?site_id=nrsi&id=OFL), which allows you to copy, modify, and redistribute them if you need to. +All of the above fonts are published under the SIL Open Font License (OFL) v1.1 (http://scripts.sil.org/cms/scripts/page.php?site_id=nrsi&id=OFL), which allows you to copy, modify, and redistribute them if you need to. -10x20-ISO8859-1.pcf - -(from https://packages.ubuntu.com/xenial/xfonts-base) +10x20-ISO8859-1.pcf, from https://packages.ubuntu.com/xenial/xfonts-base "Public domain font. Share and enjoy." diff --git a/Tests/fonts/NotoSansJP-Regular.otf b/Tests/fonts/NotoSansJP-Regular.otf new file mode 100644 index 000000000..fbccd9f16 Binary files /dev/null and b/Tests/fonts/NotoSansJP-Regular.otf differ diff --git a/Tests/fonts/TINY5x3GX.ttf b/Tests/fonts/TINY5x3GX.ttf new file mode 100755 index 000000000..bd6e208de Binary files /dev/null and b/Tests/fonts/TINY5x3GX.ttf differ diff --git a/Tests/helper.py b/Tests/helper.py index b47604a60..7038566bf 100644 --- a/Tests/helper.py +++ b/Tests/helper.py @@ -11,12 +11,13 @@ from PIL import Image, ImageMath from PIL._util import py3 import logging + logger = logging.getLogger(__name__) HAS_UPLOADER = False -if os.environ.get('SHOW_ERRORS', None): +if os.environ.get("SHOW_ERRORS", None): # local img.show for errors. HAS_UPLOADER = True @@ -25,9 +26,12 @@ if os.environ.get('SHOW_ERRORS', None): def upload(self, a, b): a.show() b.show() + + else: try: import test_image_results + HAS_UPLOADER = True except ImportError: pass @@ -35,19 +39,18 @@ else: def convert_to_comparable(a, b): new_a, new_b = a, b - if a.mode == 'P': - new_a = Image.new('L', a.size) - new_b = Image.new('L', b.size) + if a.mode == "P": + new_a = Image.new("L", a.size) + new_b = Image.new("L", b.size) new_a.putdata(a.getdata()) new_b.putdata(b.getdata()) - elif a.mode == 'I;16': - new_a = a.convert('I') - new_b = b.convert('I') + elif a.mode == "I;16": + new_a = a.convert("I") + new_b = b.convert("I") return new_a, new_b class PillowTestCase(unittest.TestCase): - def __init__(self, *args, **kwargs): unittest.TestCase.__init__(self, *args, **kwargs) # holds last result object passed to run method: @@ -75,32 +78,32 @@ class PillowTestCase(unittest.TestCase): def assert_deep_equal(self, a, b, msg=None): try: self.assertEqual( - len(a), len(b), - msg or "got length %s, expected %s" % (len(a), len(b))) + len(a), len(b), msg or "got length %s, expected %s" % (len(a), len(b)) + ) self.assertTrue( - all(x == y for x, y in zip(a, b)), - msg or "got %s, expected %s" % (a, b)) + all(x == y for x, y in zip(a, b)), msg or "got %s, expected %s" % (a, b) + ) except Exception: self.assertEqual(a, b, msg) def assert_image(self, im, mode, size, msg=None): if mode is not None: self.assertEqual( - im.mode, mode, - msg or "got mode %r, expected %r" % (im.mode, mode)) + im.mode, mode, msg or "got mode %r, expected %r" % (im.mode, mode) + ) if size is not None: self.assertEqual( - im.size, size, - msg or "got size %r, expected %r" % (im.size, size)) + im.size, size, msg or "got size %r, expected %r" % (im.size, size) + ) def assert_image_equal(self, a, b, msg=None): self.assertEqual( - a.mode, b.mode, - msg or "got mode %r, expected %r" % (a.mode, b.mode)) + a.mode, b.mode, msg or "got mode %r, expected %r" % (a.mode, b.mode) + ) self.assertEqual( - a.size, b.size, - msg or "got size %r, expected %r" % (a.size, b.size)) + a.size, b.size, msg or "got size %r, expected %r" % (a.size, b.size) + ) if a.tobytes() != b.tobytes(): if HAS_UPLOADER: try: @@ -120,26 +123,28 @@ class PillowTestCase(unittest.TestCase): def assert_image_similar(self, a, b, epsilon, msg=None): epsilon = float(epsilon) self.assertEqual( - a.mode, b.mode, - msg or "got mode %r, expected %r" % (a.mode, b.mode)) + a.mode, b.mode, msg or "got mode %r, expected %r" % (a.mode, b.mode) + ) self.assertEqual( - a.size, b.size, - msg or "got size %r, expected %r" % (a.size, b.size)) + a.size, b.size, msg or "got size %r, expected %r" % (a.size, b.size) + ) a, b = convert_to_comparable(a, b) diff = 0 for ach, bch in zip(a.split(), b.split()): - chdiff = ImageMath.eval("abs(a - b)", a=ach, b=bch).convert('L') + chdiff = ImageMath.eval("abs(a - b)", a=ach, b=bch).convert("L") diff += sum(i * num for i, num in enumerate(chdiff.histogram())) - ave_diff = float(diff)/(a.size[0]*a.size[1]) + ave_diff = float(diff) / (a.size[0] * a.size[1]) try: self.assertGreaterEqual( - epsilon, ave_diff, - (msg or '') + - " average pixel value difference %.4f > epsilon %.4f" % ( - ave_diff, epsilon)) + epsilon, + ave_diff, + (msg or "") + + " average pixel value difference %.4f > epsilon %.4f" + % (ave_diff, epsilon), + ) except Exception as e: if HAS_UPLOADER: try: @@ -149,8 +154,7 @@ class PillowTestCase(unittest.TestCase): pass raise e - def assert_image_similar_tofile(self, a, filename, epsilon, msg=None, - mode=None): + def assert_image_similar_tofile(self, a, filename, epsilon, msg=None, mode=None): with Image.open(filename) as img: if mode: img = img.convert(mode) @@ -168,9 +172,9 @@ class PillowTestCase(unittest.TestCase): # Verify some things. if warn_class is None: - self.assertEqual(len(w), 0, - "Expected no warnings, got %s" % - [v.category for v in w]) + self.assertEqual( + len(w), 0, "Expected no warnings, got %s" % [v.category for v in w] + ) else: self.assertGreaterEqual(len(w), 1) found = False @@ -192,27 +196,26 @@ class PillowTestCase(unittest.TestCase): value = True for i, target in enumerate(targets): - value *= (target - threshold <= actuals[i] <= target + threshold) + value *= target - threshold <= actuals[i] <= target + threshold - self.assertTrue(value, - msg + ': ' + repr(actuals) + ' != ' + repr(targets)) + self.assertTrue(value, msg + ": " + repr(actuals) + " != " + repr(targets)) - def skipKnownBadTest(self, msg=None, platform=None, - travis=None, interpreter=None): + def skipKnownBadTest(self, msg=None, platform=None, travis=None, interpreter=None): # Skip if platform/travis matches, and # PILLOW_RUN_KNOWN_BAD is not true in the environment. - if os.environ.get('PILLOW_RUN_KNOWN_BAD', False): - print(os.environ.get('PILLOW_RUN_KNOWN_BAD', False)) + if os.environ.get("PILLOW_RUN_KNOWN_BAD", False): + print(os.environ.get("PILLOW_RUN_KNOWN_BAD", False)) return skip = True if platform is not None: skip = sys.platform.startswith(platform) if travis is not None: - skip = skip and (travis == bool(os.environ.get('TRAVIS', False))) + skip = skip and (travis == bool(os.environ.get("TRAVIS", False))) if interpreter is not None: - skip = skip and (interpreter == 'pypy' and - hasattr(sys, 'pypy_version_info')) + skip = skip and ( + interpreter == "pypy" and hasattr(sys, "pypy_version_info") + ) if skip: self.skipTest(msg or "Known Bad Test") @@ -234,7 +237,7 @@ class PillowTestCase(unittest.TestCase): raise IOError() -@unittest.skipIf(sys.platform.startswith('win32'), "requires Unix or macOS") +@unittest.skipIf(sys.platform.startswith("win32"), "requires Unix or macOS") class PillowLeakTestCase(PillowTestCase): # requires unix/macOS iterations = 100 # count @@ -249,8 +252,9 @@ class PillowLeakTestCase(PillowTestCase): """ from resource import getrusage, RUSAGE_SELF + mem = getrusage(RUSAGE_SELF).ru_maxrss - if sys.platform == 'darwin': + if sys.platform == "darwin": # man 2 getrusage: # ru_maxrss # This is the maximum resident set size utilized (in bytes). @@ -266,8 +270,8 @@ class PillowLeakTestCase(PillowTestCase): start_mem = self._get_mem_usage() for cycle in range(self.iterations): core() - mem = (self._get_mem_usage() - start_mem) - msg = 'memory usage limit exceeded in iteration %d' % cycle + mem = self._get_mem_usage() - start_mem + msg = "memory usage limit exceeded in iteration %d" % cycle self.assertLess(mem, self.mem_limit, msg) @@ -281,11 +285,13 @@ if not py3: def fromstring(data): from io import BytesIO + return Image.open(BytesIO(data)) def tostring(im, string_format, **options): from io import BytesIO + out = BytesIO() im.save(out, string_format, **options) return out.getvalue() @@ -318,7 +324,8 @@ def command_succeeds(cmd): command succeeds, or False if an OSError was raised by subprocess.Popen. """ import subprocess - with open(os.devnull, 'wb') as f: + + with open(os.devnull, "wb") as f: try: subprocess.call(cmd, stdout=f, stderr=subprocess.STDOUT) except OSError: @@ -327,40 +334,41 @@ def command_succeeds(cmd): def djpeg_available(): - return command_succeeds(['djpeg', '-version']) + return command_succeeds(["djpeg", "-version"]) def cjpeg_available(): - return command_succeeds(['cjpeg', '-version']) + return command_succeeds(["cjpeg", "-version"]) def netpbm_available(): - return (command_succeeds(["ppmquant", "--version"]) and - command_succeeds(["ppmtogif", "--version"])) + return command_succeeds(["ppmquant", "--version"]) and command_succeeds( + ["ppmtogif", "--version"] + ) def imagemagick_available(): - return IMCONVERT and command_succeeds([IMCONVERT, '-version']) + return IMCONVERT and command_succeeds([IMCONVERT, "-version"]) def on_appveyor(): - return 'APPVEYOR' in os.environ + return "APPVEYOR" in os.environ -if sys.platform == 'win32': - IMCONVERT = os.environ.get('MAGICK_HOME', '') +if sys.platform == "win32": + IMCONVERT = os.environ.get("MAGICK_HOME", "") if IMCONVERT: - IMCONVERT = os.path.join(IMCONVERT, 'convert.exe') + IMCONVERT = os.path.join(IMCONVERT, "convert.exe") else: - IMCONVERT = 'convert' + IMCONVERT = "convert" def distro(): - if os.path.exists('/etc/os-release'): - with open('/etc/os-release', 'r') as f: + if os.path.exists("/etc/os-release"): + with open("/etc/os-release", "r") as f: for line in f: - if 'ID=' in line: - return line.strip().split('=')[1] + if "ID=" in line: + return line.strip().split("=")[1] class cached_property(object): diff --git a/Tests/images/app13.jpg b/Tests/images/app13.jpg new file mode 100644 index 000000000..b02d71b40 Binary files /dev/null and b/Tests/images/app13.jpg differ diff --git a/Tests/images/hopper_draw.ico b/Tests/images/hopper_draw.ico new file mode 100644 index 000000000..014711896 Binary files /dev/null and b/Tests/images/hopper_draw.ico differ diff --git a/Tests/images/hopper_unexpected.ico b/Tests/images/hopper_unexpected.ico new file mode 100644 index 000000000..639828ae0 Binary files /dev/null and b/Tests/images/hopper_unexpected.ico differ diff --git a/Tests/images/imagedraw_arc_width_pieslice.png b/Tests/images/imagedraw_arc_width_pieslice.png new file mode 100644 index 000000000..950d95dd6 Binary files /dev/null and b/Tests/images/imagedraw_arc_width_pieslice.png differ diff --git a/Tests/images/imagedraw_ellipse_width_large.png b/Tests/images/imagedraw_ellipse_width_large.png new file mode 100644 index 000000000..9d3c3326b Binary files /dev/null and b/Tests/images/imagedraw_ellipse_width_large.png differ diff --git a/Tests/images/test_complex_unicode_text2.png b/Tests/images/test_complex_unicode_text2.png new file mode 100644 index 000000000..543b174c0 Binary files /dev/null and b/Tests/images/test_complex_unicode_text2.png differ diff --git a/Tests/images/test_direction_ttb.png b/Tests/images/test_direction_ttb.png new file mode 100644 index 000000000..825f3213e Binary files /dev/null and b/Tests/images/test_direction_ttb.png differ diff --git a/Tests/images/test_extents.gif b/Tests/images/test_extents.gif new file mode 100644 index 000000000..03c436435 Binary files /dev/null and b/Tests/images/test_extents.gif differ diff --git a/Tests/images/test_x_max_and_y_offset.png b/Tests/images/test_x_max_and_y_offset.png new file mode 100644 index 000000000..f8bec3e95 Binary files /dev/null and b/Tests/images/test_x_max_and_y_offset.png differ diff --git a/Tests/images/test_y_offset.png b/Tests/images/test_y_offset.png index 5a166be8c..2d57890cb 100644 Binary files a/Tests/images/test_y_offset.png and b/Tests/images/test_y_offset.png differ diff --git a/Tests/images/tiff_16bit_RGB.tiff b/Tests/images/tiff_16bit_RGB.tiff new file mode 100644 index 000000000..5eb7c73c2 Binary files /dev/null and b/Tests/images/tiff_16bit_RGB.tiff differ diff --git a/Tests/images/tiff_16bit_RGB_target.png b/Tests/images/tiff_16bit_RGB_target.png new file mode 100644 index 000000000..923580004 Binary files /dev/null and b/Tests/images/tiff_16bit_RGB_target.png differ diff --git a/Tests/images/tiff_strip_cmyk_16l_jpeg.tif b/Tests/images/tiff_strip_cmyk_16l_jpeg.tif new file mode 100644 index 000000000..8bfd8bd6a Binary files /dev/null and b/Tests/images/tiff_strip_cmyk_16l_jpeg.tif differ diff --git a/Tests/images/variation_adobe.png b/Tests/images/variation_adobe.png new file mode 100644 index 000000000..71b879bc5 Binary files /dev/null and b/Tests/images/variation_adobe.png differ diff --git a/Tests/images/variation_adobe_axes.png b/Tests/images/variation_adobe_axes.png new file mode 100644 index 000000000..9376c1d7b Binary files /dev/null and b/Tests/images/variation_adobe_axes.png differ diff --git a/Tests/images/variation_adobe_name.png b/Tests/images/variation_adobe_name.png new file mode 100644 index 000000000..9e5fe70e5 Binary files /dev/null and b/Tests/images/variation_adobe_name.png differ diff --git a/Tests/images/variation_tiny.png b/Tests/images/variation_tiny.png new file mode 100644 index 000000000..a0ff3f594 Binary files /dev/null and b/Tests/images/variation_tiny.png differ diff --git a/Tests/images/variation_tiny_axes.png b/Tests/images/variation_tiny_axes.png new file mode 100644 index 000000000..d06ac7a60 Binary files /dev/null and b/Tests/images/variation_tiny_axes.png differ diff --git a/Tests/images/variation_tiny_name.png b/Tests/images/variation_tiny_name.png new file mode 100644 index 000000000..a0c6ffe3f Binary files /dev/null and b/Tests/images/variation_tiny_name.png differ diff --git a/Tests/import_all.py b/Tests/import_all.py index 11682237b..ccfc53aef 100644 --- a/Tests/import_all.py +++ b/Tests/import_all.py @@ -5,6 +5,7 @@ import os import traceback import sys + sys.path.insert(0, ".") for file in glob.glob("src/PIL/*.py"): diff --git a/Tests/make_hash.py b/Tests/make_hash.py index c52e009ec..bacb391fa 100644 --- a/Tests/make_hash.py +++ b/Tests/make_hash.py @@ -4,21 +4,33 @@ from __future__ import print_function modes = [ "1", - "L", "LA", "La", - "I", "I;16", "I;16L", "I;16B", "I;32L", "I;32B", + "L", + "LA", + "La", + "I", + "I;16", + "I;16L", + "I;16B", + "I;32L", + "I;32B", "F", - "P", "PA", - "RGB", "RGBA", "RGBa", "RGBX", + "P", + "PA", + "RGB", + "RGBA", + "RGBa", + "RGBX", "CMYK", "YCbCr", - "LAB", "HSV", - ] + "LAB", + "HSV", +] def hash(s, i): # djb2 hash: multiply by 33 and xor character for c in s: - i = (((i << 5) + i) ^ ord(c)) & 0xffffffff + i = (((i << 5) + i) ^ ord(c)) & 0xFFFFFFFF return i diff --git a/Tests/test_000_sanity.py b/Tests/test_000_sanity.py index b9dd413f9..0dccb6f59 100644 --- a/Tests/test_000_sanity.py +++ b/Tests/test_000_sanity.py @@ -5,7 +5,6 @@ import PIL.Image class TestSanity(PillowTestCase): - def test_sanity(self): # Make sure we have the binary extension @@ -13,7 +12,7 @@ class TestSanity(PillowTestCase): # Create an image and do stuff with it. im = PIL.Image.new("1", (100, 100)) - self.assertEqual((im.mode, im.size), ('1', (100, 100))) + self.assertEqual((im.mode, im.size), ("1", (100, 100))) self.assertEqual(len(im.tobytes()), 1300) # Create images in all remaining major modes. diff --git a/Tests/test_binary.py b/Tests/test_binary.py index bf9ba1e5f..8b46192cd 100644 --- a/Tests/test_binary.py +++ b/Tests/test_binary.py @@ -4,21 +4,20 @@ from PIL import _binary class TestBinary(PillowTestCase): - def test_standard(self): - self.assertEqual(_binary.i8(b'*'), 42) - self.assertEqual(_binary.o8(42), b'*') + self.assertEqual(_binary.i8(b"*"), 42) + self.assertEqual(_binary.o8(42), b"*") def test_little_endian(self): - self.assertEqual(_binary.i16le(b'\xff\xff\x00\x00'), 65535) - self.assertEqual(_binary.i32le(b'\xff\xff\x00\x00'), 65535) + self.assertEqual(_binary.i16le(b"\xff\xff\x00\x00"), 65535) + self.assertEqual(_binary.i32le(b"\xff\xff\x00\x00"), 65535) - self.assertEqual(_binary.o16le(65535), b'\xff\xff') - self.assertEqual(_binary.o32le(65535), b'\xff\xff\x00\x00') + self.assertEqual(_binary.o16le(65535), b"\xff\xff") + self.assertEqual(_binary.o32le(65535), b"\xff\xff\x00\x00") def test_big_endian(self): - self.assertEqual(_binary.i16be(b'\x00\x00\xff\xff'), 0) - self.assertEqual(_binary.i32be(b'\x00\x00\xff\xff'), 65535) + self.assertEqual(_binary.i16be(b"\x00\x00\xff\xff"), 0) + self.assertEqual(_binary.i32be(b"\x00\x00\xff\xff"), 65535) - self.assertEqual(_binary.o16be(65535), b'\xff\xff') - self.assertEqual(_binary.o32be(65535), b'\x00\x00\xff\xff') + self.assertEqual(_binary.o16be(65535), b"\xff\xff") + self.assertEqual(_binary.o32be(65535), b"\x00\x00\xff\xff") diff --git a/Tests/test_bmp_reference.py b/Tests/test_bmp_reference.py index 0e32c93dd..fcdf20e5a 100644 --- a/Tests/test_bmp_reference.py +++ b/Tests/test_bmp_reference.py @@ -4,19 +4,22 @@ from .helper import PillowTestCase from PIL import Image import os -base = os.path.join('Tests', 'images', 'bmp') +base = os.path.join("Tests", "images", "bmp") class TestBmpReference(PillowTestCase): - - def get_files(self, d, ext='.bmp'): - return [os.path.join(base, d, f) for f - in os.listdir(os.path.join(base, d)) if ext in f] + def get_files(self, d, ext=".bmp"): + return [ + os.path.join(base, d, f) + for f in os.listdir(os.path.join(base, d)) + if ext in f + ] def test_bad(self): """ These shouldn't crash/dos, but they shouldn't return anything either """ - for f in self.get_files('b'): + for f in self.get_files("b"): + def open(f): try: im = Image.open(f) @@ -41,13 +44,12 @@ class TestBmpReference(PillowTestCase): "pal8os2sp.bmp", "rgb32bf-xbgr.bmp", ] - for f in self.get_files('q'): + for f in self.get_files("q"): try: im = Image.open(f) im.load() if os.path.basename(f) not in supported: - print("Please add %s to the partially supported" - " bmp specs." % f) + print("Please add %s to the partially supported bmp specs." % f) except Exception: # as msg: if os.path.basename(f) in supported: raise @@ -57,49 +59,52 @@ class TestBmpReference(PillowTestCase): html directory that we can compare against. """ # Target files, if they're not just replacing the extension - file_map = {'pal1wb.bmp': 'pal1.png', - 'pal4rle.bmp': 'pal4.png', - 'pal8-0.bmp': 'pal8.png', - 'pal8rle.bmp': 'pal8.png', - 'pal8topdown.bmp': 'pal8.png', - 'pal8nonsquare.bmp': 'pal8nonsquare-v.png', - 'pal8os2.bmp': 'pal8.png', - 'pal8os2sp.bmp': 'pal8.png', - 'pal8os2v2.bmp': 'pal8.png', - 'pal8os2v2-16.bmp': 'pal8.png', - 'pal8v4.bmp': 'pal8.png', - 'pal8v5.bmp': 'pal8.png', - 'rgb16-565pal.bmp': 'rgb16-565.png', - 'rgb24pal.bmp': 'rgb24.png', - 'rgb32.bmp': 'rgb24.png', - 'rgb32bf.bmp': 'rgb24.png' - } + file_map = { + "pal1wb.bmp": "pal1.png", + "pal4rle.bmp": "pal4.png", + "pal8-0.bmp": "pal8.png", + "pal8rle.bmp": "pal8.png", + "pal8topdown.bmp": "pal8.png", + "pal8nonsquare.bmp": "pal8nonsquare-v.png", + "pal8os2.bmp": "pal8.png", + "pal8os2sp.bmp": "pal8.png", + "pal8os2v2.bmp": "pal8.png", + "pal8os2v2-16.bmp": "pal8.png", + "pal8v4.bmp": "pal8.png", + "pal8v5.bmp": "pal8.png", + "rgb16-565pal.bmp": "rgb16-565.png", + "rgb24pal.bmp": "rgb24.png", + "rgb32.bmp": "rgb24.png", + "rgb32bf.bmp": "rgb24.png", + } def get_compare(f): name = os.path.split(f)[1] if name in file_map: - return os.path.join(base, 'html', file_map[name]) + return os.path.join(base, "html", file_map[name]) name = os.path.splitext(name)[0] - return os.path.join(base, 'html', "%s.png" % name) + return os.path.join(base, "html", "%s.png" % name) - for f in self.get_files('g'): + for f in self.get_files("g"): try: im = Image.open(f) im.load() compare = Image.open(get_compare(f)) compare.load() - if im.mode == 'P': + if im.mode == "P": # assert image similar doesn't really work # with paletized image, since the palette might # be differently ordered for an equivalent image. - im = im.convert('RGBA') - compare = im.convert('RGBA') + im = im.convert("RGBA") + compare = im.convert("RGBA") self.assert_image_similar(im, compare, 5) except Exception as msg: # there are three here that are unsupported: - unsupported = (os.path.join(base, 'g', 'rgb32bf.bmp'), - os.path.join(base, 'g', 'pal8rle.bmp'), - os.path.join(base, 'g', 'pal4rle.bmp')) + unsupported = ( + os.path.join(base, "g", "rgb32bf.bmp"), + os.path.join(base, "g", "pal8rle.bmp"), + os.path.join(base, "g", "pal4rle.bmp"), + ) if f not in unsupported: self.fail("Unsupported Image %s: %s" % (f, msg)) diff --git a/Tests/test_box_blur.py b/Tests/test_box_blur.py index b9939c5f2..ae8985580 100644 --- a/Tests/test_box_blur.py +++ b/Tests/test_box_blur.py @@ -4,6 +4,7 @@ from PIL import Image, ImageFilter sample = Image.new("L", (7, 5)) +# fmt: off sample.putdata(sum([ [210, 50, 20, 10, 220, 230, 80], [190, 210, 20, 180, 170, 40, 110], @@ -11,10 +12,10 @@ sample.putdata(sum([ [220, 40, 230, 80, 130, 250, 40], [250, 0, 80, 30, 60, 20, 110], ], [])) +# fmt: on class TestBoxBlurApi(PillowTestCase): - def test_imageops_box_blur(self): i = sample.filter(ImageFilter.BoxBlur(1)) self.assertEqual(i.mode, sample.mode) @@ -23,7 +24,6 @@ class TestBoxBlurApi(PillowTestCase): class TestBoxBlur(PillowTestCase): - def box_blur(self, image, radius=1, n=1): return image._new(image.im.box_blur(radius, n)) @@ -32,8 +32,7 @@ class TestBoxBlur(PillowTestCase): for data_row in data: im_row = [next(it) for _ in range(im.size[0])] if any( - abs(data_v - im_v) > delta - for data_v, im_v in zip(data_row, im_row) + abs(data_v - im_v) > delta for data_v, im_v in zip(data_row, im_row) ): self.assertEqual(im_row, data_row) self.assertRaises(StopIteration, next, it) @@ -41,7 +40,7 @@ class TestBoxBlur(PillowTestCase): def assertBlur(self, im, radius, data, passes=1, delta=0): # check grayscale image self.assertImage(self.box_blur(im, radius, passes), data, delta) - rgba = Image.merge('RGBA', (im, im, im, im)) + rgba = Image.merge("RGBA", (im, im, im, im)) for band in self.box_blur(rgba, radius, passes).split(): self.assertImage(band, data, delta) @@ -61,110 +60,135 @@ class TestBoxBlur(PillowTestCase): def test_radius_0(self): self.assertBlur( - sample, 0, + sample, + 0, [ + # fmt: off [210, 50, 20, 10, 220, 230, 80], [190, 210, 20, 180, 170, 40, 110], [120, 210, 250, 60, 220, 0, 220], [220, 40, 230, 80, 130, 250, 40], [250, 0, 80, 30, 60, 20, 110], - ] + # fmt: on + ], ) def test_radius_0_02(self): self.assertBlur( - sample, 0.02, + sample, + 0.02, [ + # fmt: off [206, 55, 20, 17, 215, 223, 83], [189, 203, 31, 171, 169, 46, 110], [125, 206, 241, 69, 210, 13, 210], [215, 49, 221, 82, 131, 235, 48], [244, 7, 80, 32, 60, 27, 107], + # fmt: on ], delta=2, ) def test_radius_0_05(self): self.assertBlur( - sample, 0.05, + sample, + 0.05, [ + # fmt: off [202, 62, 22, 27, 209, 215, 88], [188, 194, 44, 161, 168, 56, 111], [131, 201, 229, 81, 198, 31, 198], [209, 62, 209, 86, 133, 216, 59], [237, 17, 80, 36, 60, 35, 103], + # fmt: on ], delta=2, ) def test_radius_0_1(self): self.assertBlur( - sample, 0.1, + sample, + 0.1, [ + # fmt: off [196, 72, 24, 40, 200, 203, 93], [187, 183, 62, 148, 166, 68, 111], [139, 193, 213, 96, 182, 54, 182], [201, 78, 193, 91, 133, 191, 73], [227, 31, 80, 42, 61, 47, 99], + # fmt: on ], delta=1, ) def test_radius_0_5(self): self.assertBlur( - sample, 0.5, + sample, + 0.5, [ + # fmt: off [176, 101, 46, 83, 163, 165, 111], [176, 149, 108, 122, 144, 120, 117], [164, 171, 159, 141, 134, 119, 129], [170, 136, 133, 114, 116, 124, 109], [184, 95, 72, 70, 69, 81, 89], + # fmt: on ], delta=1, ) def test_radius_1(self): self.assertBlur( - sample, 1, + sample, + 1, [ + # fmt: off [170, 109, 63, 97, 146, 153, 116], [168, 142, 112, 128, 126, 143, 121], [169, 166, 142, 149, 126, 131, 114], [159, 156, 109, 127, 94, 117, 112], [164, 128, 63, 87, 76, 89, 90], + # fmt: on ], delta=1, ) def test_radius_1_5(self): self.assertBlur( - sample, 1.5, + sample, + 1.5, [ + # fmt: off [155, 120, 105, 112, 124, 137, 130], [160, 136, 124, 125, 127, 134, 130], [166, 147, 130, 125, 120, 121, 119], [168, 145, 119, 109, 103, 105, 110], [168, 134, 96, 85, 85, 89, 97], + # fmt: on ], delta=1, ) def test_radius_bigger_then_half(self): self.assertBlur( - sample, 3, + sample, + 3, [ + # fmt: off [144, 145, 142, 128, 114, 115, 117], [148, 145, 137, 122, 109, 111, 112], [152, 145, 131, 117, 103, 107, 108], [156, 144, 126, 111, 97, 102, 103], [160, 144, 121, 106, 92, 98, 99], + # fmt: on ], delta=1, ) def test_radius_bigger_then_width(self): self.assertBlur( - sample, 10, + sample, + 10, [ [158, 153, 147, 141, 135, 129, 123], [159, 153, 147, 141, 136, 130, 124], @@ -175,9 +199,10 @@ class TestBoxBlur(PillowTestCase): delta=0, ) - def test_exteme_large_radius(self): + def test_extreme_large_radius(self): self.assertBlur( - sample, 600, + sample, + 600, [ [162, 162, 162, 162, 162, 162, 162], [162, 162, 162, 162, 162, 162, 162], @@ -190,13 +215,16 @@ class TestBoxBlur(PillowTestCase): def test_two_passes(self): self.assertBlur( - sample, 1, + sample, + 1, [ + # fmt: off [153, 123, 102, 109, 132, 135, 129], [159, 138, 123, 121, 133, 131, 126], [162, 147, 136, 124, 127, 121, 121], [159, 140, 125, 108, 111, 106, 108], [154, 126, 105, 87, 94, 93, 97], + # fmt: on ], passes=2, delta=1, @@ -204,13 +232,16 @@ class TestBoxBlur(PillowTestCase): def test_three_passes(self): self.assertBlur( - sample, 1, + sample, + 1, [ + # fmt: off [146, 131, 116, 118, 126, 131, 130], [151, 138, 125, 123, 126, 128, 127], [154, 143, 129, 123, 120, 120, 119], [152, 139, 122, 113, 108, 108, 108], [148, 132, 112, 102, 97, 99, 100], + # fmt: on ], passes=3, delta=1, diff --git a/Tests/test_color_lut.py b/Tests/test_color_lut.py index 97035c793..6208b6a80 100644 --- a/Tests/test_color_lut.py +++ b/Tests/test_color_lut.py @@ -20,185 +20,197 @@ class TestColorLut3DCoreAPI(PillowTestCase): table = [ [ - r / float(size1D-1) if size1D != 1 else 0, - g / float(size2D-1) if size2D != 1 else 0, - b / float(size3D-1) if size3D != 1 else 0, - r / float(size1D-1) if size1D != 1 else 0, - g / float(size2D-1) if size2D != 1 else 0, + r / float(size1D - 1) if size1D != 1 else 0, + g / float(size2D - 1) if size2D != 1 else 0, + b / float(size3D - 1) if size3D != 1 else 0, + r / float(size1D - 1) if size1D != 1 else 0, + g / float(size2D - 1) if size2D != 1 else 0, ][:channels] for b in range(size3D) for g in range(size2D) for r in range(size1D) ] return ( - channels, size1D, size2D, size3D, - [item for sublist in table for item in sublist]) + channels, + size1D, + size2D, + size3D, + [item for sublist in table for item in sublist], + ) def test_wrong_args(self): - im = Image.new('RGB', (10, 10), 0) + im = Image.new("RGB", (10, 10), 0) with self.assertRaisesRegex(ValueError, "filter"): - im.im.color_lut_3d('RGB', - Image.CUBIC, - *self.generate_identity_table(3, 3)) + im.im.color_lut_3d("RGB", Image.CUBIC, *self.generate_identity_table(3, 3)) with self.assertRaisesRegex(ValueError, "image mode"): - im.im.color_lut_3d('wrong', - Image.LINEAR, - *self.generate_identity_table(3, 3)) + im.im.color_lut_3d( + "wrong", Image.LINEAR, *self.generate_identity_table(3, 3) + ) with self.assertRaisesRegex(ValueError, "table_channels"): - im.im.color_lut_3d('RGB', - Image.LINEAR, - *self.generate_identity_table(5, 3)) + im.im.color_lut_3d("RGB", Image.LINEAR, *self.generate_identity_table(5, 3)) with self.assertRaisesRegex(ValueError, "table_channels"): - im.im.color_lut_3d('RGB', - Image.LINEAR, - *self.generate_identity_table(1, 3)) + im.im.color_lut_3d("RGB", Image.LINEAR, *self.generate_identity_table(1, 3)) with self.assertRaisesRegex(ValueError, "table_channels"): - im.im.color_lut_3d('RGB', - Image.LINEAR, - *self.generate_identity_table(2, 3)) + im.im.color_lut_3d("RGB", Image.LINEAR, *self.generate_identity_table(2, 3)) with self.assertRaisesRegex(ValueError, "Table size"): - im.im.color_lut_3d('RGB', - Image.LINEAR, - *self.generate_identity_table(3, (1, 3, 3))) + im.im.color_lut_3d( + "RGB", Image.LINEAR, *self.generate_identity_table(3, (1, 3, 3)) + ) with self.assertRaisesRegex(ValueError, "Table size"): - im.im.color_lut_3d('RGB', - Image.LINEAR, - *self.generate_identity_table(3, (66, 3, 3))) + im.im.color_lut_3d( + "RGB", Image.LINEAR, *self.generate_identity_table(3, (66, 3, 3)) + ) with self.assertRaisesRegex(ValueError, r"size1D \* size2D \* size3D"): - im.im.color_lut_3d('RGB', - Image.LINEAR, - 3, 2, 2, 2, [0, 0, 0] * 7) + im.im.color_lut_3d("RGB", Image.LINEAR, 3, 2, 2, 2, [0, 0, 0] * 7) with self.assertRaisesRegex(ValueError, r"size1D \* size2D \* size3D"): - im.im.color_lut_3d('RGB', - Image.LINEAR, - 3, 2, 2, 2, [0, 0, 0] * 9) + im.im.color_lut_3d("RGB", Image.LINEAR, 3, 2, 2, 2, [0, 0, 0] * 9) with self.assertRaises(TypeError): - im.im.color_lut_3d('RGB', - Image.LINEAR, - 3, 2, 2, 2, [0, 0, "0"] * 8) + im.im.color_lut_3d("RGB", Image.LINEAR, 3, 2, 2, 2, [0, 0, "0"] * 8) with self.assertRaises(TypeError): - im.im.color_lut_3d('RGB', - Image.LINEAR, - 3, 2, 2, 2, 16) + im.im.color_lut_3d("RGB", Image.LINEAR, 3, 2, 2, 2, 16) def test_correct_args(self): - im = Image.new('RGB', (10, 10), 0) + im = Image.new("RGB", (10, 10), 0) - im.im.color_lut_3d('RGB', Image.LINEAR, - *self.generate_identity_table(3, 3)) + im.im.color_lut_3d("RGB", Image.LINEAR, *self.generate_identity_table(3, 3)) - im.im.color_lut_3d('CMYK', Image.LINEAR, - *self.generate_identity_table(4, 3)) + im.im.color_lut_3d("CMYK", Image.LINEAR, *self.generate_identity_table(4, 3)) - im.im.color_lut_3d('RGB', Image.LINEAR, - *self.generate_identity_table(3, (2, 3, 3))) + im.im.color_lut_3d( + "RGB", Image.LINEAR, *self.generate_identity_table(3, (2, 3, 3)) + ) - im.im.color_lut_3d('RGB', Image.LINEAR, - *self.generate_identity_table(3, (65, 3, 3))) + im.im.color_lut_3d( + "RGB", Image.LINEAR, *self.generate_identity_table(3, (65, 3, 3)) + ) - im.im.color_lut_3d('RGB', Image.LINEAR, - *self.generate_identity_table(3, (3, 65, 3))) + im.im.color_lut_3d( + "RGB", Image.LINEAR, *self.generate_identity_table(3, (3, 65, 3)) + ) - im.im.color_lut_3d('RGB', Image.LINEAR, - *self.generate_identity_table(3, (3, 3, 65))) + im.im.color_lut_3d( + "RGB", Image.LINEAR, *self.generate_identity_table(3, (3, 3, 65)) + ) def test_wrong_mode(self): with self.assertRaisesRegex(ValueError, "wrong mode"): - im = Image.new('L', (10, 10), 0) - im.im.color_lut_3d('RGB', Image.LINEAR, - *self.generate_identity_table(3, 3)) + im = Image.new("L", (10, 10), 0) + im.im.color_lut_3d("RGB", Image.LINEAR, *self.generate_identity_table(3, 3)) with self.assertRaisesRegex(ValueError, "wrong mode"): - im = Image.new('RGB', (10, 10), 0) - im.im.color_lut_3d('L', Image.LINEAR, - *self.generate_identity_table(3, 3)) + im = Image.new("RGB", (10, 10), 0) + im.im.color_lut_3d("L", Image.LINEAR, *self.generate_identity_table(3, 3)) with self.assertRaisesRegex(ValueError, "wrong mode"): - im = Image.new('L', (10, 10), 0) - im.im.color_lut_3d('L', Image.LINEAR, - *self.generate_identity_table(3, 3)) + im = Image.new("L", (10, 10), 0) + im.im.color_lut_3d("L", Image.LINEAR, *self.generate_identity_table(3, 3)) with self.assertRaisesRegex(ValueError, "wrong mode"): - im = Image.new('RGB', (10, 10), 0) - im.im.color_lut_3d('RGBA', Image.LINEAR, - *self.generate_identity_table(3, 3)) + im = Image.new("RGB", (10, 10), 0) + im.im.color_lut_3d( + "RGBA", Image.LINEAR, *self.generate_identity_table(3, 3) + ) with self.assertRaisesRegex(ValueError, "wrong mode"): - im = Image.new('RGB', (10, 10), 0) - im.im.color_lut_3d('RGB', Image.LINEAR, - *self.generate_identity_table(4, 3)) + im = Image.new("RGB", (10, 10), 0) + im.im.color_lut_3d("RGB", Image.LINEAR, *self.generate_identity_table(4, 3)) def test_correct_mode(self): - im = Image.new('RGBA', (10, 10), 0) - im.im.color_lut_3d('RGBA', Image.LINEAR, - *self.generate_identity_table(3, 3)) + im = Image.new("RGBA", (10, 10), 0) + im.im.color_lut_3d("RGBA", Image.LINEAR, *self.generate_identity_table(3, 3)) - im = Image.new('RGBA', (10, 10), 0) - im.im.color_lut_3d('RGBA', Image.LINEAR, - *self.generate_identity_table(4, 3)) + im = Image.new("RGBA", (10, 10), 0) + im.im.color_lut_3d("RGBA", Image.LINEAR, *self.generate_identity_table(4, 3)) - im = Image.new('RGB', (10, 10), 0) - im.im.color_lut_3d('HSV', Image.LINEAR, - *self.generate_identity_table(3, 3)) + im = Image.new("RGB", (10, 10), 0) + im.im.color_lut_3d("HSV", Image.LINEAR, *self.generate_identity_table(3, 3)) - im = Image.new('RGB', (10, 10), 0) - im.im.color_lut_3d('RGBA', Image.LINEAR, - *self.generate_identity_table(4, 3)) + im = Image.new("RGB", (10, 10), 0) + im.im.color_lut_3d("RGBA", Image.LINEAR, *self.generate_identity_table(4, 3)) def test_identities(self): - g = Image.linear_gradient('L') - im = Image.merge('RGB', [g, g.transpose(Image.ROTATE_90), - g.transpose(Image.ROTATE_180)]) + g = Image.linear_gradient("L") + im = Image.merge( + "RGB", [g, g.transpose(Image.ROTATE_90), g.transpose(Image.ROTATE_180)] + ) # Fast test with small cubes for size in [2, 3, 5, 7, 11, 16, 17]: - self.assert_image_equal(im, im._new( - im.im.color_lut_3d('RGB', Image.LINEAR, - *self.generate_identity_table(3, size)))) + self.assert_image_equal( + im, + im._new( + im.im.color_lut_3d( + "RGB", Image.LINEAR, *self.generate_identity_table(3, size) + ) + ), + ) # Not so fast - self.assert_image_equal(im, im._new( - im.im.color_lut_3d('RGB', Image.LINEAR, - *self.generate_identity_table(3, (2, 2, 65))))) + self.assert_image_equal( + im, + im._new( + im.im.color_lut_3d( + "RGB", Image.LINEAR, *self.generate_identity_table(3, (2, 2, 65)) + ) + ), + ) def test_identities_4_channels(self): - g = Image.linear_gradient('L') - im = Image.merge('RGB', [g, g.transpose(Image.ROTATE_90), - g.transpose(Image.ROTATE_180)]) + g = Image.linear_gradient("L") + im = Image.merge( + "RGB", [g, g.transpose(Image.ROTATE_90), g.transpose(Image.ROTATE_180)] + ) # Red channel copied to alpha self.assert_image_equal( - Image.merge('RGBA', (im.split()*2)[:4]), - im._new(im.im.color_lut_3d('RGBA', Image.LINEAR, - *self.generate_identity_table(4, 17)))) + Image.merge("RGBA", (im.split() * 2)[:4]), + im._new( + im.im.color_lut_3d( + "RGBA", Image.LINEAR, *self.generate_identity_table(4, 17) + ) + ), + ) def test_copy_alpha_channel(self): - g = Image.linear_gradient('L') - im = Image.merge('RGBA', [g, g.transpose(Image.ROTATE_90), - g.transpose(Image.ROTATE_180), - g.transpose(Image.ROTATE_270)]) + g = Image.linear_gradient("L") + im = Image.merge( + "RGBA", + [ + g, + g.transpose(Image.ROTATE_90), + g.transpose(Image.ROTATE_180), + g.transpose(Image.ROTATE_270), + ], + ) - self.assert_image_equal(im, im._new( - im.im.color_lut_3d('RGBA', Image.LINEAR, - *self.generate_identity_table(3, 17)))) + self.assert_image_equal( + im, + im._new( + im.im.color_lut_3d( + "RGBA", Image.LINEAR, *self.generate_identity_table(3, 17) + ) + ), + ) def test_channels_order(self): - g = Image.linear_gradient('L') - im = Image.merge('RGB', [g, g.transpose(Image.ROTATE_90), - g.transpose(Image.ROTATE_180)]) + g = Image.linear_gradient("L") + im = Image.merge( + "RGB", [g, g.transpose(Image.ROTATE_90), g.transpose(Image.ROTATE_180)] + ) # Reverse channels by splitting and using table + # fmt: off self.assert_image_equal( Image.merge('RGB', im.split()[::-1]), im._new(im.im.color_lut_3d('RGB', Image.LINEAR, @@ -209,12 +221,15 @@ class TestColorLut3DCoreAPI(PillowTestCase): 1, 0, 0, 1, 0, 1, 1, 1, 0, 1, 1, 1, ]))) + # fmt: on def test_overflow(self): - g = Image.linear_gradient('L') - im = Image.merge('RGB', [g, g.transpose(Image.ROTATE_90), - g.transpose(Image.ROTATE_180)]) + g = Image.linear_gradient("L") + im = Image.merge( + "RGB", [g, g.transpose(Image.ROTATE_90), g.transpose(Image.ROTATE_180)] + ) + # fmt: off transformed = im._new(im.im.color_lut_3d('RGB', Image.LINEAR, 3, 2, 2, 2, [ @@ -224,6 +239,7 @@ class TestColorLut3DCoreAPI(PillowTestCase): -1, -1, 2, 2, -1, 2, -1, 2, 2, 2, 2, 2, ])).load() + # fmt: on self.assertEqual(transformed[0, 0], (0, 0, 255)) self.assertEqual(transformed[50, 50], (0, 0, 255)) self.assertEqual(transformed[255, 0], (0, 255, 255)) @@ -233,6 +249,7 @@ class TestColorLut3DCoreAPI(PillowTestCase): self.assertEqual(transformed[255, 255], (255, 255, 0)) self.assertEqual(transformed[205, 205], (255, 255, 0)) + # fmt: off transformed = im._new(im.im.color_lut_3d('RGB', Image.LINEAR, 3, 2, 2, 2, [ @@ -242,6 +259,7 @@ class TestColorLut3DCoreAPI(PillowTestCase): -3, -3, 5, 5, -3, 5, -3, 5, 5, 5, 5, 5, ])).load() + # fmt: on self.assertEqual(transformed[0, 0], (0, 0, 255)) self.assertEqual(transformed[50, 50], (0, 0, 255)) self.assertEqual(transformed[255, 0], (0, 255, 255)) @@ -286,14 +304,15 @@ class TestColorLut3DFilter(PillowTestCase): self.assertEqual(tuple(lut.size), (2, 2, 2)) self.assertEqual(lut.name, "Color 3D LUT") + # fmt: off lut = ImageFilter.Color3DLUT((2, 2, 2), [ (0, 1, 2), (3, 4, 5), (6, 7, 8), (9, 10, 11), (12, 13, 14), (15, 16, 17), (18, 19, 20), (21, 22, 23)]) + # fmt: on self.assertEqual(tuple(lut.size), (2, 2, 2)) self.assertEqual(lut.table, list(range(24))) - lut = ImageFilter.Color3DLUT((2, 2, 2), [(0, 1, 2, 3)] * 8, - channels=4) + 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) @@ -318,7 +337,7 @@ class TestColorLut3DFilter(PillowTestCase): self.assertEqual(lut.table.shape, (table.size,)) # Check application - Image.new('RGB', (10, 10), 0).filter(lut) + Image.new("RGB", (10, 10), 0).filter(lut) # Check copy table[0] = 33 @@ -332,40 +351,34 @@ class TestColorLut3DFilter(PillowTestCase): @unittest.skipIf(numpy is None, "Numpy is not installed") def test_numpy_formats(self): - g = Image.linear_gradient('L') - im = Image.merge('RGB', [g, g.transpose(Image.ROTATE_90), - g.transpose(Image.ROTATE_180)]) + g = Image.linear_gradient("L") + im = Image.merge( + "RGB", [g, g.transpose(Image.ROTATE_90), g.transpose(Image.ROTATE_180)] + ) - lut = ImageFilter.Color3DLUT.generate((7, 9, 11), - lambda r, g, b: (r, g, b)) + lut = ImageFilter.Color3DLUT.generate((7, 9, 11), lambda r, g, b: (r, g, b)) lut.table = numpy.array(lut.table, dtype=numpy.float32)[:-1] with self.assertRaisesRegex(ValueError, "should have table_channels"): im.filter(lut) - lut = ImageFilter.Color3DLUT.generate((7, 9, 11), - lambda r, g, b: (r, g, b)) - lut.table = (numpy.array(lut.table, dtype=numpy.float32) - .reshape((7 * 9 * 11), 3)) + lut = ImageFilter.Color3DLUT.generate((7, 9, 11), lambda r, g, b: (r, g, b)) + lut.table = numpy.array(lut.table, dtype=numpy.float32).reshape((7 * 9 * 11), 3) with self.assertRaisesRegex(ValueError, "should have table_channels"): im.filter(lut) - lut = ImageFilter.Color3DLUT.generate((7, 9, 11), - lambda r, g, b: (r, g, b)) + lut = ImageFilter.Color3DLUT.generate((7, 9, 11), lambda r, g, b: (r, g, b)) lut.table = numpy.array(lut.table, dtype=numpy.float16) self.assert_image_equal(im, im.filter(lut)) - lut = ImageFilter.Color3DLUT.generate((7, 9, 11), - lambda r, g, b: (r, g, b)) + lut = ImageFilter.Color3DLUT.generate((7, 9, 11), lambda r, g, b: (r, g, b)) lut.table = numpy.array(lut.table, dtype=numpy.float32) self.assert_image_equal(im, im.filter(lut)) - lut = ImageFilter.Color3DLUT.generate((7, 9, 11), - lambda r, g, b: (r, g, b)) + lut = ImageFilter.Color3DLUT.generate((7, 9, 11), lambda r, g, b: (r, g, b)) lut.table = numpy.array(lut.table, dtype=numpy.float64) self.assert_image_equal(im, im.filter(lut)) - lut = ImageFilter.Color3DLUT.generate((7, 9, 11), - lambda r, g, b: (r, g, b)) + lut = ImageFilter.Color3DLUT.generate((7, 9, 11), lambda r, g, b: (r, g, b)) lut.table = numpy.array(lut.table, dtype=numpy.int32) im.filter(lut) lut.table = numpy.array(lut.table, dtype=numpy.int8) @@ -373,54 +386,65 @@ class TestColorLut3DFilter(PillowTestCase): def test_repr(self): lut = ImageFilter.Color3DLUT(2, [0, 1, 2] * 8) - self.assertEqual(repr(lut), - "") + self.assertEqual(repr(lut), "") lut = ImageFilter.Color3DLUT( - (3, 4, 5), array('f', [0, 0, 0, 0] * (3 * 4 * 5)), - channels=4, target_mode='YCbCr', _copy_table=False) + (3, 4, 5), + array("f", [0, 0, 0, 0] * (3 * 4 * 5)), + channels=4, + target_mode="YCbCr", + _copy_table=False, + ) self.assertEqual( - repr(lut), - "") + repr(lut), "" + ) class TestGenerateColorLut3D(PillowTestCase): def test_wrong_channels_count(self): with self.assertRaisesRegex(ValueError, "3 or 4 output channels"): ImageFilter.Color3DLUT.generate( - 5, channels=2, callback=lambda r, g, b: (r, g, b)) + 5, channels=2, callback=lambda r, g, b: (r, g, b) + ) with self.assertRaisesRegex(ValueError, "should have either channels"): ImageFilter.Color3DLUT.generate(5, lambda r, g, b: (r, g, b, r)) with self.assertRaisesRegex(ValueError, "should have either channels"): ImageFilter.Color3DLUT.generate( - 5, channels=4, callback=lambda r, g, b: (r, g, b)) + 5, channels=4, callback=lambda r, g, b: (r, g, b) + ) def test_3_channels(self): lut = ImageFilter.Color3DLUT.generate(5, lambda r, g, b: (r, g, b)) self.assertEqual(tuple(lut.size), (5, 5, 5)) self.assertEqual(lut.name, "Color 3D LUT") + # fmt: off self.assertEqual(lut.table[:24], [ 0.0, 0.0, 0.0, 0.25, 0.0, 0.0, 0.5, 0.0, 0.0, 0.75, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 0.25, 0.0, 0.25, 0.25, 0.0, 0.5, 0.25, 0.0]) + # fmt: on def test_4_channels(self): lut = ImageFilter.Color3DLUT.generate( - 5, channels=4, callback=lambda r, g, b: (b, r, g, (r+g+b) / 2)) + 5, channels=4, callback=lambda r, g, b: (b, r, g, (r + g + b) / 2) + ) self.assertEqual(tuple(lut.size), (5, 5, 5)) self.assertEqual(lut.name, "Color 3D LUT") + # fmt: off self.assertEqual(lut.table[:24], [ 0.0, 0.0, 0.0, 0.0, 0.0, 0.25, 0.0, 0.125, 0.0, 0.5, 0.0, 0.25, 0.0, 0.75, 0.0, 0.375, 0.0, 1.0, 0.0, 0.5, 0.0, 0.0, 0.25, 0.125 ]) + # fmt: on def test_apply(self): lut = ImageFilter.Color3DLUT.generate(5, lambda r, g, b: (r, g, b)) - g = Image.linear_gradient('L') - im = Image.merge('RGB', [g, g.transpose(Image.ROTATE_90), - g.transpose(Image.ROTATE_180)]) + g = Image.linear_gradient("L") + im = Image.merge( + "RGB", [g, g.transpose(Image.ROTATE_90), g.transpose(Image.ROTATE_180)] + ) self.assertEqual(im, im.filter(lut)) @@ -442,80 +466,96 @@ class TestTransformColorLut3D(PillowTestCase): def test_target_mode(self): source = ImageFilter.Color3DLUT.generate( - 2, lambda r, g, b: (r, g, b), target_mode='HSV') + 2, lambda r, g, b: (r, g, b), target_mode="HSV" + ) lut = source.transform(lambda r, g, b: (r, g, b)) - self.assertEqual(lut.mode, 'HSV') + self.assertEqual(lut.mode, "HSV") - lut = source.transform(lambda r, g, b: (r, g, b), target_mode='RGB') - self.assertEqual(lut.mode, 'RGB') + lut = source.transform(lambda r, g, b: (r, g, b), target_mode="RGB") + self.assertEqual(lut.mode, "RGB") def test_3_to_3_channels(self): - source = ImageFilter.Color3DLUT.generate( - (3, 4, 5), lambda r, g, b: (r, g, b)) - lut = source.transform(lambda r, g, b: (r*r, g*g, b*b)) + source = ImageFilter.Color3DLUT.generate((3, 4, 5), lambda r, g, b: (r, g, b)) + lut = source.transform(lambda r, g, b: (r * r, g * g, b * b)) self.assertEqual(tuple(lut.size), tuple(source.size)) self.assertEqual(len(lut.table), len(source.table)) self.assertNotEqual(lut.table, source.table) - self.assertEqual(lut.table[0:10], [ - 0.0, 0.0, 0.0, 0.25, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0]) + self.assertEqual( + lut.table[0:10], [0.0, 0.0, 0.0, 0.25, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0] + ) def test_3_to_4_channels(self): - source = ImageFilter.Color3DLUT.generate( - (6, 5, 4), lambda r, g, b: (r, g, b)) - lut = source.transform(lambda r, g, b: (r*r, g*g, b*b, 1), channels=4) + source = ImageFilter.Color3DLUT.generate((6, 5, 4), lambda r, g, b: (r, g, b)) + lut = source.transform(lambda r, g, b: (r * r, g * g, b * b, 1), channels=4) self.assertEqual(tuple(lut.size), tuple(source.size)) self.assertNotEqual(len(lut.table), len(source.table)) self.assertNotEqual(lut.table, source.table) + # fmt: off self.assertEqual(lut.table[0:16], [ 0.0, 0.0, 0.0, 1, 0.2**2, 0.0, 0.0, 1, 0.4**2, 0.0, 0.0, 1, 0.6**2, 0.0, 0.0, 1]) + # fmt: on def test_4_to_3_channels(self): source = ImageFilter.Color3DLUT.generate( - (3, 6, 5), lambda r, g, b: (r, g, b, 1), channels=4) - lut = source.transform(lambda r, g, b, a: (a - r*r, a - g*g, a - b*b), - channels=3) + (3, 6, 5), lambda r, g, b: (r, g, b, 1), channels=4 + ) + lut = source.transform( + lambda r, g, b, a: (a - r * r, a - g * g, a - b * b), channels=3 + ) self.assertEqual(tuple(lut.size), tuple(source.size)) self.assertNotEqual(len(lut.table), len(source.table)) self.assertNotEqual(lut.table, source.table) + # fmt: off self.assertEqual(lut.table[0:18], [ 1.0, 1.0, 1.0, 0.75, 1.0, 1.0, 0.0, 1.0, 1.0, 1.0, 0.96, 1.0, 0.75, 0.96, 1.0, 0.0, 0.96, 1.0]) + # fmt: on def test_4_to_4_channels(self): source = ImageFilter.Color3DLUT.generate( - (6, 5, 4), lambda r, g, b: (r, g, b, 1), channels=4) - lut = source.transform(lambda r, g, b, a: (r*r, g*g, b*b, a - 0.5)) + (6, 5, 4), lambda r, g, b: (r, g, b, 1), channels=4 + ) + lut = source.transform(lambda r, g, b, a: (r * r, g * g, b * b, a - 0.5)) self.assertEqual(tuple(lut.size), tuple(source.size)) self.assertEqual(len(lut.table), len(source.table)) self.assertNotEqual(lut.table, source.table) + # fmt: off self.assertEqual(lut.table[0:16], [ 0.0, 0.0, 0.0, 0.5, 0.2**2, 0.0, 0.0, 0.5, 0.4**2, 0.0, 0.0, 0.5, 0.6**2, 0.0, 0.0, 0.5]) + # fmt: on def test_with_normals_3_channels(self): source = ImageFilter.Color3DLUT.generate( - (6, 5, 4), lambda r, g, b: (r*r, g*g, b*b)) + (6, 5, 4), lambda r, g, b: (r * r, g * g, b * b) + ) lut = source.transform( - lambda nr, ng, nb, r, g, b: (nr - r, ng - g, nb - b), - with_normals=True) + lambda nr, ng, nb, r, g, b: (nr - r, ng - g, nb - b), with_normals=True + ) self.assertEqual(tuple(lut.size), tuple(source.size)) self.assertEqual(len(lut.table), len(source.table)) self.assertNotEqual(lut.table, source.table) + # fmt: off self.assertEqual(lut.table[0:18], [ 0.0, 0.0, 0.0, 0.16, 0.0, 0.0, 0.24, 0.0, 0.0, 0.24, 0.0, 0.0, 0.8 - (0.8**2), 0, 0, 0, 0, 0]) + # fmt: on def test_with_normals_4_channels(self): source = ImageFilter.Color3DLUT.generate( - (3, 6, 5), lambda r, g, b: (r*r, g*g, b*b, 1), channels=4) + (3, 6, 5), lambda r, g, b: (r * r, g * g, b * b, 1), channels=4 + ) lut = source.transform( - lambda nr, ng, nb, r, g, b, a: (nr - r, ng - g, nb - b, a-0.5), - with_normals=True) + lambda nr, ng, nb, r, g, b, a: (nr - r, ng - g, nb - b, a - 0.5), + with_normals=True, + ) self.assertEqual(tuple(lut.size), tuple(source.size)) self.assertEqual(len(lut.table), len(source.table)) self.assertNotEqual(lut.table, source.table) + # fmt: off 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]) + # fmt: on diff --git a/Tests/test_core_resources.py b/Tests/test_core_resources.py index fe9f5ccc4..c8ba4b4d5 100644 --- a/Tests/test_core_resources.py +++ b/Tests/test_core_resources.py @@ -6,39 +6,39 @@ from .helper import unittest, PillowTestCase from PIL import Image -is_pypy = hasattr(sys, 'pypy_version_info') +is_pypy = hasattr(sys, "pypy_version_info") class TestCoreStats(PillowTestCase): def test_get_stats(self): # Create at least one image - Image.new('RGB', (10, 10)) + Image.new("RGB", (10, 10)) stats = Image.core.get_stats() - self.assertIn('new_count', stats) - self.assertIn('reused_blocks', stats) - self.assertIn('freed_blocks', stats) - self.assertIn('allocated_blocks', stats) - self.assertIn('reallocated_blocks', stats) - self.assertIn('blocks_cached', stats) + self.assertIn("new_count", stats) + self.assertIn("reused_blocks", stats) + self.assertIn("freed_blocks", stats) + self.assertIn("allocated_blocks", stats) + self.assertIn("reallocated_blocks", stats) + self.assertIn("blocks_cached", stats) def test_reset_stats(self): Image.core.reset_stats() stats = Image.core.get_stats() - self.assertEqual(stats['new_count'], 0) - self.assertEqual(stats['reused_blocks'], 0) - self.assertEqual(stats['freed_blocks'], 0) - self.assertEqual(stats['allocated_blocks'], 0) - self.assertEqual(stats['reallocated_blocks'], 0) - self.assertEqual(stats['blocks_cached'], 0) + self.assertEqual(stats["new_count"], 0) + self.assertEqual(stats["reused_blocks"], 0) + self.assertEqual(stats["freed_blocks"], 0) + self.assertEqual(stats["allocated_blocks"], 0) + self.assertEqual(stats["reallocated_blocks"], 0) + self.assertEqual(stats["blocks_cached"], 0) class TestCoreMemory(PillowTestCase): def tearDown(self): # Restore default values Image.core.set_alignment(1) - Image.core.set_block_size(1024*1024) + Image.core.set_block_size(1024 * 1024) Image.core.set_blocks_max(0) Image.core.clear_cache() @@ -54,7 +54,7 @@ class TestCoreMemory(PillowTestCase): self.assertEqual(alignment, i) # Try to construct new image - Image.new('RGB', (10, 10)) + Image.new("RGB", (10, 10)) self.assertRaises(ValueError, Image.core.set_alignment, 0) self.assertRaises(ValueError, Image.core.set_alignment, -1) @@ -66,13 +66,13 @@ class TestCoreMemory(PillowTestCase): self.assertGreaterEqual(block_size, 4096) def test_set_block_size(self): - for i in [4096, 2*4096, 3*4096]: + for i in [4096, 2 * 4096, 3 * 4096]: Image.core.set_block_size(i) block_size = Image.core.get_block_size() self.assertEqual(block_size, i) # Try to construct new image - Image.new('RGB', (10, 10)) + Image.new("RGB", (10, 10)) self.assertRaises(ValueError, Image.core.set_block_size, 0) self.assertRaises(ValueError, Image.core.set_block_size, -1) @@ -82,13 +82,13 @@ class TestCoreMemory(PillowTestCase): Image.core.reset_stats() Image.core.set_blocks_max(0) Image.core.set_block_size(4096) - Image.new('RGB', (256, 256)) + Image.new("RGB", (256, 256)) stats = Image.core.get_stats() - self.assertGreaterEqual(stats['new_count'], 1) - self.assertGreaterEqual(stats['allocated_blocks'], 64) + self.assertGreaterEqual(stats["new_count"], 1) + self.assertGreaterEqual(stats["allocated_blocks"], 64) if not is_pypy: - self.assertGreaterEqual(stats['freed_blocks'], 64) + self.assertGreaterEqual(stats["freed_blocks"], 64) def test_get_blocks_max(self): blocks_max = Image.core.get_blocks_max() @@ -102,24 +102,26 @@ class TestCoreMemory(PillowTestCase): self.assertEqual(blocks_max, i) # Try to construct new image - Image.new('RGB', (10, 10)) + Image.new("RGB", (10, 10)) self.assertRaises(ValueError, Image.core.set_blocks_max, -1) + if sys.maxsize < 2 ** 32: + self.assertRaises(ValueError, Image.core.set_blocks_max, 2 ** 29) @unittest.skipIf(is_pypy, "images are not collected") def test_set_blocks_max_stats(self): Image.core.reset_stats() Image.core.set_blocks_max(128) Image.core.set_block_size(4096) - Image.new('RGB', (256, 256)) - Image.new('RGB', (256, 256)) + Image.new("RGB", (256, 256)) + Image.new("RGB", (256, 256)) stats = Image.core.get_stats() - self.assertGreaterEqual(stats['new_count'], 2) - self.assertGreaterEqual(stats['allocated_blocks'], 64) - self.assertGreaterEqual(stats['reused_blocks'], 64) - self.assertEqual(stats['freed_blocks'], 0) - self.assertEqual(stats['blocks_cached'], 64) + self.assertGreaterEqual(stats["new_count"], 2) + self.assertGreaterEqual(stats["allocated_blocks"], 64) + self.assertGreaterEqual(stats["reused_blocks"], 64) + self.assertEqual(stats["freed_blocks"], 0) + self.assertEqual(stats["blocks_cached"], 64) @unittest.skipIf(is_pypy, "images are not collected") def test_clear_cache_stats(self): @@ -127,55 +129,55 @@ class TestCoreMemory(PillowTestCase): Image.core.clear_cache() Image.core.set_blocks_max(128) Image.core.set_block_size(4096) - Image.new('RGB', (256, 256)) - Image.new('RGB', (256, 256)) + Image.new("RGB", (256, 256)) + Image.new("RGB", (256, 256)) # Keep 16 blocks in cache Image.core.clear_cache(16) stats = Image.core.get_stats() - self.assertGreaterEqual(stats['new_count'], 2) - self.assertGreaterEqual(stats['allocated_blocks'], 64) - self.assertGreaterEqual(stats['reused_blocks'], 64) - self.assertGreaterEqual(stats['freed_blocks'], 48) - self.assertEqual(stats['blocks_cached'], 16) + self.assertGreaterEqual(stats["new_count"], 2) + self.assertGreaterEqual(stats["allocated_blocks"], 64) + self.assertGreaterEqual(stats["reused_blocks"], 64) + self.assertGreaterEqual(stats["freed_blocks"], 48) + self.assertEqual(stats["blocks_cached"], 16) def test_large_images(self): Image.core.reset_stats() Image.core.set_blocks_max(0) Image.core.set_block_size(4096) - Image.new('RGB', (2048, 16)) + Image.new("RGB", (2048, 16)) Image.core.clear_cache() stats = Image.core.get_stats() - self.assertGreaterEqual(stats['new_count'], 1) - self.assertGreaterEqual(stats['allocated_blocks'], 16) - self.assertGreaterEqual(stats['reused_blocks'], 0) - self.assertEqual(stats['blocks_cached'], 0) + self.assertGreaterEqual(stats["new_count"], 1) + self.assertGreaterEqual(stats["allocated_blocks"], 16) + self.assertGreaterEqual(stats["reused_blocks"], 0) + self.assertEqual(stats["blocks_cached"], 0) if not is_pypy: - self.assertGreaterEqual(stats['freed_blocks'], 16) + self.assertGreaterEqual(stats["freed_blocks"], 16) class TestEnvVars(PillowTestCase): def tearDown(self): # Restore default values Image.core.set_alignment(1) - Image.core.set_block_size(1024*1024) + Image.core.set_block_size(1024 * 1024) Image.core.set_blocks_max(0) Image.core.clear_cache() def test_units(self): - Image._apply_env_variables({'PILLOW_BLOCKS_MAX': '2K'}) - self.assertEqual(Image.core.get_blocks_max(), 2*1024) - Image._apply_env_variables({'PILLOW_BLOCK_SIZE': '2m'}) - self.assertEqual(Image.core.get_block_size(), 2*1024*1024) + Image._apply_env_variables({"PILLOW_BLOCKS_MAX": "2K"}) + self.assertEqual(Image.core.get_blocks_max(), 2 * 1024) + Image._apply_env_variables({"PILLOW_BLOCK_SIZE": "2m"}) + self.assertEqual(Image.core.get_block_size(), 2 * 1024 * 1024) def test_warnings(self): self.assert_warning( - UserWarning, Image._apply_env_variables, - {'PILLOW_ALIGNMENT': '15'}) + UserWarning, Image._apply_env_variables, {"PILLOW_ALIGNMENT": "15"} + ) self.assert_warning( - UserWarning, Image._apply_env_variables, - {'PILLOW_BLOCK_SIZE': '1024'}) + UserWarning, Image._apply_env_variables, {"PILLOW_BLOCK_SIZE": "1024"} + ) self.assert_warning( - UserWarning, Image._apply_env_variables, - {'PILLOW_BLOCKS_MAX': 'wat'}) + UserWarning, Image._apply_env_variables, {"PILLOW_BLOCKS_MAX": "wat"} + ) diff --git a/Tests/test_decompression_bomb.py b/Tests/test_decompression_bomb.py index bc0bab525..6b224632d 100644 --- a/Tests/test_decompression_bomb.py +++ b/Tests/test_decompression_bomb.py @@ -8,7 +8,6 @@ ORIGINAL_LIMIT = Image.MAX_IMAGE_PIXELS class TestDecompressionBomb(PillowTestCase): - def tearDown(self): Image.MAX_IMAGE_PIXELS = ORIGINAL_LIMIT @@ -33,20 +32,17 @@ class TestDecompressionBomb(PillowTestCase): Image.MAX_IMAGE_PIXELS = 128 * 128 - 1 self.assertEqual(Image.MAX_IMAGE_PIXELS, 128 * 128 - 1) - self.assert_warning(Image.DecompressionBombWarning, - Image.open, TEST_FILE) + self.assert_warning(Image.DecompressionBombWarning, Image.open, TEST_FILE) def test_exception(self): # Set limit to trigger exception on the test file Image.MAX_IMAGE_PIXELS = 64 * 128 - 1 self.assertEqual(Image.MAX_IMAGE_PIXELS, 64 * 128 - 1) - self.assertRaises(Image.DecompressionBombError, - lambda: Image.open(TEST_FILE)) + self.assertRaises(Image.DecompressionBombError, lambda: Image.open(TEST_FILE)) class TestDecompressionCrop(PillowTestCase): - def setUp(self): self.src = hopper() Image.MAX_IMAGE_PIXELS = self.src.height * self.src.width * 4 - 1 @@ -58,21 +54,17 @@ class TestDecompressionCrop(PillowTestCase): # Crops can extend the extents, therefore we should have the # same decompression bomb warnings on them. box = (0, 0, self.src.width * 2, self.src.height * 2) - self.assert_warning(Image.DecompressionBombWarning, - self.src.crop, box) + self.assert_warning(Image.DecompressionBombWarning, self.src.crop, box) def test_crop_decompression_checks(self): im = Image.new("RGB", (100, 100)) - good_values = ((-9999, -9999, -9990, -9990), - (-999, -999, -990, -990)) + good_values = ((-9999, -9999, -9990, -9990), (-999, -999, -990, -990)) - warning_values = ((-160, -160, 99, 99), - (160, 160, -99, -99)) + warning_values = ((-160, -160, 99, 99), (160, 160, -99, -99)) - error_values = ((-99909, -99990, 99999, 99999), - (99909, 99990, -99999, -99999)) + error_values = ((-99909, -99990, 99999, 99999), (99909, 99990, -99999, -99999)) for value in good_values: self.assertEqual(im.crop(value).size, (9, 9)) diff --git a/Tests/test_features.py b/Tests/test_features.py index 15b5982ce..d7e505ac7 100644 --- a/Tests/test_features.py +++ b/Tests/test_features.py @@ -1,44 +1,43 @@ +from __future__ import unicode_literals + +import io + from .helper import unittest, PillowTestCase from PIL import features try: from PIL import _webp + HAVE_WEBP = True except ImportError: HAVE_WEBP = False class TestFeatures(PillowTestCase): - def test_check(self): # Check the correctness of the convenience function for module in features.modules: - self.assertEqual(features.check_module(module), - features.check(module)) + self.assertEqual(features.check_module(module), features.check(module)) for codec in features.codecs: - self.assertEqual(features.check_codec(codec), - features.check(codec)) + self.assertEqual(features.check_codec(codec), features.check(codec)) for feature in features.features: - self.assertEqual(features.check_feature(feature), - features.check(feature)) + self.assertEqual(features.check_feature(feature), features.check(feature)) @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) + self.assertEqual( + features.check("transp_webp"), not _webp.WebPDecoderBuggyAlpha() + ) + self.assertEqual(features.check("transp_webp"), _webp.HAVE_TRANSPARENCY) @unittest.skipUnless(HAVE_WEBP, "WebP not available") def test_webp_mux(self): - self.assertEqual(features.check('webp_mux'), - _webp.HAVE_WEBPMUX) + self.assertEqual(features.check("webp_mux"), _webp.HAVE_WEBPMUX) @unittest.skipUnless(HAVE_WEBP, "WebP not available") def test_webp_anim(self): - self.assertEqual(features.check('webp_anim'), - _webp.HAVE_WEBPANIM) + self.assertEqual(features.check("webp_anim"), _webp.HAVE_WEBPANIM) def test_check_modules(self): for feature in features.modules: @@ -63,3 +62,27 @@ class TestFeatures(PillowTestCase): module = "unsupported_module" # Act / Assert self.assertRaises(ValueError, features.check_module, module) + + def test_pilinfo(self): + buf = io.StringIO() + features.pilinfo(buf) + out = buf.getvalue() + lines = out.splitlines() + self.assertEqual(lines[0], "-" * 68) + self.assertTrue(lines[1].startswith("Pillow ")) + self.assertEqual(lines[2], "-" * 68) + self.assertTrue(lines[3].startswith("Python modules loaded from ")) + self.assertTrue(lines[4].startswith("Binary modules loaded from ")) + self.assertEqual(lines[5], "-" * 68) + self.assertTrue(lines[6].startswith("Python ")) + jpeg = ( + "\n" + + "-" * 68 + + "\n" + + "JPEG image/jpeg\n" + + "Extensions: .jfif, .jpe, .jpeg, .jpg\n" + + "Features: open, save\n" + + "-" * 68 + + "\n" + ) + self.assertIn(jpeg, out) diff --git a/Tests/test_file_bmp.py b/Tests/test_file_bmp.py index b97a84227..71ea72751 100644 --- a/Tests/test_file_bmp.py +++ b/Tests/test_file_bmp.py @@ -5,11 +5,10 @@ import io class TestFileBmp(PillowTestCase): - def roundtrip(self, im): outfile = self.tempfile("temp.bmp") - im.save(outfile, 'BMP') + im.save(outfile, "BMP") reloaded = Image.open(outfile) reloaded.load() @@ -28,8 +27,7 @@ class TestFileBmp(PillowTestCase): def test_invalid_file(self): with open("Tests/images/flower.jpg", "rb") as fp: - self.assertRaises(SyntaxError, - BmpImagePlugin.BmpImageFile, fp) + self.assertRaises(SyntaxError, BmpImagePlugin.BmpImageFile, fp) def test_save_to_bytes(self): output = io.BytesIO() @@ -62,27 +60,27 @@ class TestFileBmp(PillowTestCase): im = Image.open("Tests/images/hopper.bmp") # Act - im.save(outfile, 'JPEG', dpi=im.info['dpi']) + im.save(outfile, "JPEG", dpi=im.info["dpi"]) # Assert reloaded = Image.open(outfile) reloaded.load() - self.assertEqual(im.info['dpi'], reloaded.info['dpi']) + self.assertEqual(im.info["dpi"], reloaded.info["dpi"]) self.assertEqual(im.size, reloaded.size) self.assertEqual(reloaded.format, "JPEG") def test_load_dpi_rounding(self): # Round up - im = Image.open('Tests/images/hopper.bmp') + im = Image.open("Tests/images/hopper.bmp") self.assertEqual(im.info["dpi"], (96, 96)) # Round down - im = Image.open('Tests/images/hopper_roundDown.bmp') + im = Image.open("Tests/images/hopper_roundDown.bmp") self.assertEqual(im.info["dpi"], (72, 72)) def test_save_dpi_rounding(self): outfile = self.tempfile("temp.bmp") - im = Image.open('Tests/images/hopper.bmp') + im = Image.open("Tests/images/hopper.bmp") im.save(outfile, dpi=(72.2, 72.2)) reloaded = Image.open(outfile) @@ -94,17 +92,17 @@ class TestFileBmp(PillowTestCase): def test_load_dib(self): # test for #1293, Imagegrab returning Unsupported Bitfields Format - im = Image.open('Tests/images/clipboard.dib') + im = Image.open("Tests/images/clipboard.dib") self.assertEqual(im.format, "DIB") self.assertEqual(im.get_format_mimetype(), "image/bmp") - target = Image.open('Tests/images/clipboard_target.png') + target = Image.open("Tests/images/clipboard_target.png") self.assert_image_equal(im, target) def test_save_dib(self): outfile = self.tempfile("temp.dib") - im = Image.open('Tests/images/clipboard.dib') + im = Image.open("Tests/images/clipboard.dib") im.save(outfile) reloaded = Image.open(outfile) diff --git a/Tests/test_file_bufrstub.py b/Tests/test_file_bufrstub.py index 307029136..eb59b2469 100644 --- a/Tests/test_file_bufrstub.py +++ b/Tests/test_file_bufrstub.py @@ -6,7 +6,6 @@ TEST_FILE = "Tests/images/gfs.t06z.rassda.tm00.bufr_d" class TestFileBufrStub(PillowTestCase): - def test_open(self): # Act im = Image.open(TEST_FILE) @@ -23,8 +22,9 @@ class TestFileBufrStub(PillowTestCase): invalid_file = "Tests/images/flower.jpg" # Act / Assert - self.assertRaises(SyntaxError, - BufrStubImagePlugin.BufrStubImageFile, invalid_file) + self.assertRaises( + SyntaxError, BufrStubImagePlugin.BufrStubImageFile, invalid_file + ) def test_load(self): # Arrange diff --git a/Tests/test_file_container.py b/Tests/test_file_container.py index 04f055464..eb13d5f18 100644 --- a/Tests/test_file_container.py +++ b/Tests/test_file_container.py @@ -7,7 +7,6 @@ TEST_FILE = "Tests/images/dummy.container" class TestFileContainer(PillowTestCase): - def test_sanity(self): dir(Image) dir(ContainerIO) @@ -106,14 +105,16 @@ class TestFileContainer(PillowTestCase): def test_readlines(self): # Arrange - expected = ["This is line 1\n", - "This is line 2\n", - "This is line 3\n", - "This is line 4\n", - "This is line 5\n", - "This is line 6\n", - "This is line 7\n", - "This is line 8\n"] + expected = [ + "This is line 1\n", + "This is line 2\n", + "This is line 3\n", + "This is line 4\n", + "This is line 5\n", + "This is line 6\n", + "This is line 7\n", + "This is line 8\n", + ] with open(TEST_FILE) as fh: container = ContainerIO.ContainerIO(fh, 0, 120) diff --git a/Tests/test_file_cur.py b/Tests/test_file_cur.py index 734c330e6..a2ffb2e60 100644 --- a/Tests/test_file_cur.py +++ b/Tests/test_file_cur.py @@ -6,7 +6,6 @@ TEST_FILE = "Tests/images/deerstalker.cur" class TestFileCur(PillowTestCase): - def test_sanity(self): im = Image.open(TEST_FILE) @@ -20,8 +19,7 @@ class TestFileCur(PillowTestCase): def test_invalid_file(self): invalid_file = "Tests/images/flower.jpg" - self.assertRaises(SyntaxError, - CurImagePlugin.CurImageFile, invalid_file) + self.assertRaises(SyntaxError, CurImagePlugin.CurImageFile, invalid_file) no_cursors_file = "Tests/images/no_cursors.cur" diff --git a/Tests/test_file_dcx.py b/Tests/test_file_dcx.py index 0da364648..8e6ab111f 100644 --- a/Tests/test_file_dcx.py +++ b/Tests/test_file_dcx.py @@ -7,7 +7,6 @@ TEST_FILE = "Tests/images/hopper.dcx" class TestFileDcx(PillowTestCase): - def test_sanity(self): # Arrange @@ -24,12 +23,12 @@ class TestFileDcx(PillowTestCase): 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, - DcxImagePlugin.DcxImageFile, fp) + self.assertRaises(SyntaxError, DcxImagePlugin.DcxImageFile, fp) def test_tell(self): # Arrange @@ -55,7 +54,7 @@ class TestFileDcx(PillowTestCase): self.assertLess(im.tell(), n_frames) # Test that seeking to the last frame does not raise an error - im.seek(n_frames-1) + im.seek(n_frames - 1) def test_seek_too_far(self): # Arrange diff --git a/Tests/test_file_dds.py b/Tests/test_file_dds.py index 605c5f69b..80b452da2 100644 --- a/Tests/test_file_dds.py +++ b/Tests/test_file_dds.py @@ -15,7 +15,7 @@ class TestFileDds(PillowTestCase): def test_sanity_dxt1(self): """Check DXT1 images can be opened""" - target = Image.open(TEST_FILE_DXT1.replace('.dds', '.png')) + target = Image.open(TEST_FILE_DXT1.replace(".dds", ".png")) im = Image.open(TEST_FILE_DXT1) im.load() @@ -24,12 +24,12 @@ class TestFileDds(PillowTestCase): self.assertEqual(im.mode, "RGBA") self.assertEqual(im.size, (256, 256)) - self.assert_image_equal(target.convert('RGBA'), im) + self.assert_image_equal(target.convert("RGBA"), im) def test_sanity_dxt5(self): """Check DXT5 images can be opened""" - target = Image.open(TEST_FILE_DXT5.replace('.dds', '.png')) + target = Image.open(TEST_FILE_DXT5.replace(".dds", ".png")) im = Image.open(TEST_FILE_DXT5) im.load() @@ -43,7 +43,7 @@ class TestFileDds(PillowTestCase): def test_sanity_dxt3(self): """Check DXT3 images can be opened""" - target = Image.open(TEST_FILE_DXT3.replace('.dds', '.png')) + target = Image.open(TEST_FILE_DXT3.replace(".dds", ".png")) im = Image.open(TEST_FILE_DXT3) im.load() @@ -57,7 +57,7 @@ class TestFileDds(PillowTestCase): def test_dx10_bc7(self): """Check DX10 images can be opened""" - target = Image.open(TEST_FILE_DX10_BC7.replace('.dds', '.png')) + target = Image.open(TEST_FILE_DX10_BC7.replace(".dds", ".png")) im = Image.open(TEST_FILE_DX10_BC7) im.load() @@ -69,13 +69,16 @@ class TestFileDds(PillowTestCase): self.assert_image_equal(target, im) def test_unimplemented_dxgi_format(self): - self.assertRaises(NotImplementedError, Image.open, - "Tests/images/unimplemented_dxgi_format.dds") + self.assertRaises( + NotImplementedError, + Image.open, + "Tests/images/unimplemented_dxgi_format.dds", + ) def test_uncompressed_rgb(self): """Check uncompressed RGB images can be opened""" - target = Image.open(TEST_FILE_UNCOMPRESSED_RGB.replace('.dds', '.png')) + target = Image.open(TEST_FILE_UNCOMPRESSED_RGB.replace(".dds", ".png")) im = Image.open(TEST_FILE_UNCOMPRESSED_RGB) im.load() @@ -110,7 +113,7 @@ class TestFileDds(PillowTestCase): def test_short_header(self): """ Check a short header""" - with open(TEST_FILE_DXT5, 'rb') as f: + with open(TEST_FILE_DXT5, "rb") as f: img_file = f.read() def short_header(): @@ -121,7 +124,7 @@ class TestFileDds(PillowTestCase): def test_short_file(self): """ Check that the appropriate error is thrown for a short file""" - with open(TEST_FILE_DXT5, 'rb') as f: + with open(TEST_FILE_DXT5, "rb") as f: img_file = f.read() def short_file(): @@ -131,5 +134,8 @@ class TestFileDds(PillowTestCase): self.assertRaises(IOError, short_file) def test_unimplemented_pixel_format(self): - self.assertRaises(NotImplementedError, Image.open, - "Tests/images/unimplemented_pixel_format.dds") + self.assertRaises( + NotImplementedError, + Image.open, + "Tests/images/unimplemented_pixel_format.dds", + ) diff --git a/Tests/test_file_eps.py b/Tests/test_file_eps.py index 1f6025d69..00736c38c 100644 --- a/Tests/test_file_eps.py +++ b/Tests/test_file_eps.py @@ -21,7 +21,6 @@ file3 = "Tests/images/binary_preview_map.eps" class TestFileEps(PillowTestCase): - @unittest.skipUnless(HAS_GHOSTSCRIPT, "Ghostscript not available") def test_sanity(self): # Regular scale @@ -53,8 +52,7 @@ class TestFileEps(PillowTestCase): def test_invalid_file(self): invalid_file = "Tests/images/flower.jpg" - self.assertRaises(SyntaxError, - EpsImagePlugin.EpsImageFile, invalid_file) + self.assertRaises(SyntaxError, EpsImagePlugin.EpsImageFile, invalid_file) @unittest.skipUnless(HAS_GHOSTSCRIPT, "Ghostscript not available") def test_cmyk(self): @@ -67,8 +65,8 @@ class TestFileEps(PillowTestCase): cmyk_image.load() self.assertEqual(cmyk_image.mode, "RGB") - if 'jpeg_decoder' in dir(Image.core): - target = Image.open('Tests/images/pil_sample_rgb.jpg') + if "jpeg_decoder" in dir(Image.core): + target = Image.open("Tests/images/pil_sample_rgb.jpg") self.assert_image_similar(cmyk_image, target, 10) @unittest.skipUnless(HAS_GHOSTSCRIPT, "Ghostscript not available") @@ -86,19 +84,19 @@ class TestFileEps(PillowTestCase): 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') + 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') + 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: + with open(file1, "rb") as f: img_bytes = io.BytesIO(f.read()) img = Image.open(img_bytes) @@ -110,7 +108,7 @@ class TestFileEps(PillowTestCase): def test_image_mode_not_supported(self): im = hopper("RGBA") - tmpfile = self.tempfile('temp.eps') + tmpfile = self.tempfile("temp.eps") self.assertRaises(ValueError, im.save, tmpfile) @unittest.skipUnless(HAS_GHOSTSCRIPT, "Ghostscript not available") @@ -195,33 +193,33 @@ class TestFileEps(PillowTestCase): Image.open(file3) def _test_readline(self, t, ending): - ending = "Failure with line ending: %s" % ("".join( - "%s" % ord(s) - for s in ending)) - self.assertEqual(t.readline().strip('\r\n'), 'something', ending) - self.assertEqual(t.readline().strip('\r\n'), 'else', ending) - self.assertEqual(t.readline().strip('\r\n'), 'baz', ending) - self.assertEqual(t.readline().strip('\r\n'), 'bif', ending) + ending = "Failure with line ending: %s" % ( + "".join("%s" % ord(s) for s in ending) + ) + self.assertEqual(t.readline().strip("\r\n"), "something", ending) + self.assertEqual(t.readline().strip("\r\n"), "else", ending) + self.assertEqual(t.readline().strip("\r\n"), "baz", ending) + self.assertEqual(t.readline().strip("\r\n"), "bif", ending) def _test_readline_io_psfile(self, test_string, ending): - f = io.BytesIO(test_string.encode('latin-1')) + f = io.BytesIO(test_string.encode("latin-1")) t = EpsImagePlugin.PSFile(f) self._test_readline(t, ending) def _test_readline_file_psfile(self, test_string, ending): - f = self.tempfile('temp.txt') - with open(f, 'wb') as w: - w.write(test_string.encode('latin-1')) + f = self.tempfile("temp.txt") + with open(f, "wb") as w: + w.write(test_string.encode("latin-1")) - with open(f, 'rb') as r: + with open(f, "rb") as r: t = EpsImagePlugin.PSFile(r) self._test_readline(t, ending) def test_readline(self): # check all the freaking line endings possible from the spec # test_string = u'something\r\nelse\n\rbaz\rbif\n' - line_endings = ['\r\n', '\n', '\n\r', '\r'] - strings = ['something', 'else', 'baz', 'bif'] + line_endings = ["\r\n", "\n", "\n\r", "\r"] + strings = ["something", "else", "baz", "bif"] for ending in line_endings: s = ending.join(strings) @@ -231,10 +229,12 @@ class TestFileEps(PillowTestCase): def test_open_eps(self): # https://github.com/python-pillow/Pillow/issues/1104 # Arrange - FILES = ["Tests/images/illu10_no_preview.eps", - "Tests/images/illu10_preview.eps", - "Tests/images/illuCS6_no_preview.eps", - "Tests/images/illuCS6_preview.eps"] + FILES = [ + "Tests/images/illu10_no_preview.eps", + "Tests/images/illu10_preview.eps", + "Tests/images/illuCS6_no_preview.eps", + "Tests/images/illuCS6_preview.eps", + ] # Act / Assert for filename in FILES: diff --git a/Tests/test_file_fitsstub.py b/Tests/test_file_fitsstub.py index 6cb2de110..86ddecb9d 100644 --- a/Tests/test_file_fitsstub.py +++ b/Tests/test_file_fitsstub.py @@ -6,7 +6,6 @@ TEST_FILE = "Tests/images/hopper.fits" class TestFileFitsStub(PillowTestCase): - def test_open(self): # Act im = Image.open(TEST_FILE) @@ -23,8 +22,9 @@ class TestFileFitsStub(PillowTestCase): invalid_file = "Tests/images/flower.jpg" # Act / Assert - self.assertRaises(SyntaxError, - FitsStubImagePlugin.FITSStubImageFile, invalid_file) + self.assertRaises( + SyntaxError, FitsStubImagePlugin.FITSStubImageFile, invalid_file + ) def test_load(self): # Arrange @@ -42,5 +42,5 @@ class TestFileFitsStub(PillowTestCase): # Act / Assert: stub cannot save without an implemented handler self.assertRaises(IOError, im.save, dummy_filename) self.assertRaises( - IOError, - FitsStubImagePlugin._save, im, dummy_fp, dummy_filename) + IOError, FitsStubImagePlugin._save, im, dummy_fp, dummy_filename + ) diff --git a/Tests/test_file_fli.py b/Tests/test_file_fli.py index f67f0ada1..e5dadc3dc 100644 --- a/Tests/test_file_fli.py +++ b/Tests/test_file_fli.py @@ -11,7 +11,6 @@ animated_test_file = "Tests/images/a.fli" class TestFileFli(PillowTestCase): - def test_sanity(self): im = Image.open(static_test_file) im.load() @@ -31,6 +30,7 @@ class TestFileFli(PillowTestCase): def open(): im = Image.open(static_test_file) im.load() + self.assert_warning(None, open) def test_tell(self): @@ -46,8 +46,7 @@ class TestFileFli(PillowTestCase): def test_invalid_file(self): invalid_file = "Tests/images/flower.jpg" - self.assertRaises(SyntaxError, - FliImagePlugin.FliImageFile, invalid_file) + self.assertRaises(SyntaxError, FliImagePlugin.FliImageFile, invalid_file) def test_n_frames(self): im = Image.open(static_test_file) @@ -67,7 +66,7 @@ class TestFileFli(PillowTestCase): self.assertLess(im.tell(), n_frames) # Test that seeking to the last frame does not raise an error - im.seek(n_frames-1) + im.seek(n_frames - 1) def test_seek_tell(self): im = Image.open(animated_test_file) diff --git a/Tests/test_file_fpx.py b/Tests/test_file_fpx.py index 7283ba088..a426ca904 100644 --- a/Tests/test_file_fpx.py +++ b/Tests/test_file_fpx.py @@ -10,14 +10,11 @@ else: @unittest.skipUnless(olefile_installed, "olefile package not installed") class TestFileFpx(PillowTestCase): - def test_invalid_file(self): # Test an invalid OLE file invalid_file = "Tests/images/flower.jpg" - self.assertRaises(SyntaxError, - FpxImagePlugin.FpxImageFile, invalid_file) + self.assertRaises(SyntaxError, FpxImagePlugin.FpxImageFile, invalid_file) # Test a valid OLE file, but not an FPX file ole_file = "Tests/images/test-ole-file.doc" - self.assertRaises(SyntaxError, - FpxImagePlugin.FpxImageFile, ole_file) + self.assertRaises(SyntaxError, FpxImagePlugin.FpxImageFile, ole_file) diff --git a/Tests/test_file_ftex.py b/Tests/test_file_ftex.py index 07e29d7e0..16ecab2d1 100644 --- a/Tests/test_file_ftex.py +++ b/Tests/test_file_ftex.py @@ -3,14 +3,13 @@ from PIL import Image class TestFileFtex(PillowTestCase): - def test_load_raw(self): - im = Image.open('Tests/images/ftex_uncompressed.ftu') - target = Image.open('Tests/images/ftex_uncompressed.png') + im = Image.open("Tests/images/ftex_uncompressed.ftu") + target = Image.open("Tests/images/ftex_uncompressed.png") self.assert_image_equal(im, target) def test_load_dxt1(self): - im = Image.open('Tests/images/ftex_dxt1.ftc') - target = Image.open('Tests/images/ftex_dxt1.png') - self.assert_image_similar(im, target.convert('RGBA'), 15) + 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) diff --git a/Tests/test_file_gbr.py b/Tests/test_file_gbr.py index 1eb66264d..bdb52a6fa 100644 --- a/Tests/test_file_gbr.py +++ b/Tests/test_file_gbr.py @@ -4,16 +4,14 @@ from PIL import Image, GbrImagePlugin class TestFileGbr(PillowTestCase): - def test_invalid_file(self): invalid_file = "Tests/images/flower.jpg" - self.assertRaises(SyntaxError, - GbrImagePlugin.GbrImageFile, invalid_file) + self.assertRaises(SyntaxError, GbrImagePlugin.GbrImageFile, invalid_file) def test_gbr_file(self): - im = Image.open('Tests/images/gbr.gbr') + im = Image.open("Tests/images/gbr.gbr") - target = Image.open('Tests/images/gbr.png') + target = Image.open("Tests/images/gbr.png") self.assert_image_equal(target, im) diff --git a/Tests/test_file_gd.py b/Tests/test_file_gd.py index 67c9fba7b..82b089a5f 100644 --- a/Tests/test_file_gd.py +++ b/Tests/test_file_gd.py @@ -6,15 +6,13 @@ TEST_GD_FILE = "Tests/images/hopper.gd" class TestFileGd(PillowTestCase): - def test_sanity(self): im = GdImageFile.open(TEST_GD_FILE) self.assertEqual(im.size, (128, 128)) self.assertEqual(im.format, "GD") def test_bad_mode(self): - self.assertRaises(ValueError, - GdImageFile.open, TEST_GD_FILE, 'bad mode') + self.assertRaises(ValueError, GdImageFile.open, TEST_GD_FILE, "bad mode") def test_invalid_file(self): invalid_file = "Tests/images/flower.jpg" diff --git a/Tests/test_file_gif.py b/Tests/test_file_gif.py index c2188a0a2..1f0358bec 100644 --- a/Tests/test_file_gif.py +++ b/Tests/test_file_gif.py @@ -6,6 +6,7 @@ from io import BytesIO try: from PIL import _webp + HAVE_WEBP = True except ImportError: HAVE_WEBP = False @@ -20,7 +21,6 @@ with open(TEST_GIF, "rb") as f: class TestFileGif(PillowTestCase): - def setUp(self): if "gif_encoder" not in codecs or "gif_decoder" not in codecs: self.skipTest("gif support not available") # can this happen? @@ -37,13 +37,13 @@ class TestFileGif(PillowTestCase): 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" - self.assertRaises(SyntaxError, - GifImagePlugin.GifImageFile, invalid_file) + self.assertRaises(SyntaxError, GifImagePlugin.GifImageFile, invalid_file) def test_optimize(self): def test_grayscale(optimize): @@ -70,19 +70,22 @@ class TestFileGif(PillowTestCase): # Check for correctness after conversion back to RGB def check(colors, size, expected_palette_length): # make an image with empty colors in the start of the palette range - im = Image.frombytes('P', (colors, colors), - bytes(bytearray(range(256-colors, 256))*colors)) + im = Image.frombytes( + "P", + (colors, colors), + bytes(bytearray(range(256 - colors, 256)) * colors), + ) im = im.resize((size, size)) outfile = BytesIO() - im.save(outfile, 'GIF') + im.save(outfile, "GIF") outfile.seek(0) reloaded = Image.open(outfile) # check palette length - palette_length = max(i+1 for i, v in enumerate(reloaded.histogram()) if v) + palette_length = max(i + 1 for i, v in enumerate(reloaded.histogram()) if v) self.assertEqual(expected_palette_length, palette_length) - self.assert_image_equal(im.convert('RGB'), reloaded.convert('RGB')) + self.assert_image_equal(im.convert("RGB"), reloaded.convert("RGB")) # These do optimize the palette check(128, 511, 128) @@ -106,79 +109,76 @@ class TestFileGif(PillowTestCase): self.assertEqual(im.mode, "L") def test_roundtrip(self): - out = self.tempfile('temp.gif') + out = self.tempfile("temp.gif") im = hopper() im.save(out) reread = Image.open(out) - self.assert_image_similar(reread.convert('RGB'), im, 50) + self.assert_image_similar(reread.convert("RGB"), im, 50) def test_roundtrip2(self): # see https://github.com/python-pillow/Pillow/issues/403 - out = self.tempfile('temp.gif') + out = self.tempfile("temp.gif") im = Image.open(TEST_GIF) im2 = im.copy() im2.save(out) reread = Image.open(out) - self.assert_image_similar(reread.convert('RGB'), hopper(), 50) + self.assert_image_similar(reread.convert("RGB"), hopper(), 50) def test_roundtrip_save_all(self): # Single frame image - out = self.tempfile('temp.gif') + out = self.tempfile("temp.gif") im = hopper() im.save(out, save_all=True) reread = Image.open(out) - self.assert_image_similar(reread.convert('RGB'), im, 50) + self.assert_image_similar(reread.convert("RGB"), im, 50) # Multiframe image im = Image.open("Tests/images/dispose_bgnd.gif") - out = self.tempfile('temp.gif') + out = self.tempfile("temp.gif") im.save(out, save_all=True) reread = Image.open(out) self.assertEqual(reread.n_frames, 5) def test_headers_saving_for_animated_gifs(self): - important_headers = ['background', 'version', 'duration', 'loop'] + important_headers = ["background", "version", "duration", "loop"] # Multiframe image im = Image.open("Tests/images/dispose_bgnd.gif") info = im.info.copy() - out = self.tempfile('temp.gif') + out = self.tempfile("temp.gif") im.save(out, save_all=True) reread = Image.open(out) for header in important_headers: - self.assertEqual( - info[header], - reread.info[header] - ) + self.assertEqual(info[header], reread.info[header]) def test_palette_handling(self): # see https://github.com/python-pillow/Pillow/issues/513 im = Image.open(TEST_GIF) - im = im.convert('RGB') + im = im.convert("RGB") im = im.resize((100, 100), Image.LANCZOS) - im2 = im.convert('P', palette=Image.ADAPTIVE, colors=256) + im2 = im.convert("P", palette=Image.ADAPTIVE, colors=256) - f = self.tempfile('temp.gif') + f = self.tempfile("temp.gif") im2.save(f, optimize=True) reloaded = Image.open(f) - self.assert_image_similar(im, reloaded.convert('RGB'), 10) + self.assert_image_similar(im, reloaded.convert("RGB"), 10) def test_palette_434(self): # see https://github.com/python-pillow/Pillow/issues/434 def roundtrip(im, *args, **kwargs): - out = self.tempfile('temp.gif') + out = self.tempfile("temp.gif") im.copy().save(out, *args, **kwargs) reloaded = Image.open(out) @@ -192,7 +192,7 @@ class TestFileGif(PillowTestCase): im = im.convert("RGB") # check automatic P conversion - reloaded = roundtrip(im).convert('RGB') + reloaded = roundtrip(im).convert("RGB") self.assert_image_equal(im, reloaded) @unittest.skipUnless(netpbm_available(), "netpbm not available") @@ -240,10 +240,7 @@ class TestFileGif(PillowTestCase): self.assert_image_equal(im, expected) def test_n_frames(self): - for path, n_frames in [ - [TEST_GIF, 1], - ['Tests/images/iss634.gif', 42] - ]: + for path, n_frames in [[TEST_GIF, 1], ["Tests/images/iss634.gif", 42]]: # Test is_animated before n_frames im = Image.open(path) self.assertEqual(im.is_animated, n_frames != 1) @@ -262,7 +259,7 @@ class TestFileGif(PillowTestCase): self.assertLess(im.tell(), n_frames) # Test that seeking to the last frame does not raise an error - im.seek(n_frames-1) + im.seek(n_frames - 1) def test_dispose_none(self): img = Image.open("Tests/images/dispose_none.gif") @@ -292,18 +289,15 @@ class TestFileGif(PillowTestCase): pass def test_save_dispose(self): - out = self.tempfile('temp.gif') + out = self.tempfile("temp.gif") im_list = [ - Image.new('L', (100, 100), '#000'), - Image.new('L', (100, 100), '#111'), - Image.new('L', (100, 100), '#222'), + Image.new("L", (100, 100), "#000"), + Image.new("L", (100, 100), "#111"), + Image.new("L", (100, 100), "#222"), ] for method in range(0, 4): im_list[0].save( - out, - save_all=True, - append_images=im_list[1:], - disposal=method + out, save_all=True, append_images=im_list[1:], disposal=method ) img = Image.open(out) for _ in range(2): @@ -315,14 +309,14 @@ class TestFileGif(PillowTestCase): out, save_all=True, append_images=im_list[1:], - disposal=tuple(range(len(im_list))) - ) + disposal=tuple(range(len(im_list))), + ) img = Image.open(out) for i in range(2): img.seek(img.tell() + 1) - self.assertEqual(img.disposal_method, i+1) + self.assertEqual(img.disposal_method, i + 1) def test_dispose2_palette(self): out = self.tempfile('temp.gif') @@ -411,42 +405,39 @@ class TestFileGif(PillowTestCase): img.seek(img.tell() + 1) # all transparent pixels should be replaced with the color from the # first frame - self.assertEqual(img.histogram()[img.info['transparency']], 0) + self.assertEqual(img.histogram()[img.info["transparency"]], 0) def test_duration(self): duration = 1000 - out = self.tempfile('temp.gif') - im = Image.new('L', (100, 100), '#000') + out = self.tempfile("temp.gif") + im = Image.new("L", (100, 100), "#000") # Check that the argument has priority over the info settings - im.info['duration'] = 100 + im.info["duration"] = 100 im.save(out, duration=duration) reread = Image.open(out) - self.assertEqual(reread.info['duration'], duration) + self.assertEqual(reread.info["duration"], duration) def test_multiple_duration(self): duration_list = [1000, 2000, 3000] - out = self.tempfile('temp.gif') + out = self.tempfile("temp.gif") im_list = [ - Image.new('L', (100, 100), '#000'), - Image.new('L', (100, 100), '#111'), - Image.new('L', (100, 100), '#222') + Image.new("L", (100, 100), "#000"), + Image.new("L", (100, 100), "#111"), + Image.new("L", (100, 100), "#222"), ] # duration as list im_list[0].save( - out, - save_all=True, - append_images=im_list[1:], - duration=duration_list + out, save_all=True, append_images=im_list[1:], duration=duration_list ) reread = Image.open(out) for duration in duration_list: - self.assertEqual(reread.info['duration'], duration) + self.assertEqual(reread.info["duration"], duration) try: reread.seek(reread.tell() + 1) except EOFError: @@ -454,15 +445,12 @@ class TestFileGif(PillowTestCase): # duration as tuple im_list[0].save( - out, - save_all=True, - append_images=im_list[1:], - duration=tuple(duration_list) + out, save_all=True, append_images=im_list[1:], duration=tuple(duration_list) ) reread = Image.open(out) for duration in duration_list: - self.assertEqual(reread.info['duration'], duration) + self.assertEqual(reread.info["duration"], duration) try: reread.seek(reread.tell() + 1) except EOFError: @@ -471,20 +459,17 @@ class TestFileGif(PillowTestCase): def test_identical_frames(self): duration_list = [1000, 1500, 2000, 4000] - out = self.tempfile('temp.gif') + out = self.tempfile("temp.gif") im_list = [ - Image.new('L', (100, 100), '#000'), - Image.new('L', (100, 100), '#000'), - Image.new('L', (100, 100), '#000'), - Image.new('L', (100, 100), '#111') + Image.new("L", (100, 100), "#000"), + Image.new("L", (100, 100), "#000"), + Image.new("L", (100, 100), "#000"), + Image.new("L", (100, 100), "#111"), ] # duration as list im_list[0].save( - out, - save_all=True, - append_images=im_list[1:], - duration=duration_list + out, save_all=True, append_images=im_list[1:], duration=duration_list ) reread = Image.open(out) @@ -492,64 +477,63 @@ class TestFileGif(PillowTestCase): self.assertEqual(reread.n_frames, 2) # Assert that the new duration is the total of the identical frames - self.assertEqual(reread.info['duration'], 4500) + self.assertEqual(reread.info["duration"], 4500) def test_number_of_loops(self): number_of_loops = 2 - out = self.tempfile('temp.gif') - im = Image.new('L', (100, 100), '#000') + out = self.tempfile("temp.gif") + im = Image.new("L", (100, 100), "#000") im.save(out, loop=number_of_loops) reread = Image.open(out) - self.assertEqual(reread.info['loop'], number_of_loops) + self.assertEqual(reread.info["loop"], number_of_loops) def test_background(self): - out = self.tempfile('temp.gif') - im = Image.new('L', (100, 100), '#000') - im.info['background'] = 1 + out = self.tempfile("temp.gif") + im = Image.new("L", (100, 100), "#000") + im.info["background"] = 1 im.save(out) reread = Image.open(out) - self.assertEqual(reread.info['background'], im.info['background']) + 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) + self.assertIsInstance(im.info["background"], tuple) im.save(out) def test_comment(self): im = Image.open(TEST_GIF) - self.assertEqual(im.info['comment'], - b"File written by Adobe Photoshop\xa8 4.0") + self.assertEqual(im.info["comment"], b"File written by Adobe Photoshop\xa8 4.0") - out = self.tempfile('temp.gif') - im = Image.new('L', (100, 100), '#000') - im.info['comment'] = b"Test comment text" + out = self.tempfile("temp.gif") + im = Image.new("L", (100, 100), "#000") + im.info["comment"] = b"Test comment text" im.save(out) reread = Image.open(out) - self.assertEqual(reread.info['comment'], im.info['comment']) + 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') + 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.info["comment"] = comment im.save(out) reread = Image.open(out) - self.assertEqual(reread.info['comment'], comment) + self.assertEqual(reread.info["comment"], comment) def test_zero_comment_subblocks(self): - im = Image.open('Tests/images/hopper_zero_comment_subblocks.gif') + 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') + out = self.tempfile("temp.gif") def assertVersionAfterSave(im, version): im.save(out) @@ -557,11 +541,11 @@ class TestFileGif(PillowTestCase): self.assertEqual(reread.info["version"], version) # Test that GIF87a is used by default - im = Image.new('L', (100, 100), '#000') + im = Image.new("L", (100, 100), "#000") assertVersionAfterSave(im, b"GIF87a") # Test setting the version to 89a - im = Image.new('L', (100, 100), '#000') + im = Image.new("L", (100, 100), "#000") im.info["version"] = b"89a" assertVersionAfterSave(im, b"GIF89a") @@ -578,12 +562,11 @@ class TestFileGif(PillowTestCase): assertVersionAfterSave(im, b"GIF87a") def test_append_images(self): - out = self.tempfile('temp.gif') + out = self.tempfile("temp.gif") # Test appending single frame images - im = Image.new('RGB', (100, 100), '#f00') - ims = [Image.new('RGB', (100, 100), color) for color - in ['#0f0', '#00f']] + im = Image.new("RGB", (100, 100), "#f00") + ims = [Image.new("RGB", (100, 100), color) for color in ["#0f0", "#00f"]] im.copy().save(out, save_all=True, append_images=ims) reread = Image.open(out) @@ -593,6 +576,7 @@ class TestFileGif(PillowTestCase): def imGenerator(ims): for im in ims: yield im + im.save(out, save_all=True, append_images=imGenerator(ims)) reread = Image.open(out) @@ -614,44 +598,43 @@ class TestFileGif(PillowTestCase): # the top palette entry to trigger the bug. data = bytes(bytearray(range(1, 254))) - palette = ImagePalette.ImagePalette("RGB", list(range(256))*3) + palette = ImagePalette.ImagePalette("RGB", list(range(256)) * 3) - im = Image.new('L', (253, 1)) + im = Image.new("L", (253, 1)) im.frombytes(data) im.putpalette(palette) - out = self.tempfile('temp.gif') + out = self.tempfile("temp.gif") im.save(out, transparency=253) reloaded = Image.open(out) - self.assertEqual(reloaded.info['transparency'], 253) + self.assertEqual(reloaded.info["transparency"], 253) def test_rgb_transparency(self): - out = self.tempfile('temp.gif') + out = self.tempfile("temp.gif") # Single frame - im = Image.new('RGB', (1, 1)) - im.info['transparency'] = (255, 0, 0) + 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) + 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) + 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) + self.assertNotIn("transparency", reloaded.info) def test_bbox(self): - out = self.tempfile('temp.gif') + out = self.tempfile("temp.gif") - im = Image.new('RGB', (100, 100), '#fff') - ims = [Image.new("RGB", (100, 100), '#000')] + im = Image.new("RGB", (100, 100), "#fff") + ims = [Image.new("RGB", (100, 100), "#000")] im.save(out, save_all=True, append_images=ims) reread = Image.open(out) @@ -660,26 +643,26 @@ class TestFileGif(PillowTestCase): def test_palette_save_L(self): # generate an L mode image with a separate palette - im = hopper('P') - im_l = Image.frombytes('L', im.size, im.tobytes()) + im = hopper("P") + im_l = Image.frombytes("L", im.size, im.tobytes()) palette = bytes(bytearray(im.getpalette())) - out = self.tempfile('temp.gif') + out = self.tempfile("temp.gif") im_l.save(out, palette=palette) reloaded = Image.open(out) - self.assert_image_equal(reloaded.convert('RGB'), im.convert('RGB')) + self.assert_image_equal(reloaded.convert("RGB"), im.convert("RGB")) def test_palette_save_P(self): # pass in a different palette, then construct what the image # would look like. # Forcing a non-straight grayscale palette. - im = hopper('P') - palette = bytes(bytearray([255-i//3 for i in range(768)])) + im = hopper("P") + palette = bytes(bytearray([255 - i // 3 for i in range(768)])) - out = self.tempfile('temp.gif') + out = self.tempfile("temp.gif") im.save(out, palette=palette) reloaded = Image.open(out) @@ -690,10 +673,10 @@ class TestFileGif(PillowTestCase): # pass in a different palette, as an ImagePalette.ImagePalette # effectively the same as test_palette_save_P - im = hopper('P') - palette = ImagePalette.ImagePalette('RGB', list(range(256))[::-1]*3) + im = hopper("P") + palette = ImagePalette.ImagePalette("RGB", list(range(256))[::-1] * 3) - out = self.tempfile('temp.gif') + out = self.tempfile("temp.gif") im.save(out, palette=palette) reloaded = Image.open(out) @@ -703,22 +686,22 @@ class TestFileGif(PillowTestCase): def test_save_I(self): # Test saving something that would trigger the auto-convert to 'L' - im = hopper('I') + im = hopper("I") - out = self.tempfile('temp.gif') + out = self.tempfile("temp.gif") im.save(out) reloaded = Image.open(out) - self.assert_image_equal(reloaded.convert('L'), im.convert('L')) + self.assert_image_equal(reloaded.convert("L"), im.convert("L")) def test_getdata(self): # test getheader/getdata against legacy values # Create a 'P' image with holes in the palette im = Image._wedge().resize((16, 16)) - im.putpalette(ImagePalette.ImagePalette('RGB')) - im.info = {'background': 0} + im.putpalette(ImagePalette.ImagePalette("RGB")) + im.info = {"background": 0} - passed_palette = bytes(bytearray([255-i//3 for i in range(768)])) + passed_palette = bytes(bytearray([255 - i // 3 for i in range(768)])) GifImagePlugin._FORCE_OPTIMIZE = True try: @@ -726,10 +709,11 @@ class TestFileGif(PillowTestCase): d = GifImagePlugin.getdata(im) import pickle + # Enable to get target values on pre-refactor version # with open('Tests/images/gif_header_data.pkl', 'wb') as f: # pickle.dump((h, d), f, 1) - with open('Tests/images/gif_header_data.pkl', 'rb') as f: + with open("Tests/images/gif_header_data.pkl", "rb") as f: (h_target, d_target) = pickle.load(f) self.assertEqual(h, h_target) @@ -739,8 +723,14 @@ class TestFileGif(PillowTestCase): def test_lzw_bits(self): # see https://github.com/python-pillow/Pillow/issues/2811 - im = Image.open('Tests/images/issue_2811.gif') + im = Image.open("Tests/images/issue_2811.gif") self.assertEqual(im.tile[0][3][0], 11) # LZW bits # codec error prepatch im.load() + + def test_extents(self): + im = Image.open("Tests/images/test_extents.gif") + self.assertEqual(im.size, (100, 100)) + im.seek(1) + self.assertEqual(im.size, (150, 150)) diff --git a/Tests/test_file_gimpgradient.py b/Tests/test_file_gimpgradient.py index c89440239..14e0f3b38 100644 --- a/Tests/test_file_gimpgradient.py +++ b/Tests/test_file_gimpgradient.py @@ -4,7 +4,6 @@ from PIL import GimpGradientFile class TestImage(PillowTestCase): - def test_linear_pos_le_middle(self): # Arrange middle = 0.5 @@ -96,6 +95,7 @@ class TestImage(PillowTestCase): def test_load_via_imagepalette(self): # Arrange from PIL import ImagePalette + test_file = "Tests/images/gimp_gradient.ggr" # Act @@ -109,6 +109,7 @@ class TestImage(PillowTestCase): def test_load_1_3_via_imagepalette(self): # Arrange from PIL import ImagePalette + # GIMP 1.3 gradient files contain a name field test_file = "Tests/images/gimp_gradient_with_name.ggr" diff --git a/Tests/test_file_gimppalette.py b/Tests/test_file_gimppalette.py index 1ebac44e7..973dd3060 100644 --- a/Tests/test_file_gimppalette.py +++ b/Tests/test_file_gimppalette.py @@ -4,23 +4,22 @@ from PIL.GimpPaletteFile import GimpPaletteFile class TestImage(PillowTestCase): - def test_sanity(self): - with open('Tests/images/test.gpl', 'rb') as fp: + with open("Tests/images/test.gpl", "rb") as fp: GimpPaletteFile(fp) - with open('Tests/images/hopper.jpg', 'rb') as fp: + with open("Tests/images/hopper.jpg", "rb") as fp: self.assertRaises(SyntaxError, GimpPaletteFile, fp) - with open('Tests/images/bad_palette_file.gpl', 'rb') as fp: + with open("Tests/images/bad_palette_file.gpl", "rb") as fp: self.assertRaises(SyntaxError, GimpPaletteFile, fp) - with open('Tests/images/bad_palette_entry.gpl', 'rb') as fp: + with open("Tests/images/bad_palette_entry.gpl", "rb") as fp: self.assertRaises(ValueError, GimpPaletteFile, fp) def test_get_palette(self): # Arrange - with open('Tests/images/custom_gimp_palette.gpl', 'rb') as fp: + with open("Tests/images/custom_gimp_palette.gpl", "rb") as fp: palette_file = GimpPaletteFile(fp) # Act diff --git a/Tests/test_file_gribstub.py b/Tests/test_file_gribstub.py index 79e826945..0f371778a 100644 --- a/Tests/test_file_gribstub.py +++ b/Tests/test_file_gribstub.py @@ -6,7 +6,6 @@ TEST_FILE = "Tests/images/WAlaska.wind.7days.grb" class TestFileGribStub(PillowTestCase): - def test_open(self): # Act im = Image.open(TEST_FILE) @@ -23,8 +22,9 @@ class TestFileGribStub(PillowTestCase): invalid_file = "Tests/images/flower.jpg" # Act / Assert - self.assertRaises(SyntaxError, - GribStubImagePlugin.GribStubImageFile, invalid_file) + self.assertRaises( + SyntaxError, GribStubImagePlugin.GribStubImageFile, invalid_file + ) def test_load(self): # Arrange diff --git a/Tests/test_file_hdf5stub.py b/Tests/test_file_hdf5stub.py index 598ef0c5c..efb8de238 100644 --- a/Tests/test_file_hdf5stub.py +++ b/Tests/test_file_hdf5stub.py @@ -6,7 +6,6 @@ TEST_FILE = "Tests/images/hdf5.h5" class TestFileHdf5Stub(PillowTestCase): - def test_open(self): # Act im = Image.open(TEST_FILE) @@ -23,8 +22,9 @@ class TestFileHdf5Stub(PillowTestCase): invalid_file = "Tests/images/flower.jpg" # Act / Assert - self.assertRaises(SyntaxError, - Hdf5StubImagePlugin.HDF5StubImageFile, invalid_file) + self.assertRaises( + SyntaxError, Hdf5StubImagePlugin.HDF5StubImageFile, invalid_file + ) def test_load(self): # Arrange @@ -42,5 +42,5 @@ class TestFileHdf5Stub(PillowTestCase): # Act / Assert: stub cannot save without an implemented handler self.assertRaises(IOError, im.save, dummy_filename) self.assertRaises( - IOError, - Hdf5StubImagePlugin._save, im, dummy_fp, dummy_filename) + IOError, Hdf5StubImagePlugin._save, im, dummy_fp, dummy_filename + ) diff --git a/Tests/test_file_icns.py b/Tests/test_file_icns.py index ac60731f9..01ad34e04 100644 --- a/Tests/test_file_icns.py +++ b/Tests/test_file_icns.py @@ -8,11 +8,10 @@ import sys # sample icon file TEST_FILE = "Tests/images/pillow.icns" -enable_jpeg2k = hasattr(Image.core, 'jp2klib_version') +enable_jpeg2k = hasattr(Image.core, "jp2klib_version") class TestFileIcns(PillowTestCase): - def test_sanity(self): # Loading this icon by default should result in the largest size # (512x512@2x) being loaded @@ -25,7 +24,7 @@ class TestFileIcns(PillowTestCase): self.assertEqual(im.size, (1024, 1024)) self.assertEqual(im.format, "ICNS") - @unittest.skipIf(sys.platform != 'darwin', "requires macOS") + @unittest.skipIf(sys.platform != "darwin", "requires macOS") def test_save(self): im = Image.open(TEST_FILE) @@ -38,12 +37,12 @@ class TestFileIcns(PillowTestCase): self.assertEqual(reread.size, (1024, 1024)) self.assertEqual(reread.format, "ICNS") - @unittest.skipIf(sys.platform != 'darwin', "requires macOS") + @unittest.skipIf(sys.platform != "darwin", "requires macOS") def test_save_append_images(self): im = Image.open(TEST_FILE) temp_file = self.tempfile("temp.icns") - provided_im = Image.new('RGBA', (32, 32), (255, 0, 0, 128)) + provided_im = Image.new("RGBA", (32, 32), (255, 0, 0, 128)) im.save(temp_file, append_images=[provided_im]) reread = Image.open(temp_file) @@ -58,14 +57,13 @@ class TestFileIcns(PillowTestCase): # Check that we can load all of the sizes, and that the final pixel # dimensions are as expected im = Image.open(TEST_FILE) - for w, h, r in im.info['sizes']: + for w, h, r in im.info["sizes"]: wr = w * r hr = h * r - im2 = Image.open(TEST_FILE) - im2.size = (w, h, r) - im2.load() - self.assertEqual(im2.mode, 'RGBA') - self.assertEqual(im2.size, (wr, hr)) + im.size = (w, h, r) + im.load() + self.assertEqual(im.mode, "RGBA") + self.assertEqual(im.size, (wr, hr)) # Check that we cannot load an incorrect size with self.assertRaises(ValueError): @@ -74,14 +72,14 @@ class TestFileIcns(PillowTestCase): 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). - im = Image.open('Tests/images/pillow2.icns') - for w, h, r in im.info['sizes']: + im = Image.open("Tests/images/pillow2.icns") + for w, h, r in im.info["sizes"]: wr = w * r hr = h * r - im2 = Image.open('Tests/images/pillow2.icns') + im2 = Image.open("Tests/images/pillow2.icns") im2.size = (w, h, r) im2.load() - self.assertEqual(im2.mode, 'RGBA') + self.assertEqual(im2.mode, "RGBA") self.assertEqual(im2.size, (wr, hr)) def test_jp2_icon(self): @@ -95,18 +93,18 @@ class TestFileIcns(PillowTestCase): if not enable_jpeg2k: return - im = Image.open('Tests/images/pillow3.icns') - for w, h, r in im.info['sizes']: + im = Image.open("Tests/images/pillow3.icns") + for w, h, r in im.info["sizes"]: wr = w * r hr = h * r - im2 = Image.open('Tests/images/pillow3.icns') + im2 = Image.open("Tests/images/pillow3.icns") im2.size = (w, h, r) im2.load() - self.assertEqual(im2.mode, 'RGBA') + self.assertEqual(im2.mode, "RGBA") self.assertEqual(im2.size, (wr, hr)) def test_getimage(self): - with open(TEST_FILE, 'rb') as fp: + with open(TEST_FILE, "rb") as fp: icns_file = IcnsImagePlugin.IcnsFile(fp) im = icns_file.getimage() @@ -118,6 +116,5 @@ class TestFileIcns(PillowTestCase): self.assertEqual(im.size, (512, 512)) def test_not_an_icns_file(self): - with io.BytesIO(b'invalid\n') as fp: - self.assertRaises(SyntaxError, - IcnsImagePlugin.IcnsFile, fp) + with io.BytesIO(b"invalid\n") as fp: + self.assertRaises(SyntaxError, IcnsImagePlugin.IcnsFile, fp) diff --git a/Tests/test_file_ico.py b/Tests/test_file_ico.py index f6244e086..8427e2bd7 100644 --- a/Tests/test_file_ico.py +++ b/Tests/test_file_ico.py @@ -1,13 +1,12 @@ from .helper import PillowTestCase, hopper import io -from PIL import Image, IcoImagePlugin +from PIL import Image, ImageDraw, IcoImagePlugin TEST_ICO_FILE = "Tests/images/hopper.ico" class TestFileIco(PillowTestCase): - def test_sanity(self): im = Image.open(TEST_ICO_FILE) im.load() @@ -18,8 +17,7 @@ class TestFileIco(PillowTestCase): def test_invalid_file(self): with open("Tests/images/flower.jpg", "rb") as fp: - self.assertRaises(SyntaxError, - IcoImagePlugin.IcoImageFile, fp) + self.assertRaises(SyntaxError, IcoImagePlugin.IcoImageFile, fp) def test_save_to_bytes(self): output = io.BytesIO() @@ -29,13 +27,12 @@ class TestFileIco(PillowTestCase): # the default image output.seek(0) reloaded = Image.open(output) - self.assertEqual(reloaded.info['sizes'], {(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) self.assertEqual(reloaded.format, "ICO") - self.assert_image_equal(reloaded, - hopper().resize((64, 64), Image.LANCZOS)) + self.assert_image_equal(reloaded, hopper().resize((64, 64), Image.LANCZOS)) # the other one output.seek(0) @@ -45,8 +42,7 @@ class TestFileIco(PillowTestCase): self.assertEqual(im.mode, reloaded.mode) self.assertEqual((32, 32), reloaded.size) self.assertEqual(reloaded.format, "ICO") - self.assert_image_equal(reloaded, - hopper().resize((32, 32), Image.LANCZOS)) + self.assert_image_equal(reloaded, hopper().resize((32, 32), Image.LANCZOS)) def test_incorrect_size(self): im = Image.open(TEST_ICO_FILE) @@ -81,5 +77,26 @@ class TestFileIco(PillowTestCase): # Assert self.assertEqual( - im_saved.info['sizes'], - {(16, 16), (24, 24), (32, 32), (48, 48)}) + im_saved.info["sizes"], {(16, 16), (24, 24), (32, 32), (48, 48)} + ) + + def test_unexpected_size(self): + # This image has been manually hexedited to state that it is 16x32 + # while the image within is still 16x16 + im = self.assert_warning( + UserWarning, Image.open, "Tests/images/hopper_unexpected.ico" + ) + self.assertEqual(im.size, (16, 16)) + + def test_draw_reloaded(self): + im = Image.open(TEST_ICO_FILE) + outfile = self.tempfile("temp_saved_hopper_draw.ico") + + draw = ImageDraw.Draw(im) + draw.line((0, 0) + im.size, "#f00") + im.save(outfile) + + im = Image.open(outfile) + im.save("Tests/images/hopper_draw.ico") + reloaded = Image.open("Tests/images/hopper_draw.ico") + self.assert_image_equal(im, reloaded) diff --git a/Tests/test_file_im.py b/Tests/test_file_im.py index 8e774ce0a..2a89e39dc 100644 --- a/Tests/test_file_im.py +++ b/Tests/test_file_im.py @@ -7,7 +7,6 @@ TEST_IM = "Tests/images/hopper.im" class TestFileIm(PillowTestCase): - def test_sanity(self): im = Image.open(TEST_IM) im.load() @@ -19,6 +18,7 @@ class TestFileIm(PillowTestCase): def open(): im = Image.open(TEST_IM) im.load() + self.assert_warning(None, open) def test_tell(self): @@ -45,11 +45,11 @@ class TestFileIm(PillowTestCase): self.assertLess(im.tell(), n_frames) # Test that seeking to the last frame does not raise an error - im.seek(n_frames-1) + im.seek(n_frames - 1) def test_roundtrip(self): - for mode in ["RGB", "P"]: - out = self.tempfile('temp.im') + for mode in ["RGB", "P", "PA"]: + out = self.tempfile("temp.im") im = hopper(mode) im.save(out) reread = Image.open(out) @@ -57,15 +57,14 @@ class TestFileIm(PillowTestCase): self.assert_image_equal(reread, im) def test_save_unsupported_mode(self): - out = self.tempfile('temp.im') + out = self.tempfile("temp.im") im = hopper("HSV") self.assertRaises(ValueError, im.save, out) def test_invalid_file(self): invalid_file = "Tests/images/flower.jpg" - self.assertRaises(SyntaxError, - ImImagePlugin.ImImageFile, invalid_file) + self.assertRaises(SyntaxError, ImImagePlugin.ImImageFile, invalid_file) def test_number(self): self.assertEqual(1.2, ImImagePlugin.number("1.2")) diff --git a/Tests/test_file_iptc.py b/Tests/test_file_iptc.py index 83b735464..9f48633e1 100644 --- a/Tests/test_file_iptc.py +++ b/Tests/test_file_iptc.py @@ -6,7 +6,6 @@ TEST_FILE = "Tests/images/iptc.jpg" class TestFileIptc(PillowTestCase): - def test_getiptcinfo_jpg_none(self): # Arrange im = hopper() @@ -58,6 +57,7 @@ class TestFileIptc(PillowTestCase): except ImportError: from io import StringIO import sys + old_stdout = sys.stdout sys.stdout = mystdout = StringIO() diff --git a/Tests/test_file_jpeg.py b/Tests/test_file_jpeg.py index 25c3f754a..4ade11a29 100644 --- a/Tests/test_file_jpeg.py +++ b/Tests/test_file_jpeg.py @@ -15,7 +15,6 @@ TEST_FILE = "Tests/images/hopper.jpg" class TestFileJpeg(PillowTestCase): - def setUp(self): if "jpeg_encoder" not in codecs or "jpeg_decoder" not in codecs: self.skipTest("jpeg support not available") @@ -29,14 +28,13 @@ class TestFileJpeg(PillowTestCase): im.bytes = test_bytes # for testing only return im - def gen_random_image(self, size, mode='RGB'): + def gen_random_image(self, size, mode="RGB"): """ Generates a very hard to compress file :param size: tuple :param mode: optional image mode """ - return Image.frombytes(mode, size, - os.urandom(size[0]*size[1]*len(mode))) + return Image.frombytes(mode, size, os.urandom(size[0] * size[1] * len(mode))) def test_sanity(self): @@ -54,10 +52,11 @@ class TestFileJpeg(PillowTestCase): # Test APP/COM reader (@PIL135) im = Image.open(TEST_FILE) self.assertEqual( - im.applist[0], - ("APP0", b"JFIF\x00\x01\x01\x01\x00`\x00`\x00\x00")) - self.assertEqual(im.applist[1], ( - "COM", b"File written by Adobe Photoshop\xa8 4.0\x00")) + im.applist[0], ("APP0", b"JFIF\x00\x01\x01\x01\x00`\x00`\x00\x00") + ) + self.assertEqual( + im.applist[1], ("COM", b"File written by Adobe Photoshop\xa8 4.0\x00") + ) self.assertEqual(len(im.applist), 2) def test_cmyk(self): @@ -72,8 +71,7 @@ class TestFileJpeg(PillowTestCase): self.assertGreater(y, 0.8) self.assertEqual(k, 0.0) # the opposite corner is black - c, m, y, k = [x / 255.0 for x in im.getpixel(( - im.size[0]-1, im.size[1]-1))] + c, m, y, k = [x / 255.0 for x in im.getpixel((im.size[0] - 1, im.size[1] - 1))] self.assertGreater(k, 0.9) # roundtrip, and check again im = self.roundtrip(im) @@ -82,8 +80,7 @@ class TestFileJpeg(PillowTestCase): self.assertGreater(m, 0.8) self.assertGreater(y, 0.8) self.assertEqual(k, 0.0) - c, m, y, k = [x / 255.0 for x in im.getpixel(( - im.size[0]-1, im.size[1]-1))] + c, m, y, k = [x / 255.0 for x in im.getpixel((im.size[0] - 1, im.size[1] - 1))] self.assertGreater(k, 0.9) def test_dpi(self): @@ -91,6 +88,7 @@ class TestFileJpeg(PillowTestCase): im = Image.open(TEST_FILE) im = self.roundtrip(im, dpi=(xdpi, ydpi or xdpi)) return im.info.get("dpi") + self.assertEqual(test(72), (72, 72)) self.assertEqual(test(300), (300, 300)) self.assertEqual(test(100, 200), (100, 200)) @@ -119,31 +117,38 @@ class TestFileJpeg(PillowTestCase): # The ICC APP marker can store 65519 bytes per marker, so # using a 4-byte test code should allow us to detect out of # order issues. - icc_profile = (b"Test"*int(n/4+1))[:n] + icc_profile = (b"Test" * int(n / 4 + 1))[:n] self.assertEqual(len(icc_profile), n) # sanity im1 = self.roundtrip(hopper(), icc_profile=icc_profile) self.assertEqual(im1.info.get("icc_profile"), icc_profile or None) + test(0) test(1) test(3) test(4) test(5) - test(65533-14) # full JPEG marker block - test(65533-14+1) # full block plus one byte + test(65533 - 14) # full JPEG marker block + test(65533 - 14 + 1) # full block plus one byte test(ImageFile.MAXBLOCK) # full buffer block - test(ImageFile.MAXBLOCK+1) # full buffer block plus one byte - test(ImageFile.MAXBLOCK*4+3) # large block + test(ImageFile.MAXBLOCK + 1) # full buffer block plus one byte + test(ImageFile.MAXBLOCK * 4 + 3) # large block def test_large_icc_meta(self): # https://github.com/python-pillow/Pillow/issues/148 # Sometimes the meta data on the icc_profile block is bigger than # Image.MAXBLOCK or the image size. - im = Image.open('Tests/images/icc_profile_big.jpg') + im = Image.open("Tests/images/icc_profile_big.jpg") f = self.tempfile("temp.jpg") icc_profile = im.info["icc_profile"] # Should not raise IOError for image with icc larger than image size. - im.save(f, format='JPEG', progressive=True, quality=95, - icc_profile=icc_profile, optimize=True) + im.save( + f, + format="JPEG", + progressive=True, + quality=95, + icc_profile=icc_profile, + optimize=True, + ) def test_optimize(self): im1 = self.roundtrip(hopper()) @@ -156,9 +161,9 @@ class TestFileJpeg(PillowTestCase): def test_optimize_large_buffer(self): # https://github.com/python-pillow/Pillow/issues/148 - f = self.tempfile('temp.jpg') + f = self.tempfile("temp.jpg") # this requires ~ 1.5x Image.MAXBLOCK - im = Image.new("RGB", (4096, 4096), 0xff3333) + im = Image.new("RGB", (4096, 4096), 0xFF3333) im.save(f, format="JPEG", optimize=True) def test_progressive(self): @@ -173,13 +178,13 @@ class TestFileJpeg(PillowTestCase): self.assertGreaterEqual(im1.bytes, im3.bytes) def test_progressive_large_buffer(self): - f = self.tempfile('temp.jpg') + f = self.tempfile("temp.jpg") # this requires ~ 1.5x Image.MAXBLOCK - im = Image.new("RGB", (4096, 4096), 0xff3333) + im = Image.new("RGB", (4096, 4096), 0xFF3333) im.save(f, format="JPEG", progressive=True) def test_progressive_large_buffer_highest_quality(self): - f = self.tempfile('temp.jpg') + f = self.tempfile("temp.jpg") im = self.gen_random_image((255, 255)) # this requires more bytes than pixels in the image im.save(f, format="JPEG", progressive=True, quality=100) @@ -187,30 +192,31 @@ class TestFileJpeg(PillowTestCase): def test_progressive_cmyk_buffer(self): # Issue 2272, quality 90 cmyk image is tripping the large buffer bug. f = BytesIO() - im = self.gen_random_image((256, 256), 'CMYK') - im.save(f, format='JPEG', progressive=True, quality=94) + im = self.gen_random_image((256, 256), "CMYK") + im.save(f, format="JPEG", progressive=True, quality=94) def test_large_exif(self): # https://github.com/python-pillow/Pillow/issues/148 - f = self.tempfile('temp.jpg') + f = self.tempfile("temp.jpg") im = hopper() - im.save(f, 'JPEG', quality=90, exif=b"1"*65532) + im.save(f, "JPEG", quality=90, exif=b"1" * 65532) def test_exif_typeerror(self): - im = Image.open('Tests/images/exif_typeerror.jpg') + im = Image.open("Tests/images/exif_typeerror.jpg") # Should not raise a TypeError im._getexif() def test_exif_gps(self): # Arrange - im = Image.open('Tests/images/exif_gps.jpg') + im = Image.open("Tests/images/exif_gps.jpg") gps_index = 34853 expected_exif_gps = { - 0: b'\x00\x00\x00\x01', + 0: b"\x00\x00\x00\x01", 2: (4294967295, 1), - 5: b'\x01', + 5: b"\x01", 30: 65535, - 29: '1999:99:99 99:99:99'} + 29: "1999:99:99 99:99:99", + } # Act exif = im._getexif() @@ -222,35 +228,39 @@ class TestFileJpeg(PillowTestCase): # rolling back exif support in 3.1 to pre-3.0 formatting. # expected from 2.9, with b/u qualifiers switched for 3.2 compatibility # this test passes on 2.9 and 3.1, but not 3.0 - expected_exif = {34867: 4294967295, - 258: (24, 24, 24), - 36867: '2099:09:29 10:10:10', - 34853: {0: b'\x00\x00\x00\x01', - 2: (4294967295, 1), - 5: b'\x01', - 30: 65535, - 29: '1999:99:99 99:99:99'}, - 296: 65535, - 34665: 185, - 41994: 65535, - 514: 4294967295, - 271: 'Make', - 272: 'XXX-XXX', - 305: 'PIL', - 42034: ((1, 1), (1, 1), (1, 1), (1, 1)), - 42035: 'LensMake', - 34856: b'\xaa\xaa\xaa\xaa\xaa\xaa', - 282: (4294967295, 1), - 33434: (4294967295, 1)} + expected_exif = { + 34867: 4294967295, + 258: (24, 24, 24), + 36867: "2099:09:29 10:10:10", + 34853: { + 0: b"\x00\x00\x00\x01", + 2: (4294967295, 1), + 5: b"\x01", + 30: 65535, + 29: "1999:99:99 99:99:99", + }, + 296: 65535, + 34665: 185, + 41994: 65535, + 514: 4294967295, + 271: "Make", + 272: "XXX-XXX", + 305: "PIL", + 42034: ((1, 1), (1, 1), (1, 1), (1, 1)), + 42035: "LensMake", + 34856: b"\xaa\xaa\xaa\xaa\xaa\xaa", + 282: (4294967295, 1), + 33434: (4294967295, 1), + } - im = Image.open('Tests/images/exif_gps.jpg') + im = Image.open("Tests/images/exif_gps.jpg") exif = im._getexif() for tag, value in expected_exif.items(): self.assertEqual(value, exif[tag]) def test_exif_gps_typeerror(self): - im = Image.open('Tests/images/exif_gps_typeerror.jpg') + im = Image.open("Tests/images/exif_gps_typeerror.jpg") # Should not raise a TypeError im._getexif() @@ -291,6 +301,7 @@ class TestFileJpeg(PillowTestCase): def getsampling(im): layer = im.layer return layer[0][1:3] + layer[1][1:3] + layer[2][1:3] + # experimental API im = self.roundtrip(hopper(), subsampling=-1) # default self.assertEqual(getsampling(im), (2, 2, 1, 1, 1, 1)) @@ -312,13 +323,12 @@ class TestFileJpeg(PillowTestCase): im = self.roundtrip(hopper(), subsampling="4:1:1") self.assertEqual(getsampling(im), (2, 2, 1, 1, 1, 1)) - self.assertRaises( - TypeError, self.roundtrip, hopper(), subsampling="1:1:1") + self.assertRaises(TypeError, self.roundtrip, hopper(), subsampling="1:1:1") def test_exif(self): im = Image.open("Tests/images/pil_sample_rgb.jpg") info = im._getexif() - self.assertEqual(info[305], 'Adobe Photoshop CS Macintosh') + self.assertEqual(info[305], "Adobe Photoshop CS Macintosh") def test_mp(self): im = Image.open("Tests/images/pil_sample_rgb.jpg") @@ -327,16 +337,16 @@ class TestFileJpeg(PillowTestCase): def test_quality_keep(self): # RGB im = Image.open("Tests/images/hopper.jpg") - f = self.tempfile('temp.jpg') - im.save(f, quality='keep') + f = self.tempfile("temp.jpg") + im.save(f, quality="keep") # Grayscale im = Image.open("Tests/images/hopper_gray.jpg") - f = self.tempfile('temp.jpg') - im.save(f, quality='keep') + f = self.tempfile("temp.jpg") + im.save(f, quality="keep") # CMYK im = Image.open("Tests/images/pil_sample_cmyk.jpg") - f = self.tempfile('temp.jpg') - im.save(f, quality='keep') + f = self.tempfile("temp.jpg") + im.save(f, quality="keep") def test_junk_jpeg_header(self): # https://github.com/python-pillow/Pillow/issues/630 @@ -364,8 +374,8 @@ class TestFileJpeg(PillowTestCase): def _n_qtables_helper(self, n, test_file): im = Image.open(test_file) - f = self.tempfile('temp.jpg') - im.save(f, qtables=[[n]*64]*n) + f = self.tempfile("temp.jpg") + im.save(f, qtables=[[n] * 64] * n) im = Image.open(f) self.assertEqual(len(im.quantization), n) reloaded = self.roundtrip(im, qtables="keep") @@ -376,18 +386,18 @@ class TestFileJpeg(PillowTestCase): qtables = im.quantization reloaded = self.roundtrip(im, qtables=qtables, subsampling=0) self.assertEqual(im.quantization, reloaded.quantization) - self.assert_image_similar(im, self.roundtrip(im, qtables='web_low'), - 30) - self.assert_image_similar(im, self.roundtrip(im, qtables='web_high'), - 30) - self.assert_image_similar(im, self.roundtrip(im, qtables='keep'), 30) + self.assert_image_similar(im, self.roundtrip(im, qtables="web_low"), 30) + self.assert_image_similar(im, self.roundtrip(im, qtables="web_high"), 30) + self.assert_image_similar(im, self.roundtrip(im, qtables="keep"), 30) # valid bounds for baseline qtable bounds_qtable = [int(s) for s in ("255 1 " * 32).split(None)] self.roundtrip(im, qtables=[bounds_qtable]) # values from wizard.txt in jpeg9-a src package. - standard_l_qtable = [int(s) for s in """ + standard_l_qtable = [ + int(s) + for s in """ 16 11 10 16 24 40 51 61 12 12 14 19 26 58 60 55 14 13 16 24 40 57 69 56 @@ -396,9 +406,14 @@ class TestFileJpeg(PillowTestCase): 24 35 55 64 81 104 113 92 49 64 78 87 103 121 120 101 72 92 95 98 112 100 103 99 - """.split(None)] + """.split( + None + ) + ] - standard_chrominance_qtable = [int(s) for s in """ + standard_chrominance_qtable = [ + int(s) + for s in """ 17 18 24 47 99 99 99 99 18 21 26 66 99 99 99 99 24 26 56 99 99 99 99 99 @@ -407,25 +422,36 @@ class TestFileJpeg(PillowTestCase): 99 99 99 99 99 99 99 99 99 99 99 99 99 99 99 99 99 99 99 99 99 99 99 99 - """.split(None)] + """.split( + None + ) + ] # list of qtable lists self.assert_image_similar( - im, self.roundtrip( - im, qtables=[standard_l_qtable, standard_chrominance_qtable]), - 30) + im, + self.roundtrip( + im, qtables=[standard_l_qtable, standard_chrominance_qtable] + ), + 30, + ) # tuple of qtable lists self.assert_image_similar( - im, self.roundtrip( - im, qtables=(standard_l_qtable, standard_chrominance_qtable)), - 30) + im, + self.roundtrip( + im, qtables=(standard_l_qtable, standard_chrominance_qtable) + ), + 30, + ) # dict of qtable lists - self.assert_image_similar(im, - self.roundtrip(im, qtables={ - 0: standard_l_qtable, - 1: standard_chrominance_qtable - }), 30) + self.assert_image_similar( + im, + self.roundtrip( + im, qtables={0: standard_l_qtable, 1: standard_chrominance_qtable} + ), + 30, + ) self._n_qtables_helper(1, "Tests/images/hopper_gray.jpg") self._n_qtables_helper(1, "Tests/images/pil_sample_rgb.jpg") @@ -437,18 +463,16 @@ class TestFileJpeg(PillowTestCase): self._n_qtables_helper(4, "Tests/images/pil_sample_cmyk.jpg") # not a sequence - self.assertRaises(ValueError, self.roundtrip, im, qtables='a') + self.assertRaises(ValueError, self.roundtrip, im, qtables="a") # sequence wrong length self.assertRaises(ValueError, self.roundtrip, im, qtables=[]) # sequence wrong length - self.assertRaises(ValueError, - self.roundtrip, im, qtables=[1, 2, 3, 4, 5]) + self.assertRaises(ValueError, self.roundtrip, im, qtables=[1, 2, 3, 4, 5]) # qtable entry not a sequence self.assertRaises(ValueError, self.roundtrip, im, qtables=[1]) # qtable entry has wrong number of items - self.assertRaises(ValueError, - self.roundtrip, im, qtables=[[1, 2, 3, 4]]) + self.assertRaises(ValueError, self.roundtrip, im, qtables=[[1, 2, 3, 4]]) @unittest.skipUnless(djpeg_available(), "djpeg not available") def test_load_djpeg(self): @@ -468,11 +492,12 @@ class TestFileJpeg(PillowTestCase): def test_no_duplicate_0x1001_tag(self): # Arrange from PIL import ExifTags + tag_ids = {v: k for k, v in ExifTags.TAGS.items()} # Assert - self.assertEqual(tag_ids['RelatedImageWidth'], 0x1001) - self.assertEqual(tag_ids['RelatedImageLength'], 0x1002) + self.assertEqual(tag_ids["RelatedImageWidth"], 0x1001) + self.assertEqual(tag_ids["RelatedImageLength"], 0x1002) def test_MAXBLOCK_scaling(self): im = self.gen_random_image((512, 512)) @@ -482,9 +507,9 @@ class TestFileJpeg(PillowTestCase): reloaded = Image.open(f) # none of these should crash - reloaded.save(f, quality='keep') - reloaded.save(f, quality='keep', progressive=True) - reloaded.save(f, quality='keep', optimize=True) + reloaded.save(f, quality="keep") + reloaded.save(f, quality="keep", progressive=True) + reloaded.save(f, quality="keep", optimize=True) def test_bad_mpo_header(self): """ Treat unknown MPO as JPEG """ @@ -500,14 +525,14 @@ class TestFileJpeg(PillowTestCase): def test_save_correct_modes(self): out = BytesIO() - for mode in ['1', 'L', 'RGB', 'RGBX', 'CMYK', 'YCbCr']: + for mode in ["1", "L", "RGB", "RGBX", "CMYK", "YCbCr"]: img = Image.new(mode, (20, 20)) img.save(out, "JPEG") def test_save_wrong_modes(self): # ref https://github.com/python-pillow/Pillow/issues/2005 out = BytesIO() - for mode in ['LA', 'La', 'RGBA', 'RGBa', 'P']: + for mode in ["LA", "La", "RGBA", "RGBa", "P"]: img = Image.new(mode, (20, 20)) self.assertRaises(IOError, img.save, out, "JPEG") @@ -517,25 +542,25 @@ class TestFileJpeg(PillowTestCase): im = Image.open("Tests/images/hopper.tif") # Act - im.save(outfile, 'JPEG', dpi=im.info['dpi']) + im.save(outfile, "JPEG", dpi=im.info["dpi"]) # Assert reloaded = Image.open(outfile) reloaded.load() - self.assertEqual(im.info['dpi'], reloaded.info['dpi']) + self.assertEqual(im.info["dpi"], reloaded.info["dpi"]) def test_load_dpi_rounding(self): # Round up - im = Image.open('Tests/images/iptc_roundUp.jpg') + im = Image.open("Tests/images/iptc_roundUp.jpg") self.assertEqual(im.info["dpi"], (44, 44)) # Round down - im = Image.open('Tests/images/iptc_roundDown.jpg') + im = Image.open("Tests/images/iptc_roundDown.jpg") self.assertEqual(im.info["dpi"], (2, 2)) def test_save_dpi_rounding(self): outfile = self.tempfile("temp.jpg") - im = Image.open('Tests/images/hopper.jpg') + im = Image.open("Tests/images/hopper.jpg") im.save(outfile, dpi=(72.2, 72.2)) reloaded = Image.open(outfile) @@ -609,19 +634,26 @@ class TestFileJpeg(PillowTestCase): im = Image.open("Tests/images/exif-ifd-offset.jpg") # Act / Assert - self.assertEqual(im._getexif()[306], '2017:03:13 23:03:09') + self.assertEqual(im._getexif()[306], "2017:03:13 23:03:09") def test_photoshop(self): im = Image.open("Tests/images/photoshop-200dpi.jpg") - self.assertEqual(im.info["photoshop"][0x03ed], { - 'XResolution': 200.0, - 'DisplayedUnitsX': 1, - 'YResolution': 200.0, - 'DisplayedUnitsY': 1, - }) + self.assertEqual( + im.info["photoshop"][0x03ED], + { + "XResolution": 200.0, + "DisplayedUnitsX": 1, + "YResolution": 200.0, + "DisplayedUnitsY": 1, + }, + ) + + # This image does not contain a Photoshop header string + im = Image.open("Tests/images/app13.jpg") + self.assertNotIn("photoshop", im.info) -@unittest.skipUnless(sys.platform.startswith('win32'), "Windows only") +@unittest.skipUnless(sys.platform.startswith("win32"), "Windows only") class TestFileCloseW32(PillowTestCase): def setUp(self): if "jpeg_encoder" not in codecs or "jpeg_decoder" not in codecs: diff --git a/Tests/test_file_jpeg2k.py b/Tests/test_file_jpeg2k.py index 4b34354e2..a2483fade 100644 --- a/Tests/test_file_jpeg2k.py +++ b/Tests/test_file_jpeg2k.py @@ -5,7 +5,7 @@ from io import BytesIO codecs = dir(Image.core) -test_card = Image.open('Tests/images/test-card.png') +test_card = Image.open("Tests/images/test-card.png") test_card.load() # OpenJPEG 2.0.0 outputs this debugging message sometimes; we should @@ -14,10 +14,9 @@ test_card.load() class TestFileJpeg2k(PillowTestCase): - def setUp(self): if "jpeg2k_encoder" not in codecs or "jpeg2k_decoder" not in codecs: - self.skipTest('JPEG 2000 support not available') + self.skipTest("JPEG 2000 support not available") def roundtrip(self, im, **options): out = BytesIO() @@ -31,29 +30,28 @@ class TestFileJpeg2k(PillowTestCase): def test_sanity(self): # Internal version number - self.assertRegex(Image.core.jp2klib_version, r'\d+\.\d+\.\d+$') + self.assertRegex(Image.core.jp2klib_version, r"\d+\.\d+\.\d+$") - im = Image.open('Tests/images/test-card-lossless.jp2') + im = Image.open("Tests/images/test-card-lossless.jp2") px = im.load() self.assertEqual(px[0, 0], (0, 0, 0)) - self.assertEqual(im.mode, 'RGB') + self.assertEqual(im.mode, "RGB") self.assertEqual(im.size, (640, 480)) - self.assertEqual(im.format, 'JPEG2000') - self.assertEqual(im.get_format_mimetype(), 'image/jp2') + 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') + 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" - self.assertRaises(SyntaxError, - Jpeg2KImagePlugin.Jpeg2KImageFile, invalid_file) + self.assertRaises(SyntaxError, Jpeg2KImagePlugin.Jpeg2KImageFile, invalid_file) def test_bytesio(self): - with open('Tests/images/test-card-lossless.jp2', 'rb') as f: + with open("Tests/images/test-card-lossless.jp2", "rb") as f: data = BytesIO(f.read()) im = Image.open(data) im.load() @@ -63,14 +61,14 @@ class TestFileJpeg2k(PillowTestCase): # PIL (they were made using Adobe Photoshop) def test_lossless(self): - im = Image.open('Tests/images/test-card-lossless.jp2') + im = Image.open("Tests/images/test-card-lossless.jp2") im.load() - outfile = self.tempfile('temp_test-card.png') + outfile = self.tempfile("temp_test-card.png") im.save(outfile) self.assert_image_similar(im, test_card, 1.0e-3) def test_lossy_tiled(self): - im = Image.open('Tests/images/test-card-lossy-tiled.jp2') + im = Image.open("Tests/images/test-card-lossy-tiled.jp2") im.load() self.assert_image_similar(im, test_card, 2.0) @@ -88,8 +86,8 @@ class TestFileJpeg2k(PillowTestCase): def test_tiled_offset_rt(self): im = self.roundtrip( - test_card, tile_size=(128, 128), - tile_offset=(0, 0), offset=(32, 32)) + test_card, tile_size=(128, 128), tile_offset=(0, 0), offset=(32, 32) + ) self.assert_image_equal(im, test_card) def test_irreversible_rt(self): @@ -97,40 +95,34 @@ class TestFileJpeg2k(PillowTestCase): self.assert_image_similar(im, test_card, 2.0) def test_prog_qual_rt(self): - im = self.roundtrip( - test_card, quality_layers=[60, 40, 20], progression='LRCP') + im = self.roundtrip(test_card, quality_layers=[60, 40, 20], progression="LRCP") self.assert_image_similar(im, test_card, 2.0) def test_prog_res_rt(self): - im = self.roundtrip(test_card, num_resolutions=8, progression='RLCP') + im = self.roundtrip(test_card, num_resolutions=8, progression="RLCP") self.assert_image_equal(im, test_card) def test_reduce(self): - im = Image.open('Tests/images/test-card-lossless.jp2') + im = Image.open("Tests/images/test-card-lossless.jp2") im.reduce = 2 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 - ]: + 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) + 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], - progression='LRCP') + test_card.save( + out, "JPEG2000", quality_layers=[100, 50, 10], progression="LRCP" + ) out.seek(0) im = Image.open(out) @@ -146,49 +138,49 @@ class TestFileJpeg2k(PillowTestCase): def test_rgba(self): # Arrange - j2k = Image.open('Tests/images/rgb_trns_ycbc.j2k') - jp2 = Image.open('Tests/images/rgb_trns_ycbc.jp2') + j2k = Image.open("Tests/images/rgb_trns_ycbc.j2k") + jp2 = Image.open("Tests/images/rgb_trns_ycbc.jp2") # Act j2k.load() jp2.load() # Assert - self.assertEqual(j2k.mode, 'RGBA') - self.assertEqual(jp2.mode, 'RGBA') + self.assertEqual(j2k.mode, "RGBA") + self.assertEqual(jp2.mode, "RGBA") def test_16bit_monochrome_has_correct_mode(self): - j2k = Image.open('Tests/images/16bit.cropped.j2k') - jp2 = Image.open('Tests/images/16bit.cropped.jp2') + j2k = Image.open("Tests/images/16bit.cropped.j2k") + jp2 = Image.open("Tests/images/16bit.cropped.jp2") j2k.load() jp2.load() - self.assertEqual(j2k.mode, 'I;16') - self.assertEqual(jp2.mode, 'I;16') + self.assertEqual(j2k.mode, "I;16") + self.assertEqual(jp2.mode, "I;16") def test_16bit_monochrome_jp2_like_tiff(self): - tiff_16bit = Image.open('Tests/images/16bit.cropped.tif') - jp2 = Image.open('Tests/images/16bit.cropped.jp2') + tiff_16bit = Image.open("Tests/images/16bit.cropped.tif") + jp2 = Image.open("Tests/images/16bit.cropped.jp2") self.assert_image_similar(jp2, tiff_16bit, 1e-3) def test_16bit_monochrome_j2k_like_tiff(self): - tiff_16bit = Image.open('Tests/images/16bit.cropped.tif') - j2k = Image.open('Tests/images/16bit.cropped.j2k') + tiff_16bit = Image.open("Tests/images/16bit.cropped.tif") + j2k = Image.open("Tests/images/16bit.cropped.j2k") self.assert_image_similar(j2k, tiff_16bit, 1e-3) def test_16bit_j2k_roundtrips(self): - j2k = Image.open('Tests/images/16bit.cropped.j2k') + j2k = Image.open("Tests/images/16bit.cropped.j2k") im = self.roundtrip(j2k) self.assert_image_equal(im, j2k) def test_16bit_jp2_roundtrips(self): - jp2 = Image.open('Tests/images/16bit.cropped.jp2') + jp2 = Image.open("Tests/images/16bit.cropped.jp2") im = self.roundtrip(jp2) self.assert_image_equal(im, jp2) @@ -196,12 +188,13 @@ class TestFileJpeg2k(PillowTestCase): # prepatch, a malformed jp2 file could cause an UnboundLocalError # exception. with self.assertRaises(IOError): - Image.open('Tests/images/unbound_variable.jp2') + Image.open("Tests/images/unbound_variable.jp2") def test_parser_feed(self): # Arrange from PIL import ImageFile - with open('Tests/images/test-card-lossless.jp2', 'rb') as f: + + with open("Tests/images/test-card-lossless.jp2", "rb") as f: data = f.read() # Act diff --git a/Tests/test_file_libtiff.py b/Tests/test_file_libtiff.py index 0292b7733..79955a5af 100644 --- a/Tests/test_file_libtiff.py +++ b/Tests/test_file_libtiff.py @@ -16,9 +16,8 @@ logger = logging.getLogger(__name__) class LibTiffTestCase(PillowTestCase): - def setUp(self): - if not features.check('libtiff'): + if not features.check("libtiff"): self.skipTest("tiff support not available") def _assert_noerr(self, im): @@ -31,7 +30,7 @@ class LibTiffTestCase(PillowTestCase): im.getdata() try: - self.assertEqual(im._compression, 'group4') + self.assertEqual(im._compression, "group4") except AttributeError: print("No _compression") print(dir(im)) @@ -41,11 +40,10 @@ class LibTiffTestCase(PillowTestCase): im.save(out) out_bytes = io.BytesIO() - im.save(out_bytes, format='tiff', compression='group4') + im.save(out_bytes, format="tiff", compression="group4") class TestFileLibTiff(LibTiffTestCase): - def test_g4_tiff(self): """Test the ordinary file path load path""" @@ -64,7 +62,7 @@ class TestFileLibTiff(LibTiffTestCase): """Testing the string load path""" test_file = "Tests/images/hopper_g4_500.tif" - with open(test_file, 'rb') as f: + with open(test_file, "rb") as f: im = Image.open(f) self.assertEqual(im.size, (500, 500)) @@ -74,7 +72,7 @@ class TestFileLibTiff(LibTiffTestCase): """Testing the stringio loading code path""" test_file = "Tests/images/hopper_g4_500.tif" s = io.BytesIO() - with open(test_file, 'rb') as f: + with open(test_file, "rb") as f: s.write(f.read()) s.seek(0) im = Image.open(s) @@ -84,16 +82,16 @@ class TestFileLibTiff(LibTiffTestCase): def test_g4_eq_png(self): """ Checking that we're actually getting the data that we expect""" - png = Image.open('Tests/images/hopper_bw_500.png') - g4 = Image.open('Tests/images/hopper_g4_500.tif') + png = Image.open("Tests/images/hopper_bw_500.png") + g4 = Image.open("Tests/images/hopper_g4_500.tif") self.assert_image_equal(g4, png) # see https://github.com/python-pillow/Pillow/issues/279 def test_g4_fillorder_eq_png(self): """ Checking that we're actually getting the data that we expect""" - png = Image.open('Tests/images/g4-fillorder-test.png') - g4 = Image.open('Tests/images/g4-fillorder-test.tif') + png = Image.open("Tests/images/g4-fillorder-test.png") + g4 = Image.open("Tests/images/g4-fillorder-test.tif") self.assert_image_equal(g4, png) @@ -111,9 +109,9 @@ class TestFileLibTiff(LibTiffTestCase): self.assertEqual(reread.size, (500, 500)) self._assert_noerr(reread) self.assert_image_equal(reread, rot) - self.assertEqual(reread.info['compression'], 'group4') + self.assertEqual(reread.info["compression"], "group4") - self.assertEqual(reread.info['compression'], orig.info['compression']) + self.assertEqual(reread.info["compression"], orig.info["compression"]) self.assertNotEqual(orig.tobytes(), reread.tobytes()) @@ -123,18 +121,16 @@ class TestFileLibTiff(LibTiffTestCase): self.assertEqual(im.mode, "RGB") self.assertEqual(im.size, (278, 374)) - self.assertEqual( - im.tile[0][:3], ('tiff_adobe_deflate', (0, 0, 278, 374), 0)) + self.assertEqual(im.tile[0][:3], ("libtiff", (0, 0, 278, 374), 0)) im.load() - self.assert_image_equal_tofile(im, - 'Tests/images/tiff_adobe_deflate.png') + self.assert_image_equal_tofile(im, "Tests/images/tiff_adobe_deflate.png") def test_write_metadata(self): """ Test metadata writing through libtiff """ for legacy_api in [False, True]: - img = Image.open('Tests/images/hopper_g4.tif') - f = self.tempfile('temp.tiff') + img = Image.open("Tests/images/hopper_g4.tif") + f = self.tempfile("temp.tiff") img.save(f, tiffinfo=img.tag) @@ -145,8 +141,12 @@ class TestFileLibTiff(LibTiffTestCase): # PhotometricInterpretation is set from SAVE_INFO, # not the original image. - ignored = ['StripByteCounts', 'RowsPerStrip', 'PageNumber', - 'PhotometricInterpretation'] + ignored = [ + "StripByteCounts", + "RowsPerStrip", + "PageNumber", + "PhotometricInterpretation", + ] loaded = Image.open(f) if legacy_api: @@ -154,28 +154,27 @@ class TestFileLibTiff(LibTiffTestCase): else: reloaded = loaded.tag_v2.named() - for tag, value in itertools.chain(reloaded.items(), - original.items()): + for tag, value in itertools.chain(reloaded.items(), original.items()): if tag not in ignored: val = original[tag] - if tag.endswith('Resolution'): + if tag.endswith("Resolution"): if legacy_api: self.assertEqual( c_float(val[0][0] / val[0][1]).value, c_float(value[0][0] / value[0][1]).value, - msg="%s didn't roundtrip" % tag) + msg="%s didn't roundtrip" % tag, + ) else: self.assertEqual( - c_float(val).value, c_float(value).value, - msg="%s didn't roundtrip" % tag) + c_float(val).value, + c_float(value).value, + msg="%s didn't roundtrip" % tag, + ) else: - self.assertEqual( - val, value, msg="%s didn't roundtrip" % tag) + self.assertEqual(val, value, msg="%s didn't roundtrip" % tag) # https://github.com/python-pillow/Pillow/issues/1561 - requested_fields = ['StripByteCounts', - 'RowsPerStrip', - 'StripOffsets'] + requested_fields = ["StripByteCounts", "RowsPerStrip", "StripOffsets"] for field in requested_fields: self.assertIn(field, reloaded, "%s not in metadata" % field) @@ -186,13 +185,15 @@ class TestFileLibTiff(LibTiffTestCase): # Get the list of the ones that we should be able to write - core_items = {tag: info for tag, info in ((s, TiffTags.lookup(s)) for s - in TiffTags.LIBTIFF_CORE) - if info.type is not None} + core_items = { + tag: info + for tag, info in ((s, TiffTags.lookup(s)) for s in TiffTags.LIBTIFF_CORE) + if info.type is not None + } # Exclude ones that have special meaning # that we're already testing them - im = Image.open('Tests/images/hopper_g4.tif') + im = Image.open("Tests/images/hopper_g4.tif") for tag in im.tag_v2: try: del core_items[tag] @@ -206,11 +207,13 @@ class TestFileLibTiff(LibTiffTestCase): # 5: "rational", # 12: "double", # Type: dummy value - values = {2: 'test', - 3: 1, - 4: 2**20, - 5: TiffImagePlugin.IFDRational(100, 1), - 12: 1.05} + values = { + 2: "test", + 3: 1, + 4: 2 ** 20, + 5: TiffImagePlugin.IFDRational(100, 1), + 12: 1.05, + } new_ifd = TiffImagePlugin.ImageFileDirectory_v2() for tag, info in core_items.items(): @@ -219,8 +222,7 @@ class TestFileLibTiff(LibTiffTestCase): if info.length == 0: new_ifd[tag] = tuple(values[info.type] for _ in range(3)) else: - new_ifd[tag] = tuple(values[info.type] - for _ in range(info.length)) + new_ifd[tag] = tuple(values[info.type] for _ in range(info.length)) # Extra samples really doesn't make sense in this application. del new_ifd[338] @@ -236,16 +238,17 @@ class TestFileLibTiff(LibTiffTestCase): custom = { 37000: [4, TiffTags.SHORT], 37001: [4.2, TiffTags.RATIONAL], - 37002: ['custom tag value', TiffTags.ASCII], - 37003: [u'custom tag value', TiffTags.ASCII], - 37004: [b'custom tag value', TiffTags.BYTE] + 37002: ["custom tag value", TiffTags.ASCII], + 37003: [u"custom tag value", TiffTags.ASCII], + 37004: [b"custom tag value", TiffTags.BYTE], } libtiff_version = TiffImagePlugin._libtiff_version() libtiffs = [False] - if distutils.version.StrictVersion(libtiff_version) >= \ - distutils.version.StrictVersion("4.0"): + if distutils.version.StrictVersion( + libtiff_version + ) >= distutils.version.StrictVersion("4.0"): libtiffs.append(True) for libtiff in libtiffs: @@ -281,68 +284,68 @@ class TestFileLibTiff(LibTiffTestCase): def test_int_dpi(self): # issue #1765 - im = hopper('RGB') - out = self.tempfile('temp.tif') + im = hopper("RGB") + out = self.tempfile("temp.tif") TiffImagePlugin.WRITE_LIBTIFF = True im.save(out, dpi=(72, 72)) TiffImagePlugin.WRITE_LIBTIFF = False reloaded = Image.open(out) - self.assertEqual(reloaded.info['dpi'], (72.0, 72.0)) + self.assertEqual(reloaded.info["dpi"], (72.0, 72.0)) def test_g3_compression(self): - i = Image.open('Tests/images/hopper_g4_500.tif') + i = Image.open("Tests/images/hopper_g4_500.tif") out = self.tempfile("temp.tif") - i.save(out, compression='group3') + i.save(out, compression="group3") reread = Image.open(out) - self.assertEqual(reread.info['compression'], 'group3') + self.assertEqual(reread.info["compression"], "group3") self.assert_image_equal(reread, i) def test_little_endian(self): - im = Image.open('Tests/images/16bit.deflate.tif') + im = Image.open("Tests/images/16bit.deflate.tif") self.assertEqual(im.getpixel((0, 0)), 480) - self.assertEqual(im.mode, 'I;16') + self.assertEqual(im.mode, "I;16") b = im.tobytes() # Bytes are in image native order (little endian) if py3: - self.assertEqual(b[0], ord(b'\xe0')) - self.assertEqual(b[1], ord(b'\x01')) + self.assertEqual(b[0], ord(b"\xe0")) + self.assertEqual(b[1], ord(b"\x01")) else: - self.assertEqual(b[0], b'\xe0') - self.assertEqual(b[1], b'\x01') + self.assertEqual(b[0], b"\xe0") + self.assertEqual(b[1], b"\x01") out = self.tempfile("temp.tif") # out = "temp.le.tif" im.save(out) reread = Image.open(out) - self.assertEqual(reread.info['compression'], im.info['compression']) + self.assertEqual(reread.info["compression"], im.info["compression"]) self.assertEqual(reread.getpixel((0, 0)), 480) # UNDONE - libtiff defaults to writing in native endian, so # on big endian, we'll get back mode = 'I;16B' here. def test_big_endian(self): - im = Image.open('Tests/images/16bit.MM.deflate.tif') + im = Image.open("Tests/images/16bit.MM.deflate.tif") self.assertEqual(im.getpixel((0, 0)), 480) - self.assertEqual(im.mode, 'I;16B') + self.assertEqual(im.mode, "I;16B") b = im.tobytes() # Bytes are in image native order (big endian) if py3: - self.assertEqual(b[0], ord(b'\x01')) - self.assertEqual(b[1], ord(b'\xe0')) + self.assertEqual(b[0], ord(b"\x01")) + self.assertEqual(b[1], ord(b"\xe0")) else: - self.assertEqual(b[0], b'\x01') - self.assertEqual(b[1], b'\xe0') + self.assertEqual(b[0], b"\x01") + self.assertEqual(b[1], b"\xe0") out = self.tempfile("temp.tif") im.save(out) reread = Image.open(out) - self.assertEqual(reread.info['compression'], im.info['compression']) + self.assertEqual(reread.info["compression"], im.info["compression"]) self.assertEqual(reread.getpixel((0, 0)), 480) def test_g4_string_info(self): @@ -352,18 +355,18 @@ class TestFileLibTiff(LibTiffTestCase): out = self.tempfile("temp.tif") - orig.tag[269] = 'temp.tif' + orig.tag[269] = "temp.tif" orig.save(out) reread = Image.open(out) - self.assertEqual('temp.tif', reread.tag_v2[269]) - self.assertEqual('temp.tif', reread.tag[269][0]) + self.assertEqual("temp.tif", reread.tag_v2[269]) + self.assertEqual("temp.tif", reread.tag[269][0]) def test_12bit_rawmode(self): """ Are we generating the same interpretation of the image as Imagemagick is? """ TiffImagePlugin.READ_LIBTIFF = True - im = Image.open('Tests/images/12bit.cropped.tif') + im = Image.open("Tests/images/12bit.cropped.tif") im.load() TiffImagePlugin.READ_LIBTIFF = False # to make the target -- @@ -372,18 +375,19 @@ class TestFileLibTiff(LibTiffTestCase): # imagemagick will auto scale so that a 12bit FFF is 16bit FFF0, # so we need to unshift so that the integer values are the same. - self.assert_image_equal_tofile(im, 'Tests/images/12in16bit.tif') + self.assert_image_equal_tofile(im, "Tests/images/12in16bit.tif") def test_blur(self): # test case from irc, how to do blur on b/w image # and save to compressed tif. from PIL import ImageFilter - out = self.tempfile('temp.tif') - im = Image.open('Tests/images/pport_g4.tif') - im = im.convert('L') + + out = self.tempfile("temp.tif") + im = Image.open("Tests/images/pport_g4.tif") + im = im.convert("L") im = im.filter(ImageFilter.GaussianBlur(4)) - im.save(out, compression='tiff_adobe_deflate') + im.save(out, compression="tiff_adobe_deflate") im2 = Image.open(out) im2.load() @@ -391,23 +395,23 @@ class TestFileLibTiff(LibTiffTestCase): self.assert_image_equal(im, im2) def test_compressions(self): - im = hopper('RGB') - out = self.tempfile('temp.tif') + im = hopper("RGB") + out = self.tempfile("temp.tif") - for compression in ('packbits', 'tiff_lzw'): + for compression in ("packbits", "tiff_lzw"): im.save(out, compression=compression) im2 = Image.open(out) self.assert_image_equal(im, im2) - im.save(out, compression='jpeg') + im.save(out, compression="jpeg") im2 = Image.open(out) self.assert_image_similar(im, im2, 30) def test_cmyk_save(self): - im = hopper('CMYK') - out = self.tempfile('temp.tif') + im = hopper("CMYK") + out = self.tempfile("temp.tif") - im.save(out, compression='tiff_adobe_deflate') + im.save(out, compression="tiff_adobe_deflate") im2 = Image.open(out) self.assert_image_equal(im, im2) @@ -416,12 +420,12 @@ class TestFileLibTiff(LibTiffTestCase): to output on stderr from the error thrown by libtiff. We need to capture that but not now""" - im = hopper('RGB') - out = self.tempfile('temp.tif') + im = hopper("RGB") + out = self.tempfile("temp.tif") - self.assertRaises(IOError, im.save, out, compression='tiff_ccitt') - self.assertRaises(IOError, im.save, out, compression='group3') - self.assertRaises(IOError, im.save, out, compression='group4') + self.assertRaises(IOError, im.save, out, compression="tiff_ccitt") + self.assertRaises(IOError, im.save, out, compression="group3") + self.assertRaises(IOError, im.save, out, compression="group4") def test_fp_leak(self): im = Image.open("Tests/images/hopper_g4_500.tif") @@ -437,30 +441,30 @@ class TestFileLibTiff(LibTiffTestCase): def test_multipage(self): # issue #862 TiffImagePlugin.READ_LIBTIFF = True - im = Image.open('Tests/images/multipage.tiff') + im = Image.open("Tests/images/multipage.tiff") # file is a multipage tiff, 10x10 green, 10x10 red, 20x20 blue im.seek(0) self.assertEqual(im.size, (10, 10)) - self.assertEqual(im.convert('RGB').getpixel((0, 0)), (0, 128, 0)) + self.assertEqual(im.convert("RGB").getpixel((0, 0)), (0, 128, 0)) self.assertTrue(im.tag.next) im.seek(1) self.assertEqual(im.size, (10, 10)) - self.assertEqual(im.convert('RGB').getpixel((0, 0)), (255, 0, 0)) + self.assertEqual(im.convert("RGB").getpixel((0, 0)), (255, 0, 0)) self.assertTrue(im.tag.next) im.seek(2) self.assertFalse(im.tag.next) self.assertEqual(im.size, (20, 20)) - self.assertEqual(im.convert('RGB').getpixel((0, 0)), (0, 0, 255)) + self.assertEqual(im.convert("RGB").getpixel((0, 0)), (0, 0, 255)) TiffImagePlugin.READ_LIBTIFF = False def test_multipage_nframes(self): # issue #862 TiffImagePlugin.READ_LIBTIFF = True - im = Image.open('Tests/images/multipage.tiff') + im = Image.open("Tests/images/multipage.tiff") frames = im.n_frames self.assertEqual(frames, 3) for _ in range(frames): @@ -472,7 +476,7 @@ class TestFileLibTiff(LibTiffTestCase): def test__next(self): TiffImagePlugin.READ_LIBTIFF = True - im = Image.open('Tests/images/hopper.tif') + im = Image.open("Tests/images/hopper.tif") self.assertFalse(im.tag.next) im.load() self.assertFalse(im.tag.next) @@ -501,7 +505,7 @@ class TestFileLibTiff(LibTiffTestCase): "Tests/images/tiff_gray_2_4_bpp/hopper2I.tif", "Tests/images/tiff_gray_2_4_bpp/hopper2R.tif", "Tests/images/tiff_gray_2_4_bpp/hopper2IR.tif", - ) + ), ), ( 7.3, # epsilon @@ -510,7 +514,7 @@ class TestFileLibTiff(LibTiffTestCase): "Tests/images/tiff_gray_2_4_bpp/hopper4I.tif", "Tests/images/tiff_gray_2_4_bpp/hopper4R.tif", "Tests/images/tiff_gray_2_4_bpp/hopper4IR.tif", - ) + ), ), ) original = hopper("L") @@ -545,7 +549,7 @@ class TestFileLibTiff(LibTiffTestCase): self.assert_image_similar(pilim, pilim_load, 0) save_bytesio() - save_bytesio('raw') + save_bytesio("raw") save_bytesio("packbits") save_bytesio("tiff_lzw") @@ -554,12 +558,12 @@ class TestFileLibTiff(LibTiffTestCase): def test_crashing_metadata(self): # issue 1597 - im = Image.open('Tests/images/rdf.tif') - out = self.tempfile('temp.tif') + im = Image.open("Tests/images/rdf.tif") + out = self.tempfile("temp.tif") TiffImagePlugin.WRITE_LIBTIFF = True # this shouldn't crash - im.save(out, format='TIFF') + im.save(out, format="TIFF") TiffImagePlugin.WRITE_LIBTIFF = False def test_page_number_x_0(self): @@ -580,8 +584,8 @@ class TestFileLibTiff(LibTiffTestCase): # https://github.com/python-pillow/Pillow/issues/1651 tmpfile = self.tempfile("temp.tif") - with open(tmpfile, 'wb') as f: - with open("Tests/images/g4-multi.tiff", 'rb') as src: + with open(tmpfile, "wb") as f: + with open("Tests/images/g4-multi.tiff", "rb") as src: f.write(src.read()) im = Image.open(tmpfile) @@ -592,29 +596,29 @@ class TestFileLibTiff(LibTiffTestCase): def test_read_icc(self): with Image.open("Tests/images/hopper.iccprofile.tif") as img: - icc = img.info.get('icc_profile') + icc = img.info.get("icc_profile") self.assertIsNotNone(icc) TiffImagePlugin.READ_LIBTIFF = True with Image.open("Tests/images/hopper.iccprofile.tif") as img: - icc_libtiff = img.info.get('icc_profile') + icc_libtiff = img.info.get("icc_profile") self.assertIsNotNone(icc_libtiff) TiffImagePlugin.READ_LIBTIFF = False self.assertEqual(icc, icc_libtiff) def test_multipage_compression(self): - im = Image.open('Tests/images/compression.tif') + im = Image.open("Tests/images/compression.tif") im.seek(0) - self.assertEqual(im._compression, 'tiff_ccitt') + self.assertEqual(im._compression, "tiff_ccitt") self.assertEqual(im.size, (10, 10)) im.seek(1) - self.assertEqual(im._compression, 'packbits') + self.assertEqual(im._compression, "packbits") self.assertEqual(im.size, (10, 10)) im.load() im.seek(0) - self.assertEqual(im._compression, 'tiff_ccitt') + self.assertEqual(im._compression, "tiff_ccitt") self.assertEqual(im.size, (10, 10)) im.load() @@ -631,6 +635,26 @@ class TestFileLibTiff(LibTiffTestCase): # Should not raise UnicodeDecodeError or anything else im.save(outfile) + def test_16bit_RGB_tiff(self): + im = Image.open("Tests/images/tiff_16bit_RGB.tiff") + + self.assertEqual(im.mode, "RGB") + self.assertEqual(im.size, (100, 40)) + self.assertEqual( + im.tile, + [ + ( + "libtiff", + (0, 0, 100, 40), + 0, + ("RGB;16N", "tiff_adobe_deflate", False, 8), + ) + ], + ) + im.load() + + self.assert_image_equal_tofile(im, "Tests/images/tiff_16bit_RGB_target.png") + def test_16bit_RGBa_tiff(self): im = Image.open("Tests/images/tiff_16bit_RGBa.tiff") @@ -638,12 +662,11 @@ class TestFileLibTiff(LibTiffTestCase): self.assertEqual(im.size, (100, 40)) self.assertEqual( im.tile, - [('tiff_lzw', (0, 0, 100, 40), 0, ('RGBa;16N', 'tiff_lzw', False))] + [("libtiff", (0, 0, 100, 40), 0, ("RGBa;16N", "tiff_lzw", False, 38236))], ) im.load() - self.assert_image_equal_tofile( - im, "Tests/images/tiff_16bit_RGBa_target.png") + self.assert_image_equal_tofile(im, "Tests/images/tiff_16bit_RGBa_target.png") def test_gimp_tiff(self): # Read TIFF JPEG images from GIMP [@PIL168] @@ -658,7 +681,7 @@ class TestFileLibTiff(LibTiffTestCase): self.assertEqual(im.mode, "RGB") self.assertEqual(im.size, (256, 256)) self.assertEqual( - im.tile, [('jpeg', (0, 0, 256, 256), 0, ('RGB', 'jpeg', False))] + im.tile, [("libtiff", (0, 0, 256, 256), 0, ("RGB", "jpeg", False, 5122))] ) im.load() @@ -667,15 +690,14 @@ class TestFileLibTiff(LibTiffTestCase): def test_sampleformat(self): # https://github.com/python-pillow/Pillow/issues/1466 im = Image.open("Tests/images/copyleft.tiff") - self.assertEqual(im.mode, 'RGB') + self.assertEqual(im.mode, "RGB") - self.assert_image_equal_tofile(im, "Tests/images/copyleft.png", - mode='RGB') + self.assert_image_equal_tofile(im, "Tests/images/copyleft.png", mode="RGB") def test_lzw(self): im = Image.open("Tests/images/hopper_lzw.tif") - self.assertEqual(im.mode, 'RGB') + self.assertEqual(im.mode, "RGB") self.assertEqual(im.size, (128, 128)) self.assertEqual(im.format, "TIFF") im2 = hopper() @@ -687,6 +709,12 @@ class TestFileLibTiff(LibTiffTestCase): self.assert_image_similar_tofile(im, "Tests/images/pil_sample_cmyk.jpg", 0.5) + def test_strip_cmyk_16l_jpeg(self): + infile = "Tests/images/tiff_strip_cmyk_16l_jpeg.tif" + im = Image.open(infile) + + self.assert_image_similar_tofile(im, "Tests/images/pil_sample_cmyk.jpg", 0.5) + def test_strip_ycbcr_jpeg_2x2_sampling(self): infile = "Tests/images/tiff_strip_ycbcr_jpeg_2x2_sampling.tif" im = Image.open(infile) @@ -721,5 +749,6 @@ class TestFileLibTiff(LibTiffTestCase): infile = "Tests/images/old-style-jpeg-compression.tif" im = Image.open(infile) - self.assert_image_equal_tofile(im, - "Tests/images/old-style-jpeg-compression.png") + self.assert_image_equal_tofile( + im, "Tests/images/old-style-jpeg-compression.png" + ) diff --git a/Tests/test_file_libtiff_small.py b/Tests/test_file_libtiff_small.py index 6b379718e..0db37c7ea 100644 --- a/Tests/test_file_libtiff_small.py +++ b/Tests/test_file_libtiff_small.py @@ -17,7 +17,7 @@ class TestFileLibTiffSmall(LibTiffTestCase): """Testing the open file load path""" test_file = "Tests/images/hopper_g4.tif" - with open(test_file, 'rb') as f: + with open(test_file, "rb") as f: im = Image.open(f) self.assertEqual(im.size, (128, 128)) @@ -26,9 +26,10 @@ class TestFileLibTiffSmall(LibTiffTestCase): def test_g4_hopper_bytesio(self): """Testing the bytesio loading code path""" from io import BytesIO + test_file = "Tests/images/hopper_g4.tif" s = BytesIO() - with open(test_file, 'rb') as f: + with open(test_file, "rb") as f: s.write(f.read()) s.seek(0) im = Image.open(s) diff --git a/Tests/test_file_mcidas.py b/Tests/test_file_mcidas.py index e273faad9..5b8f4d592 100644 --- a/Tests/test_file_mcidas.py +++ b/Tests/test_file_mcidas.py @@ -4,12 +4,10 @@ from PIL import Image, McIdasImagePlugin class TestFileMcIdas(PillowTestCase): - def test_invalid_file(self): invalid_file = "Tests/images/flower.jpg" - self.assertRaises(SyntaxError, - McIdasImagePlugin.McIdasImageFile, invalid_file) + self.assertRaises(SyntaxError, McIdasImagePlugin.McIdasImageFile, invalid_file) def test_valid_file(self): # Arrange diff --git a/Tests/test_file_mic.py b/Tests/test_file_mic.py index 3a7daa459..390f56af1 100644 --- a/Tests/test_file_mic.py +++ b/Tests/test_file_mic.py @@ -13,9 +13,8 @@ TEST_FILE = "Tests/images/hopper.mic" @unittest.skipUnless(olefile_installed, "olefile package not installed") -@unittest.skipUnless(features.check('libtiff'), "libtiff not installed") +@unittest.skipUnless(features.check("libtiff"), "libtiff not installed") class TestFileMic(PillowTestCase): - def test_sanity(self): im = Image.open(TEST_FILE) im.load() @@ -24,8 +23,8 @@ class TestFileMic(PillowTestCase): self.assertEqual(im.format, "MIC") # Adjust for the gamma of 2.2 encoded into the file - lut = ImagePalette.make_gamma_lut(1/2.2) - im = Image.merge('RGBA', [chan.point(lut) for chan in im.split()]) + lut = ImagePalette.make_gamma_lut(1 / 2.2) + im = Image.merge("RGBA", [chan.point(lut) for chan in im.split()]) im2 = hopper("RGBA") self.assert_image_similar(im, im2, 10) @@ -57,10 +56,8 @@ class TestFileMic(PillowTestCase): def test_invalid_file(self): # Test an invalid OLE file invalid_file = "Tests/images/flower.jpg" - self.assertRaises(SyntaxError, - MicImagePlugin.MicImageFile, invalid_file) + self.assertRaises(SyntaxError, MicImagePlugin.MicImageFile, invalid_file) # Test a valid OLE file, but not a MIC file ole_file = "Tests/images/test-ole-file.doc" - self.assertRaises(SyntaxError, - MicImagePlugin.MicImageFile, ole_file) + self.assertRaises(SyntaxError, MicImagePlugin.MicImageFile, ole_file) diff --git a/Tests/test_file_mpo.py b/Tests/test_file_mpo.py index ce1ca96b6..45f88ca61 100644 --- a/Tests/test_file_mpo.py +++ b/Tests/test_file_mpo.py @@ -7,7 +7,6 @@ test_files = ["Tests/images/sugarshack.mpo", "Tests/images/frozenpond.mpo"] class TestFileMpo(PillowTestCase): - def setUp(self): codecs = dir(Image.core) if "jpeg_encoder" not in codecs or "jpeg_decoder" not in codecs: @@ -35,23 +34,25 @@ class TestFileMpo(PillowTestCase): 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) im = Image.open(test_file) - self.assertEqual(im.applist[0][0], 'APP1') - self.assertEqual(im.applist[1][0], 'APP2') - self.assertEqual(im.applist[1][1][:16], - b'MPF\x00MM\x00*\x00\x00\x00\x08\x00\x03\xb0\x00') + self.assertEqual(im.applist[0][0], "APP1") + self.assertEqual(im.applist[1][0], "APP2") + self.assertEqual( + im.applist[1][1][:16], b"MPF\x00MM\x00*\x00\x00\x00\x08\x00\x03\xb0\x00" + ) self.assertEqual(len(im.applist), 2) def test_exif(self): for test_file in test_files: im = Image.open(test_file) info = im._getexif() - self.assertEqual(info[272], 'Nintendo 3DS') + self.assertEqual(info[272], "Nintendo 3DS") self.assertEqual(info[296], 2) self.assertEqual(info[34665], 188) @@ -68,19 +69,19 @@ class TestFileMpo(PillowTestCase): # Nintendo im = Image.open("Tests/images/sugarshack.mpo") exif = im.getexif() - self.assertEqual(exif.get_ifd(0x927c)[0x1101]["Parallax"], -44.798187255859375) + self.assertEqual(exif.get_ifd(0x927C)[0x1101]["Parallax"], -44.798187255859375) # Fujifilm im = Image.open("Tests/images/fujifilm.mpo") im.seek(1) exif = im.getexif() - self.assertEqual(exif.get_ifd(0x927c)[0xb211], -3.125) + self.assertEqual(exif.get_ifd(0x927C)[0xB211], -3.125) def test_mp(self): for test_file in test_files: im = Image.open(test_file) mpinfo = im._getmp() - self.assertEqual(mpinfo[45056], b'0100') + self.assertEqual(mpinfo[45056], b"0100") self.assertEqual(mpinfo[45057], 2) def test_mp_offset(self): @@ -88,7 +89,7 @@ class TestFileMpo(PillowTestCase): # 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[45056], b"0100") self.assertEqual(mpinfo[45057], 2) def test_mp_attribute(self): @@ -97,17 +98,16 @@ class TestFileMpo(PillowTestCase): mpinfo = im._getmp() frameNumber = 0 for mpentry in mpinfo[45058]: - mpattr = mpentry['Attribute'] + mpattr = mpentry["Attribute"] if frameNumber: - self.assertFalse(mpattr['RepresentativeImageFlag']) + self.assertFalse(mpattr["RepresentativeImageFlag"]) else: - self.assertTrue(mpattr['RepresentativeImageFlag']) - self.assertFalse(mpattr['DependentParentImageFlag']) - self.assertFalse(mpattr['DependentChildImageFlag']) - self.assertEqual(mpattr['ImageDataFormat'], 'JPEG') - self.assertEqual(mpattr['MPType'], - 'Multi-Frame Image: (Disparity)') - self.assertEqual(mpattr['Reserved'], 0) + self.assertTrue(mpattr["RepresentativeImageFlag"]) + self.assertFalse(mpattr["DependentParentImageFlag"]) + self.assertFalse(mpattr["DependentChildImageFlag"]) + self.assertEqual(mpattr["ImageDataFormat"], "JPEG") + self.assertEqual(mpattr["MPType"], "Multi-Frame Image: (Disparity)") + self.assertEqual(mpattr["Reserved"], 0) frameNumber += 1 def test_seek(self): @@ -144,7 +144,7 @@ class TestFileMpo(PillowTestCase): self.assertLess(im.tell(), n_frames) # Test that seeking to the last frame does not raise an error - im.seek(n_frames-1) + im.seek(n_frames - 1) def test_image_grab(self): for test_file in test_files: diff --git a/Tests/test_file_msp.py b/Tests/test_file_msp.py index 724fc78b1..c60138ebd 100644 --- a/Tests/test_file_msp.py +++ b/Tests/test_file_msp.py @@ -10,7 +10,6 @@ YA_EXTRA_DIR = "Tests/images/msp" class TestFileMsp(PillowTestCase): - def test_sanity(self): test_file = self.tempfile("temp.msp") @@ -25,8 +24,7 @@ class TestFileMsp(PillowTestCase): def test_invalid_file(self): invalid_file = "Tests/images/flower.jpg" - self.assertRaises(SyntaxError, - MspImagePlugin.MspImageFile, invalid_file) + self.assertRaises(SyntaxError, MspImagePlugin.MspImageFile, invalid_file) def test_bad_checksum(self): # Arrange @@ -34,8 +32,7 @@ class TestFileMsp(PillowTestCase): bad_checksum = "Tests/images/hopper_bad_checksum.msp" # Act / Assert - self.assertRaises(SyntaxError, - MspImagePlugin.MspImageFile, bad_checksum) + self.assertRaises(SyntaxError, MspImagePlugin.MspImageFile, bad_checksum) def test_open_windows_v1(self): # Arrange @@ -51,25 +48,26 @@ class TestFileMsp(PillowTestCase): target = Image.open(target_path) self.assert_image_equal(im, target) - @unittest.skipIf(not os.path.exists(EXTRA_DIR), - "Extra image files not installed") + @unittest.skipIf(not os.path.exists(EXTRA_DIR), "Extra image files not installed") def test_open_windows_v2(self): - files = (os.path.join(EXTRA_DIR, f) for f in os.listdir(EXTRA_DIR) - if os.path.splitext(f)[1] == '.msp') + files = ( + os.path.join(EXTRA_DIR, f) + for f in os.listdir(EXTRA_DIR) + if os.path.splitext(f)[1] == ".msp" + ) for path in files: - self._assert_file_image_equal(path, - path.replace('.msp', '.png')) + self._assert_file_image_equal(path, path.replace(".msp", ".png")) - @unittest.skipIf(not os.path.exists(YA_EXTRA_DIR), - "Even More Extra image files not installed") + @unittest.skipIf( + not os.path.exists(YA_EXTRA_DIR), "Even More Extra image files not installed" + ) def test_msp_v2(self): for f in os.listdir(YA_EXTRA_DIR): - if '.MSP' not in f: + if ".MSP" not in f: continue path = os.path.join(YA_EXTRA_DIR, f) - self._assert_file_image_equal(path, - path.replace('.MSP', '.png')) + self._assert_file_image_equal(path, path.replace(".MSP", ".png")) def test_cannot_save_wrong_mode(self): # Arrange diff --git a/Tests/test_file_palm.py b/Tests/test_file_palm.py index a6491634a..f68b5a871 100644 --- a/Tests/test_file_palm.py +++ b/Tests/test_file_palm.py @@ -46,6 +46,13 @@ class TestFilePalm(PillowTestCase): self.skipKnownBadTest("Palm P image is wrong") self.roundtrip(mode) + def test_l_ioerror(self): + # Arrange + mode = "L" + + # Act / Assert + self.assertRaises(IOError, self.helper_save_as_palm, mode) + def test_rgb_ioerror(self): # Arrange mode = "RGB" diff --git a/Tests/test_file_pcd.py b/Tests/test_file_pcd.py index 7296a303f..1bd66783a 100644 --- a/Tests/test_file_pcd.py +++ b/Tests/test_file_pcd.py @@ -3,9 +3,8 @@ from PIL import Image class TestFilePcd(PillowTestCase): - def test_load_raw(self): - im = Image.open('Tests/images/hopper.pcd') + im = Image.open("Tests/images/hopper.pcd") im.load() # should not segfault. # Note that this image was created with a resized hopper diff --git a/Tests/test_file_pcx.py b/Tests/test_file_pcx.py index 7608db47c..ae8f0f2ee 100644 --- a/Tests/test_file_pcx.py +++ b/Tests/test_file_pcx.py @@ -4,7 +4,6 @@ from PIL import Image, ImageFile, PcxImagePlugin class TestFilePcx(PillowTestCase): - def _roundtrip(self, im): f = self.tempfile("temp.pcx") im.save(f) @@ -17,7 +16,7 @@ class TestFilePcx(PillowTestCase): self.assert_image_equal(im2, im) def test_sanity(self): - for mode in ('1', 'L', 'P', 'RGB'): + for mode in ("1", "L", "P", "RGB"): self._roundtrip(hopper(mode)) # Test an unsupported mode @@ -28,14 +27,13 @@ class TestFilePcx(PillowTestCase): def test_invalid_file(self): invalid_file = "Tests/images/flower.jpg" - self.assertRaises(SyntaxError, - PcxImagePlugin.PcxImageFile, invalid_file) + self.assertRaises(SyntaxError, PcxImagePlugin.PcxImageFile, invalid_file) def test_odd(self): # see issue #523, odd sized images should have a stride that's even. # not that imagemagick or gimp write pcx that way. # we were not handling properly. - for mode in ('1', 'L', 'P', 'RGB'): + for mode in ("1", "L", "P", "RGB"): # larger, odd sized images are better here to ensure that # we handle interrupted scan lines properly. self._roundtrip(hopper(mode).resize((511, 511))) @@ -50,17 +48,17 @@ class TestFilePcx(PillowTestCase): self.assertEqual(im.tile[0][1], (0, 0, 447, 144)) # Make sure all pixels are either 0 or 255. - self.assertEqual(im.histogram()[0] + im.histogram()[255], 447*144) + self.assertEqual(im.histogram()[0] + im.histogram()[255], 447 * 144) def test_1px_width(self): - im = Image.new('L', (1, 256)) + im = Image.new("L", (1, 256)) px = im.load() for y in range(256): px[0, y] = y self._roundtrip(im) def test_large_count(self): - im = Image.new('L', (256, 1)) + im = Image.new("L", (256, 1)) px = im.load() for x in range(256): px[x, 0] = x // 67 * 67 @@ -75,7 +73,7 @@ class TestFilePcx(PillowTestCase): ImageFile.MAXBLOCK = _last def test_break_in_count_overflow(self): - im = Image.new('L', (256, 5)) + im = Image.new("L", (256, 5)) px = im.load() for y in range(4): for x in range(256): @@ -83,7 +81,7 @@ class TestFilePcx(PillowTestCase): self._test_buffer_overflow(im) def test_break_one_in_loop(self): - im = Image.new('L', (256, 5)) + im = Image.new("L", (256, 5)) px = im.load() for y in range(5): for x in range(256): @@ -91,7 +89,7 @@ class TestFilePcx(PillowTestCase): self._test_buffer_overflow(im) def test_break_many_in_loop(self): - im = Image.new('L', (256, 5)) + im = Image.new("L", (256, 5)) px = im.load() for y in range(4): for x in range(256): @@ -101,7 +99,7 @@ class TestFilePcx(PillowTestCase): self._test_buffer_overflow(im) def test_break_one_at_end(self): - im = Image.new('L', (256, 5)) + im = Image.new("L", (256, 5)) px = im.load() for y in range(5): for x in range(256): @@ -110,7 +108,7 @@ class TestFilePcx(PillowTestCase): self._test_buffer_overflow(im) def test_break_many_at_end(self): - im = Image.new('L', (256, 5)) + im = Image.new("L", (256, 5)) px = im.load() for y in range(5): for x in range(256): @@ -121,7 +119,7 @@ class TestFilePcx(PillowTestCase): self._test_buffer_overflow(im) def test_break_padding(self): - im = Image.new('L', (257, 5)) + im = Image.new("L", (257, 5)) px = im.load() for y in range(5): for x in range(257): diff --git a/Tests/test_file_pdf.py b/Tests/test_file_pdf.py index 7b024426b..b7ee70034 100644 --- a/Tests/test_file_pdf.py +++ b/Tests/test_file_pdf.py @@ -8,7 +8,6 @@ import time class TestFilePdf(PillowTestCase): - def helper_save_as_pdf(self, mode, **kwargs): # Arrange im = hopper(mode) @@ -21,11 +20,17 @@ class TestFilePdf(PillowTestCase): self.assertTrue(os.path.isfile(outfile)) self.assertGreater(os.path.getsize(outfile), 0) with PdfParser.PdfParser(outfile) as pdf: - if kwargs.get("append_images", False) or \ - kwargs.get("append", False): + if kwargs.get("append_images", False) or kwargs.get("append", False): self.assertGreater(len(pdf.pages), 1) else: self.assertGreater(len(pdf.pages), 0) + with open(outfile, "rb") as fp: + contents = fp.read() + size = tuple( + int(d) + for d in contents.split(b"/MediaBox [ 0 0 ")[1].split(b"]")[0].split() + ) + self.assertEqual(im.size, size) return outfile @@ -77,7 +82,7 @@ class TestFilePdf(PillowTestCase): # Multiframe image im = Image.open("Tests/images/dispose_bgnd.gif") - outfile = self.tempfile('temp.pdf') + outfile = self.tempfile("temp.pdf") im.save(outfile, save_all=True) self.assertTrue(os.path.isfile(outfile)) @@ -94,6 +99,7 @@ class TestFilePdf(PillowTestCase): def imGenerator(ims): for im in ims: yield im + im.save(outfile, save_all=True, append_images=imGenerator(ims)) self.assertTrue(os.path.isfile(outfile)) @@ -110,7 +116,7 @@ class TestFilePdf(PillowTestCase): # Test saving a multiframe image without save_all im = Image.open("Tests/images/dispose_bgnd.gif") - outfile = self.tempfile('temp.pdf') + outfile = self.tempfile("temp.pdf") im.save(outfile) self.assertTrue(os.path.isfile(outfile)) @@ -119,8 +125,8 @@ class TestFilePdf(PillowTestCase): def test_pdf_open(self): # fail on a buffer full of null bytes self.assertRaises( - PdfParser.PdfFormatError, - PdfParser.PdfParser, buf=bytearray(65536)) + PdfParser.PdfFormatError, PdfParser.PdfParser, buf=bytearray(65536) + ) # make an empty PDF object with PdfParser.PdfParser() as empty_pdf: @@ -157,10 +163,9 @@ class TestFilePdf(PillowTestCase): im = hopper("RGB") temp_dir = tempfile.mkdtemp() try: - self.assertRaises(IOError, - im.save, - os.path.join(temp_dir, "nonexistent.pdf"), - append=True) + self.assertRaises( + IOError, im.save, os.path.join(temp_dir, "nonexistent.pdf"), append=True + ) finally: os.rmdir(temp_dir) @@ -189,9 +194,9 @@ class TestFilePdf(PillowTestCase): with PdfParser.PdfParser(pdf_filename, mode="r+b") as pdf: self.assertEqual(len(pdf.pages), 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.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) @@ -218,8 +223,7 @@ class TestFilePdf(PillowTestCase): # append two images mode_CMYK = hopper("CMYK") mode_P = hopper("P") - mode_CMYK.save(pdf_filename, - append=True, save_all=True, append_images=[mode_P]) + mode_CMYK.save(pdf_filename, append=True, save_all=True, append_images=[mode_P]) # open the PDF again, check pages and info again with PdfParser.PdfParser(pdf_filename) as pdf: @@ -237,10 +241,16 @@ class TestFilePdf(PillowTestCase): 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", + "RGB", + title="title", + author="author", + subject="subject", + keywords="keywords", + creator="creator", + producer="producer", creationDate=time.strptime("2000", "%Y"), - modDate=time.strptime("2001", "%Y")) + modDate=time.strptime("2001", "%Y"), + ) # open it, check pages and info with PdfParser.PdfParser(pdf_filename) as pdf: @@ -251,8 +261,7 @@ class TestFilePdf(PillowTestCase): 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.CreationDate, time.strptime("2000", "%Y")) self.assertEqual(pdf.info.ModDate, time.strptime("2001", "%Y")) self.check_pdf_pages_consistency(pdf) diff --git a/Tests/test_file_pixar.py b/Tests/test_file_pixar.py index 3b998c0b5..df5b22d75 100644 --- a/Tests/test_file_pixar.py +++ b/Tests/test_file_pixar.py @@ -6,7 +6,6 @@ TEST_FILE = "Tests/images/hopper.pxr" class TestFilePixar(PillowTestCase): - def test_sanity(self): im = Image.open(TEST_FILE) im.load() @@ -21,6 +20,4 @@ class TestFilePixar(PillowTestCase): def test_invalid_file(self): invalid_file = "Tests/images/flower.jpg" - self.assertRaises( - SyntaxError, - PixarImagePlugin.PixarImageFile, invalid_file) + self.assertRaises(SyntaxError, PixarImagePlugin.PixarImageFile, invalid_file) diff --git a/Tests/test_file_png.py b/Tests/test_file_png.py index 6a061fe6a..4824f122b 100644 --- a/Tests/test_file_png.py +++ b/Tests/test_file_png.py @@ -8,6 +8,7 @@ import sys try: from PIL import _webp + HAVE_WEBP = True except ImportError: HAVE_WEBP = False @@ -32,7 +33,7 @@ def chunk(cid, *data): o32 = PngImagePlugin.o32 -IHDR = chunk(b"IHDR", o32(1), o32(1), b'\x08\x02', b'\0\0\0') +IHDR = chunk(b"IHDR", o32(1), o32(1), b"\x08\x02", b"\0\0\0") IDAT = chunk(b"IDAT") IEND = chunk(b"IEND") @@ -52,7 +53,6 @@ def roundtrip(im, **options): class TestFilePng(PillowTestCase): - def setUp(self): if "zip_encoder" not in codecs or "zip_decoder" not in codecs: self.skipTest("zip/deflate support not available") @@ -86,7 +86,7 @@ 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') + self.assertEqual(im.get_format_mimetype(), "image/png") for mode in ["1", "L", "P", "RGB", "I", "I;16"]: im = hopper(mode) @@ -99,8 +99,7 @@ class TestFilePng(PillowTestCase): def test_invalid_file(self): invalid_file = "Tests/images/flower.jpg" - self.assertRaises(SyntaxError, - PngImagePlugin.PngImageFile, invalid_file) + self.assertRaises(SyntaxError, PngImagePlugin.PngImageFile, invalid_file) def test_broken(self): # Check reading of totally broken files. In this case, the test @@ -112,76 +111,83 @@ class TestFilePng(PillowTestCase): def test_bad_text(self): # Make sure PIL can read malformed tEXt chunks (@PIL152) - im = load(HEAD + chunk(b'tEXt') + TAIL) + im = load(HEAD + chunk(b"tEXt") + TAIL) self.assertEqual(im.info, {}) - im = load(HEAD + chunk(b'tEXt', b'spam') + TAIL) - self.assertEqual(im.info, {'spam': ''}) + im = load(HEAD + chunk(b"tEXt", b"spam") + TAIL) + self.assertEqual(im.info, {"spam": ""}) - im = load(HEAD + chunk(b'tEXt', b'spam\0') + TAIL) - self.assertEqual(im.info, {'spam': ''}) + im = load(HEAD + chunk(b"tEXt", b"spam\0") + TAIL) + self.assertEqual(im.info, {"spam": ""}) - im = load(HEAD + chunk(b'tEXt', b'spam\0egg') + TAIL) - self.assertEqual(im.info, {'spam': 'egg'}) + im = load(HEAD + chunk(b"tEXt", b"spam\0egg") + TAIL) + self.assertEqual(im.info, {"spam": "egg"}) - im = load(HEAD + chunk(b'tEXt', b'spam\0egg\0') + TAIL) - self.assertEqual(im.info, {'spam': 'egg\x00'}) + im = load(HEAD + chunk(b"tEXt", b"spam\0egg\0") + TAIL) + self.assertEqual(im.info, {"spam": "egg\x00"}) def test_bad_ztxt(self): # Test reading malformed zTXt chunks (python-pillow/Pillow#318) - im = load(HEAD + chunk(b'zTXt') + TAIL) + im = load(HEAD + chunk(b"zTXt") + TAIL) self.assertEqual(im.info, {}) - im = load(HEAD + chunk(b'zTXt', b'spam') + TAIL) - self.assertEqual(im.info, {'spam': ''}) + im = load(HEAD + chunk(b"zTXt", b"spam") + TAIL) + self.assertEqual(im.info, {"spam": ""}) - im = load(HEAD + chunk(b'zTXt', b'spam\0') + TAIL) - self.assertEqual(im.info, {'spam': ''}) + im = load(HEAD + chunk(b"zTXt", b"spam\0") + TAIL) + self.assertEqual(im.info, {"spam": ""}) - im = load(HEAD + chunk(b'zTXt', b'spam\0\0') + TAIL) - self.assertEqual(im.info, {'spam': ''}) + im = load(HEAD + chunk(b"zTXt", b"spam\0\0") + TAIL) + self.assertEqual(im.info, {"spam": ""}) - im = load(HEAD + chunk( - b'zTXt', b'spam\0\0' + zlib.compress(b'egg')[:1]) + TAIL) - self.assertEqual(im.info, {'spam': ''}) + im = load(HEAD + chunk(b"zTXt", b"spam\0\0" + zlib.compress(b"egg")[:1]) + TAIL) + self.assertEqual(im.info, {"spam": ""}) - im = load( - HEAD + chunk(b'zTXt', b'spam\0\0' + zlib.compress(b'egg')) + TAIL) - self.assertEqual(im.info, {'spam': 'egg'}) + im = load(HEAD + chunk(b"zTXt", b"spam\0\0" + zlib.compress(b"egg")) + TAIL) + self.assertEqual(im.info, {"spam": "egg"}) def test_bad_itxt(self): - im = load(HEAD + chunk(b'iTXt') + TAIL) + im = load(HEAD + chunk(b"iTXt") + TAIL) self.assertEqual(im.info, {}) - im = load(HEAD + chunk(b'iTXt', b'spam') + TAIL) + im = load(HEAD + chunk(b"iTXt", b"spam") + TAIL) self.assertEqual(im.info, {}) - im = load(HEAD + chunk(b'iTXt', b'spam\0') + TAIL) + im = load(HEAD + chunk(b"iTXt", b"spam\0") + TAIL) self.assertEqual(im.info, {}) - im = load(HEAD + chunk(b'iTXt', b'spam\0\x02') + TAIL) + im = load(HEAD + chunk(b"iTXt", b"spam\0\x02") + TAIL) self.assertEqual(im.info, {}) - im = load(HEAD + chunk(b'iTXt', b'spam\0\0\0foo\0') + TAIL) + im = load(HEAD + chunk(b"iTXt", b"spam\0\0\0foo\0") + TAIL) self.assertEqual(im.info, {}) - im = load(HEAD + chunk(b'iTXt', b'spam\0\0\0en\0Spam\0egg') + TAIL) + im = load(HEAD + chunk(b"iTXt", b"spam\0\0\0en\0Spam\0egg") + TAIL) self.assertEqual(im.info, {"spam": "egg"}) self.assertEqual(im.info["spam"].lang, "en") self.assertEqual(im.info["spam"].tkey, "Spam") - im = load(HEAD + chunk(b'iTXt', b'spam\0\1\0en\0Spam\0' + - zlib.compress(b"egg")[:1]) + TAIL) - self.assertEqual(im.info, {'spam': ''}) + im = load( + HEAD + + chunk(b"iTXt", b"spam\0\1\0en\0Spam\0" + zlib.compress(b"egg")[:1]) + + TAIL + ) + self.assertEqual(im.info, {"spam": ""}) - im = load(HEAD + chunk(b'iTXt', b'spam\0\1\1en\0Spam\0' + - zlib.compress(b"egg")) + TAIL) + im = load( + HEAD + + chunk(b"iTXt", b"spam\0\1\1en\0Spam\0" + zlib.compress(b"egg")) + + TAIL + ) self.assertEqual(im.info, {}) - im = load(HEAD + chunk(b'iTXt', b'spam\0\1\0en\0Spam\0' + - zlib.compress(b"egg")) + TAIL) + im = load( + HEAD + + chunk(b"iTXt", b"spam\0\1\0en\0Spam\0" + zlib.compress(b"egg")) + + TAIL + ) self.assertEqual(im.info, {"spam": "egg"}) self.assertEqual(im.info["spam"].lang, "en") self.assertEqual(im.info["spam"].tkey, "Spam") @@ -213,7 +219,7 @@ class TestFilePng(PillowTestCase): self.assert_image(im, "RGBA", (162, 150)) # image has 124 unique alpha values - self.assertEqual(len(im.getchannel('A').getcolors()), 124) + self.assertEqual(len(im.getchannel("A").getcolors()), 124) def test_load_transparent_rgb(self): test_file = "Tests/images/rgb_trns.png" @@ -225,7 +231,7 @@ class TestFilePng(PillowTestCase): self.assert_image(im, "RGBA", (64, 64)) # image has 876 transparent pixels - self.assertEqual(im.getchannel('A').getcolors()[0][0], 876) + self.assertEqual(im.getchannel("A").getcolors()[0][0], 876) def test_save_p_transparent_palette(self): in_file = "Tests/images/pil123p.png" @@ -247,7 +253,7 @@ class TestFilePng(PillowTestCase): self.assert_image(im, "RGBA", (162, 150)) # image has 124 unique alpha values - self.assertEqual(len(im.getchannel('A').getcolors()), 124) + self.assertEqual(len(im.getchannel("A").getcolors()), 124) def test_save_p_single_transparency(self): in_file = "Tests/images/p_trns_single.png" @@ -271,7 +277,7 @@ class TestFilePng(PillowTestCase): self.assertEqual(im.getpixel((31, 31)), (0, 255, 52, 0)) # image has 876 transparent pixels - self.assertEqual(im.getchannel('A').getcolors()[0][0], 876) + self.assertEqual(im.getchannel("A").getcolors()[0][0], 876) def test_save_p_transparent_black(self): # check if solid black image with full transparency @@ -292,19 +298,14 @@ class TestFilePng(PillowTestCase): self.assertEqual(im.getcolors(), [(100, (0, 0, 0, 0))]) def test_save_greyscale_transparency(self): - for mode, num_transparent in { - "1": 1994, - "L": 559, - "I": 559, - }.items(): - in_file = "Tests/images/"+mode.lower()+"_trns.png" + for mode, num_transparent in {"1": 1994, "L": 559, "I": 559}.items(): + in_file = "Tests/images/" + mode.lower() + "_trns.png" im = Image.open(in_file) self.assertEqual(im.mode, mode) self.assertEqual(im.info["transparency"], 255) - im_rgba = im.convert('RGBA') - self.assertEqual( - im_rgba.getchannel("A").getcolors()[0][0], num_transparent) + im_rgba = im.convert("RGBA") + self.assertEqual(im_rgba.getchannel("A").getcolors()[0][0], num_transparent) test_file = self.tempfile("temp.png") im.save(test_file) @@ -314,9 +315,10 @@ class TestFilePng(PillowTestCase): self.assertEqual(test_im.info["transparency"], 255) self.assert_image_equal(im, test_im) - test_im_rgba = test_im.convert('RGBA') + test_im_rgba = test_im.convert("RGBA") self.assertEqual( - test_im_rgba.getchannel('A').getcolors()[0][0], num_transparent) + test_im_rgba.getchannel("A").getcolors()[0][0], num_transparent + ) def test_save_rgb_single_transparency(self): in_file = "Tests/images/caption_6_33_22.png" @@ -345,7 +347,7 @@ class TestFilePng(PillowTestCase): # -14: malformed chunk for offset in (-10, -13, -14): - with open(TEST_PNG_FILE, 'rb') as f: + with open(TEST_PNG_FILE, "rb") as f: test_file = f.read()[:offset] im = Image.open(BytesIO(test_file)) @@ -355,12 +357,11 @@ class TestFilePng(PillowTestCase): def test_verify_ignores_crc_error(self): # check ignores crc errors in ancillary chunks - chunk_data = chunk(b'tEXt', b'spam') - broken_crc_chunk_data = chunk_data[:-1] + b'q' # break CRC + chunk_data = chunk(b"tEXt", b"spam") + broken_crc_chunk_data = chunk_data[:-1] + b"q" # break CRC image_data = HEAD + broken_crc_chunk_data + TAIL - self.assertRaises(SyntaxError, PngImagePlugin.PngImageFile, - BytesIO(image_data)) + self.assertRaises(SyntaxError, PngImagePlugin.PngImageFile, BytesIO(image_data)) ImageFile.LOAD_TRUNCATED_IMAGES = True try: @@ -372,12 +373,13 @@ class TestFilePng(PillowTestCase): def test_verify_not_ignores_crc_error_in_required_chunk(self): # check does not ignore crc errors in required chunks - image_data = MAGIC + IHDR[:-1] + b'q' + TAIL + image_data = MAGIC + IHDR[:-1] + b"q" + TAIL ImageFile.LOAD_TRUNCATED_IMAGES = True try: - self.assertRaises(SyntaxError, PngImagePlugin.PngImageFile, - BytesIO(image_data)) + self.assertRaises( + SyntaxError, PngImagePlugin.PngImageFile, BytesIO(image_data) + ) finally: ImageFile.LOAD_TRUNCATED_IMAGES = False @@ -417,8 +419,8 @@ class TestFilePng(PillowTestCase): info.add_text("ZIP", "VALUE", zip=True) im = roundtrip(im, pnginfo=info) - self.assertEqual(im.info, {'TXT': 'VALUE', 'ZIP': 'VALUE'}) - self.assertEqual(im.text, {'TXT': 'VALUE', 'ZIP': 'VALUE'}) + self.assertEqual(im.info, {"TXT": "VALUE", "ZIP": "VALUE"}) + self.assertEqual(im.text, {"TXT": "VALUE", "ZIP": "VALUE"}) def test_roundtrip_itxt(self): # Check iTXt roundtripping @@ -426,8 +428,7 @@ class TestFilePng(PillowTestCase): im = Image.new("RGB", (32, 32)) info = PngImagePlugin.PngInfo() info.add_itxt("spam", "Eggs", "en", "Spam") - info.add_text("eggs", PngImagePlugin.iTXt("Spam", "en", "Eggs"), - zip=True) + info.add_text("eggs", PngImagePlugin.iTXt("Spam", "en", "Eggs"), zip=True) im = roundtrip(im, pnginfo=info) self.assertEqual(im.info, {"spam": "Eggs", "eggs": "Spam"}) @@ -459,11 +460,11 @@ class TestFilePng(PillowTestCase): self.assertEqual(im.info, {"Text": value}) if py3: - rt_text(" Aa" + chr(0xa0) + chr(0xc4) + chr(0xff)) # Latin1 - rt_text(chr(0x400) + chr(0x472) + chr(0x4ff)) # Cyrillic - rt_text(chr(0x4e00) + chr(0x66f0) + # CJK - chr(0x9fba) + chr(0x3042) + chr(0xac00)) - rt_text("A" + chr(0xc4) + chr(0x472) + chr(0x3042)) # Combined + rt_text(" Aa" + chr(0xA0) + chr(0xC4) + chr(0xFF)) # Latin1 + rt_text(chr(0x400) + chr(0x472) + chr(0x4FF)) # Cyrillic + # CJK: + rt_text(chr(0x4E00) + chr(0x66F0) + chr(0x9FBA) + chr(0x3042) + chr(0xAC00)) + rt_text("A" + chr(0xC4) + chr(0x472) + chr(0x3042)) # Combined def test_scary(self): # Check reading of evil PNG file. For information, see: @@ -471,8 +472,8 @@ class TestFilePng(PillowTestCase): # The first byte is removed from pngtest_bad.png # to avoid classification as malware. - with open("Tests/images/pngtest_bad.png.bin", 'rb') as fd: - data = b'\x89' + fd.read() + with open("Tests/images/pngtest_bad.png.bin", "rb") as fd: + data = b"\x89" + fd.read() pngfile = BytesIO(data) self.assertRaises(IOError, Image.open, pngfile) @@ -494,17 +495,16 @@ class TestFilePng(PillowTestCase): def test_trns_p(self): # Check writing a transparency of 0, issue #528 - im = hopper('P') - im.info['transparency'] = 0 + im = hopper("P") + im.info["transparency"] = 0 f = self.tempfile("temp.png") im.save(f) im2 = Image.open(f) - self.assertIn('transparency', im2.info) + self.assertIn("transparency", im2.info) - self.assert_image_equal(im2.convert('RGBA'), - im.convert('RGBA')) + self.assert_image_equal(im2.convert("RGBA"), im.convert("RGBA")) def test_trns_null(self): # Check reading images with null tRNS value, issue #1239 @@ -515,39 +515,39 @@ class TestFilePng(PillowTestCase): def test_save_icc_profile(self): im = Image.open("Tests/images/icc_profile_none.png") - self.assertIsNone(im.info['icc_profile']) + self.assertIsNone(im.info["icc_profile"]) with_icc = Image.open("Tests/images/icc_profile.png") - expected_icc = with_icc.info['icc_profile'] + expected_icc = with_icc.info["icc_profile"] im = roundtrip(im, icc_profile=expected_icc) - self.assertEqual(im.info['icc_profile'], expected_icc) + self.assertEqual(im.info["icc_profile"], expected_icc) def test_discard_icc_profile(self): - im = Image.open('Tests/images/icc_profile.png') + im = Image.open("Tests/images/icc_profile.png") im = roundtrip(im, icc_profile=None) - self.assertNotIn('icc_profile', im.info) + self.assertNotIn("icc_profile", im.info) def test_roundtrip_icc_profile(self): - im = Image.open('Tests/images/icc_profile.png') - expected_icc = im.info['icc_profile'] + im = Image.open("Tests/images/icc_profile.png") + expected_icc = im.info["icc_profile"] im = roundtrip(im) - self.assertEqual(im.info['icc_profile'], expected_icc) + self.assertEqual(im.info["icc_profile"], expected_icc) def test_roundtrip_no_icc_profile(self): im = Image.open("Tests/images/icc_profile_none.png") - self.assertIsNone(im.info['icc_profile']) + self.assertIsNone(im.info["icc_profile"]) im = roundtrip(im) - self.assertNotIn('icc_profile', im.info) + self.assertNotIn("icc_profile", im.info) def test_repr_png(self): im = hopper() repr_png = Image.open(BytesIO(im._repr_png_())) - self.assertEqual(repr_png.format, 'PNG') + self.assertEqual(repr_png.format, "PNG") self.assert_image_equal(im, repr_png) def test_chunk_order(self): @@ -579,10 +579,10 @@ class TestFilePng(PillowTestCase): def test_textual_chunks_after_idat(self): im = Image.open("Tests/images/hopper.png") - self.assertIn('comment', im.text.keys()) + 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', + "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) @@ -601,7 +601,7 @@ class TestFilePng(PillowTestCase): # 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'}) + self.assertEqual(im.text, {"TXT": "VALUE", "ZIP": "VALUE"}) def test_exif(self): im = Image.open("Tests/images/exif.png") @@ -637,20 +637,21 @@ class TestFilePng(PillowTestCase): reloaded = Image.open(test_file) self.assertEqual(reloaded.info["exif"], b"Exif\x00\x00exifstring") - @unittest.skipUnless(HAVE_WEBP and _webp.HAVE_WEBPANIM, - "WebP support not installed with animation") + @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') + 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") +@unittest.skipIf(sys.platform.startswith("win32"), "requires Unix or macOS") class TestTruncatedPngPLeaks(PillowLeakTestCase): - mem_limit = 2*1024 # max increase in K + mem_limit = 2 * 1024 # max increase in K iterations = 100 # Leak is 56k/iteration, this will leak 5.6megs def setUp(self): @@ -658,7 +659,7 @@ class TestTruncatedPngPLeaks(PillowLeakTestCase): self.skipTest("zip/deflate support not available") def test_leak_load(self): - with open('Tests/images/hopper.png', 'rb') as f: + with open("Tests/images/hopper.png", "rb") as f: DATA = BytesIO(f.read(16 * 1024)) ImageFile.LOAD_TRUNCATED_IMAGES = True diff --git a/Tests/test_file_ppm.py b/Tests/test_file_ppm.py index fa024f2cd..74406612f 100644 --- a/Tests/test_file_ppm.py +++ b/Tests/test_file_ppm.py @@ -7,7 +7,6 @@ test_file = "Tests/images/hopper.ppm" class TestFilePpm(PillowTestCase): - def test_sanity(self): im = Image.open(test_file) im.load() @@ -17,39 +16,39 @@ class TestFilePpm(PillowTestCase): self.assertEqual(im.get_format_mimetype(), "image/x-portable-pixmap") def test_16bit_pgm(self): - im = Image.open('Tests/images/16_bit_binary.pgm') + im = Image.open("Tests/images/16_bit_binary.pgm") im.load() - self.assertEqual(im.mode, 'I') + self.assertEqual(im.mode, "I") self.assertEqual(im.size, (20, 100)) self.assertEqual(im.get_format_mimetype(), "image/x-portable-graymap") - tgt = Image.open('Tests/images/16_bit_binary_pgm.png') + tgt = Image.open("Tests/images/16_bit_binary_pgm.png") self.assert_image_equal(im, tgt) def test_16bit_pgm_write(self): - im = Image.open('Tests/images/16_bit_binary.pgm') + im = Image.open("Tests/images/16_bit_binary.pgm") im.load() - f = self.tempfile('temp.pgm') - im.save(f, 'PPM') + f = self.tempfile("temp.pgm") + im.save(f, "PPM") reloaded = Image.open(f) self.assert_image_equal(im, reloaded) def test_pnm(self): - im = Image.open('Tests/images/hopper.pnm') + im = Image.open("Tests/images/hopper.pnm") self.assert_image_similar(im, hopper(), 0.0001) - f = self.tempfile('temp.pnm') + f = self.tempfile("temp.pnm") im.save(f) reloaded = Image.open(f) self.assert_image_equal(im, reloaded) def test_truncated_file(self): - path = self.tempfile('temp.pgm') - with open(path, 'w') as f: - f.write('P6') + path = self.tempfile("temp.pgm") + with open(path, "w") as f: + f.write("P6") self.assertRaises(ValueError, Image.open, path) @@ -60,17 +59,17 @@ class TestFilePpm(PillowTestCase): # sizes. with self.assertRaises(IOError): - Image.open('Tests/images/negative_size.ppm') + Image.open("Tests/images/negative_size.ppm") def test_mimetypes(self): - path = self.tempfile('temp.pgm') + path = self.tempfile("temp.pgm") - with open(path, 'w') as f: + with open(path, "w") as f: f.write("P4\n128 128\n255") im = Image.open(path) self.assertEqual(im.get_format_mimetype(), "image/x-portable-bitmap") - with open(path, 'w') as f: + with open(path, "w") as f: f.write("PyCMYK\n128 128\n255") im = Image.open(path) self.assertEqual(im.get_format_mimetype(), "image/x-portable-anymap") diff --git a/Tests/test_file_psd.py b/Tests/test_file_psd.py index 3b8a7add6..45fc0ef71 100644 --- a/Tests/test_file_psd.py +++ b/Tests/test_file_psd.py @@ -6,7 +6,6 @@ test_file = "Tests/images/hopper.psd" class TestImagePsd(PillowTestCase): - def test_sanity(self): im = Image.open(test_file) im.load() @@ -17,11 +16,17 @@ class TestImagePsd(PillowTestCase): im2 = hopper() self.assert_image_similar(im, im2, 4.8) + def test_unclosed_file(self): + def open(): + im = Image.open(test_file) + im.load() + + self.assert_warning(None, open) + def test_invalid_file(self): invalid_file = "Tests/images/flower.jpg" - self.assertRaises(SyntaxError, - PsdImagePlugin.PsdImageFile, invalid_file) + self.assertRaises(SyntaxError, PsdImagePlugin.PsdImageFile, invalid_file) def test_n_frames(self): im = Image.open("Tests/images/hopper_merged.psd") @@ -35,14 +40,14 @@ class TestImagePsd(PillowTestCase): def test_eoferror(self): im = Image.open(test_file) # PSD seek index starts at 1 rather than 0 - n_frames = im.n_frames+1 + n_frames = im.n_frames + 1 # Test seeking past the last frame self.assertRaises(EOFError, im.seek, n_frames) self.assertLess(im.tell(), n_frames) # Test that seeking to the last frame does not raise an error - im.seek(n_frames-1) + im.seek(n_frames - 1) def test_seek_tell(self): im = Image.open(test_file) @@ -65,6 +70,12 @@ class TestImagePsd(PillowTestCase): self.assertRaises(EOFError, im.seek, -1) + def test_open_after_exclusive_load(self): + im = Image.open(test_file) + im.load() + im.seek(im.tell() + 1) + im.load() + def test_icc_profile(self): im = Image.open(test_file) self.assertIn("icc_profile", im.info) diff --git a/Tests/test_file_sgi.py b/Tests/test_file_sgi.py index 1ad70e24f..bf0af5066 100644 --- a/Tests/test_file_sgi.py +++ b/Tests/test_file_sgi.py @@ -4,7 +4,6 @@ from PIL import Image, SgiImagePlugin class TestFileSgi(PillowTestCase): - def test_rgb(self): # Created with ImageMagick then renamed: # convert hopper.ppm -compress None sgi:hopper.rgb @@ -12,7 +11,7 @@ class TestFileSgi(PillowTestCase): im = Image.open(test_file) self.assert_image_equal(im, hopper()) - self.assertEqual(im.get_format_mimetype(), 'image/rgb') + self.assertEqual(im.get_format_mimetype(), "image/rgb") def test_rgb16(self): test_file = "Tests/images/hopper16.rgb" @@ -26,8 +25,8 @@ class TestFileSgi(PillowTestCase): test_file = "Tests/images/hopper.bw" im = Image.open(test_file) - self.assert_image_similar(im, hopper('L'), 2) - self.assertEqual(im.get_format_mimetype(), 'image/sgi') + self.assert_image_similar(im, hopper("L"), 2) + self.assertEqual(im.get_format_mimetype(), "image/sgi") def test_rgba(self): # Created with ImageMagick: @@ -35,9 +34,9 @@ class TestFileSgi(PillowTestCase): test_file = "Tests/images/transparent.sgi" im = Image.open(test_file) - target = Image.open('Tests/images/transparent.png') + target = Image.open("Tests/images/transparent.png") self.assert_image_equal(im, target) - self.assertEqual(im.get_format_mimetype(), 'image/sgi') + self.assertEqual(im.get_format_mimetype(), "image/sgi") def test_rle(self): # Created with ImageMagick: @@ -45,47 +44,46 @@ class TestFileSgi(PillowTestCase): test_file = "Tests/images/hopper.sgi" im = Image.open(test_file) - target = Image.open('Tests/images/hopper.rgb') + target = Image.open("Tests/images/hopper.rgb") self.assert_image_equal(im, target) def test_rle16(self): test_file = "Tests/images/tv16.sgi" im = Image.open(test_file) - target = Image.open('Tests/images/tv.rgb') + target = Image.open("Tests/images/tv.rgb") self.assert_image_equal(im, target) def test_invalid_file(self): invalid_file = "Tests/images/flower.jpg" - self.assertRaises(ValueError, - SgiImagePlugin.SgiImageFile, invalid_file) + self.assertRaises(ValueError, SgiImagePlugin.SgiImageFile, invalid_file) def test_write(self): def roundtrip(img): - out = self.tempfile('temp.sgi') - img.save(out, format='sgi') + out = self.tempfile("temp.sgi") + img.save(out, format="sgi") reloaded = Image.open(out) self.assert_image_equal(img, reloaded) - for mode in ('L', 'RGB', 'RGBA'): + for mode in ("L", "RGB", "RGBA"): roundtrip(hopper(mode)) # Test 1 dimension for an L mode image - roundtrip(Image.new('L', (10, 1))) + roundtrip(Image.new("L", (10, 1))) def test_write16(self): test_file = "Tests/images/hopper16.rgb" im = Image.open(test_file) - out = self.tempfile('temp.sgi') - im.save(out, format='sgi', bpc=2) + out = self.tempfile("temp.sgi") + im.save(out, format="sgi", bpc=2) reloaded = Image.open(out) self.assert_image_equal(im, reloaded) def test_unsupported_mode(self): - im = hopper('LA') - out = self.tempfile('temp.sgi') + im = hopper("LA") + out = self.tempfile("temp.sgi") - self.assertRaises(ValueError, im.save, out, format='sgi') + self.assertRaises(ValueError, im.save, out, format="sgi") diff --git a/Tests/test_file_spider.py b/Tests/test_file_spider.py index f160272fd..7dec15058 100644 --- a/Tests/test_file_spider.py +++ b/Tests/test_file_spider.py @@ -10,7 +10,6 @@ TEST_FILE = "Tests/images/hopper.spider" class TestImageSpider(PillowTestCase): - def test_sanity(self): im = Image.open(TEST_FILE) im.load() @@ -22,11 +21,12 @@ class TestImageSpider(PillowTestCase): def open(): im = Image.open(TEST_FILE) im.load() + self.assert_warning(None, open) def test_save(self): # Arrange - temp = self.tempfile('temp.spider') + temp = self.tempfile("temp.spider") im = hopper() # Act diff --git a/Tests/test_file_sun.py b/Tests/test_file_sun.py index 65c00eea2..7270665c3 100644 --- a/Tests/test_file_sun.py +++ b/Tests/test_file_sun.py @@ -4,11 +4,10 @@ from PIL import Image, SunImagePlugin import os -EXTRA_DIR = 'Tests/images/sunraster' +EXTRA_DIR = "Tests/images/sunraster" class TestFileSun(PillowTestCase): - def test_sanity(self): # Arrange # Created with ImageMagick: convert hopper.jpg hopper.ras @@ -23,20 +22,20 @@ class TestFileSun(PillowTestCase): self.assert_image_similar(im, hopper(), 5) # visually verified invalid_file = "Tests/images/flower.jpg" - self.assertRaises(SyntaxError, - SunImagePlugin.SunImageFile, invalid_file) + self.assertRaises(SyntaxError, SunImagePlugin.SunImageFile, invalid_file) def test_im1(self): - im = Image.open('Tests/images/sunraster.im1') - target = Image.open('Tests/images/sunraster.im1.png') + im = Image.open("Tests/images/sunraster.im1") + target = Image.open("Tests/images/sunraster.im1.png") self.assert_image_equal(im, target) - @unittest.skipIf(not os.path.exists(EXTRA_DIR), - "Extra image files not installed") + @unittest.skipIf(not os.path.exists(EXTRA_DIR), "Extra image files not installed") def test_others(self): - files = (os.path.join(EXTRA_DIR, f) for f in - os.listdir(EXTRA_DIR) if os.path.splitext(f)[1] - in ('.sun', '.SUN', '.ras')) + files = ( + os.path.join(EXTRA_DIR, f) + for f in os.listdir(EXTRA_DIR) + if os.path.splitext(f)[1] in (".sun", ".SUN", ".ras") + ) for path in files: with Image.open(path) as im: im.load() diff --git a/Tests/test_file_tar.py b/Tests/test_file_tar.py index cd2b95778..cd123e112 100644 --- a/Tests/test_file_tar.py +++ b/Tests/test_file_tar.py @@ -9,15 +9,14 @@ TEST_TAR_FILE = "Tests/images/hopper.tar" class TestFileTar(PillowTestCase): - def setUp(self): if "zip_decoder" not in codecs and "jpeg_decoder" not in codecs: self.skipTest("neither jpeg nor zip support available") def test_sanity(self): for codec, test_path, format in [ - ['zip_decoder', 'hopper.png', 'PNG'], - ['jpeg_decoder', 'hopper.jpg', 'JPEG'] + ["zip_decoder", "hopper.png", "PNG"], + ["jpeg_decoder", "hopper.jpg", "JPEG"], ]: if codec in codecs: tar = TarIO.TarIO(TEST_TAR_FILE, test_path) @@ -28,9 +27,9 @@ class TestFileTar(PillowTestCase): self.assertEqual(im.format, format) def test_close(self): - tar = TarIO.TarIO(TEST_TAR_FILE, 'hopper.jpg') + tar = TarIO.TarIO(TEST_TAR_FILE, "hopper.jpg") tar.close() def test_contextmanager(self): - with TarIO.TarIO(TEST_TAR_FILE, 'hopper.jpg'): + with TarIO.TarIO(TEST_TAR_FILE, "hopper.jpg"): pass diff --git a/Tests/test_file_tga.py b/Tests/test_file_tga.py index a8ab00d7f..6bc37f258 100644 --- a/Tests/test_file_tga.py +++ b/Tests/test_file_tga.py @@ -16,16 +16,13 @@ class TestFileTga(PillowTestCase): _MODES = ("L", "LA", "P", "RGB", "RGBA") _ORIGINS = ("tl", "bl") - _ORIGIN_TO_ORIENTATION = { - "tl": 1, - "bl": -1 - } + _ORIGIN_TO_ORIENTATION = {"tl": 1, "bl": -1} def test_sanity(self): for mode in self._MODES: png_paths = glob( - os.path.join( - _TGA_DIR_COMMON, "*x*_{}.png".format(mode.lower()))) + os.path.join(_TGA_DIR_COMMON, "*x*_{}.png".format(mode.lower())) + ) for png_path in png_paths: reference_im = Image.open(png_path) @@ -34,21 +31,22 @@ class TestFileTga(PillowTestCase): path_no_ext = os.path.splitext(png_path)[0] for origin, rle in product(self._ORIGINS, (True, False)): tga_path = "{}_{}_{}.tga".format( - path_no_ext, origin, "rle" if rle else "raw") + path_no_ext, origin, "rle" if rle else "raw" + ) original_im = Image.open(tga_path) self.assertEqual(original_im.format, "TGA") self.assertEqual(original_im.get_format_mimetype(), "image/x-tga") if rle: - self.assertEqual( - original_im.info["compression"], "tga_rle") + self.assertEqual(original_im.info["compression"], "tga_rle") self.assertEqual( original_im.info["orientation"], - self._ORIGIN_TO_ORIENTATION[origin]) + self._ORIGIN_TO_ORIENTATION[origin], + ) if mode == "P": self.assertEqual( - original_im.getpalette(), - reference_im.getpalette()) + original_im.getpalette(), reference_im.getpalette() + ) self.assert_image_equal(original_im, reference_im) @@ -62,14 +60,15 @@ class TestFileTga(PillowTestCase): if rle: self.assertEqual( saved_im.info["compression"], - original_im.info["compression"]) + original_im.info["compression"], + ) self.assertEqual( - saved_im.info["orientation"], - original_im.info["orientation"]) + saved_im.info["orientation"], original_im.info["orientation"] + ) if mode == "P": self.assertEqual( - saved_im.getpalette(), - original_im.getpalette()) + saved_im.getpalette(), original_im.getpalette() + ) self.assert_image_equal(saved_im, original_im) @@ -128,8 +127,7 @@ class TestFileTga(PillowTestCase): # Save with custom id section greater than 255 characters id_section = b"Test content" * 25 - self.assert_warning(UserWarning, - lambda: im.save(out, id_section=id_section)) + self.assert_warning(UserWarning, lambda: im.save(out, id_section=id_section)) test_im = Image.open(out) self.assertEqual(test_im.info["id_section"], id_section[:255]) @@ -191,15 +189,13 @@ class TestFileTga(PillowTestCase): in_file = "Tests/images/la.tga" im = Image.open(in_file) self.assertEqual(im.mode, "LA") - self.assertEqual( - im.getchannel("A").getcolors()[0][0], num_transparent) + self.assertEqual(im.getchannel("A").getcolors()[0][0], num_transparent) out = self.tempfile("temp.tga") im.save(out) test_im = Image.open(out) self.assertEqual(test_im.mode, "LA") - self.assertEqual( - test_im.getchannel("A").getcolors()[0][0], num_transparent) + self.assertEqual(test_im.getchannel("A").getcolors()[0][0], num_transparent) self.assert_image_equal(im, test_im) diff --git a/Tests/test_file_tiff.py b/Tests/test_file_tiff.py index 9f2a04bb9..945372f86 100644 --- a/Tests/test_file_tiff.py +++ b/Tests/test_file_tiff.py @@ -12,7 +12,6 @@ logger = logging.getLogger(__name__) class TestFileTiff(PillowTestCase): - def test_sanity(self): filename = self.tempfile("temp.tif") @@ -44,6 +43,7 @@ class TestFileTiff(PillowTestCase): def open(): im = Image.open("Tests/images/multipage.tiff") im.load() + self.assert_warning(None, open) def test_mac_tiff(self): @@ -54,7 +54,7 @@ class TestFileTiff(PillowTestCase): self.assertEqual(im.mode, "RGBA") self.assertEqual(im.size, (55, 43)) - self.assertEqual(im.tile, [('raw', (0, 0, 55, 43), 8, ('RGBa', 0, 1))]) + self.assertEqual(im.tile, [("raw", (0, 0, 55, 43), 8, ("RGBa", 0, 1))]) im.load() self.assert_image_similar_tofile(im, "Tests/images/pil136.png", 1) @@ -64,16 +64,14 @@ class TestFileTiff(PillowTestCase): self.assertEqual(im.mode, "RGBA") self.assertEqual(im.size, (52, 53)) - self.assertEqual(im.tile, - [('raw', (0, 0, 52, 53), 160, ('RGBA', 0, 1))]) + self.assertEqual(im.tile, [("raw", (0, 0, 52, 53), 160, ("RGBA", 0, 1))]) im.load() def test_set_legacy_api(self): ifd = TiffImagePlugin.ImageFileDirectory_v2() with self.assertRaises(Exception) as e: ifd.legacy_api = None - self.assertEqual(str(e.exception), - "Not allowing setting of legacy api") + self.assertEqual(str(e.exception), "Not allowing setting of legacy api") def test_size(self): filename = "Tests/images/pil168.tif" @@ -81,6 +79,7 @@ class TestFileTiff(PillowTestCase): def set_size(): im.size = (256, 256) + self.assert_warning(DeprecationWarning, set_size) def test_xyres_tiff(self): @@ -92,29 +91,24 @@ class TestFileTiff(PillowTestCase): self.assertIsInstance(im.tag[Y_RESOLUTION][0], tuple) # v2 api - self.assertIsInstance(im.tag_v2[X_RESOLUTION], - TiffImagePlugin.IFDRational) - self.assertIsInstance(im.tag_v2[Y_RESOLUTION], - TiffImagePlugin.IFDRational) + self.assertIsInstance(im.tag_v2[X_RESOLUTION], TiffImagePlugin.IFDRational) + self.assertIsInstance(im.tag_v2[Y_RESOLUTION], TiffImagePlugin.IFDRational) - self.assertEqual(im.info['dpi'], (72., 72.)) + self.assertEqual(im.info["dpi"], (72.0, 72.0)) def test_xyres_fallback_tiff(self): filename = "Tests/images/compression.tif" im = Image.open(filename) # v2 api - self.assertIsInstance(im.tag_v2[X_RESOLUTION], - TiffImagePlugin.IFDRational) - self.assertIsInstance(im.tag_v2[Y_RESOLUTION], - TiffImagePlugin.IFDRational) - self.assertRaises(KeyError, - lambda: im.tag_v2[RESOLUTION_UNIT]) + self.assertIsInstance(im.tag_v2[X_RESOLUTION], TiffImagePlugin.IFDRational) + self.assertIsInstance(im.tag_v2[Y_RESOLUTION], TiffImagePlugin.IFDRational) + self.assertRaises(KeyError, lambda: im.tag_v2[RESOLUTION_UNIT]) # Legacy. - self.assertEqual(im.info['resolution'], (100., 100.)) + self.assertEqual(im.info["resolution"], (100.0, 100.0)) # Fallback "inch". - self.assertEqual(im.info['dpi'], (100., 100.)) + self.assertEqual(im.info["dpi"], (100.0, 100.0)) def test_int_resolution(self): filename = "Tests/images/pil168.tif" @@ -124,20 +118,21 @@ class TestFileTiff(PillowTestCase): im.tag_v2[X_RESOLUTION] = 71 im.tag_v2[Y_RESOLUTION] = 71 im._setup() - self.assertEqual(im.info['dpi'], (71., 71.)) + self.assertEqual(im.info["dpi"], (71.0, 71.0)) def test_load_dpi_rounding(self): - for resolutionUnit, dpi in ((None, (72, 73)), - (2, (72, 73)), - (3, (183, 185))): + for resolutionUnit, dpi in ((None, (72, 73)), (2, (72, 73)), (3, (183, 185))): im = Image.open( - "Tests/images/hopper_roundDown_"+str(resolutionUnit)+".tif") + "Tests/images/hopper_roundDown_" + str(resolutionUnit) + ".tif" + ) self.assertEqual(im.tag_v2.get(RESOLUTION_UNIT), resolutionUnit) - self.assertEqual(im.info['dpi'], (dpi[0], dpi[0])) + self.assertEqual(im.info["dpi"], (dpi[0], dpi[0])) - im = Image.open("Tests/images/hopper_roundUp_"+str(resolutionUnit)+".tif") + im = Image.open( + "Tests/images/hopper_roundUp_" + str(resolutionUnit) + ".tif" + ) self.assertEqual(im.tag_v2.get(RESOLUTION_UNIT), resolutionUnit) - self.assertEqual(im.info['dpi'], (dpi[1], dpi[1])) + self.assertEqual(im.info["dpi"], (dpi[1], dpi[1])) def test_save_dpi_rounding(self): outfile = self.tempfile("temp.tif") @@ -148,12 +143,13 @@ class TestFileTiff(PillowTestCase): reloaded = Image.open(outfile) reloaded.load() - self.assertEqual((round(dpi), round(dpi)), reloaded.info['dpi']) + self.assertEqual((round(dpi), round(dpi)), reloaded.info["dpi"]) def test_save_setting_missing_resolution(self): b = BytesIO() Image.open("Tests/images/10ct_32bit_128.tiff").save( - b, format="tiff", resolution=123.45) + b, format="tiff", resolution=123.45 + ) im = Image.open(b) self.assertEqual(float(im.tag_v2[X_RESOLUTION]), 123.45) self.assertEqual(float(im.tag_v2[Y_RESOLUTION]), 123.45) @@ -161,16 +157,14 @@ class TestFileTiff(PillowTestCase): def test_invalid_file(self): invalid_file = "Tests/images/flower.jpg" - self.assertRaises(SyntaxError, - TiffImagePlugin.TiffImageFile, invalid_file) + self.assertRaises(SyntaxError, TiffImagePlugin.TiffImageFile, invalid_file) TiffImagePlugin.PREFIXES.append(b"\xff\xd8\xff\xe0") - self.assertRaises(SyntaxError, - TiffImagePlugin.TiffImageFile, invalid_file) + self.assertRaises(SyntaxError, TiffImagePlugin.TiffImageFile, invalid_file) TiffImagePlugin.PREFIXES.pop() def test_bad_exif(self): - i = Image.open('Tests/images/hopper_bad_exif.jpg') + i = Image.open("Tests/images/hopper_bad_exif.jpg") # Should not raise struct.error. self.assert_warning(UserWarning, i._getexif) @@ -185,38 +179,38 @@ class TestFileTiff(PillowTestCase): self.assertRaises(IOError, im.save, outfile) def test_little_endian(self): - im = Image.open('Tests/images/16bit.cropped.tif') + im = Image.open("Tests/images/16bit.cropped.tif") self.assertEqual(im.getpixel((0, 0)), 480) - self.assertEqual(im.mode, 'I;16') + self.assertEqual(im.mode, "I;16") b = im.tobytes() # Bytes are in image native order (little endian) if py3: - self.assertEqual(b[0], ord(b'\xe0')) - self.assertEqual(b[1], ord(b'\x01')) + self.assertEqual(b[0], ord(b"\xe0")) + self.assertEqual(b[1], ord(b"\x01")) else: - self.assertEqual(b[0], b'\xe0') - self.assertEqual(b[1], b'\x01') + self.assertEqual(b[0], b"\xe0") + self.assertEqual(b[1], b"\x01") def test_big_endian(self): - im = Image.open('Tests/images/16bit.MM.cropped.tif') + im = Image.open("Tests/images/16bit.MM.cropped.tif") self.assertEqual(im.getpixel((0, 0)), 480) - self.assertEqual(im.mode, 'I;16B') + self.assertEqual(im.mode, "I;16B") b = im.tobytes() # Bytes are in image native order (big endian) if py3: - self.assertEqual(b[0], ord(b'\x01')) - self.assertEqual(b[1], ord(b'\xe0')) + self.assertEqual(b[0], ord(b"\x01")) + self.assertEqual(b[1], ord(b"\xe0")) else: - self.assertEqual(b[0], b'\x01') - self.assertEqual(b[1], b'\xe0') + self.assertEqual(b[0], b"\x01") + self.assertEqual(b[1], b"\xe0") def test_16bit_s(self): - im = Image.open('Tests/images/16bit.s.tif') + im = Image.open("Tests/images/16bit.s.tif") im.load() - self.assertEqual(im.mode, 'I') + self.assertEqual(im.mode, "I") self.assertEqual(im.getpixel((0, 0)), 32767) self.assertEqual(im.getpixel((0, 1)), 0) @@ -224,7 +218,7 @@ class TestFileTiff(PillowTestCase): """ Are we generating the same interpretation of the image as Imagemagick is? """ - im = Image.open('Tests/images/12bit.cropped.tif') + im = Image.open("Tests/images/12bit.cropped.tif") # to make the target -- # convert 12bit.cropped.tif -depth 16 tmp.tif @@ -232,33 +226,33 @@ class TestFileTiff(PillowTestCase): # imagemagick will auto scale so that a 12bit FFF is 16bit FFF0, # so we need to unshift so that the integer values are the same. - self.assert_image_equal_tofile(im, 'Tests/images/12in16bit.tif') + self.assert_image_equal_tofile(im, "Tests/images/12in16bit.tif") def test_32bit_float(self): # Issue 614, specific 32-bit float format - path = 'Tests/images/10ct_32bit_128.tiff' + path = "Tests/images/10ct_32bit_128.tiff" im = Image.open(path) im.load() self.assertEqual(im.getpixel((0, 0)), -0.4526388943195343) - self.assertEqual( - im.getextrema(), (-3.140936851501465, 3.140684127807617)) + 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') + 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], - ['Tests/images/multipage.tiff', 3] + ["Tests/images/multipage-lastframe.tif", 1], + ["Tests/images/multipage.tiff", 3], ]: im = Image.open(path) self.assertEqual(im.n_frames, n_frames) self.assertEqual(im.is_animated, n_frames != 1) def test_eoferror(self): - im = Image.open('Tests/images/multipage-lastframe.tif') + im = Image.open("Tests/images/multipage-lastframe.tif") n_frames = im.n_frames # Test seeking past the last frame @@ -266,37 +260,37 @@ class TestFileTiff(PillowTestCase): self.assertLess(im.tell(), n_frames) # Test that seeking to the last frame does not raise an error - im.seek(n_frames-1) + im.seek(n_frames - 1) def test_multipage(self): # issue #862 - im = Image.open('Tests/images/multipage.tiff') + im = Image.open("Tests/images/multipage.tiff") # file is a multipage tiff: 10x10 green, 10x10 red, 20x20 blue im.seek(0) self.assertEqual(im.size, (10, 10)) - self.assertEqual(im.convert('RGB').getpixel((0, 0)), (0, 128, 0)) + self.assertEqual(im.convert("RGB").getpixel((0, 0)), (0, 128, 0)) im.seek(1) im.load() self.assertEqual(im.size, (10, 10)) - self.assertEqual(im.convert('RGB').getpixel((0, 0)), (255, 0, 0)) + self.assertEqual(im.convert("RGB").getpixel((0, 0)), (255, 0, 0)) im.seek(0) im.load() self.assertEqual(im.size, (10, 10)) - self.assertEqual(im.convert('RGB').getpixel((0, 0)), (0, 128, 0)) + self.assertEqual(im.convert("RGB").getpixel((0, 0)), (0, 128, 0)) im.seek(2) im.load() self.assertEqual(im.size, (20, 20)) - self.assertEqual(im.convert('RGB').getpixel((0, 0)), (0, 0, 255)) + self.assertEqual(im.convert("RGB").getpixel((0, 0)), (0, 0, 255)) def test_multipage_last_frame(self): - im = Image.open('Tests/images/multipage-lastframe.tif') + im = Image.open("Tests/images/multipage-lastframe.tif") im.load() self.assertEqual(im.size, (20, 20)) - self.assertEqual(im.convert('RGB').getpixel((0, 0)), (0, 0, 255)) + self.assertEqual(im.convert("RGB").getpixel((0, 0)), (0, 0, 255)) def test___str__(self): filename = "Tests/images/pil136.tiff" @@ -314,16 +308,39 @@ class TestFileTiff(PillowTestCase): im = Image.open(filename) # v2 interface - v2_tags = {256: 55, 257: 43, 258: (8, 8, 8, 8), 259: 1, - 262: 2, 296: 2, 273: (8,), 338: (1,), 277: 4, - 279: (9460,), 282: 72.0, 283: 72.0, 284: 1} + v2_tags = { + 256: 55, + 257: 43, + 258: (8, 8, 8, 8), + 259: 1, + 262: 2, + 296: 2, + 273: (8,), + 338: (1,), + 277: 4, + 279: (9460,), + 282: 72.0, + 283: 72.0, + 284: 1, + } self.assertEqual(dict(im.tag_v2), v2_tags) # legacy interface - legacy_tags = {256: (55,), 257: (43,), 258: (8, 8, 8, 8), 259: (1,), - 262: (2,), 296: (2,), 273: (8,), 338: (1,), 277: (4,), - 279: (9460,), 282: ((720000, 10000),), - 283: ((720000, 10000),), 284: (1,)} + legacy_tags = { + 256: (55,), + 257: (43,), + 258: (8, 8, 8, 8), + 259: (1,), + 262: (2,), + 296: (2,), + 273: (8,), + 338: (1,), + 277: (4,), + 279: (9460,), + 282: ((720000, 10000),), + 283: ((720000, 10000),), + 284: (1,), + } self.assertEqual(dict(im.tag), legacy_tags) def test__delitem__(self): @@ -351,13 +368,13 @@ class TestFileTiff(PillowTestCase): ifd = TiffImagePlugin.ImageFileDirectory_v2() data = b"abcdabcd" ret = ifd.load_float(data, False) - self.assertEqual(ret, (1.6777999408082104e+22, 1.6777999408082104e+22)) + self.assertEqual(ret, (1.6777999408082104e22, 1.6777999408082104e22)) def test_load_double(self): ifd = TiffImagePlugin.ImageFileDirectory_v2() data = b"abcdefghabcdefgh" ret = ifd.load_double(data, False) - self.assertEqual(ret, (8.540883223036124e+194, 8.540883223036124e+194)) + self.assertEqual(ret, (8.540883223036124e194, 8.540883223036124e194)) def test_seek(self): filename = "Tests/images/pil136.tiff" @@ -374,12 +391,14 @@ class TestFileTiff(PillowTestCase): def test__limit_rational_int(self): from PIL.TiffImagePlugin import _limit_rational + value = 34 ret = _limit_rational(value, 65536) self.assertEqual(ret, (34, 1)) def test__limit_rational_float(self): from PIL.TiffImagePlugin import _limit_rational + value = 22.3 ret = _limit_rational(value, 65536) self.assertEqual(ret, (223, 10)) @@ -401,7 +420,7 @@ class TestFileTiff(PillowTestCase): "Tests/images/tiff_gray_2_4_bpp/hopper2I.tif", "Tests/images/tiff_gray_2_4_bpp/hopper2R.tif", "Tests/images/tiff_gray_2_4_bpp/hopper2IR.tif", - ) + ), ), ( 7.3, # epsilon @@ -410,7 +429,7 @@ class TestFileTiff(PillowTestCase): "Tests/images/tiff_gray_2_4_bpp/hopper4I.tif", "Tests/images/tiff_gray_2_4_bpp/hopper4R.tif", "Tests/images/tiff_gray_2_4_bpp/hopper4IR.tif", - ) + ), ), ) original = hopper("L") @@ -426,9 +445,7 @@ class TestFileTiff(PillowTestCase): self.assert_image_equal(im, im2) def test_with_underscores(self): - kwargs = {'resolution_unit': 'inch', - 'x_resolution': 72, - 'y_resolution': 36} + kwargs = {"resolution_unit": "inch", "x_resolution": 72, "y_resolution": 36} filename = self.tempfile("temp.tif") hopper("RGB").save(filename, **kwargs) im = Image.open(filename) @@ -459,8 +476,7 @@ class TestFileTiff(PillowTestCase): infile = "Tests/images/tiff_strip_raw.tif" im = Image.open(infile) - self.assert_image_equal_tofile(im, - "Tests/images/tiff_adobe_deflate.png") + self.assert_image_equal_tofile(im, "Tests/images/tiff_adobe_deflate.png") def test_strip_planar_raw(self): # gdal_translate -of GTiff -co INTERLEAVE=BAND \ @@ -468,16 +484,14 @@ class TestFileTiff(PillowTestCase): infile = "Tests/images/tiff_strip_planar_raw.tif" im = Image.open(infile) - self.assert_image_equal_tofile(im, - "Tests/images/tiff_adobe_deflate.png") + self.assert_image_equal_tofile(im, "Tests/images/tiff_adobe_deflate.png") def test_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") + 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 \ @@ -486,8 +500,17 @@ class TestFileTiff(PillowTestCase): infile = "Tests/images/tiff_tiled_planar_raw.tif" im = Image.open(infile) - self.assert_image_equal_tofile(im, - "Tests/images/tiff_adobe_deflate.png") + self.assert_image_equal_tofile(im, "Tests/images/tiff_adobe_deflate.png") + + def test_palette(self): + for mode in ["P", "PA"]: + outfile = self.tempfile("temp.tif") + + im = hopper(mode) + im.save(outfile) + + reloaded = Image.open(outfile) + self.assert_image_equal(im.convert("RGB"), reloaded.convert("RGB")) def test_tiff_save_all(self): import io @@ -503,9 +526,8 @@ class TestFileTiff(PillowTestCase): # Test appending images mp = io.BytesIO() - im = Image.new('RGB', (100, 100), '#f00') - ims = [Image.new('RGB', (100, 100), color) for color - in ['#0f0', '#00f']] + im = Image.new("RGB", (100, 100), "#f00") + ims = [Image.new("RGB", (100, 100), color) for color in ["#0f0", "#00f"]] im.copy().save(mp, format="TIFF", save_all=True, append_images=ims) mp.seek(0, os.SEEK_SET) @@ -516,9 +538,9 @@ class TestFileTiff(PillowTestCase): def imGenerator(ims): for im in ims: yield im + mp = io.BytesIO() - im.save(mp, format="TIFF", save_all=True, - append_images=imGenerator(ims)) + im.save(mp, format="TIFF", save_all=True, append_images=imGenerator(ims)) mp.seek(0, os.SEEK_SET) reread = Image.open(mp) @@ -529,15 +551,15 @@ class TestFileTiff(PillowTestCase): # At the time of writing this will only work for non-compressed tiffs # as libtiff does not support embedded ICC profiles, # ImageFile._save(..) however does. - im = Image.new('RGB', (1, 1)) - im.info['icc_profile'] = 'Dummy value' + im = Image.new("RGB", (1, 1)) + im.info["icc_profile"] = "Dummy value" # Try save-load round trip to make sure both handle icc_profile. - tmpfile = self.tempfile('temp.tif') - im.save(tmpfile, 'TIFF', compression='raw') + tmpfile = self.tempfile("temp.tif") + im.save(tmpfile, "TIFF", compression="raw") reloaded = Image.open(tmpfile) - self.assertEqual(b'Dummy value', reloaded.info['icc_profile']) + self.assertEqual(b"Dummy value", reloaded.info["icc_profile"]) def test_close_on_load_exclusive(self): # similar to test_fd_leak, but runs on unixlike os @@ -558,7 +580,7 @@ class TestFileTiff(PillowTestCase): with Image.open("Tests/images/uint16_1_4660.tif") as im: im.save(tmpfile) - with open(tmpfile, 'rb') as f: + with open(tmpfile, "rb") as f: im = Image.open(f) fp = im.fp self.assertFalse(fp.closed) @@ -566,7 +588,7 @@ class TestFileTiff(PillowTestCase): self.assertFalse(fp.closed) -@unittest.skipUnless(sys.platform.startswith('win32'), "Windows only") +@unittest.skipUnless(sys.platform.startswith("win32"), "Windows only") class TestFileTiffW32(PillowTestCase): def test_fd_leak(self): tmpfile = self.tempfile("temp.tif") diff --git a/Tests/test_file_tiff_metadata.py b/Tests/test_file_tiff_metadata.py index 14ec3ab6c..f9b197a56 100644 --- a/Tests/test_file_tiff_metadata.py +++ b/Tests/test_file_tiff_metadata.py @@ -10,7 +10,6 @@ tag_ids = {info.name: info.value for info in TiffTags.TAGS_V2.values()} class TestFileTiffMetadata(PillowTestCase): - def test_rt_metadata(self): """ Test writing arbitrary metadata into the tiff image directory Use case is ImageJ private tags, one numeric, one arbitrary @@ -29,23 +28,23 @@ class TestFileTiffMetadata(PillowTestCase): # the tiff file format can't take 8 bit bytes in that field. basetextdata = "This is some arbitrary metadata for a text field" - bindata = basetextdata.encode('ascii') + b" \xff" + bindata = basetextdata.encode("ascii") + b" \xff" textdata = basetextdata + " " + chr(255) reloaded_textdata = basetextdata + " ?" floatdata = 12.345 doubledata = 67.89 info = TiffImagePlugin.ImageFileDirectory() - ImageJMetaData = tag_ids['ImageJMetaData'] - ImageJMetaDataByteCounts = tag_ids['ImageJMetaDataByteCounts'] - ImageDescription = tag_ids['ImageDescription'] + ImageJMetaData = tag_ids["ImageJMetaData"] + ImageJMetaDataByteCounts = tag_ids["ImageJMetaDataByteCounts"] + ImageDescription = tag_ids["ImageDescription"] info[ImageJMetaDataByteCounts] = len(bindata) info[ImageJMetaData] = bindata - info[tag_ids['RollAngle']] = floatdata - info.tagtype[tag_ids['RollAngle']] = 11 - info[tag_ids['YawAngle']] = doubledata - info.tagtype[tag_ids['YawAngle']] = 12 + info[tag_ids["RollAngle"]] = floatdata + info.tagtype[tag_ids["RollAngle"]] = 11 + info[tag_ids["YawAngle"]] = doubledata + info.tagtype[tag_ids["YawAngle"]] = 12 info[ImageDescription] = textdata @@ -56,8 +55,7 @@ class TestFileTiffMetadata(PillowTestCase): loaded = Image.open(f) self.assertEqual(loaded.tag[ImageJMetaDataByteCounts], (len(bindata),)) - self.assertEqual(loaded.tag_v2[ImageJMetaDataByteCounts], - (len(bindata),)) + self.assertEqual(loaded.tag_v2[ImageJMetaDataByteCounts], (len(bindata),)) self.assertEqual(loaded.tag[ImageJMetaData], bindata) self.assertEqual(loaded.tag_v2[ImageJMetaData], bindata) @@ -65,9 +63,9 @@ class TestFileTiffMetadata(PillowTestCase): self.assertEqual(loaded.tag[ImageDescription], (reloaded_textdata,)) self.assertEqual(loaded.tag_v2[ImageDescription], reloaded_textdata) - loaded_float = loaded.tag[tag_ids['RollAngle']][0] + loaded_float = loaded.tag[tag_ids["RollAngle"]][0] self.assertAlmostEqual(loaded_float, floatdata, places=5) - loaded_double = loaded.tag[tag_ids['YawAngle']][0] + loaded_double = loaded.tag[tag_ids["YawAngle"]][0] self.assertAlmostEqual(loaded_double, doubledata) # check with 2 element ImageJMetaDataByteCounts, issue #2006 @@ -76,55 +74,61 @@ class TestFileTiffMetadata(PillowTestCase): img.save(f, tiffinfo=info) loaded = Image.open(f) - self.assertEqual(loaded.tag[ImageJMetaDataByteCounts], - (8, len(bindata) - 8)) - self.assertEqual(loaded.tag_v2[ImageJMetaDataByteCounts], - (8, len(bindata) - 8)) + self.assertEqual(loaded.tag[ImageJMetaDataByteCounts], (8, len(bindata) - 8)) + self.assertEqual(loaded.tag_v2[ImageJMetaDataByteCounts], (8, len(bindata) - 8)) def test_read_metadata(self): - img = Image.open('Tests/images/hopper_g4.tif') + img = Image.open("Tests/images/hopper_g4.tif") - self.assertEqual({'YResolution': IFDRational(4294967295, 113653537), - 'PlanarConfiguration': 1, - 'BitsPerSample': (1,), - 'ImageLength': 128, - 'Compression': 4, - 'FillOrder': 1, - 'RowsPerStrip': 128, - 'ResolutionUnit': 3, - 'PhotometricInterpretation': 0, - 'PageNumber': (0, 1), - 'XResolution': IFDRational(4294967295, 113653537), - 'ImageWidth': 128, - 'Orientation': 1, - 'StripByteCounts': (1968,), - 'SamplesPerPixel': 1, - 'StripOffsets': (8,) - }, img.tag_v2.named()) + self.assertEqual( + { + "YResolution": IFDRational(4294967295, 113653537), + "PlanarConfiguration": 1, + "BitsPerSample": (1,), + "ImageLength": 128, + "Compression": 4, + "FillOrder": 1, + "RowsPerStrip": 128, + "ResolutionUnit": 3, + "PhotometricInterpretation": 0, + "PageNumber": (0, 1), + "XResolution": IFDRational(4294967295, 113653537), + "ImageWidth": 128, + "Orientation": 1, + "StripByteCounts": (1968,), + "SamplesPerPixel": 1, + "StripOffsets": (8,), + }, + img.tag_v2.named(), + ) - self.assertEqual({'YResolution': ((4294967295, 113653537),), - 'PlanarConfiguration': (1,), - 'BitsPerSample': (1,), - 'ImageLength': (128,), - 'Compression': (4,), - 'FillOrder': (1,), - 'RowsPerStrip': (128,), - 'ResolutionUnit': (3,), - 'PhotometricInterpretation': (0,), - 'PageNumber': (0, 1), - 'XResolution': ((4294967295, 113653537),), - 'ImageWidth': (128,), - 'Orientation': (1,), - 'StripByteCounts': (1968,), - 'SamplesPerPixel': (1,), - 'StripOffsets': (8,) - }, img.tag.named()) + self.assertEqual( + { + "YResolution": ((4294967295, 113653537),), + "PlanarConfiguration": (1,), + "BitsPerSample": (1,), + "ImageLength": (128,), + "Compression": (4,), + "FillOrder": (1,), + "RowsPerStrip": (128,), + "ResolutionUnit": (3,), + "PhotometricInterpretation": (0,), + "PageNumber": (0, 1), + "XResolution": ((4294967295, 113653537),), + "ImageWidth": (128,), + "Orientation": (1,), + "StripByteCounts": (1968,), + "SamplesPerPixel": (1,), + "StripOffsets": (8,), + }, + img.tag.named(), + ) def test_write_metadata(self): """ Test metadata writing through the python code """ - img = Image.open('Tests/images/hopper.tif') + img = Image.open("Tests/images/hopper.tif") - f = self.tempfile('temp.tiff') + f = self.tempfile("temp.tiff") img.save(f, tiffinfo=img.tag) loaded = Image.open(f) @@ -134,42 +138,44 @@ class TestFileTiffMetadata(PillowTestCase): for k, v in original.items(): if isinstance(v, IFDRational): - original[k] = IFDRational(*_limit_rational(v, 2**31)) + original[k] = IFDRational(*_limit_rational(v, 2 ** 31)) elif isinstance(v, tuple) and isinstance(v[0], IFDRational): - original[k] = tuple(IFDRational(*_limit_rational(elt, 2**31)) - for elt in v) + original[k] = tuple( + IFDRational(*_limit_rational(elt, 2 ** 31)) for elt in v + ) - ignored = ['StripByteCounts', 'RowsPerStrip', - 'PageNumber', 'StripOffsets'] + ignored = ["StripByteCounts", "RowsPerStrip", "PageNumber", "StripOffsets"] for tag, value in reloaded.items(): if tag in ignored: continue - if (isinstance(original[tag], tuple) - and isinstance(original[tag][0], IFDRational)): + if isinstance(original[tag], tuple) and isinstance( + original[tag][0], IFDRational + ): # Need to compare element by element in the tuple, # not comparing tuples of object references - self.assert_deep_equal(original[tag], - value, - "%s didn't roundtrip, %s, %s" % - (tag, original[tag], value)) + self.assert_deep_equal( + original[tag], + value, + "%s didn't roundtrip, %s, %s" % (tag, original[tag], value), + ) else: - self.assertEqual(original[tag], - value, - "%s didn't roundtrip, %s, %s" % - (tag, original[tag], value)) + self.assertEqual( + original[tag], + value, + "%s didn't roundtrip, %s, %s" % (tag, original[tag], value), + ) for tag, value in original.items(): if tag not in ignored: - self.assertEqual( - value, reloaded[tag], "%s didn't roundtrip" % tag) + self.assertEqual(value, reloaded[tag], "%s didn't roundtrip" % tag) def test_no_duplicate_50741_tag(self): - self.assertEqual(tag_ids['MakerNoteSafety'], 50741) - self.assertEqual(tag_ids['BestQualityScale'], 50780) + self.assertEqual(tag_ids["MakerNoteSafety"], 50741) + self.assertEqual(tag_ids["BestQualityScale"], 50780) def test_empty_metadata(self): - f = io.BytesIO(b'II*\x00\x08\x00\x00\x00') + f = io.BytesIO(b"II*\x00\x08\x00\x00\x00") head = f.read(8) info = TiffImagePlugin.ImageFileDirectory(head) # Should not raise struct.error. @@ -177,31 +183,31 @@ class TestFileTiffMetadata(PillowTestCase): def test_iccprofile(self): # https://github.com/python-pillow/Pillow/issues/1462 - im = Image.open('Tests/images/hopper.iccprofile.tif') - out = self.tempfile('temp.tiff') + im = Image.open("Tests/images/hopper.iccprofile.tif") + out = self.tempfile("temp.tiff") im.save(out) reloaded = Image.open(out) - self.assertNotIsInstance(im.info['icc_profile'], tuple) - self.assertEqual(im.info['icc_profile'], reloaded.info['icc_profile']) + self.assertNotIsInstance(im.info["icc_profile"], tuple) + self.assertEqual(im.info["icc_profile"], reloaded.info["icc_profile"]) def test_iccprofile_binary(self): # https://github.com/python-pillow/Pillow/issues/1526 # We should be able to load this, # but probably won't be able to save it. - im = Image.open('Tests/images/hopper.iccprofile_binary.tif') + im = Image.open("Tests/images/hopper.iccprofile_binary.tif") self.assertEqual(im.tag_v2.tagtype[34675], 1) - self.assertTrue(im.info['icc_profile']) + self.assertTrue(im.info["icc_profile"]) def test_iccprofile_save_png(self): - im = Image.open('Tests/images/hopper.iccprofile.tif') - outfile = self.tempfile('temp.png') + im = Image.open("Tests/images/hopper.iccprofile.tif") + outfile = self.tempfile("temp.png") im.save(outfile) def test_iccprofile_binary_save_png(self): - im = Image.open('Tests/images/hopper.iccprofile_binary.tif') - outfile = self.tempfile('temp.png') + im = Image.open("Tests/images/hopper.iccprofile_binary.tif") + outfile = self.tempfile("temp.png") im.save(outfile) def test_exif_div_zero(self): @@ -209,8 +215,8 @@ class TestFileTiffMetadata(PillowTestCase): info = TiffImagePlugin.ImageFileDirectory_v2() info[41988] = TiffImagePlugin.IFDRational(0, 0) - out = self.tempfile('temp.tiff') - im.save(out, tiffinfo=info, compression='raw') + out = self.tempfile("temp.tiff") + im.save(out, tiffinfo=info, compression="raw") reloaded = Image.open(out) self.assertEqual(0, reloaded.tag_v2[41988].numerator) @@ -218,10 +224,11 @@ class TestFileTiffMetadata(PillowTestCase): def test_expty_values(self): data = io.BytesIO( - b'II*\x00\x08\x00\x00\x00\x03\x00\x1a\x01\x05\x00\x00\x00\x00\x00' - b'\x00\x00\x00\x00\x1b\x01\x05\x00\x00\x00\x00\x00\x00\x00\x00\x00' - b'\x98\x82\x02\x00\x07\x00\x00\x002\x00\x00\x00\x00\x00\x00\x00a ' - b'text\x00\x00') + b"II*\x00\x08\x00\x00\x00\x03\x00\x1a\x01\x05\x00\x00\x00\x00\x00" + b"\x00\x00\x00\x00\x1b\x01\x05\x00\x00\x00\x00\x00\x00\x00\x00\x00" + b"\x98\x82\x02\x00\x07\x00\x00\x002\x00\x00\x00\x00\x00\x00\x00a " + b"text\x00\x00" + ) head = data.read(8) info = TiffImagePlugin.ImageFileDirectory_v2(head) info.load(data) @@ -230,10 +237,10 @@ class TestFileTiffMetadata(PillowTestCase): self.assertIn(33432, info) def test_PhotoshopInfo(self): - im = Image.open('Tests/images/issue_2278.tif') + im = Image.open("Tests/images/issue_2278.tif") self.assertIsInstance(im.tag_v2[34377], bytes) - out = self.tempfile('temp.tiff') + out = self.tempfile("temp.tiff") im.save(out) reloaded = Image.open(out) self.assertIsInstance(reloaded.tag_v2[34377], bytes) @@ -242,7 +249,7 @@ class TestFileTiffMetadata(PillowTestCase): ifd = TiffImagePlugin.ImageFileDirectory_v2() # 277: ("SamplesPerPixel", SHORT, 1), - ifd._tagdata[277] = struct.pack('hh', 4, 4) + ifd._tagdata[277] = struct.pack("hh", 4, 4) ifd.tagtype[277] = TiffTags.SHORT # Should not raise ValueError. diff --git a/Tests/test_file_wal.py b/Tests/test_file_wal.py index 1e0a835d2..8b85aaecd 100644 --- a/Tests/test_file_wal.py +++ b/Tests/test_file_wal.py @@ -4,7 +4,6 @@ from PIL import WalImageFile class TestFileWal(PillowTestCase): - def test_open(self): # Arrange TEST_FILE = "Tests/images/hopper.wal" diff --git a/Tests/test_file_webp.py b/Tests/test_file_webp.py index 7e6fad93c..659f2baf1 100644 --- a/Tests/test_file_webp.py +++ b/Tests/test_file_webp.py @@ -4,6 +4,7 @@ from PIL import Image, WebPImagePlugin try: from PIL import _webp + HAVE_WEBP = True except ImportError: HAVE_WEBP = False @@ -16,8 +17,7 @@ class TestUnsupportedWebp(PillowTestCase): file_path = "Tests/images/hopper.webp" self.assert_warning( - UserWarning, - lambda: self.assertRaises(IOError, Image.open, file_path) + UserWarning, lambda: self.assertRaises(IOError, Image.open, file_path) ) if HAVE_WEBP: @@ -26,7 +26,6 @@ class TestUnsupportedWebp(PillowTestCase): @unittest.skipIf(not HAVE_WEBP, "WebP support not installed") class TestFileWebp(PillowTestCase): - def setUp(self): self.rgb_mode = "RGB" @@ -51,7 +50,8 @@ class TestFileWebp(PillowTestCase): # generated with: # dwebp -ppm ../../Tests/images/hopper.webp -o hopper_webp_bits.ppm self.assert_image_similar_tofile( - image, 'Tests/images/hopper_webp_bits.ppm', 1.0) + image, "Tests/images/hopper_webp_bits.ppm", 1.0 + ) def test_write_rgb(self): """ @@ -72,7 +72,8 @@ class TestFileWebp(PillowTestCase): # generated with: dwebp -ppm temp.webp -o hopper_webp_write.ppm self.assert_image_similar_tofile( - image, 'Tests/images/hopper_webp_write.ppm', 12.0) + image, "Tests/images/hopper_webp_write.ppm", 12.0 + ) # This test asserts that the images are similar. If the average pixel # difference between the two images is less than the epsilon value, @@ -149,12 +150,13 @@ class TestFileWebp(PillowTestCase): def test_file_pointer_could_be_reused(self): file_path = "Tests/images/hopper.webp" - with open(file_path, 'rb') as blob: + with open(file_path, "rb") as blob: Image.open(blob).load() Image.open(blob).load() - @unittest.skipUnless(HAVE_WEBP and _webp.HAVE_WEBPANIM, - "WebP save all not available") + @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)) @@ -169,6 +171,7 @@ class TestFileWebp(PillowTestCase): 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)]) + 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 c868fa1df..255fa6e22 100644 --- a/Tests/test_file_webp_alpha.py +++ b/Tests/test_file_webp_alpha.py @@ -10,11 +10,11 @@ except ImportError: @unittest.skipIf(_webp is None, "WebP support not installed") class TestFileWebpAlpha(PillowTestCase): - def setUp(self): if _webp.WebPDecoderBuggyAlpha(self): - self.skipTest("Buggy early version of WebP installed, " - "not testing transparency") + self.skipTest( + "Buggy early version of WebP installed, not testing transparency" + ) def test_read_rgba(self): """ @@ -34,7 +34,7 @@ class TestFileWebpAlpha(PillowTestCase): image.tobytes() - target = Image.open('Tests/images/transparent.png') + target = Image.open("Tests/images/transparent.png") self.assert_image_similar(image, target, 20.0) def test_write_lossless_rgb(self): @@ -46,7 +46,7 @@ class TestFileWebpAlpha(PillowTestCase): temp_file = self.tempfile("temp.webp") # temp_file = "temp.webp" - pil_image = hopper('RGBA') + pil_image = hopper("RGBA") mask = Image.new("RGBA", (64, 64), (128, 128, 128, 128)) # Add some partially transparent bits: diff --git a/Tests/test_file_webp_animated.py b/Tests/test_file_webp_animated.py index c751545c7..6a73fccdf 100644 --- a/Tests/test_file_webp_animated.py +++ b/Tests/test_file_webp_animated.py @@ -4,21 +4,23 @@ from PIL import Image try: from PIL import _webp + HAVE_WEBP = True except ImportError: HAVE_WEBP = False class TestFileWebpAnimation(PillowTestCase): - def setUp(self): if not HAVE_WEBP: - self.skipTest('WebP support not installed') + self.skipTest("WebP support not installed") return if not _webp.HAVE_WEBPANIM: - self.skipTest("WebP library does not contain animation support, " - "not testing animation") + self.skipTest( + "WebP library does not contain animation support, " + "not testing animation" + ) def test_n_frames(self): """ @@ -53,8 +55,8 @@ class TestFileWebpAnimation(PillowTestCase): orig.load() im.load() self.assert_image_similar(im, orig.convert("RGBA"), 25.0) - orig.seek(orig.n_frames-1) - im.seek(im.n_frames-1) + orig.seek(orig.n_frames - 1) + im.seek(im.n_frames - 1) orig.load() im.load() self.assert_image_similar(im, orig.convert("RGBA"), 25.0) @@ -78,23 +80,27 @@ class TestFileWebpAnimation(PillowTestCase): im.load() self.assert_image_equal(im, frame2.convert("RGBA")) - frame1 = Image.open('Tests/images/anim_frame1.webp') - frame2 = Image.open('Tests/images/anim_frame2.webp') + frame1 = Image.open("Tests/images/anim_frame1.webp") + frame2 = Image.open("Tests/images/anim_frame2.webp") temp_file1 = self.tempfile("temp.webp") - frame1.copy().save(temp_file1, - save_all=True, append_images=[frame2], - lossless=True) + frame1.copy().save( + temp_file1, save_all=True, append_images=[frame2], lossless=True + ) check(temp_file1) # Tests appending using a generator def imGenerator(ims): for im in ims: yield im + temp_file2 = self.tempfile("temp_generator.webp") - frame1.copy().save(temp_file2, - save_all=True, append_images=imGenerator([frame2]), - lossless=True) + frame1.copy().save( + temp_file2, + save_all=True, + append_images=imGenerator([frame2]), + lossless=True, + ) check(temp_file2) def test_timestamp_and_duration(self): @@ -105,11 +111,14 @@ class TestFileWebpAnimation(PillowTestCase): durations = [0, 10, 20, 30, 40] temp_file = self.tempfile("temp.webp") - frame1 = Image.open('Tests/images/anim_frame1.webp') - frame2 = Image.open('Tests/images/anim_frame2.webp') - frame1.save(temp_file, save_all=True, - append_images=[frame2, frame1, frame2, frame1], - duration=durations) + frame1 = Image.open("Tests/images/anim_frame1.webp") + frame2 = Image.open("Tests/images/anim_frame2.webp") + frame1.save( + temp_file, + save_all=True, + append_images=[frame2, frame1, frame2, frame1], + duration=durations, + ) im = Image.open(temp_file) self.assertEqual(im.n_frames, 5) @@ -133,18 +142,21 @@ class TestFileWebpAnimation(PillowTestCase): dur = 33 temp_file = self.tempfile("temp.webp") - frame1 = Image.open('Tests/images/anim_frame1.webp') - frame2 = Image.open('Tests/images/anim_frame2.webp') - frame1.save(temp_file, save_all=True, - append_images=[frame2, frame1, frame2, frame1], - duration=dur) + frame1 = Image.open("Tests/images/anim_frame1.webp") + frame2 = Image.open("Tests/images/anim_frame2.webp") + frame1.save( + temp_file, + save_all=True, + append_images=[frame2, frame1, frame2, frame1], + duration=dur, + ) im = Image.open(temp_file) self.assertEqual(im.n_frames, 5) self.assertTrue(im.is_animated) # Traverse frames in reverse, checking timestamps and durations - ts = dur * (im.n_frames-1) + ts = dur * (im.n_frames - 1) for frame in reversed(range(im.n_frames)): im.seek(frame) im.load() diff --git a/Tests/test_file_webp_lossless.py b/Tests/test_file_webp_lossless.py index 528c9177d..c9dddfcd4 100644 --- a/Tests/test_file_webp_lossless.py +++ b/Tests/test_file_webp_lossless.py @@ -4,20 +4,20 @@ from PIL import Image try: from PIL import _webp + HAVE_WEBP = True except ImportError: HAVE_WEBP = False class TestFileWebpLossless(PillowTestCase): - def setUp(self): if not HAVE_WEBP: - self.skipTest('WebP support not installed') + self.skipTest("WebP support not installed") return if _webp.WebPDecoderVersion() < 0x0200: - self.skipTest('lossless not included') + self.skipTest("lossless not included") self.rgb_mode = "RGB" diff --git a/Tests/test_file_webp_metadata.py b/Tests/test_file_webp_metadata.py index 402b6ce42..7f4b457ed 100644 --- a/Tests/test_file_webp_metadata.py +++ b/Tests/test_file_webp_metadata.py @@ -4,20 +4,20 @@ from PIL import Image try: from PIL import _webp + HAVE_WEBP = True except ImportError: HAVE_WEBP = False class TestFileWebpMetadata(PillowTestCase): - def setUp(self): if not HAVE_WEBP: - self.skipTest('WebP support not installed') + self.skipTest("WebP support not installed") return if not _webp.HAVE_WEBPMUX: - self.skipTest('WebPMux support not installed') + self.skipTest("WebPMux support not installed") def test_read_exif_metadata(self): @@ -33,8 +33,8 @@ class TestFileWebpMetadata(PillowTestCase): # camera make self.assertEqual(exif[271], "Canon") - jpeg_image = Image.open('Tests/images/flower.jpg') - expected_exif = jpeg_image.info['exif'] + jpeg_image = Image.open("Tests/images/flower.jpg") + expected_exif = jpeg_image.info["exif"] self.assertEqual(exif_data, expected_exif) @@ -43,7 +43,7 @@ class TestFileWebpMetadata(PillowTestCase): file_path = "Tests/images/flower.jpg" image = Image.open(file_path) - expected_exif = image.info['exif'] + expected_exif = image.info["exif"] test_buffer = BytesIO() @@ -52,11 +52,10 @@ class TestFileWebpMetadata(PillowTestCase): test_buffer.seek(0) webp_image = Image.open(test_buffer) - webp_exif = webp_image.info.get('exif', None) + webp_exif = webp_image.info.get("exif", None) self.assertTrue(webp_exif) if webp_exif: - self.assertEqual( - webp_exif, expected_exif, "WebP EXIF didn't match") + self.assertEqual(webp_exif, expected_exif, "WebP EXIF didn't match") def test_read_icc_profile(self): @@ -66,10 +65,10 @@ class TestFileWebpMetadata(PillowTestCase): self.assertEqual(image.format, "WEBP") self.assertTrue(image.info.get("icc_profile", None)) - icc = image.info['icc_profile'] + icc = image.info["icc_profile"] - jpeg_image = Image.open('Tests/images/flower2.jpg') - expected_icc = jpeg_image.info['icc_profile'] + jpeg_image = Image.open("Tests/images/flower2.jpg") + expected_icc = jpeg_image.info["icc_profile"] self.assertEqual(icc, expected_icc) @@ -78,7 +77,7 @@ class TestFileWebpMetadata(PillowTestCase): file_path = "Tests/images/flower2.jpg" image = Image.open(file_path) - expected_icc_profile = image.info['icc_profile'] + expected_icc_profile = image.info["icc_profile"] test_buffer = BytesIO() @@ -87,20 +86,20 @@ class TestFileWebpMetadata(PillowTestCase): test_buffer.seek(0) webp_image = Image.open(test_buffer) - webp_icc_profile = webp_image.info.get('icc_profile', None) + webp_icc_profile = webp_image.info.get("icc_profile", None) self.assertTrue(webp_icc_profile) if webp_icc_profile: self.assertEqual( - webp_icc_profile, expected_icc_profile, - "Webp ICC didn't match") + webp_icc_profile, expected_icc_profile, "Webp ICC didn't match" + ) def test_read_no_exif(self): from io import BytesIO file_path = "Tests/images/flower.jpg" image = Image.open(file_path) - self.assertIn('exif', image.info) + self.assertIn("exif", image.info) test_buffer = BytesIO() @@ -113,23 +112,28 @@ class TestFileWebpMetadata(PillowTestCase): def test_write_animated_metadata(self): if not _webp.HAVE_WEBPANIM: - self.skipTest('WebP animation support not available') + self.skipTest("WebP animation support not available") - iccp_data = ''.encode('utf-8') - exif_data = ''.encode('utf-8') - xmp_data = ''.encode('utf-8') + iccp_data = "".encode("utf-8") + exif_data = "".encode("utf-8") + xmp_data = "".encode("utf-8") temp_file = self.tempfile("temp.webp") - frame1 = Image.open('Tests/images/anim_frame1.webp') - frame2 = Image.open('Tests/images/anim_frame2.webp') - frame1.save(temp_file, save_all=True, - append_images=[frame2, frame1, frame2], - icc_profile=iccp_data, exif=exif_data, xmp=xmp_data) + frame1 = Image.open("Tests/images/anim_frame1.webp") + frame2 = Image.open("Tests/images/anim_frame2.webp") + frame1.save( + temp_file, + save_all=True, + append_images=[frame2, frame1, frame2], + icc_profile=iccp_data, + exif=exif_data, + xmp=xmp_data, + ) image = Image.open(temp_file) - self.assertIn('icc_profile', image.info) - self.assertIn('exif', image.info) - self.assertIn('xmp', image.info) - 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)) + self.assertIn("icc_profile", image.info) + self.assertIn("exif", image.info) + self.assertIn("xmp", image.info) + 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)) diff --git a/Tests/test_file_wmf.py b/Tests/test_file_wmf.py index e3c6063f2..cf104cfe3 100644 --- a/Tests/test_file_wmf.py +++ b/Tests/test_file_wmf.py @@ -5,26 +5,25 @@ from PIL import WmfImagePlugin class TestFileWmf(PillowTestCase): - def test_load_raw(self): # Test basic EMF open and rendering - im = Image.open('Tests/images/drawing.emf') + im = Image.open("Tests/images/drawing.emf") if hasattr(Image.core, "drawwmf"): # Currently, support for WMF/EMF is Windows-only im.load() # Compare to reference rendering - imref = Image.open('Tests/images/drawing_emf_ref.png') + imref = Image.open("Tests/images/drawing_emf_ref.png") imref.load() self.assert_image_similar(im, imref, 0) # Test basic WMF open and rendering - im = Image.open('Tests/images/drawing.wmf') + im = Image.open("Tests/images/drawing.wmf") if hasattr(Image.core, "drawwmf"): # Currently, support for WMF/EMF is Windows-only im.load() # Compare to reference rendering - imref = Image.open('Tests/images/drawing_wmf_ref.png') + imref = Image.open("Tests/images/drawing_wmf_ref.png") imref.load() self.assert_image_similar(im, imref, 2.0) @@ -34,6 +33,7 @@ class TestFileWmf(PillowTestCase): def save(self, im, fp, filename): self.methodCalled = True + handler = TestHandler() WmfImagePlugin.register_handler(handler) @@ -47,16 +47,16 @@ class TestFileWmf(PillowTestCase): def test_load_dpi_rounding(self): # Round up - im = Image.open('Tests/images/drawing.emf') + im = Image.open("Tests/images/drawing.emf") self.assertEqual(im.info["dpi"], 1424) # Round down - im = Image.open('Tests/images/drawing_roundDown.emf') + im = Image.open("Tests/images/drawing_roundDown.emf") self.assertEqual(im.info["dpi"], 1426) def test_save(self): im = hopper() for ext in [".wmf", ".emf"]: - tmpfile = self.tempfile("temp"+ext) + tmpfile = self.tempfile("temp" + ext) self.assertRaises(IOError, im.save, tmpfile) diff --git a/Tests/test_file_xbm.py b/Tests/test_file_xbm.py index fbcb30e90..3ebadf030 100644 --- a/Tests/test_file_xbm.py +++ b/Tests/test_file_xbm.py @@ -27,14 +27,13 @@ static char basic_bits[] = { class TestFileXbm(PillowTestCase): - def test_pil151(self): from io import BytesIO im = Image.open(BytesIO(PIL151)) im.load() - self.assertEqual(im.mode, '1') + self.assertEqual(im.mode, "1") self.assertEqual(im.size, (32, 32)) def test_open(self): @@ -46,7 +45,7 @@ class TestFileXbm(PillowTestCase): im = Image.open(filename) # Assert - self.assertEqual(im.mode, '1') + self.assertEqual(im.mode, "1") self.assertEqual(im.size, (128, 128)) def test_open_filename_with_underscore(self): @@ -58,5 +57,5 @@ class TestFileXbm(PillowTestCase): im = Image.open(filename) # Assert - self.assertEqual(im.mode, '1') + self.assertEqual(im.mode, "1") self.assertEqual(im.size, (128, 128)) diff --git a/Tests/test_file_xpm.py b/Tests/test_file_xpm.py index b57cda2e3..4133b0096 100644 --- a/Tests/test_file_xpm.py +++ b/Tests/test_file_xpm.py @@ -6,7 +6,6 @@ TEST_FILE = "Tests/images/hopper.xpm" class TestFileXpm(PillowTestCase): - def test_sanity(self): im = Image.open(TEST_FILE) im.load() @@ -15,13 +14,12 @@ class TestFileXpm(PillowTestCase): self.assertEqual(im.format, "XPM") # large error due to quantization->44 colors. - self.assert_image_similar(im.convert('RGB'), hopper('RGB'), 60) + self.assert_image_similar(im.convert("RGB"), hopper("RGB"), 60) def test_invalid_file(self): invalid_file = "Tests/images/flower.jpg" - self.assertRaises(SyntaxError, - XpmImagePlugin.XpmImageFile, invalid_file) + self.assertRaises(SyntaxError, XpmImagePlugin.XpmImageFile, invalid_file) def test_load_read(self): # Arrange diff --git a/Tests/test_file_xvthumb.py b/Tests/test_file_xvthumb.py index 11672ebae..bcced2853 100644 --- a/Tests/test_file_xvthumb.py +++ b/Tests/test_file_xvthumb.py @@ -6,7 +6,6 @@ TEST_FILE = "Tests/images/hopper.p7" class TestFileXVThumb(PillowTestCase): - def test_open(self): # Act im = Image.open(TEST_FILE) @@ -24,13 +23,13 @@ class TestFileXVThumb(PillowTestCase): bad_file = "Tests/images/hopper_bad.p7" # Act / Assert - self.assertRaises(SyntaxError, - XVThumbImagePlugin.XVThumbImageFile, bad_file) + self.assertRaises(SyntaxError, XVThumbImagePlugin.XVThumbImageFile, bad_file) def test_invalid_file(self): # Arrange invalid_file = "Tests/images/flower.jpg" # Act / Assert - self.assertRaises(SyntaxError, - XVThumbImagePlugin.XVThumbImageFile, invalid_file) + self.assertRaises( + SyntaxError, XVThumbImagePlugin.XVThumbImageFile, invalid_file + ) diff --git a/Tests/test_font_bdf.py b/Tests/test_font_bdf.py index be49e818e..69badc376 100644 --- a/Tests/test_font_bdf.py +++ b/Tests/test_font_bdf.py @@ -6,7 +6,6 @@ filename = "Tests/images/courB08.bdf" class TestFontBdf(PillowTestCase): - def test_sanity(self): with open(filename, "rb") as test_file: diff --git a/Tests/test_font_leaks.py b/Tests/test_font_leaks.py index 119012bc5..84f2a434f 100644 --- a/Tests/test_font_leaks.py +++ b/Tests/test_font_leaks.py @@ -4,22 +4,24 @@ import sys from PIL import Image, features, ImageDraw, ImageFont -@unittest.skipIf(sys.platform.startswith('win32'), "requires Unix or macOS") +@unittest.skipIf(sys.platform.startswith("win32"), "requires Unix or macOS") class TestTTypeFontLeak(PillowLeakTestCase): # fails at iteration 3 in master iterations = 10 mem_limit = 4096 # k def _test_font(self, font): - im = Image.new('RGB', (255, 255), 'white') + im = Image.new("RGB", (255, 255), "white") draw = ImageDraw.ImageDraw(im) - self._test_leak(lambda: draw.text((0, 0), "some text "*1024, # ~10k - font=font, fill="black")) + self._test_leak( + lambda: draw.text( + (0, 0), "some text " * 1024, font=font, fill="black" # ~10k + ) + ) - @unittest.skipIf(not features.check('freetype2'), - "Test requires freetype2") + @unittest.skipIf(not features.check("freetype2"), "Test requires freetype2") def test_leak(self): - ttype = ImageFont.truetype('Tests/fonts/FreeMono.ttf', 20) + ttype = ImageFont.truetype("Tests/fonts/FreeMono.ttf", 20) self._test_font(ttype) diff --git a/Tests/test_font_pcf.py b/Tests/test_font_pcf.py index d092634f3..5db389d37 100644 --- a/Tests/test_font_pcf.py +++ b/Tests/test_font_pcf.py @@ -12,7 +12,6 @@ message = "hello, world" class TestFontPcf(PillowTestCase): - def setUp(self): if "zip_encoder" not in codecs or "zip_decoder" not in codecs: self.skipTest("zlib support not available") @@ -25,15 +24,15 @@ class TestFontPcf(PillowTestCase): self.assertEqual(len([_f for _f in font.glyph if _f]), 223) tempname = self.tempfile("temp.pil") - self.addCleanup(self.delete_tempfile, tempname[:-4]+'.pbm') + self.addCleanup(self.delete_tempfile, tempname[:-4] + ".pbm") font.save(tempname) - with Image.open(tempname.replace('.pil', '.pbm')) as loaded: - with Image.open('Tests/fonts/10x20.pbm') as target: + with Image.open(tempname.replace(".pil", ".pbm")) as loaded: + with Image.open("Tests/fonts/10x20.pbm") as target: self.assert_image_equal(loaded, target) - with open(tempname, 'rb') as f_loaded: - with open('Tests/fonts/10x20.pil', 'rb') as f_target: + with open(tempname, "rb") as f_loaded: + with open("Tests/fonts/10x20.pil", "rb") as f_target: self.assertEqual(f_loaded.read(), f_target.read()) return tempname @@ -49,8 +48,8 @@ class TestFontPcf(PillowTestCase): font = ImageFont.load(tempname) im = Image.new("L", (130, 30), "white") draw = ImageDraw.Draw(im) - draw.text((0, 0), message, 'black', font=font) - with Image.open('Tests/images/test_draw_pbm_target.png') as target: + draw.text((0, 0), message, "black", font=font) + with Image.open("Tests/images/test_draw_pbm_target.png") as target: self.assert_image_similar(im, target, 0) def test_textsize(self): @@ -61,8 +60,8 @@ class TestFontPcf(PillowTestCase): self.assertEqual(dy, 20) self.assertIn(dx, (0, 10)) for l in range(len(message)): - msg = message[:l+1] - self.assertEqual(font.getsize(msg), (len(msg)*10, 20)) + msg = message[: l + 1] + self.assertEqual(font.getsize(msg), (len(msg) * 10, 20)) def _test_high_characters(self, message): tempname = self.save_font() @@ -70,12 +69,12 @@ class TestFontPcf(PillowTestCase): im = Image.new("L", (750, 30), "white") draw = ImageDraw.Draw(im) draw.text((0, 0), message, "black", font=font) - with Image.open('Tests/images/high_ascii_chars.png') as target: + with Image.open("Tests/images/high_ascii_chars.png") as target: self.assert_image_similar(im, target, 0) def test_high_characters(self): - message = "".join(chr(i+1) for i in range(140, 232)) + message = "".join(chr(i + 1) for i in range(140, 232)) self._test_high_characters(message) # accept bytes instances in Py3. if py3: - self._test_high_characters(message.encode('latin1')) + self._test_high_characters(message.encode("latin1")) diff --git a/Tests/test_format_hsv.py b/Tests/test_format_hsv.py index 3e1c00c9e..781bfc801 100644 --- a/Tests/test_format_hsv.py +++ b/Tests/test_format_hsv.py @@ -8,19 +8,18 @@ import itertools class TestFormatHSV(PillowTestCase): - def int_to_float(self, i): - return float(i)/255.0 + return float(i) / 255.0 def str_to_float(self, i): - return float(ord(i))/255.0 + return float(ord(i)) / 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)) + Image.new("HSV", (100, 100)) def wedge(self): w = Image._wedge() @@ -28,7 +27,7 @@ class TestFormatHSV(PillowTestCase): (px, h) = w.size - r = Image.new('L', (px*3, h)) + r = Image.new("L", (px * 3, h)) g = r.copy() b = r.copy() @@ -36,12 +35,12 @@ class TestFormatHSV(PillowTestCase): r.paste(w90, (px, 0)) g.paste(w90, (0, 0)) - g.paste(w, (2*px, 0)) + g.paste(w, (2 * px, 0)) b.paste(w, (px, 0)) - b.paste(w90, (2*px, 0)) + b.paste(w90, (2 * px, 0)) - img = Image.merge('RGB', (r, g, b)) + img = Image.merge("RGB", (r, g, b)) return img @@ -55,77 +54,101 @@ class TestFormatHSV(PillowTestCase): else: conv_func = self.str_to_float - if hasattr(itertools, 'izip'): + if hasattr(itertools, "izip"): iter_helper = itertools.izip else: iter_helper = itertools.zip_longest - converted = [self.tuple_to_ints(func(conv_func(_r), conv_func(_g), - conv_func(_b))) - for (_r, _g, _b) in iter_helper(r.tobytes(), g.tobytes(), - b.tobytes())] + converted = [ + self.tuple_to_ints(func(conv_func(_r), conv_func(_g), conv_func(_b))) + for (_r, _g, _b) in iter_helper(r.tobytes(), g.tobytes(), b.tobytes()) + ] if py3: - new_bytes = b''.join(bytes(chr(h)+chr(s)+chr(v), 'latin-1') for ( - h, s, v) in converted) + new_bytes = b"".join( + bytes(chr(h) + chr(s) + chr(v), "latin-1") for (h, s, v) in converted + ) else: - new_bytes = b''.join(chr(h)+chr(s)+chr(v) for ( - h, s, v) in converted) + new_bytes = b"".join(chr(h) + chr(s) + chr(v) for (h, s, v) in converted) hsv = Image.frombytes(mode, r.size, new_bytes) return hsv def to_hsv_colorsys(self, im): - return self.to_xxx_colorsys(im, colorsys.rgb_to_hsv, 'HSV') + return self.to_xxx_colorsys(im, colorsys.rgb_to_hsv, "HSV") def to_rgb_colorsys(self, im): - return self.to_xxx_colorsys(im, colorsys.hsv_to_rgb, 'RGB') + return self.to_xxx_colorsys(im, colorsys.hsv_to_rgb, "RGB") def test_wedge(self): - src = self.wedge().resize((3*32, 32), Image.BILINEAR) - im = src.convert('HSV') + src = self.wedge().resize((3 * 32, 32), Image.BILINEAR) + im = src.convert("HSV") comparable = self.to_hsv_colorsys(src) - self.assert_image_similar(im.getchannel(0), comparable.getchannel(0), - 1, "Hue conversion is wrong") - self.assert_image_similar(im.getchannel(1), comparable.getchannel(1), - 1, "Saturation conversion is wrong") - self.assert_image_similar(im.getchannel(2), comparable.getchannel(2), - 1, "Value conversion is wrong") + self.assert_image_similar( + im.getchannel(0), comparable.getchannel(0), 1, "Hue conversion is wrong" + ) + self.assert_image_similar( + im.getchannel(1), + comparable.getchannel(1), + 1, + "Saturation conversion is wrong", + ) + self.assert_image_similar( + im.getchannel(2), comparable.getchannel(2), 1, "Value conversion is wrong" + ) comparable = src - im = im.convert('RGB') + im = im.convert("RGB") - self.assert_image_similar(im.getchannel(0), comparable.getchannel(0), - 3, "R conversion is wrong") - self.assert_image_similar(im.getchannel(1), comparable.getchannel(1), - 3, "G conversion is wrong") - self.assert_image_similar(im.getchannel(2), comparable.getchannel(2), - 3, "B conversion is wrong") + self.assert_image_similar( + im.getchannel(0), comparable.getchannel(0), 3, "R conversion is wrong" + ) + self.assert_image_similar( + im.getchannel(1), comparable.getchannel(1), 3, "G conversion is wrong" + ) + self.assert_image_similar( + im.getchannel(2), comparable.getchannel(2), 3, "B conversion is wrong" + ) def test_convert(self): - im = hopper('RGB').convert('HSV') - comparable = self.to_hsv_colorsys(hopper('RGB')) + im = hopper("RGB").convert("HSV") + comparable = self.to_hsv_colorsys(hopper("RGB")) - self.assert_image_similar(im.getchannel(0), comparable.getchannel(0), - 1, "Hue conversion is wrong") - self.assert_image_similar(im.getchannel(1), comparable.getchannel(1), - 1, "Saturation conversion is wrong") - self.assert_image_similar(im.getchannel(2), comparable.getchannel(2), - 1, "Value conversion is wrong") + self.assert_image_similar( + im.getchannel(0), comparable.getchannel(0), 1, "Hue conversion is wrong" + ) + self.assert_image_similar( + im.getchannel(1), + comparable.getchannel(1), + 1, + "Saturation conversion is wrong", + ) + self.assert_image_similar( + im.getchannel(2), comparable.getchannel(2), 1, "Value conversion is wrong" + ) def test_hsv_to_rgb(self): - comparable = self.to_hsv_colorsys(hopper('RGB')) - converted = comparable.convert('RGB') + comparable = self.to_hsv_colorsys(hopper("RGB")) + converted = comparable.convert("RGB") comparable = self.to_rgb_colorsys(comparable) - self.assert_image_similar(converted.getchannel(0), - comparable.getchannel(0), - 3, "R conversion is wrong") - self.assert_image_similar(converted.getchannel(1), - comparable.getchannel(1), - 3, "G conversion is wrong") - self.assert_image_similar(converted.getchannel(2), - comparable.getchannel(2), - 3, "B conversion is wrong") + self.assert_image_similar( + converted.getchannel(0), + comparable.getchannel(0), + 3, + "R conversion is wrong", + ) + self.assert_image_similar( + converted.getchannel(1), + comparable.getchannel(1), + 3, + "G conversion is wrong", + ) + self.assert_image_similar( + converted.getchannel(2), + comparable.getchannel(2), + 3, + "B conversion is wrong", + ) diff --git a/Tests/test_format_lab.py b/Tests/test_format_lab.py index 8a096e672..87e985832 100644 --- a/Tests/test_format_lab.py +++ b/Tests/test_format_lab.py @@ -4,15 +4,14 @@ from PIL import Image class TestFormatLab(PillowTestCase): - def test_white(self): - i = Image.open('Tests/images/lab.tif') + i = Image.open("Tests/images/lab.tif") i.load() - self.assertEqual(i.mode, 'LAB') + self.assertEqual(i.mode, "LAB") - self.assertEqual(i.getbands(), ('L', 'A', 'B')) + self.assertEqual(i.getbands(), ("L", "A", "B")) k = i.getpixel((0, 0)) self.assertEqual(k, (255, 128, 128)) @@ -21,14 +20,14 @@ class TestFormatLab(PillowTestCase): a = i.getdata(1) b = i.getdata(2) - self.assertEqual(list(L), [255]*100) - self.assertEqual(list(a), [128]*100) - self.assertEqual(list(b), [128]*100) + self.assertEqual(list(L), [255] * 100) + self.assertEqual(list(a), [128] * 100) + self.assertEqual(list(b), [128] * 100) def test_green(self): # l= 50 (/100), a = -100 (-128 .. 128) b=0 in PS # == RGB: 0, 152, 117 - i = Image.open('Tests/images/lab-green.tif') + i = Image.open("Tests/images/lab-green.tif") k = i.getpixel((0, 0)) self.assertEqual(k, (128, 28, 128)) @@ -36,7 +35,7 @@ class TestFormatLab(PillowTestCase): def test_red(self): # l= 50 (/100), a = 100 (-128 .. 128) b=0 in PS # == RGB: 255, 0, 124 - i = Image.open('Tests/images/lab-red.tif') + i = Image.open("Tests/images/lab-red.tif") k = i.getpixel((0, 0)) self.assertEqual(k, (128, 228, 128)) diff --git a/Tests/test_image.py b/Tests/test_image.py index 5bfcde368..5afbbcc3c 100644 --- a/Tests/test_image.py +++ b/Tests/test_image.py @@ -8,37 +8,54 @@ import shutil class TestImage(PillowTestCase): - def test_image_modes_success(self): for mode in [ - '1', 'P', 'PA', - 'L', 'LA', 'La', - 'F', 'I', 'I;16', 'I;16L', 'I;16B', 'I;16N', - 'RGB', 'RGBX', 'RGBA', 'RGBa', - 'CMYK', 'YCbCr', 'LAB', 'HSV', + "1", + "P", + "PA", + "L", + "LA", + "La", + "F", + "I", + "I;16", + "I;16L", + "I;16B", + "I;16N", + "RGB", + "RGBX", + "RGBA", + "RGBa", + "CMYK", + "YCbCr", + "LAB", + "HSV", ]: Image.new(mode, (1, 1)) def test_image_modes_fail(self): for mode in [ - '', 'bad', 'very very long', - 'BGR;15', 'BGR;16', 'BGR;24', 'BGR;32' + "", + "bad", + "very very long", + "BGR;15", + "BGR;16", + "BGR;24", + "BGR;32", ]: with self.assertRaises(ValueError) as e: Image.new(mode, (1, 1)) - self.assertEqual(str(e.exception), 'unrecognized image mode') + self.assertEqual(str(e.exception), "unrecognized image mode") def test_sanity(self): im = Image.new("L", (100, 100)) - self.assertEqual( - repr(im)[:45], " 8 bit lut for converting I->L images see https://github.com/python-pillow/Pillow/issues/440 """ im = hopper("I") - im.point(list(range(256))*256, 'L') + im.point(list(range(256)) * 256, "L") def test_f_lut(self): """ Tests for floating point lut of 8bit gray image """ - im = hopper('L') + im = hopper("L") lut = [0.5 * float(x) for x in range(256)] - out = im.point(lut, 'F') + out = im.point(lut, "F") - int_lut = [x//2 for x in range(256)] - self.assert_image_equal(out.convert('L'), im.point(int_lut, 'L')) + int_lut = [x // 2 for x in range(256)] + self.assert_image_equal(out.convert("L"), im.point(int_lut, "L")) def test_f_mode(self): - im = hopper('F') + im = hopper("F") self.assertRaises(ValueError, im.point, None) diff --git a/Tests/test_image_putalpha.py b/Tests/test_image_putalpha.py index 702afb995..859c39115 100644 --- a/Tests/test_image_putalpha.py +++ b/Tests/test_image_putalpha.py @@ -4,7 +4,6 @@ from PIL import Image class TestImagePutAlpha(PillowTestCase): - def test_interface(self): im = Image.new("RGBA", (1, 1), (1, 2, 3, 0)) @@ -25,21 +24,21 @@ class TestImagePutAlpha(PillowTestCase): self.assertEqual(im.getpixel((0, 0)), 1) im.putalpha(2) - self.assertEqual(im.mode, 'LA') + self.assertEqual(im.mode, "LA") self.assertEqual(im.getpixel((0, 0)), (1, 2)) im = Image.new("P", (1, 1), 1) self.assertEqual(im.getpixel((0, 0)), 1) im.putalpha(2) - self.assertEqual(im.mode, 'PA') + self.assertEqual(im.mode, "PA") self.assertEqual(im.getpixel((0, 0)), (1, 2)) im = Image.new("RGB", (1, 1), (1, 2, 3)) self.assertEqual(im.getpixel((0, 0)), (1, 2, 3)) im.putalpha(4) - self.assertEqual(im.mode, 'RGBA') + self.assertEqual(im.mode, "RGBA") self.assertEqual(im.getpixel((0, 0)), (1, 2, 3, 4)) def test_readonly(self): @@ -49,5 +48,5 @@ class TestImagePutAlpha(PillowTestCase): im.putalpha(4) self.assertFalse(im.readonly) - self.assertEqual(im.mode, 'RGBA') + self.assertEqual(im.mode, "RGBA") self.assertEqual(im.getpixel((0, 0)), (1, 2, 3, 4)) diff --git a/Tests/test_image_putdata.py b/Tests/test_image_putdata.py index 1b57e38b7..3d309eef2 100644 --- a/Tests/test_image_putdata.py +++ b/Tests/test_image_putdata.py @@ -7,7 +7,6 @@ from PIL import Image class TestImagePutData(PillowTestCase): - def test_sanity(self): im1 = hopper() @@ -33,32 +32,33 @@ class TestImagePutData(PillowTestCase): im = Image.new("RGBA", (1, 1)) im.putdata([value]) return im.getpixel((0, 0)) + self.assertEqual(put(0xFFFFFFFF), (255, 255, 255, 255)) self.assertEqual(put(0xFFFFFFFF), (255, 255, 255, 255)) self.assertEqual(put(-1), (255, 255, 255, 255)) self.assertEqual(put(-1), (255, 255, 255, 255)) - if sys.maxsize > 2**32: + if sys.maxsize > 2 ** 32: self.assertEqual(put(sys.maxsize), (255, 255, 255, 255)) else: self.assertEqual(put(sys.maxsize), (255, 255, 255, 127)) def test_pypy_performance(self): - im = Image.new('L', (256, 256)) - im.putdata(list(range(256))*256) + im = Image.new("L", (256, 256)) + im.putdata(list(range(256)) * 256) def test_mode_i(self): - src = hopper('L') + src = hopper("L") data = list(src.getdata()) - im = Image.new('I', src.size, 0) + im = Image.new("I", src.size, 0) im.putdata(data, 2, 256) target = [2 * elt + 256 for elt in data] self.assertEqual(list(im.getdata()), target) def test_mode_F(self): - src = hopper('L') + src = hopper("L") data = list(src.getdata()) - im = Image.new('F', src.size, 0) + im = Image.new("F", src.size, 0) im.putdata(data, 2.0, 256.0) target = [2.0 * float(elt) + 256.0 for elt in data] @@ -68,8 +68,8 @@ class TestImagePutData(PillowTestCase): # shouldn't segfault # see https://github.com/python-pillow/Pillow/issues/1008 - arr = array('B', [0])*15000 - im = Image.new('L', (150, 100)) + arr = array("B", [0]) * 15000 + im = Image.new("L", (150, 100)) im.putdata(arr) self.assertEqual(len(im.getdata()), len(arr)) @@ -78,8 +78,8 @@ class TestImagePutData(PillowTestCase): # shouldn't segfault # see https://github.com/python-pillow/Pillow/issues/1008 - im = Image.new('F', (150, 100)) - arr = array('f', [0.0])*15000 + im = Image.new("F", (150, 100)) + arr = array("f", [0.0]) * 15000 im.putdata(arr) self.assertEqual(len(im.getdata()), len(arr)) diff --git a/Tests/test_image_putpalette.py b/Tests/test_image_putpalette.py index 4bac448fd..55fa71fa5 100644 --- a/Tests/test_image_putpalette.py +++ b/Tests/test_image_putpalette.py @@ -4,20 +4,21 @@ from PIL import ImagePalette class TestImagePutPalette(PillowTestCase): - def test_putpalette(self): def palette(mode): im = hopper(mode).copy() - im.putpalette(list(range(256))*3) + im.putpalette(list(range(256)) * 3) p = im.getpalette() if p: return im.mode, p[:10] return im.mode + self.assertRaises(ValueError, palette, "1") for mode in ["L", "LA", "P", "PA"]: - self.assertEqual(palette(mode), - ("PA" if "A" in mode else "P", - [0, 1, 2, 3, 4, 5, 6, 7, 8, 9])) + self.assertEqual( + palette(mode), + ("PA" if "A" in mode else "P", [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]), + ) self.assertRaises(ValueError, palette, "I") self.assertRaises(ValueError, palette, "F") self.assertRaises(ValueError, palette, "RGB") diff --git a/Tests/test_image_quantize.py b/Tests/test_image_quantize.py index 5f2318574..0c56609e2 100644 --- a/Tests/test_image_quantize.py +++ b/Tests/test_image_quantize.py @@ -4,60 +4,59 @@ from PIL import Image class TestImageQuantize(PillowTestCase): - def test_sanity(self): image = hopper() converted = image.quantize() - self.assert_image(converted, 'P', converted.size) - self.assert_image_similar(converted.convert('RGB'), image, 10) + self.assert_image(converted, "P", converted.size) + self.assert_image_similar(converted.convert("RGB"), image, 10) image = hopper() - converted = image.quantize(palette=hopper('P')) - self.assert_image(converted, 'P', converted.size) - self.assert_image_similar(converted.convert('RGB'), image, 60) + converted = image.quantize(palette=hopper("P")) + self.assert_image(converted, "P", converted.size) + self.assert_image_similar(converted.convert("RGB"), image, 60) def test_libimagequant_quantize(self): image = hopper() try: converted = image.quantize(100, Image.LIBIMAGEQUANT) except ValueError as ex: - if 'dependency' in str(ex).lower(): - self.skipTest('libimagequant support not available') + if "dependency" in str(ex).lower(): + self.skipTest("libimagequant support not available") else: raise - self.assert_image(converted, 'P', converted.size) - self.assert_image_similar(converted.convert('RGB'), image, 15) + self.assert_image(converted, "P", converted.size) + self.assert_image_similar(converted.convert("RGB"), image, 15) self.assertEqual(len(converted.getcolors()), 100) def test_octree_quantize(self): image = hopper() converted = image.quantize(100, Image.FASTOCTREE) - self.assert_image(converted, 'P', converted.size) - self.assert_image_similar(converted.convert('RGB'), image, 20) + self.assert_image(converted, "P", converted.size) + self.assert_image_similar(converted.convert("RGB"), image, 20) self.assertEqual(len(converted.getcolors()), 100) def test_rgba_quantize(self): - image = hopper('RGBA') + image = hopper("RGBA") self.assertRaises(ValueError, image.quantize, method=0) self.assertEqual(image.quantize().convert().mode, "RGBA") def test_quantize(self): - image = Image.open('Tests/images/caption_6_33_22.png').convert('RGB') + image = Image.open("Tests/images/caption_6_33_22.png").convert("RGB") converted = image.quantize() - self.assert_image(converted, 'P', converted.size) - self.assert_image_similar(converted.convert('RGB'), image, 1) + self.assert_image(converted, "P", converted.size) + self.assert_image_similar(converted.convert("RGB"), image, 1) def test_quantize_no_dither(self): image = hopper() - palette = Image.open('Tests/images/caption_6_33_22.png').convert('P') + palette = Image.open("Tests/images/caption_6_33_22.png").convert("P") converted = image.quantize(dither=0, palette=palette) - self.assert_image(converted, 'P', converted.size) + self.assert_image(converted, "P", converted.size) def test_quantize_dither_diff(self): image = hopper() - palette = Image.open('Tests/images/caption_6_33_22.png').convert('P') + palette = Image.open("Tests/images/caption_6_33_22.png").convert("P") dither = image.quantize(dither=1, palette=palette) nodither = image.quantize(dither=0, palette=palette) diff --git a/Tests/test_image_resample.py b/Tests/test_image_resample.py index 9ab8280ed..8f666b573 100644 --- a/Tests/test_image_resample.py +++ b/Tests/test_image_resample.py @@ -9,7 +9,7 @@ from PIL import Image, ImageDraw class TestImagingResampleVulnerability(PillowTestCase): # see https://github.com/python-pillow/Pillow/issues/1710 def test_overflow(self): - im = hopper('L') + im = hopper("L") xsize = 0x100000008 // 4 ysize = 1000 # unimportant with self.assertRaises(MemoryError): @@ -29,11 +29,11 @@ class TestImagingResampleVulnerability(PillowTestCase): im.resize((100, -100)) def test_modify_after_resizing(self): - im = hopper('RGB') + im = hopper("RGB") # get copy with same size copy = im.resize(im.size) # some in-place operation - copy.paste('black', (0, 0, im.width // 2, im.height // 2)) + copy.paste("black", (0, 0, im.width // 2, im.height // 2)) # image should be different self.assertNotEqual(im.tobytes(), copy.tobytes()) @@ -47,7 +47,7 @@ class TestImagingCoreResampleAccuracy(PillowTestCase): 1f 1f e0 e0 1f 1f e0 e0 """ - case = Image.new('L', size, 255 - color) + case = Image.new("L", size, 255 - color) rectangle = ImageDraw.Draw(case).rectangle rectangle((0, 0, size[0] // 2 - 1, size[1] // 2 - 1), color) rectangle((size[0] // 2, size[1] // 2, size[0], size[1]), color) @@ -58,13 +58,13 @@ class TestImagingCoreResampleAccuracy(PillowTestCase): """Restores a sample image from given data string which contains hex-encoded pixels from the top left fourth of a sample. """ - data = data.replace(' ', '') - sample = Image.new('L', size) + data = data.replace(" ", "") + sample = Image.new("L", size) s_px = sample.load() w, h = size[0] // 2, size[1] // 2 for y in range(h): for x in range(w): - val = int(data[(y * w + x) * 2:(y * w + x + 1) * 2], 16) + val = int(data[(y * w + x) * 2 : (y * w + x + 1) * 2], 16) s_px[x, y] = val s_px[size[0] - x - 1, size[1] - y - 1] = val s_px[x, size[1] - y - 1] = 255 - val @@ -77,118 +77,134 @@ class TestImagingCoreResampleAccuracy(PillowTestCase): for y in range(case.size[1]): for x in range(case.size[0]): if c_px[x, y] != s_px[x, y]: - message = '\nHave: \n{}\n\nExpected: \n{}'.format( - self.serialize_image(case), - self.serialize_image(sample), + message = "\nHave: \n{}\n\nExpected: \n{}".format( + self.serialize_image(case), self.serialize_image(sample) ) self.assertEqual(s_px[x, y], c_px[x, y], message) def serialize_image(self, image): s_px = image.load() - return '\n'.join( - ' '.join( - '{:02x}'.format(s_px[x, y]) - for x in range(image.size[0]) - ) + return "\n".join( + " ".join("{:02x}".format(s_px[x, y]) for x in range(image.size[0])) for y in range(image.size[1]) ) def test_reduce_box(self): - for mode in ['RGBX', 'RGB', 'La', 'L']: - case = self.make_case(mode, (8, 8), 0xe1) + for mode in ["RGBX", "RGB", "La", "L"]: + case = self.make_case(mode, (8, 8), 0xE1) case = case.resize((4, 4), Image.BOX) - data = ('e1 e1' - 'e1 e1') + # fmt: off + data = ("e1 e1" + "e1 e1") + # fmt: on for channel in case.split(): self.check_case(channel, self.make_sample(data, (4, 4))) def test_reduce_bilinear(self): - for mode in ['RGBX', 'RGB', 'La', 'L']: - case = self.make_case(mode, (8, 8), 0xe1) + for mode in ["RGBX", "RGB", "La", "L"]: + case = self.make_case(mode, (8, 8), 0xE1) case = case.resize((4, 4), Image.BILINEAR) - data = ('e1 c9' - 'c9 b7') + # fmt: off + data = ("e1 c9" + "c9 b7") + # fmt: on for channel in case.split(): self.check_case(channel, self.make_sample(data, (4, 4))) def test_reduce_hamming(self): - for mode in ['RGBX', 'RGB', 'La', 'L']: - case = self.make_case(mode, (8, 8), 0xe1) + for mode in ["RGBX", "RGB", "La", "L"]: + case = self.make_case(mode, (8, 8), 0xE1) case = case.resize((4, 4), Image.HAMMING) - data = ('e1 da' - 'da d3') + # fmt: off + data = ("e1 da" + "da d3") + # fmt: on for channel in case.split(): self.check_case(channel, self.make_sample(data, (4, 4))) def test_reduce_bicubic(self): - for mode in ['RGBX', 'RGB', 'La', 'L']: - case = self.make_case(mode, (12, 12), 0xe1) + for mode in ["RGBX", "RGB", "La", "L"]: + case = self.make_case(mode, (12, 12), 0xE1) case = case.resize((6, 6), Image.BICUBIC) - data = ('e1 e3 d4' - 'e3 e5 d6' - 'd4 d6 c9') + # fmt: off + data = ("e1 e3 d4" + "e3 e5 d6" + "d4 d6 c9") + # fmt: on for channel in case.split(): self.check_case(channel, self.make_sample(data, (6, 6))) def test_reduce_lanczos(self): - for mode in ['RGBX', 'RGB', 'La', 'L']: - case = self.make_case(mode, (16, 16), 0xe1) + for mode in ["RGBX", "RGB", "La", "L"]: + case = self.make_case(mode, (16, 16), 0xE1) case = case.resize((8, 8), Image.LANCZOS) - data = ('e1 e0 e4 d7' - 'e0 df e3 d6' - 'e4 e3 e7 da' - 'd7 d6 d9 ce') + # fmt: off + data = ("e1 e0 e4 d7" + "e0 df e3 d6" + "e4 e3 e7 da" + "d7 d6 d9 ce") + # fmt: on for channel in case.split(): self.check_case(channel, self.make_sample(data, (8, 8))) def test_enlarge_box(self): - for mode in ['RGBX', 'RGB', 'La', 'L']: - case = self.make_case(mode, (2, 2), 0xe1) + for mode in ["RGBX", "RGB", "La", "L"]: + case = self.make_case(mode, (2, 2), 0xE1) case = case.resize((4, 4), Image.BOX) - data = ('e1 e1' - 'e1 e1') + # fmt: off + data = ("e1 e1" + "e1 e1") + # fmt: on for channel in case.split(): self.check_case(channel, self.make_sample(data, (4, 4))) def test_enlarge_bilinear(self): - for mode in ['RGBX', 'RGB', 'La', 'L']: - case = self.make_case(mode, (2, 2), 0xe1) + for mode in ["RGBX", "RGB", "La", "L"]: + case = self.make_case(mode, (2, 2), 0xE1) case = case.resize((4, 4), Image.BILINEAR) - data = ('e1 b0' - 'b0 98') + # fmt: off + data = ("e1 b0" + "b0 98") + # fmt: on for channel in case.split(): self.check_case(channel, self.make_sample(data, (4, 4))) def test_enlarge_hamming(self): - for mode in ['RGBX', 'RGB', 'La', 'L']: - case = self.make_case(mode, (2, 2), 0xe1) + for mode in ["RGBX", "RGB", "La", "L"]: + case = self.make_case(mode, (2, 2), 0xE1) case = case.resize((4, 4), Image.HAMMING) - data = ('e1 d2' - 'd2 c5') + # fmt: off + data = ("e1 d2" + "d2 c5") + # fmt: on for channel in case.split(): self.check_case(channel, self.make_sample(data, (4, 4))) def test_enlarge_bicubic(self): - for mode in ['RGBX', 'RGB', 'La', 'L']: - case = self.make_case(mode, (4, 4), 0xe1) + for mode in ["RGBX", "RGB", "La", "L"]: + case = self.make_case(mode, (4, 4), 0xE1) case = case.resize((8, 8), Image.BICUBIC) - data = ('e1 e5 ee b9' - 'e5 e9 f3 bc' - 'ee f3 fd c1' - 'b9 bc c1 a2') + # fmt: off + data = ("e1 e5 ee b9" + "e5 e9 f3 bc" + "ee f3 fd c1" + "b9 bc c1 a2") + # fmt: on for channel in case.split(): self.check_case(channel, self.make_sample(data, (8, 8))) def test_enlarge_lanczos(self): - for mode in ['RGBX', 'RGB', 'La', 'L']: - case = self.make_case(mode, (6, 6), 0xe1) + for mode in ["RGBX", "RGB", "La", "L"]: + case = self.make_case(mode, (6, 6), 0xE1) case = case.resize((12, 12), Image.LANCZOS) - data = ('e1 e0 db ed f5 b8' - 'e0 df da ec f3 b7' - 'db db d6 e7 ee b5' - 'ed ec e6 fb ff bf' - 'f5 f4 ee ff ff c4' - 'b8 b7 b4 bf c4 a0') + data = ( + "e1 e0 db ed f5 b8" + "e0 df da ec f3 b7" + "db db d6 e7 ee b5" + "ed ec e6 fb ff bf" + "f5 f4 ee ff ff c4" + "b8 b7 b4 bf c4 a0" + ) for channel in case.split(): self.check_case(channel, self.make_sample(data, (12, 12))) @@ -204,29 +220,28 @@ class CoreResampleConsistencyTest(PillowTestCase): for x in range(channel.size[0]): for y in range(channel.size[1]): if px[x, y] != color: - message = "{} != {} for pixel {}".format( - px[x, y], color, (x, y)) + message = "{} != {} for pixel {}".format(px[x, y], color, (x, y)) self.assertEqual(px[x, y], color, message) def test_8u(self): - im, color = self.make_case('RGB', (0, 64, 255)) + im, color = self.make_case("RGB", (0, 64, 255)) r, g, b = im.split() self.run_case((r, color[0])) self.run_case((g, color[1])) self.run_case((b, color[2])) - self.run_case(self.make_case('L', 12)) + self.run_case(self.make_case("L", 12)) def test_32i(self): - self.run_case(self.make_case('I', 12)) - self.run_case(self.make_case('I', 0x7fffffff)) - self.run_case(self.make_case('I', -12)) - self.run_case(self.make_case('I', -1 << 31)) + self.run_case(self.make_case("I", 12)) + self.run_case(self.make_case("I", 0x7FFFFFFF)) + self.run_case(self.make_case("I", -12)) + self.run_case(self.make_case("I", -1 << 31)) def test_32f(self): - self.run_case(self.make_case('F', 1)) - self.run_case(self.make_case('F', 3.40282306074e+38)) - self.run_case(self.make_case('F', 1.175494e-38)) - self.run_case(self.make_case('F', 1.192093e-07)) + self.run_case(self.make_case("F", 1)) + self.run_case(self.make_case("F", 3.40282306074e38)) + self.run_case(self.make_case("F", 1.175494e-38)) + self.run_case(self.make_case("F", 1.192093e-07)) class CoreResampleAlphaCorrectTest(PillowTestCase): @@ -244,13 +259,16 @@ class CoreResampleAlphaCorrectTest(PillowTestCase): px = i.load() for y in range(i.size[1]): used_colors = {px[x, y][0] for x in range(i.size[0])} - self.assertEqual(256, len(used_colors), - 'All colors should present in resized image. ' - 'Only {} on {} line.'.format(len(used_colors), y)) + self.assertEqual( + 256, + len(used_colors), + "All colors should present in resized image. " + "Only {} on {} line.".format(len(used_colors), y), + ) @unittest.skip("current implementation isn't precise enough") def test_levels_rgba(self): - case = self.make_levels_case('RGBA') + case = self.make_levels_case("RGBA") self.run_levels_case(case.resize((512, 32), Image.BOX)) self.run_levels_case(case.resize((512, 32), Image.BILINEAR)) self.run_levels_case(case.resize((512, 32), Image.HAMMING)) @@ -259,7 +277,7 @@ class CoreResampleAlphaCorrectTest(PillowTestCase): @unittest.skip("current implementation isn't precise enough") def test_levels_la(self): - case = self.make_levels_case('LA') + case = self.make_levels_case("LA") self.run_levels_case(case.resize((512, 32), Image.BOX)) self.run_levels_case(case.resize((512, 32), Image.BILINEAR)) self.run_levels_case(case.resize((512, 32), Image.HAMMING)) @@ -281,24 +299,21 @@ class CoreResampleAlphaCorrectTest(PillowTestCase): for y in range(i.size[1]): for x in range(i.size[0]): if px[x, y][-1] != 0 and px[x, y][:-1] != clean_pixel: - message = 'pixel at ({}, {}) is differ:\n{}\n{}'\ - .format(x, y, px[x, y], clean_pixel) + message = "pixel at ({}, {}) is differ:\n{}\n{}".format( + x, y, px[x, y], clean_pixel + ) self.assertEqual(px[x, y][:3], clean_pixel, message) def test_dirty_pixels_rgba(self): - case = self.make_dirty_case('RGBA', (255, 255, 0, 128), (0, 0, 255, 0)) + case = self.make_dirty_case("RGBA", (255, 255, 0, 128), (0, 0, 255, 0)) self.run_dirty_case(case.resize((20, 20), Image.BOX), (255, 255, 0)) - self.run_dirty_case(case.resize((20, 20), Image.BILINEAR), - (255, 255, 0)) - self.run_dirty_case(case.resize((20, 20), Image.HAMMING), - (255, 255, 0)) - self.run_dirty_case(case.resize((20, 20), Image.BICUBIC), - (255, 255, 0)) - self.run_dirty_case(case.resize((20, 20), Image.LANCZOS), - (255, 255, 0)) + self.run_dirty_case(case.resize((20, 20), Image.BILINEAR), (255, 255, 0)) + self.run_dirty_case(case.resize((20, 20), Image.HAMMING), (255, 255, 0)) + self.run_dirty_case(case.resize((20, 20), Image.BICUBIC), (255, 255, 0)) + self.run_dirty_case(case.resize((20, 20), Image.LANCZOS), (255, 255, 0)) def test_dirty_pixels_la(self): - case = self.make_dirty_case('LA', (255, 128), (0, 0)) + case = self.make_dirty_case("LA", (255, 128), (0, 0)) self.run_dirty_case(case.resize((20, 20), Image.BOX), (255,)) self.run_dirty_case(case.resize((20, 20), Image.BILINEAR), (255,)) self.run_dirty_case(case.resize((20, 20), Image.HAMMING), (255,)) @@ -309,27 +324,27 @@ class CoreResampleAlphaCorrectTest(PillowTestCase): class CoreResamplePassesTest(PillowTestCase): @contextmanager def count(self, diff): - count = Image.core.get_stats()['new_count'] + count = Image.core.get_stats()["new_count"] yield - self.assertEqual(Image.core.get_stats()['new_count'] - count, diff) + self.assertEqual(Image.core.get_stats()["new_count"] - count, diff) def test_horizontal(self): - im = hopper('L') + im = hopper("L") with self.count(1): im.resize((im.size[0] - 10, im.size[1]), Image.BILINEAR) def test_vertical(self): - im = hopper('L') + im = hopper("L") with self.count(1): im.resize((im.size[0], im.size[1] - 10), Image.BILINEAR) def test_both(self): - im = hopper('L') + im = hopper("L") with self.count(2): im.resize((im.size[0] - 10, im.size[1] - 10), Image.BILINEAR) def test_box_horizontal(self): - im = hopper('L') + im = hopper("L") box = (20, 0, im.size[0] - 20, im.size[1]) with self.count(1): # the same size, but different box @@ -339,7 +354,7 @@ class CoreResamplePassesTest(PillowTestCase): self.assert_image_similar(with_box, cropped, 0.1) def test_box_vertical(self): - im = hopper('L') + im = hopper("L") box = (0, 20, im.size[0], im.size[1] - 20) with self.count(1): # the same size, but different box @@ -354,7 +369,7 @@ class CoreResampleCoefficientsTest(PillowTestCase): test_color = 254 for size in range(400000, 400010, 2): - i = Image.new('L', (size, 1), 0) + i = Image.new("L", (size, 1), 0) draw = ImageDraw.Draw(i) draw.rectangle((0, 0, i.size[0] // 2 - 1, 0), test_color) @@ -365,7 +380,7 @@ class CoreResampleCoefficientsTest(PillowTestCase): def test_nonzero_coefficients(self): # regression test for the wrong coefficients calculation # due to bug https://github.com/python-pillow/Pillow/issues/2161 - im = Image.new('RGBA', (1280, 1280), (0x20, 0x40, 0x60, 0xff)) + im = Image.new("RGBA", (1280, 1280), (0x20, 0x40, 0x60, 0xFF)) histogram = im.resize((256, 256), Image.BICUBIC).histogram() # first channel @@ -375,21 +390,26 @@ class CoreResampleCoefficientsTest(PillowTestCase): # third channel self.assertEqual(histogram[0x100 * 2 + 0x60], 0x10000) # fourth channel - self.assertEqual(histogram[0x100 * 3 + 0xff], 0x10000) + self.assertEqual(histogram[0x100 * 3 + 0xFF], 0x10000) class CoreResampleBoxTest(PillowTestCase): def test_wrong_arguments(self): im = hopper() - for resample in (Image.NEAREST, Image.BOX, Image.BILINEAR, - Image.HAMMING, Image.BICUBIC, Image.LANCZOS): + for resample in ( + Image.NEAREST, + Image.BOX, + Image.BILINEAR, + Image.HAMMING, + Image.BICUBIC, + Image.LANCZOS, + ): im.resize((32, 32), resample, (0, 0, im.width, im.height)) im.resize((32, 32), resample, (20, 20, im.width, im.height)) im.resize((32, 32), resample, (20, 20, 20, 100)) im.resize((32, 32), resample, (20, 20, 100, 20)) - with self.assertRaisesRegex(TypeError, - "must be sequence of length 4"): + with self.assertRaisesRegex(TypeError, "must be sequence of length 4"): im.resize((32, 32), resample, (im.width, im.height)) with self.assertRaisesRegex(ValueError, "can't be negative"): @@ -420,8 +440,7 @@ class CoreResampleBoxTest(PillowTestCase): for y0, y1 in split_range(dst_size[1], ytiles): for x0, x1 in split_range(dst_size[0], xtiles): - box = (x0 * scale[0], y0 * scale[1], - x1 * scale[0], y1 * scale[1]) + box = (x0 * scale[0], y0 * scale[1], x1 * scale[0], y1 * scale[1]) tile = im.resize((x1 - x0, y1 - y0), Image.BICUBIC, box) tiled.paste(tile, (x0, y0)) return tiled @@ -447,8 +466,7 @@ class CoreResampleBoxTest(PillowTestCase): # Image.BOX emulates supersampling (480 / 8 = 60, 360 / 8 = 45) supersampled = im.resize((60, 45), Image.BOX) - with_box = supersampled.resize(dst_size, Image.BICUBIC, - (0, 0, 59.125, 44.125)) + with_box = supersampled.resize(dst_size, Image.BICUBIC, (0, 0, 59.125, 44.125)) without_box = supersampled.resize(dst_size, Image.BICUBIC) # error with box should be much smaller than without @@ -458,7 +476,7 @@ class CoreResampleBoxTest(PillowTestCase): def test_formats(self): for resample in [Image.NEAREST, Image.BILINEAR]: - for mode in ['RGB', 'L', 'RGBA', 'LA', 'I', '']: + for mode in ["RGB", "L", "RGBA", "LA", "I", ""]: im = hopper(mode) box = (20, 20, im.size[0] - 20, im.size[1] - 20) with_box = im.resize((32, 32), resample, box) @@ -480,7 +498,7 @@ class CoreResampleBoxTest(PillowTestCase): self.assertEqual(res.size, size) self.assert_image_equal(res, im.crop(box)) except AssertionError: - print('>>>', size, box) + print(">>>", size, box) raise def test_no_passthrough(self): @@ -500,7 +518,7 @@ class CoreResampleBoxTest(PillowTestCase): # check that the difference at least that much self.assert_image_similar(res, im.crop(box), 20) except AssertionError: - print('>>>', size, box) + print(">>>", size, box) raise def test_skip_horizontal(self): @@ -518,10 +536,9 @@ class CoreResampleBoxTest(PillowTestCase): res = im.resize(size, flt, box) self.assertEqual(res.size, size) # Borders should be slightly different - self.assert_image_similar( - res, im.crop(box).resize(size, flt), 0.4) + self.assert_image_similar(res, im.crop(box).resize(size, flt), 0.4) except AssertionError: - print('>>>', size, box, flt) + print(">>>", size, box, flt) raise def test_skip_vertical(self): @@ -539,8 +556,7 @@ class CoreResampleBoxTest(PillowTestCase): res = im.resize(size, flt, box) self.assertEqual(res.size, size) # Borders should be slightly different - self.assert_image_similar( - res, im.crop(box).resize(size, flt), 0.4) + self.assert_image_similar(res, im.crop(box).resize(size, flt), 0.4) except AssertionError: - print('>>>', size, box, flt) + print(">>>", size, box, flt) raise diff --git a/Tests/test_image_resize.py b/Tests/test_image_resize.py index c47c7317b..aa831faa8 100644 --- a/Tests/test_image_resize.py +++ b/Tests/test_image_resize.py @@ -9,15 +9,24 @@ from PIL import Image class TestImagingCoreResize(PillowTestCase): - def resize(self, im, size, f): # Image class independent version of resize. im.load() return im._new(im.im.resize(size, f)) def test_nearest_mode(self): - for mode in ["1", "P", "L", "I", "F", "RGB", "RGBA", "CMYK", "YCbCr", - "I;16"]: # exotic mode + for mode in [ + "1", + "P", + "L", + "I", + "F", + "RGB", + "RGBA", + "CMYK", + "YCbCr", + "I;16", + ]: # exotic mode im = hopper(mode) r = self.resize(im, (15, 12), Image.NEAREST) self.assertEqual(r.mode, mode) @@ -25,12 +34,15 @@ class TestImagingCoreResize(PillowTestCase): self.assertEqual(r.im.bands, im.im.bands) def test_convolution_modes(self): - self.assertRaises(ValueError, self.resize, hopper("1"), - (15, 12), Image.BILINEAR) - self.assertRaises(ValueError, self.resize, hopper("P"), - (15, 12), Image.BILINEAR) - self.assertRaises(ValueError, self.resize, hopper("I;16"), - (15, 12), Image.BILINEAR) + self.assertRaises( + ValueError, self.resize, hopper("1"), (15, 12), Image.BILINEAR + ) + self.assertRaises( + ValueError, self.resize, hopper("P"), (15, 12), Image.BILINEAR + ) + self.assertRaises( + ValueError, self.resize, hopper("I;16"), (15, 12), Image.BILINEAR + ) for mode in ["L", "I", "F", "RGB", "RGBA", "CMYK", "YCbCr"]: im = hopper(mode) r = self.resize(im, (15, 12), Image.BILINEAR) @@ -39,15 +51,27 @@ class TestImagingCoreResize(PillowTestCase): self.assertEqual(r.im.bands, im.im.bands) def test_reduce_filters(self): - for f in [Image.NEAREST, Image.BOX, Image.BILINEAR, - Image.HAMMING, Image.BICUBIC, Image.LANCZOS]: + for f in [ + Image.NEAREST, + Image.BOX, + Image.BILINEAR, + Image.HAMMING, + Image.BICUBIC, + Image.LANCZOS, + ]: r = self.resize(hopper("RGB"), (15, 12), f) self.assertEqual(r.mode, "RGB") self.assertEqual(r.size, (15, 12)) def test_enlarge_filters(self): - for f in [Image.NEAREST, Image.BOX, Image.BILINEAR, - Image.HAMMING, Image.BICUBIC, Image.LANCZOS]: + for f in [ + Image.NEAREST, + Image.BOX, + Image.BILINEAR, + Image.HAMMING, + Image.BICUBIC, + Image.LANCZOS, + ]: r = self.resize(hopper("RGB"), (212, 195), f) self.assertEqual(r.mode, "RGB") self.assertEqual(r.size, (212, 195)) @@ -60,24 +84,29 @@ class TestImagingCoreResize(PillowTestCase): # an endianness issues. samples = { - 'blank': Image.new('L', (2, 2), 0), - 'filled': Image.new('L', (2, 2), 255), - 'dirty': Image.new('L', (2, 2), 0), + "blank": Image.new("L", (2, 2), 0), + "filled": Image.new("L", (2, 2), 255), + "dirty": Image.new("L", (2, 2), 0), } - samples['dirty'].putpixel((1, 1), 128) + samples["dirty"].putpixel((1, 1), 128) - for f in [Image.NEAREST, Image.BOX, Image.BILINEAR, - Image.HAMMING, Image.BICUBIC, Image.LANCZOS]: + for f in [ + Image.NEAREST, + Image.BOX, + Image.BILINEAR, + Image.HAMMING, + Image.BICUBIC, + Image.LANCZOS, + ]: # samples resized with current filter references = { - name: self.resize(ch, (4, 4), f) - for name, ch in samples.items() + name: self.resize(ch, (4, 4), f) for name, ch in samples.items() } for mode, channels_set in [ - ('RGB', ('blank', 'filled', 'dirty')), - ('RGBA', ('blank', 'blank', 'filled', 'dirty')), - ('LA', ('filled', 'dirty')), + ("RGB", ("blank", "filled", "dirty")), + ("RGBA", ("blank", "blank", "filled", "dirty")), + ("LA", ("filled", "dirty")), ]: for channels in set(permutations(channels_set)): # compile image from different channels permutations @@ -90,9 +119,15 @@ class TestImagingCoreResize(PillowTestCase): self.assert_image_equal(ch, references[channels[i]]) def test_enlarge_zero(self): - for f in [Image.NEAREST, Image.BOX, Image.BILINEAR, - Image.HAMMING, Image.BICUBIC, Image.LANCZOS]: - r = self.resize(Image.new('RGB', (0, 0), "white"), (212, 195), f) + for f in [ + Image.NEAREST, + Image.BOX, + Image.BILINEAR, + Image.HAMMING, + Image.BICUBIC, + Image.LANCZOS, + ]: + r = self.resize(Image.new("RGB", (0, 0), "white"), (212, 195), f) self.assertEqual(r.mode, "RGB") self.assertEqual(r.size, (212, 195)) self.assertEqual(r.getdata()[0], (0, 0, 0)) @@ -102,12 +137,12 @@ class TestImagingCoreResize(PillowTestCase): class TestImageResize(PillowTestCase): - def test_resize(self): def resize(mode, size): out = hopper(mode).resize(size) self.assertEqual(out.mode, mode) self.assertEqual(out.size, size) + for mode in "1", "P", "L", "RGB", "I", "F": resize(mode, (112, 103)) resize(mode, (188, 214)) diff --git a/Tests/test_image_rotate.py b/Tests/test_image_rotate.py index 05043cd06..a2363b6bc 100644 --- a/Tests/test_image_rotate.py +++ b/Tests/test_image_rotate.py @@ -3,7 +3,6 @@ from PIL import Image class TestImageRotate(PillowTestCase): - def rotate(self, im, mode, angle, center=None, translate=None): out = im.rotate(angle, center=center, translate=translate) self.assertEqual(out.mode, mode) @@ -24,12 +23,12 @@ class TestImageRotate(PillowTestCase): def test_angle(self): for angle in (0, 90, 180, 270): - im = Image.open('Tests/images/test-card.png') + im = Image.open("Tests/images/test-card.png") self.rotate(im, im.mode, angle) def test_zero(self): for angle in (0, 45, 90, 180, 270): - im = Image.new('RGB', (0, 0)) + im = Image.new("RGB", (0, 0)) self.rotate(im, im.mode, angle) def test_resample(self): @@ -38,18 +37,20 @@ class TestImageRotate(PillowTestCase): # >>> im = im.rotate(45, resample=Image.BICUBIC, expand=True) # >>> im.save('Tests/images/hopper_45.png') - target = Image.open('Tests/images/hopper_45.png') - for (resample, epsilon) in ((Image.NEAREST, 10), - (Image.BILINEAR, 5), - (Image.BICUBIC, 0)): + target = Image.open("Tests/images/hopper_45.png") + for (resample, epsilon) in ( + (Image.NEAREST, 10), + (Image.BILINEAR, 5), + (Image.BICUBIC, 0), + ): im = hopper() im = im.rotate(45, resample=resample, expand=True) self.assert_image_similar(im, target, epsilon) def test_center_0(self): im = hopper() - target = Image.open('Tests/images/hopper_45.png') - target_origin = target.size[1]/2 + target = Image.open("Tests/images/hopper_45.png") + target_origin = target.size[1] / 2 target = target.crop((0, target_origin, 128, target_origin + 128)) im = im.rotate(45, center=(0, 0), resample=Image.BICUBIC) @@ -58,7 +59,7 @@ class TestImageRotate(PillowTestCase): def test_center_14(self): im = hopper() - target = Image.open('Tests/images/hopper_45.png') + target = Image.open("Tests/images/hopper_45.png") target_origin = target.size[1] / 2 - 14 target = target.crop((6, target_origin, 128 + 6, target_origin + 128)) @@ -68,10 +69,11 @@ class TestImageRotate(PillowTestCase): def test_translate(self): im = hopper() - target = Image.open('Tests/images/hopper_45.png') + target = Image.open("Tests/images/hopper_45.png") target_origin = (target.size[1] / 2 - 64) - 5 - target = target.crop((target_origin, target_origin, - target_origin + 128, target_origin + 128)) + target = target.crop( + (target_origin, target_origin, target_origin + 128, target_origin + 128) + ) im = im.rotate(45, translate=(5, 5), resample=Image.BICUBIC) @@ -82,44 +84,43 @@ class TestImageRotate(PillowTestCase): # resulting image should be black for angle in (90, 180, 270): im = hopper().rotate(angle, center=(-1, -1)) - self.assert_image_equal(im, Image.new('RGB', im.size, 'black')) + self.assert_image_equal(im, Image.new("RGB", im.size, "black")) def test_fastpath_translate(self): # if we post-translate by -128 # resulting image should be black for angle in (0, 90, 180, 270): im = hopper().rotate(angle, translate=(-128, -128)) - self.assert_image_equal(im, Image.new('RGB', im.size, 'black')) + self.assert_image_equal(im, Image.new("RGB", im.size, "black")) def test_center(self): im = hopper() self.rotate(im, im.mode, 45, center=(0, 0)) - self.rotate(im, im.mode, 45, translate=(im.size[0]/2, 0)) - self.rotate(im, im.mode, 45, center=(0, 0), - translate=(im.size[0]/2, 0)) + self.rotate(im, im.mode, 45, translate=(im.size[0] / 2, 0)) + self.rotate(im, im.mode, 45, center=(0, 0), translate=(im.size[0] / 2, 0)) def test_rotate_no_fill(self): - im = Image.new('RGB', (100, 100), 'green') - target = Image.open('Tests/images/rotate_45_no_fill.png') + im = Image.new("RGB", (100, 100), "green") + target = Image.open("Tests/images/rotate_45_no_fill.png") im = im.rotate(45) self.assert_image_equal(im, target) def test_rotate_with_fill(self): - im = Image.new('RGB', (100, 100), 'green') - target = Image.open('Tests/images/rotate_45_with_fill.png') - im = im.rotate(45, fillcolor='white') + im = Image.new("RGB", (100, 100), "green") + target = Image.open("Tests/images/rotate_45_with_fill.png") + im = im.rotate(45, fillcolor="white") self.assert_image_equal(im, target) def test_alpha_rotate_no_fill(self): # Alpha images are handled differently internally - im = Image.new('RGBA', (10, 10), 'green') + im = Image.new("RGBA", (10, 10), "green") im = im.rotate(45, expand=1) corner = im.getpixel((0, 0)) self.assertEqual(corner, (0, 0, 0, 0)) def test_alpha_rotate_with_fill(self): # Alpha images are handled differently internally - im = Image.new('RGBA', (10, 10), 'green') + im = Image.new("RGBA", (10, 10), "green") im = im.rotate(45, expand=1, fillcolor=(255, 0, 0, 255)) corner = im.getpixel((0, 0)) self.assertEqual(corner, (255, 0, 0, 255)) diff --git a/Tests/test_image_split.py b/Tests/test_image_split.py index 2d97dabc2..e1bc17be2 100644 --- a/Tests/test_image_split.py +++ b/Tests/test_image_split.py @@ -4,33 +4,35 @@ from PIL import Image class TestImageSplit(PillowTestCase): - def test_split(self): def split(mode): layers = hopper(mode).split() return [(i.mode, i.size[0], i.size[1]) for i in layers] - self.assertEqual(split("1"), [('1', 128, 128)]) - self.assertEqual(split("L"), [('L', 128, 128)]) - self.assertEqual(split("I"), [('I', 128, 128)]) - self.assertEqual(split("F"), [('F', 128, 128)]) - self.assertEqual(split("P"), [('P', 128, 128)]) + + self.assertEqual(split("1"), [("1", 128, 128)]) + self.assertEqual(split("L"), [("L", 128, 128)]) + self.assertEqual(split("I"), [("I", 128, 128)]) + self.assertEqual(split("F"), [("F", 128, 128)]) + self.assertEqual(split("P"), [("P", 128, 128)]) self.assertEqual( - split("RGB"), [('L', 128, 128), ('L', 128, 128), ('L', 128, 128)]) + split("RGB"), [("L", 128, 128), ("L", 128, 128), ("L", 128, 128)] + ) self.assertEqual( split("RGBA"), - [('L', 128, 128), ('L', 128, 128), - ('L', 128, 128), ('L', 128, 128)]) + [("L", 128, 128), ("L", 128, 128), ("L", 128, 128), ("L", 128, 128)], + ) self.assertEqual( split("CMYK"), - [('L', 128, 128), ('L', 128, 128), - ('L', 128, 128), ('L', 128, 128)]) + [("L", 128, 128), ("L", 128, 128), ("L", 128, 128), ("L", 128, 128)], + ) self.assertEqual( - split("YCbCr"), - [('L', 128, 128), ('L', 128, 128), ('L', 128, 128)]) + split("YCbCr"), [("L", 128, 128), ("L", 128, 128), ("L", 128, 128)] + ) def test_split_merge(self): def split_merge(mode): return Image.merge(mode, hopper(mode).split()) + self.assert_image_equal(hopper("1"), split_merge("1")) self.assert_image_equal(hopper("L"), split_merge("L")) self.assert_image_equal(hopper("I"), split_merge("I")) @@ -44,7 +46,7 @@ class TestImageSplit(PillowTestCase): def test_split_open(self): codecs = dir(Image.core) - if 'zip_encoder' in codecs: + if "zip_encoder" in codecs: test_file = self.tempfile("temp.png") else: test_file = self.tempfile("temp.pcx") @@ -53,9 +55,10 @@ class TestImageSplit(PillowTestCase): hopper(mode).save(test_file) im = Image.open(test_file) return len(im.split()) + self.assertEqual(split_open("1"), 1) self.assertEqual(split_open("L"), 1) self.assertEqual(split_open("P"), 1) self.assertEqual(split_open("RGB"), 3) - if 'zip_encoder' in codecs: + if "zip_encoder" in codecs: self.assertEqual(split_open("RGBA"), 4) diff --git a/Tests/test_image_thumbnail.py b/Tests/test_image_thumbnail.py index fbadf50cf..a53f31461 100644 --- a/Tests/test_image_thumbnail.py +++ b/Tests/test_image_thumbnail.py @@ -3,7 +3,6 @@ from PIL import Image class TestImageThumbnail(PillowTestCase): - def test_sanity(self): im = hopper() diff --git a/Tests/test_image_tobitmap.py b/Tests/test_image_tobitmap.py index c5c06ba01..8af565f5e 100644 --- a/Tests/test_image_tobitmap.py +++ b/Tests/test_image_tobitmap.py @@ -2,7 +2,6 @@ from .helper import PillowTestCase, hopper, fromstring class TestImageToBitmap(PillowTestCase): - def test_sanity(self): self.assertRaises(ValueError, lambda: hopper().tobitmap()) diff --git a/Tests/test_image_tobytes.py b/Tests/test_image_tobytes.py index 2bfee2da3..d21ef7f6f 100644 --- a/Tests/test_image_tobytes.py +++ b/Tests/test_image_tobytes.py @@ -2,7 +2,6 @@ from .helper import PillowTestCase, hopper class TestImageToBytes(PillowTestCase): - def test_sanity(self): data = hopper().tobytes() self.assertIsInstance(data, bytes) diff --git a/Tests/test_image_transform.py b/Tests/test_image_transform.py index ae1cf6e3d..feedf366e 100644 --- a/Tests/test_image_transform.py +++ b/Tests/test_image_transform.py @@ -6,7 +6,6 @@ from PIL import Image class TestImageTransform(PillowTestCase): - def test_sanity(self): from PIL import ImageTransform @@ -24,54 +23,61 @@ class TestImageTransform(PillowTestCase): im.transform((100, 100), transform) def test_extent(self): - im = hopper('RGB') + im = hopper("RGB") (w, h) = im.size + # fmt: off transformed = im.transform(im.size, Image.EXTENT, (0, 0, w//2, h//2), # ul -> lr Image.BILINEAR) + # fmt: on - scaled = im.resize((w*2, h*2), Image.BILINEAR).crop((0, 0, w, h)) + scaled = im.resize((w * 2, h * 2), Image.BILINEAR).crop((0, 0, w, h)) # undone -- precision? self.assert_image_similar(transformed, scaled, 23) def test_quad(self): # one simple quad transform, equivalent to scale & crop upper left quad - im = hopper('RGB') + im = hopper("RGB") (w, h) = im.size + # fmt: off transformed = im.transform(im.size, Image.QUAD, (0, 0, 0, h//2, # ul -> ccw around quad: w//2, h//2, w//2, 0), Image.BILINEAR) + # fmt: on - scaled = im.transform((w, h), Image.AFFINE, - (.5, 0, 0, 0, .5, 0), - Image.BILINEAR) + scaled = im.transform( + (w, h), Image.AFFINE, (0.5, 0, 0, 0, 0.5, 0), Image.BILINEAR + ) self.assert_image_equal(transformed, scaled) def test_fill(self): for mode, pixel in [ - ['RGB', (255, 0, 0)], - ['RGBA', (255, 0, 0, 255)], - ['LA', (76, 0)] + ["RGB", (255, 0, 0)], + ["RGBA", (255, 0, 0, 255)], + ["LA", (76, 0)], ]: im = hopper(mode) (w, h) = im.size - transformed = im.transform(im.size, Image.EXTENT, - (0, 0, - w*2, h*2), - Image.BILINEAR, - fillcolor='red') + transformed = im.transform( + im.size, + Image.EXTENT, + (0, 0, w * 2, h * 2), + Image.BILINEAR, + fillcolor="red", + ) - self.assertEqual(transformed.getpixel((w-1, h-1)), pixel) + self.assertEqual(transformed.getpixel((w - 1, h - 1)), pixel) def test_mesh(self): # this should be a checkerboard of halfsized hoppers in ul, lr - im = hopper('RGBA') + im = hopper("RGBA") (w, h) = im.size + # fmt: off transformed = im.transform(im.size, Image.MESH, [((0, 0, w//2, h//2), # box (0, 0, 0, h, @@ -80,54 +86,50 @@ class TestImageTransform(PillowTestCase): (0, 0, 0, h, w, h, w, 0))], # ul -> ccw around quad Image.BILINEAR) + # fmt: on - scaled = im.transform((w//2, h//2), Image.AFFINE, - (2, 0, 0, 0, 2, 0), - Image.BILINEAR) + scaled = im.transform( + (w // 2, h // 2), Image.AFFINE, (2, 0, 0, 0, 2, 0), Image.BILINEAR + ) - checker = Image.new('RGBA', im.size) + checker = Image.new("RGBA", im.size) checker.paste(scaled, (0, 0)) - checker.paste(scaled, (w//2, h//2)) + checker.paste(scaled, (w // 2, h // 2)) self.assert_image_equal(transformed, checker) # now, check to see that the extra area is (0, 0, 0, 0) - blank = Image.new('RGBA', (w//2, h//2), (0, 0, 0, 0)) + blank = Image.new("RGBA", (w // 2, h // 2), (0, 0, 0, 0)) - self.assert_image_equal(blank, transformed.crop((w//2, 0, w, h//2))) - self.assert_image_equal(blank, transformed.crop((0, h//2, w//2, h))) + self.assert_image_equal(blank, transformed.crop((w // 2, 0, w, h // 2))) + self.assert_image_equal(blank, transformed.crop((0, h // 2, w // 2, h))) def _test_alpha_premult(self, op): # create image with half white, half black, # with the black half transparent. # do op, # there should be no darkness in the white section. - im = Image.new('RGBA', (10, 10), (0, 0, 0, 0)) - im2 = Image.new('RGBA', (5, 10), (255, 255, 255, 255)) + im = Image.new("RGBA", (10, 10), (0, 0, 0, 0)) + im2 = Image.new("RGBA", (5, 10), (255, 255, 255, 255)) im.paste(im2, (0, 0)) im = op(im, (40, 10)) - im_background = Image.new('RGB', (40, 10), (255, 255, 255)) + im_background = Image.new("RGB", (40, 10), (255, 255, 255)) im_background.paste(im, (0, 0), im) hist = im_background.histogram() - self.assertEqual(40*10, hist[-1]) + self.assertEqual(40 * 10, hist[-1]) def test_alpha_premult_resize(self): - def op(im, sz): return im.resize(sz, Image.BILINEAR) self._test_alpha_premult(op) def test_alpha_premult_transform(self): - def op(im, sz): (w, h) = im.size - return im.transform(sz, Image.EXTENT, - (0, 0, - w, h), - Image.BILINEAR) + return im.transform(sz, Image.EXTENT, (0, 0, w, h), Image.BILINEAR) self._test_alpha_premult(op) @@ -146,10 +148,7 @@ class TestImageTransform(PillowTestCase): # Running by default, but I'd totally understand not doing it in # the future - pattern = [ - Image.new('RGBA', (1024, 1024), (a, a, a, a)) - for a in range(1, 65) - ] + pattern = [Image.new("RGBA", (1024, 1024), (a, a, a, a)) for a in range(1, 65)] # Yeah. Watch some JIT optimize this out. pattern = None # noqa: F841 @@ -160,22 +159,41 @@ class TestImageTransform(PillowTestCase): im = hopper() self.assertRaises(ValueError, im.transform, (100, 100), None) + def test_unknown_resampling_filter(self): + im = hopper() + (w, h) = im.size + for resample in (Image.BOX, "unknown"): + self.assertRaises( + ValueError, + im.transform, + (100, 100), + Image.EXTENT, + (0, 0, w, h), + resample, + ) + class TestImageTransformAffine(PillowTestCase): transform = Image.AFFINE def _test_image(self): - im = hopper('RGB') + im = hopper("RGB") return im.crop((10, 20, im.width - 10, im.height - 20)) def _test_rotate(self, deg, transpose): im = self._test_image() - angle = - math.radians(deg) + angle = -math.radians(deg) matrix = [ - round(math.cos(angle), 15), round(math.sin(angle), 15), 0.0, - round(-math.sin(angle), 15), round(math.cos(angle), 15), 0.0, - 0, 0] + round(math.cos(angle), 15), + round(math.sin(angle), 15), + 0.0, + round(-math.sin(angle), 15), + round(math.cos(angle), 15), + 0.0, + 0, + 0, + ] matrix[2] = (1 - matrix[0] - matrix[1]) * im.width / 2 matrix[5] = (1 - matrix[3] - matrix[4]) * im.height / 2 @@ -185,8 +203,9 @@ class TestImageTransformAffine(PillowTestCase): transposed = im for resample in [Image.NEAREST, Image.BILINEAR, Image.BICUBIC]: - transformed = im.transform(transposed.size, self.transform, - matrix, resample) + transformed = im.transform( + transposed.size, self.transform, matrix, resample + ) self.assert_image_equal(transposed, transformed) def test_rotate_0_deg(self): @@ -205,21 +224,18 @@ class TestImageTransformAffine(PillowTestCase): im = self._test_image() size_up = int(round(im.width * scale)), int(round(im.height * scale)) - matrix_up = [ - 1 / scale, 0, 0, - 0, 1 / scale, 0, - 0, 0] - matrix_down = [ - scale, 0, 0, - 0, scale, 0, - 0, 0] + matrix_up = [1 / scale, 0, 0, 0, 1 / scale, 0, 0, 0] + matrix_down = [scale, 0, 0, 0, scale, 0, 0, 0] - for resample, epsilon in [(Image.NEAREST, 0), - (Image.BILINEAR, 2), (Image.BICUBIC, 1)]: - transformed = im.transform( - size_up, self.transform, matrix_up, resample) + for resample, epsilon in [ + (Image.NEAREST, 0), + (Image.BILINEAR, 2), + (Image.BICUBIC, 1), + ]: + transformed = im.transform(size_up, self.transform, matrix_up, resample) transformed = transformed.transform( - im.size, self.transform, matrix_down, resample) + im.size, self.transform, matrix_down, resample + ) self.assert_image_similar(transformed, im, epsilon * epsilonscale) def test_resize_1_1x(self): @@ -241,28 +257,25 @@ class TestImageTransformAffine(PillowTestCase): im = self._test_image() size_up = int(round(im.width + x)), int(round(im.height + y)) - matrix_up = [ - 1, 0, -x, - 0, 1, -y, - 0, 0] - matrix_down = [ - 1, 0, x, - 0, 1, y, - 0, 0] + matrix_up = [1, 0, -x, 0, 1, -y, 0, 0] + matrix_down = [1, 0, x, 0, 1, y, 0, 0] - for resample, epsilon in [(Image.NEAREST, 0), - (Image.BILINEAR, 1.5), (Image.BICUBIC, 1)]: - transformed = im.transform( - size_up, self.transform, matrix_up, resample) + for resample, epsilon in [ + (Image.NEAREST, 0), + (Image.BILINEAR, 1.5), + (Image.BICUBIC, 1), + ]: + transformed = im.transform(size_up, self.transform, matrix_up, resample) transformed = transformed.transform( - im.size, self.transform, matrix_down, resample) + im.size, self.transform, matrix_down, resample + ) self.assert_image_similar(transformed, im, epsilon * epsilonscale) def test_translate_0_1(self): - self._test_translate(.1, 0, 3.7) + self._test_translate(0.1, 0, 3.7) def test_translate_0_6(self): - self._test_translate(.6, 0, 9.1) + self._test_translate(0.6, 0, 9.1) def test_translate_50(self): self._test_translate(50, 50, 0) diff --git a/Tests/test_image_transpose.py b/Tests/test_image_transpose.py index 1cce5e986..021e174f2 100644 --- a/Tests/test_image_transpose.py +++ b/Tests/test_image_transpose.py @@ -1,15 +1,23 @@ 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) +from PIL.Image import ( + FLIP_LEFT_RIGHT, + FLIP_TOP_BOTTOM, + ROTATE_90, + ROTATE_180, + ROTATE_270, + TRANSPOSE, + TRANSVERSE, +) class TestImageTranspose(PillowTestCase): - hopper = {mode: helper.hopper(mode).crop((0, 0, 121, 127)).copy() for mode in [ - 'L', 'RGB', 'I;16', 'I;16L', 'I;16B' - ]} + hopper = { + mode: helper.hopper(mode).crop((0, 0, 121, 127)).copy() + for mode in ["L", "RGB", "I;16", "I;16L", "I;16B"] + } def test_flip_left_right(self): def transpose(mode): @@ -19,10 +27,10 @@ class TestImageTranspose(PillowTestCase): self.assertEqual(out.size, im.size) x, y = im.size - self.assertEqual(im.getpixel((1, 1)), out.getpixel((x-2, 1))) - self.assertEqual(im.getpixel((x-2, 1)), out.getpixel((1, 1))) - self.assertEqual(im.getpixel((1, y-2)), out.getpixel((x-2, y-2))) - self.assertEqual(im.getpixel((x-2, y-2)), out.getpixel((1, y-2))) + self.assertEqual(im.getpixel((1, 1)), out.getpixel((x - 2, 1))) + self.assertEqual(im.getpixel((x - 2, 1)), out.getpixel((1, 1))) + self.assertEqual(im.getpixel((1, y - 2)), out.getpixel((x - 2, y - 2))) + self.assertEqual(im.getpixel((x - 2, y - 2)), out.getpixel((1, y - 2))) for mode in self.hopper: transpose(mode) @@ -35,10 +43,10 @@ class TestImageTranspose(PillowTestCase): self.assertEqual(out.size, im.size) x, y = im.size - self.assertEqual(im.getpixel((1, 1)), out.getpixel((1, y-2))) - self.assertEqual(im.getpixel((x-2, 1)), out.getpixel((x-2, y-2))) - self.assertEqual(im.getpixel((1, y-2)), out.getpixel((1, 1))) - self.assertEqual(im.getpixel((x-2, y-2)), out.getpixel((x-2, 1))) + self.assertEqual(im.getpixel((1, 1)), out.getpixel((1, y - 2))) + self.assertEqual(im.getpixel((x - 2, 1)), out.getpixel((x - 2, y - 2))) + self.assertEqual(im.getpixel((1, y - 2)), out.getpixel((1, 1))) + self.assertEqual(im.getpixel((x - 2, y - 2)), out.getpixel((x - 2, 1))) for mode in self.hopper: transpose(mode) @@ -51,10 +59,10 @@ class TestImageTranspose(PillowTestCase): self.assertEqual(out.size, im.size[::-1]) x, y = im.size - self.assertEqual(im.getpixel((1, 1)), out.getpixel((1, x-2))) - self.assertEqual(im.getpixel((x-2, 1)), out.getpixel((1, 1))) - self.assertEqual(im.getpixel((1, y-2)), out.getpixel((y-2, x-2))) - self.assertEqual(im.getpixel((x-2, y-2)), out.getpixel((y-2, 1))) + self.assertEqual(im.getpixel((1, 1)), out.getpixel((1, x - 2))) + self.assertEqual(im.getpixel((x - 2, 1)), out.getpixel((1, 1))) + self.assertEqual(im.getpixel((1, y - 2)), out.getpixel((y - 2, x - 2))) + self.assertEqual(im.getpixel((x - 2, y - 2)), out.getpixel((y - 2, 1))) for mode in self.hopper: transpose(mode) @@ -67,10 +75,10 @@ class TestImageTranspose(PillowTestCase): self.assertEqual(out.size, im.size) x, y = im.size - self.assertEqual(im.getpixel((1, 1)), out.getpixel((x-2, y-2))) - self.assertEqual(im.getpixel((x-2, 1)), out.getpixel((1, y-2))) - self.assertEqual(im.getpixel((1, y-2)), out.getpixel((x-2, 1))) - self.assertEqual(im.getpixel((x-2, y-2)), out.getpixel((1, 1))) + self.assertEqual(im.getpixel((1, 1)), out.getpixel((x - 2, y - 2))) + self.assertEqual(im.getpixel((x - 2, 1)), out.getpixel((1, y - 2))) + self.assertEqual(im.getpixel((1, y - 2)), out.getpixel((x - 2, 1))) + self.assertEqual(im.getpixel((x - 2, y - 2)), out.getpixel((1, 1))) for mode in self.hopper: transpose(mode) @@ -83,10 +91,10 @@ class TestImageTranspose(PillowTestCase): self.assertEqual(out.size, im.size[::-1]) x, y = im.size - self.assertEqual(im.getpixel((1, 1)), out.getpixel((y-2, 1))) - self.assertEqual(im.getpixel((x-2, 1)), out.getpixel((y-2, x-2))) - self.assertEqual(im.getpixel((1, y-2)), out.getpixel((1, 1))) - self.assertEqual(im.getpixel((x-2, y-2)), out.getpixel((1, x-2))) + self.assertEqual(im.getpixel((1, 1)), out.getpixel((y - 2, 1))) + self.assertEqual(im.getpixel((x - 2, 1)), out.getpixel((y - 2, x - 2))) + self.assertEqual(im.getpixel((1, y - 2)), out.getpixel((1, 1))) + self.assertEqual(im.getpixel((x - 2, y - 2)), out.getpixel((1, x - 2))) for mode in self.hopper: transpose(mode) @@ -100,9 +108,9 @@ class TestImageTranspose(PillowTestCase): x, y = im.size self.assertEqual(im.getpixel((1, 1)), out.getpixel((1, 1))) - self.assertEqual(im.getpixel((x-2, 1)), out.getpixel((1, x-2))) - self.assertEqual(im.getpixel((1, y-2)), out.getpixel((y-2, 1))) - self.assertEqual(im.getpixel((x-2, y-2)), out.getpixel((y-2, x-2))) + self.assertEqual(im.getpixel((x - 2, 1)), out.getpixel((1, x - 2))) + self.assertEqual(im.getpixel((1, y - 2)), out.getpixel((y - 2, 1))) + self.assertEqual(im.getpixel((x - 2, y - 2)), out.getpixel((y - 2, x - 2))) for mode in self.hopper: transpose(mode) @@ -115,10 +123,10 @@ class TestImageTranspose(PillowTestCase): self.assertEqual(out.size, im.size[::-1]) x, y = im.size - self.assertEqual(im.getpixel((1, 1)), out.getpixel((y-2, x-2))) - self.assertEqual(im.getpixel((x-2, 1)), out.getpixel((y-2, 1))) - self.assertEqual(im.getpixel((1, y-2)), out.getpixel((1, x-2))) - self.assertEqual(im.getpixel((x-2, y-2)), out.getpixel((1, 1))) + self.assertEqual(im.getpixel((1, 1)), out.getpixel((y - 2, x - 2))) + self.assertEqual(im.getpixel((x - 2, 1)), out.getpixel((y - 2, 1))) + self.assertEqual(im.getpixel((1, y - 2)), out.getpixel((1, x - 2))) + self.assertEqual(im.getpixel((x - 2, y - 2)), out.getpixel((1, 1))) for mode in self.hopper: transpose(mode) @@ -130,19 +138,22 @@ class TestImageTranspose(PillowTestCase): def transpose(first, second): return im.transpose(first).transpose(second) - self.assert_image_equal( - im, transpose(FLIP_LEFT_RIGHT, FLIP_LEFT_RIGHT)) - self.assert_image_equal( - im, transpose(FLIP_TOP_BOTTOM, FLIP_TOP_BOTTOM)) + self.assert_image_equal(im, transpose(FLIP_LEFT_RIGHT, FLIP_LEFT_RIGHT)) + self.assert_image_equal(im, transpose(FLIP_TOP_BOTTOM, FLIP_TOP_BOTTOM)) self.assert_image_equal(im, transpose(ROTATE_90, ROTATE_270)) self.assert_image_equal(im, transpose(ROTATE_180, ROTATE_180)) self.assert_image_equal( - im.transpose(TRANSPOSE), transpose(ROTATE_90, FLIP_TOP_BOTTOM)) + im.transpose(TRANSPOSE), transpose(ROTATE_90, FLIP_TOP_BOTTOM) + ) self.assert_image_equal( - im.transpose(TRANSPOSE), transpose(ROTATE_270, FLIP_LEFT_RIGHT)) + im.transpose(TRANSPOSE), transpose(ROTATE_270, FLIP_LEFT_RIGHT) + ) self.assert_image_equal( - im.transpose(TRANSVERSE), transpose(ROTATE_90, FLIP_LEFT_RIGHT)) + im.transpose(TRANSVERSE), transpose(ROTATE_90, FLIP_LEFT_RIGHT) + ) self.assert_image_equal( - im.transpose(TRANSVERSE), transpose(ROTATE_270, FLIP_TOP_BOTTOM)) + im.transpose(TRANSVERSE), transpose(ROTATE_270, FLIP_TOP_BOTTOM) + ) self.assert_image_equal( - im.transpose(TRANSVERSE), transpose(ROTATE_180, TRANSPOSE)) + im.transpose(TRANSVERSE), transpose(ROTATE_180, TRANSPOSE) + ) diff --git a/Tests/test_imagechops.py b/Tests/test_imagechops.py index 8aa784cdd..76ca397df 100644 --- a/Tests/test_imagechops.py +++ b/Tests/test_imagechops.py @@ -15,7 +15,6 @@ GREY = 128 class TestImageChops(PillowTestCase): - def test_sanity(self): im = hopper("L") @@ -264,11 +263,12 @@ class TestImageChops(PillowTestCase): # Assert self.assertEqual(new.getbbox(), (0, 45, 100, 96)) self.assertEqual(new.getpixel((50, 50)), BLACK) - self.assertEqual(new.getpixel((50+xoffset, 50+yoffset)), DARK_GREEN) + self.assertEqual(new.getpixel((50 + xoffset, 50 + yoffset)), DARK_GREEN) # Test no yoffset - self.assertEqual(ImageChops.offset(im, xoffset), - ImageChops.offset(im, xoffset, xoffset)) + self.assertEqual( + ImageChops.offset(im, xoffset), ImageChops.offset(im, xoffset, xoffset) + ) def test_screen(self): # Arrange @@ -343,7 +343,6 @@ class TestImageChops(PillowTestCase): self.assertEqual(new.getpixel((50, 50)), (241, 167, 127)) def test_logical(self): - def table(op, a, b): out = [] for x in (a, b): @@ -353,23 +352,14 @@ class TestImageChops(PillowTestCase): out.append(op(imx, imy).getpixel((0, 0))) return tuple(out) - self.assertEqual( - table(ImageChops.logical_and, 0, 1), (0, 0, 0, 255)) - self.assertEqual( - table(ImageChops.logical_or, 0, 1), (0, 255, 255, 255)) - self.assertEqual( - table(ImageChops.logical_xor, 0, 1), (0, 255, 255, 0)) + self.assertEqual(table(ImageChops.logical_and, 0, 1), (0, 0, 0, 255)) + self.assertEqual(table(ImageChops.logical_or, 0, 1), (0, 255, 255, 255)) + self.assertEqual(table(ImageChops.logical_xor, 0, 1), (0, 255, 255, 0)) - self.assertEqual( - table(ImageChops.logical_and, 0, 128), (0, 0, 0, 255)) - self.assertEqual( - table(ImageChops.logical_or, 0, 128), (0, 255, 255, 255)) - self.assertEqual( - table(ImageChops.logical_xor, 0, 128), (0, 255, 255, 0)) + self.assertEqual(table(ImageChops.logical_and, 0, 128), (0, 0, 0, 255)) + self.assertEqual(table(ImageChops.logical_or, 0, 128), (0, 255, 255, 255)) + self.assertEqual(table(ImageChops.logical_xor, 0, 128), (0, 255, 255, 0)) - self.assertEqual( - table(ImageChops.logical_and, 0, 255), (0, 0, 0, 255)) - self.assertEqual( - table(ImageChops.logical_or, 0, 255), (0, 255, 255, 255)) - self.assertEqual( - table(ImageChops.logical_xor, 0, 255), (0, 255, 255, 0)) + self.assertEqual(table(ImageChops.logical_and, 0, 255), (0, 0, 0, 255)) + self.assertEqual(table(ImageChops.logical_or, 0, 255), (0, 255, 255, 255)) + self.assertEqual(table(ImageChops.logical_xor, 0, 255), (0, 255, 255, 0)) diff --git a/Tests/test_imagecms.py b/Tests/test_imagecms.py index de140710f..678af4494 100644 --- a/Tests/test_imagecms.py +++ b/Tests/test_imagecms.py @@ -9,6 +9,7 @@ import os try: from PIL import ImageCms from PIL.ImageCms import ImageCmsProfile + ImageCms.core.profile_open except ImportError: # Skipped via setUp() @@ -20,10 +21,10 @@ HAVE_PROFILE = os.path.exists(SRGB) class TestImageCms(PillowTestCase): - def setUp(self): try: from PIL import ImageCms + # need to hit getattr to trigger the delayed import error ImageCms.core.profile_open except ImportError as v: @@ -39,7 +40,7 @@ class TestImageCms(PillowTestCase): # this mostly follows the cms_test outline. v = ImageCms.versions() # should return four strings - self.assertEqual(v[0], '1.0.0 pil') + self.assertEqual(v[0], "1.0.0 pil") self.assertEqual(list(map(type, v)), [str, str, str, str]) # internal version number @@ -82,57 +83,70 @@ class TestImageCms(PillowTestCase): # get profile information for file self.assertEqual( ImageCms.getProfileName(SRGB).strip(), - 'IEC 61966-2-1 Default RGB Colour Space - sRGB') + "IEC 61966-2-1 Default RGB Colour Space - sRGB", + ) def test_info(self): self.skip_missing() self.assertEqual( - ImageCms.getProfileInfo(SRGB).splitlines(), [ - 'sRGB IEC61966-2-1 black scaled', '', - 'Copyright International Color Consortium, 2009', '']) + ImageCms.getProfileInfo(SRGB).splitlines(), + [ + "sRGB IEC61966-2-1 black scaled", + "", + "Copyright International Color Consortium, 2009", + "", + ], + ) def test_copyright(self): self.skip_missing() self.assertEqual( ImageCms.getProfileCopyright(SRGB).strip(), - 'Copyright International Color Consortium, 2009') + "Copyright International Color Consortium, 2009", + ) def test_manufacturer(self): self.skip_missing() - self.assertEqual( - ImageCms.getProfileManufacturer(SRGB).strip(), - '') + self.assertEqual(ImageCms.getProfileManufacturer(SRGB).strip(), "") def test_model(self): self.skip_missing() self.assertEqual( ImageCms.getProfileModel(SRGB).strip(), - 'IEC 61966-2-1 Default RGB Colour Space - sRGB') + "IEC 61966-2-1 Default RGB Colour Space - sRGB", + ) def test_description(self): self.skip_missing() self.assertEqual( ImageCms.getProfileDescription(SRGB).strip(), - 'sRGB IEC61966-2-1 black scaled') + "sRGB IEC61966-2-1 black scaled", + ) def test_intent(self): self.skip_missing() self.assertEqual(ImageCms.getDefaultIntent(SRGB), 0) - self.assertEqual(ImageCms.isIntentSupported( - SRGB, ImageCms.INTENT_ABSOLUTE_COLORIMETRIC, - ImageCms.DIRECTION_INPUT), 1) + self.assertEqual( + ImageCms.isIntentSupported( + SRGB, ImageCms.INTENT_ABSOLUTE_COLORIMETRIC, ImageCms.DIRECTION_INPUT + ), + 1, + ) def test_profile_object(self): # same, using profile object p = ImageCms.createProfile("sRGB") - # self.assertEqual(ImageCms.getProfileName(p).strip(), - # 'sRGB built-in - (lcms internal)') - # self.assertEqual(ImageCms.getProfileInfo(p).splitlines(), - # ['sRGB built-in', '', 'WhitePoint : D65 (daylight)', '', '']) + # self.assertEqual(ImageCms.getProfileName(p).strip(), + # 'sRGB built-in - (lcms internal)') + # self.assertEqual(ImageCms.getProfileInfo(p).splitlines(), + # ['sRGB built-in', '', 'WhitePoint : D65 (daylight)', '', '']) self.assertEqual(ImageCms.getDefaultIntent(p), 0) - self.assertEqual(ImageCms.isIntentSupported( - p, ImageCms.INTENT_ABSOLUTE_COLORIMETRIC, - ImageCms.DIRECTION_INPUT), 1) + self.assertEqual( + ImageCms.isIntentSupported( + p, ImageCms.INTENT_ABSOLUTE_COLORIMETRIC, ImageCms.DIRECTION_INPUT + ), + 1, + ) def test_extensions(self): # extensions @@ -141,7 +155,8 @@ class TestImageCms(PillowTestCase): p = ImageCms.getOpenProfile(BytesIO(i.info["icc_profile"])) self.assertEqual( ImageCms.getProfileName(p).strip(), - 'IEC 61966-2.1 Default RGB colour space - sRGB') + "IEC 61966-2.1 Default RGB colour space - sRGB", + ) def test_exceptions(self): # Test mode mismatch @@ -152,18 +167,16 @@ class TestImageCms(PillowTestCase): # the procedural pyCMS API uses PyCMSError for all sorts of errors self.assertRaises( - ImageCms.PyCMSError, - ImageCms.profileToProfile, hopper(), "foo", "bar") + ImageCms.PyCMSError, ImageCms.profileToProfile, hopper(), "foo", "bar" + ) self.assertRaises( - ImageCms.PyCMSError, - ImageCms.buildTransform, "foo", "bar", "RGB", "RGB") - self.assertRaises( - ImageCms.PyCMSError, - ImageCms.getProfileName, None) + ImageCms.PyCMSError, ImageCms.buildTransform, "foo", "bar", "RGB", "RGB" + ) + self.assertRaises(ImageCms.PyCMSError, ImageCms.getProfileName, None) self.skip_missing() self.assertRaises( - ImageCms.PyCMSError, - ImageCms.isIntentSupported, SRGB, None, None) + ImageCms.PyCMSError, ImageCms.isIntentSupported, SRGB, None, None + ) def test_display_profile(self): # try fetching the profile for the current display device @@ -174,15 +187,13 @@ class TestImageCms(PillowTestCase): ImageCms.createProfile("LAB", 6500) def test_unsupported_color_space(self): - self.assertRaises(ImageCms.PyCMSError, - ImageCms.createProfile, "unsupported") + self.assertRaises(ImageCms.PyCMSError, ImageCms.createProfile, "unsupported") def test_invalid_color_temperature(self): - self.assertRaises(ImageCms.PyCMSError, - ImageCms.createProfile, "LAB", "invalid") + self.assertRaises(ImageCms.PyCMSError, ImageCms.createProfile, "LAB", "invalid") def test_simple_lab(self): - i = Image.new('RGB', (10, 10), (128, 128, 128)) + i = Image.new("RGB", (10, 10), (128, 128, 128)) psRGB = ImageCms.createProfile("sRGB") pLab = ImageCms.createProfile("LAB") @@ -190,7 +201,7 @@ class TestImageCms(PillowTestCase): i_lab = ImageCms.applyTransform(i, t) - self.assertEqual(i_lab.mode, 'LAB') + self.assertEqual(i_lab.mode, "LAB") k = i_lab.getpixel((0, 0)) # not a linear luminance map. so L != 128: @@ -217,7 +228,7 @@ class TestImageCms(PillowTestCase): # i.save('temp.lab.tif') # visually verified vs PS. - target = Image.open('Tests/images/hopper.Lab.tif') + target = Image.open("Tests/images/hopper.Lab.tif") self.assert_image_similar(i, target, 3.5) @@ -226,17 +237,17 @@ class TestImageCms(PillowTestCase): pLab = ImageCms.createProfile("LAB") t = ImageCms.buildTransform(pLab, psRGB, "LAB", "RGB") - img = Image.open('Tests/images/hopper.Lab.tif') + img = Image.open("Tests/images/hopper.Lab.tif") img_srgb = ImageCms.applyTransform(img, t) # img_srgb.save('temp.srgb.tif') # visually verified vs ps. self.assert_image_similar(hopper(), img_srgb, 30) - self.assertTrue(img_srgb.info['icc_profile']) + self.assertTrue(img_srgb.info["icc_profile"]) - profile = ImageCmsProfile(BytesIO(img_srgb.info['icc_profile'])) - self.assertIn('sRGB', ImageCms.getProfileDescription(profile)) + profile = ImageCmsProfile(BytesIO(img_srgb.info["icc_profile"])) + self.assertIn("sRGB", ImageCms.getProfileDescription(profile)) def test_lab_roundtrip(self): # check to see if we're at least internally consistent. @@ -248,8 +259,7 @@ class TestImageCms(PillowTestCase): i = ImageCms.applyTransform(hopper(), t) - self.assertEqual(i.info['icc_profile'], - ImageCmsProfile(pLab).tobytes()) + self.assertEqual(i.info["icc_profile"], ImageCmsProfile(pLab).tobytes()) out = ImageCms.applyTransform(i, t2) @@ -264,10 +274,10 @@ class TestImageCms(PillowTestCase): # not the same bytes as the original icc_profile, # but it does roundtrip self.assertEqual(p.tobytes(), p2.tobytes()) - self.assertEqual(ImageCms.getProfileName(p), - ImageCms.getProfileName(p2)) - self.assertEqual(ImageCms.getProfileDescription(p), - ImageCms.getProfileDescription(p2)) + self.assertEqual(ImageCms.getProfileName(p), ImageCms.getProfileName(p2)) + self.assertEqual( + ImageCms.getProfileDescription(p), ImageCms.getProfileDescription(p2) + ) def test_extended_information(self): self.skip_missing() @@ -281,110 +291,150 @@ class TestImageCms(PillowTestCase): def truncate_tuple(tuple_or_float): return tuple( - truncate_tuple(val) if isinstance(val, tuple) - else int(val * power) / power for val in tuple_or_float) + truncate_tuple(val) + if isinstance(val, tuple) + else int(val * power) / power + for val in tuple_or_float + ) + self.assertEqual(truncate_tuple(tup1), truncate_tuple(tup2)) self.assertEqual(p.attributes, 4294967296) assert_truncated_tuple_equal( p.blue_colorant, - ((0.14306640625, 0.06060791015625, 0.7140960693359375), - (0.1558847490315394, 0.06603820639433387, 0.06060791015625))) + ( + (0.14306640625, 0.06060791015625, 0.7140960693359375), + (0.1558847490315394, 0.06603820639433387, 0.06060791015625), + ), + ) assert_truncated_tuple_equal( p.blue_primary, - ((0.14306641366715667, 0.06060790921083026, 0.7140960805782015), - (0.15588475410450106, 0.06603820408959558, 0.06060790921083026))) + ( + (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)))) + ( + ( + (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), - 1: (False, False, True), - 2: (False, False, True), - 3: (False, False, True) - }) + self.assertEqual( + p.clut, + { + 0: (False, False, True), + 1: (False, False, True), + 2: (False, False, True), + 3: (False, False, True), + }, + ) self.assertIsNone(p.colorant_table) self.assertIsNone(p.colorant_table_out) self.assertIsNone(p.colorimetric_intent) - self.assertEqual(p.connection_space, 'XYZ ') - self.assertEqual(p.copyright, - 'Copyright International Color Consortium, 2009') - self.assertEqual(p.creation_date, - datetime.datetime(2009, 2, 27, 21, 36, 31)) - self.assertEqual(p.device_class, 'mntr') + self.assertEqual(p.connection_space, "XYZ ") + self.assertEqual(p.copyright, "Copyright International Color Consortium, 2009") + self.assertEqual(p.creation_date, datetime.datetime(2009, 2, 27, 21, 36, 31)) + self.assertEqual(p.device_class, "mntr") assert_truncated_tuple_equal( p.green_colorant, - ((0.3851470947265625, 0.7168731689453125, 0.097076416015625), - (0.32119769927720654, 0.5978443449048152, 0.7168731689453125))) + ( + (0.3851470947265625, 0.7168731689453125, 0.097076416015625), + (0.32119769927720654, 0.5978443449048152, 0.7168731689453125), + ), + ) assert_truncated_tuple_equal( p.green_primary, - ((0.3851470888162112, 0.7168731974161346, 0.09707641738998518), - (0.32119768793686687, 0.5978443567149709, 0.7168731974161346))) + ( + (0.3851470888162112, 0.7168731974161346, 0.09707641738998518), + (0.32119768793686687, 0.5978443567149709, 0.7168731974161346), + ), + ) self.assertEqual(p.header_flags, 0) - self.assertEqual(p.header_manufacturer, '\x00\x00\x00\x00') - self.assertEqual(p.header_model, '\x00\x00\x00\x00') - self.assertEqual(p.icc_measurement_condition, { - 'backing': (0.0, 0.0, 0.0), - 'flare': 0.0, - 'geo': 'unknown', - 'observer': 1, - 'illuminant_type': 'D65' - }) + self.assertEqual(p.header_manufacturer, "\x00\x00\x00\x00") + self.assertEqual(p.header_model, "\x00\x00\x00\x00") + self.assertEqual( + p.icc_measurement_condition, + { + "backing": (0.0, 0.0, 0.0), + "flare": 0.0, + "geo": "unknown", + "observer": 1, + "illuminant_type": "D65", + }, + ) self.assertEqual(p.icc_version, 33554432) self.assertIsNone(p.icc_viewing_condition) - self.assertEqual(p.intent_supported, { - 0: (True, True, True), - 1: (True, True, True), - 2: (True, True, True), - 3: (True, True, True) - }) + self.assertEqual( + p.intent_supported, + { + 0: (True, True, True), + 1: (True, True, True), + 2: (True, True, True), + 3: (True, True, True), + }, + ) self.assertTrue(p.is_matrix_shaper) self.assertEqual(p.luminance, ((0.0, 80.0, 0.0), (0.0, 1.0, 80.0))) self.assertIsNone(p.manufacturer) assert_truncated_tuple_equal( p.media_black_point, - ((0.012054443359375, 0.0124969482421875, 0.01031494140625), - (0.34573304157549234, 0.35842450765864337, 0.0124969482421875))) + ( + (0.012054443359375, 0.0124969482421875, 0.01031494140625), + (0.34573304157549234, 0.35842450765864337, 0.0124969482421875), + ), + ) assert_truncated_tuple_equal( p.media_white_point, - ((0.964202880859375, 1.0, 0.8249053955078125), - (0.3457029219802284, 0.3585375327567059, 1.0))) + ( + (0.964202880859375, 1.0, 0.8249053955078125), + (0.3457029219802284, 0.3585375327567059, 1.0), + ), + ) assert_truncated_tuple_equal( - (p.media_white_point_temperature,), - (5000.722328847392,)) - self.assertEqual(p.model, - 'IEC 61966-2-1 Default RGB Colour Space - sRGB') + (p.media_white_point_temperature,), (5000.722328847392,) + ) + self.assertEqual(p.model, "IEC 61966-2-1 Default RGB Colour Space - sRGB") self.assertIsNone(p.perceptual_rendering_intent_gamut) - self.assertEqual( - p.profile_description, 'sRGB IEC61966-2-1 black scaled') - self.assertEqual( - p.profile_id, b')\xf8=\xde\xaf\xf2U\xaexB\xfa\xe4\xca\x839\r') + self.assertEqual(p.profile_description, "sRGB IEC61966-2-1 black scaled") + self.assertEqual(p.profile_id, b")\xf8=\xde\xaf\xf2U\xaexB\xfa\xe4\xca\x839\r") assert_truncated_tuple_equal( p.red_colorant, - ((0.436065673828125, 0.2224884033203125, 0.013916015625), - (0.6484536316398539, 0.3308524880306778, 0.2224884033203125))) + ( + (0.436065673828125, 0.2224884033203125, 0.013916015625), + (0.6484536316398539, 0.3308524880306778, 0.2224884033203125), + ), + ) assert_truncated_tuple_equal( p.red_primary, - ((0.43606566581047446, 0.22248840582960838, 0.013916015621759925), - (0.6484536250319214, 0.3308524944738204, 0.22248840582960838))) + ( + (0.43606566581047446, 0.22248840582960838, 0.013916015621759925), + (0.6484536250319214, 0.3308524944738204, 0.22248840582960838), + ), + ) self.assertEqual(p.rendering_intent, 0) self.assertIsNone(p.saturation_rendering_intent_gamut) self.assertIsNone(p.screening_description) self.assertIsNone(p.target) - self.assertEqual(p.technology, 'CRT ') + self.assertEqual(p.technology, "CRT ") self.assertEqual(p.version, 2.0) - self.assertEqual(p.viewing_condition, - 'Reference Viewing Condition in IEC 61966-2-1') - self.assertEqual(p.xcolor_space, 'RGB ') + self.assertEqual( + p.viewing_condition, "Reference Viewing Condition in IEC 61966-2-1" + ) + self.assertEqual(p.xcolor_space, "RGB ") def test_deprecations(self): self.skip_missing() @@ -431,34 +481,36 @@ class TestImageCms(PillowTestCase): with self.assertRaises(TypeError): ImageCms.ImageCmsProfile(1).tobytes() - def assert_aux_channel_preserved(self, mode, - transform_in_place, preserved_channel): + def assert_aux_channel_preserved(self, mode, transform_in_place, preserved_channel): def create_test_image(): - # set up test image with something interesting in the tested aux - # channel. - nine_grid_deltas = [ # noqa: E128 + # set up test image with something interesting in the tested aux channel. + # fmt: off + nine_grid_deltas = [ # noqa: E131 (-1, -1), (-1, 0), (-1, 1), (0, -1), (0, 0), (0, 1), (1, -1), (1, 0), (1, 1), ] + # fmt: on chans = [] bands = ImageMode.getmode(mode).bands for band_ndx in range(len(bands)): - channel_type = 'L' # 8-bit unorm + channel_type = "L" # 8-bit unorm channel_pattern = hopper(channel_type) # paste pattern with varying offsets to avoid correlation # potentially hiding some bugs (like channels getting mixed). paste_offset = ( int(band_ndx / float(len(bands)) * channel_pattern.size[0]), - int(band_ndx / float(len(bands) * 2) * channel_pattern.size[1]) + int(band_ndx / float(len(bands) * 2) * channel_pattern.size[1]), ) 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)), + tuple( + paste_offset[c] + delta[c] * channel_pattern.size[c] + for c in range(2) + ), ) chans.append(channel_data) return Image.merge(mode, chans) @@ -470,42 +522,46 @@ class TestImageCms(PillowTestCase): source_profile = ImageCms.createProfile("sRGB") destination_profile = ImageCms.createProfile("sRGB") t = ImageCms.buildTransform( - source_profile, destination_profile, inMode=mode, outMode=mode) + source_profile, destination_profile, inMode=mode, outMode=mode + ) # apply transform if transform_in_place: ImageCms.applyTransform(source_image, t, inPlace=True) result_image = source_image else: - result_image = ImageCms.applyTransform( - source_image, t, inPlace=False) + result_image = ImageCms.applyTransform(source_image, t, inPlace=False) result_image_aux = result_image.getchannel(preserved_channel) self.assert_image_equal(source_image_aux, result_image_aux) def test_preserve_auxiliary_channels_rgba(self): self.assert_aux_channel_preserved( - mode='RGBA', transform_in_place=False, preserved_channel='A') + mode="RGBA", transform_in_place=False, preserved_channel="A" + ) def test_preserve_auxiliary_channels_rgba_in_place(self): self.assert_aux_channel_preserved( - mode='RGBA', transform_in_place=True, preserved_channel='A') + mode="RGBA", transform_in_place=True, preserved_channel="A" + ) def test_preserve_auxiliary_channels_rgbx(self): self.assert_aux_channel_preserved( - mode='RGBX', transform_in_place=False, preserved_channel='X') + mode="RGBX", transform_in_place=False, preserved_channel="X" + ) def test_preserve_auxiliary_channels_rgbx_in_place(self): self.assert_aux_channel_preserved( - mode='RGBX', transform_in_place=True, preserved_channel='X') + mode="RGBX", transform_in_place=True, preserved_channel="X" + ) def test_auxiliary_channels_isolated(self): # test data in aux channels does not affect non-aux channels aux_channel_formats = [ # format, profile, color-only format, source test image - ('RGBA', 'sRGB', 'RGB', hopper('RGBA')), - ('RGBX', 'sRGB', 'RGB', hopper('RGBX')), - ('LAB', 'LAB', 'LAB', Image.open('Tests/images/hopper.Lab.tif')), + ("RGBA", "sRGB", "RGB", hopper("RGBA")), + ("RGBX", "sRGB", "RGB", hopper("RGBX")), + ("LAB", "LAB", "LAB", Image.open("Tests/images/hopper.Lab.tif")), ] for src_format in aux_channel_formats: for dst_format in aux_channel_formats: @@ -519,25 +575,34 @@ class TestImageCms(PillowTestCase): destination_profile = ImageCms.createProfile(dst_format[1]) source_image = src_format[3] test_transform = ImageCms.buildTransform( - source_profile, destination_profile, - inMode=src_format[0], outMode=dst_format[0]) + source_profile, + destination_profile, + inMode=src_format[0], + outMode=dst_format[0], + ) # test conversion from aux-ful source if transform_in_place: test_image = source_image.copy() ImageCms.applyTransform( - test_image, test_transform, inPlace=True) + test_image, test_transform, inPlace=True + ) else: test_image = ImageCms.applyTransform( - source_image, test_transform, inPlace=False) + source_image, test_transform, inPlace=False + ) # reference conversion from aux-less source reference_transform = ImageCms.buildTransform( - source_profile, destination_profile, - inMode=src_format[2], outMode=dst_format[2]) + source_profile, + destination_profile, + inMode=src_format[2], + outMode=dst_format[2], + ) reference_image = ImageCms.applyTransform( - source_image.convert(src_format[2]), - reference_transform) + source_image.convert(src_format[2]), reference_transform + ) - self.assert_image_equal(test_image.convert(dst_format[2]), - reference_image) + self.assert_image_equal( + test_image.convert(dst_format[2]), reference_image + ) diff --git a/Tests/test_imagecolor.py b/Tests/test_imagecolor.py index cb9c9843c..c0983ec4c 100644 --- a/Tests/test_imagecolor.py +++ b/Tests/test_imagecolor.py @@ -5,7 +5,6 @@ from PIL import ImageColor class TestImageColor(PillowTestCase): - def test_hash(self): # short 3 components self.assertEqual((255, 0, 0), ImageColor.getrgb("#f00")) @@ -31,12 +30,9 @@ class TestImageColor(PillowTestCase): # case insensitivity self.assertEqual(ImageColor.getrgb("#DEF"), ImageColor.getrgb("#def")) - self.assertEqual(ImageColor.getrgb("#CDEF"), - ImageColor.getrgb("#cdef")) - self.assertEqual(ImageColor.getrgb("#DEFDEF"), - ImageColor.getrgb("#defdef")) - self.assertEqual(ImageColor.getrgb("#CDEFCDEF"), - ImageColor.getrgb("#cdefcdef")) + self.assertEqual(ImageColor.getrgb("#CDEF"), ImageColor.getrgb("#cdef")) + self.assertEqual(ImageColor.getrgb("#DEFDEF"), ImageColor.getrgb("#defdef")) + self.assertEqual(ImageColor.getrgb("#CDEFCDEF"), ImageColor.getrgb("#cdefcdef")) # not a number self.assertRaises(ValueError, ImageColor.getrgb, "#fo0") @@ -81,49 +77,48 @@ class TestImageColor(PillowTestCase): self.assertEqual((255, 0, 0), ImageColor.getrgb("hsv(0,100%,100%)")) self.assertEqual((255, 0, 0), ImageColor.getrgb("hsv(360,100%,100%)")) - self.assertEqual((0, 255, 255), - ImageColor.getrgb("hsv(180,100%,100%)")) + self.assertEqual((0, 255, 255), ImageColor.getrgb("hsv(180,100%,100%)")) # alternate format - self.assertEqual(ImageColor.getrgb("hsb(0,100%,50%)"), - ImageColor.getrgb("hsv(0,100%,50%)")) + self.assertEqual( + ImageColor.getrgb("hsb(0,100%,50%)"), ImageColor.getrgb("hsv(0,100%,50%)") + ) # floats - self.assertEqual((254, 3, 3), - ImageColor.getrgb("hsl(0.1,99.2%,50.3%)")) - self.assertEqual((255, 0, 0), - ImageColor.getrgb("hsl(360.,100.0%,50%)")) + self.assertEqual((254, 3, 3), ImageColor.getrgb("hsl(0.1,99.2%,50.3%)")) + self.assertEqual((255, 0, 0), ImageColor.getrgb("hsl(360.,100.0%,50%)")) - self.assertEqual((253, 2, 2), - ImageColor.getrgb("hsv(0.1,99.2%,99.3%)")) - self.assertEqual((255, 0, 0), - ImageColor.getrgb("hsv(360.,100.0%,100%)")) + self.assertEqual((253, 2, 2), ImageColor.getrgb("hsv(0.1,99.2%,99.3%)")) + self.assertEqual((255, 0, 0), ImageColor.getrgb("hsv(360.,100.0%,100%)")) # case insensitivity - self.assertEqual(ImageColor.getrgb("RGB(255,0,0)"), - ImageColor.getrgb("rgb(255,0,0)")) - self.assertEqual(ImageColor.getrgb("RGB(100%,0%,0%)"), - ImageColor.getrgb("rgb(100%,0%,0%)")) - self.assertEqual(ImageColor.getrgb("RGBA(255,0,0,0)"), - ImageColor.getrgb("rgba(255,0,0,0)")) - self.assertEqual(ImageColor.getrgb("HSL(0,100%,50%)"), - ImageColor.getrgb("hsl(0,100%,50%)")) - self.assertEqual(ImageColor.getrgb("HSV(0,100%,50%)"), - ImageColor.getrgb("hsv(0,100%,50%)")) - self.assertEqual(ImageColor.getrgb("HSB(0,100%,50%)"), - ImageColor.getrgb("hsb(0,100%,50%)")) + self.assertEqual( + ImageColor.getrgb("RGB(255,0,0)"), ImageColor.getrgb("rgb(255,0,0)") + ) + self.assertEqual( + ImageColor.getrgb("RGB(100%,0%,0%)"), ImageColor.getrgb("rgb(100%,0%,0%)") + ) + self.assertEqual( + ImageColor.getrgb("RGBA(255,0,0,0)"), ImageColor.getrgb("rgba(255,0,0,0)") + ) + self.assertEqual( + ImageColor.getrgb("HSL(0,100%,50%)"), ImageColor.getrgb("hsl(0,100%,50%)") + ) + self.assertEqual( + ImageColor.getrgb("HSV(0,100%,50%)"), ImageColor.getrgb("hsv(0,100%,50%)") + ) + self.assertEqual( + ImageColor.getrgb("HSB(0,100%,50%)"), ImageColor.getrgb("hsb(0,100%,50%)") + ) # space agnosticism - self.assertEqual((255, 0, 0), - ImageColor.getrgb("rgb( 255 , 0 , 0 )")) - self.assertEqual((255, 0, 0), - ImageColor.getrgb("rgb( 100% , 0% , 0% )")) - self.assertEqual((255, 0, 0, 0), - ImageColor.getrgb("rgba( 255 , 0 , 0 , 0 )")) - self.assertEqual((255, 0, 0), - ImageColor.getrgb("hsl( 0 , 100% , 50% )")) - self.assertEqual((255, 0, 0), - ImageColor.getrgb("hsv( 0 , 100% , 100% )")) + self.assertEqual((255, 0, 0), ImageColor.getrgb("rgb( 255 , 0 , 0 )")) + self.assertEqual((255, 0, 0), ImageColor.getrgb("rgb( 100% , 0% , 0% )")) + self.assertEqual( + (255, 0, 0, 0), ImageColor.getrgb("rgba( 255 , 0 , 0 , 0 )") + ) + self.assertEqual((255, 0, 0), ImageColor.getrgb("hsl( 0 , 100% , 50% )")) + self.assertEqual((255, 0, 0), ImageColor.getrgb("hsv( 0 , 100% , 100% )")) # wrong number of components self.assertRaises(ValueError, ImageColor.getrgb, "rgb(255,0)") @@ -153,35 +148,32 @@ class TestImageColor(PillowTestCase): def test_rounding_errors(self): for color in ImageColor.colormap: - expected = Image.new( - "RGB", (1, 1), color).convert("L").getpixel((0, 0)) - actual = ImageColor.getcolor(color, 'L') + expected = Image.new("RGB", (1, 1), color).convert("L").getpixel((0, 0)) + actual = ImageColor.getcolor(color, "L") self.assertEqual(expected, actual) self.assertEqual( - (0, 255, 115), ImageColor.getcolor("rgba(0, 255, 115, 33)", "RGB")) + (0, 255, 115), ImageColor.getcolor("rgba(0, 255, 115, 33)", "RGB") + ) Image.new("RGB", (1, 1), "white") self.assertEqual((0, 0, 0, 255), ImageColor.getcolor("black", "RGBA")) + self.assertEqual((255, 255, 255, 255), ImageColor.getcolor("white", "RGBA")) self.assertEqual( - (255, 255, 255, 255), ImageColor.getcolor("white", "RGBA")) - self.assertEqual( - (0, 255, 115, 33), - ImageColor.getcolor("rgba(0, 255, 115, 33)", "RGBA")) + (0, 255, 115, 33), ImageColor.getcolor("rgba(0, 255, 115, 33)", "RGBA") + ) Image.new("RGBA", (1, 1), "white") self.assertEqual(0, ImageColor.getcolor("black", "L")) self.assertEqual(255, ImageColor.getcolor("white", "L")) - self.assertEqual(162, - ImageColor.getcolor("rgba(0, 255, 115, 33)", "L")) + self.assertEqual(162, ImageColor.getcolor("rgba(0, 255, 115, 33)", "L")) Image.new("L", (1, 1), "white") self.assertEqual(0, ImageColor.getcolor("black", "1")) self.assertEqual(255, ImageColor.getcolor("white", "1")) # The following test is wrong, but is current behavior # The correct result should be 255 due to the mode 1 - self.assertEqual( - 162, ImageColor.getcolor("rgba(0, 255, 115, 33)", "1")) + self.assertEqual(162, ImageColor.getcolor("rgba(0, 255, 115, 33)", "1")) # Correct behavior # self.assertEqual( # 255, ImageColor.getcolor("rgba(0, 255, 115, 33)", "1")) @@ -189,6 +181,5 @@ class TestImageColor(PillowTestCase): self.assertEqual((0, 255), ImageColor.getcolor("black", "LA")) self.assertEqual((255, 255), ImageColor.getcolor("white", "LA")) - self.assertEqual( - (162, 33), ImageColor.getcolor("rgba(0, 255, 115, 33)", "LA")) + self.assertEqual((162, 33), ImageColor.getcolor("rgba(0, 255, 115, 33)", "LA")) Image.new("LA", (1, 1), "white") diff --git a/Tests/test_imagedraw.py b/Tests/test_imagedraw.py index bceb0e3d4..7abc34aab 100644 --- a/Tests/test_imagedraw.py +++ b/Tests/test_imagedraw.py @@ -6,8 +6,8 @@ from PIL import Image, ImageColor, ImageDraw BLACK = (0, 0, 0) WHITE = (255, 255, 255) GRAY = (190, 190, 190) -DEFAULT_MODE = 'RGB' -IMAGES_PATH = os.path.join('Tests', 'images', 'imagedraw') +DEFAULT_MODE = "RGB" +IMAGES_PATH = os.path.join("Tests", "images", "imagedraw") # Image size W, H = 100, 100 @@ -30,7 +30,6 @@ KITE_POINTS = [(10, 50), (70, 10), (90, 50), (70, 90), (10, 50)] class TestImageDraw(PillowTestCase): - def test_sanity(self): im = hopper("RGB").copy() @@ -62,8 +61,7 @@ class TestImageDraw(PillowTestCase): draw.arc(bbox, start, end) # Assert - self.assert_image_similar( - im, Image.open("Tests/images/imagedraw_arc.png"), 1) + self.assert_image_similar(im, Image.open("Tests/images/imagedraw_arc.png"), 1) def test_arc1(self): self.helper_arc(BBOX1, 0, 180) @@ -85,7 +83,8 @@ class TestImageDraw(PillowTestCase): # Assert self.assert_image_equal( - im, Image.open("Tests/images/imagedraw_arc_end_le_start.png")) + im, Image.open("Tests/images/imagedraw_arc_end_le_start.png") + ) def test_arc_no_loops(self): # No need to go in loops @@ -100,7 +99,8 @@ class TestImageDraw(PillowTestCase): # Assert self.assert_image_similar( - im, Image.open("Tests/images/imagedraw_arc_no_loops.png"), 1) + im, Image.open("Tests/images/imagedraw_arc_no_loops.png"), 1 + ) def test_arc_width(self): # Arrange @@ -114,6 +114,19 @@ class TestImageDraw(PillowTestCase): # Assert self.assert_image_similar(im, Image.open(expected), 1) + def test_arc_width_pieslice_large(self): + # Tests an arc with a large enough width that it is a pieslice + # Arrange + im = Image.new("RGB", (W, H)) + draw = ImageDraw.Draw(im) + expected = "Tests/images/imagedraw_arc_width_pieslice.png" + + # Act + draw.arc(BBOX1, 10, 260, fill="yellow", width=100) + + # Assert + self.assert_image_similar(im, Image.open(expected), 1) + def test_arc_width_fill(self): # Arrange im = Image.new("RGB", (W, H)) @@ -136,8 +149,7 @@ class TestImageDraw(PillowTestCase): draw.bitmap((10, 10), small) # Assert - self.assert_image_equal( - im, Image.open("Tests/images/imagedraw_bitmap.png")) + self.assert_image_equal(im, Image.open("Tests/images/imagedraw_bitmap.png")) def helper_chord(self, mode, bbox, start, end): # Arrange @@ -211,17 +223,15 @@ class TestImageDraw(PillowTestCase): draw = ImageDraw.Draw(im) # Act - draw.ellipse(((0, 0), (W-1, H)), fill="white") + draw.ellipse(((0, 0), (W - 1, H)), fill="white") # Assert self.assert_image_similar( - im, Image.open("Tests/images/imagedraw_ellipse_edge.png"), 1) + im, Image.open("Tests/images/imagedraw_ellipse_edge.png"), 1 + ) def test_ellipse_symmetric(self): - for bbox in [ - (25, 25, 76, 76), - (25, 25, 75, 75) - ]: + for bbox in [(25, 25, 76, 76), (25, 25, 75, 75)]: im = Image.new("RGB", (101, 101)) draw = ImageDraw.Draw(im) draw.ellipse(bbox, fill="green", outline="blue") @@ -239,6 +249,18 @@ class TestImageDraw(PillowTestCase): # Assert self.assert_image_similar(im, Image.open(expected), 1) + def test_ellipse_width_large(self): + # Arrange + im = Image.new("RGB", (500, 500)) + draw = ImageDraw.Draw(im) + expected = "Tests/images/imagedraw_ellipse_width_large.png" + + # Act + draw.ellipse((25, 25, 475, 475), outline="blue", width=75) + + # Assert + self.assert_image_similar(im, Image.open(expected), 1) + def test_ellipse_width_fill(self): # Arrange im = Image.new("RGB", (W, H)) @@ -260,8 +282,7 @@ class TestImageDraw(PillowTestCase): draw.line(points, fill="yellow", width=2) # Assert - self.assert_image_equal( - im, Image.open("Tests/images/imagedraw_line.png")) + self.assert_image_equal(im, Image.open("Tests/images/imagedraw_line.png")) def test_line1(self): self.helper_line(POINTS1) @@ -287,8 +308,7 @@ class TestImageDraw(PillowTestCase): draw.shape(s, fill=1) # Assert - self.assert_image_equal( - im, Image.open("Tests/images/imagedraw_shape1.png")) + self.assert_image_equal(im, Image.open("Tests/images/imagedraw_shape1.png")) def test_shape2(self): # Arrange @@ -308,8 +328,7 @@ class TestImageDraw(PillowTestCase): draw.shape(s, outline="blue") # Assert - self.assert_image_equal( - im, Image.open("Tests/images/imagedraw_shape2.png")) + self.assert_image_equal(im, Image.open("Tests/images/imagedraw_shape2.png")) def helper_pieslice(self, bbox, start, end): # Arrange @@ -321,7 +340,8 @@ class TestImageDraw(PillowTestCase): # Assert self.assert_image_similar( - im, Image.open("Tests/images/imagedraw_pieslice.png"), 1) + im, Image.open("Tests/images/imagedraw_pieslice.png"), 1 + ) def test_pieslice1(self): self.helper_pieslice(BBOX1, -90, 45) @@ -364,8 +384,7 @@ class TestImageDraw(PillowTestCase): draw.point(points, fill="yellow") # Assert - self.assert_image_equal( - im, Image.open("Tests/images/imagedraw_point.png")) + self.assert_image_equal(im, Image.open("Tests/images/imagedraw_point.png")) def test_point1(self): self.helper_point(POINTS1) @@ -382,8 +401,7 @@ class TestImageDraw(PillowTestCase): draw.polygon(points, fill="red", outline="blue") # Assert - self.assert_image_equal( - im, Image.open("Tests/images/imagedraw_polygon.png")) + self.assert_image_equal(im, Image.open("Tests/images/imagedraw_polygon.png")) def test_polygon1(self): self.helper_polygon(POINTS1) @@ -398,8 +416,7 @@ class TestImageDraw(PillowTestCase): # Arrange im = Image.new(mode, (W, H)) draw = ImageDraw.Draw(im) - expected = "Tests/images/imagedraw_polygon_kite_{}.png".format( - mode) + expected = "Tests/images/imagedraw_polygon_kite_{}.png".format(mode) # Act draw.polygon(KITE_POINTS, fill="blue", outline="yellow") @@ -416,8 +433,7 @@ class TestImageDraw(PillowTestCase): draw.rectangle(bbox, fill="black", outline="green") # Assert - self.assert_image_equal( - im, Image.open("Tests/images/imagedraw_rectangle.png")) + self.assert_image_equal(im, Image.open("Tests/images/imagedraw_rectangle.png")) def test_rectangle1(self): self.helper_rectangle(BBOX1) @@ -429,7 +445,7 @@ class TestImageDraw(PillowTestCase): # Test drawing a rectangle bigger than the image # Arrange im = Image.new("RGB", (W, H)) - bbox = [(-1, -1), (W+1, H+1)] + bbox = [(-1, -1), (W + 1, H + 1)] draw = ImageDraw.Draw(im) expected = "Tests/images/imagedraw_big_rectangle.png" @@ -466,22 +482,18 @@ class TestImageDraw(PillowTestCase): def test_floodfill(self): red = ImageColor.getrgb("red") - for mode, value in [ - ("L", 1), - ("RGBA", (255, 0, 0, 0)), - ("RGB", red) - ]: + for mode, value in [("L", 1), ("RGBA", (255, 0, 0, 0)), ("RGB", red)]: # Arrange im = Image.new(mode, (W, H)) draw = ImageDraw.Draw(im) draw.rectangle(BBOX2, outline="yellow", fill="green") - centre_point = (int(W/2), int(H/2)) + centre_point = (int(W / 2), int(H / 2)) # Act ImageDraw.floodfill(im, centre_point, value) # Assert - expected = "Tests/images/imagedraw_floodfill_"+mode+".png" + expected = "Tests/images/imagedraw_floodfill_" + mode + ".png" im_floodfill = Image.open(expected) self.assert_image_equal(im, im_floodfill) @@ -505,16 +517,18 @@ class TestImageDraw(PillowTestCase): im = Image.new("RGB", (W, H)) draw = ImageDraw.Draw(im) draw.rectangle(BBOX2, outline="yellow", fill="green") - centre_point = (int(W/2), int(H/2)) + centre_point = (int(W / 2), int(H / 2)) # Act ImageDraw.floodfill( - im, centre_point, ImageColor.getrgb("red"), - border=ImageColor.getrgb("black")) + im, + centre_point, + ImageColor.getrgb("red"), + border=ImageColor.getrgb("black"), + ) # Assert - self.assert_image_equal( - im, Image.open("Tests/images/imagedraw_floodfill2.png")) + self.assert_image_equal(im, Image.open("Tests/images/imagedraw_floodfill2.png")) def test_floodfill_thresh(self): # floodfill() is experimental @@ -523,21 +537,17 @@ class TestImageDraw(PillowTestCase): im = Image.new("RGB", (W, H)) draw = ImageDraw.Draw(im) draw.rectangle(BBOX2, outline="darkgreen", fill="green") - centre_point = (int(W/2), int(H/2)) + centre_point = (int(W / 2), int(H / 2)) # Act - ImageDraw.floodfill( - im, centre_point, ImageColor.getrgb("red"), - thresh=30) + ImageDraw.floodfill(im, centre_point, ImageColor.getrgb("red"), thresh=30) # Assert - self.assert_image_equal( - im, Image.open("Tests/images/imagedraw_floodfill2.png")) + self.assert_image_equal(im, Image.open("Tests/images/imagedraw_floodfill2.png")) - def create_base_image_draw(self, size, - mode=DEFAULT_MODE, - background1=WHITE, - background2=GRAY): + def create_base_image_draw( + self, size, mode=DEFAULT_MODE, background1=WHITE, background2=GRAY + ): img = Image.new(mode, size, background1) for x in range(0, size[0]): for y in range(0, size[1]): @@ -546,140 +556,152 @@ class TestImageDraw(PillowTestCase): return img, ImageDraw.Draw(img) def test_square(self): - expected = Image.open(os.path.join(IMAGES_PATH, 'square.png')) + expected = Image.open(os.path.join(IMAGES_PATH, "square.png")) expected.load() img, draw = self.create_base_image_draw((10, 10)) draw.polygon([(2, 2), (2, 7), (7, 7), (7, 2)], BLACK) - self.assert_image_equal(img, expected, - 'square as normal polygon failed') + self.assert_image_equal(img, expected, "square as normal polygon failed") img, draw = self.create_base_image_draw((10, 10)) draw.polygon([(7, 7), (7, 2), (2, 2), (2, 7)], BLACK) - self.assert_image_equal(img, expected, - 'square as inverted polygon failed') + self.assert_image_equal(img, expected, "square as inverted polygon failed") img, draw = self.create_base_image_draw((10, 10)) draw.rectangle((2, 2, 7, 7), BLACK) - self.assert_image_equal(img, expected, - 'square as normal rectangle failed') + self.assert_image_equal(img, expected, "square as normal rectangle failed") img, draw = self.create_base_image_draw((10, 10)) draw.rectangle((7, 7, 2, 2), BLACK) - self.assert_image_equal( - img, expected, 'square as inverted rectangle failed') + self.assert_image_equal(img, expected, "square as inverted rectangle failed") def test_triangle_right(self): - expected = Image.open(os.path.join(IMAGES_PATH, 'triangle_right.png')) + expected = Image.open(os.path.join(IMAGES_PATH, "triangle_right.png")) expected.load() img, draw = self.create_base_image_draw((20, 20)) draw.polygon([(3, 5), (17, 5), (10, 12)], BLACK) - self.assert_image_equal(img, expected, 'triangle right failed') + self.assert_image_equal(img, expected, "triangle right failed") def test_line_horizontal(self): - expected = Image.open(os.path.join(IMAGES_PATH, - 'line_horizontal_w2px_normal.png')) + expected = Image.open( + os.path.join(IMAGES_PATH, "line_horizontal_w2px_normal.png") + ) expected.load() img, draw = self.create_base_image_draw((20, 20)) draw.line((5, 5, 14, 5), BLACK, 2) self.assert_image_equal( - img, expected, 'line straight horizontal normal 2px wide failed') - expected = Image.open(os.path.join(IMAGES_PATH, - 'line_horizontal_w2px_inverted.png')) + img, expected, "line straight horizontal normal 2px wide failed" + ) + expected = Image.open( + os.path.join(IMAGES_PATH, "line_horizontal_w2px_inverted.png") + ) expected.load() img, draw = self.create_base_image_draw((20, 20)) draw.line((14, 5, 5, 5), BLACK, 2) self.assert_image_equal( - img, expected, 'line straight horizontal inverted 2px wide failed') - expected = Image.open(os.path.join(IMAGES_PATH, - 'line_horizontal_w3px.png')) + img, expected, "line straight horizontal inverted 2px wide failed" + ) + expected = Image.open(os.path.join(IMAGES_PATH, "line_horizontal_w3px.png")) expected.load() img, draw = self.create_base_image_draw((20, 20)) draw.line((5, 5, 14, 5), BLACK, 3) self.assert_image_equal( - img, expected, 'line straight horizontal normal 3px wide failed') + img, expected, "line straight horizontal normal 3px wide failed" + ) img, draw = self.create_base_image_draw((20, 20)) draw.line((14, 5, 5, 5), BLACK, 3) self.assert_image_equal( - img, expected, 'line straight horizontal inverted 3px wide failed') - expected = Image.open(os.path.join(IMAGES_PATH, - 'line_horizontal_w101px.png')) + img, expected, "line straight horizontal inverted 3px wide failed" + ) + expected = Image.open(os.path.join(IMAGES_PATH, "line_horizontal_w101px.png")) expected.load() img, draw = self.create_base_image_draw((200, 110)) draw.line((5, 55, 195, 55), BLACK, 101) self.assert_image_equal( - img, expected, 'line straight horizontal 101px wide failed') + img, expected, "line straight horizontal 101px wide failed" + ) def test_line_h_s1_w2(self): - self.skipTest('failing') - expected = Image.open(os.path.join(IMAGES_PATH, - 'line_horizontal_slope1px_w2px.png')) + self.skipTest("failing") + expected = Image.open( + os.path.join(IMAGES_PATH, "line_horizontal_slope1px_w2px.png") + ) expected.load() img, draw = self.create_base_image_draw((20, 20)) draw.line((5, 5, 14, 6), BLACK, 2) self.assert_image_equal( - img, expected, 'line horizontal 1px slope 2px wide failed') + img, expected, "line horizontal 1px slope 2px wide failed" + ) def test_line_vertical(self): - expected = Image.open(os.path.join(IMAGES_PATH, - 'line_vertical_w2px_normal.png')) + expected = Image.open( + os.path.join(IMAGES_PATH, "line_vertical_w2px_normal.png") + ) expected.load() img, draw = self.create_base_image_draw((20, 20)) draw.line((5, 5, 5, 14), BLACK, 2) self.assert_image_equal( - img, expected, 'line straight vertical normal 2px wide failed') - expected = Image.open(os.path.join(IMAGES_PATH, - 'line_vertical_w2px_inverted.png')) + img, expected, "line straight vertical normal 2px wide failed" + ) + expected = Image.open( + os.path.join(IMAGES_PATH, "line_vertical_w2px_inverted.png") + ) expected.load() img, draw = self.create_base_image_draw((20, 20)) draw.line((5, 14, 5, 5), BLACK, 2) self.assert_image_equal( - img, expected, 'line straight vertical inverted 2px wide failed') - expected = Image.open(os.path.join(IMAGES_PATH, - 'line_vertical_w3px.png')) + img, expected, "line straight vertical inverted 2px wide failed" + ) + expected = Image.open(os.path.join(IMAGES_PATH, "line_vertical_w3px.png")) expected.load() img, draw = self.create_base_image_draw((20, 20)) draw.line((5, 5, 5, 14), BLACK, 3) self.assert_image_equal( - img, expected, 'line straight vertical normal 3px wide failed') + img, expected, "line straight vertical normal 3px wide failed" + ) img, draw = self.create_base_image_draw((20, 20)) draw.line((5, 14, 5, 5), BLACK, 3) self.assert_image_equal( - img, expected, 'line straight vertical inverted 3px wide failed') - expected = Image.open(os.path.join(IMAGES_PATH, - 'line_vertical_w101px.png')) + img, expected, "line straight vertical inverted 3px wide failed" + ) + expected = Image.open(os.path.join(IMAGES_PATH, "line_vertical_w101px.png")) expected.load() img, draw = self.create_base_image_draw((110, 200)) draw.line((55, 5, 55, 195), BLACK, 101) - self.assert_image_equal(img, expected, - 'line straight vertical 101px wide failed') - expected = Image.open(os.path.join(IMAGES_PATH, - 'line_vertical_slope1px_w2px.png')) + self.assert_image_equal( + img, expected, "line straight vertical 101px wide failed" + ) + expected = Image.open( + os.path.join(IMAGES_PATH, "line_vertical_slope1px_w2px.png") + ) expected.load() img, draw = self.create_base_image_draw((20, 20)) draw.line((5, 5, 6, 14), BLACK, 2) - self.assert_image_equal(img, expected, - 'line vertical 1px slope 2px wide failed') + self.assert_image_equal( + img, expected, "line vertical 1px slope 2px wide failed" + ) def test_line_oblique_45(self): - expected = Image.open(os.path.join(IMAGES_PATH, - 'line_oblique_45_w3px_a.png')) + expected = Image.open(os.path.join(IMAGES_PATH, "line_oblique_45_w3px_a.png")) expected.load() img, draw = self.create_base_image_draw((20, 20)) draw.line((5, 5, 14, 14), BLACK, 3) - self.assert_image_equal(img, expected, - 'line oblique 45 normal 3px wide A failed') + self.assert_image_equal( + img, expected, "line oblique 45 normal 3px wide A failed" + ) img, draw = self.create_base_image_draw((20, 20)) draw.line((14, 14, 5, 5), BLACK, 3) - self.assert_image_equal(img, expected, - 'line oblique 45 inverted 3px wide A failed') - expected = Image.open(os.path.join(IMAGES_PATH, - 'line_oblique_45_w3px_b.png')) + self.assert_image_equal( + img, expected, "line oblique 45 inverted 3px wide A failed" + ) + expected = Image.open(os.path.join(IMAGES_PATH, "line_oblique_45_w3px_b.png")) expected.load() img, draw = self.create_base_image_draw((20, 20)) draw.line((14, 5, 5, 14), BLACK, 3) - self.assert_image_equal(img, expected, - 'line oblique 45 normal 3px wide B failed') + self.assert_image_equal( + img, expected, "line oblique 45 normal 3px wide B failed" + ) img, draw = self.create_base_image_draw((20, 20)) draw.line((5, 14, 14, 5), BLACK, 3) - self.assert_image_equal(img, expected, - 'line oblique 45 inverted 3px wide B failed') + self.assert_image_equal( + img, expected, "line oblique 45 inverted 3px wide B failed" + ) def test_wide_line_dot(self): # Test drawing a wide "line" from one point to another just draws @@ -701,9 +723,22 @@ class TestImageDraw(PillowTestCase): expected = "Tests/images/imagedraw_line_joint_curve.png" # Act - xy = [(400, 280), (380, 280), (450, 280), (440, 120), (350, 200), - (310, 280), (300, 280), (250, 280), (250, 200), (150, 200), - (150, 260), (50, 200), (150, 50), (250, 100)] + xy = [ + (400, 280), + (380, 280), + (450, 280), + (440, 120), + (350, 200), + (310, 280), + (300, 280), + (250, 280), + (250, 200), + (150, 200), + (150, 260), + (50, 200), + (150, 50), + (250, 100), + ] draw.line(xy, GRAY, 50, "curve") # Assert @@ -736,18 +771,14 @@ class TestImageDraw(PillowTestCase): # Begin for mode in ["RGB", "L"]: - for fill, outline in [ - ["red", None], - ["red", "red"], - ["red", "#f00"] - ]: + for fill, outline in [["red", None], ["red", "red"], ["red", "#f00"]]: for operation, args in { - 'chord': [BBOX1, 0, 180], - 'ellipse': [BBOX1], - 'shape': [s], - 'pieslice': [BBOX1, -90, 45], - 'polygon': [[(18, 30), (85, 30), (60, 72)]], - 'rectangle': [BBOX1] + "chord": [BBOX1, 0, 180], + "ellipse": [BBOX1], + "shape": [s], + "pieslice": [BBOX1, -90, 45], + "polygon": [[(18, 30), (85, 30), (60, 72)]], + "rectangle": [BBOX1], }.items(): # Arrange im = Image.new(mode, (W, H)) @@ -759,6 +790,7 @@ class TestImageDraw(PillowTestCase): draw_method(*args) # Assert - expected = ("Tests/images/imagedraw_outline" - "_{}_{}.png".format(operation, mode)) + expected = "Tests/images/imagedraw_outline_{}_{}.png".format( + operation, mode + ) self.assert_image_similar(im, Image.open(expected), 1) diff --git a/Tests/test_imagedraw2.py b/Tests/test_imagedraw2.py index 97033c8a7..5e75a08f7 100644 --- a/Tests/test_imagedraw2.py +++ b/Tests/test_imagedraw2.py @@ -33,7 +33,6 @@ FONT_PATH = "Tests/fonts/FreeMono.ttf" class TestImageDraw(PillowTestCase): - def test_sanity(self): im = hopper("RGB").copy() @@ -74,11 +73,12 @@ class TestImageDraw(PillowTestCase): brush = ImageDraw2.Brush("white") # Act - draw.ellipse(((0, 0), (W-1, H)), brush) + draw.ellipse(((0, 0), (W - 1, H)), brush) # Assert self.assert_image_similar( - im, Image.open("Tests/images/imagedraw_ellipse_edge.png"), 1) + im, Image.open("Tests/images/imagedraw_ellipse_edge.png"), 1 + ) def helper_line(self, points): # Arrange @@ -90,8 +90,7 @@ class TestImageDraw(PillowTestCase): draw.line(points, pen) # Assert - self.assert_image_equal( - im, Image.open("Tests/images/imagedraw_line.png")) + self.assert_image_equal(im, Image.open("Tests/images/imagedraw_line.png")) def test_line1_pen(self): self.helper_line(POINTS1) @@ -111,8 +110,7 @@ class TestImageDraw(PillowTestCase): draw.line(POINTS1, pen, brush) # Assert - self.assert_image_equal( - im, Image.open("Tests/images/imagedraw_line.png")) + self.assert_image_equal(im, Image.open("Tests/images/imagedraw_line.png")) def helper_polygon(self, points): # Arrange @@ -125,8 +123,7 @@ class TestImageDraw(PillowTestCase): draw.polygon(points, pen, brush) # Assert - self.assert_image_equal( - im, Image.open("Tests/images/imagedraw_polygon.png")) + self.assert_image_equal(im, Image.open("Tests/images/imagedraw_polygon.png")) def test_polygon1(self): self.helper_polygon(POINTS1) @@ -145,8 +142,7 @@ class TestImageDraw(PillowTestCase): draw.rectangle(bbox, pen, brush) # Assert - self.assert_image_equal( - im, Image.open("Tests/images/imagedraw_rectangle.png")) + self.assert_image_equal(im, Image.open("Tests/images/imagedraw_rectangle.png")) def test_rectangle1(self): self.helper_rectangle(BBOX1) diff --git a/Tests/test_imageenhance.py b/Tests/test_imageenhance.py index 0e4e8c4f3..4f7c90559 100644 --- a/Tests/test_imageenhance.py +++ b/Tests/test_imageenhance.py @@ -5,7 +5,6 @@ from PIL import ImageEnhance class TestImageEnhance(PillowTestCase): - def test_sanity(self): # FIXME: assert_image @@ -23,10 +22,10 @@ class TestImageEnhance(PillowTestCase): def _half_transparent_image(self): # returns an image, half transparent, half solid - im = hopper('RGB') + im = hopper("RGB") - transparent = Image.new('L', im.size, 0) - solid = Image.new('L', (im.size[0]//2, im.size[1]), 255) + transparent = Image.new("L", im.size, 0) + solid = Image.new("L", (im.size[0] // 2, im.size[1]), 255) transparent.paste(solid, (0, 0)) im.putalpha(transparent) @@ -34,8 +33,11 @@ class TestImageEnhance(PillowTestCase): def _check_alpha(self, im, original, op, amount): self.assertEqual(im.getbands(), original.getbands()) - self.assert_image_equal(im.getchannel('A'), original.getchannel('A'), - "Diff on %s: %s" % (op, amount)) + self.assert_image_equal( + im.getchannel("A"), + original.getchannel("A"), + "Diff on %s: %s" % (op, amount), + ) def test_alpha(self): # Issue https://github.com/python-pillow/Pillow/issues/899 @@ -43,8 +45,11 @@ class TestImageEnhance(PillowTestCase): original = self._half_transparent_image() - for op in ['Color', 'Brightness', 'Contrast', 'Sharpness']: + for op in ["Color", "Brightness", "Contrast", "Sharpness"]: for amount in [0, 0.5, 1.0]: self._check_alpha( getattr(ImageEnhance, op)(original).enhance(amount), - original, op, amount) + original, + op, + amount, + ) diff --git a/Tests/test_imagefile.py b/Tests/test_imagefile.py index e2339d76d..83170cb2a 100644 --- a/Tests/test_imagefile.py +++ b/Tests/test_imagefile.py @@ -8,6 +8,7 @@ from PIL import EpsImagePlugin try: from PIL import _webp + HAVE_WEBP = True except ImportError: HAVE_WEBP = False @@ -21,9 +22,7 @@ SAFEBLOCK = ImageFile.SAFEBLOCK class TestImageFile(PillowTestCase): - def test_parser(self): - def roundtrip(format): im = hopper("L").resize((1000, 1000)) @@ -44,7 +43,7 @@ class TestImageFile(PillowTestCase): self.assert_image_equal(*roundtrip("BMP")) im1, im2 = roundtrip("GIF") - self.assert_image_similar(im1.convert('P'), im2, 1) + self.assert_image_similar(im1.convert("P"), im2, 1) self.assert_image_equal(*roundtrip("IM")) self.assert_image_equal(*roundtrip("MSP")) if "zip_encoder" in codecs: @@ -69,7 +68,7 @@ class TestImageFile(PillowTestCase): # md5sum: ba974835ff2d6f3f2fd0053a23521d4a # EPS comes back in RGB: - self.assert_image_similar(im1, im2.convert('L'), 20) + self.assert_image_similar(im1, im2.convert("L"), 20) if "jpeg_encoder" in codecs: im1, im2 = roundtrip("JPEG") # lossy compression @@ -78,7 +77,7 @@ class TestImageFile(PillowTestCase): self.assertRaises(IOError, roundtrip, "PDF") def test_ico(self): - with open('Tests/images/python.ico', 'rb') as f: + with open("Tests/images/python.ico", "rb") as f: data = f.read() with ImageFile.Parser() as p: p.feed(data) @@ -158,14 +157,13 @@ xoff, yoff, xsize, ysize = 10, 20, 100, 100 class MockImageFile(ImageFile.ImageFile): def _open(self): - self.rawmode = 'RGBA' - self.mode = 'RGBA' + self.rawmode = "RGBA" + self.mode = "RGBA" self._size = (200, 200) - self.tile = [("MOCK", (xoff, yoff, xoff+xsize, yoff+ysize), 32, None)] + self.tile = [("MOCK", (xoff, yoff, xoff + xsize, yoff + ysize), 32, None)] class TestPyDecoder(PillowTestCase): - def get_decoder(self): decoder = MockPyDecoder(None) @@ -173,11 +171,11 @@ class TestPyDecoder(PillowTestCase): decoder.__init__(mode, *args) return decoder - Image.register_decoder('MOCK', closure) + Image.register_decoder("MOCK", closure) return decoder def test_setimage(self): - buf = BytesIO(b'\x00'*255) + buf = BytesIO(b"\x00" * 255) im = MockImageFile(buf) d = self.get_decoder() @@ -189,10 +187,10 @@ class TestPyDecoder(PillowTestCase): self.assertEqual(d.state.xsize, xsize) self.assertEqual(d.state.ysize, ysize) - self.assertRaises(ValueError, d.set_as_raw, b'\x00') + self.assertRaises(ValueError, d.set_as_raw, b"\x00") def test_extents_none(self): - buf = BytesIO(b'\x00'*255) + buf = BytesIO(b"\x00" * 255) im = MockImageFile(buf) im.tile = [("MOCK", None, 32, None)] @@ -206,35 +204,31 @@ class TestPyDecoder(PillowTestCase): self.assertEqual(d.state.ysize, 200) def test_negsize(self): - buf = BytesIO(b'\x00'*255) + buf = BytesIO(b"\x00" * 255) im = MockImageFile(buf) - im.tile = [("MOCK", (xoff, yoff, -10, yoff+ysize), 32, None)] + im.tile = [("MOCK", (xoff, yoff, -10, yoff + ysize), 32, None)] self.get_decoder() self.assertRaises(ValueError, im.load) - im.tile = [("MOCK", (xoff, yoff, xoff+xsize, -10), 32, None)] + im.tile = [("MOCK", (xoff, yoff, xoff + xsize, -10), 32, None)] self.assertRaises(ValueError, im.load) def test_oversize(self): - buf = BytesIO(b'\x00'*255) + buf = BytesIO(b"\x00" * 255) im = MockImageFile(buf) - im.tile = [ - ("MOCK", (xoff, yoff, xoff+xsize + 100, yoff+ysize), 32, None) - ] + im.tile = [("MOCK", (xoff, yoff, xoff + xsize + 100, yoff + ysize), 32, None)] self.get_decoder() self.assertRaises(ValueError, im.load) - im.tile = [ - ("MOCK", (xoff, yoff, xoff+xsize, yoff+ysize + 100), 32, None) - ] + im.tile = [("MOCK", (xoff, yoff, xoff + xsize, yoff + ysize + 100), 32, None)] self.assertRaises(ValueError, im.load) def test_no_format(self): - buf = BytesIO(b'\x00'*255) + buf = BytesIO(b"\x00" * 255) im = MockImageFile(buf) self.assertIsNone(im.format) @@ -248,7 +242,7 @@ class TestPyDecoder(PillowTestCase): self.assertEqual(exif[40963], 450) self.assertEqual(exif[11], "gThumb 3.0.1") - out = self.tempfile('temp.jpg') + out = self.tempfile("temp.jpg") exif[258] = 8 del exif[40960] exif[40963] = 455 @@ -268,7 +262,7 @@ class TestPyDecoder(PillowTestCase): self.assertEqual(exif[40963], 200) self.assertEqual(exif[305], "Adobe Photoshop CC 2017 (Macintosh)") - out = self.tempfile('temp.jpg') + out = self.tempfile("temp.jpg") exif[258] = 8 del exif[34665] exif[40963] = 455 @@ -281,14 +275,16 @@ class TestPyDecoder(PillowTestCase): self.assertEqual(reloaded_exif[40963], 455) self.assertEqual(exif[305], "Pillow test") - @unittest.skipIf(not HAVE_WEBP or not _webp.HAVE_WEBPANIM, - "WebP support not installed with animation") + @unittest.skipIf( + not HAVE_WEBP or not _webp.HAVE_WEBPANIM, + "WebP support not installed with animation", + ) def test_exif_webp(self): im = Image.open("Tests/images/hopper.webp") exif = im.getexif() self.assertEqual(exif, {}) - out = self.tempfile('temp.webp') + out = self.tempfile("temp.webp") exif[258] = 8 exif[40963] = 455 exif[305] = "Pillow test" @@ -299,6 +295,7 @@ class TestPyDecoder(PillowTestCase): self.assertEqual(reloaded_exif[258], 8) self.assertEqual(reloaded_exif[40963], 455) self.assertEqual(exif[305], "Pillow test") + im.save(out, exif=exif) check_exif() im.save(out, exif=exif, save_all=True) @@ -309,7 +306,7 @@ class TestPyDecoder(PillowTestCase): exif = im.getexif() self.assertEqual(exif, {274: 1}) - out = self.tempfile('temp.png') + out = self.tempfile("temp.png") exif[258] = 8 del exif[274] exif[40963] = 455 @@ -318,18 +315,11 @@ class TestPyDecoder(PillowTestCase): reloaded = Image.open(out) reloaded_exif = reloaded.getexif() - self.assertEqual(reloaded_exif, { - 258: 8, - 40963: 455, - 305: 'Pillow test', - }) + self.assertEqual(reloaded_exif, {258: 8, 40963: 455, 305: "Pillow test"}) def test_exif_interop(self): im = Image.open("Tests/images/flower.jpg") exif = im.getexif() - self.assertEqual(exif.get_ifd(0xa005), { - 1: 'R98', - 2: b'0100', - 4097: 2272, - 4098: 1704, - }) + self.assertEqual( + exif.get_ifd(0xA005), {1: "R98", 2: b"0100", 4097: 2272, 4098: 1704} + ) diff --git a/Tests/test_imagefont.py b/Tests/test_imagefont.py index 33f37916c..0ee3b979e 100644 --- a/Tests/test_imagefont.py +++ b/Tests/test_imagefont.py @@ -7,6 +7,7 @@ import os import sys import copy import re +import shutil import distutils.version FONT_PATH = "Tests/fonts/FreeMono.ttf" @@ -14,8 +15,8 @@ FONT_SIZE = 20 TEST_TEXT = "hey you\nyou are awesome\nthis looks awkward" -HAS_FREETYPE = features.check('freetype2') -HAS_RAQM = features.check('raqm') +HAS_FREETYPE = features.check("freetype2") +HAS_RAQM = features.check("raqm") class SimplePatcher(object): @@ -51,32 +52,24 @@ class TestImageFont(PillowTestCase): # Freetype has different metrics depending on the version. # (and, other things, but first things first) METRICS = { - ('>=2.3', '<2.4'): { - 'multiline': 30, - 'textsize': 12, - 'getters': (13, 16)}, - ('>=2.7',): { - 'multiline': 6.2, - 'textsize': 2.5, - 'getters': (12, 16)}, - 'Default': { - 'multiline': 0.5, - 'textsize': 0.5, - 'getters': (12, 16)}, - } + (">=2.3", "<2.4"): {"multiline": 30, "textsize": 12, "getters": (13, 16)}, + (">=2.7",): {"multiline": 6.2, "textsize": 2.5, "getters": (12, 16)}, + "Default": {"multiline": 0.5, "textsize": 0.5, "getters": (12, 16)}, + } def setUp(self): freetype = distutils.version.StrictVersion(ImageFont.core.freetype2_version) - self.metrics = self.METRICS['Default'] + self.metrics = self.METRICS["Default"] for conditions, metrics in self.METRICS.items(): if not isinstance(conditions, tuple): continue for condition in conditions: - version = re.sub('[<=>]', '', condition) - if (condition.startswith('>=') and freetype >= version) or \ - (condition.startswith('<') and freetype < version): + version = re.sub("[<=>]", "", condition) + if (condition.startswith(">=") and freetype >= version) or ( + condition.startswith("<") and freetype < version + ): # Condition was met continue @@ -87,8 +80,9 @@ class TestImageFont(PillowTestCase): self.metrics = metrics def get_font(self): - return ImageFont.truetype(FONT_PATH, FONT_SIZE, - layout_engine=self.LAYOUT_ENGINE) + return ImageFont.truetype( + FONT_PATH, FONT_SIZE, layout_engine=self.LAYOUT_ENGINE + ) def test_sanity(self): self.assertRegex(ImageFont.core.freetype2_version, r"\d+\.\d+\.\d+$") @@ -102,8 +96,8 @@ class TestImageFont(PillowTestCase): self.assertEqual(ttf_copy.path, FONT_PATH) self.assertEqual(ttf_copy.size, FONT_SIZE) - ttf_copy = ttf.font_variant(size=FONT_SIZE+1) - self.assertEqual(ttf_copy.size, FONT_SIZE+1) + ttf_copy = ttf.font_variant(size=FONT_SIZE + 1) + self.assertEqual(ttf_copy.size, FONT_SIZE + 1) second_font_path = "Tests/fonts/DejaVuSans.ttf" ttf_copy = ttf.font_variant(font=second_font_path) @@ -114,13 +108,14 @@ class TestImageFont(PillowTestCase): self._render(FONT_PATH) def _font_as_bytes(self): - with open(FONT_PATH, 'rb') as f: + with open(FONT_PATH, "rb") as f: font_bytes = BytesIO(f.read()) return font_bytes def test_font_with_filelike(self): - ImageFont.truetype(self._font_as_bytes(), FONT_SIZE, - layout_engine=self.LAYOUT_ENGINE) + ImageFont.truetype( + self._font_as_bytes(), FONT_SIZE, layout_engine=self.LAYOUT_ENGINE + ) self._render(self._font_as_bytes()) # Usage note: making two fonts from the same buffer fails. # shared_bytes = self._font_as_bytes() @@ -128,31 +123,52 @@ class TestImageFont(PillowTestCase): # self.assertRaises(Exception, _render, shared_bytes) def test_font_with_open_file(self): - with open(FONT_PATH, 'rb') as f: + with open(FONT_PATH, "rb") as f: self._render(f) + def test_non_unicode_path(self): + try: + tempfile = self.tempfile("temp_" + chr(128) + ".ttf") + except UnicodeEncodeError: + self.skipTest("Unicode path could not be created") + shutil.copy(FONT_PATH, tempfile) + + ImageFont.truetype(tempfile, FONT_SIZE) + + def test_unavailable_layout_engine(self): + have_raqm = ImageFont.core.HAVE_RAQM + ImageFont.core.HAVE_RAQM = False + + try: + ttf = ImageFont.truetype( + FONT_PATH, FONT_SIZE, layout_engine=ImageFont.LAYOUT_RAQM + ) + finally: + ImageFont.core.HAVE_RAQM = have_raqm + + self.assertEqual(ttf.layout_engine, ImageFont.LAYOUT_BASIC) + def _render(self, font): txt = "Hello World!" - ttf = ImageFont.truetype(font, FONT_SIZE, - layout_engine=self.LAYOUT_ENGINE) + ttf = ImageFont.truetype(font, FONT_SIZE, layout_engine=self.LAYOUT_ENGINE) ttf.getsize(txt) img = Image.new("RGB", (256, 64), "white") d = ImageDraw.Draw(img) - d.text((10, 10), txt, font=ttf, fill='black') + d.text((10, 10), txt, font=ttf, fill="black") return img def test_render_equal(self): img_path = self._render(FONT_PATH) - with open(FONT_PATH, 'rb') as f: + with open(FONT_PATH, "rb") as f: font_filelike = BytesIO(f.read()) img_filelike = self._render(font_filelike) self.assert_image_equal(img_path, img_filelike) def test_textsize_equal(self): - im = Image.new(mode='RGB', size=(300, 100)) + im = Image.new(mode="RGB", size=(300, 100)) draw = ImageDraw.Draw(im) ttf = self.get_font() @@ -161,96 +177,101 @@ class TestImageFont(PillowTestCase): draw.text((10, 10), txt, font=ttf) draw.rectangle((10, 10, 10 + size[0], 10 + size[1])) - target = 'Tests/images/rectangle_surrounding_text.png' + target = "Tests/images/rectangle_surrounding_text.png" target_img = Image.open(target) # Epsilon ~.5 fails with FreeType 2.7 - self.assert_image_similar(im, target_img, self.metrics['textsize']) + self.assert_image_similar(im, target_img, self.metrics["textsize"]) def test_render_multiline(self): - im = Image.new(mode='RGB', size=(300, 100)) + im = Image.new(mode="RGB", size=(300, 100)) draw = ImageDraw.Draw(im) ttf = self.get_font() - line_spacing = draw.textsize('A', font=ttf)[1] + 4 + line_spacing = draw.textsize("A", font=ttf)[1] + 4 lines = TEST_TEXT.split("\n") y = 0 for line in lines: draw.text((0, y), line, font=ttf) y += line_spacing - target = 'Tests/images/multiline_text.png' + target = "Tests/images/multiline_text.png" target_img = Image.open(target) # some versions of freetype have different horizontal spacing. # setting a tight epsilon, I'm showing the original test failure # at epsilon = ~38. - self.assert_image_similar(im, target_img, self.metrics['multiline']) + self.assert_image_similar(im, target_img, self.metrics["multiline"]) def test_render_multiline_text(self): ttf = self.get_font() # Test that text() correctly connects to multiline_text() # and that align defaults to left - im = Image.new(mode='RGB', size=(300, 100)) + im = Image.new(mode="RGB", size=(300, 100)) draw = ImageDraw.Draw(im) draw.text((0, 0), TEST_TEXT, font=ttf) - target = 'Tests/images/multiline_text.png' + target = "Tests/images/multiline_text.png" target_img = Image.open(target) # Epsilon ~.5 fails with FreeType 2.7 - self.assert_image_similar(im, target_img, self.metrics['multiline']) + self.assert_image_similar(im, target_img, self.metrics["multiline"]) # Test that text() can pass on additional arguments # to multiline_text() - draw.text((0, 0), TEST_TEXT, fill=None, font=ttf, anchor=None, - spacing=4, align="left") + draw.text( + (0, 0), TEST_TEXT, fill=None, font=ttf, anchor=None, spacing=4, align="left" + ) draw.text((0, 0), TEST_TEXT, None, ttf, None, 4, "left") # Test align center and right - for align, ext in {"center": "_center", - "right": "_right"}.items(): - im = Image.new(mode='RGB', size=(300, 100)) + for align, ext in {"center": "_center", "right": "_right"}.items(): + im = Image.new(mode="RGB", size=(300, 100)) draw = ImageDraw.Draw(im) draw.multiline_text((0, 0), TEST_TEXT, font=ttf, align=align) - target = 'Tests/images/multiline_text'+ext+'.png' + target = "Tests/images/multiline_text" + ext + ".png" target_img = Image.open(target) # Epsilon ~.5 fails with FreeType 2.7 - self.assert_image_similar(im, target_img, - self.metrics['multiline']) + self.assert_image_similar(im, target_img, self.metrics["multiline"]) def test_unknown_align(self): - im = Image.new(mode='RGB', size=(300, 100)) + im = Image.new(mode="RGB", size=(300, 100)) draw = ImageDraw.Draw(im) ttf = self.get_font() # Act/Assert self.assertRaises( ValueError, - draw.multiline_text, (0, 0), TEST_TEXT, font=ttf, align="unknown") + draw.multiline_text, + (0, 0), + TEST_TEXT, + font=ttf, + align="unknown", + ) def test_draw_align(self): - im = Image.new('RGB', (300, 100), 'white') + im = Image.new("RGB", (300, 100), "white") draw = ImageDraw.Draw(im) ttf = self.get_font() line = "some text" - draw.text((100, 40), line, (0, 0, 0), font=ttf, align='left') + draw.text((100, 40), line, (0, 0, 0), font=ttf, align="left") def test_multiline_size(self): ttf = self.get_font() - im = Image.new(mode='RGB', size=(300, 100)) + im = Image.new(mode="RGB", size=(300, 100)) draw = ImageDraw.Draw(im) # Test that textsize() correctly connects to multiline_textsize() - self.assertEqual(draw.textsize(TEST_TEXT, font=ttf), - draw.multiline_textsize(TEST_TEXT, font=ttf)) + self.assertEqual( + draw.textsize(TEST_TEXT, font=ttf), + draw.multiline_textsize(TEST_TEXT, font=ttf), + ) # Test that multiline_textsize corresponds to ImageFont.textsize() # for single line text - self.assertEqual(ttf.getsize('A'), - draw.multiline_textsize('A', font=ttf)) + self.assertEqual(ttf.getsize("A"), draw.multiline_textsize("A", font=ttf)) # Test that textsize() can pass on additional arguments # to multiline_textsize() @@ -259,25 +280,26 @@ class TestImageFont(PillowTestCase): def test_multiline_width(self): ttf = self.get_font() - im = Image.new(mode='RGB', size=(300, 100)) + im = Image.new(mode="RGB", size=(300, 100)) draw = ImageDraw.Draw(im) - self.assertEqual(draw.textsize("longest line", font=ttf)[0], - draw.multiline_textsize("longest line\nline", - font=ttf)[0]) + self.assertEqual( + draw.textsize("longest line", font=ttf)[0], + draw.multiline_textsize("longest line\nline", font=ttf)[0], + ) def test_multiline_spacing(self): ttf = self.get_font() - im = Image.new(mode='RGB', size=(300, 100)) + im = Image.new(mode="RGB", size=(300, 100)) draw = ImageDraw.Draw(im) draw.multiline_text((0, 0), TEST_TEXT, font=ttf, spacing=10) - target = 'Tests/images/multiline_text_spacing.png' + target = "Tests/images/multiline_text_spacing.png" target_img = Image.open(target) # Epsilon ~.5 fails with FreeType 2.7 - self.assert_image_similar(im, target_img, self.metrics['multiline']) + self.assert_image_similar(im, target_img, self.metrics["multiline"]) def test_rotated_transposed_font(self): img_grey = Image.new("L", (100, 100)) @@ -286,8 +308,7 @@ class TestImageFont(PillowTestCase): font = self.get_font() orientation = Image.ROTATE_90 - transposed_font = ImageFont.TransposedFont( - font, orientation=orientation) + transposed_font = ImageFont.TransposedFont(font, orientation=orientation) # Original font draw.font = font @@ -308,8 +329,7 @@ class TestImageFont(PillowTestCase): font = self.get_font() orientation = None - transposed_font = ImageFont.TransposedFont( - font, orientation=orientation) + transposed_font = ImageFont.TransposedFont(font, orientation=orientation) # Original font draw.font = font @@ -327,8 +347,7 @@ class TestImageFont(PillowTestCase): text = "mask this" font = self.get_font() orientation = Image.ROTATE_90 - transposed_font = ImageFont.TransposedFont( - font, orientation=orientation) + transposed_font = ImageFont.TransposedFont(font, orientation=orientation) # Act mask = transposed_font.getmask(text) @@ -341,8 +360,7 @@ class TestImageFont(PillowTestCase): text = "mask this" font = self.get_font() orientation = None - transposed_font = ImageFont.TransposedFont( - font, orientation=orientation) + transposed_font = ImageFont.TransposedFont(font, orientation=orientation) # Act mask = transposed_font.getmask(text) @@ -358,7 +376,7 @@ class TestImageFont(PillowTestCase): name = font.getname() # Assert - self.assertEqual(('FreeMono', 'Regular'), name) + self.assertEqual(("FreeMono", "Regular"), name) def test_free_type_font_get_metrics(self): # Arrange @@ -400,14 +418,19 @@ class TestImageFont(PillowTestCase): # Act/Assert self.assertRaises(IOError, ImageFont.load_path, filename) + self.assertRaises(IOError, ImageFont.truetype, filename) + + def test_load_non_font_bytes(self): + with open("Tests/images/hopper.jpg", "rb") as f: + self.assertRaises(IOError, ImageFont.truetype, f) def test_default_font(self): # Arrange txt = 'This is a "better than nothing" default font.' - im = Image.new(mode='RGB', size=(300, 100)) + im = Image.new(mode="RGB", size=(300, 100)) draw = ImageDraw.Draw(im) - target = 'Tests/images/default_font.png' + target = "Tests/images/default_font.png" target_img = Image.open(target) # Act @@ -421,16 +444,16 @@ class TestImageFont(PillowTestCase): # issue #2614 font = self.get_font() # should not crash. - self.assertEqual((0, 0), font.getsize('')) + self.assertEqual((0, 0), font.getsize("")) def test_render_empty(self): # issue 2666 font = self.get_font() - im = Image.new(mode='RGB', size=(300, 100)) + im = Image.new(mode="RGB", size=(300, 100)) target = im.copy() draw = ImageDraw.Draw(im) # should not crash here. - draw.text((10, 10), '', font=font) + draw.text((10, 10), "", font=font) self.assert_image_equal(im, target) def test_unicode_pilfont(self): @@ -443,76 +466,99 @@ class TestImageFont(PillowTestCase): def _test_fake_loading_font(self, path_to_fake, fontname): # Make a copy of FreeTypeFont so we can patch the original free_type_font = copy.deepcopy(ImageFont.FreeTypeFont) - with SimplePatcher(ImageFont, '_FreeTypeFont', free_type_font): - def loadable_font(filepath, size, index, encoding, - *args, **kwargs): + with SimplePatcher(ImageFont, "_FreeTypeFont", free_type_font): + + def loadable_font(filepath, size, index, encoding, *args, **kwargs): if filepath == path_to_fake: - return ImageFont._FreeTypeFont(FONT_PATH, size, index, - encoding, *args, **kwargs) - return ImageFont._FreeTypeFont(filepath, size, index, - encoding, *args, **kwargs) - with SimplePatcher(ImageFont, 'FreeTypeFont', loadable_font): + return ImageFont._FreeTypeFont( + FONT_PATH, size, index, encoding, *args, **kwargs + ) + return ImageFont._FreeTypeFont( + filepath, size, index, encoding, *args, **kwargs + ) + + with SimplePatcher(ImageFont, "FreeTypeFont", loadable_font): font = ImageFont.truetype(fontname) # Make sure it's loaded name = font.getname() - self.assertEqual(('FreeMono', 'Regular'), name) + self.assertEqual(("FreeMono", "Regular"), name) - @unittest.skipIf(sys.platform.startswith('win32'), - "requires Unix or macOS") + @unittest.skipIf(sys.platform.startswith("win32"), "requires Unix or macOS") def test_find_linux_font(self): # A lot of mocking here - this is more for hitting code and # catching syntax like errors - font_directory = '/usr/local/share/fonts' - with SimplePatcher(sys, 'platform', 'linux'): + font_directory = "/usr/local/share/fonts" + with SimplePatcher(sys, "platform", "linux"): patched_env = copy.deepcopy(os.environ) - patched_env['XDG_DATA_DIRS'] = '/usr/share/:/usr/local/share/' - with SimplePatcher(os, 'environ', patched_env): + patched_env["XDG_DATA_DIRS"] = "/usr/share/:/usr/local/share/" + with SimplePatcher(os, "environ", patched_env): + def fake_walker(path): if path == font_directory: - return [(path, [], [ - 'Arial.ttf', 'Single.otf', 'Duplicate.otf', - 'Duplicate.ttf'], )] - return [(path, [], ['some_random_font.ttf'], )] - with SimplePatcher(os, 'walk', fake_walker): + return [ + ( + path, + [], + [ + "Arial.ttf", + "Single.otf", + "Duplicate.otf", + "Duplicate.ttf", + ], + ) + ] + return [(path, [], ["some_random_font.ttf"])] + + with SimplePatcher(os, "walk", fake_walker): # Test that the font loads both with and without the # extension self._test_fake_loading_font( - font_directory+'/Arial.ttf', 'Arial.ttf') - self._test_fake_loading_font( - font_directory+'/Arial.ttf', 'Arial') + font_directory + "/Arial.ttf", "Arial.ttf" + ) + self._test_fake_loading_font(font_directory + "/Arial.ttf", "Arial") # Test that non-ttf fonts can be found without the # extension self._test_fake_loading_font( - font_directory+'/Single.otf', 'Single') + font_directory + "/Single.otf", "Single" + ) # Test that ttf fonts are preferred if the extension is # not specified self._test_fake_loading_font( - font_directory+'/Duplicate.ttf', 'Duplicate') + font_directory + "/Duplicate.ttf", "Duplicate" + ) - @unittest.skipIf(sys.platform.startswith('win32'), - "requires Unix or macOS") + @unittest.skipIf(sys.platform.startswith("win32"), "requires Unix or macOS") def test_find_macos_font(self): # Like the linux test, more cover hitting code rather than testing # correctness. - font_directory = '/System/Library/Fonts' - with SimplePatcher(sys, 'platform', 'darwin'): + font_directory = "/System/Library/Fonts" + with SimplePatcher(sys, "platform", "darwin"): + def fake_walker(path): if path == font_directory: - return [(path, [], - ['Arial.ttf', 'Single.otf', - 'Duplicate.otf', 'Duplicate.ttf'], )] - return [(path, [], ['some_random_font.ttf'], )] - with SimplePatcher(os, 'walk', fake_walker): + return [ + ( + path, + [], + [ + "Arial.ttf", + "Single.otf", + "Duplicate.otf", + "Duplicate.ttf", + ], + ) + ] + return [(path, [], ["some_random_font.ttf"])] + + with SimplePatcher(os, "walk", fake_walker): + self._test_fake_loading_font(font_directory + "/Arial.ttf", "Arial.ttf") + self._test_fake_loading_font(font_directory + "/Arial.ttf", "Arial") + self._test_fake_loading_font(font_directory + "/Single.otf", "Single") self._test_fake_loading_font( - font_directory+'/Arial.ttf', 'Arial.ttf') - self._test_fake_loading_font( - font_directory+'/Arial.ttf', 'Arial') - self._test_fake_loading_font( - font_directory+'/Single.otf', 'Single') - self._test_fake_loading_font( - font_directory+'/Duplicate.ttf', 'Duplicate') + font_directory + "/Duplicate.ttf", "Duplicate" + ) def test_imagefont_getters(self): # Arrange @@ -526,26 +572,144 @@ class TestImageFont(PillowTestCase): self.assertEqual(t.font.x_ppem, 20) self.assertEqual(t.font.y_ppem, 20) self.assertEqual(t.font.glyphs, 4177) - self.assertEqual(t.getsize('A'), (12, 16)) - self.assertEqual(t.getsize('AB'), (24, 16)) - self.assertEqual(t.getsize('M'), self.metrics['getters']) - self.assertEqual(t.getsize('y'), (12, 20)) - self.assertEqual(t.getsize('a'), (12, 16)) - self.assertEqual(t.getsize_multiline('A'), (12, 16)) - self.assertEqual(t.getsize_multiline('AB'), (24, 16)) - self.assertEqual(t.getsize_multiline('a'), (12, 16)) - self.assertEqual(t.getsize_multiline('ABC\n'), (36, 36)) - self.assertEqual(t.getsize_multiline('ABC\nA'), (36, 36)) - self.assertEqual(t.getsize_multiline('ABC\nAaaa'), (48, 36)) + self.assertEqual(t.getsize("A"), (12, 16)) + self.assertEqual(t.getsize("AB"), (24, 16)) + self.assertEqual(t.getsize("M"), self.metrics["getters"]) + self.assertEqual(t.getsize("y"), (12, 20)) + self.assertEqual(t.getsize("a"), (12, 16)) + self.assertEqual(t.getsize_multiline("A"), (12, 16)) + self.assertEqual(t.getsize_multiline("AB"), (24, 16)) + self.assertEqual(t.getsize_multiline("a"), (12, 16)) + self.assertEqual(t.getsize_multiline("ABC\n"), (36, 36)) + self.assertEqual(t.getsize_multiline("ABC\nA"), (36, 36)) + self.assertEqual(t.getsize_multiline("ABC\nAaaa"), (48, 36)) def test_complex_font_settings(self): # Arrange t = self.get_font() # Act / Assert if t.layout_engine == ImageFont.LAYOUT_BASIC: - self.assertRaises(KeyError, t.getmask, 'абвг', direction='rtl') - self.assertRaises(KeyError, t.getmask, 'абвг', features=['-kern']) - self.assertRaises(KeyError, t.getmask, 'абвг', language='sr') + self.assertRaises(KeyError, t.getmask, "абвг", direction="rtl") + self.assertRaises(KeyError, t.getmask, "абвг", features=["-kern"]) + self.assertRaises(KeyError, t.getmask, "абвг", language="sr") + + def test_variation_get(self): + font = self.get_font() + + freetype = distutils.version.StrictVersion(ImageFont.core.freetype2_version) + if freetype < "2.9.1": + self.assertRaises(NotImplementedError, font.get_variation_names) + self.assertRaises(NotImplementedError, font.get_variation_axes) + return + + self.assertRaises(IOError, font.get_variation_names) + self.assertRaises(IOError, font.get_variation_axes) + + font = ImageFont.truetype("Tests/fonts/AdobeVFPrototype.ttf") + self.assertEqual( + font.get_variation_names(), + [ + b"ExtraLight", + b"Light", + b"Regular", + b"Semibold", + b"Bold", + b"Black", + b"Black Medium Contrast", + b"Black High Contrast", + b"Default", + ], + ) + self.assertEqual( + font.get_variation_axes(), + [ + {"name": b"Weight", "minimum": 200, "maximum": 900, "default": 389}, + {"name": b"Contrast", "minimum": 0, "maximum": 100, "default": 0}, + ], + ) + + font = ImageFont.truetype("Tests/fonts/TINY5x3GX.ttf") + self.assertEqual( + font.get_variation_names(), + [ + b"20", + b"40", + b"60", + b"80", + b"100", + b"120", + b"140", + b"160", + b"180", + b"200", + b"220", + b"240", + b"260", + b"280", + b"300", + b"Regular", + ], + ) + self.assertEqual( + font.get_variation_axes(), + [{"name": b"Size", "minimum": 0, "maximum": 300, "default": 0}], + ) + + def test_variation_set_by_name(self): + font = self.get_font() + + freetype = distutils.version.StrictVersion(ImageFont.core.freetype2_version) + if freetype < "2.9.1": + self.assertRaises(NotImplementedError, font.set_variation_by_name, "Bold") + return + + self.assertRaises(IOError, font.set_variation_by_name, "Bold") + + def _check_text(font, path, epsilon): + im = Image.new("RGB", (100, 75), "white") + d = ImageDraw.Draw(im) + d.text((10, 10), "Text", font=font, fill="black") + + expected = Image.open(path) + self.assert_image_similar(im, expected, epsilon) + + font = ImageFont.truetype("Tests/fonts/AdobeVFPrototype.ttf", 36) + _check_text(font, "Tests/images/variation_adobe.png", 11) + for name in ["Bold", b"Bold"]: + font.set_variation_by_name(name) + _check_text(font, "Tests/images/variation_adobe_name.png", 11) + + font = ImageFont.truetype("Tests/fonts/TINY5x3GX.ttf", 36) + _check_text(font, "Tests/images/variation_tiny.png", 40) + for name in ["200", b"200"]: + font.set_variation_by_name(name) + _check_text(font, "Tests/images/variation_tiny_name.png", 40) + + def test_variation_set_by_axes(self): + font = self.get_font() + + freetype = distutils.version.StrictVersion(ImageFont.core.freetype2_version) + if freetype < "2.9.1": + self.assertRaises(NotImplementedError, font.set_variation_by_axes, [100]) + return + + self.assertRaises(IOError, font.set_variation_by_axes, [500, 50]) + + def _check_text(font, path, epsilon): + im = Image.new("RGB", (100, 75), "white") + d = ImageDraw.Draw(im) + d.text((10, 10), "Text", font=font, fill="black") + + expected = Image.open(path) + self.assert_image_similar(im, expected, epsilon) + + font = ImageFont.truetype("Tests/fonts/AdobeVFPrototype.ttf", 36) + font.set_variation_by_axes([500, 50]) + _check_text(font, "Tests/images/variation_adobe_axes.png", 5.1) + + font = ImageFont.truetype("Tests/fonts/TINY5x3GX.ttf", 36) + font.set_variation_by_axes([100]) + _check_text(font, "Tests/images/variation_tiny_axes.png", 32.5) @unittest.skipUnless(HAS_RAQM, "Raqm not Available") diff --git a/Tests/test_imagefont_bitmap.py b/Tests/test_imagefont_bitmap.py index eb44957e4..5a8ee2e08 100644 --- a/Tests/test_imagefont_bitmap.py +++ b/Tests/test_imagefont_bitmap.py @@ -12,16 +12,18 @@ except ImportError: @unittest.skipIf(not image_font_installed, "image font not installed") class TestImageFontBitmap(PillowTestCase): def test_similar(self): - text = 'EmbeddedBitmap' - font_outline = ImageFont.truetype( - font='Tests/fonts/DejaVuSans.ttf', size=24) + text = "EmbeddedBitmap" + font_outline = ImageFont.truetype(font="Tests/fonts/DejaVuSans.ttf", size=24) font_bitmap = ImageFont.truetype( - font='Tests/fonts/DejaVuSans-bitmap.ttf', size=24) + font="Tests/fonts/DejaVuSans-bitmap.ttf", size=24 + ) size_outline = font_outline.getsize(text) size_bitmap = font_bitmap.getsize(text) - size_final = (max(size_outline[0], size_bitmap[0]), - max(size_outline[1], size_bitmap[1])) - im_bitmap = Image.new('RGB', size_final, (255, 255, 255)) + size_final = ( + max(size_outline[0], size_bitmap[0]), + max(size_outline[1], size_bitmap[1]), + ) + im_bitmap = Image.new("RGB", size_final, (255, 255, 255)) im_outline = im_bitmap.copy() draw_bitmap = ImageDraw.Draw(im_bitmap) draw_outline = ImageDraw.Draw(im_outline) @@ -29,8 +31,13 @@ class TestImageFontBitmap(PillowTestCase): # Metrics are different on the bitmap and ttf fonts, # more so on some platforms and versions of freetype than others. # Mac has a 1px difference, linux doesn't. - draw_bitmap.text((0, size_final[1] - size_bitmap[1]), - text, fill=(0, 0, 0), font=font_bitmap) - draw_outline.text((0, size_final[1] - size_outline[1]), - text, fill=(0, 0, 0), font=font_outline) + draw_bitmap.text( + (0, size_final[1] - size_bitmap[1]), text, fill=(0, 0, 0), font=font_bitmap + ) + draw_outline.text( + (0, size_final[1] - size_outline[1]), + text, + fill=(0, 0, 0), + font=font_outline, + ) self.assert_image_similar(im_bitmap, im_outline, 20) diff --git a/Tests/test_imagefontctl.py b/Tests/test_imagefontctl.py index a00971058..3ec5e9055 100644 --- a/Tests/test_imagefontctl.py +++ b/Tests/test_imagefontctl.py @@ -7,37 +7,35 @@ FONT_SIZE = 20 FONT_PATH = "Tests/fonts/DejaVuSans.ttf" -@unittest.skipUnless(features.check('raqm'), "Raqm Library is not installed.") +@unittest.skipUnless(features.check("raqm"), "Raqm Library is not installed.") class TestImagecomplextext(PillowTestCase): - def test_english(self): # smoke test, this should not fail ttf = ImageFont.truetype(FONT_PATH, FONT_SIZE) - im = Image.new(mode='RGB', size=(300, 100)) + im = Image.new(mode="RGB", size=(300, 100)) draw = ImageDraw.Draw(im) - draw.text((0, 0), 'TEST', font=ttf, fill=500, direction='ltr') + draw.text((0, 0), "TEST", font=ttf, fill=500, direction="ltr") def test_complex_text(self): ttf = ImageFont.truetype(FONT_PATH, FONT_SIZE) - im = Image.new(mode='RGB', size=(300, 100)) + im = Image.new(mode="RGB", size=(300, 100)) draw = ImageDraw.Draw(im) - draw.text((0, 0), 'اهلا عمان', font=ttf, fill=500) + draw.text((0, 0), "اهلا عمان", font=ttf, fill=500) - target = 'Tests/images/test_text.png' + target = "Tests/images/test_text.png" target_img = Image.open(target) - self.assert_image_similar(im, target_img, .5) + self.assert_image_similar(im, target_img, 0.5) def test_y_offset(self): - ttf = ImageFont.truetype("Tests/fonts/NotoNastaliqUrdu-Regular.ttf", - FONT_SIZE) + ttf = ImageFont.truetype("Tests/fonts/NotoNastaliqUrdu-Regular.ttf", FONT_SIZE) - im = Image.new(mode='RGB', size=(300, 100)) + im = Image.new(mode="RGB", size=(300, 100)) draw = ImageDraw.Draw(im) - draw.text((0, 0), 'العالم العربي', font=ttf, fill=500) + draw.text((0, 0), "العالم العربي", font=ttf, fill=500) - target = 'Tests/images/test_y_offset.png' + target = "Tests/images/test_y_offset.png" target_img = Image.open(target) self.assert_image_similar(im, target_img, 1.7) @@ -45,101 +43,142 @@ class TestImagecomplextext(PillowTestCase): def test_complex_unicode_text(self): ttf = ImageFont.truetype(FONT_PATH, FONT_SIZE) - im = Image.new(mode='RGB', size=(300, 100)) + im = Image.new(mode="RGB", size=(300, 100)) draw = ImageDraw.Draw(im) - draw.text((0, 0), 'السلام عليكم', font=ttf, fill=500) + draw.text((0, 0), "السلام عليكم", font=ttf, fill=500) - target = 'Tests/images/test_complex_unicode_text.png' + target = "Tests/images/test_complex_unicode_text.png" target_img = Image.open(target) - self.assert_image_similar(im, target_img, .5) + self.assert_image_similar(im, target_img, 0.5) + + ttf = ImageFont.truetype("Tests/fonts/KhmerOSBattambang-Regular.ttf", FONT_SIZE) + + im = Image.new(mode="RGB", size=(300, 100)) + draw = ImageDraw.Draw(im) + draw.text((0, 0), "លោកុប្បត្តិ", font=ttf, fill=500) + + target = "Tests/images/test_complex_unicode_text2.png" + target_img = Image.open(target) + + self.assert_image_similar(im, target_img, 2.3) def test_text_direction_rtl(self): ttf = ImageFont.truetype(FONT_PATH, FONT_SIZE) - im = Image.new(mode='RGB', size=(300, 100)) + im = Image.new(mode="RGB", size=(300, 100)) draw = ImageDraw.Draw(im) - draw.text((0, 0), 'English عربي', font=ttf, fill=500, direction='rtl') + draw.text((0, 0), "English عربي", font=ttf, fill=500, direction="rtl") - target = 'Tests/images/test_direction_rtl.png' + target = "Tests/images/test_direction_rtl.png" target_img = Image.open(target) - self.assert_image_similar(im, target_img, .5) + self.assert_image_similar(im, target_img, 0.5) def test_text_direction_ltr(self): ttf = ImageFont.truetype(FONT_PATH, FONT_SIZE) - im = Image.new(mode='RGB', size=(300, 100)) + im = Image.new(mode="RGB", size=(300, 100)) draw = ImageDraw.Draw(im) - draw.text((0, 0), 'سلطنة عمان Oman', - font=ttf, fill=500, direction='ltr') + draw.text((0, 0), "سلطنة عمان Oman", font=ttf, fill=500, direction="ltr") - target = 'Tests/images/test_direction_ltr.png' + target = "Tests/images/test_direction_ltr.png" target_img = Image.open(target) - self.assert_image_similar(im, target_img, .5) + self.assert_image_similar(im, target_img, 0.5) def test_text_direction_rtl2(self): ttf = ImageFont.truetype(FONT_PATH, FONT_SIZE) - im = Image.new(mode='RGB', size=(300, 100)) + im = Image.new(mode="RGB", size=(300, 100)) draw = ImageDraw.Draw(im) - draw.text((0, 0), 'Oman سلطنة عمان', - font=ttf, fill=500, direction='rtl') + draw.text((0, 0), "Oman سلطنة عمان", font=ttf, fill=500, direction="rtl") - target = 'Tests/images/test_direction_ltr.png' + target = "Tests/images/test_direction_ltr.png" target_img = Image.open(target) - self.assert_image_similar(im, target_img, .5) + self.assert_image_similar(im, target_img, 0.5) + + def test_text_direction_ttb(self): + ttf = ImageFont.truetype("Tests/fonts/NotoSansJP-Regular.otf", FONT_SIZE) + + im = Image.new(mode="RGB", size=(100, 300)) + draw = ImageDraw.Draw(im) + try: + draw.text((0, 0), "English あい", font=ttf, fill=500, direction="ttb") + except ValueError as ex: + if str(ex) == "libraqm 0.7 or greater required for 'ttb' direction": + self.skipTest("libraqm 0.7 or greater not available") + + target = "Tests/images/test_direction_ttb.png" + target_img = Image.open(target) + + self.assert_image_similar(im, target_img, 1.15) def test_ligature_features(self): ttf = ImageFont.truetype(FONT_PATH, FONT_SIZE) - im = Image.new(mode='RGB', size=(300, 100)) + im = Image.new(mode="RGB", size=(300, 100)) draw = ImageDraw.Draw(im) - draw.text((0, 0), 'filling', font=ttf, fill=500, features=['-liga']) - target = 'Tests/images/test_ligature_features.png' + draw.text((0, 0), "filling", font=ttf, fill=500, features=["-liga"]) + target = "Tests/images/test_ligature_features.png" target_img = Image.open(target) - self.assert_image_similar(im, target_img, .5) + self.assert_image_similar(im, target_img, 0.5) - liga_size = ttf.getsize('fi', features=['-liga']) + liga_size = ttf.getsize("fi", features=["-liga"]) self.assertEqual(liga_size, (13, 19)) def test_kerning_features(self): ttf = ImageFont.truetype(FONT_PATH, FONT_SIZE) - im = Image.new(mode='RGB', size=(300, 100)) + im = Image.new(mode="RGB", size=(300, 100)) draw = ImageDraw.Draw(im) - draw.text((0, 0), 'TeToAV', font=ttf, fill=500, features=['-kern']) + draw.text((0, 0), "TeToAV", font=ttf, fill=500, features=["-kern"]) - target = 'Tests/images/test_kerning_features.png' + target = "Tests/images/test_kerning_features.png" target_img = Image.open(target) - self.assert_image_similar(im, target_img, .5) + self.assert_image_similar(im, target_img, 0.5) def test_arabictext_features(self): ttf = ImageFont.truetype(FONT_PATH, FONT_SIZE) - im = Image.new(mode='RGB', size=(300, 100)) + im = Image.new(mode="RGB", size=(300, 100)) draw = ImageDraw.Draw(im) - draw.text((0, 0), 'اللغة العربية', font=ttf, fill=500, - features=['-fina', '-init', '-medi']) + draw.text( + (0, 0), + "اللغة العربية", + font=ttf, + fill=500, + features=["-fina", "-init", "-medi"], + ) - target = 'Tests/images/test_arabictext_features.png' + target = "Tests/images/test_arabictext_features.png" target_img = Image.open(target) - self.assert_image_similar(im, target_img, .5) + self.assert_image_similar(im, target_img, 0.5) + + def test_x_max_and_y_offset(self): + ttf = ImageFont.truetype("Tests/fonts/ArefRuqaa-Regular.ttf", 40) + + im = Image.new(mode="RGB", size=(50, 100)) + draw = ImageDraw.Draw(im) + draw.text((0, 0), "لح", font=ttf, fill=500) + + target = "Tests/images/test_x_max_and_y_offset.png" + target_img = Image.open(target) + + self.assert_image_similar(im, target_img, 0.5) def test_language(self): ttf = ImageFont.truetype(FONT_PATH, FONT_SIZE) - im = Image.new(mode='RGB', size=(300, 100)) + im = Image.new(mode="RGB", size=(300, 100)) draw = ImageDraw.Draw(im) - draw.text((0, 0), 'абвг', font=ttf, fill=500, - language='sr') + draw.text((0, 0), "абвг", font=ttf, fill=500, language="sr") - target = 'Tests/images/test_language.png' + target = "Tests/images/test_language.png" target_img = Image.open(target) - self.assert_image_similar(im, target_img, .5) + self.assert_image_similar(im, target_img, 0.5) diff --git a/Tests/test_imagegrab.py b/Tests/test_imagegrab.py index a2e7a028d..9ead827e0 100644 --- a/Tests/test_imagegrab.py +++ b/Tests/test_imagegrab.py @@ -7,34 +7,37 @@ try: from PIL import ImageGrab class TestImageGrab(PillowTestCase): - def test_grab(self): - im = ImageGrab.grab() - self.assert_image(im, im.mode, im.size) + for im in [ImageGrab.grab(), ImageGrab.grab(include_layered_windows=True)]: + self.assert_image(im, im.mode, im.size) def test_grabclipboard(self): if sys.platform == "darwin": - subprocess.call(['screencapture', '-cx']) + subprocess.call(["screencapture", "-cx"]) else: - p = subprocess.Popen(['powershell', '-command', '-'], - stdin=subprocess.PIPE) - p.stdin.write(b'''[Reflection.Assembly]::LoadWithPartialName("System.Drawing") + 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)''') +[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): self.skipTest("ImportError") class TestImageGrabImport(PillowTestCase): - def test_import(self): # Arrange exception = None @@ -42,6 +45,7 @@ class TestImageGrabImport(PillowTestCase): # Act try: from PIL import ImageGrab + ImageGrab.__name__ # dummy to prevent Pyflakes warning except Exception as e: exception = e @@ -51,5 +55,4 @@ class TestImageGrabImport(PillowTestCase): self.assertIsNone(exception) else: self.assertIsInstance(exception, ImportError) - self.assertEqual(str(exception), - "ImageGrab is macOS and Windows only") + self.assertEqual(str(exception), "ImageGrab is macOS and Windows only") diff --git a/Tests/test_imagemath.py b/Tests/test_imagemath.py index 8273b63c1..a1a9bad0f 100644 --- a/Tests/test_imagemath.py +++ b/Tests/test_imagemath.py @@ -27,15 +27,13 @@ images = {"A": A, "B": B, "F": F, "I": I} class TestImageMath(PillowTestCase): - def test_sanity(self): self.assertEqual(ImageMath.eval("1"), 1) self.assertEqual(ImageMath.eval("1+A", A=2), 3) self.assertEqual(pixel(ImageMath.eval("A+B", A=A, B=B)), "I 3") self.assertEqual(pixel(ImageMath.eval("A+B", images)), "I 3") self.assertEqual(pixel(ImageMath.eval("float(A)+B", images)), "F 3.0") - self.assertEqual(pixel( - ImageMath.eval("int(float(A)+B)", images)), "I 3") + self.assertEqual(pixel(ImageMath.eval("int(float(A)+B)", images)), "I 3") def test_ops(self): @@ -47,16 +45,16 @@ class TestImageMath(PillowTestCase): self.assertEqual(pixel(ImageMath.eval("A*B", images)), "I 2") self.assertEqual(pixel(ImageMath.eval("A/B", images)), "I 0") self.assertEqual(pixel(ImageMath.eval("B**2", images)), "I 4") - self.assertEqual(pixel( - ImageMath.eval("B**33", images)), "I 2147483647") + self.assertEqual(pixel(ImageMath.eval("B**33", images)), "I 2147483647") self.assertEqual(pixel(ImageMath.eval("float(A)+B", images)), "F 3.0") self.assertEqual(pixel(ImageMath.eval("float(A)-B", images)), "F -1.0") self.assertEqual(pixel(ImageMath.eval("float(A)*B", images)), "F 2.0") self.assertEqual(pixel(ImageMath.eval("float(A)/B", images)), "F 0.5") self.assertEqual(pixel(ImageMath.eval("float(B)**2", images)), "F 4.0") - self.assertEqual(pixel( - ImageMath.eval("float(B)**33", images)), "F 8589934592.0") + self.assertEqual( + pixel(ImageMath.eval("float(B)**33", images)), "F 8589934592.0" + ) def test_logical(self): self.assertEqual(pixel(ImageMath.eval("not A", images)), 0) @@ -64,12 +62,11 @@ class TestImageMath(PillowTestCase): self.assertEqual(pixel(ImageMath.eval("A or B", images)), "L 1") def test_convert(self): - self.assertEqual(pixel( - ImageMath.eval("convert(A+B, 'L')", images)), "L 3") - self.assertEqual(pixel( - ImageMath.eval("convert(A+B, '1')", images)), "1 0") - self.assertEqual(pixel( - ImageMath.eval("convert(A+B, 'RGB')", images)), "RGB (3, 3, 3)") + self.assertEqual(pixel(ImageMath.eval("convert(A+B, 'L')", images)), "L 3") + self.assertEqual(pixel(ImageMath.eval("convert(A+B, '1')", images)), "1 0") + self.assertEqual( + pixel(ImageMath.eval("convert(A+B, 'RGB')", images)), "RGB (3, 3, 3)" + ) def test_compare(self): self.assertEqual(pixel(ImageMath.eval("min(A, B)", images)), "I 1") @@ -176,9 +173,6 @@ class TestImageMath(PillowTestCase): self.assertEqual(pixel(ImageMath.eval("notequal(A, A)", A=A)), "I 0") self.assertEqual(pixel(ImageMath.eval("notequal(B, B)", B=B)), "I 0") self.assertEqual(pixel(ImageMath.eval("notequal(Z, Z)", Z=Z)), "I 0") - self.assertEqual( - pixel(ImageMath.eval("notequal(A, B)", A=A, B=B)), "I 1") - self.assertEqual( - 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") + self.assertEqual(pixel(ImageMath.eval("notequal(A, B)", A=A, B=B)), "I 1") + self.assertEqual(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") diff --git a/Tests/test_imagemorph.py b/Tests/test_imagemorph.py index 0cf15bd6c..8ecd170e8 100644 --- a/Tests/test_imagemorph.py +++ b/Tests/test_imagemorph.py @@ -5,7 +5,6 @@ from PIL import Image, ImageMorph, _imagingmorph class MorphTests(PillowTestCase): - def setUp(self): self.A = self.string_to_img( """ @@ -17,27 +16,27 @@ class MorphTests(PillowTestCase): ....... ....... """ - ) + ) def img_to_string(self, im): """Turn a (small) binary image into a string representation""" - chars = '.1' + chars = ".1" width, height = im.size - return '\n'.join( - ''.join(chars[im.getpixel((c, r)) > 0] for c in range(width)) - for r in range(height)) + return "\n".join( + "".join(chars[im.getpixel((c, r)) > 0] for c in range(width)) + for r in range(height) + ) def string_to_img(self, image_string): """Turn a string image representation into a binary image""" - rows = [s for s in image_string.replace(' ', '').split('\n') - if len(s)] + rows = [s for s in image_string.replace(" ", "").split("\n") if len(s)] height = len(rows) width = len(rows[0]) - im = Image.new('L', (width, height)) + im = Image.new("L", (width, height)) for i in range(width): for j in range(height): c = rows[j][i] - v = c in 'X1' + v = c in "X1" im.putpixel((i, j), v) return im @@ -49,55 +48,50 @@ class MorphTests(PillowTestCase): self.assertEqual(self.img_to_string(A), self.img_to_string(B)) def assert_img_equal_img_string(self, A, Bstring): - self.assertEqual( - self.img_to_string(A), - self.img_string_normalize(Bstring)) + self.assertEqual(self.img_to_string(A), self.img_string_normalize(Bstring)) def test_str_to_img(self): - im = Image.open('Tests/images/morph_a.png') + im = Image.open("Tests/images/morph_a.png") self.assert_image_equal(self.A, im) def create_lut(self): - for op in ( - 'corner', 'dilation4', 'dilation8', - 'erosion4', 'erosion8', 'edge'): + for op in ("corner", "dilation4", "dilation8", "erosion4", "erosion8", "edge"): lb = ImageMorph.LutBuilder(op_name=op) lut = lb.build_lut() - with open('Tests/images/%s.lut' % op, 'wb') as f: + with open("Tests/images/%s.lut" % op, "wb") as f: f.write(lut) # create_lut() def test_lut(self): - for op in ( - 'corner', 'dilation4', 'dilation8', - 'erosion4', 'erosion8', 'edge'): + for op in ("corner", "dilation4", "dilation8", "erosion4", "erosion8", "edge"): lb = ImageMorph.LutBuilder(op_name=op) self.assertIsNone(lb.get_lut()) lut = lb.build_lut() - with open('Tests/images/%s.lut' % op, 'rb') as f: + with open("Tests/images/%s.lut" % op, "rb") as f: self.assertEqual(lut, bytearray(f.read())) def test_no_operator_loaded(self): mop = ImageMorph.MorphOp() with self.assertRaises(Exception) as e: mop.apply(None) - self.assertEqual(str(e.exception), 'No operator loaded') + self.assertEqual(str(e.exception), "No operator loaded") with self.assertRaises(Exception) as e: mop.match(None) - self.assertEqual(str(e.exception), 'No operator loaded') + self.assertEqual(str(e.exception), "No operator loaded") with self.assertRaises(Exception) as e: mop.save_lut(None) - self.assertEqual(str(e.exception), 'No operator loaded') + self.assertEqual(str(e.exception), "No operator loaded") # Test the named patterns def test_erosion8(self): # erosion8 - mop = ImageMorph.MorphOp(op_name='erosion8') + mop = ImageMorph.MorphOp(op_name="erosion8") count, Aout = mop.apply(self.A) self.assertEqual(count, 8) - self.assert_img_equal_img_string(Aout, - """ + self.assert_img_equal_img_string( + Aout, + """ ....... ....... ....... @@ -105,15 +99,17 @@ class MorphTests(PillowTestCase): ....... ....... ....... - """) + """, + ) def test_dialation8(self): # dialation8 - mop = ImageMorph.MorphOp(op_name='dilation8') + mop = ImageMorph.MorphOp(op_name="dilation8") count, Aout = mop.apply(self.A) self.assertEqual(count, 16) - self.assert_img_equal_img_string(Aout, - """ + self.assert_img_equal_img_string( + Aout, + """ ....... .11111. .11111. @@ -121,15 +117,17 @@ class MorphTests(PillowTestCase): .11111. .11111. ....... - """) + """, + ) def test_erosion4(self): # erosion4 - mop = ImageMorph.MorphOp(op_name='dilation4') + mop = ImageMorph.MorphOp(op_name="dilation4") count, Aout = mop.apply(self.A) self.assertEqual(count, 12) - self.assert_img_equal_img_string(Aout, - """ + self.assert_img_equal_img_string( + Aout, + """ ....... ..111.. .11111. @@ -137,15 +135,17 @@ class MorphTests(PillowTestCase): .11111. ..111.. ....... - """) + """, + ) def test_edge(self): # edge - mop = ImageMorph.MorphOp(op_name='edge') + mop = ImageMorph.MorphOp(op_name="edge") count, Aout = mop.apply(self.A) self.assertEqual(count, 1) - self.assert_img_equal_img_string(Aout, - """ + self.assert_img_equal_img_string( + Aout, + """ ....... ....... ..111.. @@ -153,16 +153,17 @@ class MorphTests(PillowTestCase): ..111.. ....... ....... - """) + """, + ) def test_corner(self): # Create a corner detector pattern - mop = ImageMorph.MorphOp(patterns=['1:(... ... ...)->0', - '4:(00. 01. ...)->1']) + mop = ImageMorph.MorphOp(patterns=["1:(... ... ...)->0", "4:(00. 01. ...)->1"]) count, Aout = mop.apply(self.A) self.assertEqual(count, 5) - self.assert_img_equal_img_string(Aout, - """ + self.assert_img_equal_img_string( + Aout, + """ ....... ....... ..1.1.. @@ -170,7 +171,8 @@ class MorphTests(PillowTestCase): ..1.1.. ....... ....... - """) + """, + ) # Test the coordinate counting with the same operator coords = mop.match(self.A) @@ -183,12 +185,12 @@ class MorphTests(PillowTestCase): def test_mirroring(self): # Test 'M' for mirroring - mop = ImageMorph.MorphOp(patterns=['1:(... ... ...)->0', - 'M:(00. 01. ...)->1']) + mop = ImageMorph.MorphOp(patterns=["1:(... ... ...)->0", "M:(00. 01. ...)->1"]) count, Aout = mop.apply(self.A) self.assertEqual(count, 7) - self.assert_img_equal_img_string(Aout, - """ + self.assert_img_equal_img_string( + Aout, + """ ....... ....... ..1.1.. @@ -196,16 +198,17 @@ class MorphTests(PillowTestCase): ....... ....... ....... - """) + """, + ) def test_negate(self): # Test 'N' for negate - mop = ImageMorph.MorphOp(patterns=['1:(... ... ...)->0', - 'N:(00. 01. ...)->1']) + mop = ImageMorph.MorphOp(patterns=["1:(... ... ...)->0", "N:(00. 01. ...)->1"]) count, Aout = mop.apply(self.A) self.assertEqual(count, 8) - self.assert_img_equal_img_string(Aout, - """ + self.assert_img_equal_img_string( + Aout, + """ ....... ....... ..1.... @@ -213,32 +216,34 @@ class MorphTests(PillowTestCase): ....... ....... ....... - """) + """, + ) def test_non_binary_images(self): - im = hopper('RGB') + im = hopper("RGB") mop = ImageMorph.MorphOp(op_name="erosion8") with self.assertRaises(Exception) as e: mop.apply(im) - self.assertEqual(str(e.exception), - 'Image must be binary, meaning it must use mode L') + self.assertEqual( + str(e.exception), "Image must be binary, meaning it must use mode L" + ) with self.assertRaises(Exception) as e: mop.match(im) - self.assertEqual(str(e.exception), - 'Image must be binary, meaning it must use mode L') + self.assertEqual( + str(e.exception), "Image must be binary, meaning it must use mode L" + ) with self.assertRaises(Exception) as e: mop.get_on_pixels(im) - self.assertEqual(str(e.exception), - 'Image must be binary, meaning it must use mode L') + self.assertEqual( + str(e.exception), "Image must be binary, meaning it must use mode L" + ) def test_add_patterns(self): # Arrange - lb = ImageMorph.LutBuilder(op_name='corner') - self.assertEqual(lb.patterns, ['1:(... ... ...)->0', - '4:(00. 01. ...)->1']) - new_patterns = ['M:(00. 01. ...)->1', - 'N:(00. 01. ...)->1'] + lb = ImageMorph.LutBuilder(op_name="corner") + self.assertEqual(lb.patterns, ["1:(... ... ...)->0", "4:(00. 01. ...)->1"]) + new_patterns = ["M:(00. 01. ...)->1", "N:(00. 01. ...)->1"] # Act lb.add_patterns(new_patterns) @@ -246,44 +251,44 @@ class MorphTests(PillowTestCase): # Assert self.assertEqual( lb.patterns, - ['1:(... ... ...)->0', - '4:(00. 01. ...)->1', - 'M:(00. 01. ...)->1', - 'N:(00. 01. ...)->1']) + [ + "1:(... ... ...)->0", + "4:(00. 01. ...)->1", + "M:(00. 01. ...)->1", + "N:(00. 01. ...)->1", + ], + ) def test_unknown_pattern(self): - self.assertRaises( - Exception, - ImageMorph.LutBuilder, op_name='unknown') + self.assertRaises(Exception, ImageMorph.LutBuilder, op_name="unknown") def test_pattern_syntax_error(self): # Arrange - lb = ImageMorph.LutBuilder(op_name='corner') - new_patterns = ['a pattern with a syntax error'] + lb = ImageMorph.LutBuilder(op_name="corner") + new_patterns = ["a pattern with a syntax error"] lb.add_patterns(new_patterns) # Act / Assert with self.assertRaises(Exception) as e: lb.build_lut() self.assertEqual( - str(e.exception), - 'Syntax error in pattern "a pattern with a syntax error"') + str(e.exception), 'Syntax error in pattern "a pattern with a syntax error"' + ) def test_load_invalid_mrl(self): # Arrange - invalid_mrl = 'Tests/images/hopper.png' + invalid_mrl = "Tests/images/hopper.png" mop = ImageMorph.MorphOp() # Act / Assert with self.assertRaises(Exception) as e: mop.load_lut(invalid_mrl) - self.assertEqual(str(e.exception), - 'Wrong size operator file!') + self.assertEqual(str(e.exception), "Wrong size operator file!") def test_roundtrip_mrl(self): # Arrange - tempfile = self.tempfile('temp.mrl') - mop = ImageMorph.MorphOp(op_name='corner') + tempfile = self.tempfile("temp.mrl") + mop = ImageMorph.MorphOp(op_name="corner") initial_lut = mop.lut # Act @@ -295,7 +300,7 @@ class MorphTests(PillowTestCase): def test_set_lut(self): # Arrange - lb = ImageMorph.LutBuilder(op_name='corner') + lb = ImageMorph.LutBuilder(op_name="corner") lut = lb.build_lut() mop = ImageMorph.MorphOp() @@ -306,9 +311,9 @@ class MorphTests(PillowTestCase): self.assertEqual(mop.lut, lut) def test_wrong_mode(self): - lut = ImageMorph.LutBuilder(op_name='corner').build_lut() - imrgb = Image.new('RGB', (10, 10)) - iml = Image.new('L', (10, 10)) + lut = ImageMorph.LutBuilder(op_name="corner").build_lut() + imrgb = Image.new("RGB", (10, 10)) + iml = Image.new("L", (10, 10)) with self.assertRaises(RuntimeError): _imagingmorph.apply(bytes(lut), imrgb.im.id, iml.im.id) diff --git a/Tests/test_imageops.py b/Tests/test_imageops.py index eaa8fde3c..006af903e 100644 --- a/Tests/test_imageops.py +++ b/Tests/test_imageops.py @@ -5,13 +5,13 @@ from PIL import ImageOps try: from PIL import _webp + HAVE_WEBP = True except ImportError: HAVE_WEBP = False class TestImageOps(PillowTestCase): - class Deformer(object): def getmesh(self, im): x, y = im.size @@ -91,15 +91,15 @@ class TestImageOps(PillowTestCase): for label, color, new_size in [ ("h", None, (im.width * 4, im.height * 2)), - ("v", "#f00", (im.width * 2, im.height * 4)) + ("v", "#f00", (im.width * 2, im.height * 4)), ]: for i, centering in enumerate([(0, 0), (0.5, 0.5), (1, 1)]): - new_im = ImageOps.pad(im, new_size, - color=color, centering=centering) + new_im = ImageOps.pad(im, new_size, color=color, centering=centering) self.assertEqual(new_im.size, new_size) target = Image.open( - "Tests/images/imageops_pad_"+label+"_"+str(i)+".jpg") + "Tests/images/imageops_pad_" + label + "_" + str(i) + ".jpg" + ) self.assert_image_similar(new_im, target, 6) def test_pil163(self): @@ -135,24 +135,30 @@ class TestImageOps(PillowTestCase): im = im.convert("L") # Create image with original 2-color functionality - im_test = ImageOps.colorize(im, 'red', 'green') + im_test = ImageOps.colorize(im, "red", "green") # Test output image (2-color) left = (0, 1) middle = (127, 1) right = (255, 1) - self.assert_tuple_approx_equal(im_test.getpixel(left), - (255, 0, 0), - threshold=1, - msg='black test pixel incorrect') - self.assert_tuple_approx_equal(im_test.getpixel(middle), - (127, 63, 0), - threshold=1, - msg='mid test pixel incorrect') - self.assert_tuple_approx_equal(im_test.getpixel(right), - (0, 127, 0), - threshold=1, - msg='white test pixel incorrect') + self.assert_tuple_approx_equal( + im_test.getpixel(left), + (255, 0, 0), + threshold=1, + msg="black test pixel incorrect", + ) + self.assert_tuple_approx_equal( + im_test.getpixel(middle), + (127, 63, 0), + threshold=1, + msg="mid test pixel incorrect", + ) + self.assert_tuple_approx_equal( + im_test.getpixel(right), + (0, 127, 0), + threshold=1, + msg="white test pixel incorrect", + ) def test_colorize_2color_offset(self): # Test the colorizing function with 2-color functionality and offset @@ -162,28 +168,32 @@ class TestImageOps(PillowTestCase): im = im.convert("L") # Create image with original 2-color functionality with offsets - im_test = ImageOps.colorize(im, - black='red', - white='green', - blackpoint=50, - whitepoint=100) + im_test = ImageOps.colorize( + im, black="red", white="green", blackpoint=50, whitepoint=100 + ) # Test output image (2-color) with offsets left = (25, 1) middle = (75, 1) right = (125, 1) - self.assert_tuple_approx_equal(im_test.getpixel(left), - (255, 0, 0), - threshold=1, - msg='black test pixel incorrect') - self.assert_tuple_approx_equal(im_test.getpixel(middle), - (127, 63, 0), - threshold=1, - msg='mid test pixel incorrect') - self.assert_tuple_approx_equal(im_test.getpixel(right), - (0, 127, 0), - threshold=1, - msg='white test pixel incorrect') + self.assert_tuple_approx_equal( + im_test.getpixel(left), + (255, 0, 0), + threshold=1, + msg="black test pixel incorrect", + ) + self.assert_tuple_approx_equal( + im_test.getpixel(middle), + (127, 63, 0), + threshold=1, + msg="mid test pixel incorrect", + ) + self.assert_tuple_approx_equal( + im_test.getpixel(right), + (0, 127, 0), + threshold=1, + msg="white test pixel incorrect", + ) def test_colorize_3color_offset(self): # Test the colorizing function with 3-color functionality and offset @@ -193,13 +203,15 @@ class TestImageOps(PillowTestCase): im = im.convert("L") # Create image with new three color functionality with offsets - im_test = ImageOps.colorize(im, - black='red', - white='green', - mid='blue', - blackpoint=50, - whitepoint=200, - midpoint=100) + im_test = ImageOps.colorize( + im, + black="red", + white="green", + mid="blue", + blackpoint=50, + whitepoint=200, + midpoint=100, + ) # Test output image (3-color) with offsets left = (25, 1) @@ -207,43 +219,47 @@ class TestImageOps(PillowTestCase): middle = (100, 1) right_middle = (150, 1) right = (225, 1) - self.assert_tuple_approx_equal(im_test.getpixel(left), - (255, 0, 0), - threshold=1, - msg='black test pixel incorrect') - self.assert_tuple_approx_equal(im_test.getpixel(left_middle), - (127, 0, 127), - threshold=1, - msg='low-mid test pixel incorrect') - self.assert_tuple_approx_equal(im_test.getpixel(middle), - (0, 0, 255), - threshold=1, - msg='mid incorrect') - self.assert_tuple_approx_equal(im_test.getpixel(right_middle), - (0, 63, 127), - threshold=1, - msg='high-mid test pixel incorrect') - self.assert_tuple_approx_equal(im_test.getpixel(right), - (0, 127, 0), - threshold=1, - msg='white test pixel incorrect') + self.assert_tuple_approx_equal( + im_test.getpixel(left), + (255, 0, 0), + threshold=1, + msg="black test pixel incorrect", + ) + self.assert_tuple_approx_equal( + im_test.getpixel(left_middle), + (127, 0, 127), + threshold=1, + msg="low-mid test pixel incorrect", + ) + self.assert_tuple_approx_equal( + im_test.getpixel(middle), (0, 0, 255), threshold=1, msg="mid incorrect" + ) + self.assert_tuple_approx_equal( + im_test.getpixel(right_middle), + (0, 63, 127), + threshold=1, + msg="high-mid test pixel incorrect", + ) + self.assert_tuple_approx_equal( + im_test.getpixel(right), + (0, 127, 0), + threshold=1, + msg="white test pixel incorrect", + ) def test_exif_transpose(self): exts = [".jpg"] if HAVE_WEBP and _webp.HAVE_WEBPANIM: exts.append(".webp") for ext in exts: - base_im = Image.open("Tests/images/hopper"+ext) + base_im = Image.open("Tests/images/hopper" + ext) orientations = [base_im] for i in range(2, 9): - im = Image.open("Tests/images/hopper_orientation_"+str(i)+ext) + im = Image.open("Tests/images/hopper_orientation_" + str(i) + ext) orientations.append(im) for i, orientation_im in enumerate(orientations): - for im in [ - orientation_im, # ImageFile - orientation_im.copy() # Image - ]: + for im in [orientation_im, orientation_im.copy()]: # ImageFile # Image if i == 0: self.assertNotIn("exif", im.info) else: diff --git a/Tests/test_imageops_usm.py b/Tests/test_imageops_usm.py index a867e5430..fc49f4d2d 100644 --- a/Tests/test_imageops_usm.py +++ b/Tests/test_imageops_usm.py @@ -8,7 +8,6 @@ snakes = Image.open("Tests/images/color_snakes.png") class TestImageOpsUsm(PillowTestCase): - def test_filter_api(self): test_filter = ImageFilter.GaussianBlur(2.0) @@ -47,24 +46,36 @@ class TestImageOpsUsm(PillowTestCase): def test_usm_accuracy(self): - src = snakes.convert('RGB') + src = snakes.convert("RGB") i = src.filter(ImageFilter.UnsharpMask(5, 1024, 0)) # Image should not be changed because it have only 0 and 255 levels. self.assertEqual(i.tobytes(), src.tobytes()) def test_blur_accuracy(self): - i = snakes.filter(ImageFilter.GaussianBlur(.4)) + i = snakes.filter(ImageFilter.GaussianBlur(0.4)) # These pixels surrounded with pixels with 255 intensity. # They must be very close to 255. - for x, y, c in [(1, 0, 1), (2, 0, 1), (7, 8, 1), (8, 8, 1), (2, 9, 1), - (7, 3, 0), (8, 3, 0), (5, 8, 0), (5, 9, 0), (1, 3, 0), - (4, 3, 2), (4, 2, 2)]: + for x, y, c in [ + (1, 0, 1), + (2, 0, 1), + (7, 8, 1), + (8, 8, 1), + (2, 9, 1), + (7, 3, 0), + (8, 3, 0), + (5, 8, 0), + (5, 9, 0), + (1, 3, 0), + (4, 3, 2), + (4, 2, 2), + ]: self.assertGreaterEqual(i.im.getpixel((x, y))[c], 250) # Fuzzy match. def gp(x, y): return i.im.getpixel((x, y)) + self.assertTrue(236 <= gp(7, 4)[0] <= 239) self.assertTrue(236 <= gp(7, 5)[2] <= 239) self.assertTrue(236 <= gp(7, 6)[2] <= 239) diff --git a/Tests/test_imagepalette.py b/Tests/test_imagepalette.py index e4b5b7f72..50670f26c 100644 --- a/Tests/test_imagepalette.py +++ b/Tests/test_imagepalette.py @@ -4,12 +4,12 @@ from PIL import ImagePalette, Image class TestImagePalette(PillowTestCase): - def test_sanity(self): - ImagePalette.ImagePalette("RGB", list(range(256))*3) - self.assertRaises(ValueError, - ImagePalette.ImagePalette, "RGB", list(range(256))*2) + ImagePalette.ImagePalette("RGB", list(range(256)) * 3) + self.assertRaises( + ValueError, ImagePalette.ImagePalette, "RGB", list(range(256)) * 2 + ) def test_getcolor(self): @@ -27,7 +27,7 @@ class TestImagePalette(PillowTestCase): def test_file(self): - palette = ImagePalette.ImagePalette("RGB", list(range(256))*3) + palette = ImagePalette.ImagePalette("RGB", list(range(256)) * 3) f = self.tempfile("temp.lut") @@ -65,8 +65,9 @@ class TestImagePalette(PillowTestCase): white = 255 # Act - self.assertRaises(NotImplementedError, - ImagePalette.make_linear_lut, black, white) + self.assertRaises( + NotImplementedError, ImagePalette.make_linear_lut, black, white + ) def test_make_gamma_lut(self): # Arrange @@ -87,7 +88,7 @@ class TestImagePalette(PillowTestCase): def test_rawmode_valueerrors(self): # Arrange - palette = ImagePalette.raw("RGB", list(range(256))*3) + palette = ImagePalette.raw("RGB", list(range(256)) * 3) # Act / Assert self.assertRaises(ValueError, palette.tobytes) @@ -97,7 +98,7 @@ class TestImagePalette(PillowTestCase): def test_getdata(self): # Arrange - data_in = list(range(256))*3 + data_in = list(range(256)) * 3 palette = ImagePalette.ImagePalette("RGB", data_in) # Act @@ -108,7 +109,7 @@ class TestImagePalette(PillowTestCase): def test_rawmode_getdata(self): # Arrange - data_in = list(range(256))*3 + data_in = list(range(256)) * 3 palette = ImagePalette.raw("RGB", data_in) # Act @@ -120,17 +121,16 @@ class TestImagePalette(PillowTestCase): def test_2bit_palette(self): # issue #2258, 2 bit palettes are corrupted. - outfile = self.tempfile('temp.png') + outfile = self.tempfile("temp.png") - rgb = b'\x00' * 2 + b'\x01' * 2 + b'\x02' * 2 - img = Image.frombytes('P', (6, 1), rgb) - img.putpalette(b'\xFF\x00\x00\x00\xFF\x00\x00\x00\xFF') # RGB - img.save(outfile, format='PNG') + rgb = b"\x00" * 2 + b"\x01" * 2 + b"\x02" * 2 + img = Image.frombytes("P", (6, 1), rgb) + img.putpalette(b"\xFF\x00\x00\x00\xFF\x00\x00\x00\xFF") # RGB + img.save(outfile, format="PNG") reloaded = Image.open(outfile) self.assert_image_equal(img, reloaded) def test_invalid_palette(self): - self.assertRaises(IOError, - ImagePalette.load, "Tests/images/hopper.jpg") + self.assertRaises(IOError, ImagePalette.load, "Tests/images/hopper.jpg") diff --git a/Tests/test_imagepath.py b/Tests/test_imagepath.py index 8cf88b7c1..f5309ee14 100644 --- a/Tests/test_imagepath.py +++ b/Tests/test_imagepath.py @@ -8,7 +8,6 @@ import struct class TestImagePath(PillowTestCase): - def test_path(self): p = ImagePath.Path(list(range(10))) @@ -19,21 +18,19 @@ class TestImagePath(PillowTestCase): self.assertEqual(p[-1], (8.0, 9.0)) self.assertEqual(list(p[:1]), [(0.0, 1.0)]) with self.assertRaises(TypeError) as cm: - p['foo'] + p["foo"] + self.assertEqual(str(cm.exception), "Path indices must be integers, not str") self.assertEqual( - str(cm.exception), - "Path indices must be integers, not str") - self.assertEqual( - list(p), - [(0.0, 1.0), (2.0, 3.0), (4.0, 5.0), (6.0, 7.0), (8.0, 9.0)]) + list(p), [(0.0, 1.0), (2.0, 3.0), (4.0, 5.0), (6.0, 7.0), (8.0, 9.0)] + ) # method sanity check self.assertEqual( - p.tolist(), - [(0.0, 1.0), (2.0, 3.0), (4.0, 5.0), (6.0, 7.0), (8.0, 9.0)]) + p.tolist(), [(0.0, 1.0), (2.0, 3.0), (4.0, 5.0), (6.0, 7.0), (8.0, 9.0)] + ) self.assertEqual( - p.tolist(1), - [0.0, 1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0, 9.0]) + p.tolist(1), [0.0, 1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0, 9.0] + ) self.assertEqual(p.getbbox(), (0.0, 1.0, 8.0, 9.0)) @@ -62,7 +59,7 @@ class TestImagePath(PillowTestCase): self.assertEqual(list(p), [(0.0, 1.0)]) arr = array.array("f", [0, 1]) - if hasattr(arr, 'tobytes'): + if hasattr(arr, "tobytes"): p = ImagePath.Path(arr.tobytes()) else: p = ImagePath.Path(arr.tostring()) @@ -79,9 +76,9 @@ class TestImagePath(PillowTestCase): # and segfaults for i in range(200000): if py3: - x[i] = b'0'*16 + x[i] = b"0" * 16 else: - x[i] = "0"*16 + x[i] = "0" * 16 class evil: diff --git a/Tests/test_imageqt.py b/Tests/test_imageqt.py index bd93828ef..696acdb85 100644 --- a/Tests/test_imageqt.py +++ b/Tests/test_imageqt.py @@ -17,13 +17,15 @@ if ImageQt.qt_is_installed: def skip_if_qt_is_not_installed(_): pass + + else: + def skip_if_qt_is_not_installed(test_case): - test_case.skipTest('Qt bindings are not installed') + test_case.skipTest("Qt bindings are not installed") class PillowQtTestCase(object): - def setUp(self): skip_if_qt_is_not_installed(self) @@ -32,20 +34,19 @@ class PillowQtTestCase(object): class PillowQPixmapTestCase(PillowQtTestCase): - def setUp(self): PillowQtTestCase.setUp(self) try: - if ImageQt.qt_version == '5': + if ImageQt.qt_version == "5": from PyQt5.QtGui import QGuiApplication - elif ImageQt.qt_version == '4': + elif ImageQt.qt_version == "4": from PyQt4.QtGui import QGuiApplication - elif ImageQt.qt_version == 'side': + elif ImageQt.qt_version == "side": from PySide.QtGui import QGuiApplication - elif ImageQt.qt_version == 'side2': + elif ImageQt.qt_version == "side2": from PySide2.QtGui import QGuiApplication except ImportError: - self.skipTest('QGuiApplication not installed') + self.skipTest("QGuiApplication not installed") self.app = QGuiApplication([]) @@ -55,29 +56,28 @@ class PillowQPixmapTestCase(PillowQtTestCase): class TestImageQt(PillowQtTestCase, PillowTestCase): - def test_rgb(self): # from https://doc.qt.io/archives/qt-4.8/qcolor.html # typedef QRgb # An ARGB quadruplet on the format #AARRGGBB, # equivalent to an unsigned int. - if ImageQt.qt_version == '5': + if ImageQt.qt_version == "5": from PyQt5.QtGui import qRgb - elif ImageQt.qt_version == '4': + elif ImageQt.qt_version == "4": from PyQt4.QtGui import qRgb - elif ImageQt.qt_version == 'side': + elif ImageQt.qt_version == "side": from PySide.QtGui import qRgb - elif ImageQt.qt_version == 'side2': + elif ImageQt.qt_version == "side2": from PySide2.QtGui import qRgb self.assertEqual(qRgb(0, 0, 0), qRgba(0, 0, 0, 255)) def checkrgb(r, g, b): val = ImageQt.rgb(r, g, b) - val = val % 2**24 # drop the alpha + val = val % 2 ** 24 # drop the alpha self.assertEqual(val >> 16, r) - self.assertEqual(((val >> 8) % 2**8), g) - self.assertEqual(val % 2**8, b) + self.assertEqual(((val >> 8) % 2 ** 8), g) + self.assertEqual(val % 2 ** 8, b) checkrgb(0, 0, 0) checkrgb(255, 0, 0) @@ -85,7 +85,7 @@ class TestImageQt(PillowQtTestCase, PillowTestCase): checkrgb(0, 0, 255) def test_image(self): - for mode in ('1', 'RGB', 'RGBA', 'L', 'P'): + for mode in ("1", "RGB", "RGBA", "L", "P"): ImageQt.ImageQt(hopper(mode)) def test_deprecated(self): diff --git a/Tests/test_imagesequence.py b/Tests/test_imagesequence.py index 9fbf3fed8..38645f133 100644 --- a/Tests/test_imagesequence.py +++ b/Tests/test_imagesequence.py @@ -4,7 +4,6 @@ from PIL import Image, ImageSequence, TiffImagePlugin class TestImageSequence(PillowTestCase): - def test_sanity(self): test_file = self.tempfile("temp.im") @@ -25,19 +24,25 @@ class TestImageSequence(PillowTestCase): self.assertRaises(AttributeError, ImageSequence.Iterator, 0) def test_iterator(self): - im = Image.open('Tests/images/multipage.tiff') + im = Image.open("Tests/images/multipage.tiff") i = ImageSequence.Iterator(im) for index in range(0, im.n_frames): self.assertEqual(i[index], next(i)) - self.assertRaises(IndexError, lambda: i[index+1]) + self.assertRaises(IndexError, lambda: i[index + 1]) self.assertRaises(StopIteration, next, i) + def test_iterator_min_frame(self): + im = Image.open("Tests/images/hopper.psd") + i = ImageSequence.Iterator(im) + for index in range(1, im.n_frames): + self.assertEqual(i[index], next(i)) + def _test_multipage_tiff(self): - im = Image.open('Tests/images/multipage.tiff') + im = Image.open("Tests/images/multipage.tiff") for index, frame in enumerate(ImageSequence.Iterator(im)): frame.load() self.assertEqual(index, im.tell()) - frame.convert('RGB') + frame.convert("RGB") def test_tiff(self): self._test_multipage_tiff() @@ -53,7 +58,7 @@ class TestImageSequence(PillowTestCase): TiffImagePlugin.READ_LIBTIFF = False def test_consecutive(self): - im = Image.open('Tests/images/multipage.tiff') + im = Image.open("Tests/images/multipage.tiff") firstFrame = None for frame in ImageSequence.Iterator(im): if firstFrame is None: @@ -64,7 +69,7 @@ class TestImageSequence(PillowTestCase): def test_palette_mmap(self): # Using mmap in ImageFile can require to reload the palette. - im = Image.open('Tests/images/multipage-mmap.tiff') + im = Image.open("Tests/images/multipage-mmap.tiff") color1 = im.getpalette()[0:3] im.seek(0) color2 = im.getpalette()[0:3] diff --git a/Tests/test_imageshow.py b/Tests/test_imageshow.py index 899c057d6..a77acd307 100644 --- a/Tests/test_imageshow.py +++ b/Tests/test_imageshow.py @@ -5,7 +5,6 @@ from PIL import ImageShow class TestImageShow(PillowTestCase): - def test_sanity(self): dir(Image) dir(ImageShow) @@ -18,18 +17,20 @@ class TestImageShow(PillowTestCase): ImageShow._viewers.pop() def test_show(self): - class TestViewer: + class TestViewer(ImageShow.Viewer): methodCalled = False - def show(self, image, title=None, **options): + def show_image(self, image, **options): self.methodCalled = True return True + viewer = TestViewer() ImageShow.register(viewer, -1) - im = hopper() - self.assertTrue(ImageShow.show(im)) - self.assertTrue(viewer.methodCalled) + for mode in ("1", "I;16", "LA", "RGB", "RGBA"): + im = hopper(mode) + self.assertTrue(ImageShow.show(im)) + self.assertTrue(viewer.methodCalled) # Restore original state ImageShow._viewers.pop(0) @@ -43,4 +44,4 @@ class TestImageShow(PillowTestCase): def test_viewers(self): for viewer in ImageShow._viewers: - viewer.get_command('test.jpg') + viewer.get_command("test.jpg") diff --git a/Tests/test_imagestat.py b/Tests/test_imagestat.py index c2580a1b1..ef0f28c32 100644 --- a/Tests/test_imagestat.py +++ b/Tests/test_imagestat.py @@ -5,7 +5,6 @@ from PIL import ImageStat class TestImageStat(PillowTestCase): - def test_sanity(self): im = hopper() @@ -48,8 +47,8 @@ class TestImageStat(PillowTestCase): st = ImageStat.Stat(im) self.assertEqual(st.extrema[0], (128, 128)) - self.assertEqual(st.sum[0], 128**3) - self.assertEqual(st.sum2[0], 128**4) + self.assertEqual(st.sum[0], 128 ** 3) + self.assertEqual(st.sum2[0], 128 ** 4) self.assertEqual(st.mean[0], 128) self.assertEqual(st.median[0], 128) self.assertEqual(st.rms[0], 128) diff --git a/Tests/test_imagetk.py b/Tests/test_imagetk.py index a6a4dd4ea..9bcb4954a 100644 --- a/Tests/test_imagetk.py +++ b/Tests/test_imagetk.py @@ -5,6 +5,7 @@ from PIL._util import py3 try: from PIL import ImageTk + if py3: import tkinter as tk else: @@ -15,12 +16,11 @@ except (OSError, ImportError): # Skipped via setUp() HAS_TK = False -TK_MODES = ('1', 'L', 'P', 'RGB', 'RGBA') +TK_MODES = ("1", "L", "P", "RGB", "RGBA") @unittest.skipIf(not HAS_TK, "Tk not installed") class TestImageTk(PillowTestCase): - def setUp(self): try: # setup tk @@ -34,7 +34,7 @@ class TestImageTk(PillowTestCase): TEST_PNG = "Tests/images/hopper.png" im1 = Image.open(TEST_JPG) im2 = Image.open(TEST_PNG) - with open(TEST_PNG, 'rb') as fp: + with open(TEST_PNG, "rb") as fp: data = fp.read() kw = {"file": TEST_JPG, "data": data} @@ -61,9 +61,8 @@ class TestImageTk(PillowTestCase): self.assertEqual(im_tk.width(), im.width) self.assertEqual(im_tk.height(), im.height) - # _tkinter.TclError: this function is not yet supported - # reloaded = ImageTk.getimage(im_tk) - # self.assert_image_equal(reloaded, im) + reloaded = ImageTk.getimage(im_tk) + self.assert_image_equal(reloaded, im.convert("RGBA")) def test_photoimage_blank(self): # test a image using mode/size: @@ -77,7 +76,7 @@ class TestImageTk(PillowTestCase): # self.assert_image_equal(reloaded, im) def test_bitmapimage(self): - im = hopper('1') + im = hopper("1") # this should not crash im_tk = ImageTk.BitmapImage(im) diff --git a/Tests/test_imagewin.py b/Tests/test_imagewin.py index 16d681f2c..82f11a68d 100644 --- a/Tests/test_imagewin.py +++ b/Tests/test_imagewin.py @@ -5,7 +5,6 @@ import sys class TestImageWin(PillowTestCase): - def test_sanity(self): dir(ImageWin) @@ -32,9 +31,8 @@ class TestImageWin(PillowTestCase): self.assertEqual(wnd2, 50) -@unittest.skipUnless(sys.platform.startswith('win32'), "Windows only") +@unittest.skipUnless(sys.platform.startswith("win32"), "Windows only") class TestImageWinDib(PillowTestCase): - def test_dib_image(self): # Arrange im = hopper() diff --git a/Tests/test_imagewin_pointers.py b/Tests/test_imagewin_pointers.py index 64f921916..9c5704dbe 100644 --- a/Tests/test_imagewin_pointers.py +++ b/Tests/test_imagewin_pointers.py @@ -7,33 +7,33 @@ from io import BytesIO # see https://github.com/python-pillow/Pillow/pull/1431#issuecomment-144692652 -if sys.platform.startswith('win32'): +if sys.platform.startswith("win32"): import ctypes.wintypes class BITMAPFILEHEADER(ctypes.Structure): _pack_ = 2 _fields_ = [ - ('bfType', ctypes.wintypes.WORD), - ('bfSize', ctypes.wintypes.DWORD), - ('bfReserved1', ctypes.wintypes.WORD), - ('bfReserved2', ctypes.wintypes.WORD), - ('bfOffBits', ctypes.wintypes.DWORD), + ("bfType", ctypes.wintypes.WORD), + ("bfSize", ctypes.wintypes.DWORD), + ("bfReserved1", ctypes.wintypes.WORD), + ("bfReserved2", ctypes.wintypes.WORD), + ("bfOffBits", ctypes.wintypes.DWORD), ] class BITMAPINFOHEADER(ctypes.Structure): _pack_ = 2 _fields_ = [ - ('biSize', ctypes.wintypes.DWORD), - ('biWidth', ctypes.wintypes.LONG), - ('biHeight', ctypes.wintypes.LONG), - ('biPlanes', ctypes.wintypes.WORD), - ('biBitCount', ctypes.wintypes.WORD), - ('biCompression', ctypes.wintypes.DWORD), - ('biSizeImage', ctypes.wintypes.DWORD), - ('biXPelsPerMeter', ctypes.wintypes.LONG), - ('biYPelsPerMeter', ctypes.wintypes.LONG), - ('biClrUsed', ctypes.wintypes.DWORD), - ('biClrImportant', ctypes.wintypes.DWORD), + ("biSize", ctypes.wintypes.DWORD), + ("biWidth", ctypes.wintypes.LONG), + ("biHeight", ctypes.wintypes.LONG), + ("biPlanes", ctypes.wintypes.WORD), + ("biBitCount", ctypes.wintypes.WORD), + ("biCompression", ctypes.wintypes.DWORD), + ("biSizeImage", ctypes.wintypes.DWORD), + ("biXPelsPerMeter", ctypes.wintypes.LONG), + ("biYPelsPerMeter", ctypes.wintypes.LONG), + ("biClrUsed", ctypes.wintypes.DWORD), + ("biClrImportant", ctypes.wintypes.DWORD), ] BI_RGB = 0 @@ -57,15 +57,19 @@ if sys.platform.startswith('win32'): DeleteObject.argtypes = [ctypes.wintypes.HGDIOBJ] CreateDIBSection = ctypes.windll.gdi32.CreateDIBSection - CreateDIBSection.argtypes = [ctypes.wintypes.HDC, ctypes.c_void_p, - ctypes.c_uint, - ctypes.POINTER(ctypes.c_void_p), - ctypes.wintypes.HANDLE, ctypes.wintypes.DWORD] + CreateDIBSection.argtypes = [ + ctypes.wintypes.HDC, + ctypes.c_void_p, + ctypes.c_uint, + ctypes.POINTER(ctypes.c_void_p), + ctypes.wintypes.HANDLE, + ctypes.wintypes.DWORD, + ] CreateDIBSection.restype = ctypes.wintypes.HBITMAP def serialize_dib(bi, pixels): bf = BITMAPFILEHEADER() - bf.bfType = 0x4d42 + bf.bfType = 0x4D42 bf.bfOffBits = ctypes.sizeof(bf) + bi.biSize bf.bfSize = bf.bfOffBits + bi.biSizeImage bf.bfReserved1 = bf.bfReserved2 = 0 @@ -81,7 +85,7 @@ if sys.platform.startswith('win32'): def test_pointer(self): im = hopper() (width, height) = im.size - opath = self.tempfile('temp.png') + opath = self.tempfile("temp.png") imdib = ImageWin.Dib(im) hdr = BITMAPINFOHEADER() @@ -97,8 +101,9 @@ if sys.platform.startswith('win32'): hdc = CreateCompatibleDC(None) pixels = ctypes.c_void_p() - dib = CreateDIBSection(hdc, ctypes.byref(hdr), DIB_RGB_COLORS, - ctypes.byref(pixels), None, 0) + dib = CreateDIBSection( + hdc, ctypes.byref(hdr), DIB_RGB_COLORS, ctypes.byref(pixels), None, 0 + ) SelectObject(hdc, dib) imdib.expose(hdc) diff --git a/Tests/test_lib_image.py b/Tests/test_lib_image.py index 466c43f88..36a1e97af 100644 --- a/Tests/test_lib_image.py +++ b/Tests/test_lib_image.py @@ -4,7 +4,6 @@ from PIL import Image class TestLibImage(PillowTestCase): - def test_setmode(self): im = Image.new("L", (1, 1), 255) @@ -33,5 +32,5 @@ class TestLibImage(PillowTestCase): self.assertRaises(ValueError, im.im.setmode, "RGBABCDE") -if __name__ == '__main__': +if __name__ == "__main__": unittest.main() diff --git a/Tests/test_lib_pack.py b/Tests/test_lib_pack.py index 543d151ac..88ba3a307 100644 --- a/Tests/test_lib_pack.py +++ b/Tests/test_lib_pack.py @@ -24,32 +24,31 @@ class TestLibPack(PillowTestCase): self.assertEqual(data, im.tobytes("raw", rawmode)) def test_1(self): - self.assert_pack("1", "1", b'\x01', 0, 0, 0, 0, 0, 0, 0, X) - self.assert_pack("1", "1;I", b'\x01', X, X, X, X, X, X, X, 0) - self.assert_pack("1", "1;R", b'\x01', X, 0, 0, 0, 0, 0, 0, 0) - self.assert_pack("1", "1;IR", b'\x01', 0, X, X, X, X, X, X, X) + self.assert_pack("1", "1", b"\x01", 0, 0, 0, 0, 0, 0, 0, X) + self.assert_pack("1", "1;I", b"\x01", X, X, X, X, X, X, X, 0) + self.assert_pack("1", "1;R", b"\x01", X, 0, 0, 0, 0, 0, 0, 0) + self.assert_pack("1", "1;IR", b"\x01", 0, X, X, X, X, X, X, X) - self.assert_pack("1", "1", b'\xaa', X, 0, X, 0, X, 0, X, 0) - self.assert_pack("1", "1;I", b'\xaa', 0, X, 0, X, 0, X, 0, X) - self.assert_pack("1", "1;R", b'\xaa', 0, X, 0, X, 0, X, 0, X) - self.assert_pack("1", "1;IR", b'\xaa', X, 0, X, 0, X, 0, X, 0) + self.assert_pack("1", "1", b"\xaa", X, 0, X, 0, X, 0, X, 0) + self.assert_pack("1", "1;I", b"\xaa", 0, X, 0, X, 0, X, 0, X) + self.assert_pack("1", "1;R", b"\xaa", 0, X, 0, X, 0, X, 0, X) + self.assert_pack("1", "1;IR", b"\xaa", X, 0, X, 0, X, 0, X, 0) - self.assert_pack( - "1", "L", b'\xff\x00\x00\xff\x00\x00', X, 0, 0, X, 0, 0) + self.assert_pack("1", "L", b"\xff\x00\x00\xff\x00\x00", X, 0, 0, X, 0, 0) def test_L(self): self.assert_pack("L", "L", 1, 1, 2, 3, 4) - self.assert_pack("L", "L;16", b'\x00\xc6\x00\xaf', 198, 175) - self.assert_pack("L", "L;16B", b'\xc6\x00\xaf\x00', 198, 175) + self.assert_pack("L", "L;16", b"\x00\xc6\x00\xaf", 198, 175) + self.assert_pack("L", "L;16B", b"\xc6\x00\xaf\x00", 198, 175) def test_LA(self): self.assert_pack("LA", "LA", 2, (1, 2), (3, 4), (5, 6)) self.assert_pack("LA", "LA;L", 2, (1, 4), (2, 5), (3, 6)) def test_P(self): - self.assert_pack("P", "P;1", b'\xe4', 1, 1, 1, 0, 0, 255, 0, 0) - self.assert_pack("P", "P;2", b'\xe4', 3, 2, 1, 0) - self.assert_pack("P", "P;4", b'\x02\xef', 0, 2, 14, 15) + self.assert_pack("P", "P;1", b"\xe4", 1, 1, 1, 0, 0, 255, 0, 0) + self.assert_pack("P", "P;2", b"\xe4", 3, 2, 1, 0) + self.assert_pack("P", "P;4", b"\x02\xef", 0, 2, 14, 15) self.assert_pack("P", "P", 1, 1, 2, 3, 4) def test_PA(self): @@ -59,116 +58,118 @@ class TestLibPack(PillowTestCase): def test_RGB(self): self.assert_pack("RGB", "RGB", 3, (1, 2, 3), (4, 5, 6), (7, 8, 9)) self.assert_pack( - "RGB", "RGBX", - b'\x01\x02\x03\xff\x05\x06\x07\xff', (1, 2, 3), (5, 6, 7)) + "RGB", "RGBX", b"\x01\x02\x03\xff\x05\x06\x07\xff", (1, 2, 3), (5, 6, 7) + ) self.assert_pack( - "RGB", "XRGB", - b'\x00\x02\x03\x04\x00\x06\x07\x08', (2, 3, 4), (6, 7, 8)) + "RGB", "XRGB", b"\x00\x02\x03\x04\x00\x06\x07\x08", (2, 3, 4), (6, 7, 8) + ) self.assert_pack("RGB", "BGR", 3, (3, 2, 1), (6, 5, 4), (9, 8, 7)) self.assert_pack( - "RGB", "BGRX", - b'\x01\x02\x03\x00\x05\x06\x07\x00', (3, 2, 1), (7, 6, 5)) + "RGB", "BGRX", b"\x01\x02\x03\x00\x05\x06\x07\x00", (3, 2, 1), (7, 6, 5) + ) self.assert_pack( - "RGB", "XBGR", - b'\x00\x02\x03\x04\x00\x06\x07\x08', (4, 3, 2), (8, 7, 6)) + "RGB", "XBGR", b"\x00\x02\x03\x04\x00\x06\x07\x08", (4, 3, 2), (8, 7, 6) + ) self.assert_pack("RGB", "RGB;L", 3, (1, 4, 7), (2, 5, 8), (3, 6, 9)) self.assert_pack("RGB", "R", 1, (1, 9, 9), (2, 9, 9), (3, 9, 9)) self.assert_pack("RGB", "G", 1, (9, 1, 9), (9, 2, 9), (9, 3, 9)) self.assert_pack("RGB", "B", 1, (9, 9, 1), (9, 9, 2), (9, 9, 3)) def test_RGBA(self): + self.assert_pack("RGBA", "RGBA", 4, (1, 2, 3, 4), (5, 6, 7, 8), (9, 10, 11, 12)) self.assert_pack( - "RGBA", "RGBA", 4, (1, 2, 3, 4), (5, 6, 7, 8), (9, 10, 11, 12)) + "RGBA", "RGBA;L", 4, (1, 4, 7, 10), (2, 5, 8, 11), (3, 6, 9, 12) + ) + self.assert_pack("RGBA", "RGB", 3, (1, 2, 3, 14), (4, 5, 6, 15), (7, 8, 9, 16)) + self.assert_pack("RGBA", "BGR", 3, (3, 2, 1, 14), (6, 5, 4, 15), (9, 8, 7, 16)) + self.assert_pack("RGBA", "BGRA", 4, (3, 2, 1, 4), (7, 6, 5, 8), (11, 10, 9, 12)) + self.assert_pack("RGBA", "ABGR", 4, (4, 3, 2, 1), (8, 7, 6, 5), (12, 11, 10, 9)) self.assert_pack( - "RGBA", "RGBA;L", 4, (1, 4, 7, 10), (2, 5, 8, 11), (3, 6, 9, 12)) - self.assert_pack( - "RGBA", "RGB", 3, (1, 2, 3, 14), (4, 5, 6, 15), (7, 8, 9, 16)) - self.assert_pack( - "RGBA", "BGR", 3, (3, 2, 1, 14), (6, 5, 4, 15), (9, 8, 7, 16)) - self.assert_pack( - "RGBA", "BGRA", 4, - (3, 2, 1, 4), (7, 6, 5, 8), (11, 10, 9, 12)) - self.assert_pack( - "RGBA", "ABGR", 4, (4, 3, 2, 1), (8, 7, 6, 5), (12, 11, 10, 9)) - self.assert_pack( - "RGBA", "BGRa", 4, - (191, 127, 63, 4), (223, 191, 159, 8), (233, 212, 191, 12)) - self.assert_pack( - "RGBA", "R", 1, (1, 0, 8, 9), (2, 0, 8, 9), (3, 0, 8, 0)) - self.assert_pack( - "RGBA", "G", 1, (6, 1, 8, 9), (6, 2, 8, 9), (6, 3, 8, 9)) - self.assert_pack( - "RGBA", "B", 1, (6, 7, 1, 9), (6, 7, 2, 0), (6, 7, 3, 9)) - self.assert_pack( - "RGBA", "A", 1, (6, 7, 0, 1), (6, 7, 0, 2), (0, 7, 0, 3)) + "RGBA", + "BGRa", + 4, + (191, 127, 63, 4), + (223, 191, 159, 8), + (233, 212, 191, 12), + ) + self.assert_pack("RGBA", "R", 1, (1, 0, 8, 9), (2, 0, 8, 9), (3, 0, 8, 0)) + self.assert_pack("RGBA", "G", 1, (6, 1, 8, 9), (6, 2, 8, 9), (6, 3, 8, 9)) + self.assert_pack("RGBA", "B", 1, (6, 7, 1, 9), (6, 7, 2, 0), (6, 7, 3, 9)) + self.assert_pack("RGBA", "A", 1, (6, 7, 0, 1), (6, 7, 0, 2), (0, 7, 0, 3)) def test_RGBa(self): - self.assert_pack( - "RGBa", "RGBa", 4, (1, 2, 3, 4), (5, 6, 7, 8), (9, 10, 11, 12)) - self.assert_pack( - "RGBa", "BGRa", 4, (3, 2, 1, 4), (7, 6, 5, 8), (11, 10, 9, 12)) - self.assert_pack( - "RGBa", "aBGR", 4, (4, 3, 2, 1), (8, 7, 6, 5), (12, 11, 10, 9)) + self.assert_pack("RGBa", "RGBa", 4, (1, 2, 3, 4), (5, 6, 7, 8), (9, 10, 11, 12)) + self.assert_pack("RGBa", "BGRa", 4, (3, 2, 1, 4), (7, 6, 5, 8), (11, 10, 9, 12)) + self.assert_pack("RGBa", "aBGR", 4, (4, 3, 2, 1), (8, 7, 6, 5), (12, 11, 10, 9)) def test_RGBX(self): + self.assert_pack("RGBX", "RGBX", 4, (1, 2, 3, 4), (5, 6, 7, 8), (9, 10, 11, 12)) self.assert_pack( - "RGBX", "RGBX", 4, (1, 2, 3, 4), (5, 6, 7, 8), (9, 10, 11, 12)) + "RGBX", "RGBX;L", 4, (1, 4, 7, 10), (2, 5, 8, 11), (3, 6, 9, 12) + ) + self.assert_pack("RGBX", "RGB", 3, (1, 2, 3, X), (4, 5, 6, X), (7, 8, 9, X)) + self.assert_pack("RGBX", "BGR", 3, (3, 2, 1, X), (6, 5, 4, X), (9, 8, 7, X)) self.assert_pack( - "RGBX", "RGBX;L", 4, (1, 4, 7, 10), (2, 5, 8, 11), (3, 6, 9, 12)) + "RGBX", + "BGRX", + b"\x01\x02\x03\x00\x05\x06\x07\x00\t\n\x0b\x00", + (3, 2, 1, X), + (7, 6, 5, X), + (11, 10, 9, X), + ) self.assert_pack( - "RGBX", "RGB", 3, (1, 2, 3, X), (4, 5, 6, X), (7, 8, 9, X)) - self.assert_pack( - "RGBX", "BGR", 3, (3, 2, 1, X), (6, 5, 4, X), (9, 8, 7, X)) - self.assert_pack( - "RGBX", "BGRX", - b'\x01\x02\x03\x00\x05\x06\x07\x00\t\n\x0b\x00', - (3, 2, 1, X), (7, 6, 5, X), (11, 10, 9, X)) - self.assert_pack( - "RGBX", "XBGR", - b'\x00\x02\x03\x04\x00\x06\x07\x08\x00\n\x0b\x0c', - (4, 3, 2, X), (8, 7, 6, X), (12, 11, 10, X)) - self.assert_pack("RGBX", "R", 1, - (1, 0, 8, 9), (2, 0, 8, 9), (3, 0, 8, 0)) - self.assert_pack("RGBX", "G", 1, - (6, 1, 8, 9), (6, 2, 8, 9), (6, 3, 8, 9)) - self.assert_pack("RGBX", "B", 1, - (6, 7, 1, 9), (6, 7, 2, 0), (6, 7, 3, 9)) - self.assert_pack("RGBX", "X", 1, - (6, 7, 0, 1), (6, 7, 0, 2), (0, 7, 0, 3)) + "RGBX", + "XBGR", + b"\x00\x02\x03\x04\x00\x06\x07\x08\x00\n\x0b\x0c", + (4, 3, 2, X), + (8, 7, 6, X), + (12, 11, 10, X), + ) + self.assert_pack("RGBX", "R", 1, (1, 0, 8, 9), (2, 0, 8, 9), (3, 0, 8, 0)) + self.assert_pack("RGBX", "G", 1, (6, 1, 8, 9), (6, 2, 8, 9), (6, 3, 8, 9)) + self.assert_pack("RGBX", "B", 1, (6, 7, 1, 9), (6, 7, 2, 0), (6, 7, 3, 9)) + self.assert_pack("RGBX", "X", 1, (6, 7, 0, 1), (6, 7, 0, 2), (0, 7, 0, 3)) def test_CMYK(self): - self.assert_pack("CMYK", "CMYK", 4, - (1, 2, 3, 4), (5, 6, 7, 8), (9, 10, 11, 12)) + self.assert_pack("CMYK", "CMYK", 4, (1, 2, 3, 4), (5, 6, 7, 8), (9, 10, 11, 12)) self.assert_pack( - "CMYK", "CMYK;I", 4, - (254, 253, 252, 251), (250, 249, 248, 247), (246, 245, 244, 243)) + "CMYK", + "CMYK;I", + 4, + (254, 253, 252, 251), + (250, 249, 248, 247), + (246, 245, 244, 243), + ) self.assert_pack( - "CMYK", "CMYK;L", 4, (1, 4, 7, 10), (2, 5, 8, 11), (3, 6, 9, 12)) - self.assert_pack("CMYK", "K", 1, - (6, 7, 0, 1), (6, 7, 0, 2), (0, 7, 0, 3)) + "CMYK", "CMYK;L", 4, (1, 4, 7, 10), (2, 5, 8, 11), (3, 6, 9, 12) + ) + self.assert_pack("CMYK", "K", 1, (6, 7, 0, 1), (6, 7, 0, 2), (0, 7, 0, 3)) def test_YCbCr(self): self.assert_pack("YCbCr", "YCbCr", 3, (1, 2, 3), (4, 5, 6), (7, 8, 9)) - self.assert_pack("YCbCr", "YCbCr;L", 3, - (1, 4, 7), (2, 5, 8), (3, 6, 9)) + self.assert_pack("YCbCr", "YCbCr;L", 3, (1, 4, 7), (2, 5, 8), (3, 6, 9)) self.assert_pack( - "YCbCr", "YCbCrX", - b'\x01\x02\x03\xff\x05\x06\x07\xff\t\n\x0b\xff', - (1, 2, 3), (5, 6, 7), (9, 10, 11)) + "YCbCr", + "YCbCrX", + b"\x01\x02\x03\xff\x05\x06\x07\xff\t\n\x0b\xff", + (1, 2, 3), + (5, 6, 7), + (9, 10, 11), + ) self.assert_pack( - "YCbCr", "YCbCrK", - b'\x01\x02\x03\xff\x05\x06\x07\xff\t\n\x0b\xff', - (1, 2, 3), (5, 6, 7), (9, 10, 11)) - self.assert_pack("YCbCr", "Y", 1, - (1, 0, 8, 9), (2, 0, 8, 9), (3, 0, 8, 0)) - self.assert_pack("YCbCr", "Cb", 1, - (6, 1, 8, 9), (6, 2, 8, 9), (6, 3, 8, 9)) - self.assert_pack("YCbCr", "Cr", 1, - (6, 7, 1, 9), (6, 7, 2, 0), (6, 7, 3, 9)) + "YCbCr", + "YCbCrK", + b"\x01\x02\x03\xff\x05\x06\x07\xff\t\n\x0b\xff", + (1, 2, 3), + (5, 6, 7), + (9, 10, 11), + ) + self.assert_pack("YCbCr", "Y", 1, (1, 0, 8, 9), (2, 0, 8, 9), (3, 0, 8, 0)) + self.assert_pack("YCbCr", "Cb", 1, (6, 1, 8, 9), (6, 2, 8, 9), (6, 3, 8, 9)) + self.assert_pack("YCbCr", "Cr", 1, (6, 7, 1, 9), (6, 7, 2, 0), (6, 7, 3, 9)) def test_LAB(self): - self.assert_pack( - "LAB", "LAB", 3, (1, 130, 131), (4, 133, 134), (7, 136, 137)) + self.assert_pack("LAB", "LAB", 3, (1, 130, 131), (4, 133, 134), (7, 136, 137)) self.assert_pack("LAB", "L", 1, (1, 9, 9), (2, 9, 9), (3, 9, 9)) self.assert_pack("LAB", "A", 1, (9, 1, 9), (9, 2, 9), (9, 3, 9)) self.assert_pack("LAB", "B", 1, (9, 9, 1), (9, 9, 2), (9, 9, 3)) @@ -182,34 +183,41 @@ class TestLibPack(PillowTestCase): def test_I(self): self.assert_pack("I", "I;16B", 2, 0x0102, 0x0304) self.assert_pack( - "I", "I;32S", - b'\x83\x00\x00\x01\x01\x00\x00\x83', 0x01000083, -2097151999) + "I", "I;32S", b"\x83\x00\x00\x01\x01\x00\x00\x83", 0x01000083, -2097151999 + ) - if sys.byteorder == 'little': + if sys.byteorder == "little": self.assert_pack("I", "I", 4, 0x04030201, 0x08070605) self.assert_pack( - "I", "I;32NS", - b'\x83\x00\x00\x01\x01\x00\x00\x83', 0x01000083, -2097151999) + "I", + "I;32NS", + b"\x83\x00\x00\x01\x01\x00\x00\x83", + 0x01000083, + -2097151999, + ) else: self.assert_pack("I", "I", 4, 0x01020304, 0x05060708) self.assert_pack( - "I", "I;32NS", - b'\x83\x00\x00\x01\x01\x00\x00\x83', -2097151999, 0x01000083) + "I", + "I;32NS", + b"\x83\x00\x00\x01\x01\x00\x00\x83", + -2097151999, + 0x01000083, + ) def test_F_float(self): - self.assert_pack( - "F", "F;32F", 4, 1.539989614439558e-36, 4.063216068939723e-34) + self.assert_pack("F", "F;32F", 4, 1.539989614439558e-36, 4.063216068939723e-34) - if sys.byteorder == 'little': + if sys.byteorder == "little": + self.assert_pack("F", "F", 4, 1.539989614439558e-36, 4.063216068939723e-34) self.assert_pack( - "F", "F", 4, 1.539989614439558e-36, 4.063216068939723e-34) - self.assert_pack( - "F", "F;32NF", 4, 1.539989614439558e-36, 4.063216068939723e-34) + "F", "F;32NF", 4, 1.539989614439558e-36, 4.063216068939723e-34 + ) else: + self.assert_pack("F", "F", 4, 2.387939260590663e-38, 6.301941157072183e-36) self.assert_pack( - "F", "F", 4, 2.387939260590663e-38, 6.301941157072183e-36) - self.assert_pack( - "F", "F;32NF", 4, 2.387939260590663e-38, 6.301941157072183e-36) + "F", "F;32NF", 4, 2.387939260590663e-38, 6.301941157072183e-36 + ) class TestLibUnpack(PillowTestCase): @@ -221,54 +229,53 @@ class TestLibUnpack(PillowTestCase): data_len = data * len(pixels) data = bytes(bytearray(range(1, data_len + 1))) - im = Image.frombytes(mode, (len(pixels), 1), data, - "raw", rawmode, 0, 1) + im = Image.frombytes(mode, (len(pixels), 1), data, "raw", rawmode, 0, 1) for x, pixel in enumerate(pixels): self.assertEqual(pixel, im.getpixel((x, 0))) def test_1(self): - self.assert_unpack("1", "1", b'\x01', 0, 0, 0, 0, 0, 0, 0, X) - self.assert_unpack("1", "1;I", b'\x01', X, X, X, X, X, X, X, 0) - self.assert_unpack("1", "1;R", b'\x01', X, 0, 0, 0, 0, 0, 0, 0) - self.assert_unpack("1", "1;IR", b'\x01', 0, X, X, X, X, X, X, X) + self.assert_unpack("1", "1", b"\x01", 0, 0, 0, 0, 0, 0, 0, X) + self.assert_unpack("1", "1;I", b"\x01", X, X, X, X, X, X, X, 0) + self.assert_unpack("1", "1;R", b"\x01", X, 0, 0, 0, 0, 0, 0, 0) + self.assert_unpack("1", "1;IR", b"\x01", 0, X, X, X, X, X, X, X) - self.assert_unpack("1", "1", b'\xaa', X, 0, X, 0, X, 0, X, 0) - self.assert_unpack("1", "1;I", b'\xaa', 0, X, 0, X, 0, X, 0, X) - self.assert_unpack("1", "1;R", b'\xaa', 0, X, 0, X, 0, X, 0, X) - self.assert_unpack("1", "1;IR", b'\xaa', X, 0, X, 0, X, 0, X, 0) + self.assert_unpack("1", "1", b"\xaa", X, 0, X, 0, X, 0, X, 0) + self.assert_unpack("1", "1;I", b"\xaa", 0, X, 0, X, 0, X, 0, X) + self.assert_unpack("1", "1;R", b"\xaa", 0, X, 0, X, 0, X, 0, X) + self.assert_unpack("1", "1;IR", b"\xaa", X, 0, X, 0, X, 0, X, 0) - self.assert_unpack("1", "1;8", b'\x00\x01\x02\xff', 0, X, X, X) + self.assert_unpack("1", "1;8", b"\x00\x01\x02\xff", 0, X, X, X) def test_L(self): - self.assert_unpack("L", "L;2", b'\xe4', 255, 170, 85, 0) - self.assert_unpack("L", "L;2I", b'\xe4', 0, 85, 170, 255) - self.assert_unpack("L", "L;2R", b'\xe4', 0, 170, 85, 255) - self.assert_unpack("L", "L;2IR", b'\xe4', 255, 85, 170, 0) + self.assert_unpack("L", "L;2", b"\xe4", 255, 170, 85, 0) + self.assert_unpack("L", "L;2I", b"\xe4", 0, 85, 170, 255) + self.assert_unpack("L", "L;2R", b"\xe4", 0, 170, 85, 255) + self.assert_unpack("L", "L;2IR", b"\xe4", 255, 85, 170, 0) - self.assert_unpack("L", "L;4", b'\x02\xef', 0, 34, 238, 255) - self.assert_unpack("L", "L;4I", b'\x02\xef', 255, 221, 17, 0) - self.assert_unpack("L", "L;4R", b'\x02\xef', 68, 0, 255, 119) - self.assert_unpack("L", "L;4IR", b'\x02\xef', 187, 255, 0, 136) + self.assert_unpack("L", "L;4", b"\x02\xef", 0, 34, 238, 255) + self.assert_unpack("L", "L;4I", b"\x02\xef", 255, 221, 17, 0) + self.assert_unpack("L", "L;4R", b"\x02\xef", 68, 0, 255, 119) + self.assert_unpack("L", "L;4IR", b"\x02\xef", 187, 255, 0, 136) self.assert_unpack("L", "L", 1, 1, 2, 3, 4) self.assert_unpack("L", "L;I", 1, 254, 253, 252, 251) self.assert_unpack("L", "L;R", 1, 128, 64, 192, 32) self.assert_unpack("L", "L;16", 2, 2, 4, 6, 8) self.assert_unpack("L", "L;16B", 2, 1, 3, 5, 7) - self.assert_unpack("L", "L;16", b'\x00\xc6\x00\xaf', 198, 175) - self.assert_unpack("L", "L;16B", b'\xc6\x00\xaf\x00', 198, 175) + self.assert_unpack("L", "L;16", b"\x00\xc6\x00\xaf", 198, 175) + self.assert_unpack("L", "L;16B", b"\xc6\x00\xaf\x00", 198, 175) def test_LA(self): self.assert_unpack("LA", "LA", 2, (1, 2), (3, 4), (5, 6)) self.assert_unpack("LA", "LA;L", 2, (1, 4), (2, 5), (3, 6)) def test_P(self): - self.assert_unpack("P", "P;1", b'\xe4', 1, 1, 1, 0, 0, 1, 0, 0) - self.assert_unpack("P", "P;2", b'\xe4', 3, 2, 1, 0) + self.assert_unpack("P", "P;1", b"\xe4", 1, 1, 1, 0, 0, 1, 0, 0) + self.assert_unpack("P", "P;2", b"\xe4", 3, 2, 1, 0) # erroneous? # self.assert_unpack("P", "P;2L", b'\xe4', 1, 1, 1, 0) - self.assert_unpack("P", "P;4", b'\x02\xef', 0, 2, 14, 15) + self.assert_unpack("P", "P;4", b"\x02\xef", 0, 2, 14, 15) # erroneous? # self.assert_unpack("P", "P;4L", b'\x02\xef', 2, 10, 10, 0) self.assert_unpack("P", "P", 1, 1, 2, 3, 4) @@ -293,195 +300,256 @@ class TestLibUnpack(PillowTestCase): self.assert_unpack("RGB", "RGBX", 4, (1, 2, 3), (5, 6, 7), (9, 10, 11)) self.assert_unpack("RGB", "RGBX;L", 4, (1, 4, 7), (2, 5, 8), (3, 6, 9)) self.assert_unpack("RGB", "BGRX", 4, (3, 2, 1), (7, 6, 5), (11, 10, 9)) + self.assert_unpack("RGB", "XRGB", 4, (2, 3, 4), (6, 7, 8), (10, 11, 12)) + self.assert_unpack("RGB", "XBGR", 4, (4, 3, 2), (8, 7, 6), (12, 11, 10)) self.assert_unpack( - "RGB", "XRGB", 4, (2, 3, 4), (6, 7, 8), (10, 11, 12)) - self.assert_unpack( - "RGB", "XBGR", 4, (4, 3, 2), (8, 7, 6), (12, 11, 10)) - self.assert_unpack( - "RGB", "YCC;P", - b'D]\x9c\x82\x1a\x91\xfaOC\xe7J\x12', # random data - (127, 102, 0), (192, 227, 0), (213, 255, 170), (98, 255, 133)) + "RGB", + "YCC;P", + b"D]\x9c\x82\x1a\x91\xfaOC\xe7J\x12", # random data + (127, 102, 0), + (192, 227, 0), + (213, 255, 170), + (98, 255, 133), + ) self.assert_unpack("RGB", "R", 1, (1, 0, 0), (2, 0, 0), (3, 0, 0)) self.assert_unpack("RGB", "G", 1, (0, 1, 0), (0, 2, 0), (0, 3, 0)) self.assert_unpack("RGB", "B", 1, (0, 0, 1), (0, 0, 2), (0, 0, 3)) def test_RGBA(self): + self.assert_unpack("RGBA", "LA", 2, (1, 1, 1, 2), (3, 3, 3, 4), (5, 5, 5, 6)) self.assert_unpack( - "RGBA", "LA", 2, (1, 1, 1, 2), (3, 3, 3, 4), (5, 5, 5, 6)) + "RGBA", "LA;16B", 4, (1, 1, 1, 3), (5, 5, 5, 7), (9, 9, 9, 11) + ) self.assert_unpack( - "RGBA", "LA;16B", 4, (1, 1, 1, 3), (5, 5, 5, 7), (9, 9, 9, 11)) + "RGBA", "RGBA", 4, (1, 2, 3, 4), (5, 6, 7, 8), (9, 10, 11, 12) + ) self.assert_unpack( - "RGBA", "RGBA", 4, (1, 2, 3, 4), (5, 6, 7, 8), (9, 10, 11, 12)) + "RGBA", "RGBAX", 5, (1, 2, 3, 4), (6, 7, 8, 9), (11, 12, 13, 14) + ) self.assert_unpack( - "RGBA", "RGBAX", 5, (1, 2, 3, 4), (6, 7, 8, 9), (11, 12, 13, 14)) + "RGBA", "RGBAXX", 6, (1, 2, 3, 4), (7, 8, 9, 10), (13, 14, 15, 16) + ) self.assert_unpack( - "RGBA", "RGBAXX", 6, (1, 2, 3, 4), (7, 8, 9, 10), (13, 14, 15, 16)) + "RGBA", + "RGBa", + 4, + (63, 127, 191, 4), + (159, 191, 223, 8), + (191, 212, 233, 12), + ) self.assert_unpack( - "RGBA", "RGBa", 4, - (63, 127, 191, 4), (159, 191, 223, 8), (191, 212, 233, 12)) + "RGBA", + "RGBa", + b"\x01\x02\x03\x00\x10\x20\x30\x7f\x10\x20\x30\xff", + (0, 0, 0, 0), + (32, 64, 96, 127), + (16, 32, 48, 255), + ) self.assert_unpack( - "RGBA", "RGBa", - b'\x01\x02\x03\x00\x10\x20\x30\x7f\x10\x20\x30\xff', - (0, 0, 0, 0), (32, 64, 96, 127), (16, 32, 48, 255)) + "RGBA", + "RGBaX", + b"\x01\x02\x03\x00-\x10\x20\x30\x7f-\x10\x20\x30\xff-", + (0, 0, 0, 0), + (32, 64, 96, 127), + (16, 32, 48, 255), + ) self.assert_unpack( - "RGBA", "RGBaX", - b'\x01\x02\x03\x00-\x10\x20\x30\x7f-\x10\x20\x30\xff-', - (0, 0, 0, 0), (32, 64, 96, 127), (16, 32, 48, 255)) + "RGBA", + "RGBaXX", + b"\x01\x02\x03\x00==\x10\x20\x30\x7f!!\x10\x20\x30\xff??", + (0, 0, 0, 0), + (32, 64, 96, 127), + (16, 32, 48, 255), + ) self.assert_unpack( - "RGBA", "RGBaXX", - b'\x01\x02\x03\x00==\x10\x20\x30\x7f!!\x10\x20\x30\xff??', - (0, 0, 0, 0), (32, 64, 96, 127), (16, 32, 48, 255)) + "RGBA", + "RGBa;16L", + 8, + (63, 127, 191, 8), + (159, 191, 223, 16), + (191, 212, 233, 24), + ) self.assert_unpack( - "RGBA", "RGBa;16L", 8, - (63, 127, 191, 8), (159, 191, 223, 16), (191, 212, 233, 24)) + "RGBA", + "RGBa;16L", + b"\x88\x01\x88\x02\x88\x03\x88\x00" b"\x88\x10\x88\x20\x88\x30\x88\xff", + (0, 0, 0, 0), + (16, 32, 48, 255), + ) self.assert_unpack( - "RGBA", "RGBa;16L", - b'\x88\x01\x88\x02\x88\x03\x88\x00' - b'\x88\x10\x88\x20\x88\x30\x88\xff', - (0, 0, 0, 0), (16, 32, 48, 255)) + "RGBA", + "RGBa;16B", + 8, + (36, 109, 182, 7), + (153, 187, 221, 15), + (188, 210, 232, 23), + ) self.assert_unpack( - "RGBA", "RGBa;16B", 8, - (36, 109, 182, 7), (153, 187, 221, 15), (188, 210, 232, 23)) + "RGBA", + "RGBa;16B", + b"\x01\x88\x02\x88\x03\x88\x00\x88" b"\x10\x88\x20\x88\x30\x88\xff\x88", + (0, 0, 0, 0), + (16, 32, 48, 255), + ) self.assert_unpack( - "RGBA", "RGBa;16B", - b'\x01\x88\x02\x88\x03\x88\x00\x88' - b'\x10\x88\x20\x88\x30\x88\xff\x88', - (0, 0, 0, 0), (16, 32, 48, 255)) + "RGBA", + "BGRa", + 4, + (191, 127, 63, 4), + (223, 191, 159, 8), + (233, 212, 191, 12), + ) self.assert_unpack( - "RGBA", "BGRa", 4, - (191, 127, 63, 4), (223, 191, 159, 8), (233, 212, 191, 12)) + "RGBA", + "BGRa", + b"\x01\x02\x03\x00\x10\x20\x30\xff", + (0, 0, 0, 0), + (48, 32, 16, 255), + ) self.assert_unpack( - "RGBA", "BGRa", - b'\x01\x02\x03\x00\x10\x20\x30\xff', - (0, 0, 0, 0), (48, 32, 16, 255)) + "RGBA", + "RGBA;I", + 4, + (254, 253, 252, 4), + (250, 249, 248, 8), + (246, 245, 244, 12), + ) self.assert_unpack( - "RGBA", "RGBA;I", 4, - (254, 253, 252, 4), (250, 249, 248, 8), (246, 245, 244, 12)) - self.assert_unpack( - "RGBA", "RGBA;L", 4, (1, 4, 7, 10), (2, 5, 8, 11), (3, 6, 9, 12)) + "RGBA", "RGBA;L", 4, (1, 4, 7, 10), (2, 5, 8, 11), (3, 6, 9, 12) + ) self.assert_unpack("RGBA", "RGBA;15", 2, (8, 131, 0, 0), (24, 0, 8, 0)) self.assert_unpack("RGBA", "BGRA;15", 2, (0, 131, 8, 0), (8, 0, 24, 0)) + self.assert_unpack("RGBA", "RGBA;4B", 2, (17, 0, 34, 0), (51, 0, 68, 0)) + self.assert_unpack("RGBA", "RGBA;16L", 8, (2, 4, 6, 8), (10, 12, 14, 16)) + self.assert_unpack("RGBA", "RGBA;16B", 8, (1, 3, 5, 7), (9, 11, 13, 15)) self.assert_unpack( - "RGBA", "RGBA;4B", 2, (17, 0, 34, 0), (51, 0, 68, 0)) + "RGBA", "BGRA", 4, (3, 2, 1, 4), (7, 6, 5, 8), (11, 10, 9, 12) + ) self.assert_unpack( - "RGBA", "RGBA;16L", 8, (2, 4, 6, 8), (10, 12, 14, 16)) + "RGBA", "ARGB", 4, (2, 3, 4, 1), (6, 7, 8, 5), (10, 11, 12, 9) + ) self.assert_unpack( - "RGBA", "RGBA;16B", 8, (1, 3, 5, 7), (9, 11, 13, 15)) + "RGBA", "ABGR", 4, (4, 3, 2, 1), (8, 7, 6, 5), (12, 11, 10, 9) + ) self.assert_unpack( - "RGBA", "BGRA", 4, (3, 2, 1, 4), (7, 6, 5, 8), (11, 10, 9, 12)) - self.assert_unpack( - "RGBA", "ARGB", 4, (2, 3, 4, 1), (6, 7, 8, 5), (10, 11, 12, 9)) - self.assert_unpack( - "RGBA", "ABGR", 4, (4, 3, 2, 1), (8, 7, 6, 5), (12, 11, 10, 9)) - self.assert_unpack( - "RGBA", "YCCA;P", - b']bE\x04\xdd\xbej\xed57T\xce\xac\xce:\x11', # random data - (0, 161, 0, 4), (255, 255, 255, 237), - (27, 158, 0, 206), (0, 118, 0, 17)) - self.assert_unpack( - "RGBA", "R", 1, (1, 0, 0, 0), (2, 0, 0, 0), (3, 0, 0, 0)) - self.assert_unpack( - "RGBA", "G", 1, (0, 1, 0, 0), (0, 2, 0, 0), (0, 3, 0, 0)) - self.assert_unpack( - "RGBA", "B", 1, (0, 0, 1, 0), (0, 0, 2, 0), (0, 0, 3, 0)) - self.assert_unpack( - "RGBA", "A", 1, (0, 0, 0, 1), (0, 0, 0, 2), (0, 0, 0, 3)) + "RGBA", + "YCCA;P", + b"]bE\x04\xdd\xbej\xed57T\xce\xac\xce:\x11", # random data + (0, 161, 0, 4), + (255, 255, 255, 237), + (27, 158, 0, 206), + (0, 118, 0, 17), + ) + self.assert_unpack("RGBA", "R", 1, (1, 0, 0, 0), (2, 0, 0, 0), (3, 0, 0, 0)) + self.assert_unpack("RGBA", "G", 1, (0, 1, 0, 0), (0, 2, 0, 0), (0, 3, 0, 0)) + self.assert_unpack("RGBA", "B", 1, (0, 0, 1, 0), (0, 0, 2, 0), (0, 0, 3, 0)) + self.assert_unpack("RGBA", "A", 1, (0, 0, 0, 1), (0, 0, 0, 2), (0, 0, 0, 3)) def test_RGBa(self): self.assert_unpack( - "RGBa", "RGBa", 4, (1, 2, 3, 4), (5, 6, 7, 8), (9, 10, 11, 12)) + "RGBa", "RGBa", 4, (1, 2, 3, 4), (5, 6, 7, 8), (9, 10, 11, 12) + ) self.assert_unpack( - "RGBa", "BGRa", 4, (3, 2, 1, 4), (7, 6, 5, 8), (11, 10, 9, 12)) + "RGBa", "BGRa", 4, (3, 2, 1, 4), (7, 6, 5, 8), (11, 10, 9, 12) + ) self.assert_unpack( - "RGBa", "aRGB", 4, (2, 3, 4, 1), (6, 7, 8, 5), (10, 11, 12, 9)) + "RGBa", "aRGB", 4, (2, 3, 4, 1), (6, 7, 8, 5), (10, 11, 12, 9) + ) self.assert_unpack( - "RGBa", "aBGR", 4, (4, 3, 2, 1), (8, 7, 6, 5), (12, 11, 10, 9)) + "RGBa", "aBGR", 4, (4, 3, 2, 1), (8, 7, 6, 5), (12, 11, 10, 9) + ) def test_RGBX(self): - self.assert_unpack("RGBX", "RGB", 3, - (1, 2, 3, X), (4, 5, 6, X), (7, 8, 9, X)) - self.assert_unpack("RGBX", "RGB;L", 3, - (1, 4, 7, X), (2, 5, 8, X), (3, 6, 9, X)) + self.assert_unpack("RGBX", "RGB", 3, (1, 2, 3, X), (4, 5, 6, X), (7, 8, 9, X)) + self.assert_unpack("RGBX", "RGB;L", 3, (1, 4, 7, X), (2, 5, 8, X), (3, 6, 9, X)) self.assert_unpack("RGBX", "RGB;16B", 6, (1, 3, 5, X), (7, 9, 11, X)) - self.assert_unpack("RGBX", "BGR", 3, - (3, 2, 1, X), (6, 5, 4, X), (9, 8, 7, X)) + self.assert_unpack("RGBX", "BGR", 3, (3, 2, 1, X), (6, 5, 4, X), (9, 8, 7, X)) self.assert_unpack("RGBX", "RGB;15", 2, (8, 131, 0, X), (24, 0, 8, X)) self.assert_unpack("RGBX", "BGR;15", 2, (0, 131, 8, X), (8, 0, 24, X)) self.assert_unpack("RGBX", "RGB;4B", 2, (17, 0, 34, X), (51, 0, 68, X)) self.assert_unpack( - "RGBX", "RGBX", 4, (1, 2, 3, 4), (5, 6, 7, 8), (9, 10, 11, 12)) + "RGBX", "RGBX", 4, (1, 2, 3, 4), (5, 6, 7, 8), (9, 10, 11, 12) + ) self.assert_unpack( - "RGBX", "RGBXX", 5, (1, 2, 3, 4), (6, 7, 8, 9), (11, 12, 13, 14)) + "RGBX", "RGBXX", 5, (1, 2, 3, 4), (6, 7, 8, 9), (11, 12, 13, 14) + ) self.assert_unpack( - "RGBX", "RGBXXX", 6, (1, 2, 3, 4), (7, 8, 9, 10), (13, 14, 15, 16)) + "RGBX", "RGBXXX", 6, (1, 2, 3, 4), (7, 8, 9, 10), (13, 14, 15, 16) + ) self.assert_unpack( - "RGBX", "RGBX;L", 4, (1, 4, 7, 10), (2, 5, 8, 11), (3, 6, 9, 12)) - self.assert_unpack("RGBX", "RGBX;16L", 8, - (2, 4, 6, 8), (10, 12, 14, 16)) - self.assert_unpack("RGBX", "RGBX;16B", 8, - (1, 3, 5, 7), (9, 11, 13, 15)) - self.assert_unpack("RGBX", "BGRX", 4, - (3, 2, 1, X), (7, 6, 5, X), (11, 10, 9, X)) - self.assert_unpack("RGBX", "XRGB", 4, - (2, 3, 4, X), (6, 7, 8, X), (10, 11, 12, X)) - self.assert_unpack("RGBX", "XBGR", 4, - (4, 3, 2, X), (8, 7, 6, X), (12, 11, 10, X)) + "RGBX", "RGBX;L", 4, (1, 4, 7, 10), (2, 5, 8, 11), (3, 6, 9, 12) + ) + self.assert_unpack("RGBX", "RGBX;16L", 8, (2, 4, 6, 8), (10, 12, 14, 16)) + self.assert_unpack("RGBX", "RGBX;16B", 8, (1, 3, 5, 7), (9, 11, 13, 15)) self.assert_unpack( - "RGBX", "YCC;P", - b'D]\x9c\x82\x1a\x91\xfaOC\xe7J\x12', # random data - (127, 102, 0, X), (192, 227, 0, X), - (213, 255, 170, X), (98, 255, 133, X)) - self.assert_unpack("RGBX", "R", 1, - (1, 0, 0, 0), (2, 0, 0, 0), (3, 0, 0, 0)) - self.assert_unpack("RGBX", "G", 1, - (0, 1, 0, 0), (0, 2, 0, 0), (0, 3, 0, 0)) - self.assert_unpack("RGBX", "B", 1, - (0, 0, 1, 0), (0, 0, 2, 0), (0, 0, 3, 0)) - self.assert_unpack("RGBX", "X", 1, - (0, 0, 0, 1), (0, 0, 0, 2), (0, 0, 0, 3)) + "RGBX", "BGRX", 4, (3, 2, 1, X), (7, 6, 5, X), (11, 10, 9, X) + ) + self.assert_unpack( + "RGBX", "XRGB", 4, (2, 3, 4, X), (6, 7, 8, X), (10, 11, 12, X) + ) + self.assert_unpack( + "RGBX", "XBGR", 4, (4, 3, 2, X), (8, 7, 6, X), (12, 11, 10, X) + ) + self.assert_unpack( + "RGBX", + "YCC;P", + b"D]\x9c\x82\x1a\x91\xfaOC\xe7J\x12", # random data + (127, 102, 0, X), + (192, 227, 0, X), + (213, 255, 170, X), + (98, 255, 133, X), + ) + self.assert_unpack("RGBX", "R", 1, (1, 0, 0, 0), (2, 0, 0, 0), (3, 0, 0, 0)) + self.assert_unpack("RGBX", "G", 1, (0, 1, 0, 0), (0, 2, 0, 0), (0, 3, 0, 0)) + self.assert_unpack("RGBX", "B", 1, (0, 0, 1, 0), (0, 0, 2, 0), (0, 0, 3, 0)) + self.assert_unpack("RGBX", "X", 1, (0, 0, 0, 1), (0, 0, 0, 2), (0, 0, 0, 3)) def test_CMYK(self): self.assert_unpack( - "CMYK", "CMYK", 4, (1, 2, 3, 4), (5, 6, 7, 8), (9, 10, 11, 12)) + "CMYK", "CMYK", 4, (1, 2, 3, 4), (5, 6, 7, 8), (9, 10, 11, 12) + ) self.assert_unpack( - "CMYK", "CMYKX", 5, (1, 2, 3, 4), (6, 7, 8, 9), (11, 12, 13, 14)) + "CMYK", "CMYKX", 5, (1, 2, 3, 4), (6, 7, 8, 9), (11, 12, 13, 14) + ) self.assert_unpack( - "CMYK", "CMYKXX", 6, (1, 2, 3, 4), (7, 8, 9, 10), (13, 14, 15, 16)) + "CMYK", "CMYKXX", 6, (1, 2, 3, 4), (7, 8, 9, 10), (13, 14, 15, 16) + ) self.assert_unpack( - "CMYK", "CMYK;I", 4, - (254, 253, 252, 251), (250, 249, 248, 247), (246, 245, 244, 243)) + "CMYK", + "CMYK;I", + 4, + (254, 253, 252, 251), + (250, 249, 248, 247), + (246, 245, 244, 243), + ) self.assert_unpack( - "CMYK", "CMYK;L", 4, (1, 4, 7, 10), (2, 5, 8, 11), (3, 6, 9, 12)) - self.assert_unpack("CMYK", "C", 1, - (1, 0, 0, 0), (2, 0, 0, 0), (3, 0, 0, 0)) - self.assert_unpack("CMYK", "M", 1, - (0, 1, 0, 0), (0, 2, 0, 0), (0, 3, 0, 0)) - self.assert_unpack("CMYK", "Y", 1, - (0, 0, 1, 0), (0, 0, 2, 0), (0, 0, 3, 0)) - self.assert_unpack("CMYK", "K", 1, - (0, 0, 0, 1), (0, 0, 0, 2), (0, 0, 0, 3)) + "CMYK", "CMYK;L", 4, (1, 4, 7, 10), (2, 5, 8, 11), (3, 6, 9, 12) + ) + self.assert_unpack("CMYK", "C", 1, (1, 0, 0, 0), (2, 0, 0, 0), (3, 0, 0, 0)) + self.assert_unpack("CMYK", "M", 1, (0, 1, 0, 0), (0, 2, 0, 0), (0, 3, 0, 0)) + self.assert_unpack("CMYK", "Y", 1, (0, 0, 1, 0), (0, 0, 2, 0), (0, 0, 3, 0)) + self.assert_unpack("CMYK", "K", 1, (0, 0, 0, 1), (0, 0, 0, 2), (0, 0, 0, 3)) self.assert_unpack( - "CMYK", "C;I", 1, (254, 0, 0, 0), (253, 0, 0, 0), (252, 0, 0, 0)) + "CMYK", "C;I", 1, (254, 0, 0, 0), (253, 0, 0, 0), (252, 0, 0, 0) + ) self.assert_unpack( - "CMYK", "M;I", 1, (0, 254, 0, 0), (0, 253, 0, 0), (0, 252, 0, 0)) + "CMYK", "M;I", 1, (0, 254, 0, 0), (0, 253, 0, 0), (0, 252, 0, 0) + ) self.assert_unpack( - "CMYK", "Y;I", 1, (0, 0, 254, 0), (0, 0, 253, 0), (0, 0, 252, 0)) + "CMYK", "Y;I", 1, (0, 0, 254, 0), (0, 0, 253, 0), (0, 0, 252, 0) + ) self.assert_unpack( - "CMYK", "K;I", 1, (0, 0, 0, 254), (0, 0, 0, 253), (0, 0, 0, 252)) + "CMYK", "K;I", 1, (0, 0, 0, 254), (0, 0, 0, 253), (0, 0, 0, 252) + ) def test_YCbCr(self): - self.assert_unpack( - "YCbCr", "YCbCr", 3, (1, 2, 3), (4, 5, 6), (7, 8, 9)) - self.assert_unpack( - "YCbCr", "YCbCr;L", 3, (1, 4, 7), (2, 5, 8), (3, 6, 9)) - self.assert_unpack( - "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", "YCbCr", 3, (1, 2, 3), (4, 5, 6), (7, 8, 9)) + self.assert_unpack("YCbCr", "YCbCr;L", 3, (1, 4, 7), (2, 5, 8), (3, 6, 9)) + self.assert_unpack("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)) def test_LAB(self): - self.assert_unpack( - "LAB", "LAB", 3, (1, 130, 131), (4, 133, 134), (7, 136, 137)) + self.assert_unpack("LAB", "LAB", 3, (1, 130, 131), (4, 133, 134), (7, 136, 137)) self.assert_unpack("LAB", "L", 1, (1, 0, 0), (2, 0, 0), (3, 0, 0)) self.assert_unpack("LAB", "A", 1, (0, 1, 0), (0, 2, 0), (0, 3, 0)) self.assert_unpack("LAB", "B", 1, (0, 0, 1), (0, 0, 2), (0, 0, 3)) @@ -494,120 +562,141 @@ class TestLibUnpack(PillowTestCase): def test_I(self): self.assert_unpack("I", "I;8", 1, 0x01, 0x02, 0x03, 0x04) - self.assert_unpack("I", "I;8S", b'\x01\x83', 1, -125) + self.assert_unpack("I", "I;8S", b"\x01\x83", 1, -125) self.assert_unpack("I", "I;16", 2, 0x0201, 0x0403) - self.assert_unpack("I", "I;16S", b'\x83\x01\x01\x83', 0x0183, -31999) + self.assert_unpack("I", "I;16S", b"\x83\x01\x01\x83", 0x0183, -31999) self.assert_unpack("I", "I;16B", 2, 0x0102, 0x0304) - self.assert_unpack("I", "I;16BS", b'\x83\x01\x01\x83', -31999, 0x0183) + self.assert_unpack("I", "I;16BS", b"\x83\x01\x01\x83", -31999, 0x0183) self.assert_unpack("I", "I;32", 4, 0x04030201, 0x08070605) self.assert_unpack( - "I", "I;32S", - b'\x83\x00\x00\x01\x01\x00\x00\x83', 0x01000083, -2097151999) + "I", "I;32S", b"\x83\x00\x00\x01\x01\x00\x00\x83", 0x01000083, -2097151999 + ) self.assert_unpack("I", "I;32B", 4, 0x01020304, 0x05060708) self.assert_unpack( - "I", "I;32BS", - b'\x83\x00\x00\x01\x01\x00\x00\x83', -2097151999, 0x01000083) + "I", "I;32BS", b"\x83\x00\x00\x01\x01\x00\x00\x83", -2097151999, 0x01000083 + ) - if sys.byteorder == 'little': + if sys.byteorder == "little": self.assert_unpack("I", "I", 4, 0x04030201, 0x08070605) self.assert_unpack("I", "I;16N", 2, 0x0201, 0x0403) - self.assert_unpack("I", "I;16NS", - b'\x83\x01\x01\x83', 0x0183, -31999) + self.assert_unpack("I", "I;16NS", b"\x83\x01\x01\x83", 0x0183, -31999) self.assert_unpack("I", "I;32N", 4, 0x04030201, 0x08070605) self.assert_unpack( - "I", "I;32NS", - b'\x83\x00\x00\x01\x01\x00\x00\x83', 0x01000083, -2097151999) + "I", + "I;32NS", + b"\x83\x00\x00\x01\x01\x00\x00\x83", + 0x01000083, + -2097151999, + ) else: self.assert_unpack("I", "I", 4, 0x01020304, 0x05060708) self.assert_unpack("I", "I;16N", 2, 0x0102, 0x0304) - self.assert_unpack("I", "I;16NS", - b'\x83\x01\x01\x83', -31999, 0x0183) + self.assert_unpack("I", "I;16NS", b"\x83\x01\x01\x83", -31999, 0x0183) self.assert_unpack("I", "I;32N", 4, 0x01020304, 0x05060708) self.assert_unpack( - "I", "I;32NS", - b'\x83\x00\x00\x01\x01\x00\x00\x83', -2097151999, 0x01000083) + "I", + "I;32NS", + b"\x83\x00\x00\x01\x01\x00\x00\x83", + -2097151999, + 0x01000083, + ) def test_F_int(self): self.assert_unpack("F", "F;8", 1, 0x01, 0x02, 0x03, 0x04) - self.assert_unpack("F", "F;8S", b'\x01\x83', 1, -125) + self.assert_unpack("F", "F;8S", b"\x01\x83", 1, -125) self.assert_unpack("F", "F;16", 2, 0x0201, 0x0403) - self.assert_unpack("F", "F;16S", b'\x83\x01\x01\x83', 0x0183, -31999) + self.assert_unpack("F", "F;16S", b"\x83\x01\x01\x83", 0x0183, -31999) self.assert_unpack("F", "F;16B", 2, 0x0102, 0x0304) - self.assert_unpack("F", "F;16BS", b'\x83\x01\x01\x83', -31999, 0x0183) + self.assert_unpack("F", "F;16BS", b"\x83\x01\x01\x83", -31999, 0x0183) self.assert_unpack("F", "F;32", 4, 67305984, 134678016) self.assert_unpack( - "F", "F;32S", - b'\x83\x00\x00\x01\x01\x00\x00\x83', 16777348, -2097152000) + "F", "F;32S", b"\x83\x00\x00\x01\x01\x00\x00\x83", 16777348, -2097152000 + ) self.assert_unpack("F", "F;32B", 4, 0x01020304, 0x05060708) self.assert_unpack( - "F", "F;32BS", - b'\x83\x00\x00\x01\x01\x00\x00\x83', -2097152000, 16777348) + "F", "F;32BS", b"\x83\x00\x00\x01\x01\x00\x00\x83", -2097152000, 16777348 + ) - if sys.byteorder == 'little': + if sys.byteorder == "little": self.assert_unpack("F", "F;16N", 2, 0x0201, 0x0403) - self.assert_unpack( - "F", "F;16NS", - b'\x83\x01\x01\x83', 0x0183, -31999) + self.assert_unpack("F", "F;16NS", b"\x83\x01\x01\x83", 0x0183, -31999) self.assert_unpack("F", "F;32N", 4, 67305984, 134678016) self.assert_unpack( - "F", "F;32NS", - b'\x83\x00\x00\x01\x01\x00\x00\x83', 16777348, -2097152000) + "F", + "F;32NS", + b"\x83\x00\x00\x01\x01\x00\x00\x83", + 16777348, + -2097152000, + ) else: self.assert_unpack("F", "F;16N", 2, 0x0102, 0x0304) - self.assert_unpack( - "F", "F;16NS", - b'\x83\x01\x01\x83', -31999, 0x0183) + self.assert_unpack("F", "F;16NS", b"\x83\x01\x01\x83", -31999, 0x0183) self.assert_unpack("F", "F;32N", 4, 0x01020304, 0x05060708) self.assert_unpack( - "F", "F;32NS", - b'\x83\x00\x00\x01\x01\x00\x00\x83', - -2097152000, 16777348) + "F", + "F;32NS", + b"\x83\x00\x00\x01\x01\x00\x00\x83", + -2097152000, + 16777348, + ) def test_F_float(self): self.assert_unpack( - "F", "F;32F", 4, - 1.539989614439558e-36, 4.063216068939723e-34) + "F", "F;32F", 4, 1.539989614439558e-36, 4.063216068939723e-34 + ) self.assert_unpack( - "F", "F;32BF", 4, - 2.387939260590663e-38, 6.301941157072183e-36) + "F", "F;32BF", 4, 2.387939260590663e-38, 6.301941157072183e-36 + ) self.assert_unpack( - "F", "F;64F", - b'333333\xc3?\x00\x00\x00\x00\x00J\x93\xc0', # by struct.pack - 0.15000000596046448, -1234.5) + "F", + "F;64F", + b"333333\xc3?\x00\x00\x00\x00\x00J\x93\xc0", # by struct.pack + 0.15000000596046448, + -1234.5, + ) self.assert_unpack( - "F", "F;64BF", - b'?\xc3333333\xc0\x93J\x00\x00\x00\x00\x00', # by struct.pack - 0.15000000596046448, -1234.5) + "F", + "F;64BF", + b"?\xc3333333\xc0\x93J\x00\x00\x00\x00\x00", # by struct.pack + 0.15000000596046448, + -1234.5, + ) - if sys.byteorder == 'little': + if sys.byteorder == "little": self.assert_unpack( - "F", "F", 4, - 1.539989614439558e-36, 4.063216068939723e-34) + "F", "F", 4, 1.539989614439558e-36, 4.063216068939723e-34 + ) self.assert_unpack( - "F", "F;32NF", 4, - 1.539989614439558e-36, 4.063216068939723e-34) + "F", "F;32NF", 4, 1.539989614439558e-36, 4.063216068939723e-34 + ) self.assert_unpack( - "F", "F;64NF", - b'333333\xc3?\x00\x00\x00\x00\x00J\x93\xc0', - 0.15000000596046448, -1234.5) + "F", + "F;64NF", + b"333333\xc3?\x00\x00\x00\x00\x00J\x93\xc0", + 0.15000000596046448, + -1234.5, + ) else: self.assert_unpack( - "F", "F", 4, - 2.387939260590663e-38, 6.301941157072183e-36) + "F", "F", 4, 2.387939260590663e-38, 6.301941157072183e-36 + ) self.assert_unpack( - "F", "F;32NF", 4, - 2.387939260590663e-38, 6.301941157072183e-36) + "F", "F;32NF", 4, 2.387939260590663e-38, 6.301941157072183e-36 + ) self.assert_unpack( - "F", "F;64NF", - b'?\xc3333333\xc0\x93J\x00\x00\x00\x00\x00', - 0.15000000596046448, -1234.5) + "F", + "F;64NF", + b"?\xc3333333\xc0\x93J\x00\x00\x00\x00\x00", + 0.15000000596046448, + -1234.5, + ) def test_I16(self): self.assert_unpack("I;16", "I;16", 2, 0x0201, 0x0403, 0x0605) self.assert_unpack("I;16B", "I;16B", 2, 0x0102, 0x0304, 0x0506) self.assert_unpack("I;16L", "I;16L", 2, 0x0201, 0x0403, 0x0605) self.assert_unpack("I;16", "I;12", 2, 0x0010, 0x0203, 0x0040) - if sys.byteorder == 'little': + if sys.byteorder == "little": self.assert_unpack("I;16", "I;16N", 2, 0x0201, 0x0403, 0x0605) self.assert_unpack("I;16B", "I;16N", 2, 0x0201, 0x0403, 0x0605) self.assert_unpack("I;16L", "I;16N", 2, 0x0201, 0x0403, 0x0605) @@ -616,6 +705,14 @@ class TestLibUnpack(PillowTestCase): self.assert_unpack("I;16B", "I;16N", 2, 0x0102, 0x0304, 0x0506) self.assert_unpack("I;16L", "I;16N", 2, 0x0102, 0x0304, 0x0506) + def test_CMYK16(self): + self.assert_unpack("CMYK", "CMYK;16L", 8, (2, 4, 6, 8), (10, 12, 14, 16)) + self.assert_unpack("CMYK", "CMYK;16B", 8, (1, 3, 5, 7), (9, 11, 13, 15)) + if sys.byteorder == "little": + self.assert_unpack("CMYK", "CMYK;16N", 8, (2, 4, 6, 8), (10, 12, 14, 16)) + else: + self.assert_unpack("CMYK", "CMYK;16N", 8, (1, 3, 5, 7), (9, 11, 13, 15)) + def test_value_error(self): self.assertRaises(ValueError, self.assert_unpack, "L", "L", 0, 0) self.assertRaises(ValueError, self.assert_unpack, "RGB", "RGB", 2, 0) diff --git a/Tests/test_locale.py b/Tests/test_locale.py index d40019e59..8836e4f5f 100644 --- a/Tests/test_locale.py +++ b/Tests/test_locale.py @@ -24,11 +24,10 @@ path = "Tests/images/hopper.jpg" class TestLocale(PillowTestCase): - def test_sanity(self): Image.open(path) try: locale.setlocale(locale.LC_ALL, "polish") except locale.Error: - unittest.skip('Polish locale not available') + unittest.skip("Polish locale not available") Image.open(path) diff --git a/Tests/test_main.py b/Tests/test_main.py new file mode 100644 index 000000000..847def834 --- /dev/null +++ b/Tests/test_main.py @@ -0,0 +1,33 @@ +from __future__ import unicode_literals + +import os +import subprocess +import sys +from unittest import TestCase + + +class TestMain(TestCase): + def test_main(self): + out = subprocess.check_output([sys.executable, "-m", "PIL"]).decode("utf-8") + lines = out.splitlines() + self.assertEqual(lines[0], "-" * 68) + self.assertTrue(lines[1].startswith("Pillow ")) + self.assertEqual(lines[2], "-" * 68) + self.assertTrue(lines[3].startswith("Python modules loaded from ")) + self.assertTrue(lines[4].startswith("Binary modules loaded from ")) + self.assertEqual(lines[5], "-" * 68) + self.assertTrue(lines[6].startswith("Python ")) + jpeg = ( + os.linesep + + "-" * 68 + + os.linesep + + "JPEG image/jpeg" + + os.linesep + + "Extensions: .jfif, .jpe, .jpeg, .jpg" + + os.linesep + + "Features: open, save" + + os.linesep + + "-" * 68 + + os.linesep + ) + self.assertIn(jpeg, out) diff --git a/Tests/test_map.py b/Tests/test_map.py index 2eeb1fc5f..33920f118 100644 --- a/Tests/test_map.py +++ b/Tests/test_map.py @@ -4,8 +4,7 @@ import sys from PIL import Image -@unittest.skipIf(sys.platform.startswith('win32'), - "Win32 does not call map_buffer") +@unittest.skipIf(sys.platform.startswith("win32"), "Win32 does not call map_buffer") class TestMap(PillowTestCase): def test_overflow(self): # There is the potential to overflow comparisons in map.c @@ -18,7 +17,7 @@ class TestMap(PillowTestCase): Image.MAX_IMAGE_PIXELS = None # This image hits the offset test. - im = Image.open('Tests/images/l2rgb_read.bmp') + im = Image.open("Tests/images/l2rgb_read.bmp") with self.assertRaises((ValueError, MemoryError, IOError)): im.load() diff --git a/Tests/test_mode_i16.py b/Tests/test_mode_i16.py index 80730a312..27ca03ce5 100644 --- a/Tests/test_mode_i16.py +++ b/Tests/test_mode_i16.py @@ -5,7 +5,7 @@ from PIL import Image class TestModeI16(PillowTestCase): - original = hopper().resize((32, 32)).convert('I') + original = hopper().resize((32, 32)).convert("I") def verify(self, im1): im2 = self.original.copy() @@ -18,9 +18,10 @@ class TestModeI16(PillowTestCase): p1 = pix1[xy] p2 = pix2[xy] self.assertEqual( - p1, p2, - ("got %r from mode %s at %s, expected %r" % - (p1, im1.mode, xy, p2))) + p1, + p2, + ("got %r from mode %s at %s, expected %r" % (p1, im1.mode, xy, p2)), + ) def test_basic(self): # PIL 1.1 has limited support for 16-bit image data. Check that @@ -51,8 +52,8 @@ class TestModeI16(PillowTestCase): self.verify(imOut) imOut = Image.new(mode, (w, h), None) - imOut.paste(imIn.crop((0, 0, w//2, h)), (0, 0)) - imOut.paste(imIn.crop((w//2, 0, w, h)), (w//2, 0)) + imOut.paste(imIn.crop((0, 0, w // 2, h)), (0, 0)) + imOut.paste(imIn.crop((w // 2, 0, w, h)), (w // 2, 0)) self.verify(imIn) self.verify(imOut) @@ -83,11 +84,10 @@ class TestModeI16(PillowTestCase): basic("I") def test_tobytes(self): - def tobytes(mode): return Image.new(mode, (1, 1), 1).tobytes() - order = 1 if Image._ENDIAN == '<' else -1 + order = 1 if Image._ENDIAN == "<" else -1 self.assertEqual(tobytes("L"), b"\x01") self.assertEqual(tobytes("I;16"), b"\x01\x00") diff --git a/Tests/test_numpy.py b/Tests/test_numpy.py index bdb0ed874..d171e8c9b 100644 --- a/Tests/test_numpy.py +++ b/Tests/test_numpy.py @@ -15,7 +15,6 @@ TEST_IMAGE_SIZE = (10, 10) @unittest.skipIf(numpy is None, "Numpy is not installed") class TestNumpy(PillowTestCase): def test_numpy_to_image(self): - def to_image(dtype, bands=1, boolean=0): if bands == 1: if boolean: @@ -29,7 +28,7 @@ class TestNumpy(PillowTestCase): print("data mismatch for", dtype) else: data = list(range(100)) - a = numpy.array([[x]*bands for x in data], dtype=dtype) + a = numpy.array([[x] * bands for x in data], dtype=dtype) a.shape = TEST_IMAGE_SIZE[0], TEST_IMAGE_SIZE[1], bands i = Image.fromarray(a) if list(i.getchannel(0).getdata()) != list(range(100)): @@ -37,8 +36,8 @@ class TestNumpy(PillowTestCase): return i # Check supported 1-bit integer formats - self.assert_image(to_image(numpy.bool, 1, 1), '1', TEST_IMAGE_SIZE) - self.assert_image(to_image(numpy.bool8, 1, 1), '1', TEST_IMAGE_SIZE) + self.assert_image(to_image(numpy.bool, 1, 1), "1", TEST_IMAGE_SIZE) + self.assert_image(to_image(numpy.bool8, 1, 1), "1", TEST_IMAGE_SIZE) # Check supported 8-bit integer formats self.assert_image(to_image(numpy.uint8), "L", TEST_IMAGE_SIZE) @@ -53,7 +52,7 @@ class TestNumpy(PillowTestCase): # self.assert_image(to_image(numpy.int), "I", TEST_IMAGE_SIZE) # Check 16-bit integer formats - if Image._ENDIAN == '<': + if Image._ENDIAN == "<": self.assert_image(to_image(numpy.uint16), "I;16", TEST_IMAGE_SIZE) else: self.assert_image(to_image(numpy.uint16), "I;16B", TEST_IMAGE_SIZE) @@ -96,22 +95,22 @@ class TestNumpy(PillowTestCase): np_size = np.shape[1], np.shape[0] self.assertEqual(img.size, np_size) px = img.load() - for x in range(0, img.size[0], int(img.size[0]/10)): - for y in range(0, img.size[1], int(img.size[1]/10)): + for x in range(0, img.size[0], int(img.size[0] / 10)): + for y in range(0, img.size[1], int(img.size[1] / 10)): self.assert_deep_equal(px[x, y], np[y, x]) def test_16bit(self): - img = Image.open('Tests/images/16bit.cropped.tif') + img = Image.open("Tests/images/16bit.cropped.tif") np_img = numpy.array(img) self._test_img_equals_nparray(img, np_img) - self.assertEqual(np_img.dtype, numpy.dtype('u2'), - ("I;16L", 'u2"), + ("I;16L", "", 0), - (b"\x90\x1F\xA3", 8)) - self.assertEqual(PdfParser.get_value(b"asd < 9 0 1 f A > qwe", 3), - (b"\x90\x1F\xA0", 17)) + self.assertEqual(PdfParser.get_value(b"%cmt\n %cmt\n 123\n", 0), (123, 15)) + self.assertEqual(PdfParser.get_value(b"<901FA3>", 0), (b"\x90\x1F\xA3", 8)) + self.assertEqual( + PdfParser.get_value(b"asd < 9 0 1 f A > qwe", 3), (b"\x90\x1F\xA0", 17) + ) self.assertEqual(PdfParser.get_value(b"(asd)", 0), (b"asd", 5)) - self.assertEqual(PdfParser.get_value(b"(asd(qwe)zxc)zzz(aaa)", 0), - (b"asd(qwe)zxc", 13)) - self.assertEqual(PdfParser.get_value(b"(Two \\\nwords.)", 0), - (b"Two words.", 14)) - self.assertEqual(PdfParser.get_value(b"(Two\nlines.)", 0), - (b"Two\nlines.", 12)) - self.assertEqual(PdfParser.get_value(b"(Two\r\nlines.)", 0), - (b"Two\nlines.", 13)) - self.assertEqual(PdfParser.get_value(b"(Two\\nlines.)", 0), - (b"Two\nlines.", 13)) - self.assertEqual(PdfParser.get_value(b"(One\\(paren).", 0), - (b"One(paren", 12)) - self.assertEqual(PdfParser.get_value(b"(One\\)paren).", 0), - (b"One)paren", 12)) + self.assertEqual( + PdfParser.get_value(b"(asd(qwe)zxc)zzz(aaa)", 0), (b"asd(qwe)zxc", 13) + ) + self.assertEqual( + PdfParser.get_value(b"(Two \\\nwords.)", 0), (b"Two words.", 14) + ) + self.assertEqual(PdfParser.get_value(b"(Two\nlines.)", 0), (b"Two\nlines.", 12)) + self.assertEqual( + PdfParser.get_value(b"(Two\r\nlines.)", 0), (b"Two\nlines.", 13) + ) + self.assertEqual( + PdfParser.get_value(b"(Two\\nlines.)", 0), (b"Two\nlines.", 13) + ) + self.assertEqual(PdfParser.get_value(b"(One\\(paren).", 0), (b"One(paren", 12)) + self.assertEqual(PdfParser.get_value(b"(One\\)paren).", 0), (b"One)paren", 12)) self.assertEqual(PdfParser.get_value(b"(\\0053)", 0), (b"\x053", 7)) self.assertEqual(PdfParser.get_value(b"(\\053)", 0), (b"\x2B", 6)) self.assertEqual(PdfParser.get_value(b"(\\53)", 0), (b"\x2B", 5)) @@ -88,37 +97,43 @@ class TestPdfParser(PillowTestCase): b"D:2018072921": "20180729210000", b"D:20180729214124Z": "20180729214124", b"D:20180729214124+08'00'": "20180729134124", - b"D:20180729214124-05'00'": "20180730024124" + 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) + 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") - self.assertEqual(bytes(IndirectObjectDef(*IndirectReference(1, 2))), - b"1 2 obj") + self.assertEqual(bytes(IndirectObjectDef(*IndirectReference(1, 2))), b"1 2 obj") self.assertEqual(bytes(PdfName(b"Name#Hash")), b"/Name#23Hash") self.assertEqual(bytes(PdfName("Name#Hash")), b"/Name#23Hash") - self.assertEqual(bytes(PdfDict({b"Name": IndirectReference(1, 2)})), - b"<<\n/Name 1 2 R\n>>") - self.assertEqual(bytes(PdfDict({"Name": IndirectReference(1, 2)})), - b"<<\n/Name 1 2 R\n>>") + self.assertEqual( + bytes(PdfDict({b"Name": IndirectReference(1, 2)})), b"<<\n/Name 1 2 R\n>>" + ) + self.assertEqual( + bytes(PdfDict({"Name": IndirectReference(1, 2)})), b"<<\n/Name 1 2 R\n>>" + ) self.assertEqual(pdf_repr(IndirectReference(1, 2)), b"1 2 R") - self.assertEqual(pdf_repr(IndirectObjectDef(*IndirectReference(1, 2))), - b"1 2 obj") + self.assertEqual( + pdf_repr(IndirectObjectDef(*IndirectReference(1, 2))), b"1 2 obj" + ) self.assertEqual(pdf_repr(PdfName(b"Name#Hash")), b"/Name#23Hash") self.assertEqual(pdf_repr(PdfName("Name#Hash")), b"/Name#23Hash") - self.assertEqual(pdf_repr(PdfDict({b"Name": IndirectReference(1, 2)})), - b"<<\n/Name 1 2 R\n>>") - self.assertEqual(pdf_repr(PdfDict({"Name": IndirectReference(1, 2)})), - b"<<\n/Name 1 2 R\n>>") + self.assertEqual( + pdf_repr(PdfDict({b"Name": IndirectReference(1, 2)})), + b"<<\n/Name 1 2 R\n>>", + ) + self.assertEqual( + pdf_repr(PdfDict({"Name": IndirectReference(1, 2)})), b"<<\n/Name 1 2 R\n>>" + ) self.assertEqual(pdf_repr(123), b"123") self.assertEqual(pdf_repr(True), b"true") self.assertEqual(pdf_repr(False), b"false") self.assertEqual(pdf_repr(None), b"null") self.assertEqual(pdf_repr(b"a)/b\\(c"), br"(a\)/b\\\(c)") - self.assertEqual(pdf_repr([123, True, {"a": PdfName(b"b")}]), - b"[ 123 true <<\n/a /b\n>> ]") + 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>") diff --git a/Tests/test_pickle.py b/Tests/test_pickle.py index 45ef0f1db..710f2a1bd 100644 --- a/Tests/test_pickle.py +++ b/Tests/test_pickle.py @@ -4,25 +4,25 @@ from PIL import Image class TestPickle(PillowTestCase): - def helper_pickle_file(self, pickle, protocol=0, mode=None): # Arrange - im = Image.open('Tests/images/hopper.jpg') - filename = self.tempfile('temp.pkl') + im = Image.open("Tests/images/hopper.jpg") + filename = self.tempfile("temp.pkl") if mode: im = im.convert(mode) # Act - with open(filename, 'wb') as f: + with open(filename, "wb") as f: pickle.dump(im, f, protocol) - with open(filename, 'rb') as f: + with open(filename, "rb") as f: loaded_im = pickle.load(f) # Assert self.assertEqual(im, loaded_im) - def helper_pickle_string(self, pickle, protocol=0, - test_file='Tests/images/hopper.jpg', mode=None): + def helper_pickle_string( + self, pickle, protocol=0, test_file="Tests/images/hopper.jpg", mode=None + ): im = Image.open(test_file) if mode: im = im.convert(mode) @@ -61,19 +61,28 @@ class TestPickle(PillowTestCase): # Act / Assert for test_file in [ - "Tests/images/test-card.png", - "Tests/images/zero_bb.png", - "Tests/images/zero_bb_scale2.png", - "Tests/images/non_zero_bb.png", - "Tests/images/non_zero_bb_scale2.png", - "Tests/images/p_trns_single.png", - "Tests/images/pil123p.png", - "Tests/images/itxt_chunks.png" + "Tests/images/test-card.png", + "Tests/images/zero_bb.png", + "Tests/images/zero_bb_scale2.png", + "Tests/images/non_zero_bb.png", + "Tests/images/non_zero_bb_scale2.png", + "Tests/images/p_trns_single.png", + "Tests/images/pil123p.png", + "Tests/images/itxt_chunks.png", ]: for protocol in range(0, pickle.HIGHEST_PROTOCOL + 1): - self.helper_pickle_string(pickle, - protocol=protocol, - test_file=test_file) + self.helper_pickle_string( + pickle, protocol=protocol, test_file=test_file + ) + + def test_pickle_pa_mode(self): + # Arrange + import pickle + + # Act / Assert + for protocol in range(0, pickle.HIGHEST_PROTOCOL + 1): + self.helper_pickle_string(pickle, protocol, mode="PA") + self.helper_pickle_file(pickle, protocol, mode="PA") def test_pickle_l_mode(self): # Arrange @@ -84,6 +93,25 @@ class TestPickle(PillowTestCase): self.helper_pickle_string(pickle, protocol, mode="L") self.helper_pickle_file(pickle, protocol, mode="L") + def test_pickle_la_mode_with_palette(self): + # Arrange + import pickle + + im = Image.open("Tests/images/hopper.jpg") + filename = self.tempfile("temp.pkl") + im = im.convert("PA") + + # Act / Assert + for protocol in range(0, pickle.HIGHEST_PROTOCOL + 1): + im.mode = "LA" + with open(filename, "wb") as f: + pickle.dump(im, f, protocol) + with open(filename, "rb") as f: + loaded_im = pickle.load(f) + + im.mode = "PA" + self.assertEqual(im, loaded_im) + def test_cpickle_l_mode(self): # Arrange try: diff --git a/Tests/test_psdraw.py b/Tests/test_psdraw.py index 73ef5e2b2..319f9b789 100644 --- a/Tests/test_psdraw.py +++ b/Tests/test_psdraw.py @@ -6,17 +6,16 @@ import sys class TestPsDraw(PillowTestCase): - def _create_document(self, ps): im = Image.open("Tests/images/hopper.ppm") title = "hopper" - box = (1*72, 2*72, 7*72, 10*72) # in points + box = (1 * 72, 2 * 72, 7 * 72, 10 * 72) # in points ps.begin_document(title) # draw diagonal lines in a cross - ps.line((1*72, 2*72), (7*72, 10*72)) - ps.line((7*72, 2*72), (1*72, 10*72)) + ps.line((1 * 72, 2 * 72), (7 * 72, 10 * 72)) + ps.line((7 * 72, 2 * 72), (1 * 72, 10 * 72)) # draw the image (75 dpi) ps.image(box, im, 75) @@ -24,7 +23,7 @@ class TestPsDraw(PillowTestCase): # draw title ps.setfont("Courier", 36) - ps.text((3*72, 4*72), title) + ps.text((3 * 72, 4 * 72), title) ps.end_document() @@ -34,7 +33,7 @@ class TestPsDraw(PillowTestCase): # https://pillow.readthedocs.io/en/latest/handbook/tutorial.html#drawing-postscript # Arrange - tempfile = self.tempfile('temp.ps') + tempfile = self.tempfile("temp.ps") with open(tempfile, "wb") as fp: # Act ps = PSDraw.PSDraw(fp) diff --git a/Tests/test_pyroma.py b/Tests/test_pyroma.py index 7c48c365f..69cf25b45 100644 --- a/Tests/test_pyroma.py +++ b/Tests/test_pyroma.py @@ -26,5 +26,5 @@ class TestPyroma(PillowTestCase): ) else: - # Should have a near-perfect score - self.assertEqual(rating, (9, ["Your package does not have license data."])) + # Should have a perfect score + self.assertEqual(rating, (10, [])) diff --git a/Tests/test_qt_image_fromqpixmap.py b/Tests/test_qt_image_fromqpixmap.py index 894d7c8a5..2bd448c61 100644 --- a/Tests/test_qt_image_fromqpixmap.py +++ b/Tests/test_qt_image_fromqpixmap.py @@ -5,12 +5,11 @@ from PIL import ImageQt class TestFromQPixmap(PillowQPixmapTestCase, PillowTestCase): - def roundtrip(self, expected): result = ImageQt.fromqpixmap(ImageQt.toqpixmap(expected)) # Qt saves all pixmaps as rgb - self.assert_image_equal(result, expected.convert('RGB')) + self.assert_image_equal(result, expected.convert("RGB")) def test_sanity(self): - for mode in ('1', 'RGB', 'RGBA', 'L', 'P'): + for mode in ("1", "RGB", "RGBA", "L", "P"): self.roundtrip(hopper(mode)) diff --git a/Tests/test_qt_image_toqimage.py b/Tests/test_qt_image_toqimage.py index c1aa64b57..ec2f032a3 100644 --- a/Tests/test_qt_image_toqimage.py +++ b/Tests/test_qt_image_toqimage.py @@ -10,30 +10,30 @@ if ImageQt.qt_is_installed: try: from PyQt5 import QtGui from PyQt5.QtWidgets import QWidget, QHBoxLayout, QLabel, QApplication + QT_VERSION = 5 except (ImportError, RuntimeError): try: from PySide2 import QtGui - from PySide2.QtWidgets import QWidget, QHBoxLayout, QLabel, \ - QApplication + from PySide2.QtWidgets import QWidget, QHBoxLayout, QLabel, QApplication + QT_VERSION = 5 except (ImportError, RuntimeError): try: from PyQt4 import QtGui - from PyQt4.QtGui import QWidget, QHBoxLayout, QLabel, \ - QApplication + from PyQt4.QtGui import QWidget, QHBoxLayout, QLabel, QApplication + QT_VERSION = 4 except (ImportError, RuntimeError): from PySide import QtGui - from PySide.QtGui import QWidget, QHBoxLayout, QLabel, \ - QApplication + from PySide.QtGui import QWidget, QHBoxLayout, QLabel, QApplication + QT_VERSION = 4 class TestToQImage(PillowQtTestCase, PillowTestCase): - def test_sanity(self): - for mode in ('RGB', 'RGBA', 'L', 'P', '1'): + for mode in ("RGB", "RGBA", "L", "P", "1"): src = hopper(mode) data = ImageQt.toqimage(src) @@ -42,12 +42,12 @@ class TestToQImage(PillowQtTestCase, PillowTestCase): # reload directly from the qimage rt = ImageQt.fromqimage(data) - if mode in ('L', 'P', '1'): - self.assert_image_equal(rt, src.convert('RGB')) + if mode in ("L", "P", "1"): + self.assert_image_equal(rt, src.convert("RGB")) else: self.assert_image_equal(rt, src) - if mode == '1': + if mode == "1": # BW appears to not save correctly on QT4 and QT5 # kicks out errors on console: # libpng warning: Invalid color type/bit depth combination @@ -56,15 +56,15 @@ class TestToQImage(PillowQtTestCase, PillowTestCase): continue # Test saving the file - tempfile = self.tempfile('temp_{}.png'.format(mode)) + tempfile = self.tempfile("temp_{}.png".format(mode)) data.save(tempfile) # Check that it actually worked. reloaded = Image.open(tempfile) # Gray images appear to come back in palette mode. # They're roughly equivalent - if QT_VERSION == 4 and mode == 'L': - src = src.convert('P') + if QT_VERSION == 4 and mode == "L": + src = src.convert("P") self.assert_image_equal(reloaded, src) def test_segfault(self): @@ -75,8 +75,8 @@ class TestToQImage(PillowQtTestCase, PillowTestCase): if ImageQt.qt_is_installed: - class Example(QWidget): + class Example(QWidget): def __init__(self): super(Example, self).__init__() diff --git a/Tests/test_qt_image_toqpixmap.py b/Tests/test_qt_image_toqpixmap.py index 9bb7183b7..11dfab861 100644 --- a/Tests/test_qt_image_toqpixmap.py +++ b/Tests/test_qt_image_toqpixmap.py @@ -8,14 +8,13 @@ if ImageQt.qt_is_installed: class TestToQPixmap(PillowQPixmapTestCase, PillowTestCase): - def test_sanity(self): - for mode in ('1', 'RGB', 'RGBA', 'L', 'P'): + for mode in ("1", "RGB", "RGBA", "L", "P"): data = ImageQt.toqpixmap(hopper(mode)) self.assertIsInstance(data, QPixmap) self.assertFalse(data.isNull()) # Test saving the file - tempfile = self.tempfile('temp_{}.png'.format(mode)) + tempfile = self.tempfile("temp_{}.png".format(mode)) data.save(tempfile) diff --git a/Tests/test_shell_injection.py b/Tests/test_shell_injection.py index 77fd67f01..83c003fc9 100644 --- a/Tests/test_shell_injection.py +++ b/Tests/test_shell_injection.py @@ -9,18 +9,11 @@ from PIL import Image, JpegImagePlugin, GifImagePlugin TEST_JPG = "Tests/images/hopper.jpg" TEST_GIF = "Tests/images/hopper.gif" -test_filenames = ( - "temp_';", - "temp_\";", - "temp_'\"|", - "temp_'\"||", - "temp_'\"&&", -) +test_filenames = ("temp_';", 'temp_";', "temp_'\"|", "temp_'\"||", "temp_'\"&&") -@unittest.skipIf(sys.platform.startswith('win32'), "requires Unix or macOS") +@unittest.skipIf(sys.platform.startswith("win32"), "requires Unix or macOS") class TestShellInjection(PillowTestCase): - def assert_save_filename_check(self, src_img, save_func): for filename in test_filenames: dest_file = self.tempfile(filename) diff --git a/Tests/test_tiff_ifdrational.py b/Tests/test_tiff_ifdrational.py index fae4d7ed6..66da8b3f6 100644 --- a/Tests/test_tiff_ifdrational.py +++ b/Tests/test_tiff_ifdrational.py @@ -7,7 +7,6 @@ from fractions import Fraction class Test_IFDRational(PillowTestCase): - def _test_equal(self, num, denom, target): t = IFDRational(num, denom) @@ -44,17 +43,16 @@ class Test_IFDRational(PillowTestCase): def test_ifd_rational_save(self): methods = (True, False) - if 'libtiff_encoder' not in dir(Image.core): + if "libtiff_encoder" not in dir(Image.core): methods = (False,) for libtiff in methods: TiffImagePlugin.WRITE_LIBTIFF = libtiff im = hopper() - out = self.tempfile('temp.tiff') + out = self.tempfile("temp.tiff") res = IFDRational(301, 1) - im.save(out, dpi=(res, res), compression='raw') + im.save(out, dpi=(res, res), compression="raw") reloaded = Image.open(out) - self.assertEqual(float(IFDRational(301, 1)), - float(reloaded.tag_v2[282])) + self.assertEqual(float(IFDRational(301, 1)), float(reloaded.tag_v2[282])) diff --git a/Tests/test_uploader.py b/Tests/test_uploader.py index e40e7fb86..46dbd824a 100644 --- a/Tests/test_uploader.py +++ b/Tests/test_uploader.py @@ -3,11 +3,11 @@ from .helper import PillowTestCase, hopper class TestUploader(PillowTestCase): def check_upload_equal(self): - result = hopper('P').convert('RGB') - target = hopper('RGB') + result = hopper("P").convert("RGB") + target = hopper("RGB") self.assert_image_equal(result, target) def check_upload_similar(self): - result = hopper('P').convert('RGB') - target = hopper('RGB') + result = hopper("P").convert("RGB") + target = hopper("RGB") self.assert_image_similar(result, target, 0) diff --git a/Tests/test_util.py b/Tests/test_util.py index 08e9c1665..593922f8c 100644 --- a/Tests/test_util.py +++ b/Tests/test_util.py @@ -4,7 +4,6 @@ from PIL import _util class TestUtil(PillowTestCase): - def test_is_string_type(self): # Arrange color = "red" @@ -35,11 +34,12 @@ class TestUtil(PillowTestCase): # Assert self.assertTrue(it_is) - @unittest.skipIf(not _util.py36, 'os.path support for Paths added in 3.6') + @unittest.skipIf(not _util.py36, "os.path support for Paths added in 3.6") def test_path_obj_is_path(self): # Arrange from pathlib import Path - test_path = Path('filename.ext') + + test_path = Path("filename.ext") # Act it_is = _util.isPath(test_path) @@ -50,7 +50,7 @@ class TestUtil(PillowTestCase): def test_is_not_path(self): # Arrange filename = self.tempfile("temp.ext") - fp = open(filename, 'w').close() + fp = open(filename, "w").close() # Act it_is_not = _util.isPath(fp) diff --git a/Tests/test_webp_leaks.py b/Tests/test_webp_leaks.py index 03befd507..67586ba3a 100644 --- a/Tests/test_webp_leaks.py +++ b/Tests/test_webp_leaks.py @@ -5,14 +5,14 @@ from io import BytesIO test_file = "Tests/images/hopper.webp" -@unittest.skipUnless(features.check('webp'), "WebP is not installed") +@unittest.skipUnless(features.check("webp"), "WebP is not installed") class TestWebPLeaks(PillowLeakTestCase): mem_limit = 3 * 1024 # kb iterations = 100 def test_leak_load(self): - with open(test_file, 'rb') as f: + with open(test_file, "rb") as f: im_data = f.read() def core(): diff --git a/azure-pipelines.yml b/azure-pipelines.yml index 082c6918a..446b3f0b0 100644 --- a/azure-pipelines.yml +++ b/azure-pipelines.yml @@ -58,10 +58,10 @@ jobs: - template: .azure-pipelines/jobs/test-docker.yml parameters: - docker: 'fedora-28-amd64' - name: 'fedora_28_amd64' + docker: 'fedora-29-amd64' + name: 'fedora_29_amd64' - template: .azure-pipelines/jobs/test-docker.yml parameters: - docker: 'fedora-29-amd64' - name: 'fedora_29_amd64' + docker: 'fedora-30-amd64' + name: 'fedora_30_amd64' diff --git a/depends/install_imagequant.sh b/depends/install_imagequant.sh index e284bb0e4..1813bae09 100755 --- a/depends/install_imagequant.sh +++ b/depends/install_imagequant.sh @@ -1,7 +1,7 @@ #!/bin/bash # install libimagequant -archive=libimagequant-2.12.2 +archive=libimagequant-2.12.3 ./download-and-extract.sh $archive https://raw.githubusercontent.com/python-pillow/pillow-depends/master/$archive.tar.gz diff --git a/depends/install_openjpeg.sh b/depends/install_openjpeg.sh index ed7f0d6b5..a93498282 100755 --- a/depends/install_openjpeg.sh +++ b/depends/install_openjpeg.sh @@ -1,7 +1,7 @@ #!/bin/bash # install openjpeg -archive=openjpeg-2.3.0 +archive=openjpeg-2.3.1 ./download-and-extract.sh $archive https://raw.githubusercontent.com/python-pillow/pillow-depends/master/$archive.tar.gz diff --git a/depends/install_raqm.sh b/depends/install_raqm.sh index 655e841d9..38a5bfd52 100755 --- a/depends/install_raqm.sh +++ b/depends/install_raqm.sh @@ -2,7 +2,7 @@ # install raqm -archive=raqm-0.5.0 +archive=raqm-0.7.0 ./download-and-extract.sh $archive https://raw.githubusercontent.com/python-pillow/pillow-depends/master/$archive.tar.gz diff --git a/depends/install_raqm_cmake.sh b/depends/install_raqm_cmake.sh index 0c5ed8b69..c0dcd93b7 100755 --- a/depends/install_raqm_cmake.sh +++ b/depends/install_raqm_cmake.sh @@ -2,7 +2,7 @@ # install raqm -archive=raqm-cmake-b517ba80 +archive=raqm-cmake-99300ff3 ./download-and-extract.sh $archive https://raw.githubusercontent.com/python-pillow/pillow-depends/master/$archive.tar.gz diff --git a/docs/conf.py b/docs/conf.py index 2c25588a0..0eb137daa 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -29,27 +29,26 @@ import PIL # Add any Sphinx extension module names here, as strings. They can be # extensions coming with Sphinx (named 'sphinx.ext.*') or your custom # ones. -extensions = ['sphinx.ext.autodoc', 'sphinx.ext.viewcode', - 'sphinx.ext.intersphinx'] +extensions = ["sphinx.ext.autodoc", "sphinx.ext.viewcode", "sphinx.ext.intersphinx"] # Add any paths that contain templates here, relative to this directory. -templates_path = ['_templates'] +templates_path = ["_templates"] # The suffix(es) of source filenames. # You can specify multiple suffix as a list of string: # source_suffix = ['.rst', '.md'] -source_suffix = '.rst' +source_suffix = ".rst" # The encoding of source files. # source_encoding = 'utf-8-sig' # The master toctree document. -master_doc = 'index' +master_doc = "index" # General information about the project. -project = u'Pillow (PIL Fork)' -copyright = u'1995-2011 Fredrik Lundh, 2010-2019 Alex Clark and Contributors' -author = u'Fredrik Lundh, Alex Clark and Contributors' +project = u"Pillow (PIL Fork)" +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 # |version| and |release|, also used in various other places throughout the @@ -75,7 +74,7 @@ language = None # List of patterns, relative to source directory, that match files and # directories to ignore when looking for source files. -exclude_patterns = ['_build'] +exclude_patterns = ["_build"] # The reST default role (used for this markup: `text`) to use for all # documents. @@ -93,7 +92,7 @@ exclude_patterns = ['_build'] # show_authors = False # The name of the Pygments (syntax highlighting) style to use. -pygments_style = 'sphinx' +pygments_style = "sphinx" # A list of ignored prefixes for module index sorting. # modindex_common_prefix = [] @@ -140,7 +139,7 @@ html_theme_path = [sphinx_rtd_theme.get_html_theme_path()] # 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', 'resources'] +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 @@ -203,20 +202,17 @@ html_static_path = ['_static', 'resources'] # html_search_scorer = 'scorer.js' # Output file base name for HTML help builder. -htmlhelp_basename = 'PillowPILForkdoc' +htmlhelp_basename = "PillowPILForkdoc" # -- Options for LaTeX output --------------------------------------------- latex_elements = { # The paper size ('letterpaper' or 'a4paper'). # 'papersize': 'letterpaper', - # The font size ('10pt', '11pt' or '12pt'). # 'pointsize': '10pt', - # Additional stuff for the LaTeX preamble. # 'preamble': '', - # Latex figure (float) alignment # 'figure_align': 'htbp', } @@ -225,8 +221,13 @@ latex_elements = { # (source start file, target name, title, # author, documentclass [howto, manual, or own class]). latex_documents = [ - (master_doc, 'PillowPILFork.tex', u'Pillow (PIL Fork) Documentation', - u'Alex Clark', 'manual'), + ( + master_doc, + "PillowPILFork.tex", + u"Pillow (PIL Fork) Documentation", + u"Alex Clark", + "manual", + ) ] # The name of an image file (relative to this directory) to place at the top of @@ -255,8 +256,7 @@ latex_documents = [ # One entry per manual page. List of tuples # (source start file, name, description, authors, manual section). man_pages = [ - (master_doc, 'pillowpilfork', u'Pillow (PIL Fork) Documentation', - [author], 1) + (master_doc, "pillowpilfork", u"Pillow (PIL Fork) Documentation", [author], 1) ] # If true, show URL addresses after external links. @@ -269,10 +269,15 @@ man_pages = [ # (source start file, target name, title, author, # 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.', - 'Miscellaneous'), + ( + master_doc, + "PillowPILFork", + u"Pillow (PIL Fork) Documentation", + author, + "PillowPILFork", + "Pillow is the friendly PIL fork by Alex Clark and Contributors.", + "Miscellaneous", + ) ] # Documents to append as an appendix to all manuals. @@ -289,4 +294,4 @@ texinfo_documents = [ def setup(app): - app.add_javascript('js/script.js') + app.add_javascript("js/script.js") diff --git a/docs/deprecations.rst b/docs/deprecations.rst index ae29b4254..c21aea2b4 100644 --- a/docs/deprecations.rst +++ b/docs/deprecations.rst @@ -76,8 +76,8 @@ PILLOW_VERSION constant .. deprecated:: 5.2.0 -``PILLOW_VERSION`` has been deprecated and will be removed in the next -major release. Use ``__version__`` instead. +``PILLOW_VERSION`` has been deprecated and will be removed in 7.0.0. Use ``__version__`` +instead. ImageCms.CmsProfile attributes ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ diff --git a/docs/example/DdsImagePlugin.py b/docs/example/DdsImagePlugin.py index 631dc2d61..c171bb1f1 100644 --- a/docs/example/DdsImagePlugin.py +++ b/docs/example/DdsImagePlugin.py @@ -61,8 +61,7 @@ DDS_LUMINANCEA = DDPF_LUMINANCE | DDPF_ALPHAPIXELS DDS_ALPHA = DDPF_ALPHA DDS_PAL8 = DDPF_PALETTEINDEXED8 -DDS_HEADER_FLAGS_TEXTURE = (DDSD_CAPS | DDSD_HEIGHT | DDSD_WIDTH | - DDSD_PIXELFORMAT) +DDS_HEADER_FLAGS_TEXTURE = DDSD_CAPS | DDSD_HEIGHT | DDSD_WIDTH | DDSD_PIXELFORMAT DDS_HEADER_FLAGS_MIPMAP = DDSD_MIPMAPCOUNT DDS_HEADER_FLAGS_VOLUME = DDSD_DEPTH DDS_HEADER_FLAGS_PITCH = DDSD_PITCH @@ -94,9 +93,9 @@ DXT5_FOURCC = 0x35545844 def _decode565(bits): - a = ((bits >> 11) & 0x1f) << 3 - b = ((bits >> 5) & 0x3f) << 2 - c = (bits & 0x1f) << 3 + a = ((bits >> 11) & 0x1F) << 3 + b = ((bits >> 5) & 0x3F) << 2 + c = (bits & 0x1F) << 3 return a, b, c @@ -145,7 +144,7 @@ def _dxt1(data, width, height): r, g, b = 0, 0, 0 idx = 4 * ((y + j) * width + x + i) - ret[idx:idx+4] = struct.pack('4B', r, g, b, 255) + ret[idx : idx + 4] = struct.pack("4B", r, g, b, 255) return bytes(ret) @@ -167,7 +166,7 @@ def _dxtc_alpha(a0, a1, ac0, ac1, ai): elif ac == 6: alpha = 0 elif ac == 7: - alpha = 0xff + alpha = 0xFF else: alpha = ((6 - ac) * a0 + (ac - 1) * a1) // 5 @@ -180,8 +179,7 @@ def _dxt5(data, width, height): for y in range(0, height, 4): for x in range(0, width, 4): - a0, a1, ac0, ac1, c0, c1, code = struct.unpack("<2BHI2HI", - data.read(16)) + a0, a1, ac0, ac1, c0, c1, code = struct.unpack("<2BHI2HI", data.read(16)) r0, g0, b0 = _decode565(c0) r1, g1, b1 = _decode565(c1) @@ -202,7 +200,7 @@ def _dxt5(data, width, height): r, g, b = _c3(r0, r1), _c3(g0, g1), _c3(b0, b1) idx = 4 * ((y + j) * width + x + i) - ret[idx:idx+4] = struct.pack('4B', r, g, b, alpha) + ret[idx : idx + 4] = struct.pack("4B", r, g, b, alpha) return bytes(ret) @@ -230,8 +228,7 @@ class DdsImageFile(ImageFile.ImageFile): # pixel format pfsize, pfflags = struct.unpack("<2I", header.read(8)) fourcc = header.read(4) - bitcount, rmask, gmask, bmask, amask = struct.unpack("<5I", - header.read(20)) + bitcount, rmask, gmask, bmask, amask = struct.unpack("<5I", header.read(20)) if fourcc == b"DXT1": self.decoder = "DXT1" @@ -240,9 +237,7 @@ class DdsImageFile(ImageFile.ImageFile): else: raise NotImplementedError("Unimplemented pixel format %r" % fourcc) - self.tile = [ - (self.decoder, (0, 0) + self.size, 0, (self.mode, 0, 1)) - ] + self.tile = [(self.decoder, (0, 0) + self.size, 0, (self.mode, 0, 1))] def load_seek(self, pos): pass @@ -270,8 +265,8 @@ class DXT5Decoder(ImageFile.PyDecoder): return 0, 0 -Image.register_decoder('DXT1', DXT1Decoder) -Image.register_decoder('DXT5', DXT5Decoder) +Image.register_decoder("DXT1", DXT1Decoder) +Image.register_decoder("DXT5", DXT5Decoder) def _validate(prefix): diff --git a/docs/handbook/concepts.rst b/docs/handbook/concepts.rst index b2611c11b..055821a15 100644 --- a/docs/handbook/concepts.rst +++ b/docs/handbook/concepts.rst @@ -24,8 +24,10 @@ To get the number and names of bands in an image, use the Modes ----- -The ``mode`` of an image defines the type and depth of a pixel in the -image. The current release supports the following standard modes: +The ``mode`` of an image defines the type and depth of a pixel in the image. +Each pixel uses the full range of the bit depth. So a 1-bit pixel has a range +of 0-1, an 8-bit pixel has a range of 0-255 and so on. The current release +supports the following standard modes: * ``1`` (1-bit pixels, black and white, stored with one pixel per byte) * ``L`` (8-bit pixels, black and white) diff --git a/docs/handbook/overview.rst b/docs/handbook/overview.rst index b52939b89..17964d1c5 100644 --- a/docs/handbook/overview.rst +++ b/docs/handbook/overview.rst @@ -33,7 +33,7 @@ DIB interface ` that can be used with PythonWin and other Windows-based toolkits. Many other GUI toolkits come with some kind of PIL support. -For debugging, there’s also a :py:meth:`show` method which saves an image to +For debugging, there’s also a :py:meth:`~PIL.Image.Image.show` method which saves an image to disk, and calls an external display utility. Image Processing diff --git a/docs/handbook/tutorial.rst b/docs/handbook/tutorial.rst index b05972f93..a0868a89c 100644 --- a/docs/handbook/tutorial.rst +++ b/docs/handbook/tutorial.rst @@ -473,8 +473,8 @@ Reading from a string :: + from PIL import Image import StringIO - im = Image.open(StringIO.StringIO(buffer)) Note that the library rewinds the file (using ``seek(0)``) before reading the diff --git a/docs/handbook/writing-your-own-file-decoder.rst b/docs/handbook/writing-your-own-file-decoder.rst index 2e68656ca..0763109ab 100644 --- a/docs/handbook/writing-your-own-file-decoder.rst +++ b/docs/handbook/writing-your-own-file-decoder.rst @@ -201,7 +201,8 @@ table describes some commonly used **raw modes**: +-----------+-----------------------------------------------------------------+ | ``BGR`` | 24-bit true colour, stored as (blue, green, red). | +-----------+-----------------------------------------------------------------+ -| ``RGBX`` | 24-bit true colour, stored as (red, green, blue, pad). | +| ``RGBX`` | 24-bit true colour, stored as (red, green, blue, pad). The pad | +| | pixels may vary. | +-----------+-----------------------------------------------------------------+ | ``RGB;L`` | 24-bit true colour, line interleaved (first all red pixels, then| | | all green pixels, finally all blue pixels). | @@ -255,9 +256,10 @@ If the raw decoder cannot handle your format, PIL also provides a special “bit decoder that can be used to read various packed formats into a floating point image memory. -To use the bit decoder with the frombytes function, use the following syntax:: +To use the bit decoder with the :py:func:`PIL.Image.frombytes` function, use +the following syntax:: - image = frombytes( + image = Image.frombytes( mode, size, data, "bit", bits, pad, fill, sign, orientation ) diff --git a/docs/installation.rst b/docs/installation.rst index eb90d9db6..2b9ef72a0 100644 --- a/docs/installation.rst +++ b/docs/installation.rst @@ -163,17 +163,17 @@ Many of Pillow's features require external libraries: * **openjpeg** provides JPEG 2000 functionality. - * Pillow has been tested with openjpeg **2.0.0** and **2.1.0**. + * Pillow has been tested with openjpeg **2.0.0**, **2.1.0** and **2.3.1**. * Pillow does **not** support the earlier **1.5** series which ships with Debian Jessie. * **libimagequant** provides improved color quantization - * Pillow has been tested with libimagequant **2.6-2.12.2** + * Pillow has been tested with libimagequant **2.6-2.12.3** * 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, + * Windows support: Libimagequant requires VS2015/MSVC 19 to compile, so it is unlikely to work with Python 2.7 on Windows. * **libraqm** provides complex text layout support. @@ -382,23 +382,25 @@ These platforms are built and tested for every change. +----------------------------------+-------------------------------+-----------------------+ |**Operating system** |**Tested Python versions** |**Tested Architecture**| +----------------------------------+-------------------------------+-----------------------+ -| Alpine | 2.7 |x86-64 | +| Alpine | 2.7, 3.6 |x86-64 | +----------------------------------+-------------------------------+-----------------------+ -| Arch | 2.7 |x86-64 | +| Arch | 2.7, 3.7 |x86-64 | +----------------------------------+-------------------------------+-----------------------+ -| Amazon | 2.7 |x86-64 | +| Amazon Linux 1 | 2.7, 3.6 |x86-64 | +----------------------------------+-------------------------------+-----------------------+ -| Centos 6 | 2.7 |x86-64 | +| Amazon Linux 2 | 2.7, 3.6 |x86-64 | +----------------------------------+-------------------------------+-----------------------+ -| Centos 7 | 2.7 |x86-64 | +| CentOS 6 | 2.7, 3.6 |x86-64 | +----------------------------------+-------------------------------+-----------------------+ -| Debian Stretch | 2.7 |x86 | +| CentOS 7 | 2.7, 3.6 |x86-64 | +----------------------------------+-------------------------------+-----------------------+ -| Fedora 28 | 2.7 |x86-64 | +| Debian 9 Stretch | 2.7, 3.5 |x86 | +----------------------------------+-------------------------------+-----------------------+ -| Fedora 29 | 2.7 |x86-64 | +| Fedora 29 | 2.7, 3.7 |x86-64 | +----------------------------------+-------------------------------+-----------------------+ -| Mac OS X 10.10 Yosemite* | 2.7, 3.5, 3.6, 3.7 |x86-64 | +| Fedora 30 | 2.7, 3.7 |x86-64 | ++----------------------------------+-------------------------------+-----------------------+ +| macOS 10.13 High Sierra* | 2.7, 3.5, 3.6, 3.7 |x86-64 | +----------------------------------+-------------------------------+-----------------------+ | Ubuntu Linux 16.04 LTS | 2.7, 3.5, 3.6, 3.7, |x86-64 | | | PyPy, PyPy3 | | @@ -408,7 +410,7 @@ These platforms are built and tested for every change. | | PyPy, 3.7/MinGW |x86 | +----------------------------------+-------------------------------+-----------------------+ -\* Mac OS X CI is not run for every commit, but is run for every release. +\* macOS CI is not run for every commit, but is run for every release. Other Platforms ^^^^^^^^^^^^^^^ @@ -423,7 +425,9 @@ 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.14 Mojave | 2.7, 3.5, 3.6, 3.7 | 6.0.0 |x86-64 | +| +------------------------------+--------------------------------+ + +| | 3.4 | 5.4.1 | | +----------------------------------+------------------------------+--------------------------------+-----------------------+ | macOS 10.13 High Sierra | 2.7, 3.4, 3.5, 3.6 | 4.2.1 |x86-64 | +----------------------------------+------------------------------+--------------------------------+-----------------------+ diff --git a/docs/reference/Image.rst b/docs/reference/Image.rst index 388116a10..cfbcb8b6b 100644 --- a/docs/reference/Image.rst +++ b/docs/reference/Image.rst @@ -16,7 +16,7 @@ Open, rotate, and display an image (using the default viewer) ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ The following script loads an image, rotates it 45 degrees, and displays it -using an external viewer (usually xv on Unix, and the paint program on +using an external viewer (usually xv on Unix, and the Paint program on Windows). .. code-block:: python @@ -116,15 +116,66 @@ ITU-R 709, using the D65 luminant) to the CIE XYZ color space: rgb2xyz = ( 0.412453, 0.357580, 0.180423, 0, 0.212671, 0.715160, 0.072169, 0, - 0.019334, 0.119193, 0.950227, 0 ) + 0.019334, 0.119193, 0.950227, 0) out = im.convert("RGB", rgb2xyz) .. automethod:: PIL.Image.Image.copy .. automethod:: PIL.Image.Image.crop + +This crops the input image with the provided coordinates: + +.. code-block:: python + + from PIL import Image + + im = Image.open("hopper.jpg") + + # The crop method from the Image module takes four coordinates as input. + # The right can also be represented as (left+width) + # and lower can be represented as (upper+height). + (left, upper, right, lower) = (20, 20, 100, 100) + + # Here the image "im" is cropped and assigned to new variable im_crop + im_crop = im.crop((left, upper, right, lower)) + + .. automethod:: PIL.Image.Image.draft .. automethod:: PIL.Image.Image.filter + +This blurs the input image using a filter from the ``ImageFilter`` module: + +.. code-block:: python + + from PIL import Image, ImageFilter + + im = Image.open("hopper.jpg") + + # Blur the input image using the filter ImageFilter.BLUR + im_blurred = im.filter(filter=ImageFilter.BLUR) + .. automethod:: PIL.Image.Image.getbands + +This helps to get the bands of the input image: + +.. code-block:: python + + from PIL import Image + + im = Image.open("hopper.jpg") + print(im.getbands()) # Returns ('R', 'G', 'B') + .. automethod:: PIL.Image.Image.getbbox + +This helps to get the bounding box coordinates of the input image: + +.. code-block:: python + + from PIL import Image + + im = Image.open("hopper.jpg") + print(im.getbbox()) + # Returns four coordinates in the format (left, upper, right, lower) + .. automethod:: PIL.Image.Image.getcolors .. automethod:: PIL.Image.Image.getdata .. automethod:: PIL.Image.Image.getextrema @@ -140,8 +191,35 @@ ITU-R 709, using the D65 luminant) to the CIE XYZ color space: .. automethod:: PIL.Image.Image.putpixel .. automethod:: PIL.Image.Image.quantize .. automethod:: PIL.Image.Image.resize + +This resizes the given image from ``(width, height)`` to ``(width/2, height/2)``: + +.. code-block:: python + + from PIL import Image + + im = Image.open("hopper.jpg") + + # Provide the target width and height of the image + (width, height) = (im.width // 2, im.height // 2) + im_resized = im.resize((width, height)) + .. automethod:: PIL.Image.Image.remap_palette .. automethod:: PIL.Image.Image.rotate + +This rotates the input image by ``theta`` degrees counter clockwise: + +.. code-block:: python + + from PIL import Image + + im = Image.open("hopper.jpg") + + # Rotate the image by 60 degrees counter clockwise + theta = 60 + # Angle is in degrees counter clockwise + im_rotated = im.rotate(angle=theta) + .. automethod:: PIL.Image.Image.save .. automethod:: PIL.Image.Image.seek .. automethod:: PIL.Image.Image.show @@ -154,6 +232,21 @@ ITU-R 709, using the D65 luminant) to the CIE XYZ color space: .. automethod:: PIL.Image.Image.tostring .. automethod:: PIL.Image.Image.transform .. automethod:: PIL.Image.Image.transpose + +This flips the input image by using the ``Image.FLIP_LEFT_RIGHT`` method. + +.. code-block:: python + + from PIL import Image + + im = Image.open("hopper.jpg") + + # Flip the image from left to right + im_flipped = im.transpose(method=Image.FLIP_LEFT_RIGHT) + # To flip the image from top to bottom, + # use the method "Image.FLIP_TOP_BOTTOM" + + .. automethod:: PIL.Image.Image.verify .. automethod:: PIL.Image.Image.fromstring @@ -210,9 +303,9 @@ Instances of the :py:class:`Image` class have the following attributes: .. py:attribute:: palette - Colour palette table, if any. If mode is “P”, this should be an instance of - the :py:class:`~PIL.ImagePalette.ImagePalette` class. Otherwise, it should - be set to ``None``. + Colour palette table, if any. If mode is "P" or "PA", this should be an + instance of the :py:class:`~PIL.ImagePalette.ImagePalette` class. + Otherwise, it should be set to ``None``. :type: :py:class:`~PIL.ImagePalette.ImagePalette` or ``None`` diff --git a/docs/reference/ImageDraw.rst b/docs/reference/ImageDraw.rst index b50b770d0..cd44cd6e9 100644 --- a/docs/reference/ImageDraw.rst +++ b/docs/reference/ImageDraw.rst @@ -287,11 +287,11 @@ Methods .. versionadded:: 4.2.0 - :param language: Language of the text. Different languages may use + :param language: Language of the text. Different languages may use different glyph shapes or ligatures. This parameter tells the font which language the text is in, and to apply the correct substitutions as appropriate, if available. - It should be a `BCP47 language code + It should be a `BCP 47 language code ` Requires libraqm. @@ -326,11 +326,11 @@ Methods .. versionadded:: 4.2.0 - :param language: Language of the text. Different languages may use + :param language: Language of the text. Different languages may use different glyph shapes or ligatures. This parameter tells the font which language the text is in, and to apply the correct substitutions as appropriate, if available. - It should be a `BCP47 language code + It should be a `BCP 47 language code ` Requires libraqm. @@ -362,11 +362,11 @@ Methods Requires libraqm. .. versionadded:: 4.2.0 - :param language: Language of the text. Different languages may use + :param language: Language of the text. Different languages may use different glyph shapes or ligatures. This parameter tells the font which language the text is in, and to apply the correct substitutions as appropriate, if available. - It should be a `BCP47 language code + It should be a `BCP 47 language code ` Requires libraqm. @@ -398,11 +398,11 @@ Methods .. versionadded:: 4.2.0 - :param language: Language of the text. Different languages may use + :param language: Language of the text. Different languages may use different glyph shapes or ligatures. This parameter tells the font which language the text is in, and to apply the correct substitutions as appropriate, if available. - It should be a `BCP47 language code + It should be a `BCP 47 language code ` Requires libraqm. diff --git a/docs/reference/ImageFilter.rst b/docs/reference/ImageFilter.rst index 3368f799f..52a7d7500 100644 --- a/docs/reference/ImageFilter.rst +++ b/docs/reference/ImageFilter.rst @@ -38,12 +38,31 @@ image enhancement filters: * **SMOOTH_MORE** .. autoclass:: PIL.ImageFilter.Color3DLUT + :members: + .. autoclass:: PIL.ImageFilter.BoxBlur + :members: + .. autoclass:: PIL.ImageFilter.GaussianBlur + :members: + .. autoclass:: PIL.ImageFilter.UnsharpMask + :members: + .. autoclass:: PIL.ImageFilter.Kernel + :members: + .. autoclass:: PIL.ImageFilter.RankFilter + :members: + .. autoclass:: PIL.ImageFilter.MedianFilter + :members: + .. autoclass:: PIL.ImageFilter.MinFilter + :members: + .. autoclass:: PIL.ImageFilter.MaxFilter + :members: + .. autoclass:: PIL.ImageFilter.ModeFilter + :members: diff --git a/docs/reference/ImageFont.rst b/docs/reference/ImageFont.rst index b30bdac03..e1b7b6261 100644 --- a/docs/reference/ImageFont.rst +++ b/docs/reference/ImageFont.rst @@ -51,9 +51,9 @@ Methods Returns width and height (in pixels) of given text if rendered in font with provided direction, features, and language. - + :param text: Text to measure. - + :param direction: Direction of the text. It can be 'rtl' (right to left), 'ltr' (left to right) or 'ttb' (top to bottom). Requires libraqm. @@ -73,11 +73,11 @@ Methods .. versionadded:: 4.2.0 - :param language: Language of the text. Different languages may use + :param language: Language of the text. Different languages may use different glyph shapes or ligatures. This parameter tells the font which language the text is in, and to apply the correct substitutions as appropriate, if available. - It should be a `BCP47 language code + It should be a `BCP 47 language code ` Requires libraqm. @@ -119,11 +119,11 @@ Methods .. versionadded:: 4.2.0 - :param language: Language of the text. Different languages may use + :param language: Language of the text. Different languages may use different glyph shapes or ligatures. This parameter tells the font which language the text is in, and to apply the correct substitutions as appropriate, if available. - It should be a `BCP47 language code + It should be a `BCP 47 language code ` Requires libraqm. @@ -131,3 +131,6 @@ Methods :return: An internal PIL storage memory instance as defined by the :py:mod:`PIL.Image.core` interface module. + +.. autoclass:: PIL.ImageFont.FreeTypeFont + :members: diff --git a/docs/reference/ImageGrab.rst b/docs/reference/ImageGrab.rst index 39aaef6bc..ed7482e99 100644 --- a/docs/reference/ImageGrab.rst +++ b/docs/reference/ImageGrab.rst @@ -11,7 +11,7 @@ or the clipboard to a PIL image memory. .. versionadded:: 1.1.3 -.. py:function:: PIL.ImageGrab.grab(bbox=None) +.. py:function:: PIL.ImageGrab.grab(bbox=None, include_layered_windows=False) Take a snapshot of the screen. The pixels inside the bounding box are returned as an "RGB" image on Windows or "RGBA" on macOS. @@ -20,6 +20,7 @@ or the clipboard to a PIL image memory. .. versionadded:: 1.1.3 (Windows), 3.0.0 (macOS) :param bbox: What region to copy. Default is the entire screen. + :param include_layered_windows: Includes layered windows. Windows OS only. :return: An image .. py:function:: PIL.ImageGrab.grabclipboard() diff --git a/docs/reference/ImageMath.rst b/docs/reference/ImageMath.rst index 445a7e277..ca30244d1 100644 --- a/docs/reference/ImageMath.rst +++ b/docs/reference/ImageMath.rst @@ -5,8 +5,8 @@ ========================== The :py:mod:`ImageMath` module can be used to evaluate “image expressions”. The -module provides a single eval function, which takes an expression string and -one or more images. +module provides a single :py:meth:`~PIL.ImageMath.eval` function, which takes +an expression string and one or more images. Example: Using the :py:mod:`~PIL.ImageMath` module -------------------------------------------------- diff --git a/docs/reference/ImageSequence.rst b/docs/reference/ImageSequence.rst index f8ea9ee92..251ea3a93 100644 --- a/docs/reference/ImageSequence.rst +++ b/docs/reference/ImageSequence.rst @@ -25,3 +25,4 @@ The :py:class:`~PIL.ImageSequence.Iterator` class ------------------------------------------------- .. autoclass:: PIL.ImageSequence.Iterator + :members: diff --git a/docs/reference/ImageStat.rst b/docs/reference/ImageStat.rst index b8925bf8c..28a884d05 100644 --- a/docs/reference/ImageStat.rst +++ b/docs/reference/ImageStat.rst @@ -20,6 +20,13 @@ for a region of an image. Min/max values for each band in the image. + .. Note:: This relies on the :py:meth:`~PIL.Image.histogram` method, and simply + returns the low and high bins used. This is correct for images with 8 bits per + channel, but fails for other modes such as ``I`` or ``F``. Instead, use + :py:meth:`~PIL.Image.getextrema` to return per-band extrema for the image. + This is more correct and efficient because, for non-8-bit modes, the histogram + method uses :py:meth:`~PIL.Image.getextrema` to determine the bins used. + .. py:attribute:: count Total number of pixels for each band in the image. diff --git a/docs/reference/ImageWin.rst b/docs/reference/ImageWin.rst index 2696e7e99..ff3d6a7fc 100644 --- a/docs/reference/ImageWin.rst +++ b/docs/reference/ImageWin.rst @@ -24,6 +24,8 @@ Tkinter makes the window handle available via the winfo_id method: .. autoclass:: PIL.ImageWin.Dib :members: - .. autoclass:: PIL.ImageWin.HDC + :members: + .. autoclass:: PIL.ImageWin.HWND + :members: diff --git a/docs/releasenotes/6.1.0.rst b/docs/releasenotes/6.1.0.rst new file mode 100644 index 000000000..98568fb52 --- /dev/null +++ b/docs/releasenotes/6.1.0.rst @@ -0,0 +1,39 @@ +6.1.0 +----- + +API Additions +============= + +ImageGrab.grab +^^^^^^^^^^^^^^ + +An optional ``include_layered_windows`` parameter has been added to ``ImageGrab.grab``, +defaulting to ``False``. If true, layered windows will be included in the resulting +image on Windows. + +Variation fonts +^^^^^^^^^^^^^^^ + +Variation fonts are now supported, allowing for different styles from the same font +file. ``ImageFont.FreeTypeFont`` has four new methods, +:py:meth:`PIL.ImageFont.FreeTypeFont.get_variation_names` and +:py:meth:`PIL.ImageFont.FreeTypeFont.set_variation_by_name` for using named styles, and +:py:meth:`PIL.ImageFont.FreeTypeFont.get_variation_axes` and +:py:meth:`PIL.ImageFont.FreeTypeFont.set_variation_by_axes` for using font axes +instead. An ``IOError`` will be raised if the font is not a variation font. FreeType +2.9.1 or greater is required. + +Other Changes +============= + +ImageTk.getimage +^^^^^^^^^^^^^^^^ + +This function is now supported. It returns the contents of an ``ImageTk.PhotoImage`` as +an RGBA ``Image.Image`` instance. + +Top To Bottom Complex Text Rendering +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +Drawing text in the 'ttb' direction with ImageFont has been significantly improved +and requires Raqm 0.7 or greater. diff --git a/docs/releasenotes/index.rst b/docs/releasenotes/index.rst index 9a088375b..400288883 100644 --- a/docs/releasenotes/index.rst +++ b/docs/releasenotes/index.rst @@ -6,6 +6,7 @@ Release Notes .. toctree:: :maxdepth: 2 + 6.1.0 6.0.0 5.4.1 5.4.0 diff --git a/mp_compile.py b/mp_compile.py index f50210ba5..04bfc3c9c 100644 --- a/mp_compile.py +++ b/mp_compile.py @@ -10,7 +10,7 @@ import os import sys try: - MAX_PROCS = int(os.environ.get('MAX_CONCURRENCY', min(4, cpu_count()))) + MAX_PROCS = int(os.environ.get("MAX_CONCURRENCY", min(4, cpu_count()))) except NotImplementedError: MAX_PROCS = None @@ -26,9 +26,17 @@ def _mp_compile_one(tp): return -def _mp_compile(self, sources, output_dir=None, macros=None, - include_dirs=None, debug=0, extra_preargs=None, - extra_postargs=None, depends=None): +def _mp_compile( + self, + sources, + output_dir=None, + macros=None, + include_dirs=None, + debug=0, + extra_preargs=None, + extra_postargs=None, + depends=None, +): """Compile one or more source files. see distutils.ccompiler.CCompiler.compile for comments. @@ -37,7 +45,8 @@ def _mp_compile(self, sources, output_dir=None, macros=None, # entirely or implement _compile(). macros, objects, extra_postargs, pp_opts, build = self._setup_compile( - output_dir, macros, include_dirs, sources, depends, extra_postargs) + output_dir, macros, include_dirs, sources, depends, extra_postargs + ) cc_args = self._get_cc_args(pp_opts, debug, extra_preargs) pool = Pool(MAX_PROCS) @@ -45,8 +54,7 @@ def _mp_compile(self, sources, output_dir=None, macros=None, print("Building using %d processes" % pool._processes) except Exception: pass - arr = [(self, obj, build, cc_args, extra_postargs, pp_opts) - for obj in objects] + arr = [(self, obj, build, cc_args, extra_postargs, pp_opts) for obj in objects] pool.map_async(_mp_compile_one, arr) pool.close() pool.join() @@ -56,8 +64,8 @@ def _mp_compile(self, sources, output_dir=None, macros=None, def install(): - fl_win = sys.platform.startswith('win') - fl_cygwin = sys.platform.startswith('cygwin') + fl_win = sys.platform.startswith("win") + fl_cygwin = sys.platform.startswith("cygwin") if fl_win or fl_cygwin: # Windows barfs on multiprocessing installs @@ -72,11 +80,11 @@ def install(): Pool(2) CCompiler.compile = _mp_compile except Exception as msg: - print("Exception installing mp_compile, proceeding without:" - "%s" % msg) + print("Exception installing mp_compile, proceeding without: %s" % msg) else: - print("Single threaded build, not installing mp_compile:" - "%s processes" % MAX_PROCS) + print( + "Single threaded build, not installing mp_compile: %s processes" % MAX_PROCS + ) # We monkeypatch Python 2.7 diff --git a/requirements.txt b/requirements.txt index cd3de4e1f..6971bfdc0 100644 --- a/requirements.txt +++ b/requirements.txt @@ -2,6 +2,7 @@ -e . alabaster Babel +black; python_version >= '3.6' check-manifest cov-core coverage diff --git a/selftest.py b/selftest.py index f4383b120..4dea3363c 100755 --- a/selftest.py +++ b/selftest.py @@ -90,6 +90,8 @@ def testimage(): 2 >>> len(im.histogram()) 768 + >>> '%.7f' % im.entropy() + '8.8212866' >>> _info(im.point(list(range(256))*3)) (None, 'RGB', (128, 128)) >>> _info(im.resize((64, 64))) @@ -161,12 +163,12 @@ if __name__ == "__main__": exit_status = 0 - print("-"*68) + print("-" * 68) print("Pillow", Image.__version__, "TEST SUMMARY ") - print("-"*68) + print("-" * 68) print("Python modules loaded from", os.path.dirname(Image.__file__)) print("Binary modules loaded from", os.path.dirname(Image.core.__file__)) - print("-"*68) + print("-" * 68) for name, feature in [ ("pil", "PIL CORE"), ("tkinter", "TKINTER"), @@ -180,16 +182,17 @@ if __name__ == "__main__": ("jpg_2000", "OPENJPEG (JPEG2000)"), ("zlib", "ZLIB (PNG/ZIP)"), ("libtiff", "LIBTIFF"), - ("raqm", "RAQM (Bidirectional Text)") + ("raqm", "RAQM (Bidirectional Text)"), ]: if features.check(name): print("---", feature, "support ok") else: print("***", feature, "support not installed") - print("-"*68) + print("-" * 68) # use doctest to make sure the test program behaves as documented! import doctest + print("Running selftest:") status = doctest.testmod(sys.modules[__name__]) if status[0]: diff --git a/setup.cfg b/setup.cfg index bcaf82f27..3ab2e127f 100644 --- a/setup.cfg +++ b/setup.cfg @@ -1,8 +1,6 @@ [aliases] test=pytest -[metadata] -license_file = LICENSE - [flake8] +extend-ignore = E203, W503 max-line-length = 88 diff --git a/setup.py b/setup.py index 8eb0b3c6b..2c25543a9 100755 --- a/setup.py +++ b/setup.py @@ -29,26 +29,81 @@ if sys.platform == "win32" and sys.version_info >= (3, 8): warnings.warn( "Pillow does not yet support Python {}.{} and does not yet provide " "prebuilt Windows binaries. We do not recommend building from " - "source on Windows.".format(sys.version_info.major, - sys.version_info.minor), - RuntimeWarning) + "source on Windows.".format(sys.version_info.major, sys.version_info.minor), + RuntimeWarning, + ) _IMAGING = ("decode", "encode", "map", "display", "outline", "path") _LIB_IMAGING = ( - "Access", "AlphaComposite", "Resample", "Bands", "BcnDecode", "BitDecode", - "Blend", "Chops", "ColorLUT", "Convert", "ConvertYCbCr", "Copy", "Crop", - "Dib", "Draw", "Effects", "EpsEncode", "File", "Fill", "Filter", - "FliDecode", "Geometry", "GetBBox", "GifDecode", "GifEncode", "HexDecode", - "Histo", "JpegDecode", "JpegEncode", "Matrix", "ModeFilter", - "Negative", "Offset", "Pack", "PackDecode", "Palette", "Paste", "Quant", - "QuantOctree", "QuantHash", "QuantHeap", "PcdDecode", "PcxDecode", - "PcxEncode", "Point", "RankFilter", "RawDecode", "RawEncode", "Storage", - "SgiRleDecode", "SunRleDecode", "TgaRleDecode", "TgaRleEncode", "Unpack", - "UnpackYCC", "UnsharpMask", "XbmDecode", "XbmEncode", "ZipDecode", - "ZipEncode", "TiffDecode", "Jpeg2KDecode", "Jpeg2KEncode", "BoxBlur", - "QuantPngQuant", "codec_fd") + "Access", + "AlphaComposite", + "Resample", + "Bands", + "BcnDecode", + "BitDecode", + "Blend", + "Chops", + "ColorLUT", + "Convert", + "ConvertYCbCr", + "Copy", + "Crop", + "Dib", + "Draw", + "Effects", + "EpsEncode", + "File", + "Fill", + "Filter", + "FliDecode", + "Geometry", + "GetBBox", + "GifDecode", + "GifEncode", + "HexDecode", + "Histo", + "JpegDecode", + "JpegEncode", + "Matrix", + "ModeFilter", + "Negative", + "Offset", + "Pack", + "PackDecode", + "Palette", + "Paste", + "Quant", + "QuantOctree", + "QuantHash", + "QuantHeap", + "PcdDecode", + "PcxDecode", + "PcxEncode", + "Point", + "RankFilter", + "RawDecode", + "RawEncode", + "Storage", + "SgiRleDecode", + "SunRleDecode", + "TgaRleDecode", + "TgaRleEncode", + "Unpack", + "UnpackYCC", + "UnsharpMask", + "XbmDecode", + "XbmEncode", + "ZipDecode", + "ZipEncode", + "TiffDecode", + "Jpeg2KDecode", + "Jpeg2KEncode", + "BoxBlur", + "QuantPngQuant", + "codec_fd", +) DEBUG = False @@ -61,8 +116,8 @@ class RequiredDependencyException(Exception): pass -PLATFORM_MINGW = 'mingw' in ccompiler.get_default_compiler() -PLATFORM_PYPY = hasattr(sys, 'pypy_version_info') +PLATFORM_MINGW = "mingw" in ccompiler.get_default_compiler() +PLATFORM_PYPY = hasattr(sys, "pypy_version_info") def _dbg(s, tp=None): @@ -77,39 +132,36 @@ def _find_library_dirs_ldconfig(): # Based on ctypes.util from Python 2 if sys.platform.startswith("linux") or sys.platform.startswith("gnu"): - if struct.calcsize('l') == 4: - machine = os.uname()[4] + '-32' + if struct.calcsize("l") == 4: + machine = os.uname()[4] + "-32" else: - machine = os.uname()[4] + '-64' + machine = os.uname()[4] + "-64" mach_map = { - 'x86_64-64': 'libc6,x86-64', - 'ppc64-64': 'libc6,64bit', - 'sparc64-64': 'libc6,64bit', - 's390x-64': 'libc6,64bit', - 'ia64-64': 'libc6,IA-64', - } - abi_type = mach_map.get(machine, 'libc6') + "x86_64-64": "libc6,x86-64", + "ppc64-64": "libc6,64bit", + "sparc64-64": "libc6,64bit", + "s390x-64": "libc6,64bit", + "ia64-64": "libc6,IA-64", + } + abi_type = mach_map.get(machine, "libc6") # Assuming GLIBC's ldconfig (with option -p) # Alpine Linux uses musl that can't print cache - args = ['/sbin/ldconfig', '-p'] - expr = r'.*\(%s.*\) => (.*)' % abi_type + args = ["/sbin/ldconfig", "-p"] + expr = r".*\(%s.*\) => (.*)" % abi_type env = dict(os.environ) - env['LC_ALL'] = 'C' - env['LANG'] = 'C' + env["LC_ALL"] = "C" + env["LANG"] = "C" elif sys.platform.startswith("freebsd"): - args = ['/sbin/ldconfig', '-r'] - expr = r'.* => (.*)' + args = ["/sbin/ldconfig", "-r"] + expr = r".* => (.*)" env = {} - null = open(os.devnull, 'wb') + null = open(os.devnull, "wb") try: with null: - p = subprocess.Popen(args, - stderr=null, - stdout=subprocess.PIPE, - env=env) + p = subprocess.Popen(args, stderr=null, stdout=subprocess.PIPE, env=env) except OSError: # E.g. command not found return [] [data, _] = p.communicate() @@ -130,10 +182,10 @@ def _add_directory(path, subdir, where=None): subdir = os.path.realpath(subdir) if os.path.isdir(subdir) and subdir not in path: if where is None: - _dbg('Appending path %s', subdir) + _dbg("Appending path %s", subdir) path.append(subdir) else: - _dbg('Inserting path %s', subdir) + _dbg("Inserting path %s", subdir) path.insert(where, subdir) elif subdir in path and where is not None: path.remove(subdir) @@ -142,9 +194,9 @@ def _add_directory(path, subdir, where=None): def _find_include_file(self, include): for directory in self.compiler.include_dirs: - _dbg('Checking for include file %s in %s', (include, directory)) + _dbg("Checking for include file %s in %s", (include, directory)) if os.path.isfile(os.path.join(directory, include)): - _dbg('Found %s', include) + _dbg("Found %s", include) return 1 return 0 @@ -152,10 +204,9 @@ def _find_include_file(self, include): def _find_library_file(self, library): ret = self.compiler.find_library_file(self.compiler.library_dirs, library) if ret: - _dbg('Found library %s at %s', (library, ret)) + _dbg("Found library %s at %s", (library, ret)) else: - _dbg("Couldn't find library %s in %s", - (library, self.compiler.library_dirs)) + _dbg("Couldn't find library %s in %s", (library, self.compiler.library_dirs)) return ret @@ -167,18 +218,18 @@ def _cmd_exists(cmd): def _read(file): - with open(file, 'rb') as fp: + with open(file, "rb") as fp: return fp.read() def get_version(): - version_file = 'src/PIL/_version.py' - with open(version_file, 'r') as f: - exec(compile(f.read(), version_file, 'exec')) - return locals()['__version__'] + version_file = "src/PIL/_version.py" + with open(version_file, "r") as f: + exec(compile(f.read(), version_file, "exec")) + return locals()["__version__"] -NAME = 'Pillow' +NAME = "Pillow" PILLOW_VERSION = get_version() JPEG_ROOT = None JPEG2K_ROOT = None @@ -191,17 +242,11 @@ LCMS_ROOT = None def _pkg_config(name): try: - command_libs = [ - 'pkg-config', - '--libs-only-L', name, - ] - command_cflags = [ - 'pkg-config', - '--cflags-only-I', name, - ] + command_libs = ["pkg-config", "--libs-only-L", name] + command_cflags = ["pkg-config", "--cflags-only-I", name] if not DEBUG: - command_libs.append('--silence-errors') - command_cflags.append('--silence-errors') + command_libs.append("--silence-errors") + command_cflags.append("--silence-errors") libs = ( subprocess.check_output(command_libs) .decode("utf8") @@ -221,10 +266,19 @@ def _pkg_config(name): class pil_build_ext(build_ext): class feature: - features = ['zlib', 'jpeg', 'tiff', 'freetype', 'lcms', 'webp', - 'webpmux', 'jpeg2000', 'imagequant'] + features = [ + "zlib", + "jpeg", + "tiff", + "freetype", + "lcms", + "webp", + "webpmux", + "jpeg2000", + "imagequant", + ] - required = {'jpeg', 'zlib'} + required = {"jpeg", "zlib"} def __init__(self): for f in self.features: @@ -242,23 +296,24 @@ class pil_build_ext(build_ext): feature = feature() - user_options = build_ext.user_options + [ - ('disable-%s' % x, None, 'Disable support for %s' % x) for x in feature - ] + [ - ('enable-%s' % x, None, 'Enable support for %s' % x) for x in feature - ] + [ - ('disable-platform-guessing', None, - 'Disable platform guessing on Linux'), - ('debug', None, 'Debug logging') - ] + [('add-imaging-libs=', None, 'Add libs to _imaging build')] + user_options = ( + build_ext.user_options + + [("disable-%s" % x, None, "Disable support for %s" % x) for x in feature] + + [("enable-%s" % x, None, "Enable support for %s" % x) for x in feature] + + [ + ("disable-platform-guessing", None, "Disable platform guessing on Linux"), + ("debug", None, "Debug logging"), + ] + + [("add-imaging-libs=", None, "Add libs to _imaging build")] + ) def initialize_options(self): self.disable_platform_guessing = None self.add_imaging_libs = "" build_ext.initialize_options(self) for x in self.feature: - setattr(self, 'disable_%s' % x, None) - setattr(self, 'enable_%s' % x, None) + setattr(self, "disable_%s" % x, None) + setattr(self, "enable_%s" % x, None) def finalize_options(self): build_ext.finalize_options(self) @@ -272,16 +327,16 @@ class pil_build_ext(build_ext): # number of jobs. self.parallel = mp_compile.MAX_PROCS for x in self.feature: - if getattr(self, 'disable_%s' % x): + if getattr(self, "disable_%s" % x): setattr(self.feature, x, False) self.feature.required.discard(x) - _dbg('Disabling %s', x) - if getattr(self, 'enable_%s' % x): + _dbg("Disabling %s", x) + if getattr(self, "enable_%s" % x): raise ValueError( - 'Conflicting options: --enable-%s and --disable-%s' % - (x, x)) - if getattr(self, 'enable_%s' % x): - _dbg('Requiring %s', x) + "Conflicting options: --enable-%s and --disable-%s" % (x, x) + ) + if getattr(self, "enable_%s" % x): + _dbg("Requiring %s", x) self.feature.required.add(x) def build_extensions(self): @@ -292,34 +347,35 @@ class pil_build_ext(build_ext): _add_directory(include_dirs, "src/libImaging") pkg_config = None - if _cmd_exists('pkg-config'): + if _cmd_exists("pkg-config"): pkg_config = _pkg_config # # add configured kits - for root_name, lib_name in dict(JPEG_ROOT="libjpeg", - JPEG2K_ROOT="libopenjp2", - TIFF_ROOT=("libtiff-5", "libtiff-4"), - ZLIB_ROOT="zlib", - FREETYPE_ROOT="freetype2", - LCMS_ROOT="lcms2", - IMAGEQUANT_ROOT="libimagequant" - ).items(): + for root_name, lib_name in dict( + JPEG_ROOT="libjpeg", + JPEG2K_ROOT="libopenjp2", + TIFF_ROOT=("libtiff-5", "libtiff-4"), + ZLIB_ROOT="zlib", + FREETYPE_ROOT="freetype2", + LCMS_ROOT="lcms2", + IMAGEQUANT_ROOT="libimagequant", + ).items(): root = globals()[root_name] if root is None and root_name in os.environ: prefix = os.environ[root_name] - root = (os.path.join(prefix, 'lib'), os.path.join(prefix, 'include')) + root = (os.path.join(prefix, "lib"), os.path.join(prefix, "include")) if root is None and pkg_config: if isinstance(lib_name, tuple): for lib_name2 in lib_name: - _dbg('Looking for `%s` using pkg-config.' % lib_name2) + _dbg("Looking for `%s` using pkg-config." % lib_name2) root = pkg_config(lib_name2) if root: break else: - _dbg('Looking for `%s` using pkg-config.' % lib_name) + _dbg("Looking for `%s` using pkg-config." % lib_name) root = pkg_config(lib_name) if isinstance(root, tuple): @@ -330,21 +386,21 @@ class pil_build_ext(build_ext): _add_directory(library_dirs, lib_root) _add_directory(include_dirs, include_root) - # respect CFLAGS/LDFLAGS - for k in ('CFLAGS', 'LDFLAGS'): + # respect CFLAGS/CPPFLAGS/LDFLAGS + for k in ("CFLAGS", "CPPFLAGS", "LDFLAGS"): if k in os.environ: - for match in re.finditer(r'-I([^\s]+)', os.environ[k]): + for match in re.finditer(r"-I([^\s]+)", os.environ[k]): _add_directory(include_dirs, match.group(1)) - for match in re.finditer(r'-L([^\s]+)', os.environ[k]): + for match in re.finditer(r"-L([^\s]+)", os.environ[k]): _add_directory(library_dirs, match.group(1)) # include, rpath, if set as environment variables: - for k in ('C_INCLUDE_PATH', 'CPATH', 'INCLUDE'): + for k in ("C_INCLUDE_PATH", "CPATH", "INCLUDE"): if k in os.environ: for d in os.environ[k].split(os.path.pathsep): _add_directory(include_dirs, d) - for k in ('LD_RUN_PATH', 'LIBRARY_PATH', 'LIB'): + for k in ("LD_RUN_PATH", "LIBRARY_PATH", "LIB"): if k in os.environ: for d in os.environ[k].split(os.path.pathsep): _add_directory(library_dirs, d) @@ -362,9 +418,10 @@ class pil_build_ext(build_ext): elif sys.platform == "cygwin": # pythonX.Y.dll.a is in the /usr/lib/pythonX.Y/config directory - _add_directory(library_dirs, - os.path.join("/usr/lib", "python%s" % - sys.version[:3], "config")) + _add_directory( + library_dirs, + os.path.join("/usr/lib", "python%s" % sys.version[:3], "config"), + ) elif sys.platform == "darwin": # attempt to make sure we pick freetype2 over other versions @@ -379,8 +436,11 @@ class pil_build_ext(build_ext): # if Homebrew is installed, use its lib and include directories try: - prefix = subprocess.check_output(['brew', '--prefix']).strip( - ).decode('latin1') + prefix = ( + subprocess.check_output(["brew", "--prefix"]) + .strip() + .decode("latin1") + ) except Exception: # Homebrew not installed prefix = None @@ -389,28 +449,30 @@ class pil_build_ext(build_ext): if prefix: # add Homebrew's include and lib directories - _add_directory(library_dirs, os.path.join(prefix, 'lib')) - _add_directory(include_dirs, os.path.join(prefix, 'include')) - ft_prefix = os.path.join(prefix, 'opt', 'freetype') + _add_directory(library_dirs, os.path.join(prefix, "lib")) + _add_directory(include_dirs, os.path.join(prefix, "include")) + ft_prefix = os.path.join(prefix, "opt", "freetype") if ft_prefix and os.path.isdir(ft_prefix): # freetype might not be linked into Homebrew's prefix - _add_directory(library_dirs, os.path.join(ft_prefix, 'lib')) - _add_directory(include_dirs, - os.path.join(ft_prefix, 'include')) + _add_directory(library_dirs, os.path.join(ft_prefix, "lib")) + _add_directory(include_dirs, os.path.join(ft_prefix, "include")) else: # fall back to freetype from XQuartz if # Homebrew's freetype is missing _add_directory(library_dirs, "/usr/X11/lib") _add_directory(include_dirs, "/usr/X11/include") - elif sys.platform.startswith("linux") or \ - sys.platform.startswith("gnu") or \ - sys.platform.startswith("freebsd"): + elif ( + sys.platform.startswith("linux") + or sys.platform.startswith("gnu") + or sys.platform.startswith("freebsd") + ): for dirname in _find_library_dirs_ldconfig(): _add_directory(library_dirs, dirname) - if sys.platform.startswith("linux") and \ - os.environ.get('ANDROID_ROOT', None): + if sys.platform.startswith("linux") and os.environ.get( + "ANDROID_ROOT", None + ): # termux support for android. # system libraries (zlib) are installed in /system/lib # headers are at $PREFIX/include @@ -439,25 +501,28 @@ class pil_build_ext(build_ext): # alpine, at least _add_directory(library_dirs, "/lib") - # on Windows, look for the OpenJPEG libraries in the location that - # the official installer puts them if sys.platform == "win32": - program_files = os.environ.get('ProgramFiles', '') + if PLATFORM_MINGW: + _add_directory( + include_dirs, "C:\\msys64\\mingw32\\include\\libimagequant" + ) + + # on Windows, look for the OpenJPEG libraries in the location that + # the official installer puts them + program_files = os.environ.get("ProgramFiles", "") best_version = (0, 0) best_path = None for name in os.listdir(program_files): - if name.startswith('OpenJPEG '): - version = tuple(int(x) for x in - name[9:].strip().split('.')) + if name.startswith("OpenJPEG "): + version = tuple(int(x) for x in name[9:].strip().split(".")) if version > best_version: best_version = version best_path = os.path.join(program_files, name) if best_path: - _dbg('Adding %s to search list', best_path) - _add_directory(library_dirs, os.path.join(best_path, 'lib')) - _add_directory(include_dirs, - os.path.join(best_path, 'include')) + _dbg("Adding %s to search list", best_path) + _add_directory(library_dirs, os.path.join(best_path, "lib")) + _add_directory(include_dirs, os.path.join(best_path, "include")) # # insert new dirs *before* default libs, to avoid conflicts @@ -471,51 +536,51 @@ class pil_build_ext(build_ext): feature = self.feature - if feature.want('zlib'): - _dbg('Looking for zlib') + if feature.want("zlib"): + _dbg("Looking for zlib") if _find_include_file(self, "zlib.h"): if _find_library_file(self, "z"): feature.zlib = "z" - elif (sys.platform == "win32" and - _find_library_file(self, "zlib")): + elif sys.platform == "win32" and _find_library_file(self, "zlib"): feature.zlib = "zlib" # alternative name - if feature.want('jpeg'): - _dbg('Looking for jpeg') + if feature.want("jpeg"): + _dbg("Looking for jpeg") if _find_include_file(self, "jpeglib.h"): if _find_library_file(self, "jpeg"): feature.jpeg = "jpeg" - elif (sys.platform == "win32" and - _find_library_file(self, "libjpeg")): + elif sys.platform == "win32" and _find_library_file(self, "libjpeg"): feature.jpeg = "libjpeg" # alternative name feature.openjpeg_version = None - if feature.want('jpeg2000'): - _dbg('Looking for jpeg2000') + if feature.want("jpeg2000"): + _dbg("Looking for jpeg2000") best_version = None best_path = None # Find the best version for directory in self.compiler.include_dirs: - _dbg('Checking for openjpeg-#.# in %s', directory) + _dbg("Checking for openjpeg-#.# in %s", directory) try: listdir = os.listdir(directory) except Exception: # WindowsError, FileNotFoundError continue for name in listdir: - if name.startswith('openjpeg-') and \ - os.path.isfile(os.path.join(directory, name, - 'openjpeg.h')): - _dbg('Found openjpeg.h in %s/%s', (directory, name)) - version = tuple(int(x) for x in name[9:].split('.')) + if name.startswith("openjpeg-") and os.path.isfile( + os.path.join(directory, name, "openjpeg.h") + ): + _dbg("Found openjpeg.h in %s/%s", (directory, name)) + version = tuple(int(x) for x in name[9:].split(".")) if best_version is None or version > best_version: best_version = version best_path = os.path.join(directory, name) - _dbg('Best openjpeg version %s so far in %s', - (best_version, best_path)) + _dbg( + "Best openjpeg version %s so far in %s", + (best_version, best_path), + ) - if best_version and _find_library_file(self, 'openjp2'): + if best_version and _find_library_file(self, "openjp2"): # Add the directory to the include path so we can include # rather than having to cope with the versioned # include path @@ -524,45 +589,43 @@ class pil_build_ext(build_ext): # self.compiler.include_dirs. Should investigate how that is # possible. _add_directory(self.compiler.include_dirs, best_path, 0) - feature.jpeg2000 = 'openjp2' - feature.openjpeg_version = '.'.join(str(x) for x in - best_version) + feature.jpeg2000 = "openjp2" + feature.openjpeg_version = ".".join(str(x) for x in best_version) - if feature.want('imagequant'): - _dbg('Looking for imagequant') - if _find_include_file(self, 'libimagequant.h'): + if feature.want("imagequant"): + _dbg("Looking for imagequant") + if _find_include_file(self, "libimagequant.h"): if _find_library_file(self, "imagequant"): feature.imagequant = "imagequant" elif _find_library_file(self, "libimagequant"): feature.imagequant = "libimagequant" - if feature.want('tiff'): - _dbg('Looking for tiff') - if _find_include_file(self, 'tiff.h'): + if feature.want("tiff"): + _dbg("Looking for tiff") + if _find_include_file(self, "tiff.h"): if _find_library_file(self, "tiff"): feature.tiff = "tiff" - if (sys.platform in ["win32", "darwin"] and - _find_library_file(self, "libtiff")): + if sys.platform in ["win32", "darwin"] and _find_library_file( + self, "libtiff" + ): feature.tiff = "libtiff" - if feature.want('freetype'): - _dbg('Looking for freetype') + if feature.want("freetype"): + _dbg("Looking for freetype") if _find_library_file(self, "freetype"): # look for freetype2 include files freetype_version = 0 for subdir in self.compiler.include_dirs: - _dbg('Checking for include file %s in %s', - ("ft2build.h", subdir)) + _dbg("Checking for include file %s in %s", ("ft2build.h", subdir)) if os.path.isfile(os.path.join(subdir, "ft2build.h")): - _dbg('Found %s in %s', ("ft2build.h", subdir)) + _dbg("Found %s in %s", ("ft2build.h", subdir)) freetype_version = 21 subdir = os.path.join(subdir, "freetype2") break subdir = os.path.join(subdir, "freetype2") - _dbg('Checking for include file %s in %s', - ("ft2build.h", subdir)) + _dbg("Checking for include file %s in %s", ("ft2build.h", subdir)) if os.path.isfile(os.path.join(subdir, "ft2build.h")): - _dbg('Found %s in %s', ("ft2build.h", subdir)) + _dbg("Found %s in %s", ("ft2build.h", subdir)) freetype_version = 21 break if freetype_version: @@ -570,8 +633,8 @@ class pil_build_ext(build_ext): if subdir: _add_directory(self.compiler.include_dirs, subdir, 0) - if feature.want('lcms'): - _dbg('Looking for lcms') + if feature.want("lcms"): + _dbg("Looking for lcms") if _find_include_file(self, "lcms2.h"): if _find_library_file(self, "lcms2"): feature.lcms = "lcms2" @@ -579,30 +642,34 @@ class pil_build_ext(build_ext): # alternate Windows name. feature.lcms = "lcms2_static" - if feature.want('webp'): - _dbg('Looking for webp') - if (_find_include_file(self, "webp/encode.h") and - _find_include_file(self, "webp/decode.h")): + if feature.want("webp"): + _dbg("Looking for webp") + if _find_include_file(self, "webp/encode.h") and _find_include_file( + self, "webp/decode.h" + ): # In Google's precompiled zip it is call "libwebp": if _find_library_file(self, "webp"): feature.webp = "webp" elif _find_library_file(self, "libwebp"): feature.webp = "libwebp" - if feature.want('webpmux'): - _dbg('Looking for webpmux') - if (_find_include_file(self, "webp/mux.h") and - _find_include_file(self, "webp/demux.h")): - if (_find_library_file(self, "webpmux") and - _find_library_file(self, "webpdemux")): + if feature.want("webpmux"): + _dbg("Looking for webpmux") + if _find_include_file(self, "webp/mux.h") and _find_include_file( + self, "webp/demux.h" + ): + if _find_library_file(self, "webpmux") and _find_library_file( + self, "webpdemux" + ): feature.webpmux = "webpmux" - if (_find_library_file(self, "libwebpmux") and - _find_library_file(self, "libwebpdemux")): + if _find_library_file(self, "libwebpmux") and _find_library_file( + self, "libwebpdemux" + ): feature.webpmux = "libwebpmux" for f in feature: if not getattr(feature, f) and feature.require(f): - if f in ('jpeg', 'zlib'): + if f in ("jpeg", "zlib"): raise RequiredDependencyException(f) raise DependencyException(f) @@ -636,7 +703,7 @@ class pil_build_ext(build_ext): defs.append(("HAVE_LIBTIFF", None)) if sys.platform == "win32": libs.extend(["kernel32", "user32", "gdi32"]) - if struct.unpack("h", "\0\1".encode('ascii'))[0] == 1: + if struct.unpack("h", "\0\1".encode("ascii"))[0] == 1: defs.append(("WORDS_BIGENDIAN", None)) if sys.platform == "win32" and not (PLATFORM_PYPY or PLATFORM_MINGW): @@ -644,10 +711,7 @@ class pil_build_ext(build_ext): else: defs.append(("PILLOW_VERSION", '"%s"' % PILLOW_VERSION)) - exts = [(Extension("PIL._imaging", - files, - libraries=libs, - define_macros=defs))] + exts = [(Extension("PIL._imaging", files, libraries=libs, define_macros=defs))] # # additional libraries @@ -655,17 +719,26 @@ class pil_build_ext(build_ext): if feature.freetype: libs = ["freetype"] defs = [] - exts.append(Extension( - "PIL._imagingft", ["src/_imagingft.c"], libraries=libs, - define_macros=defs)) + exts.append( + Extension( + "PIL._imagingft", + ["src/_imagingft.c"], + libraries=libs, + define_macros=defs, + ) + ) if feature.lcms: extra = [] if sys.platform == "win32": extra.extend(["user32", "gdi32"]) - exts.append(Extension("PIL._imagingcms", - ["src/_imagingcms.c"], - libraries=[feature.lcms] + extra)) + exts.append( + Extension( + "PIL._imagingcms", + ["src/_imagingcms.c"], + libraries=[feature.lcms] + extra, + ) + ) if feature.webp: libs = [feature.webp] @@ -674,18 +747,23 @@ class pil_build_ext(build_ext): if feature.webpmux: defs.append(("HAVE_WEBPMUX", None)) libs.append(feature.webpmux) - libs.append(feature.webpmux.replace('pmux', 'pdemux')) + libs.append(feature.webpmux.replace("pmux", "pdemux")) - exts.append(Extension("PIL._webp", - ["src/_webp.c"], - libraries=libs, - define_macros=defs)) + exts.append( + Extension( + "PIL._webp", ["src/_webp.c"], libraries=libs, define_macros=defs + ) + ) - tk_libs = ['psapi'] if sys.platform == 'win32' else [] - exts.append(Extension("PIL._imagingtk", - ["src/_imagingtk.c", "src/Tk/tkImaging.c"], - include_dirs=['src/Tk'], - libraries=tk_libs)) + tk_libs = ["psapi"] if sys.platform == "win32" else [] + exts.append( + Extension( + "PIL._imagingtk", + ["src/_imagingtk.c", "src/Tk/tkImaging.c"], + include_dirs=["src/Tk"], + libraries=tk_libs, + ) + ) exts.append(Extension("PIL._imagingmath", ["src/_imagingmath.c"])) exts.append(Extension("PIL._imagingmorph", ["src/_imagingmorph.c"])) @@ -713,8 +791,7 @@ class pil_build_ext(build_ext): options = [ (feature.jpeg, "JPEG"), - (feature.jpeg2000, "OPENJPEG (JPEG2000)", - feature.openjpeg_version), + (feature.jpeg2000, "OPENJPEG (JPEG2000)", feature.openjpeg_version), (feature.zlib, "ZLIB (PNG/ZIP)"), (feature.imagequant, "LIBIMAGEQUANT"), (feature.tiff, "LIBTIFF"), @@ -727,9 +804,9 @@ class pil_build_ext(build_ext): all = 1 for option in options: if option[0]: - version = '' + version = "" if len(option) >= 3 and option[2]: - version = ' (%s)' % option[2] + version = " (%s)" % option[2] print("--- %s support available%s" % (option[1], version)) else: print("*** %s support not available" % option[1]) @@ -740,8 +817,10 @@ class pil_build_ext(build_ext): if not all: print("To add a missing option, make sure you have the required") print("library and headers.") - print("See https://pillow.readthedocs.io/en/latest/installation." - "html#building-from-source") + print( + "See https://pillow.readthedocs.io/en/latest/installation." + "html#building-from-source" + ) print("") print("To check the build, run the selftest.py script.") @@ -749,47 +828,49 @@ class pil_build_ext(build_ext): def debug_build(): - return hasattr(sys, 'gettotalrefcount') + return hasattr(sys, "gettotalrefcount") -needs_pytest = {'pytest', 'test', 'ptr'}.intersection(sys.argv) -pytest_runner = ['pytest-runner'] if needs_pytest else [] +needs_pytest = {"pytest", "test", "ptr"}.intersection(sys.argv) +pytest_runner = ["pytest-runner"] if needs_pytest else [] try: - setup(name=NAME, - version=PILLOW_VERSION, - description='Python Imaging Library (Fork)', - long_description=_read('README.rst').decode('utf-8'), - author='Alex Clark (Fork Author)', - author_email='aclark@aclark.net', - url='http://python-pillow.org', - classifiers=[ - "Development Status :: 6 - Mature", - "License :: OSI Approved :: Historical Permission Notice and Disclaimer (HPND)", # noqa: E501 - "Programming Language :: Python :: 2", - "Programming Language :: Python :: 2.7", - "Programming Language :: Python :: 3", - "Programming Language :: Python :: 3.5", - "Programming Language :: Python :: 3.6", - "Programming Language :: Python :: 3.7", - "Programming Language :: Python :: Implementation :: CPython", - "Programming Language :: Python :: Implementation :: PyPy", - "Topic :: Multimedia :: Graphics", - "Topic :: Multimedia :: Graphics :: Capture :: Digital Camera", - "Topic :: Multimedia :: Graphics :: Capture :: Screen Capture", - "Topic :: Multimedia :: Graphics :: Graphics Conversion", - "Topic :: Multimedia :: Graphics :: Viewers", - ], - python_requires=">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*", - cmdclass={"build_ext": pil_build_ext}, - ext_modules=[Extension("PIL._imaging", ["_imaging.c"])], - include_package_data=True, - setup_requires=pytest_runner, - tests_require=['pytest'], - packages=["PIL"], - package_dir={'': 'src'}, - keywords=["Imaging", ], - zip_safe=not (debug_build() or PLATFORM_MINGW), ) + setup( + name=NAME, + version=PILLOW_VERSION, + description="Python Imaging Library (Fork)", + long_description=_read("README.rst").decode("utf-8"), + author="Alex Clark (Fork Author)", + author_email="aclark@aclark.net", + url="http://python-pillow.org", + classifiers=[ + "Development Status :: 6 - Mature", + "License :: OSI Approved :: Historical Permission Notice and Disclaimer (HPND)", # noqa: E501 + "Programming Language :: Python :: 2", + "Programming Language :: Python :: 2.7", + "Programming Language :: Python :: 3", + "Programming Language :: Python :: 3.5", + "Programming Language :: Python :: 3.6", + "Programming Language :: Python :: 3.7", + "Programming Language :: Python :: Implementation :: CPython", + "Programming Language :: Python :: Implementation :: PyPy", + "Topic :: Multimedia :: Graphics", + "Topic :: Multimedia :: Graphics :: Capture :: Digital Camera", + "Topic :: Multimedia :: Graphics :: Capture :: Screen Capture", + "Topic :: Multimedia :: Graphics :: Graphics Conversion", + "Topic :: Multimedia :: Graphics :: Viewers", + ], + python_requires=">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*", + cmdclass={"build_ext": pil_build_ext}, + ext_modules=[Extension("PIL._imaging", ["_imaging.c"])], + include_package_data=True, + setup_requires=pytest_runner, + tests_require=["pytest"], + packages=["PIL"], + package_dir={"": "src"}, + keywords=["Imaging"], + zip_safe=not (debug_build() or PLATFORM_MINGW), + ) except RequiredDependencyException as err: msg = """ @@ -799,7 +880,9 @@ a required dependency when compiling Pillow from source. Please see the install instructions at: https://pillow.readthedocs.io/en/latest/installation.html -""" % (str(err)) +""" % ( + str(err) + ) sys.stderr.write(msg) raise RequiredDependencyException(msg) except DependencyException as err: @@ -808,6 +891,9 @@ except DependencyException as err: The headers or library files could not be found for %s, which was requested by the option flag --enable-%s -""" % (str(err), str(err)) +""" % ( + str(err), + str(err), + ) sys.stderr.write(msg) raise DependencyException(msg) diff --git a/src/PIL/BdfFontFile.py b/src/PIL/BdfFontFile.py index eac19bde1..9d43bbefa 100644 --- a/src/PIL/BdfFontFile.py +++ b/src/PIL/BdfFontFile.py @@ -32,14 +32,10 @@ bdf_slant = { "O": "Oblique", "RI": "Reverse Italic", "RO": "Reverse Oblique", - "OT": "Other" + "OT": "Other", } -bdf_spacing = { - "P": "Proportional", - "M": "Monospaced", - "C": "Cell" -} +bdf_spacing = {"P": "Proportional", "M": "Monospaced", "C": "Cell"} def bdf_char(f): @@ -50,7 +46,7 @@ def bdf_char(f): return None if s[:9] == b"STARTCHAR": break - id = s[9:].strip().decode('ascii') + id = s[9:].strip().decode("ascii") # load symbol properties props = {} @@ -59,7 +55,7 @@ def bdf_char(f): if not s or s[:6] == b"BITMAP": break i = s.find(b" ") - props[s[:i].decode('ascii')] = s[i+1:-1].decode('ascii') + props[s[:i].decode("ascii")] = s[i + 1 : -1].decode("ascii") # load bitmap bitmap = [] @@ -73,7 +69,7 @@ def bdf_char(f): [x, y, l, d] = [int(p) for p in props["BBX"].split()] [dx, dy] = [int(p) for p in props["DWIDTH"].split()] - bbox = (dx, dy), (l, -d-y, x+l, -d), (0, 0, x, y) + bbox = (dx, dy), (l, -d - y, x + l, -d), (0, 0, x, y) try: im = Image.frombytes("1", (x, y), bitmap, "hex", "1") @@ -87,8 +83,8 @@ def bdf_char(f): ## # Font file plugin for the X11 BDF format. -class BdfFontFile(FontFile.FontFile): +class BdfFontFile(FontFile.FontFile): def __init__(self, fp): FontFile.FontFile.__init__(self) @@ -105,10 +101,10 @@ class BdfFontFile(FontFile.FontFile): if not s or s[:13] == b"ENDPROPERTIES": break i = s.find(b" ") - props[s[:i].decode('ascii')] = s[i+1:-1].decode('ascii') + props[s[:i].decode("ascii")] = s[i + 1 : -1].decode("ascii") if s[:i] in [b"COMMENT", b"COPYRIGHT"]: if s.find(b"LogicalFontDescription") < 0: - comments.append(s[i+1:-1].decode('ascii')) + comments.append(s[i + 1 : -1].decode("ascii")) while True: c = bdf_char(fp) diff --git a/src/PIL/BlpImagePlugin.py b/src/PIL/BlpImagePlugin.py index 398e0fa0c..9690ed2b0 100644 --- a/src/PIL/BlpImagePlugin.py +++ b/src/PIL/BlpImagePlugin.py @@ -47,11 +47,7 @@ BLP_ALPHA_ENCODING_DXT5 = 7 def unpack_565(i): - return ( - ((i >> 11) & 0x1f) << 3, - ((i >> 5) & 0x3f) << 2, - (i & 0x1f) << 3 - ) + return (((i >> 11) & 0x1F) << 3, ((i >> 5) & 0x3F) << 2, (i & 0x1F) << 3) def decode_dxt1(data, alpha=False): @@ -119,7 +115,7 @@ def decode_dxt3(data): for block in range(blocks): idx = block * 16 - block = data[idx:idx + 16] + block = data[idx : idx + 16] # Decode next 16-byte block. bits = struct.unpack_from("<8B", block) color0, color1 = struct.unpack_from(">= 4 else: high = True - a &= 0xf + a &= 0xF a *= 17 # We get a value between 0 and 15 color_code = (code >> 2 * (4 * j + i)) & 0x03 @@ -172,14 +168,12 @@ def decode_dxt5(data): for block in range(blocks): idx = block * 16 - block = data[idx:idx + 16] + block = data[idx : idx + 16] # Decode next 16-byte block. a0, a1 = struct.unpack_from("= 52: - for idx, mask in enumerate(['r_mask', - 'g_mask', - 'b_mask', - 'a_mask']): - file_info[mask] = i32( - header_data[36 + idx * 4:40 + idx * 4] - ) + for idx, mask in enumerate( + ["r_mask", "g_mask", "b_mask", "a_mask"] + ): + file_info[mask] = i32(header_data[36 + idx * 4 : 40 + idx * 4]) else: # 40 byte headers only have the three components in the # bitfields masks, ref: @@ -139,121 +133,133 @@ class BmpImageFile(ImageFile.ImageFile): # There is a 4th component in the RGBQuad, in the alpha # location, but it is listed as a reserved component, # and it is not generally an alpha channel - file_info['a_mask'] = 0x0 - for mask in ['r_mask', 'g_mask', 'b_mask']: + file_info["a_mask"] = 0x0 + for mask in ["r_mask", "g_mask", "b_mask"]: file_info[mask] = i32(read(4)) - file_info['rgb_mask'] = (file_info['r_mask'], - file_info['g_mask'], - file_info['b_mask']) - file_info['rgba_mask'] = (file_info['r_mask'], - file_info['g_mask'], - file_info['b_mask'], - file_info['a_mask']) + file_info["rgb_mask"] = ( + file_info["r_mask"], + file_info["g_mask"], + file_info["b_mask"], + ) + file_info["rgba_mask"] = ( + file_info["r_mask"], + file_info["g_mask"], + file_info["b_mask"], + file_info["a_mask"], + ) else: - raise IOError("Unsupported BMP header type (%d)" % - file_info['header_size']) + raise IOError("Unsupported BMP header type (%d)" % file_info["header_size"]) # ------------------ Special case : header is reported 40, which # ---------------------- is shorter than real size for bpp >= 16 - self._size = file_info['width'], file_info['height'] + self._size = file_info["width"], file_info["height"] # ------- If color count was not found in the header, compute from bits - file_info["colors"] = (file_info["colors"] - if file_info.get("colors", 0) - else (1 << file_info["bits"])) + file_info["colors"] = ( + file_info["colors"] + if file_info.get("colors", 0) + else (1 << file_info["bits"]) + ) # ------------------------------- Check abnormal values for DOS attacks - if file_info['width'] * file_info['height'] > 2**31: + if file_info["width"] * file_info["height"] > 2 ** 31: raise IOError("Unsupported BMP Size: (%dx%d)" % self.size) # ---------------------- Check bit depth for unusual unsupported values - self.mode, raw_mode = BIT2MODE.get(file_info['bits'], (None, None)) + self.mode, raw_mode = BIT2MODE.get(file_info["bits"], (None, None)) if self.mode is None: - raise IOError("Unsupported BMP pixel depth (%d)" - % file_info['bits']) + raise IOError("Unsupported BMP pixel depth (%d)" % file_info["bits"]) # ---------------- Process BMP with Bitfields compression (not palette) - if file_info['compression'] == self.BITFIELDS: + if file_info["compression"] == self.BITFIELDS: SUPPORTED = { - 32: [(0xff0000, 0xff00, 0xff, 0x0), - (0xff0000, 0xff00, 0xff, 0xff000000), - (0xff, 0xff00, 0xff0000, 0xff000000), - (0x0, 0x0, 0x0, 0x0), - (0xff000000, 0xff0000, 0xff00, 0x0)], - 24: [(0xff0000, 0xff00, 0xff)], - 16: [(0xf800, 0x7e0, 0x1f), (0x7c00, 0x3e0, 0x1f)] + 32: [ + (0xFF0000, 0xFF00, 0xFF, 0x0), + (0xFF0000, 0xFF00, 0xFF, 0xFF000000), + (0xFF, 0xFF00, 0xFF0000, 0xFF000000), + (0x0, 0x0, 0x0, 0x0), + (0xFF000000, 0xFF0000, 0xFF00, 0x0), + ], + 24: [(0xFF0000, 0xFF00, 0xFF)], + 16: [(0xF800, 0x7E0, 0x1F), (0x7C00, 0x3E0, 0x1F)], } MASK_MODES = { - (32, (0xff0000, 0xff00, 0xff, 0x0)): "BGRX", - (32, (0xff000000, 0xff0000, 0xff00, 0x0)): "XBGR", - (32, (0xff, 0xff00, 0xff0000, 0xff000000)): "RGBA", - (32, (0xff0000, 0xff00, 0xff, 0xff000000)): "BGRA", + (32, (0xFF0000, 0xFF00, 0xFF, 0x0)): "BGRX", + (32, (0xFF000000, 0xFF0000, 0xFF00, 0x0)): "XBGR", + (32, (0xFF, 0xFF00, 0xFF0000, 0xFF000000)): "RGBA", + (32, (0xFF0000, 0xFF00, 0xFF, 0xFF000000)): "BGRA", (32, (0x0, 0x0, 0x0, 0x0)): "BGRA", - (24, (0xff0000, 0xff00, 0xff)): "BGR", - (16, (0xf800, 0x7e0, 0x1f)): "BGR;16", - (16, (0x7c00, 0x3e0, 0x1f)): "BGR;15" + (24, (0xFF0000, 0xFF00, 0xFF)): "BGR", + (16, (0xF800, 0x7E0, 0x1F)): "BGR;16", + (16, (0x7C00, 0x3E0, 0x1F)): "BGR;15", } - if file_info['bits'] in SUPPORTED: - if file_info['bits'] == 32 and \ - file_info['rgba_mask'] in SUPPORTED[file_info['bits']]: - raw_mode = MASK_MODES[ - (file_info["bits"], file_info["rgba_mask"]) - ] + if file_info["bits"] in SUPPORTED: + if ( + file_info["bits"] == 32 + and file_info["rgba_mask"] in SUPPORTED[file_info["bits"]] + ): + raw_mode = MASK_MODES[(file_info["bits"], file_info["rgba_mask"])] self.mode = "RGBA" if "A" in raw_mode else self.mode - elif (file_info['bits'] in (24, 16) and - file_info['rgb_mask'] in SUPPORTED[file_info['bits']]): - raw_mode = MASK_MODES[ - (file_info['bits'], file_info['rgb_mask']) - ] + elif ( + file_info["bits"] in (24, 16) + and file_info["rgb_mask"] in SUPPORTED[file_info["bits"]] + ): + raw_mode = MASK_MODES[(file_info["bits"], file_info["rgb_mask"])] else: raise IOError("Unsupported BMP bitfields layout") else: raise IOError("Unsupported BMP bitfields layout") - elif file_info['compression'] == self.RAW: - if file_info['bits'] == 32 and header == 22: # 32-bit .cur offset + elif file_info["compression"] == self.RAW: + if file_info["bits"] == 32 and header == 22: # 32-bit .cur offset raw_mode, self.mode = "BGRA", "RGBA" else: - raise IOError("Unsupported BMP compression (%d)" % - file_info['compression']) + raise IOError("Unsupported BMP compression (%d)" % file_info["compression"]) # --------------- Once the header is processed, process the palette/LUT if self.mode == "P": # Paletted for 1, 4 and 8 bit images # ---------------------------------------------------- 1-bit images - if not (0 < file_info['colors'] <= 65536): - raise IOError("Unsupported BMP Palette size (%d)" % - file_info['colors']) + if not (0 < file_info["colors"] <= 65536): + raise IOError("Unsupported BMP Palette size (%d)" % file_info["colors"]) else: - padding = file_info['palette_padding'] - palette = read(padding * file_info['colors']) + padding = file_info["palette_padding"] + palette = read(padding * file_info["colors"]) greyscale = True - indices = (0, 255) if file_info['colors'] == 2 else \ - list(range(file_info['colors'])) + indices = ( + (0, 255) + if file_info["colors"] == 2 + else list(range(file_info["colors"])) + ) # ----------------- Check if greyscale and ignore palette if so for ind, val in enumerate(indices): - rgb = palette[ind*padding:ind*padding + 3] + rgb = palette[ind * padding : ind * padding + 3] if rgb != o8(val) * 3: greyscale = False # ------- If all colors are grey, white or black, ditch palette if greyscale: - self.mode = "1" if file_info['colors'] == 2 else "L" + self.mode = "1" if file_info["colors"] == 2 else "L" raw_mode = self.mode else: self.mode = "P" self.palette = ImagePalette.raw( - "BGRX" if padding == 4 else "BGR", palette) + "BGRX" if padding == 4 else "BGR", palette + ) # ---------------------------- Finally set the tile data for the plugin - self.info['compression'] = file_info['compression'] + self.info["compression"] = file_info["compression"] self.tile = [ - ('raw', - (0, 0, file_info['width'], file_info['height']), - offset or self.fp.tell(), - (raw_mode, - ((file_info['width'] * file_info['bits'] + 31) >> 3) & (~3), - file_info['direction'])) + ( + "raw", + (0, 0, file_info["width"], file_info["height"]), + offset or self.fp.tell(), + ( + raw_mode, + ((file_info["width"] * file_info["bits"] + 31) >> 3) & (~3), + file_info["direction"], + ), + ) ] def _open(self): @@ -280,6 +286,7 @@ class DibImageFile(BmpImageFile): def _open(self): self._bitmap() + # # -------------------------------------------------------------------- # Write BMP file @@ -311,31 +318,36 @@ def _save(im, fp, filename, bitmap_header=True): # 1 meter == 39.3701 inches ppm = tuple(map(lambda x: int(x * 39.3701 + 0.5), dpi)) - stride = ((im.size[0]*bits+7)//8+3) & (~3) + stride = ((im.size[0] * bits + 7) // 8 + 3) & (~3) header = 40 # or 64 for OS/2 version 2 image = stride * im.size[1] # bitmap header if bitmap_header: offset = 14 + header + colors * 4 - fp.write(b"BM" + # file type (magic) - o32(offset+image) + # file size - o32(0) + # reserved - o32(offset)) # image data offset + fp.write( + b"BM" + + o32(offset + image) # file type (magic) + + o32(0) # file size + + o32(offset) # reserved + ) # image data offset # bitmap info header - fp.write(o32(header) + # info header size - o32(im.size[0]) + # width - o32(im.size[1]) + # height - o16(1) + # planes - o16(bits) + # depth - o32(0) + # compression (0=uncompressed) - o32(image) + # size of bitmap - o32(ppm[0]) + o32(ppm[1]) + # resolution - o32(colors) + # colors used - o32(colors)) # colors important + fp.write( + o32(header) # info header size + + o32(im.size[0]) # width + + o32(im.size[1]) # height + + o16(1) # planes + + o16(bits) # depth + + o32(0) # compression (0=uncompressed) + + o32(image) # size of bitmap + + o32(ppm[0]) # resolution + + o32(ppm[1]) # resolution + + o32(colors) # colors used + + o32(colors) # colors important + ) - fp.write(b"\0" * (header - 40)) # padding (for OS/2 format) + fp.write(b"\0" * (header - 40)) # padding (for OS/2 format) if im.mode == "1": for i in (0, 255): @@ -346,8 +358,8 @@ def _save(im, fp, filename, bitmap_header=True): elif im.mode == "P": fp.write(im.im.getpalette("RGB", "BGRX")) - ImageFile._save(im, fp, [("raw", (0, 0)+im.size, 0, - (rawmode, stride, -1))]) + ImageFile._save(im, fp, [("raw", (0, 0) + im.size, 0, (rawmode, stride, -1))]) + # # -------------------------------------------------------------------- diff --git a/src/PIL/BufrStubImagePlugin.py b/src/PIL/BufrStubImagePlugin.py index a1957b32a..56cac3bb1 100644 --- a/src/PIL/BufrStubImagePlugin.py +++ b/src/PIL/BufrStubImagePlugin.py @@ -27,6 +27,7 @@ def register_handler(handler): # -------------------------------------------------------------------- # Image adapter + def _accept(prefix): return prefix[:4] == b"BUFR" or prefix[:4] == b"ZCZC" diff --git a/src/PIL/ContainerIO.py b/src/PIL/ContainerIO.py index e6c288c83..3cf9d82d2 100644 --- a/src/PIL/ContainerIO.py +++ b/src/PIL/ContainerIO.py @@ -22,7 +22,6 @@ import io class ContainerIO(object): - def __init__(self, file, offset, length): """ Create file object. diff --git a/src/PIL/CurImagePlugin.py b/src/PIL/CurImagePlugin.py index e0a5fae62..68afac06d 100644 --- a/src/PIL/CurImagePlugin.py +++ b/src/PIL/CurImagePlugin.py @@ -36,6 +36,7 @@ def _accept(prefix): ## # Image plugin for Windows Cursor files. + class CurImageFile(BmpImagePlugin.BmpImageFile): format = "CUR" @@ -65,9 +66,9 @@ class CurImageFile(BmpImagePlugin.BmpImageFile): self._bitmap(i32(m[12:]) + offset) # patch up the bitmap height - self._size = self.size[0], self.size[1]//2 + self._size = self.size[0], self.size[1] // 2 d, e, o, a = self.tile[0] - self.tile[0] = d, (0, 0)+self.size, o, a + self.tile[0] = d, (0, 0) + self.size, o, a return diff --git a/src/PIL/DcxImagePlugin.py b/src/PIL/DcxImagePlugin.py index 3c8c2bc8a..57c321417 100644 --- a/src/PIL/DcxImagePlugin.py +++ b/src/PIL/DcxImagePlugin.py @@ -39,6 +39,7 @@ def _accept(prefix): ## # Image plugin for the Intel DCX format. + class DcxImageFile(PcxImageFile): format = "DCX" diff --git a/src/PIL/DdsImagePlugin.py b/src/PIL/DdsImagePlugin.py index 3954a1d6e..a9afb3849 100644 --- a/src/PIL/DdsImagePlugin.py +++ b/src/PIL/DdsImagePlugin.py @@ -61,8 +61,7 @@ DDS_LUMINANCEA = DDPF_LUMINANCE | DDPF_ALPHAPIXELS DDS_ALPHA = DDPF_ALPHA DDS_PAL8 = DDPF_PALETTEINDEXED8 -DDS_HEADER_FLAGS_TEXTURE = (DDSD_CAPS | DDSD_HEIGHT | DDSD_WIDTH | - DDSD_PIXELFORMAT) +DDS_HEADER_FLAGS_TEXTURE = DDSD_CAPS | DDSD_HEIGHT | DDSD_WIDTH | DDSD_PIXELFORMAT DDS_HEADER_FLAGS_MIPMAP = DDSD_MIPMAPCOUNT DDS_HEADER_FLAGS_VOLUME = DDSD_DEPTH DDS_HEADER_FLAGS_PITCH = DDSD_PITCH @@ -130,8 +129,8 @@ class DdsImageFile(ImageFile.ImageFile): masks = {mask: ["R", "G", "B", "A"][i] for i, mask in enumerate(masks)} rawmode = "" if bitcount == 32: - rawmode += masks[0xff000000] - rawmode += masks[0xff0000] + masks[0xff00] + masks[0xff] + rawmode += masks[0xFF000000] + rawmode += masks[0xFF0000] + masks[0xFF00] + masks[0xFF] self.tile = [("raw", (0, 0) + self.size, 0, (rawmode, 0, 1))] else: @@ -151,24 +150,21 @@ class DdsImageFile(ImageFile.ImageFile): # ignoring flags which pertain to volume textures and cubemaps dxt10 = BytesIO(self.fp.read(20)) dxgi_format, dimension = struct.unpack(" 0: - s = fp.read(min(lengthfile, 100*1024)) + s = fp.read(min(lengthfile, 100 * 1024)) if not s: break lengthfile -= len(s) f.write(s) # Build Ghostscript command - command = ["gs", - "-q", # quiet mode - "-g%dx%d" % size, # set output geometry (pixels) - "-r%fx%f" % res, # set input DPI (dots per inch) - "-dBATCH", # exit after processing - "-dNOPAUSE", # don't pause between pages - "-dSAFER", # safe mode - "-sDEVICE=ppmraw", # ppm driver - "-sOutputFile=%s" % outfile, # output file - # adjust for image origin - "-c", "%d %d translate" % (-bbox[0], -bbox[1]), - "-f", infile, # input file - # showpage (see https://bugs.ghostscript.com/show_bug.cgi?id=698272) - "-c", "showpage", - ] + command = [ + "gs", + "-q", # quiet mode + "-g%dx%d" % size, # set output geometry (pixels) + "-r%fx%f" % res, # set input DPI (dots per inch) + "-dBATCH", # exit after processing + "-dNOPAUSE", # don't pause between pages + "-dSAFER", # safe mode + "-sDEVICE=ppmraw", # ppm driver + "-sOutputFile=%s" % outfile, # output file + # adjust for image origin + "-c", + "%d %d translate" % (-bbox[0], -bbox[1]), + "-f", + infile, # input file + # showpage (see https://bugs.ghostscript.com/show_bug.cgi?id=698272) + "-c", + "showpage", + ] if gs_windows_binary is not None: if not gs_windows_binary: - raise WindowsError('Unable to locate Ghostscript on paths') + raise WindowsError("Unable to locate Ghostscript on paths") command[0] = gs_windows_binary # push data through Ghostscript try: startupinfo = None - if sys.platform.startswith('win'): + if sys.platform.startswith("win"): startupinfo = subprocess.STARTUPINFO() startupinfo.dwFlags |= subprocess.STARTF_USESHOWWINDOW subprocess.check_call(command, startupinfo=startupinfo) @@ -163,6 +172,7 @@ class PSFile(object): """ Wrapper for bytesio object that treats either CR or LF as end of line. """ + def __init__(self, fp): self.fp = fp self.char = None @@ -185,12 +195,12 @@ class PSFile(object): if self.char in b"\r\n": self.char = None - return s.decode('latin-1') + return s.decode("latin-1") def _accept(prefix): - return prefix[:4] == b"%!PS" or \ - (len(prefix) >= 4 and i32(prefix) == 0xC6D3D0C5) + return prefix[:4] == b"%!PS" or (len(prefix) >= 4 and i32(prefix) == 0xC6D3D0C5) + ## # Image plugin for Encapsulated Postscript. This plugin supports only @@ -224,7 +234,7 @@ class EpsImageFile(ImageFile.ImageFile): # Load EPS header s_raw = fp.readline() - s = s_raw.strip('\r\n') + s = s_raw.strip("\r\n") while s_raw: if s: @@ -246,8 +256,9 @@ class EpsImageFile(ImageFile.ImageFile): # put floating point values there anyway. box = [int(float(i)) for i in v.split()] self._size = box[2] - box[0], box[3] - box[1] - self.tile = [("eps", (0, 0) + self.size, offset, - (length, box))] + self.tile = [ + ("eps", (0, 0) + self.size, offset, (length, box)) + ] except Exception: pass @@ -262,7 +273,7 @@ class EpsImageFile(ImageFile.ImageFile): self.info[k[:8]] = k[9:] else: self.info[k] = "" - elif s[0] == '%': + elif s[0] == "%": # handle non-DSC Postscript comments that some # tools mistakenly put in the Comments section pass @@ -270,7 +281,7 @@ class EpsImageFile(ImageFile.ImageFile): raise IOError("bad EPS header") s_raw = fp.readline() - s = s_raw.strip('\r\n') + s = s_raw.strip("\r\n") if s and s[:1] != "%": break @@ -297,7 +308,7 @@ class EpsImageFile(ImageFile.ImageFile): self._size = int(x), int(y) return - s = fp.readline().strip('\r\n') + s = fp.readline().strip("\r\n") if not s: break @@ -344,6 +355,7 @@ class EpsImageFile(ImageFile.ImageFile): # # -------------------------------------------------------------------- + def _save(im, fp, filename, eps=1): """EPS Writer for the Python Imaging Library.""" @@ -366,7 +378,7 @@ def _save(im, fp, filename, eps=1): wrapped_fp = False if fp != sys.stdout: if sys.version_info.major > 2: - fp = io.TextIOWrapper(fp, encoding='latin-1') + fp = io.TextIOWrapper(fp, encoding="latin-1") wrapped_fp = True try: @@ -381,7 +393,7 @@ def _save(im, fp, filename, eps=1): fp.write("%%EndComments\n") fp.write("%%Page: 1 1\n") fp.write("%%ImageData: %d %d " % im.size) - fp.write("%d %d 0 1 1 \"%s\"\n" % operator) + fp.write('%d %d 0 1 1 "%s"\n' % operator) # # image header @@ -396,7 +408,7 @@ def _save(im, fp, filename, eps=1): if hasattr(fp, "flush"): fp.flush() - ImageFile._save(im, base_fp, [("eps", (0, 0)+im.size, 0, None)]) + ImageFile._save(im, base_fp, [("eps", (0, 0) + im.size, 0, None)]) fp.write("\n%%%%EndBinary\n") fp.write("grestore end\n") @@ -406,6 +418,7 @@ def _save(im, fp, filename, eps=1): if wrapped_fp: fp.detach() + # # -------------------------------------------------------------------- diff --git a/src/PIL/ExifTags.py b/src/PIL/ExifTags.py index a8ad26bcc..47a981e0f 100644 --- a/src/PIL/ExifTags.py +++ b/src/PIL/ExifTags.py @@ -18,11 +18,10 @@ # Maps EXIF tags to tag names. TAGS = { - # possibly incomplete - 0x000b: "ProcessingSoftware", - 0x00fe: "NewSubfileType", - 0x00ff: "SubfileType", + 0x000B: "ProcessingSoftware", + 0x00FE: "NewSubfileType", + 0x00FF: "SubfileType", 0x0100: "ImageWidth", 0x0101: "ImageLength", 0x0102: "BitsPerSample", @@ -31,10 +30,10 @@ TAGS = { 0x0107: "Thresholding", 0x0108: "CellWidth", 0x0109: "CellLength", - 0x010a: "FillOrder", - 0x010d: "DocumentName", - 0x010e: "ImageDescription", - 0x010f: "Make", + 0x010A: "FillOrder", + 0x010D: "DocumentName", + 0x010E: "ImageDescription", + 0x010F: "Make", 0x0110: "Model", 0x0111: "StripOffsets", 0x0112: "Orientation", @@ -43,10 +42,10 @@ TAGS = { 0x0117: "StripByteCounts", 0x0118: "MinSampleValue", 0x0119: "MaxSampleValue", - 0x011a: "XResolution", - 0x011b: "YResolution", - 0x011c: "PlanarConfiguration", - 0x011d: "PageName", + 0x011A: "XResolution", + 0x011B: "YResolution", + 0x011C: "PlanarConfiguration", + 0x011D: "PageName", 0x0120: "FreeOffsets", 0x0121: "FreeByteCounts", 0x0122: "GrayResponseUnit", @@ -55,24 +54,24 @@ TAGS = { 0x0125: "T6Options", 0x0128: "ResolutionUnit", 0x0129: "PageNumber", - 0x012d: "TransferFunction", + 0x012D: "TransferFunction", 0x0131: "Software", 0x0132: "DateTime", - 0x013b: "Artist", - 0x013c: "HostComputer", - 0x013d: "Predictor", - 0x013e: "WhitePoint", - 0x013f: "PrimaryChromaticities", + 0x013B: "Artist", + 0x013C: "HostComputer", + 0x013D: "Predictor", + 0x013E: "WhitePoint", + 0x013F: "PrimaryChromaticities", 0x0140: "ColorMap", 0x0141: "HalftoneHints", 0x0142: "TileWidth", 0x0143: "TileLength", 0x0144: "TileOffsets", 0x0145: "TileByteCounts", - 0x014a: "SubIFDs", - 0x014c: "InkSet", - 0x014d: "InkNames", - 0x014e: "NumberOfInks", + 0x014A: "SubIFDs", + 0x014C: "InkSet", + 0x014D: "InkNames", + 0x014E: "NumberOfInks", 0x0150: "DotRange", 0x0151: "TargetPrinter", 0x0152: "ExtraSamples", @@ -83,9 +82,9 @@ TAGS = { 0x0157: "ClipPath", 0x0158: "XClipPathUnits", 0x0159: "YClipPathUnits", - 0x015a: "Indexed", - 0x015b: "JPEGTables", - 0x015f: "OPIProxy", + 0x015A: "Indexed", + 0x015B: "JPEGTables", + 0x015F: "OPIProxy", 0x0200: "JPEGProc", 0x0201: "JpegIFOffset", 0x0202: "JpegIFByteCount", @@ -99,20 +98,20 @@ TAGS = { 0x0212: "YCbCrSubSampling", 0x0213: "YCbCrPositioning", 0x0214: "ReferenceBlackWhite", - 0x02bc: "XMLPacket", + 0x02BC: "XMLPacket", 0x1000: "RelatedImageFileFormat", 0x1001: "RelatedImageWidth", 0x1002: "RelatedImageLength", 0x4746: "Rating", 0x4749: "RatingPercent", - 0x800d: "ImageID", - 0x828d: "CFARepeatPatternDim", - 0x828e: "CFAPattern", - 0x828f: "BatteryLevel", + 0x800D: "ImageID", + 0x828D: "CFARepeatPatternDim", + 0x828E: "CFAPattern", + 0x828F: "BatteryLevel", 0x8298: "Copyright", - 0x829a: "ExposureTime", - 0x829d: "FNumber", - 0x83bb: "IPTCNAA", + 0x829A: "ExposureTime", + 0x829D: "FNumber", + 0x83BB: "IPTCNAA", 0x8649: "ImageResources", 0x8769: "ExifOffset", 0x8773: "InterColorProfile", @@ -122,8 +121,8 @@ TAGS = { 0x8827: "ISOSpeedRatings", 0x8828: "OECF", 0x8829: "Interlace", - 0x882a: "TimeZoneOffset", - 0x882b: "SelfTimerMode", + 0x882A: "TimeZoneOffset", + 0x882B: "SelfTimerMode", 0x9000: "ExifVersion", 0x9003: "DateTimeOriginal", 0x9004: "DateTimeDigitized", @@ -138,142 +137,142 @@ TAGS = { 0x9207: "MeteringMode", 0x9208: "LightSource", 0x9209: "Flash", - 0x920a: "FocalLength", - 0x920b: "FlashEnergy", - 0x920c: "SpatialFrequencyResponse", - 0x920d: "Noise", + 0x920A: "FocalLength", + 0x920B: "FlashEnergy", + 0x920C: "SpatialFrequencyResponse", + 0x920D: "Noise", 0x9211: "ImageNumber", 0x9212: "SecurityClassification", 0x9213: "ImageHistory", 0x9214: "SubjectLocation", 0x9215: "ExposureIndex", 0x9216: "TIFF/EPStandardID", - 0x927c: "MakerNote", + 0x927C: "MakerNote", 0x9286: "UserComment", 0x9290: "SubsecTime", 0x9291: "SubsecTimeOriginal", 0x9292: "SubsecTimeDigitized", - 0x9c9b: "XPTitle", - 0x9c9c: "XPComment", - 0x9c9d: "XPAuthor", - 0x9c9e: "XPKeywords", - 0x9c9f: "XPSubject", - 0xa000: "FlashPixVersion", - 0xa001: "ColorSpace", - 0xa002: "ExifImageWidth", - 0xa003: "ExifImageHeight", - 0xa004: "RelatedSoundFile", - 0xa005: "ExifInteroperabilityOffset", - 0xa20b: "FlashEnergy", - 0xa20c: "SpatialFrequencyResponse", - 0xa20e: "FocalPlaneXResolution", - 0xa20f: "FocalPlaneYResolution", - 0xa210: "FocalPlaneResolutionUnit", - 0xa214: "SubjectLocation", - 0xa215: "ExposureIndex", - 0xa217: "SensingMethod", - 0xa300: "FileSource", - 0xa301: "SceneType", - 0xa302: "CFAPattern", - 0xa401: "CustomRendered", - 0xa402: "ExposureMode", - 0xa403: "WhiteBalance", - 0xa404: "DigitalZoomRatio", - 0xa405: "FocalLengthIn35mmFilm", - 0xa406: "SceneCaptureType", - 0xa407: "GainControl", - 0xa408: "Contrast", - 0xa409: "Saturation", - 0xa40a: "Sharpness", - 0xa40b: "DeviceSettingDescription", - 0xa40c: "SubjectDistanceRange", - 0xa420: "ImageUniqueID", - 0xa430: "CameraOwnerName", - 0xa431: "BodySerialNumber", - 0xa432: "LensSpecification", - 0xa433: "LensMake", - 0xa434: "LensModel", - 0xa435: "LensSerialNumber", - 0xa500: "Gamma", - 0xc4a5: "PrintImageMatching", - 0xc612: "DNGVersion", - 0xc613: "DNGBackwardVersion", - 0xc614: "UniqueCameraModel", - 0xc615: "LocalizedCameraModel", - 0xc616: "CFAPlaneColor", - 0xc617: "CFALayout", - 0xc618: "LinearizationTable", - 0xc619: "BlackLevelRepeatDim", - 0xc61a: "BlackLevel", - 0xc61b: "BlackLevelDeltaH", - 0xc61c: "BlackLevelDeltaV", - 0xc61d: "WhiteLevel", - 0xc61e: "DefaultScale", - 0xc61f: "DefaultCropOrigin", - 0xc620: "DefaultCropSize", - 0xc621: "ColorMatrix1", - 0xc622: "ColorMatrix2", - 0xc623: "CameraCalibration1", - 0xc624: "CameraCalibration2", - 0xc625: "ReductionMatrix1", - 0xc626: "ReductionMatrix2", - 0xc627: "AnalogBalance", - 0xc628: "AsShotNeutral", - 0xc629: "AsShotWhiteXY", - 0xc62a: "BaselineExposure", - 0xc62b: "BaselineNoise", - 0xc62c: "BaselineSharpness", - 0xc62d: "BayerGreenSplit", - 0xc62e: "LinearResponseLimit", - 0xc62f: "CameraSerialNumber", - 0xc630: "LensInfo", - 0xc631: "ChromaBlurRadius", - 0xc632: "AntiAliasStrength", - 0xc633: "ShadowScale", - 0xc634: "DNGPrivateData", - 0xc635: "MakerNoteSafety", - 0xc65a: "CalibrationIlluminant1", - 0xc65b: "CalibrationIlluminant2", - 0xc65c: "BestQualityScale", - 0xc65d: "RawDataUniqueID", - 0xc68b: "OriginalRawFileName", - 0xc68c: "OriginalRawFileData", - 0xc68d: "ActiveArea", - 0xc68e: "MaskedAreas", - 0xc68f: "AsShotICCProfile", - 0xc690: "AsShotPreProfileMatrix", - 0xc691: "CurrentICCProfile", - 0xc692: "CurrentPreProfileMatrix", - 0xc6bf: "ColorimetricReference", - 0xc6f3: "CameraCalibrationSignature", - 0xc6f4: "ProfileCalibrationSignature", - 0xc6f6: "AsShotProfileName", - 0xc6f7: "NoiseReductionApplied", - 0xc6f8: "ProfileName", - 0xc6f9: "ProfileHueSatMapDims", - 0xc6fa: "ProfileHueSatMapData1", - 0xc6fb: "ProfileHueSatMapData2", - 0xc6fc: "ProfileToneCurve", - 0xc6fd: "ProfileEmbedPolicy", - 0xc6fe: "ProfileCopyright", - 0xc714: "ForwardMatrix1", - 0xc715: "ForwardMatrix2", - 0xc716: "PreviewApplicationName", - 0xc717: "PreviewApplicationVersion", - 0xc718: "PreviewSettingsName", - 0xc719: "PreviewSettingsDigest", - 0xc71a: "PreviewColorSpace", - 0xc71b: "PreviewDateTime", - 0xc71c: "RawImageDigest", - 0xc71d: "OriginalRawFileDigest", - 0xc71e: "SubTileBlockSize", - 0xc71f: "RowInterleaveFactor", - 0xc725: "ProfileLookTableDims", - 0xc726: "ProfileLookTableData", - 0xc740: "OpcodeList1", - 0xc741: "OpcodeList2", - 0xc74e: "OpcodeList3", - 0xc761: "NoiseProfile" + 0x9C9B: "XPTitle", + 0x9C9C: "XPComment", + 0x9C9D: "XPAuthor", + 0x9C9E: "XPKeywords", + 0x9C9F: "XPSubject", + 0xA000: "FlashPixVersion", + 0xA001: "ColorSpace", + 0xA002: "ExifImageWidth", + 0xA003: "ExifImageHeight", + 0xA004: "RelatedSoundFile", + 0xA005: "ExifInteroperabilityOffset", + 0xA20B: "FlashEnergy", + 0xA20C: "SpatialFrequencyResponse", + 0xA20E: "FocalPlaneXResolution", + 0xA20F: "FocalPlaneYResolution", + 0xA210: "FocalPlaneResolutionUnit", + 0xA214: "SubjectLocation", + 0xA215: "ExposureIndex", + 0xA217: "SensingMethod", + 0xA300: "FileSource", + 0xA301: "SceneType", + 0xA302: "CFAPattern", + 0xA401: "CustomRendered", + 0xA402: "ExposureMode", + 0xA403: "WhiteBalance", + 0xA404: "DigitalZoomRatio", + 0xA405: "FocalLengthIn35mmFilm", + 0xA406: "SceneCaptureType", + 0xA407: "GainControl", + 0xA408: "Contrast", + 0xA409: "Saturation", + 0xA40A: "Sharpness", + 0xA40B: "DeviceSettingDescription", + 0xA40C: "SubjectDistanceRange", + 0xA420: "ImageUniqueID", + 0xA430: "CameraOwnerName", + 0xA431: "BodySerialNumber", + 0xA432: "LensSpecification", + 0xA433: "LensMake", + 0xA434: "LensModel", + 0xA435: "LensSerialNumber", + 0xA500: "Gamma", + 0xC4A5: "PrintImageMatching", + 0xC612: "DNGVersion", + 0xC613: "DNGBackwardVersion", + 0xC614: "UniqueCameraModel", + 0xC615: "LocalizedCameraModel", + 0xC616: "CFAPlaneColor", + 0xC617: "CFALayout", + 0xC618: "LinearizationTable", + 0xC619: "BlackLevelRepeatDim", + 0xC61A: "BlackLevel", + 0xC61B: "BlackLevelDeltaH", + 0xC61C: "BlackLevelDeltaV", + 0xC61D: "WhiteLevel", + 0xC61E: "DefaultScale", + 0xC61F: "DefaultCropOrigin", + 0xC620: "DefaultCropSize", + 0xC621: "ColorMatrix1", + 0xC622: "ColorMatrix2", + 0xC623: "CameraCalibration1", + 0xC624: "CameraCalibration2", + 0xC625: "ReductionMatrix1", + 0xC626: "ReductionMatrix2", + 0xC627: "AnalogBalance", + 0xC628: "AsShotNeutral", + 0xC629: "AsShotWhiteXY", + 0xC62A: "BaselineExposure", + 0xC62B: "BaselineNoise", + 0xC62C: "BaselineSharpness", + 0xC62D: "BayerGreenSplit", + 0xC62E: "LinearResponseLimit", + 0xC62F: "CameraSerialNumber", + 0xC630: "LensInfo", + 0xC631: "ChromaBlurRadius", + 0xC632: "AntiAliasStrength", + 0xC633: "ShadowScale", + 0xC634: "DNGPrivateData", + 0xC635: "MakerNoteSafety", + 0xC65A: "CalibrationIlluminant1", + 0xC65B: "CalibrationIlluminant2", + 0xC65C: "BestQualityScale", + 0xC65D: "RawDataUniqueID", + 0xC68B: "OriginalRawFileName", + 0xC68C: "OriginalRawFileData", + 0xC68D: "ActiveArea", + 0xC68E: "MaskedAreas", + 0xC68F: "AsShotICCProfile", + 0xC690: "AsShotPreProfileMatrix", + 0xC691: "CurrentICCProfile", + 0xC692: "CurrentPreProfileMatrix", + 0xC6BF: "ColorimetricReference", + 0xC6F3: "CameraCalibrationSignature", + 0xC6F4: "ProfileCalibrationSignature", + 0xC6F6: "AsShotProfileName", + 0xC6F7: "NoiseReductionApplied", + 0xC6F8: "ProfileName", + 0xC6F9: "ProfileHueSatMapDims", + 0xC6FA: "ProfileHueSatMapData1", + 0xC6FB: "ProfileHueSatMapData2", + 0xC6FC: "ProfileToneCurve", + 0xC6FD: "ProfileEmbedPolicy", + 0xC6FE: "ProfileCopyright", + 0xC714: "ForwardMatrix1", + 0xC715: "ForwardMatrix2", + 0xC716: "PreviewApplicationName", + 0xC717: "PreviewApplicationVersion", + 0xC718: "PreviewSettingsName", + 0xC719: "PreviewSettingsDigest", + 0xC71A: "PreviewColorSpace", + 0xC71B: "PreviewDateTime", + 0xC71C: "RawImageDigest", + 0xC71D: "OriginalRawFileDigest", + 0xC71E: "SubTileBlockSize", + 0xC71F: "RowInterleaveFactor", + 0xC725: "ProfileLookTableDims", + 0xC726: "ProfileLookTableData", + 0xC740: "OpcodeList1", + 0xC741: "OpcodeList2", + 0xC74E: "OpcodeList3", + 0xC761: "NoiseProfile", } ## diff --git a/src/PIL/FitsStubImagePlugin.py b/src/PIL/FitsStubImagePlugin.py index 63c195c43..7e6d35ee5 100644 --- a/src/PIL/FitsStubImagePlugin.py +++ b/src/PIL/FitsStubImagePlugin.py @@ -23,6 +23,7 @@ def register_handler(handler): global _handler _handler = handler + # -------------------------------------------------------------------- # Image adapter diff --git a/src/PIL/FliImagePlugin.py b/src/PIL/FliImagePlugin.py index bbc1a1340..82015e2fc 100644 --- a/src/PIL/FliImagePlugin.py +++ b/src/PIL/FliImagePlugin.py @@ -27,6 +27,7 @@ __version__ = "0.2" # # decoder + def _accept(prefix): return len(prefix) >= 6 and i16(prefix[4:6]) in [0xAF11, 0xAF12] @@ -35,6 +36,7 @@ def _accept(prefix): # Image plugin for the FLI/FLC animation format. Use the seek # method to load individual frames. + class FliImageFile(ImageFile.ImageFile): format = "FLI" @@ -46,9 +48,11 @@ class FliImageFile(ImageFile.ImageFile): # HEAD s = self.fp.read(128) magic = i16(s[4:6]) - if not (magic in [0xAF11, 0xAF12] and - i16(s[14:16]) in [0, 3] and # flags - s[20:22] == b"\x00\x00"): # reserved + if not ( + magic in [0xAF11, 0xAF12] + and i16(s[14:16]) in [0, 3] # flags + and s[20:22] == b"\x00\x00" # reserved + ): raise SyntaxError("not an FLI/FLC file") # frames @@ -84,7 +88,7 @@ class FliImageFile(ImageFile.ImageFile): elif i16(s[4:6]) == 4: self._palette(palette, 0) - palette = [o8(r)+o8(g)+o8(b) for (r, g, b) in palette] + palette = [o8(r) + o8(g) + o8(b) for (r, g, b) in palette] self.palette = ImagePalette.raw("RGB", b"".join(palette)) # set things up to decode first frame @@ -106,8 +110,8 @@ class FliImageFile(ImageFile.ImageFile): s = self.fp.read(n * 3) for n in range(0, len(s), 3): r = i8(s[n]) << shift - g = i8(s[n+1]) << shift - b = i8(s[n+2]) << shift + g = i8(s[n + 1]) << shift + b = i8(s[n + 2]) << shift palette[i] = (r, g, b) i += 1 @@ -152,7 +156,7 @@ class FliImageFile(ImageFile.ImageFile): framesize = i32(s) self.decodermaxblock = framesize - self.tile = [("fli", (0, 0)+self.size, self.__offset, None)] + self.tile = [("fli", (0, 0) + self.size, self.__offset, None)] self.__offset += framesize diff --git a/src/PIL/FontFile.py b/src/PIL/FontFile.py index b43f44762..d7dc020c4 100644 --- a/src/PIL/FontFile.py +++ b/src/PIL/FontFile.py @@ -33,6 +33,7 @@ def puti16(fp, values): ## # Base class for raster font file handlers. + class FontFile(object): bitmap = None @@ -61,7 +62,7 @@ class FontFile(object): w = w + (src[2] - src[0]) if w > WIDTH: lines += 1 - w = (src[2] - src[0]) + w = src[2] - src[0] maxwidth = max(maxwidth, w) xsize = maxwidth @@ -103,7 +104,7 @@ class FontFile(object): # font metrics with open(os.path.splitext(filename)[0] + ".pil", "wb") as fp: fp.write(b"PILfont\n") - fp.write((";;;;;;%d;\n" % self.ysize).encode('ascii')) # HACK!!! + fp.write((";;;;;;%d;\n" % self.ysize).encode("ascii")) # HACK!!! fp.write(b"DATA\n") for id in range(256): m = self.metrics[id] diff --git a/src/PIL/FpxImagePlugin.py b/src/PIL/FpxImagePlugin.py index 5e8a814f2..e2ff47289 100644 --- a/src/PIL/FpxImagePlugin.py +++ b/src/PIL/FpxImagePlugin.py @@ -29,22 +29,23 @@ __version__ = "0.1" # we map from colour field tuples to (mode, rawmode) descriptors MODES = { # opacity - (0x00007ffe): ("A", "L"), + (0x00007FFE): ("A", "L"), # monochrome (0x00010000,): ("L", "L"), - (0x00018000, 0x00017ffe): ("RGBA", "LA"), + (0x00018000, 0x00017FFE): ("RGBA", "LA"), # photo YCC (0x00020000, 0x00020001, 0x00020002): ("RGB", "YCC;P"), - (0x00028000, 0x00028001, 0x00028002, 0x00027ffe): ("RGBA", "YCCA;P"), + (0x00028000, 0x00028001, 0x00028002, 0x00027FFE): ("RGBA", "YCCA;P"), # standard RGB (NIFRGB) (0x00030000, 0x00030001, 0x00030002): ("RGB", "RGB"), - (0x00038000, 0x00038001, 0x00038002, 0x00037ffe): ("RGBA", "RGBA"), + (0x00038000, 0x00038001, 0x00038002, 0x00037FFE): ("RGBA", "RGBA"), } # # -------------------------------------------------------------------- + def _accept(prefix): return prefix[:8] == olefile.MAGIC @@ -52,6 +53,7 @@ def _accept(prefix): ## # Image plugin for the FlashPix images. + class FpxImageFile(ImageFile.ImageFile): format = "FPX" @@ -76,10 +78,9 @@ class FpxImageFile(ImageFile.ImageFile): # # get the Image Contents Property Set - prop = self.ole.getproperties([ - "Data Object Store %06d" % index, - "\005Image Contents" - ]) + prop = self.ole.getproperties( + ["Data Object Store %06d" % index, "\005Image Contents"] + ) # size (highest resolution) @@ -105,7 +106,7 @@ class FpxImageFile(ImageFile.ImageFile): colors = [] for i in range(i32(s, 4)): # note: for now, we ignore the "uncalibrated" flag - colors.append(i32(s, 8+i*4) & 0x7fffffff) + colors.append(i32(s, 8 + i * 4) & 0x7FFFFFFF) self.mode, self.rawmode = MODES[tuple(colors)] @@ -125,7 +126,7 @@ class FpxImageFile(ImageFile.ImageFile): stream = [ "Data Object Store %06d" % index, "Resolution %04d" % subimage, - "Subimage 0000 Header" + "Subimage 0000 Header", ] fp = self.ole.openstream(stream) @@ -157,17 +158,29 @@ class FpxImageFile(ImageFile.ImageFile): for i in range(0, len(s), length): - compression = i32(s, i+8) + compression = i32(s, i + 8) if compression == 0: - self.tile.append(("raw", (x, y, x+xtile, y+ytile), - i32(s, i) + 28, (self.rawmode))) + self.tile.append( + ( + "raw", + (x, y, x + xtile, y + ytile), + i32(s, i) + 28, + (self.rawmode), + ) + ) elif compression == 1: # FIXME: the fill decoder is not implemented - self.tile.append(("fill", (x, y, x+xtile, y+ytile), - i32(s, i) + 28, (self.rawmode, s[12:16]))) + self.tile.append( + ( + "fill", + (x, y, x + xtile, y + ytile), + i32(s, i) + 28, + (self.rawmode, s[12:16]), + ) + ) elif compression == 2: @@ -189,8 +202,14 @@ class FpxImageFile(ImageFile.ImageFile): # The image is stored as defined by rawmode jpegmode = rawmode - self.tile.append(("jpeg", (x, y, x+xtile, y+ytile), - i32(s, i) + 28, (rawmode, jpegmode))) + self.tile.append( + ( + "jpeg", + (x, y, x + xtile, y + ytile), + i32(s, i) + 28, + (rawmode, jpegmode), + ) + ) # FIXME: jpeg tables are tile dependent; the prefix # data must be placed in the tile descriptor itself! @@ -213,11 +232,11 @@ class FpxImageFile(ImageFile.ImageFile): def load(self): if not self.fp: - self.fp = self.ole.openstream(self.stream[:2] + - ["Subimage 0000 Data"]) + self.fp = self.ole.openstream(self.stream[:2] + ["Subimage 0000 Data"]) return ImageFile.ImageFile.load(self) + # # -------------------------------------------------------------------- diff --git a/src/PIL/FtexImagePlugin.py b/src/PIL/FtexImagePlugin.py index f1b9acdc1..76c7a6953 100644 --- a/src/PIL/FtexImagePlugin.py +++ b/src/PIL/FtexImagePlugin.py @@ -87,10 +87,9 @@ class FtexImageFile(ImageFile.ImageFile): self.mode = "RGBA" self.tile = [("bcn", (0, 0) + self.size, 0, (1))] elif format == FORMAT_UNCOMPRESSED: - self.tile = [("raw", (0, 0) + self.size, 0, ('RGB', 0, 1))] + self.tile = [("raw", (0, 0) + self.size, 0, ("RGB", 0, 1))] else: - raise ValueError( - "Invalid texture compression format: %r" % (format)) + raise ValueError("Invalid texture compression format: %r" % (format)) self.fp.close() self.fp = BytesIO(data) diff --git a/src/PIL/GbrImagePlugin.py b/src/PIL/GbrImagePlugin.py index 4bd9fae56..2de56aadf 100644 --- a/src/PIL/GbrImagePlugin.py +++ b/src/PIL/GbrImagePlugin.py @@ -29,13 +29,13 @@ from ._binary import i32be as i32 def _accept(prefix): - return len(prefix) >= 8 and \ - i32(prefix[:4]) >= 20 and i32(prefix[4:8]) in (1, 2) + return len(prefix) >= 8 and i32(prefix[:4]) >= 20 and i32(prefix[4:8]) in (1, 2) ## # Image plugin for the GIMP brush format. + class GbrImageFile(ImageFile.ImageFile): format = "GBR" @@ -55,24 +55,23 @@ class GbrImageFile(ImageFile.ImageFile): if width <= 0 or height <= 0: raise SyntaxError("not a GIMP brush") if color_depth not in (1, 4): - raise SyntaxError( - "Unsupported GIMP brush color depth: %s" % color_depth) + raise SyntaxError("Unsupported GIMP brush color depth: %s" % color_depth) if version == 1: - comment_length = header_size-20 + comment_length = header_size - 20 else: - comment_length = header_size-28 + comment_length = header_size - 28 magic_number = self.fp.read(4) - if magic_number != b'GIMP': + if magic_number != b"GIMP": raise SyntaxError("not a GIMP brush, bad magic number") - self.info['spacing'] = i32(self.fp.read(4)) + self.info["spacing"] = i32(self.fp.read(4)) comment = self.fp.read(comment_length)[:-1] if color_depth == 1: self.mode = "L" else: - self.mode = 'RGBA' + self.mode = "RGBA" self._size = width, height @@ -88,6 +87,7 @@ class GbrImageFile(ImageFile.ImageFile): self.im = Image.core.new(self.mode, self.size) self.frombytes(self.fp.read(self._data_size)) + # # registry diff --git a/src/PIL/GdImageFile.py b/src/PIL/GdImageFile.py index 9f00b2fef..2d492358c 100644 --- a/src/PIL/GdImageFile.py +++ b/src/PIL/GdImageFile.py @@ -37,6 +37,7 @@ __version__ = "0.1" # this plugin, you have to import the GdImageFile module and # use the GdImageFile.open function. + class GdImageFile(ImageFile.ImageFile): format = "GD" @@ -57,15 +58,17 @@ class GdImageFile(ImageFile.ImageFile): trueColorOffset = 2 if trueColor else 0 # transparency index - tindex = i32(s[7+trueColorOffset:7+trueColorOffset+4]) + tindex = i32(s[7 + trueColorOffset : 7 + trueColorOffset + 4]) if tindex < 256: self.info["transparency"] = tindex self.palette = ImagePalette.raw( - "XBGR", s[7+trueColorOffset+4:7+trueColorOffset+4+256*4]) + "XBGR", s[7 + trueColorOffset + 4 : 7 + trueColorOffset + 4 + 256 * 4] + ) - self.tile = [("raw", (0, 0)+self.size, 7+trueColorOffset+4+256*4, - ("L", 0, 1))] + self.tile = [ + ("raw", (0, 0) + self.size, 7 + trueColorOffset + 4 + 256 * 4, ("L", 0, 1)) + ] def open(fp, mode="r"): diff --git a/src/PIL/GifImagePlugin.py b/src/PIL/GifImagePlugin.py index b328982aa..d6d646c59 100644 --- a/src/PIL/GifImagePlugin.py +++ b/src/PIL/GifImagePlugin.py @@ -37,6 +37,7 @@ __version__ = "0.9" # -------------------------------------------------------------------- # Identify/read GIF files + def _accept(prefix): return prefix[:6] in [b"GIF87a", b"GIF89a"] @@ -45,6 +46,7 @@ def _accept(prefix): # Image plugin for GIF images. This plugin supports both GIF87 and # GIF89 images. + class GifImageFile(ImageFile.ImageFile): format = "GIF" @@ -78,7 +80,7 @@ class GifImageFile(ImageFile.ImageFile): # check if palette contains colour indices p = self.fp.read(3 << bits) for i in range(0, len(p), 3): - if not (i//3 == i8(p[i]) == i8(p[i+1]) == i8(p[i+2])): + if not (i // 3 == i8(p[i]) == i8(p[i + 1]) == i8(p[i + 2])): p = ImagePalette.raw("RGB", p) self.global_palette = self.palette = p break @@ -168,6 +170,7 @@ class GifImageFile(ImageFile.ImageFile): self.im.paste(self.dispose, self.dispose_extent) from copy import copy + self.palette = copy(self.global_palette) info = {} @@ -233,6 +236,8 @@ class GifImageFile(ImageFile.ImageFile): # extent x0, y0 = i16(s[0:]), i16(s[2:]) x1, y1 = x0 + i16(s[4:]), y0 + i16(s[6:]) + if x1 > self.size[0] or y1 > self.size[1]: + self._size = max(x1, self.size[0]), max(y1, self.size[1]) self.dispose_extent = x0, y0, x1, y1 flags = i8(s[8]) @@ -240,16 +245,14 @@ class GifImageFile(ImageFile.ImageFile): if flags & 128: bits = (flags & 7) + 1 - self.palette =\ - ImagePalette.raw("RGB", self.fp.read(3 << bits)) + self.palette = ImagePalette.raw("RGB", self.fp.read(3 << bits)) # image data bits = i8(self.fp.read(1)) self.__offset = self.fp.tell() - self.tile = [("gif", - (x0, y0, x1, y1), - self.__offset, - (bits, interlace))] + self.tile = [ + ("gif", (x0, y0, x1, y1), self.__offset, (bits, interlace)) + ] break else: @@ -262,8 +265,7 @@ class GifImageFile(ImageFile.ImageFile): self.dispose = None elif self.disposal_method == 2: # replace with background colour - self.dispose = Image.core.fill("P", self.size, - self.info["background"]) + self.dispose = Image.core.fill("P", self.size, self.info["background"]) else: # replace with previous contents if self.im: @@ -301,8 +303,7 @@ class GifImageFile(ImageFile.ImageFile): # we do this by pasting the updated area onto the previous # frame which we then use as the current image content updated = self._crop(self.im, self.dispose_extent) - self._prev_im.paste(updated, self.dispose_extent, - updated.convert('RGBA')) + self._prev_im.paste(updated, self.dispose_extent, updated.convert("RGBA")) self.im = self._prev_im self._prev_im = self.im.copy() @@ -315,15 +316,12 @@ class GifImageFile(ImageFile.ImageFile): finally: self.__fp = None + # -------------------------------------------------------------------- # Write GIF files -RAWMODE = { - "1": "L", - "L": "L", - "P": "P" -} +RAWMODE = {"1": "L", "L": "L", "P": "P"} def _normalize_mode(im, initial_call=False): @@ -374,19 +372,23 @@ def _normalize_palette(im, palette, info): if isinstance(palette, (bytes, bytearray, list)): source_palette = bytearray(palette[:768]) if isinstance(palette, ImagePalette.ImagePalette): - source_palette = bytearray(itertools.chain.from_iterable( - zip(palette.palette[:256], - palette.palette[256:512], - palette.palette[512:768]))) + source_palette = bytearray( + itertools.chain.from_iterable( + zip( + palette.palette[:256], + palette.palette[256:512], + palette.palette[512:768], + ) + ) + ) if im.mode == "P": if not source_palette: source_palette = im.im.getpalette("RGB")[:768] else: # L-mode if not source_palette: - source_palette = bytearray(i//3 for i in range(768)) - im.palette = ImagePalette.ImagePalette("RGB", - palette=source_palette) + source_palette = bytearray(i // 3 for i in range(768)) + im.palette = ImagePalette.ImagePalette("RGB", palette=source_palette) used_palette_colors = _get_optimize(im, info) if used_palette_colors is not None: @@ -412,8 +414,7 @@ def _write_single_frame(im, fp, palette): _write_local_header(fp, im, (0, 0), flags) im_out.encoderconfig = (8, get_interlace(im)) - ImageFile._save(im_out, fp, [("gif", (0, 0)+im.size, 0, - RAWMODE[im_out.mode])]) + ImageFile._save(im_out, fp, [("gif", (0, 0) + im.size, 0, RAWMODE[im_out.mode])]) fp.write(b"\0") # end of image data @@ -438,7 +439,7 @@ def _write_multiple_frames(im, fp, palette): encoderinfo = im.encoderinfo.copy() if isinstance(duration, (list, tuple)): - encoderinfo['duration'] = duration[frame_count] + encoderinfo["duration"] = duration[frame_count] if isinstance(disposal, (list, tuple)): encoderinfo["disposal"] = disposal[frame_count] frame_count += 1 @@ -451,47 +452,40 @@ def _write_multiple_frames(im, fp, palette): else: base_image = previous["im"] - if _get_palette_bytes(im_frame) == \ - _get_palette_bytes(base_image): + if _get_palette_bytes(im_frame) == _get_palette_bytes(base_frame): delta = ImageChops.subtract_modulo(im_frame, base_image) else: delta = ImageChops.subtract_modulo( - im_frame.convert('RGB'), base_image.convert('RGB')) + im_frame.convert("RGB"), base_image.convert("RGB")) bbox = delta.getbbox() if not bbox: # This frame is identical to the previous frame if duration: - previous['encoderinfo']['duration'] += \ - encoderinfo['duration'] + previous["encoderinfo"]["duration"] += encoderinfo["duration"] continue else: bbox = None background = Image.new("P", im_frame.size, 0) - im_frames.append({ - 'im': im_frame, - 'bbox': bbox, - 'encoderinfo': encoderinfo - }) + im_frames.append({"im": im_frame, "bbox": bbox, "encoderinfo": encoderinfo}) if len(im_frames) > 1: for frame_data in im_frames: - im_frame = frame_data['im'] + im_frame = frame_data["im"] if("disposal" in frame_data["encoderinfo"] and frame_data["encoderinfo"]["disposal"] == 2): frame_data['encoderinfo']['include_color_table'] = True - if not frame_data['bbox']: + if not frame_data["bbox"]: # global header - for s in _get_global_header(im_frame, - frame_data['encoderinfo']): + for s in _get_global_header(im_frame, frame_data["encoderinfo"]): fp.write(s) offset = (0, 0) else: # compress difference - frame_data['encoderinfo']['include_color_table'] = True + frame_data["encoderinfo"]["include_color_table"] = True - im_frame = im_frame.crop(frame_data['bbox']) - offset = frame_data['bbox'][:2] - _write_frame_data(fp, im_frame, offset, frame_data['encoderinfo']) + im_frame = im_frame.crop(frame_data["bbox"]) + offset = frame_data["bbox"][:2] + _write_frame_data(fp, im_frame, offset, frame_data["encoderinfo"]) return True @@ -550,7 +544,7 @@ def _write_local_header(fp, im, offset, flags): else: duration = 0 - disposal = int(im.encoderinfo.get('disposal', 0)) + disposal = int(im.encoderinfo.get("disposal", 0)) if transparent_color_exists or duration != 0 or disposal: packed_flag = 1 if transparent_color_exists else 0 @@ -558,34 +552,35 @@ def _write_local_header(fp, im, offset, flags): if not transparent_color_exists: transparency = 0 - fp.write(b"!" + - o8(249) + # extension intro - o8(4) + # length - o8(packed_flag) + # packed fields - o16(duration) + # duration - o8(transparency) + # transparency index - o8(0)) + fp.write( + b"!" + + o8(249) # extension intro + + o8(4) # length + + o8(packed_flag) # packed fields + + o16(duration) # duration + + o8(transparency) # transparency index + + o8(0) + ) - if "comment" in im.encoderinfo and \ - 1 <= len(im.encoderinfo["comment"]): - fp.write(b"!" + - o8(254)) # extension intro + if "comment" in im.encoderinfo and 1 <= len(im.encoderinfo["comment"]): + fp.write(b"!" + o8(254)) # extension intro for i in range(0, len(im.encoderinfo["comment"]), 255): - subblock = im.encoderinfo["comment"][i:i+255] - fp.write(o8(len(subblock)) + - subblock) + subblock = im.encoderinfo["comment"][i : i + 255] + fp.write(o8(len(subblock)) + subblock) fp.write(o8(0)) if "loop" in im.encoderinfo: number_of_loops = im.encoderinfo["loop"] - fp.write(b"!" + - o8(255) + # extension intro - o8(11) + - b"NETSCAPE2.0" + - o8(3) + - o8(1) + - o16(number_of_loops) + # number of loops - o8(0)) - include_color_table = im.encoderinfo.get('include_color_table') + fp.write( + b"!" + + o8(255) # extension intro + + o8(11) + + b"NETSCAPE2.0" + + o8(3) + + o8(1) + + o16(number_of_loops) # number of loops + + o8(0) + ) + include_color_table = im.encoderinfo.get("include_color_table") if include_color_table: palette_bytes = _get_palette_bytes(im) # If needed, expand palette to minimum size @@ -593,18 +588,20 @@ def _write_local_header(fp, im, offset, flags): palette_bytes = palette_bytes*2 color_table_size = _get_color_table_size(palette_bytes) if color_table_size: - flags = flags | 128 # local color table flag + flags = flags | 128 # local color table flag flags = flags | color_table_size - fp.write(b"," + - o16(offset[0]) + # offset - o16(offset[1]) + - o16(im.size[0]) + # size - o16(im.size[1]) + - o8(flags)) # flags + fp.write( + b"," + + o16(offset[0]) # offset + + o16(offset[1]) + + o16(im.size[0]) # size + + o16(im.size[1]) + + o8(flags) # flags + ) if include_color_table and color_table_size: fp.write(_get_header_palette(palette_bytes)) - fp.write(o8(8)) # bits + fp.write(o8(8)) # bits def _save_netpbm(im, fp, filename): @@ -618,21 +615,23 @@ def _save_netpbm(im, fp, filename): import os from subprocess import Popen, check_call, PIPE, CalledProcessError + tempfile = im._dump() - with open(filename, 'wb') as f: + with open(filename, "wb") as f: if im.mode != "RGB": - with open(os.devnull, 'wb') as devnull: + with open(os.devnull, "wb") as devnull: check_call(["ppmtogif", tempfile], stdout=f, stderr=devnull) else: # Pipe ppmquant output into ppmtogif # "ppmquant 256 %s | ppmtogif > %s" % (tempfile, filename) quant_cmd = ["ppmquant", "256", tempfile] togif_cmd = ["ppmtogif"] - with open(os.devnull, 'wb') as devnull: + with open(os.devnull, "wb") as devnull: quant_proc = Popen(quant_cmd, stdout=PIPE, stderr=devnull) - togif_proc = Popen(togif_cmd, stdin=quant_proc.stdout, - stdout=f, stderr=devnull) + togif_proc = Popen( + togif_cmd, stdin=quant_proc.stdout, stdout=f, stderr=devnull + ) # Allow ppmquant to receive SIGPIPE if ppmtogif exits quant_proc.stdout.close() @@ -678,7 +677,7 @@ def _get_optimize(im, info): # * If we have a 'large' image, the palette is in the noise. # create the new palette if not every color is used - optimise = _FORCE_OPTIMIZE or im.mode == 'L' + optimise = _FORCE_OPTIMIZE or im.mode == "L" if optimise or im.width * im.height < 512 * 512: # check which colors are used used_palette_colors = [] @@ -686,15 +685,18 @@ def _get_optimize(im, info): if count: used_palette_colors.append(i) - if optimise or (len(used_palette_colors) <= 128 and - max(used_palette_colors) > len(used_palette_colors)): + if optimise or ( + len(used_palette_colors) <= 128 + and max(used_palette_colors) > len(used_palette_colors) + ): return used_palette_colors def _get_color_table_size(palette_bytes): # calculate the palette size for the header import math - color_table_size = int(math.ceil(math.log(len(palette_bytes)//3, 2)))-1 + + color_table_size = int(math.ceil(math.log(len(palette_bytes) // 3, 2))) - 1 if color_table_size < 0: color_table_size = 0 return color_table_size @@ -712,7 +714,7 @@ def _get_header_palette(palette_bytes): # add the missing amount of bytes # the palette has to be 2< 0: palette_bytes += o8(0) * 3 * actual_target_size_diff return palette_bytes @@ -737,9 +739,9 @@ def _get_global_header(im, info): version = b"87a" for extensionKey in ["transparency", "duration", "loop", "comment"]: if info and extensionKey in info: - if ((extensionKey == "duration" and info[extensionKey] == 0) or - (extensionKey == "comment" and - not (1 <= len(info[extensionKey]) <= 255))): + if (extensionKey == "duration" and info[extensionKey] == 0) or ( + extensionKey == "comment" and not (1 <= len(info[extensionKey]) <= 255) + ): continue version = b"89a" break @@ -760,18 +762,17 @@ def _get_global_header(im, info): color_table_size = _get_color_table_size(palette_bytes) return [ - b"GIF"+version + # signature + version - o16(im.size[0]) + # canvas width - o16(im.size[1]), # canvas height - + b"GIF" # signature + + version # version + + o16(im.size[0]) # canvas width + + o16(im.size[1]), # canvas height # Logical Screen Descriptor # size of global color table + global color table flag - o8(color_table_size + 128), # packed fields + o8(color_table_size + 128), # packed fields # background + reserved/aspect o8(background) + o8(0), - # Global Color Table - _get_header_palette(palette_bytes) + _get_header_palette(palette_bytes), ] @@ -782,13 +783,15 @@ def _write_frame_data(fp, im_frame, offset, params): # local image header _write_local_header(fp, im_frame, offset, 0) - ImageFile._save(im_frame, fp, [("gif", (0, 0)+im_frame.size, 0, - RAWMODE[im_frame.mode])]) + ImageFile._save( + im_frame, fp, [("gif", (0, 0) + im_frame.size, 0, RAWMODE[im_frame.mode])] + ) fp.write(b"\0") # end of image data finally: del im_frame.encoderinfo + # -------------------------------------------------------------------- # Legacy GIF utilities @@ -837,6 +840,7 @@ def getdata(im, offset=(0, 0), **params): :returns: List of Bytes containing gif encoded frame data """ + class Collector(object): data = [] diff --git a/src/PIL/GimpGradientFile.py b/src/PIL/GimpGradientFile.py index 10593da24..bc17b7060 100644 --- a/src/PIL/GimpGradientFile.py +++ b/src/PIL/GimpGradientFile.py @@ -72,7 +72,7 @@ class GradientFile(object): for i in range(entries): - x = i / float(entries-1) + x = i / float(entries - 1) while x1 < x: ix += 1 @@ -100,8 +100,8 @@ class GradientFile(object): ## # File handler for GIMP's gradient format. -class GimpGradientFile(GradientFile): +class GimpGradientFile(GradientFile): def __init__(self, fp): if fp.readline()[:13] != b"GIMP Gradient": diff --git a/src/PIL/GimpPaletteFile.py b/src/PIL/GimpPaletteFile.py index 0ee899988..693829a23 100644 --- a/src/PIL/GimpPaletteFile.py +++ b/src/PIL/GimpPaletteFile.py @@ -21,13 +21,14 @@ from ._binary import o8 ## # File handler for GIMP's palette format. + class GimpPaletteFile(object): rawmode = "RGB" def __init__(self, fp): - self.palette = [o8(i)*3 for i in range(256)] + self.palette = [o8(i) * 3 for i in range(256)] if fp.readline()[:12] != b"GIMP Palette": raise SyntaxError("not a GIMP palette file") diff --git a/src/PIL/GribStubImagePlugin.py b/src/PIL/GribStubImagePlugin.py index 243ea2a53..8a24a9829 100644 --- a/src/PIL/GribStubImagePlugin.py +++ b/src/PIL/GribStubImagePlugin.py @@ -28,6 +28,7 @@ def register_handler(handler): # -------------------------------------------------------------------- # Image adapter + def _accept(prefix): return prefix[0:4] == b"GRIB" and i8(prefix[7]) == 1 diff --git a/src/PIL/Hdf5StubImagePlugin.py b/src/PIL/Hdf5StubImagePlugin.py index 8783f804d..a3ea12f99 100644 --- a/src/PIL/Hdf5StubImagePlugin.py +++ b/src/PIL/Hdf5StubImagePlugin.py @@ -27,6 +27,7 @@ def register_handler(handler): # -------------------------------------------------------------------- # Image adapter + def _accept(prefix): return prefix[:8] == b"\x89HDF\r\n\x1a\n" diff --git a/src/PIL/IcnsImagePlugin.py b/src/PIL/IcnsImagePlugin.py index 4a10b24b8..9ab9d00f2 100644 --- a/src/PIL/IcnsImagePlugin.py +++ b/src/PIL/IcnsImagePlugin.py @@ -24,7 +24,7 @@ import struct import sys import tempfile -enable_jpeg2k = hasattr(Image.core, 'jp2klib_version') +enable_jpeg2k = hasattr(Image.core, "jp2klib_version") if enable_jpeg2k: from PIL import Jpeg2KImagePlugin @@ -32,7 +32,7 @@ HEADERSIZE = 8 def nextheader(fobj): - return struct.unpack('>4sI', fobj.read(HEADERSIZE)) + return struct.unpack(">4sI", fobj.read(HEADERSIZE)) def read_32t(fobj, start_length, size): @@ -40,8 +40,8 @@ def read_32t(fobj, start_length, size): (start, length) = start_length fobj.seek(start) sig = fobj.read(4) - if sig != b'\x00\x00\x00\x00': - raise SyntaxError('Unknown signature, expecting 0x00000000') + if sig != b"\x00\x00\x00\x00": + raise SyntaxError("Unknown signature, expecting 0x00000000") return read_32(fobj, (start + 4, length - 4), size) @@ -81,12 +81,8 @@ def read_32(fobj, start_length, size): if bytesleft <= 0: break if bytesleft != 0: - raise SyntaxError( - "Error reading channel [%r left]" % bytesleft - ) - band = Image.frombuffer( - "L", pixel_size, b"".join(data), "raw", "L", 0, 1 - ) + raise SyntaxError("Error reading channel [%r left]" % bytesleft) + band = Image.frombuffer("L", pixel_size, b"".join(data), "raw", "L", 0, 1) im.im.putband(band.im, band_ix) return {"RGB": im} @@ -97,9 +93,7 @@ def read_mk(fobj, start_length, size): fobj.seek(start) pixel_size = (size[0] * size[2], size[1] * size[2]) sizesq = pixel_size[0] * pixel_size[1] - band = Image.frombuffer( - "L", pixel_size, fobj.read(sizesq), "raw", "L", 0, 1 - ) + band = Image.frombuffer("L", pixel_size, fobj.read(sizesq), "raw", "L", 0, 1) return {"A": band} @@ -107,73 +101,58 @@ def read_png_or_jpeg2000(fobj, start_length, size): (start, length) = start_length fobj.seek(start) sig = fobj.read(12) - if sig[:8] == b'\x89PNG\x0d\x0a\x1a\x0a': + if sig[:8] == b"\x89PNG\x0d\x0a\x1a\x0a": fobj.seek(start) im = PngImagePlugin.PngImageFile(fobj) return {"RGBA": im} - elif sig[:4] == b'\xff\x4f\xff\x51' \ - or sig[:4] == b'\x0d\x0a\x87\x0a' \ - or sig == b'\x00\x00\x00\x0cjP \x0d\x0a\x87\x0a': + elif ( + sig[:4] == b"\xff\x4f\xff\x51" + or sig[:4] == b"\x0d\x0a\x87\x0a" + or sig == b"\x00\x00\x00\x0cjP \x0d\x0a\x87\x0a" + ): if not enable_jpeg2k: - raise ValueError('Unsupported icon subimage format (rebuild PIL ' - 'with JPEG 2000 support to fix this)') + raise ValueError( + "Unsupported icon subimage format (rebuild PIL " + "with JPEG 2000 support to fix this)" + ) # j2k, jpc or j2c fobj.seek(start) jp2kstream = fobj.read(length) f = io.BytesIO(jp2kstream) im = Jpeg2KImagePlugin.Jpeg2KImageFile(f) - if im.mode != 'RGBA': - im = im.convert('RGBA') + if im.mode != "RGBA": + im = im.convert("RGBA") return {"RGBA": im} else: - raise ValueError('Unsupported icon subimage format') + raise ValueError("Unsupported icon subimage format") class IcnsFile(object): SIZES = { - (512, 512, 2): [ - (b'ic10', read_png_or_jpeg2000), - ], - (512, 512, 1): [ - (b'ic09', read_png_or_jpeg2000), - ], - (256, 256, 2): [ - (b'ic14', read_png_or_jpeg2000), - ], - (256, 256, 1): [ - (b'ic08', read_png_or_jpeg2000), - ], - (128, 128, 2): [ - (b'ic13', read_png_or_jpeg2000), - ], + (512, 512, 2): [(b"ic10", read_png_or_jpeg2000)], + (512, 512, 1): [(b"ic09", read_png_or_jpeg2000)], + (256, 256, 2): [(b"ic14", read_png_or_jpeg2000)], + (256, 256, 1): [(b"ic08", read_png_or_jpeg2000)], + (128, 128, 2): [(b"ic13", read_png_or_jpeg2000)], (128, 128, 1): [ - (b'ic07', read_png_or_jpeg2000), - (b'it32', read_32t), - (b't8mk', read_mk), - ], - (64, 64, 1): [ - (b'icp6', read_png_or_jpeg2000), - ], - (32, 32, 2): [ - (b'ic12', read_png_or_jpeg2000), - ], - (48, 48, 1): [ - (b'ih32', read_32), - (b'h8mk', read_mk), + (b"ic07", read_png_or_jpeg2000), + (b"it32", read_32t), + (b"t8mk", read_mk), ], + (64, 64, 1): [(b"icp6", read_png_or_jpeg2000)], + (32, 32, 2): [(b"ic12", read_png_or_jpeg2000)], + (48, 48, 1): [(b"ih32", read_32), (b"h8mk", read_mk)], (32, 32, 1): [ - (b'icp5', read_png_or_jpeg2000), - (b'il32', read_32), - (b'l8mk', read_mk), - ], - (16, 16, 2): [ - (b'ic11', read_png_or_jpeg2000), + (b"icp5", read_png_or_jpeg2000), + (b"il32", read_32), + (b"l8mk", read_mk), ], + (16, 16, 2): [(b"ic11", read_png_or_jpeg2000)], (16, 16, 1): [ - (b'icp4', read_png_or_jpeg2000), - (b'is32', read_32), - (b's8mk', read_mk), + (b"icp4", read_png_or_jpeg2000), + (b"is32", read_32), + (b"s8mk", read_mk), ], } @@ -185,13 +164,13 @@ class IcnsFile(object): self.dct = dct = {} self.fobj = fobj sig, filesize = nextheader(fobj) - if sig != b'icns': - raise SyntaxError('not an icns file') + if sig != b"icns": + raise SyntaxError("not an icns file") i = HEADERSIZE while i < filesize: sig, blocksize = nextheader(fobj) if blocksize <= 0: - raise SyntaxError('invalid block header') + raise SyntaxError("invalid block header") i += HEADERSIZE blocksize -= HEADERSIZE dct[sig] = (i, blocksize) @@ -233,7 +212,7 @@ class IcnsFile(object): size = (size[0], size[1], 1) channels = self.dataforsize(size) - im = channels.get('RGBA', None) + im = channels.get("RGBA", None) if im: return im @@ -248,6 +227,7 @@ class IcnsFile(object): ## # Image plugin for Mac OS icons. + class IcnsImageFile(ImageFile.ImageFile): """ PIL image support for Mac OS .icns files. @@ -264,13 +244,13 @@ class IcnsImageFile(ImageFile.ImageFile): def _open(self): self.icns = IcnsFile(self.fp) - self.mode = 'RGBA' - self.info['sizes'] = self.icns.itersizes() + self.mode = "RGBA" + self.info["sizes"] = self.icns.itersizes() self.best_size = self.icns.bestsize() - self.size = (self.best_size[0] * self.best_size[2], - self.best_size[1] * self.best_size[2]) - # Just use this to see if it's loaded or not yet. - self.tile = ('',) + self.size = ( + self.best_size[0] * self.best_size[2], + self.best_size[1] * self.best_size[2], + ) @property def size(self): @@ -279,27 +259,33 @@ class IcnsImageFile(ImageFile.ImageFile): @size.setter def size(self, value): info_size = value - if info_size not in self.info['sizes'] and len(info_size) == 2: + if info_size not in self.info["sizes"] and len(info_size) == 2: info_size = (info_size[0], info_size[1], 1) - if info_size not in self.info['sizes'] and len(info_size) == 3 and \ - info_size[2] == 1: - simple_sizes = [(size[0] * size[2], size[1] * size[2]) - for size in self.info['sizes']] + if ( + info_size not in self.info["sizes"] + and len(info_size) == 3 + and info_size[2] == 1 + ): + simple_sizes = [ + (size[0] * size[2], size[1] * size[2]) for size in self.info["sizes"] + ] if value in simple_sizes: - info_size = self.info['sizes'][simple_sizes.index(value)] - if info_size not in self.info['sizes']: - raise ValueError( - "This is not one of the allowed sizes of this image") + info_size = self.info["sizes"][simple_sizes.index(value)] + if info_size not in self.info["sizes"]: + raise ValueError("This is not one of the allowed sizes of this image") self._size = value def load(self): if len(self.size) == 3: self.best_size = self.size - self.size = (self.best_size[0] * self.best_size[2], - self.best_size[1] * self.best_size[2]) + self.size = ( + self.best_size[0] * self.best_size[2], + self.best_size[1] * self.best_size[2], + ) Image.Image.load(self) - if not self.tile: + if self.im and self.im.size == self.size: + # Already loaded return self.load_prepare() # This is likely NOT the best way to do it, but whatever. @@ -311,11 +297,6 @@ class IcnsImageFile(ImageFile.ImageFile): self.im = im.im self.mode = im.mode self.size = im.size - if self._exclusive_fp: - self.fp.close() - self.fp = None - self.icns = None - self.tile = () self.load_end() @@ -331,31 +312,30 @@ def _save(im, fp, filename): fp.flush() # create the temporary set of pngs - iconset = tempfile.mkdtemp('.iconset') - provided_images = {im.width: im - for im in im.encoderinfo.get("append_images", [])} + iconset = tempfile.mkdtemp(".iconset") + provided_images = {im.width: im for im in im.encoderinfo.get("append_images", [])} last_w = None second_path = None for w in [16, 32, 128, 256, 512]: - prefix = 'icon_{}x{}'.format(w, w) + prefix = "icon_{}x{}".format(w, w) - first_path = os.path.join(iconset, prefix+'.png') + first_path = os.path.join(iconset, prefix + ".png") if last_w == w: shutil.copyfile(second_path, first_path) else: im_w = provided_images.get(w, im.resize((w, w), Image.LANCZOS)) im_w.save(first_path) - second_path = os.path.join(iconset, prefix+'@2x.png') - im_w2 = provided_images.get(w*2, im.resize((w*2, w*2), Image.LANCZOS)) + second_path = os.path.join(iconset, prefix + "@2x.png") + im_w2 = provided_images.get(w * 2, im.resize((w * 2, w * 2), Image.LANCZOS)) im_w2.save(second_path) - last_w = w*2 + last_w = w * 2 # iconutil -c icns -o {} {} from subprocess import Popen, PIPE, CalledProcessError convert_cmd = ["iconutil", "-c", "icns", "-o", filename, iconset] - with open(os.devnull, 'wb') as devnull: + with open(os.devnull, "wb") as devnull: convert_proc = Popen(convert_cmd, stdout=PIPE, stderr=devnull) convert_proc.stdout.close() @@ -369,29 +349,28 @@ def _save(im, fp, filename): raise CalledProcessError(retcode, convert_cmd) -Image.register_open(IcnsImageFile.format, IcnsImageFile, - lambda x: x[:4] == b'icns') -Image.register_extension(IcnsImageFile.format, '.icns') +Image.register_open(IcnsImageFile.format, IcnsImageFile, lambda x: x[:4] == b"icns") +Image.register_extension(IcnsImageFile.format, ".icns") -if sys.platform == 'darwin': +if sys.platform == "darwin": Image.register_save(IcnsImageFile.format, _save) Image.register_mime(IcnsImageFile.format, "image/icns") -if __name__ == '__main__': +if __name__ == "__main__": if len(sys.argv) < 2: print("Syntax: python IcnsImagePlugin.py [file]") sys.exit() - imf = IcnsImageFile(open(sys.argv[1], 'rb')) - for size in imf.info['sizes']: + imf = IcnsImageFile(open(sys.argv[1], "rb")) + for size in imf.info["sizes"]: imf.size = size imf.load() im = imf.im - im.save('out-%s-%s-%s.png' % size) + im.save("out-%s-%s-%s.png" % size) im = Image.open(sys.argv[1]) im.save("out.png") - if sys.platform == 'windows': + if sys.platform == "windows": os.startfile("out.png") diff --git a/src/PIL/IcoImagePlugin.py b/src/PIL/IcoImagePlugin.py index c1c0775da..d6a6bc9d3 100644 --- a/src/PIL/IcoImagePlugin.py +++ b/src/PIL/IcoImagePlugin.py @@ -23,6 +23,7 @@ import struct +import warnings from io import BytesIO from . import Image, ImageFile, BmpImagePlugin, PngImagePlugin @@ -41,16 +42,20 @@ _MAGIC = b"\0\0\1\0" def _save(im, fp, filename): fp.write(_MAGIC) # (2+2) - sizes = im.encoderinfo.get("sizes", - [(16, 16), (24, 24), (32, 32), (48, 48), - (64, 64), (128, 128), (256, 256)]) + sizes = im.encoderinfo.get( + "sizes", + [(16, 16), (24, 24), (32, 32), (48, 48), (64, 64), (128, 128), (256, 256)], + ) width, height = im.size - sizes = filter(lambda x: False if (x[0] > width or x[1] > height or - x[0] > 256 or x[1] > 256) else True, - sizes) + sizes = filter( + lambda x: False + if (x[0] > width or x[1] > height or x[0] > 256 or x[1] > 256) + else True, + sizes, + ) sizes = list(sizes) fp.write(struct.pack("=8bpp) - 'reserved': i8(s[3]), - 'planes': i16(s[4:]), - 'bpp': i16(s[6:]), - 'size': i32(s[8:]), - 'offset': i32(s[12:]) + "width": i8(s[0]), + "height": i8(s[1]), + "nb_color": i8(s[2]), # No. of colors in image (0 if >=8bpp) + "reserved": i8(s[3]), + "planes": i16(s[4:]), + "bpp": i16(s[6:]), + "size": i32(s[8:]), + "offset": i32(s[12:]), } # See Wikipedia - for j in ('width', 'height'): + for j in ("width", "height"): if not icon_header[j]: icon_header[j] = 256 # See Wikipedia notes about color depth. # We need this just to differ images with equal sizes - icon_header['color_depth'] = (icon_header['bpp'] or - (icon_header['nb_color'] != 0 and - ceil(log(icon_header['nb_color'], - 2))) or 256) + icon_header["color_depth"] = ( + icon_header["bpp"] + or ( + icon_header["nb_color"] != 0 + and ceil(log(icon_header["nb_color"], 2)) + ) + or 256 + ) - icon_header['dim'] = (icon_header['width'], icon_header['height']) - icon_header['square'] = (icon_header['width'] * - icon_header['height']) + icon_header["dim"] = (icon_header["width"], icon_header["height"]) + icon_header["square"] = icon_header["width"] * icon_header["height"] self.entry.append(icon_header) - self.entry = sorted(self.entry, key=lambda x: x['color_depth']) + self.entry = sorted(self.entry, key=lambda x: x["color_depth"]) # ICO images are usually squares # self.entry = sorted(self.entry, key=lambda x: x['width']) - self.entry = sorted(self.entry, key=lambda x: x['square']) + self.entry = sorted(self.entry, key=lambda x: x["square"]) self.entry.reverse() def sizes(self): """ Get a list of all available icon sizes and color depths. """ - return {(h['width'], h['height']) for h in self.entry} + return {(h["width"], h["height"]) for h in self.entry} + + def getentryindex(self, size, bpp=False): + for (i, h) in enumerate(self.entry): + if size == h["dim"] and (bpp is False or bpp == h["color_depth"]): + return i + return 0 def getimage(self, size, bpp=False): """ Get an image from the icon """ - for (i, h) in enumerate(self.entry): - if size == h['dim'] and (bpp is False or bpp == h['color_depth']): - return self.frame(i) - return self.frame(0) + return self.frame(self.getentryindex(size, bpp)) def frame(self, idx): """ @@ -159,9 +170,9 @@ class IcoFile(object): header = self.entry[idx] - self.buf.seek(header['offset']) + self.buf.seek(header["offset"]) data = self.buf.read(8) - self.buf.seek(header['offset']) + self.buf.seek(header["offset"]) if data[:8] == PngImagePlugin._MAGIC: # png frame @@ -196,11 +207,11 @@ class IcoFile(object): # convert to an 8bpp grayscale image mask = Image.frombuffer( - 'L', # 8bpp - im.size, # (w, h) - alpha_bytes, # source chars - 'raw', # raw decoder - ('L', 0, -1) # 8bpp inverted, unpadded, reversed + "L", # 8bpp + im.size, # (w, h) + alpha_bytes, # source chars + "raw", # raw decoder + ("L", 0, -1), # 8bpp inverted, unpadded, reversed ) else: # get AND image from end of bitmap @@ -212,8 +223,7 @@ class IcoFile(object): # the total mask data is # padded row size * height / bits per char - and_mask_offset = o + int(im.size[0] * im.size[1] * - (bpp / 8.0)) + and_mask_offset = o + int(im.size[0] * im.size[1] * (bpp / 8.0)) total_bytes = int((w * im.size[1]) / 8) self.buf.seek(and_mask_offset) @@ -221,17 +231,17 @@ class IcoFile(object): # convert raw data to image mask = Image.frombuffer( - '1', # 1 bpp - im.size, # (w, h) - mask_data, # source chars - 'raw', # raw decoder - ('1;I', int(w/8), -1) # 1bpp inverted, padded, reversed + "1", # 1 bpp + im.size, # (w, h) + mask_data, # source chars + "raw", # raw decoder + ("1;I", int(w / 8), -1), # 1bpp inverted, padded, reversed ) # now we have two images, im is XOR image and mask is AND image # apply mask image as alpha channel - im = im.convert('RGBA') + im = im.convert("RGBA") im.putalpha(mask) return im @@ -240,6 +250,7 @@ class IcoFile(object): ## # Image plugin for Windows Icon files. + class IcoImageFile(ImageFile.ImageFile): """ PIL read-only image support for Microsoft Windows .ico files. @@ -256,13 +267,14 @@ class IcoImageFile(ImageFile.ImageFile): . https://code.google.com/archive/p/casadebender/wikis/Win32IconImagePlugin.wiki """ + format = "ICO" format_description = "Windows Icon" def _open(self): self.ico = IcoFile(self.fp) - self.info['sizes'] = self.ico.sizes() - self.size = self.ico.entry[0]['dim'] + self.info["sizes"] = self.ico.sizes() + self.size = self.ico.entry[0]["dim"] self.load() @property @@ -271,23 +283,35 @@ class IcoImageFile(ImageFile.ImageFile): @size.setter def size(self, value): - if value not in self.info['sizes']: - raise ValueError( - "This is not one of the allowed sizes of this image") + if value not in self.info["sizes"]: + raise ValueError("This is not one of the allowed sizes of this image") self._size = value def load(self): + if self.im and self.im.size == self.size: + # Already loaded + return im = self.ico.getimage(self.size) # if tile is PNG, it won't really be loaded yet im.load() self.im = im.im self.mode = im.mode - self.size = im.size + if im.size != self.size: + warnings.warn("Image was not the expected size") + + index = self.ico.getentryindex(self.size) + sizes = list(self.info["sizes"]) + sizes[index] = im.size + self.info["sizes"] = set(sizes) + + self.size = im.size def load_seek(self): # Flag the ImageFile.Parser so that it # just does all the decode at the end. pass + + # # -------------------------------------------------------------------- diff --git a/src/PIL/ImImagePlugin.py b/src/PIL/ImImagePlugin.py index 08250e959..46deb29a0 100644 --- a/src/PIL/ImImagePlugin.py +++ b/src/PIL/ImImagePlugin.py @@ -48,8 +48,17 @@ SCALE = "Scale (x,y)" SIZE = "Image size (x*y)" MODE = "Image type" -TAGS = {COMMENT: 0, DATE: 0, EQUIPMENT: 0, FRAMES: 0, LUT: 0, NAME: 0, - SCALE: 0, SIZE: 0, MODE: 0} +TAGS = { + COMMENT: 0, + DATE: 0, + EQUIPMENT: 0, + FRAMES: 0, + LUT: 0, + NAME: 0, + SCALE: 0, + SIZE: 0, + MODE: 0, +} OPEN = { # ifunc93/p3cfunc formats @@ -71,6 +80,7 @@ OPEN = { "RYB3 image": ("RGB", "RYB;T"), # extensions "LA image": ("LA", "LA;L"), + "PA image": ("LA", "PA;L"), "RGBA image": ("RGBA", "RGBA;L"), "RGBX image": ("RGBX", "RGBX;L"), "CMYK image": ("CMYK", "CMYK;L"), @@ -107,6 +117,7 @@ def number(s): ## # Image plugin for the IFUNC IM file format. + class ImImageFile(ImageFile.ImageFile): format = "IM" @@ -139,7 +150,7 @@ class ImImageFile(ImageFile.ImageFile): if s == b"\r": continue - if not s or s == b'\0' or s == b'\x1A': + if not s or s == b"\0" or s == b"\x1A": break # FIXME: this may read whole file if not a text file @@ -148,9 +159,9 @@ class ImImageFile(ImageFile.ImageFile): if len(s) > 100: raise SyntaxError("not an IM file") - if s[-2:] == b'\r\n': + if s[-2:] == b"\r\n": s = s[:-2] - elif s[-1:] == b'\n': + elif s[-1:] == b"\n": s = s[:-1] try: @@ -164,8 +175,8 @@ class ImImageFile(ImageFile.ImageFile): # Don't know if this is the correct encoding, # but a decent guess (I guess) - k = k.decode('latin-1', 'replace') - v = v.decode('latin-1', 'replace') + k = k.decode("latin-1", "replace") + v = v.decode("latin-1", "replace") # Convert value as appropriate if k in [FRAMES, SCALE, SIZE]: @@ -191,8 +202,9 @@ class ImImageFile(ImageFile.ImageFile): else: - raise SyntaxError("Syntax error in IM header: " + - s.decode('ascii', 'replace')) + raise SyntaxError( + "Syntax error in IM header: " + s.decode("ascii", "replace") + ) if not n: raise SyntaxError("Not an IM file") @@ -202,7 +214,7 @@ class ImImageFile(ImageFile.ImageFile): self.mode = self.info[MODE] # Skip forward to start of image data - while s and s[0:1] != b'\x1A': + while s and s[0:1] != b"\x1A": s = self.fp.read(1) if not s: raise SyntaxError("File truncated") @@ -213,20 +225,21 @@ class ImImageFile(ImageFile.ImageFile): greyscale = 1 # greyscale palette linear = 1 # linear greyscale palette for i in range(256): - if palette[i] == palette[i+256] == palette[i+512]: + if palette[i] == palette[i + 256] == palette[i + 512]: if i8(palette[i]) != i: linear = 0 else: greyscale = 0 - if self.mode == "L" or self.mode == "LA": + if self.mode in ["L", "LA", "P", "PA"]: if greyscale: if not linear: self.lut = [i8(c) for c in palette[:256]] else: - if self.mode == "L": + if self.mode in ["L", "P"]: self.mode = self.rawmode = "P" - elif self.mode == "LA": - self.mode = self.rawmode = "PA" + elif self.mode in ["LA", "PA"]: + self.mode = "PA" + self.rawmode = "PA;L" self.palette = ImagePalette.raw("RGB;L", palette) elif self.mode == "RGB": if not greyscale or not linear: @@ -245,8 +258,7 @@ class ImImageFile(ImageFile.ImageFile): # use bit decoder (if necessary) bits = int(self.rawmode[2:]) if bits not in [8, 16, 32]: - self.tile = [("bit", (0, 0)+self.size, offs, - (bits, 8, 3, 0, -1))] + self.tile = [("bit", (0, 0) + self.size, offs, (bits, 8, 3, 0, -1))] return except ValueError: pass @@ -255,13 +267,14 @@ class ImImageFile(ImageFile.ImageFile): # Old LabEye/3PC files. Would be very surprised if anyone # ever stumbled upon such a file ;-) size = self.size[0] * self.size[1] - self.tile = [("raw", (0, 0)+self.size, offs, ("G", 0, -1)), - ("raw", (0, 0)+self.size, offs+size, ("R", 0, -1)), - ("raw", (0, 0)+self.size, offs+2*size, ("B", 0, -1))] + self.tile = [ + ("raw", (0, 0) + self.size, offs, ("G", 0, -1)), + ("raw", (0, 0) + self.size, offs + size, ("R", 0, -1)), + ("raw", (0, 0) + self.size, offs + 2 * size, ("B", 0, -1)), + ] else: # LabEye/IFUNC files - self.tile = [("raw", (0, 0)+self.size, offs, - (self.rawmode, 0, -1))] + self.tile = [("raw", (0, 0) + self.size, offs, (self.rawmode, 0, -1))] @property def n_frames(self): @@ -287,7 +300,7 @@ class ImImageFile(ImageFile.ImageFile): self.fp = self.__fp - self.tile = [("raw", (0, 0)+self.size, offs, (self.rawmode, 0, -1))] + self.tile = [("raw", (0, 0) + self.size, offs, (self.rawmode, 0, -1))] def tell(self): return self.frame @@ -301,6 +314,7 @@ class ImImageFile(ImageFile.ImageFile): finally: self.__fp = None + # # -------------------------------------------------------------------- # Save IM files @@ -322,7 +336,7 @@ SAVE = { "RGBA": ("RGBA", "RGBA;L"), "RGBX": ("RGBX", "RGBX;L"), "CMYK": ("CMYK", "CMYK;L"), - "YCbCr": ("YCC", "YCbCr;L") + "YCbCr": ("YCC", "YCbCr;L"), } @@ -335,17 +349,18 @@ def _save(im, fp, filename): frames = im.encoderinfo.get("frames", 1) - fp.write(("Image type: %s image\r\n" % image_type).encode('ascii')) + fp.write(("Image type: %s image\r\n" % image_type).encode("ascii")) if filename: - fp.write(("Name: %s\r\n" % filename).encode('ascii')) - fp.write(("Image size (x*y): %d*%d\r\n" % im.size).encode('ascii')) - fp.write(("File size (no of images): %d\r\n" % frames).encode('ascii')) - if im.mode == "P": + fp.write(("Name: %s\r\n" % filename).encode("ascii")) + fp.write(("Image size (x*y): %d*%d\r\n" % im.size).encode("ascii")) + fp.write(("File size (no of images): %d\r\n" % frames).encode("ascii")) + if im.mode in ["P", "PA"]: fp.write(b"Lut: 1\r\n") - fp.write(b"\000" * (511-fp.tell()) + b"\032") - if im.mode == "P": + fp.write(b"\000" * (511 - fp.tell()) + b"\032") + if im.mode in ["P", "PA"]: fp.write(im.im.getpalette("RGB", "RGB;L")) # 768 bytes - ImageFile._save(im, fp, [("raw", (0, 0)+im.size, 0, (rawmode, 0, -1))]) + ImageFile._save(im, fp, [("raw", (0, 0) + im.size, 0, (rawmode, 0, -1))]) + # # -------------------------------------------------------------------- diff --git a/src/PIL/Image.py b/src/PIL/Image.py index 18121c6fe..973325d8b 100644 --- a/src/PIL/Image.py +++ b/src/PIL/Image.py @@ -38,6 +38,7 @@ try: import builtins except ImportError: import __builtin__ + builtins = __builtin__ from . import ImageMode, TiffTags @@ -52,6 +53,7 @@ import atexit # type stuff import numbers + try: # Python 3 from collections.abc import Callable, MutableMapping @@ -91,13 +93,13 @@ try: # Also note that Image.core is not a publicly documented interface, # and should be considered private and subject to change. from . import _imaging as core - if __version__ != getattr(core, 'PILLOW_VERSION', None): - raise ImportError("The _imaging extension was built for another " - "version of Pillow or PIL:\n" - "Core version: %s\n" - "Pillow version: %s" % - (getattr(core, 'PILLOW_VERSION', None), - __version__)) + + if __version__ != getattr(core, "PILLOW_VERSION", None): + raise ImportError( + "The _imaging extension was built for another version of Pillow or PIL:\n" + "Core version: %s\n" + "Pillow version: %s" % (getattr(core, "PILLOW_VERSION", None), __version__) + ) except ImportError as v: core = _imaging_not_installed() @@ -107,10 +109,9 @@ except ImportError as v: # the right version (windows only). Print a warning, if # possible. warnings.warn( - "The _imaging extension was built for another version " - "of Python.", - RuntimeWarning - ) + "The _imaging extension was built for another version of Python.", + RuntimeWarning, + ) elif str(v).startswith("The _imaging extension"): warnings.warn(str(v), RuntimeWarning) elif "Symbol not found: _PyUnicodeUCS2_" in str(v): @@ -119,23 +120,23 @@ except ImportError as v: warnings.warn( "The _imaging extension was built for Python with UCS2 support; " "recompile Pillow or build Python --without-wide-unicode. ", - RuntimeWarning - ) + RuntimeWarning, + ) elif "Symbol not found: _PyUnicodeUCS4_" in str(v): # should match _PyUnicodeUCS4_FromString and # _PyUnicodeUCS4_AsLatin1String warnings.warn( "The _imaging extension was built for Python with UCS4 support; " "recompile Pillow or build Python --with-wide-unicode. ", - RuntimeWarning - ) + RuntimeWarning, + ) # Fail here anyway. Don't let people run with a mostly broken Pillow. # see docs/porting.rst raise # works everywhere, win for pypy, not cpython -USE_CFFI_ACCESS = hasattr(sys, 'pypy_version_info') +USE_CFFI_ACCESS = hasattr(sys, "pypy_version_info") try: import cffi except ImportError: @@ -143,10 +144,12 @@ except ImportError: try: from pathlib import Path + HAS_PATHLIB = True except ImportError: try: from pathlib2 import Path + HAS_PATHLIB = True except ImportError: HAS_PATHLIB = False @@ -215,7 +218,7 @@ NORMAL = 0 SEQUENCE = 1 CONTAINER = 2 -if hasattr(core, 'DEFAULT_STRATEGY'): +if hasattr(core, "DEFAULT_STRATEGY"): DEFAULT_STRATEGY = core.DEFAULT_STRATEGY FILTERED = core.FILTERED HUFFMAN_ONLY = core.HUFFMAN_ONLY @@ -241,7 +244,6 @@ ENCODERS = {} _MODEINFO = { # NOTE: this table will be removed in future versions. use # getmode* functions or ImageMode descriptors instead. - # official modes "1": ("L", "L", ("1",)), "L": ("L", "L", ("L",)), @@ -255,46 +257,44 @@ _MODEINFO = { "YCbCr": ("RGB", "L", ("Y", "Cb", "Cr")), "LAB": ("RGB", "L", ("L", "A", "B")), "HSV": ("RGB", "L", ("H", "S", "V")), - # Experimental modes include I;16, I;16L, I;16B, RGBa, BGR;15, and # BGR;24. Use these modes only if you know exactly what you're # doing... - } -if sys.byteorder == 'little': - _ENDIAN = '<' +if sys.byteorder == "little": + _ENDIAN = "<" else: - _ENDIAN = '>' + _ENDIAN = ">" _MODE_CONV = { # official modes - "1": ('|b1', None), # Bits need to be extended to bytes - "L": ('|u1', None), - "LA": ('|u1', 2), - "I": (_ENDIAN + 'i4', None), - "F": (_ENDIAN + 'f4', None), - "P": ('|u1', None), - "RGB": ('|u1', 3), - "RGBX": ('|u1', 4), - "RGBA": ('|u1', 4), - "CMYK": ('|u1', 4), - "YCbCr": ('|u1', 3), - "LAB": ('|u1', 3), # UNDONE - unsigned |u1i1i1 - "HSV": ('|u1', 3), + "1": ("|b1", None), # Bits need to be extended to bytes + "L": ("|u1", None), + "LA": ("|u1", 2), + "I": (_ENDIAN + "i4", None), + "F": (_ENDIAN + "f4", None), + "P": ("|u1", None), + "RGB": ("|u1", 3), + "RGBX": ("|u1", 4), + "RGBA": ("|u1", 4), + "CMYK": ("|u1", 4), + "YCbCr": ("|u1", 3), + "LAB": ("|u1", 3), # UNDONE - unsigned |u1i1i1 + "HSV": ("|u1", 3), # I;16 == I;16L, and I;32 == I;32L - "I;16": ('u2', None), - "I;16L": ('i2', None), - "I;16LS": ('u4', None), - "I;32L": ('i4', None), - "I;32LS": ('u2", None), + "I;16L": ("i2", None), + "I;16LS": ("u4", None), + "I;32L": ("i4", None), + "I;32LS": ("= 3: + def __del__(self): self.__exit__() @@ -632,9 +648,9 @@ class Image(object): def _dump(self, file=None, format=None, **options): import tempfile - suffix = '' + suffix = "" if format: - suffix = '.'+format + suffix = "." + format if not file: f, filename = tempfile.mkstemp(suffix) @@ -654,25 +670,30 @@ class Image(object): return filename def __eq__(self, other): - return (self.__class__ is other.__class__ and - self.mode == other.mode and - self.size == other.size and - self.info == other.info and - self.category == other.category and - self.readonly == other.readonly and - self.getpalette() == other.getpalette() and - self.tobytes() == other.tobytes()) + return ( + self.__class__ is other.__class__ + and self.mode == other.mode + and self.size == other.size + and self.info == other.info + and self.category == other.category + and self.readonly == other.readonly + and self.getpalette() == other.getpalette() + and self.tobytes() == other.tobytes() + ) def __ne__(self, other): - eq = (self == other) + eq = self == other return not eq def __repr__(self): return "<%s.%s image mode=%s size=%dx%d at 0x%X>" % ( - self.__class__.__module__, self.__class__.__name__, - self.mode, self.size[0], self.size[1], - id(self) - ) + self.__class__.__module__, + self.__class__.__name__, + self.mode, + self.size[0], + self.size[1], + id(self), + ) def _repr_png_(self): """ iPython display hook support @@ -680,7 +701,7 @@ class Image(object): :returns: png version of the image as bytes """ b = io.BytesIO() - self.save(b, 'PNG') + self.save(b, "PNG") return b.getvalue() @property @@ -688,24 +709,19 @@ class Image(object): # numpy array interface support new = {} shape, typestr = _conv_type_shape(self) - new['shape'] = shape - new['typestr'] = typestr - new['version'] = 3 - if self.mode == '1': + new["shape"] = shape + new["typestr"] = typestr + new["version"] = 3 + if self.mode == "1": # Binary images need to be extended from bits to bytes # See: https://github.com/python-pillow/Pillow/issues/350 - new['data'] = self.tobytes('raw', 'L') + new["data"] = self.tobytes("raw", "L") else: - new['data'] = self.tobytes() + new["data"] = self.tobytes() return new def __getstate__(self): - return [ - self.info, - self.mode, - self.size, - self.getpalette(), - self.tobytes()] + return [self.info, self.mode, self.size, self.getpalette(), self.tobytes()] def __setstate__(self, state): Image.__init__(self) @@ -715,7 +731,7 @@ class Image(object): self.mode = mode self._size = size self.im = core.new(mode, size) - if mode in ("L", "P") and palette: + if mode in ("L", "LA", "P", "PA") and palette: self.putpalette(palette) self.frombytes(data) @@ -763,8 +779,9 @@ class Image(object): return b"".join(data) def tostring(self, *args, **kw): - raise NotImplementedError("tostring() has been removed. " - "Please call tobytes() instead.") + raise NotImplementedError( + "tostring() has been removed. Please call tobytes() instead." + ) def tobitmap(self, name="image"): """ @@ -781,11 +798,15 @@ class Image(object): if self.mode != "1": raise ValueError("not a bitmap") data = self.tobytes("xbm") - return b"".join([ - ("#define %s_width %d\n" % (name, self.size[0])).encode('ascii'), - ("#define %s_height %d\n" % (name, self.size[1])).encode('ascii'), - ("static char %s_bits[] = {\n" % name).encode('ascii'), data, b"};" - ]) + return b"".join( + [ + ("#define %s_width %d\n" % (name, self.size[0])).encode("ascii"), + ("#define %s_height %d\n" % (name, self.size[1])).encode("ascii"), + ("static char %s_bits[] = {\n" % name).encode("ascii"), + data, + b"};", + ] + ) def frombytes(self, data, decoder_name="raw", *args): """ @@ -814,8 +835,9 @@ class Image(object): raise ValueError("cannot decode image data") def fromstring(self, *args, **kw): - raise NotImplementedError("fromstring() has been removed. " - "Please call frombytes() instead.") + raise NotImplementedError( + "fromstring() has been removed. Please call frombytes() instead." + ) def load(self): """ @@ -850,6 +872,7 @@ class Image(object): if self.pyaccess: return self.pyaccess from . import PyAccess + self.pyaccess = PyAccess.new(self, self.readonly) if self.pyaccess: return self.pyaccess @@ -866,8 +889,7 @@ class Image(object): """ pass - def convert(self, mode=None, matrix=None, dither=None, - palette=WEB, colors=256): + def convert(self, mode=None, matrix=None, dither=None, palette=WEB, colors=256): """ Returns a converted copy of this image. For the "P" mode, this method translates pixels through the palette. If mode is @@ -920,7 +942,7 @@ class Image(object): if not mode or (mode == self.mode and not matrix): return self.copy() - has_transparency = self.info.get('transparency') is not None + has_transparency = self.info.get("transparency") is not None if matrix: # matrix conversion if mode not in ("L", "RGB"): @@ -928,19 +950,24 @@ class Image(object): im = self.im.convert_matrix(mode, matrix) new = self._new(im) if has_transparency and self.im.bands == 3: - transparency = new.info['transparency'] + transparency = new.info["transparency"] def convert_transparency(m, v): - v = m[0]*v[0] + m[1]*v[1] + m[2]*v[2] + m[3]*0.5 + v = m[0] * v[0] + m[1] * v[1] + m[2] * v[2] + m[3] * 0.5 return max(0, min(255, int(v))) + if mode == "L": transparency = convert_transparency(matrix, transparency) elif len(mode) == 3: - transparency = tuple([ - convert_transparency(matrix[i*4:i*4+4], transparency) - for i in range(0, len(transparency)) - ]) - new.info['transparency'] = transparency + transparency = tuple( + [ + convert_transparency( + matrix[i * 4 : i * 4 + 4], transparency + ) + for i in range(0, len(transparency)) + ] + ) + new.info["transparency"] = transparency return new if mode == "P" and self.mode == "RGBA": @@ -950,45 +977,48 @@ class Image(object): delete_trns = False # transparency handling if has_transparency: - if self.mode in ('1', 'L', 'I', 'RGB') and mode == 'RGBA': + if self.mode in ("1", "L", "I", "RGB") and mode == "RGBA": # Use transparent conversion to promote from transparent # color to an alpha channel. - new_im = self._new(self.im.convert_transparent( - mode, self.info['transparency'])) - del new_im.info['transparency'] + new_im = self._new( + self.im.convert_transparent(mode, self.info["transparency"]) + ) + del new_im.info["transparency"] return new_im - elif self.mode in ('L', 'RGB', 'P') and mode in ('L', 'RGB', 'P'): - t = self.info['transparency'] + elif self.mode in ("L", "RGB", "P") and mode in ("L", "RGB", "P"): + t = self.info["transparency"] if isinstance(t, bytes): # Dragons. This can't be represented by a single color - warnings.warn('Palette images with Transparency ' + - ' expressed in bytes should be converted ' + - 'to RGBA images') + warnings.warn( + "Palette images with Transparency expressed in bytes should be " + "converted to RGBA images" + ) delete_trns = True else: # get the new transparency color. # use existing conversions trns_im = Image()._new(core.new(self.mode, (1, 1))) - if self.mode == 'P': + if self.mode == "P": trns_im.putpalette(self.palette) if isinstance(t, tuple): try: t = trns_im.palette.getcolor(t) except Exception: - raise ValueError("Couldn't allocate a palette " - "color for transparency") + raise ValueError( + "Couldn't allocate a palette color for transparency" + ) trns_im.putpixel((0, 0), t) - if mode in ('L', 'RGB'): + if mode in ("L", "RGB"): trns_im = trns_im.convert(mode) else: # can't just retrieve the palette number, got to do it # after quantization. - trns_im = trns_im.convert('RGB') + trns_im = trns_im.convert("RGB") trns = trns_im.getpixel((0, 0)) - elif self.mode == 'P' and mode == 'RGBA': - t = self.info['transparency'] + elif self.mode == "P" and mode == "RGBA": + t = self.info["transparency"] delete_trns = True if isinstance(t, bytes): @@ -996,27 +1026,26 @@ class Image(object): elif isinstance(t, int): self.im.putpalettealpha(t, 0) else: - raise ValueError("Transparency for P mode should" + - " be bytes or int") + raise ValueError("Transparency for P mode should be bytes or int") if mode == "P" and palette == ADAPTIVE: im = self.im.quantize(colors) new = self._new(im) from . import ImagePalette + new.palette = ImagePalette.raw("RGB", new.im.getpalette("RGB")) if delete_trns: # This could possibly happen if we requantize to fewer colors. # The transparency would be totally off in that case. - del new.info['transparency'] + del new.info["transparency"] if trns is not None: try: - new.info['transparency'] = new.palette.getcolor(trns) + new.info["transparency"] = new.palette.getcolor(trns) except Exception: # if we can't make a transparent color, don't leave the old # transparency hanging around to mess us up. - del new.info['transparency'] - warnings.warn("Couldn't allocate palette entry " + - "for transparency") + del new.info["transparency"] + warnings.warn("Couldn't allocate palette entry for transparency") return new # colorspace conversion @@ -1036,17 +1065,16 @@ class Image(object): new_im = self._new(im) if delete_trns: # crash fail if we leave a bytes transparency in an rgb/l mode. - del new_im.info['transparency'] + del new_im.info["transparency"] if trns is not None: - if new_im.mode == 'P': + if new_im.mode == "P": try: - new_im.info['transparency'] = new_im.palette.getcolor(trns) + new_im.info["transparency"] = new_im.palette.getcolor(trns) except Exception: - del new_im.info['transparency'] - warnings.warn("Couldn't allocate palette entry " + - "for transparency") + del new_im.info["transparency"] + warnings.warn("Couldn't allocate palette entry for transparency") else: - new_im.info['transparency'] = trns + new_im.info["transparency"] = trns return new_im def quantize(self, colors=256, method=None, kmeans=0, palette=None, dither=1): @@ -1075,14 +1103,15 @@ class Image(object): if method is None: # defaults: method = 0 - if self.mode == 'RGBA': + if self.mode == "RGBA": method = 2 - if self.mode == 'RGBA' and method not in (2, 3): + if self.mode == "RGBA" and method not in (2, 3): # Caller specified an invalid mode. raise ValueError( - 'Fast Octree (method == 2) and libimagequant (method == 3) ' + - 'are the only valid methods for quantizing RGBA images') + "Fast Octree (method == 2) and libimagequant (method == 3) " + "are the only valid methods for quantizing RGBA images" + ) if palette: # use palette from reference image @@ -1092,13 +1121,14 @@ class Image(object): if self.mode != "RGB" and self.mode != "L": raise ValueError( "only RGB or L mode images can be quantized to a palette" - ) + ) im = self.im.convert("P", dither, palette.im) return self._new(im) im = self._new(self.im.quantize(colors, method, kmeans)) from . import ImagePalette + mode = im.im.getpalettemode() im.palette = ImagePalette.ImagePalette(mode, im.im.getpalette(mode, mode)) @@ -1197,8 +1227,9 @@ class Image(object): if isinstance(filter, Callable): filter = filter() if not hasattr(filter, "filter"): - raise TypeError("filter argument should be ImageFilter.Filter " + - "instance or class") + raise TypeError( + "filter argument should be ImageFilter.Filter instance or class" + ) multiband = isinstance(filter, ImageFilter.MultibandFilter) if self.im.bands == 1 or multiband: @@ -1374,6 +1405,7 @@ class Image(object): bi-level image (mode "1") or a greyscale image ("L"). :param mask: An optional mask. + :param extrema: An optional tuple of manually-specified extrema. :returns: A list containing pixel counts. """ self.load() @@ -1386,9 +1418,36 @@ class Image(object): return self.im.histogram(extrema) return self.im.histogram() + def entropy(self, mask=None, extrema=None): + """ + Calculates and returns the entropy for the image. + + A bilevel image (mode "1") is treated as a greyscale ("L") + image by this method. + + If a mask is provided, the method employs the histogram for + those parts of the image where the mask image is non-zero. + The mask image must have the same size as the image, and be + either a bi-level image (mode "1") or a greyscale image ("L"). + + :param mask: An optional mask. + :param extrema: An optional tuple of manually-specified extrema. + :returns: A float value representing the image entropy + """ + self.load() + if mask: + mask.load() + return self.im.entropy((0, 0), mask.im) + if self.mode in ("I", "F"): + if extrema is None: + extrema = self.getextrema() + return self.im.entropy(extrema) + return self.im.entropy() + def offset(self, xoffset, yoffset=None): - raise NotImplementedError("offset() has been removed. " - "Please call ImageChops.offset() instead.") + raise NotImplementedError( + "offset() has been removed. Please call ImageChops.offset() instead." + ) def paste(self, im, box=None, mask=None): """ @@ -1446,13 +1505,12 @@ class Image(object): size = mask.size else: # FIXME: use self.size here? - raise ValueError( - "cannot determine region size; use 4-item box" - ) - box += (box[0]+size[0], box[1]+size[1]) + raise ValueError("cannot determine region size; use 4-item box") + box += (box[0] + size[0], box[1] + size[1]) if isStringType(im): from . import ImageColor + im = ImageColor.getcolor(im, self.mode) elif isImageType(im): @@ -1689,8 +1747,11 @@ class Image(object): if self.pyaccess: return self.pyaccess.putpixel(xy, value) - if self.mode == "P" and \ - isinstance(value, (list, tuple)) and len(value) in [3, 4]: + if ( + self.mode == "P" + and isinstance(value, (list, tuple)) + and len(value) in [3, 4] + ): # RGB or RGBA value for a P image value = self.palette.getcolor(value) return self.im.putpixel(xy, value) @@ -1715,16 +1776,16 @@ class Image(object): if self.mode == "P": real_source_palette = self.im.getpalette("RGB")[:768] else: # L-mode - real_source_palette = bytearray(i//3 for i in range(768)) + real_source_palette = bytearray(i // 3 for i in range(768)) else: real_source_palette = source_palette palette_bytes = b"" - new_positions = [0]*256 + new_positions = [0] * 256 # pick only the used colors from the palette for i, oldPosition in enumerate(dest_map): - palette_bytes += real_source_palette[oldPosition*3:oldPosition*3+3] + palette_bytes += real_source_palette[oldPosition * 3 : oldPosition * 3 + 3] new_positions[oldPosition] = i # replace the palette color id of all pixel with the new id @@ -1748,26 +1809,25 @@ class Image(object): mapping_palette = bytearray(new_positions) m_im = self.copy() - m_im.mode = 'P' + m_im.mode = "P" - m_im.palette = ImagePalette.ImagePalette("RGB", - palette=mapping_palette*3, - size=768) + m_im.palette = ImagePalette.ImagePalette( + "RGB", palette=mapping_palette * 3, size=768 + ) # possibly set palette dirty, then # m_im.putpalette(mapping_palette, 'L') # converts to 'P' # or just force it. # UNDONE -- this is part of the general issue with palettes m_im.im.putpalette(*m_im.palette.getdata()) - m_im = m_im.convert('L') + m_im = m_im.convert("L") # Internally, we require 768 bytes for a palette. - new_palette_bytes = (palette_bytes + - (768 - len(palette_bytes)) * b'\x00') + new_palette_bytes = palette_bytes + (768 - len(palette_bytes)) * b"\x00" m_im.putpalette(new_palette_bytes) - m_im.palette = ImagePalette.ImagePalette("RGB", - palette=palette_bytes, - size=len(palette_bytes)) + m_im.palette = ImagePalette.ImagePalette( + "RGB", palette=palette_bytes, size=len(palette_bytes) + ) return m_im @@ -1791,10 +1851,23 @@ class Image(object): :returns: An :py:class:`~PIL.Image.Image` object. """ - if resample not in ( - NEAREST, BILINEAR, BICUBIC, LANCZOS, BOX, HAMMING, - ): - raise ValueError("unknown resampling filter") + if resample not in (NEAREST, BILINEAR, BICUBIC, LANCZOS, BOX, HAMMING): + message = "Unknown resampling filter ({}).".format(resample) + + filters = [ + "{} ({})".format(filter[1], filter[0]) + for filter in ( + (NEAREST, "Image.NEAREST"), + (LANCZOS, "Image.LANCZOS"), + (BILINEAR, "Image.BILINEAR"), + (BICUBIC, "Image.BICUBIC"), + (BOX, "Image.BOX"), + (HAMMING, "Image.HAMMING"), + ) + ] + raise ValueError( + message + " Use " + ", ".join(filters[:-1]) + " or " + filters[-1] + ) size = tuple(size) @@ -1809,8 +1882,8 @@ class Image(object): if self.mode in ("1", "P"): resample = NEAREST - if self.mode in ['LA', 'RGBA']: - im = self.convert(self.mode[:-1]+'a') + if self.mode in ["LA", "RGBA"]: + im = self.convert(self.mode[:-1] + "a") im = im.resize(size, resample, box) return im.convert(self.mode) @@ -1818,8 +1891,15 @@ class Image(object): return self._new(self.im.resize(size, resample, box)) - def rotate(self, angle, resample=NEAREST, expand=0, center=None, - translate=None, fillcolor=None): + def rotate( + self, + angle, + resample=NEAREST, + expand=0, + center=None, + translate=None, + fillcolor=None, + ): """ Returns a rotated copy of this image. This method returns a copy of this image, rotated the given number of degrees counter @@ -1889,19 +1969,23 @@ class Image(object): else: rotn_center = center - angle = - math.radians(angle) + angle = -math.radians(angle) matrix = [ - round(math.cos(angle), 15), round(math.sin(angle), 15), 0.0, - round(-math.sin(angle), 15), round(math.cos(angle), 15), 0.0 + round(math.cos(angle), 15), + round(math.sin(angle), 15), + 0.0, + round(-math.sin(angle), 15), + round(math.cos(angle), 15), + 0.0, ] def transform(x, y, matrix): (a, b, c, d, e, f) = matrix - return a*x + b*y + c, d*x + e*y + f + return a * x + b * y + c, d * x + e * y + f - matrix[2], matrix[5] = transform(-rotn_center[0] - post_trans[0], - -rotn_center[1] - post_trans[1], - matrix) + matrix[2], matrix[5] = transform( + -rotn_center[0] - post_trans[0], -rotn_center[1] - post_trans[1], matrix + ) matrix[2] += rotn_center[0] matrix[5] += rotn_center[1] @@ -1919,13 +2003,10 @@ class Image(object): # We multiply a translation matrix from the right. Because of its # special form, this is the same as taking the image of the # translation vector as new translation vector. - matrix[2], matrix[5] = transform(-(nw - w) / 2.0, - -(nh - h) / 2.0, - matrix) + matrix[2], matrix[5] = transform(-(nw - w) / 2.0, -(nh - h) / 2.0, matrix) w, h = nw, nh - return self.transform((w, h), AFFINE, matrix, resample, - fillcolor=fillcolor) + return self.transform((w, h), AFFINE, matrix, resample, fillcolor=fillcolor) def save(self, fp, format=None, **params): """ @@ -1972,7 +2053,7 @@ class Image(object): # may mutate self! self._ensure_mutable() - save_all = params.pop('save_all', False) + save_all = params.pop("save_all", False) self.encoderinfo = params self.encoderconfig = () @@ -1986,7 +2067,7 @@ class Image(object): try: format = EXTENSION[ext] except KeyError: - raise ValueError('unknown file extension: {}'.format(ext)) + raise ValueError("unknown file extension: {}".format(ext)) if format.upper() not in SAVE: init() @@ -1996,7 +2077,7 @@ class Image(object): save_handler = SAVE[format.upper()] if open_fp: - if params.get('append', False): + if params.get("append", False): fp = builtins.open(filename, "r+b") else: # Open also for reading ("+"), because TIFF save_all @@ -2088,8 +2169,7 @@ class Image(object): try: channel = self.getbands().index(channel) except ValueError: - raise ValueError( - 'The image has no channel "{}"'.format(channel)) + raise ValueError('The image has no channel "{}"'.format(channel)) return self._new(self.im.getband(channel)) @@ -2151,8 +2231,9 @@ class Image(object): # FIXME: the different transform methods need further explanation # instead of bloating the method docs, add a separate chapter. - def transform(self, size, method, data=None, resample=NEAREST, - fill=1, fillcolor=None): + def transform( + self, size, method, data=None, resample=NEAREST, fill=1, fillcolor=None + ): """ Transforms this image. This method creates a new image with the given size, and the same mode as the original, and copies data @@ -2195,13 +2276,19 @@ class Image(object): :returns: An :py:class:`~PIL.Image.Image` object. """ - if self.mode == 'LA': - return self.convert('La').transform( - size, method, data, resample, fill, fillcolor).convert('LA') + if self.mode == "LA": + return ( + self.convert("La") + .transform(size, method, data, resample, fill, fillcolor) + .convert("LA") + ) - if self.mode == 'RGBA': - return self.convert('RGBa').transform( - size, method, data, resample, fill, fillcolor).convert('RGBA') + if self.mode == "RGBA": + return ( + self.convert("RGBa") + .transform(size, method, data, resample, fill, fillcolor) + .convert("RGBA") + ) if isinstance(method, ImageTransformHandler): return method.transform(size, self, resample=resample, fill=fill) @@ -2217,16 +2304,15 @@ class Image(object): if method == MESH: # list of quads for box, quad in data: - im.__transformer(box, self, QUAD, quad, resample, - fillcolor is None) + im.__transformer(box, self, QUAD, quad, resample, fillcolor is None) else: - im.__transformer((0, 0)+size, self, method, data, - resample, fillcolor is None) + im.__transformer( + (0, 0) + size, self, method, data, resample, fillcolor is None + ) return im - def __transformer(self, box, image, method, data, - resample=NEAREST, fill=1): + def __transformer(self, box, image, method, data, resample=NEAREST, fill=1): w = box[2] - box[0] h = box[3] - box[1] @@ -2254,16 +2340,41 @@ class Image(object): x0, y0 = nw As = 1.0 / w At = 1.0 / h - data = (x0, (ne[0]-x0)*As, (sw[0]-x0)*At, - (se[0]-sw[0]-ne[0]+x0)*As*At, - y0, (ne[1]-y0)*As, (sw[1]-y0)*At, - (se[1]-sw[1]-ne[1]+y0)*As*At) + data = ( + x0, + (ne[0] - x0) * As, + (sw[0] - x0) * At, + (se[0] - sw[0] - ne[0] + x0) * As * At, + y0, + (ne[1] - y0) * As, + (sw[1] - y0) * At, + (se[1] - sw[1] - ne[1] + y0) * As * At, + ) else: raise ValueError("unknown transformation method") if resample not in (NEAREST, BILINEAR, BICUBIC): - raise ValueError("unknown resampling filter") + if resample in (BOX, HAMMING, LANCZOS): + message = { + BOX: "Image.BOX", + HAMMING: "Image.HAMMING", + LANCZOS: "Image.LANCZOS/Image.ANTIALIAS", + }[resample] + " ({}) cannot be used.".format(resample) + else: + message = "Unknown resampling filter ({}).".format(resample) + + filters = [ + "{} ({})".format(filter[1], filter[0]) + for filter in ( + (NEAREST, "Image.NEAREST"), + (BILINEAR, "Image.BILINEAR"), + (BICUBIC, "Image.BICUBIC"), + ) + ] + raise ValueError( + message + " Use " + ", ".join(filters[:-1]) + " or " + filters[-1] + ) image.load() @@ -2300,6 +2411,7 @@ class Image(object): def toqimage(self): """Returns a QImage copy of this image""" from . import ImageQt + if not ImageQt.qt_is_installed: raise ImportError("Qt bindings are not installed") return ImageQt.toqimage(self) @@ -2307,6 +2419,7 @@ class Image(object): def toqpixmap(self): """Returns a QPixmap copy of this image""" from . import ImageQt + if not ImageQt.qt_is_installed: raise ImportError("Qt bindings are not installed") return ImageQt.toqpixmap(self) @@ -2315,6 +2428,7 @@ class Image(object): # -------------------------------------------------------------------- # Abstract handlers. + class ImagePointHandler(object): # used as a mixin by point transforms (for use with im.point) pass @@ -2331,6 +2445,7 @@ class ImageTransformHandler(object): # # Debugging + def _wedge(): """Create greyscale wedge (for debugging only)""" @@ -2381,13 +2496,14 @@ def new(mode, size, color=0): # css3-style specifier from . import ImageColor + color = ImageColor.getcolor(color, mode) im = Image() - if mode == "P" and \ - isinstance(color, (list, tuple)) and len(color) in [3, 4]: + if mode == "P" and isinstance(color, (list, tuple)) and len(color) in [3, 4]: # RGB or RGBA value for a P image from . import ImagePalette + im.palette = ImagePalette.ImagePalette() color = im.palette.getcolor(color) return im._new(core.fill(mode, size, color)) @@ -2432,8 +2548,9 @@ def frombytes(mode, size, data, decoder_name="raw", *args): def fromstring(*args, **kw): - raise NotImplementedError("fromstring() has been removed. " + - "Please call frombytes() instead.") + raise NotImplementedError( + "fromstring() has been removed. Please call frombytes() instead." + ) def frombuffer(mode, size, data, decoder_name="raw", *args): @@ -2483,14 +2600,13 @@ def frombuffer(mode, size, data, decoder_name="raw", *args): "the frombuffer defaults may change in a future release; " "for portability, change the call to read:\n" " frombuffer(mode, size, data, 'raw', mode, 0, 1)", - RuntimeWarning, stacklevel=2 + RuntimeWarning, + stacklevel=2, ) args = mode, 0, -1 # may change to (mode, 0, 1) post-1.1.6 if args[0] in _MAPMODES: im = new(mode, (1, 1)) - im = im._new( - core.map_buffer(data, size, decoder_name, None, 0, args) - ) + im = im._new(core.map_buffer(data, size, decoder_name, None, 0, args)) im.readonly = 1 return im @@ -2524,12 +2640,12 @@ def fromarray(obj, mode=None): .. versionadded:: 1.1.6 """ arr = obj.__array_interface__ - shape = arr['shape'] + shape = arr["shape"] ndim = len(shape) - strides = arr.get('strides', None) + strides = arr.get("strides", None) if mode is None: try: - typekey = (1, 1) + shape[2:], arr['typestr'] + typekey = (1, 1) + shape[2:], arr["typestr"] mode, rawmode = _fromarray_typemap[typekey] except KeyError: raise TypeError("Cannot handle this data type") @@ -2546,7 +2662,7 @@ def fromarray(obj, mode=None): size = shape[1], shape[0] if strides is not None: - if hasattr(obj, 'tobytes'): + if hasattr(obj, "tobytes"): obj = obj.tobytes() else: obj = obj.tostring() @@ -2557,6 +2673,7 @@ def fromarray(obj, mode=None): def fromqimage(im): """Creates an image instance from a QImage image""" from . import ImageQt + if not ImageQt.qt_is_installed: raise ImportError("Qt bindings are not installed") return ImageQt.fromqimage(im) @@ -2565,6 +2682,7 @@ def fromqimage(im): def fromqpixmap(im): """Creates an image instance from a QPixmap image""" from . import ImageQt + if not ImageQt.qt_is_installed: raise ImportError("Qt bindings are not installed") return ImageQt.fromqpixmap(im) @@ -2591,7 +2709,7 @@ _fromarray_typemap = { ((1, 1, 2), "|u1"): ("LA", "LA"), ((1, 1, 3), "|u1"): ("RGB", "RGB"), ((1, 1, 4), "|u1"): ("RGBA", "RGBA"), - } +} # shortcuts _fromarray_typemap[((1, 1), _ENDIAN + "i4")] = ("I", "I") @@ -2607,15 +2725,15 @@ def _decompression_bomb_check(size): if pixels > 2 * MAX_IMAGE_PIXELS: raise DecompressionBombError( "Image size (%d pixels) exceeds limit of %d pixels, " - "could be decompression bomb DOS attack." % - (pixels, 2 * MAX_IMAGE_PIXELS)) + "could be decompression bomb DOS attack." % (pixels, 2 * MAX_IMAGE_PIXELS) + ) if pixels > MAX_IMAGE_PIXELS: warnings.warn( "Image size (%d pixels) exceeds limit of %d pixels, " - "could be decompression bomb DOS attack." % - (pixels, MAX_IMAGE_PIXELS), - DecompressionBombWarning) + "could be decompression bomb DOS attack." % (pixels, MAX_IMAGE_PIXELS), + DecompressionBombWarning, + ) def open(fp, mode="r"): @@ -2643,10 +2761,10 @@ def open(fp, mode="r"): exclusive_fp = False filename = "" - if isPath(fp): - filename = fp - elif HAS_PATHLIB and isinstance(fp, Path): + if HAS_PATHLIB and isinstance(fp, Path): filename = str(fp.resolve()) + elif isPath(fp): + filename = fp if filename: fp = builtins.open(filename, "rb") @@ -2701,8 +2819,8 @@ def open(fp, mode="r"): fp.close() for message in accept_warnings: warnings.warn(message) - raise IOError("cannot identify image file %r" - % (filename if filename else fp)) + raise IOError("cannot identify image file %r" % (filename if filename else fp)) + # # Image processing. @@ -2806,6 +2924,7 @@ def merge(mode, bands): # -------------------------------------------------------------------- # Plugin registry + def register_open(id, factory, accept=None): """ Register an image file plugin. This function should not be used @@ -2919,6 +3038,7 @@ def register_encoder(name, encoder): # -------------------------------------------------------------------- # Simple display support. User code may override this. + def _show(image, **options): # override me, as necessary _showxv(image, **options) @@ -2926,12 +3046,14 @@ def _show(image, **options): def _showxv(image, title=None, **options): from . import ImageShow + ImageShow.show(image, title, **options) # -------------------------------------------------------------------- # Effects + def effect_mandelbrot(size, extent, quality): """ Generate a Mandelbrot set covering the given extent. @@ -2977,14 +3099,15 @@ def radial_gradient(mode): # -------------------------------------------------------------------- # Resources + def _apply_env_variables(env=None): if env is None: env = os.environ for var_name, setter in [ - ('PILLOW_ALIGNMENT', core.set_alignment), - ('PILLOW_BLOCK_SIZE', core.set_block_size), - ('PILLOW_BLOCKS_MAX', core.set_blocks_max), + ("PILLOW_ALIGNMENT", core.set_alignment), + ("PILLOW_BLOCK_SIZE", core.set_block_size), + ("PILLOW_BLOCKS_MAX", core.set_blocks_max), ]: if var_name not in env: continue @@ -2992,21 +3115,21 @@ def _apply_env_variables(env=None): var = env[var_name].lower() units = 1 - for postfix, mul in [('k', 1024), ('m', 1024*1024)]: + for postfix, mul in [("k", 1024), ("m", 1024 * 1024)]: if var.endswith(postfix): units = mul - var = var[:-len(postfix)] + var = var[: -len(postfix)] try: var = int(var) * units except ValueError: - warnings.warn("{0} is not int".format(var_name)) + warnings.warn("{} is not int".format(var_name)) continue try: setter(var) except ValueError as e: - warnings.warn("{0}: {1}".format(var_name, e)) + warnings.warn("{}: {}".format(var_name, e)) _apply_env_variables() @@ -3042,6 +3165,7 @@ class Exif(MutableMapping): pass else: from . import TiffImagePlugin + info = TiffImagePlugin.ImageFileDirectory_v1(self.head) info.load(self.fp) return self._fixup_dict(info) @@ -3057,6 +3181,7 @@ class Exif(MutableMapping): self.head = self.fp.read(8) # process dictionary from . import TiffImagePlugin + info = TiffImagePlugin.ImageFileDirectory_v1(self.head) self.endian = info._endian self.fp.seek(info.next) @@ -3077,6 +3202,7 @@ class Exif(MutableMapping): def tobytes(self, offset=0): from . import TiffImagePlugin + if self.endian == "<": head = b"II\x2A\x00\x08\x00\x00\x00" else: @@ -3084,86 +3210,89 @@ class Exif(MutableMapping): ifd = TiffImagePlugin.ImageFileDirectory_v2(ifh=head) for tag, value in self._data.items(): ifd[tag] = value - return b"Exif\x00\x00"+head+ifd.tobytes(offset) + return b"Exif\x00\x00" + head + ifd.tobytes(offset) def get_ifd(self, tag): if tag not in self._ifds and tag in self._data: - if tag == 0xa005: # interop + if tag == 0xA005: # interop self._ifds[tag] = self._get_ifd_dict(tag) - elif tag == 0x927c: # makernote - from . import TiffImagePlugin - if self._data[0x927c][:8] == b"FUJIFILM": - exif_data = self._data[0x927c] + elif tag == 0x927C: # makernote + from .TiffImagePlugin import ImageFileDirectory_v2 + + if self._data[0x927C][:8] == b"FUJIFILM": + exif_data = self._data[0x927C] ifd_offset = i32le(exif_data[8:12]) ifd_data = exif_data[ifd_offset:] makernote = {} for i in range(0, struct.unpack(" 4: offset, = struct.unpack("H", ifd_data[:2])[0]): ifd_tag, typ, count, data = struct.unpack( - ">HHL4s", ifd_data[i*12 + 2:(i+1)*12 + 2]) + ">HHL4s", ifd_data[i * 12 + 2 : (i + 1) * 12 + 2] + ) if ifd_tag == 0x1101: # CameraInfo offset, = struct.unpack(">L", data) self.fp.seek(offset) - camerainfo = {'ModelID': self.fp.read(4)} + camerainfo = {"ModelID": self.fp.read(4)} self.fp.read(4) # Seconds since 2000 - camerainfo['TimeStamp'] = i32le(self.fp.read(12)) + camerainfo["TimeStamp"] = i32le(self.fp.read(12)) self.fp.read(4) - camerainfo['InternalSerialNumber'] = self.fp.read(4) + camerainfo["InternalSerialNumber"] = self.fp.read(4) self.fp.read(12) parallax = self.fp.read(4) - handler =\ - TiffImagePlugin.ImageFileDirectory_v2._load_dispatch[ - TiffTags.FLOAT - ][1] - camerainfo['Parallax'] = handler( - TiffImagePlugin.ImageFileDirectory_v2(), - parallax, False) + handler = ImageFileDirectory_v2._load_dispatch[ + TiffTags.FLOAT + ][1] + camerainfo["Parallax"] = handler( + ImageFileDirectory_v2(), parallax, False + ) self.fp.read(4) - camerainfo['Category'] = self.fp.read(2) + camerainfo["Category"] = self.fp.read(2) makernote = {0x1101: dict(self._fixup_dict(camerainfo))} - self._ifds[0x927c] = makernote + self._ifds[0x927C] = makernote return self._ifds.get(tag, {}) def __str__(self): @@ -3179,6 +3308,7 @@ class Exif(MutableMapping): return tag in self._data if not py3: + def has_key(self, tag): return tag in self diff --git a/src/PIL/ImageCms.py b/src/PIL/ImageCms.py index c75ad6a66..647a673f5 100644 --- a/src/PIL/ImageCms.py +++ b/src/PIL/ImageCms.py @@ -19,12 +19,14 @@ from __future__ import print_function import sys from PIL import Image + try: from PIL import _imagingcms except ImportError as ex: # Allow error import for doc purposes, but error out when accessing # anything in core. - from _util import deferred_error + from ._util import deferred_error + _imagingcms = deferred_error(ex) from PIL._util import isStringType @@ -132,7 +134,7 @@ FLAGS = { "SOFTPROOFING": 16384, # Do softproofing "PRESERVEBLACK": 32768, # Black preservation "NODEFAULTRESOURCEDEF": 16777216, # CRD special - "GRIDPOINTS": lambda n: ((n) & 0xFF) << 16 # Gridpoints + "GRIDPOINTS": lambda n: ((n) & 0xFF) << 16, # Gridpoints } _MAX_FLAG = 0 @@ -148,8 +150,8 @@ for flag in FLAGS.values(): ## # Profile. -class ImageCmsProfile(object): +class ImageCmsProfile(object): def __init__(self, profile): """ :param profile: Either a string representing a filename, @@ -197,22 +199,31 @@ class ImageCmsTransform(Image.ImagePointHandler): Will return the output profile in the output.info['icc_profile']. """ - def __init__(self, input, output, input_mode, output_mode, - intent=INTENT_PERCEPTUAL, proof=None, - proof_intent=INTENT_ABSOLUTE_COLORIMETRIC, flags=0): + def __init__( + self, + input, + output, + input_mode, + output_mode, + intent=INTENT_PERCEPTUAL, + proof=None, + proof_intent=INTENT_ABSOLUTE_COLORIMETRIC, + flags=0, + ): if proof is None: self.transform = core.buildTransform( - input.profile, output.profile, - input_mode, output_mode, - intent, - flags + input.profile, output.profile, input_mode, output_mode, intent, flags ) else: self.transform = core.buildProofTransform( - input.profile, output.profile, proof.profile, - input_mode, output_mode, - intent, proof_intent, - flags + input.profile, + output.profile, + proof.profile, + input_mode, + output_mode, + intent, + proof_intent, + flags, ) # Note: inputMode and outputMode are for pyCMS compatibility only self.input_mode = self.inputMode = input_mode @@ -228,7 +239,7 @@ class ImageCmsTransform(Image.ImagePointHandler): if imOut is None: imOut = Image.new(self.output_mode, im.size, None) self.transform.apply(im.im.id, imOut.im.id) - imOut.info['icc_profile'] = self.output_profile.tobytes() + imOut.info["icc_profile"] = self.output_profile.tobytes() return imOut def apply_in_place(self, im): @@ -236,7 +247,7 @@ class ImageCmsTransform(Image.ImagePointHandler): if im.mode != self.output_mode: raise ValueError("mode mismatch") # wrong output mode self.transform.apply(im.im.id, im.im.id) - im.info['icc_profile'] = self.output_profile.tobytes() + im.info["icc_profile"] = self.output_profile.tobytes() return im @@ -247,6 +258,7 @@ def get_display_profile(handle=None): if sys.platform == "win32": from PIL import ImageWin + if isinstance(handle, ImageWin.HDC): profile = core.get_display_profile_win32(handle, 1) else: @@ -265,16 +277,24 @@ def get_display_profile(handle=None): # pyCMS compatible layer # --------------------------------------------------------------------. + class PyCMSError(Exception): """ (pyCMS) Exception class. This is used for all errors in the pyCMS API. """ + pass def profileToProfile( - im, inputProfile, outputProfile, renderingIntent=INTENT_PERCEPTUAL, - outputMode=None, inPlace=False, flags=0): + im, + inputProfile, + outputProfile, + renderingIntent=INTENT_PERCEPTUAL, + outputMode=None, + inPlace=False, + flags=0, +): """ (pyCMS) Applies an ICC transformation to a given image, mapping from inputProfile to outputProfile. @@ -333,8 +353,7 @@ def profileToProfile( raise PyCMSError("renderingIntent must be an integer between 0 and 3") if not isinstance(flags, int) or not (0 <= flags <= _MAX_FLAG): - raise PyCMSError( - "flags must be an integer between 0 and %s" + _MAX_FLAG) + raise PyCMSError("flags must be an integer between 0 and %s" + _MAX_FLAG) try: if not isinstance(inputProfile, ImageCmsProfile): @@ -342,8 +361,12 @@ def profileToProfile( if not isinstance(outputProfile, ImageCmsProfile): outputProfile = ImageCmsProfile(outputProfile) transform = ImageCmsTransform( - inputProfile, outputProfile, im.mode, outputMode, - renderingIntent, flags=flags + inputProfile, + outputProfile, + im.mode, + outputMode, + renderingIntent, + flags=flags, ) if inPlace: transform.apply_in_place(im) @@ -379,8 +402,13 @@ def getOpenProfile(profileFilename): def buildTransform( - inputProfile, outputProfile, inMode, outMode, - renderingIntent=INTENT_PERCEPTUAL, flags=0): + inputProfile, + outputProfile, + inMode, + outMode, + renderingIntent=INTENT_PERCEPTUAL, + flags=0, +): """ (pyCMS) Builds an ICC transform mapping from the inputProfile to the outputProfile. Use applyTransform to apply the transform to a given @@ -440,8 +468,7 @@ def buildTransform( raise PyCMSError("renderingIntent must be an integer between 0 and 3") if not isinstance(flags, int) or not (0 <= flags <= _MAX_FLAG): - raise PyCMSError( - "flags must be an integer between 0 and %s" + _MAX_FLAG) + raise PyCMSError("flags must be an integer between 0 and %s" + _MAX_FLAG) try: if not isinstance(inputProfile, ImageCmsProfile): @@ -449,17 +476,22 @@ def buildTransform( if not isinstance(outputProfile, ImageCmsProfile): outputProfile = ImageCmsProfile(outputProfile) return ImageCmsTransform( - inputProfile, outputProfile, inMode, outMode, - renderingIntent, flags=flags) + inputProfile, outputProfile, inMode, outMode, renderingIntent, flags=flags + ) except (IOError, TypeError, ValueError) as v: raise PyCMSError(v) def buildProofTransform( - inputProfile, outputProfile, proofProfile, inMode, outMode, - renderingIntent=INTENT_PERCEPTUAL, - proofRenderingIntent=INTENT_ABSOLUTE_COLORIMETRIC, - flags=FLAGS["SOFTPROOFING"]): + inputProfile, + outputProfile, + proofProfile, + inMode, + outMode, + renderingIntent=INTENT_PERCEPTUAL, + proofRenderingIntent=INTENT_ABSOLUTE_COLORIMETRIC, + flags=FLAGS["SOFTPROOFING"], +): """ (pyCMS) Builds an ICC transform mapping from the inputProfile to the outputProfile, but tries to simulate the result that would be @@ -538,8 +570,7 @@ def buildProofTransform( raise PyCMSError("renderingIntent must be an integer between 0 and 3") if not isinstance(flags, int) or not (0 <= flags <= _MAX_FLAG): - raise PyCMSError( - "flags must be an integer between 0 and %s" + _MAX_FLAG) + raise PyCMSError("flags must be an integer between 0 and %s" + _MAX_FLAG) try: if not isinstance(inputProfile, ImageCmsProfile): @@ -549,8 +580,15 @@ def buildProofTransform( if not isinstance(proofProfile, ImageCmsProfile): proofProfile = ImageCmsProfile(proofProfile) return ImageCmsTransform( - inputProfile, outputProfile, inMode, outMode, renderingIntent, - proofProfile, proofRenderingIntent, flags) + inputProfile, + outputProfile, + inMode, + outMode, + renderingIntent, + proofProfile, + proofRenderingIntent, + flags, + ) except (IOError, TypeError, ValueError) as v: raise PyCMSError(v) @@ -568,7 +606,7 @@ def applyTransform(im, transform, inPlace=False): If inPlace is True and transform.inMode != transform.outMode, a PyCMSError is raised. - If im.mode, transfer.inMode, or transfer.outMode is not supported by + If im.mode, transform.inMode, or transform.outMode is not supported by pyCMSdll or the profiles you used for the transform, a PyCMSError is raised. @@ -641,15 +679,16 @@ def createProfile(colorSpace, colorTemp=-1): if colorSpace not in ["LAB", "XYZ", "sRGB"]: raise PyCMSError( "Color space not supported for on-the-fly profile creation (%s)" - % colorSpace) + % colorSpace + ) if colorSpace == "LAB": try: colorTemp = float(colorTemp) except (TypeError, ValueError): raise PyCMSError( - "Color temperature must be numeric, \"%s\" not valid" - % colorTemp) + 'Color temperature must be numeric, "%s" not valid' % colorTemp + ) try: return core.createProfile(colorSpace, colorTemp) @@ -948,7 +987,4 @@ def versions(): (pyCMS) Fetches versions. """ - return ( - VERSION, core.littlecms_version, - sys.version.split()[0], Image.__version__ - ) + return (VERSION, core.littlecms_version, sys.version.split()[0], Image.__version__) diff --git a/src/PIL/ImageColor.py b/src/PIL/ImageColor.py index d3b3b00ac..d4e4e1f47 100644 --- a/src/PIL/ImageColor.py +++ b/src/PIL/ImageColor.py @@ -41,95 +41,77 @@ def getrgb(color): return rgb # check for known string formats - if re.match('#[a-f0-9]{3}$', color): - return ( - int(color[1]*2, 16), - int(color[2]*2, 16), - int(color[3]*2, 16), - ) + if re.match("#[a-f0-9]{3}$", color): + return (int(color[1] * 2, 16), int(color[2] * 2, 16), int(color[3] * 2, 16)) - if re.match('#[a-f0-9]{4}$', color): + if re.match("#[a-f0-9]{4}$", color): return ( - int(color[1]*2, 16), - int(color[2]*2, 16), - int(color[3]*2, 16), - int(color[4]*2, 16), - ) + int(color[1] * 2, 16), + int(color[2] * 2, 16), + int(color[3] * 2, 16), + int(color[4] * 2, 16), + ) - if re.match('#[a-f0-9]{6}$', color): - return ( - int(color[1:3], 16), - int(color[3:5], 16), - int(color[5:7], 16), - ) + if re.match("#[a-f0-9]{6}$", color): + return (int(color[1:3], 16), int(color[3:5], 16), int(color[5:7], 16)) - if re.match('#[a-f0-9]{8}$', color): + if re.match("#[a-f0-9]{8}$", color): return ( int(color[1:3], 16), int(color[3:5], 16), int(color[5:7], 16), int(color[7:9], 16), - ) + ) m = re.match(r"rgb\(\s*(\d+)\s*,\s*(\d+)\s*,\s*(\d+)\s*\)$", color) if m: - return ( - int(m.group(1)), - int(m.group(2)), - int(m.group(3)) - ) + return (int(m.group(1)), int(m.group(2)), int(m.group(3))) m = re.match(r"rgb\(\s*(\d+)%\s*,\s*(\d+)%\s*,\s*(\d+)%\s*\)$", color) if m: return ( int((int(m.group(1)) * 255) / 100.0 + 0.5), int((int(m.group(2)) * 255) / 100.0 + 0.5), - int((int(m.group(3)) * 255) / 100.0 + 0.5) - ) + int((int(m.group(3)) * 255) / 100.0 + 0.5), + ) m = re.match( - r"hsl\(\s*(\d+\.?\d*)\s*,\s*(\d+\.?\d*)%\s*,\s*(\d+\.?\d*)%\s*\)$", - color, + r"hsl\(\s*(\d+\.?\d*)\s*,\s*(\d+\.?\d*)%\s*,\s*(\d+\.?\d*)%\s*\)$", color ) if m: from colorsys import hls_to_rgb + rgb = hls_to_rgb( float(m.group(1)) / 360.0, float(m.group(3)) / 100.0, float(m.group(2)) / 100.0, - ) + ) return ( int(rgb[0] * 255 + 0.5), int(rgb[1] * 255 + 0.5), - int(rgb[2] * 255 + 0.5) - ) + int(rgb[2] * 255 + 0.5), + ) m = re.match( - r"hs[bv]\(\s*(\d+\.?\d*)\s*,\s*(\d+\.?\d*)%\s*,\s*(\d+\.?\d*)%\s*\)$", - color, + r"hs[bv]\(\s*(\d+\.?\d*)\s*,\s*(\d+\.?\d*)%\s*,\s*(\d+\.?\d*)%\s*\)$", color ) if m: from colorsys import hsv_to_rgb + rgb = hsv_to_rgb( float(m.group(1)) / 360.0, float(m.group(2)) / 100.0, float(m.group(3)) / 100.0, - ) + ) return ( int(rgb[0] * 255 + 0.5), int(rgb[1] * 255 + 0.5), - int(rgb[2] * 255 + 0.5) - ) + int(rgb[2] * 255 + 0.5), + ) - m = re.match(r"rgba\(\s*(\d+)\s*,\s*(\d+)\s*,\s*(\d+)\s*,\s*(\d+)\s*\)$", - color) + m = re.match(r"rgba\(\s*(\d+)\s*,\s*(\d+)\s*,\s*(\d+)\s*,\s*(\d+)\s*\)$", color) if m: - return ( - int(m.group(1)), - int(m.group(2)), - int(m.group(3)), - int(m.group(4)) - ) + return (int(m.group(1)), int(m.group(2)), int(m.group(3)), int(m.group(4))) raise ValueError("unknown color specifier: %r" % color) @@ -151,11 +133,11 @@ def getcolor(color, mode): if Image.getmodebase(mode) == "L": r, g, b = color - color = (r*299 + g*587 + b*114)//1000 - if mode[-1] == 'A': + color = (r * 299 + g * 587 + b * 114) // 1000 + if mode[-1] == "A": return (color, alpha) else: - if mode[-1] == 'A': + if mode[-1] == "A": return color + (alpha,) return color diff --git a/src/PIL/ImageDraw.py b/src/PIL/ImageDraw.py index 86512bb82..d43938281 100644 --- a/src/PIL/ImageDraw.py +++ b/src/PIL/ImageDraw.py @@ -45,7 +45,6 @@ directly. class ImageDraw(object): - def __init__(self, im, mode=None): """ Create a drawing instance. @@ -95,6 +94,7 @@ class ImageDraw(object): if not self.font: # FIXME: should add a font repository from . import ImageFont + self.font = ImageFont.load_default() return self.font @@ -156,13 +156,12 @@ class ImageDraw(object): if ink is not None: self.draw.draw_lines(xy, ink, width) if joint == "curve" and width > 4: - for i in range(1, len(xy)-1): + for i in range(1, len(xy) - 1): point = xy[i] angles = [ - math.degrees(math.atan2( - end[0] - start[0], start[1] - end[1] - )) % 360 - for start, end in ((xy[i-1], point), (point, xy[i+1])) + math.degrees(math.atan2(end[0] - start[0], start[1] - end[1])) + % 360 + for start, end in ((xy[i - 1], point), (point, xy[i + 1])) ] if angles[0] == angles[1]: # This is a straight line, so no joint is required @@ -171,21 +170,23 @@ class ImageDraw(object): def coord_at_angle(coord, angle): x, y = coord angle -= 90 - distance = width/2 - 1 - return tuple([ - p + - (math.floor(p_d) if p_d > 0 else math.ceil(p_d)) - for p, p_d in - ((x, distance * math.cos(math.radians(angle))), - (y, distance * math.sin(math.radians(angle)))) - ]) - flipped = ((angles[1] > angles[0] and - angles[1] - 180 > angles[0]) or - (angles[1] < angles[0] and - angles[1] + 180 > angles[0])) + distance = width / 2 - 1 + return tuple( + [ + p + (math.floor(p_d) if p_d > 0 else math.ceil(p_d)) + for p, p_d in ( + (x, distance * math.cos(math.radians(angle))), + (y, distance * math.sin(math.radians(angle))), + ) + ] + ) + + flipped = ( + angles[1] > angles[0] and angles[1] - 180 > angles[0] + ) or (angles[1] < angles[0] and angles[1] + 180 > angles[0]) coords = [ - (point[0] - width/2 + 1, point[1] - width/2 + 1), - (point[0] + width/2 - 1, point[1] + width/2 - 1) + (point[0] - width / 2 + 1, point[1] - width / 2 + 1), + (point[0] + width / 2 - 1, point[1] + width / 2 - 1), ] if flipped: start, end = (angles[1] + 90, angles[0] + 90) @@ -197,15 +198,15 @@ class ImageDraw(object): # Cover potential gaps between the line and the joint if flipped: gapCoords = [ - coord_at_angle(point, angles[0]+90), + coord_at_angle(point, angles[0] + 90), point, - coord_at_angle(point, angles[1]+90) + coord_at_angle(point, angles[1] + 90), ] else: gapCoords = [ - coord_at_angle(point, angles[0]-90), + coord_at_angle(point, angles[0] - 90), point, - coord_at_angle(point, angles[1]-90) + coord_at_angle(point, angles[1] - 90), ] self.line(gapCoords, fill, width=3) @@ -259,11 +260,9 @@ class ImageDraw(object): return text.split(split_character) - def text(self, xy, text, fill=None, font=None, anchor=None, - *args, **kwargs): + def text(self, xy, text, fill=None, font=None, anchor=None, *args, **kwargs): if self._multiline_check(text): - return self.multiline_text(xy, text, fill, font, anchor, - *args, **kwargs) + return self.multiline_text(xy, text, fill, font, anchor, *args, **kwargs) ink, fill = self._getink(fill) if font is None: font = self.getfont() @@ -271,8 +270,7 @@ class ImageDraw(object): ink = fill if ink is not None: try: - mask, offset = font.getmask2(text, self.fontmode, - *args, **kwargs) + mask, offset = font.getmask2(text, self.fontmode, *args, **kwargs) xy = xy[0] + offset[0], xy[1] + offset[1] except AttributeError: try: @@ -281,18 +279,27 @@ class ImageDraw(object): mask = font.getmask(text) self.draw.draw_bitmap(xy, mask, ink) - def multiline_text(self, xy, text, fill=None, font=None, anchor=None, - spacing=4, align="left", direction=None, features=None, - language=None): + def multiline_text( + self, + xy, + text, + fill=None, + font=None, + anchor=None, + spacing=4, + align="left", + direction=None, + features=None, + language=None, + ): widths = [] max_width = 0 lines = self._multiline_split(text) - line_spacing = self.textsize('A', font=font)[1] + spacing + line_spacing = self.textsize("A", font=font)[1] + spacing for line in lines: - line_width, line_height = self.textsize(line, font, - direction=direction, - features=features, - language=language) + line_width, line_height = self.textsize( + line, font, direction=direction, features=features, language=language + ) widths.append(line_width) max_width = max(max_width, line_width) left, top = xy @@ -302,36 +309,47 @@ class ImageDraw(object): elif align == "center": left += (max_width - widths[idx]) / 2.0 elif align == "right": - left += (max_width - widths[idx]) + left += max_width - widths[idx] else: raise ValueError('align must be "left", "center" or "right"') - self.text((left, top), line, fill, font, anchor, - direction=direction, features=features, language=language) + self.text( + (left, top), + line, + fill, + font, + anchor, + direction=direction, + features=features, + language=language, + ) top += line_spacing left = xy[0] - def textsize(self, text, font=None, spacing=4, direction=None, - features=None, language=None): + def textsize( + self, text, font=None, spacing=4, direction=None, features=None, language=None + ): """Get the size of a given string, in pixels.""" if self._multiline_check(text): - return self.multiline_textsize(text, font, spacing, - direction, features, language) + return self.multiline_textsize( + text, font, spacing, direction, features, language + ) if font is None: font = self.getfont() return font.getsize(text, direction, features, language) - def multiline_textsize(self, text, font=None, spacing=4, direction=None, - features=None, language=None): + def multiline_textsize( + self, text, font=None, spacing=4, direction=None, features=None, language=None + ): max_width = 0 lines = self._multiline_split(text) - line_spacing = self.textsize('A', font=font)[1] + spacing + line_spacing = self.textsize("A", font=font)[1] + spacing for line in lines: - line_width, line_height = self.textsize(line, font, spacing, - direction, features, - language) + line_width, line_height = self.textsize( + line, font, spacing, direction, features, language + ) max_width = max(max_width, line_width) - return max_width, len(lines)*line_spacing - spacing + return max_width, len(lines) * line_spacing - spacing def Draw(im, mode=None): @@ -417,7 +435,7 @@ def floodfill(image, xy, value, border=None, thresh=0): while edge: new_edge = set() for (x, y) in edge: # 4 adjacent method - for (s, t) in ((x+1, y), (x-1, y), (x, y+1), (x, y-1)): + for (s, t) in ((x + 1, y), (x - 1, y), (x, y + 1), (x, y - 1)): if (s, t) in full_edge: continue # if already processed, skip try: @@ -442,6 +460,6 @@ def _color_diff(color1, color2): Uses 1-norm distance to calculate difference between two values. """ if isinstance(color2, tuple): - return sum([abs(color1[i]-color2[i]) for i in range(0, len(color2))]) + return sum([abs(color1[i] - color2[i]) for i in range(0, len(color2))]) else: - return abs(color1-color2) + return abs(color1 - color2) diff --git a/src/PIL/ImageDraw2.py b/src/PIL/ImageDraw2.py index f7902b031..324d869f0 100644 --- a/src/PIL/ImageDraw2.py +++ b/src/PIL/ImageDraw2.py @@ -38,7 +38,6 @@ class Font(object): class Draw(object): - def __init__(self, image, size=None, color=None): if not hasattr(image, "im"): image = Image.new(image, size, color) diff --git a/src/PIL/ImageEnhance.py b/src/PIL/ImageEnhance.py index 1b78bfd9b..534eb4f16 100644 --- a/src/PIL/ImageEnhance.py +++ b/src/PIL/ImageEnhance.py @@ -22,7 +22,6 @@ from . import Image, ImageFilter, ImageStat class _Enhance(object): - def enhance(self, factor): """ Returns an enhanced image. @@ -45,14 +44,14 @@ class Color(_Enhance): factor of 0.0 gives a black and white image. A factor of 1.0 gives the original image. """ + def __init__(self, image): self.image = image - self.intermediate_mode = 'L' - if 'A' in image.getbands(): - self.intermediate_mode = 'LA' + self.intermediate_mode = "L" + if "A" in image.getbands(): + self.intermediate_mode = "LA" - self.degenerate = image.convert( - self.intermediate_mode).convert(image.mode) + self.degenerate = image.convert(self.intermediate_mode).convert(image.mode) class Contrast(_Enhance): @@ -62,13 +61,14 @@ class Contrast(_Enhance): to the contrast control on a TV set. An enhancement factor of 0.0 gives a solid grey image. A factor of 1.0 gives the original image. """ + def __init__(self, image): self.image = image mean = int(ImageStat.Stat(image.convert("L")).mean[0] + 0.5) self.degenerate = Image.new("L", image.size, mean).convert(image.mode) - if 'A' in image.getbands(): - self.degenerate.putalpha(image.getchannel('A')) + if "A" in image.getbands(): + self.degenerate.putalpha(image.getchannel("A")) class Brightness(_Enhance): @@ -78,12 +78,13 @@ class Brightness(_Enhance): enhancement factor of 0.0 gives a black image. A factor of 1.0 gives the original image. """ + def __init__(self, image): self.image = image self.degenerate = Image.new(image.mode, image.size, 0) - if 'A' in image.getbands(): - self.degenerate.putalpha(image.getchannel('A')) + if "A" in image.getbands(): + self.degenerate.putalpha(image.getchannel("A")) class Sharpness(_Enhance): @@ -93,9 +94,10 @@ class Sharpness(_Enhance): enhancement factor of 0.0 gives a blurred image, a factor of 1.0 gives the original image, and a factor of 2.0 gives a sharpened image. """ + def __init__(self, image): self.image = image self.degenerate = image.filter(ImageFilter.SMOOTH) - if 'A' in image.getbands(): - self.degenerate.putalpha(image.getchannel('A')) + if "A" in image.getbands(): + self.degenerate.putalpha(image.getchannel("A")) diff --git a/src/PIL/ImageFile.py b/src/PIL/ImageFile.py index 06e7b76ba..e5173a1fb 100644 --- a/src/PIL/ImageFile.py +++ b/src/PIL/ImageFile.py @@ -35,7 +35,7 @@ import struct MAXBLOCK = 65536 -SAFEBLOCK = 1024*1024 +SAFEBLOCK = 1024 * 1024 LOAD_TRUNCATED_IMAGES = False @@ -44,7 +44,7 @@ ERRORS = { -2: "decoding error", -3: "unknown error", -8: "bad configuration", - -9: "out of memory error" + -9: "out of memory error", } @@ -62,6 +62,7 @@ def raise_ioerror(error): # -------------------------------------------------------------------- # Helpers + def _tilesort(t): # sort on offset return t[2] @@ -71,6 +72,7 @@ def _tilesort(t): # -------------------------------------------------------------------- # ImageFile base class + class ImageFile(Image.Image): "Base class for image file format handlers." @@ -101,11 +103,13 @@ class ImageFile(Image.Image): try: self._open() - except (IndexError, # end of data - TypeError, # end of data (ord) - KeyError, # unsupported mode - EOFError, # got header but not the first frame - struct.error) as v: + except ( + IndexError, # end of data + TypeError, # end of data (ord) + KeyError, # unsupported mode + EOFError, # got header but not the first frame + struct.error, + ) as v: # close the file only if we have opened it this constructor if self._exclusive_fp: self.fp.close() @@ -147,7 +151,7 @@ class ImageFile(Image.Image): self.map = None use_mmap = self.filename and len(self.tile) == 1 # As of pypy 2.1.0, memory mapping was failing here. - use_mmap = use_mmap and not hasattr(sys, 'pypy_version_info') + use_mmap = use_mmap and not hasattr(sys, "pypy_version_info") readonly = 0 @@ -168,9 +172,12 @@ class ImageFile(Image.Image): if use_mmap: # try memory mapping decoder_name, extents, offset, args = self.tile[0] - if decoder_name == "raw" and len(args) >= 3 and \ - args[0] == self.mode and \ - args[0] in Image._MAPMODES: + if ( + decoder_name == "raw" + and len(args) >= 3 + and args[0] == self.mode + and args[0] in Image._MAPMODES + ): try: if hasattr(Image.core, "map"): # use built-in mapper WIN32 only @@ -178,16 +185,18 @@ class ImageFile(Image.Image): self.map.seek(offset) self.im = self.map.readimage( self.mode, self.size, args[1], args[2] - ) + ) else: # use mmap, if possible import mmap + with open(self.filename, "r") as fp: - self.map = mmap.mmap(fp.fileno(), 0, - access=mmap.ACCESS_READ) + self.map = mmap.mmap( + fp.fileno(), 0, access=mmap.ACCESS_READ + ) self.im = Image.core.map_buffer( - self.map, self.size, decoder_name, extents, - offset, args) + self.map, self.size, decoder_name, extents, offset, args + ) readonly = 1 # After trashing self.im, # we might need to reload the palette data. @@ -209,8 +218,9 @@ class ImageFile(Image.Image): prefix = b"" for decoder_name, extents, offset, args in self.tile: - decoder = Image._getdecoder(self.mode, decoder_name, - args, self.decoderconfig) + decoder = Image._getdecoder( + self.mode, decoder_name, args, self.decoderconfig + ) try: seek(offset) decoder.setimage(self.im, extents) @@ -234,9 +244,10 @@ class ImageFile(Image.Image): break else: self.tile = [] - raise IOError("image file is truncated " - "(%d bytes not processed)" % - len(b)) + raise IOError( + "image file is truncated " + "(%d bytes not processed)" % len(b) + ) b = b + s n, err_code = decoder.decode(b) @@ -264,8 +275,7 @@ class ImageFile(Image.Image): def load_prepare(self): # create image memory if necessary - if not self.im or\ - self.im.mode != self.mode or self.im.size != self.size: + if not self.im or self.im.mode != self.mode or self.im.size != self.size: self.im = Image.core.new(self.mode, self.size) # create palette (optional) if self.mode == "P": @@ -284,11 +294,15 @@ class ImageFile(Image.Image): # pass def _seek_check(self, frame): - if (frame < self._min_frame or + if ( + frame < self._min_frame # Only check upper limit on frames if additional seek operations # are not required to do so - (not (hasattr(self, "_n_frames") and self._n_frames is None) and - frame >= self.n_frames+self._min_frame)): + or ( + not (hasattr(self, "_n_frames") and self._n_frames is None) + and frame >= self.n_frames + self._min_frame + ) + ): raise EOFError("attempt to seek outside sequence") return self.tell() != frame @@ -303,9 +317,7 @@ class StubImageFile(ImageFile): """ def _open(self): - raise NotImplementedError( - "StubImageFile subclass must implement _open" - ) + raise NotImplementedError("StubImageFile subclass must implement _open") def load(self): loader = self._load() @@ -319,9 +331,7 @@ class StubImageFile(ImageFile): def _load(self): """(Hook) Find actual image loader.""" - raise NotImplementedError( - "StubImageFile subclass must implement _load" - ) + raise NotImplementedError("StubImageFile subclass must implement _load") class Parser(object): @@ -329,6 +339,7 @@ class Parser(object): Incremental image parser. This class implements the standard feed/close consumer interface. """ + incremental = None image = None data = None @@ -413,15 +424,13 @@ class Parser(object): im.load_prepare() d, e, o, a = im.tile[0] im.tile = [] - self.decoder = Image._getdecoder( - im.mode, d, a, im.decoderconfig - ) + self.decoder = Image._getdecoder(im.mode, d, a, im.decoderconfig) self.decoder.setimage(im.im, e) # calculate decoder offset self.offset = o if self.offset <= len(self.data): - self.data = self.data[self.offset:] + self.data = self.data[self.offset :] self.offset = 0 self.image = im @@ -463,6 +472,7 @@ class Parser(object): # -------------------------------------------------------------------- + def _save(im, fp, tile, bufsize=0): """Helper to save image based on tile list @@ -557,8 +567,7 @@ class PyCodecState(object): self.yoff = 0 def extents(self): - return (self.xoff, self.yoff, - self.xoff+self.xsize, self.yoff+self.ysize) + return (self.xoff, self.yoff, self.xoff + self.xsize, self.yoff + self.ysize) class PyDecoder(object): @@ -648,8 +657,10 @@ class PyDecoder(object): if self.state.xsize <= 0 or self.state.ysize <= 0: raise ValueError("Size cannot be negative") - if (self.state.xsize + self.state.xoff > self.im.size[0] or - self.state.ysize + self.state.yoff > self.im.size[1]): + if ( + self.state.xsize + self.state.xoff > self.im.size[0] + or self.state.ysize + self.state.yoff > self.im.size[1] + ): raise ValueError("Tile cannot extend outside image") def set_as_raw(self, data, rawmode=None): @@ -664,7 +675,7 @@ class PyDecoder(object): if not rawmode: rawmode = self.mode - d = Image._getdecoder(self.mode, 'raw', (rawmode)) + d = Image._getdecoder(self.mode, "raw", (rawmode)) d.setimage(self.im, self.state.extents()) s = d.decode(data) diff --git a/src/PIL/ImageFilter.py b/src/PIL/ImageFilter.py index 271f93b0a..fa4162b61 100644 --- a/src/PIL/ImageFilter.py +++ b/src/PIL/ImageFilter.py @@ -57,12 +57,13 @@ class Kernel(BuiltinFilter): :param offset: Offset. If given, this value is added to the result, after it has been divided by the scale factor. """ + name = "Kernel" def __init__(self, size, kernel, scale=None, offset=0): if scale is None: # default scale is sum of kernel - scale = functools.reduce(lambda a, b: a+b, kernel) + scale = functools.reduce(lambda a, b: a + b, kernel) if size[0] * size[1] != len(kernel): raise ValueError("not enough coefficients in kernel") self.filterargs = size, scale, offset, kernel @@ -78,6 +79,7 @@ class RankFilter(Filter): ``size * size / 2`` for a median filter, ``size * size - 1`` for a max filter, etc. """ + name = "Rank" def __init__(self, size, rank): @@ -87,7 +89,7 @@ class RankFilter(Filter): def filter(self, image): if image.mode == "P": raise ValueError("cannot filter palette images") - image = image.expand(self.size//2, self.size//2) + image = image.expand(self.size // 2, self.size // 2) return image.rankfilter(self.size, self.rank) @@ -98,11 +100,12 @@ class MedianFilter(RankFilter): :param size: The kernel size, in pixels. """ + name = "Median" def __init__(self, size=3): self.size = size - self.rank = size*size//2 + self.rank = size * size // 2 class MinFilter(RankFilter): @@ -112,6 +115,7 @@ class MinFilter(RankFilter): :param size: The kernel size, in pixels. """ + name = "Min" def __init__(self, size=3): @@ -126,11 +130,12 @@ class MaxFilter(RankFilter): :param size: The kernel size, in pixels. """ + name = "Max" def __init__(self, size=3): self.size = size - self.rank = size*size-1 + self.rank = size * size - 1 class ModeFilter(Filter): @@ -141,6 +146,7 @@ class ModeFilter(Filter): :param size: The kernel size, in pixels. """ + name = "Mode" def __init__(self, size=3): @@ -155,6 +161,7 @@ class GaussianBlur(MultibandFilter): :param radius: Blur radius. """ + name = "GaussianBlur" def __init__(self, radius=2): @@ -175,6 +182,7 @@ class BoxBlur(MultibandFilter): returns an identical image. Radius 1 takes 1 pixel in each direction, i.e. 9 pixels in total. """ + name = "BoxBlur" def __init__(self, radius): @@ -198,6 +206,7 @@ class UnsharpMask(MultibandFilter): .. _digital unsharp masking: https://en.wikipedia.org/wiki/Unsharp_masking#Digital_unsharp_masking """ # noqa: E501 + name = "UnsharpMask" def __init__(self, radius=2, percent=150, threshold=3): @@ -211,96 +220,116 @@ class UnsharpMask(MultibandFilter): class BLUR(BuiltinFilter): name = "Blur" + # fmt: off filterargs = (5, 5), 16, 0, ( - 1, 1, 1, 1, 1, - 1, 0, 0, 0, 1, - 1, 0, 0, 0, 1, - 1, 0, 0, 0, 1, - 1, 1, 1, 1, 1 - ) + 1, 1, 1, 1, 1, + 1, 0, 0, 0, 1, + 1, 0, 0, 0, 1, + 1, 0, 0, 0, 1, + 1, 1, 1, 1, 1, + ) + # fmt: on class CONTOUR(BuiltinFilter): name = "Contour" + # fmt: off filterargs = (3, 3), 1, 255, ( -1, -1, -1, -1, 8, -1, - -1, -1, -1 - ) + -1, -1, -1, + ) + # fmt: on class DETAIL(BuiltinFilter): name = "Detail" + # fmt: off filterargs = (3, 3), 6, 0, ( - 0, -1, 0, + 0, -1, 0, -1, 10, -1, - 0, -1, 0 - ) + 0, -1, 0, + ) + # fmt: on class EDGE_ENHANCE(BuiltinFilter): name = "Edge-enhance" + # fmt: off filterargs = (3, 3), 2, 0, ( -1, -1, -1, -1, 10, -1, - -1, -1, -1 - ) + -1, -1, -1, + ) + # fmt: on class EDGE_ENHANCE_MORE(BuiltinFilter): name = "Edge-enhance More" + # fmt: off filterargs = (3, 3), 1, 0, ( -1, -1, -1, -1, 9, -1, - -1, -1, -1 - ) + -1, -1, -1, + ) + # fmt: on class EMBOSS(BuiltinFilter): name = "Emboss" + # fmt: off filterargs = (3, 3), 1, 128, ( - -1, 0, 0, - 0, 1, 0, - 0, 0, 0 - ) + -1, 0, 0, + 0, 1, 0, + 0, 0, 0, + ) + # fmt: on class FIND_EDGES(BuiltinFilter): name = "Find Edges" + # fmt: off filterargs = (3, 3), 1, 0, ( -1, -1, -1, -1, 8, -1, - -1, -1, -1 - ) + -1, -1, -1, + ) + # fmt: on class SHARPEN(BuiltinFilter): name = "Sharpen" + # fmt: off filterargs = (3, 3), 16, 0, ( -2, -2, -2, -2, 32, -2, - -2, -2, -2 - ) + -2, -2, -2, + ) + # fmt: on class SMOOTH(BuiltinFilter): name = "Smooth" + # fmt: off filterargs = (3, 3), 13, 0, ( - 1, 1, 1, - 1, 5, 1, - 1, 1, 1 - ) + 1, 1, 1, + 1, 5, 1, + 1, 1, 1, + ) + # fmt: on class SMOOTH_MORE(BuiltinFilter): name = "Smooth More" + # fmt: off filterargs = (5, 5), 100, 0, ( - 1, 1, 1, 1, 1, - 1, 5, 5, 5, 1, - 1, 5, 44, 5, 1, - 1, 5, 5, 5, 1, - 1, 1, 1, 1, 1 - ) + 1, 1, 1, 1, 1, + 1, 5, 5, 5, 1, + 1, 5, 44, 5, 1, + 1, 5, 5, 5, 1, + 1, 1, 1, 1, 1, + ) + # fmt: on class Color3DLUT(MultibandFilter): @@ -327,6 +356,7 @@ class Color3DLUT(MultibandFilter): than ``channels`` channels. Default is ``None``, which means that mode wouldn't be changed. """ + name = "Color 3D LUT" def __init__(self, size, table, channels=3, target_mode=None, **kwargs): @@ -338,7 +368,7 @@ class Color3DLUT(MultibandFilter): # Hidden flag `_copy_table=False` could be used to avoid extra copying # of the table if the table is specially made for the constructor. - copy_table = kwargs.get('_copy_table', True) + copy_table = kwargs.get("_copy_table", True) items = size[0] * size[1] * size[2] wrong_size = False @@ -346,8 +376,11 @@ class Color3DLUT(MultibandFilter): if copy_table: table = table.copy() - if table.shape in [(items * channels,), (items, channels), - (size[2], size[1], size[0], channels)]: + if table.shape in [ + (items * channels,), + (items, channels), + (size[2], size[1], size[0], channels), + ]: table = table.reshape(items * channels) else: wrong_size = True @@ -363,7 +396,8 @@ class Color3DLUT(MultibandFilter): if len(pixel) != channels: raise ValueError( "The elements of the table should " - "have a length of {}.".format(channels)) + "have a length of {}.".format(channels) + ) table.extend(pixel) if wrong_size or len(table) != items * channels: @@ -371,7 +405,9 @@ class Color3DLUT(MultibandFilter): "The table should have either channels * size**3 float items " "or size**3 items of channels-sized tuples with floats. " "Table should be: {}x{}x{}x{}. Actual length: {}".format( - channels, size[0], size[1], size[2], len(table))) + channels, size[0], size[1], size[2], len(table) + ) + ) self.table = table @staticmethod @@ -379,8 +415,9 @@ class Color3DLUT(MultibandFilter): try: _, _, _ = size except ValueError: - raise ValueError("Size should be either an integer or " - "a tuple of three integers.") + raise ValueError( + "Size should be either an integer or a tuple of three integers." + ) except TypeError: size = (size, size, size) size = [int(x) for x in size] @@ -411,15 +448,20 @@ class Color3DLUT(MultibandFilter): for b in range(size3D): for g in range(size2D): for r in range(size1D): - table[idx_out:idx_out + channels] = callback( - r / (size1D-1), g / (size2D-1), b / (size3D-1)) + table[idx_out : idx_out + channels] = callback( + r / (size1D - 1), g / (size2D - 1), b / (size3D - 1) + ) idx_out += channels - return cls((size1D, size2D, size3D), table, channels=channels, - target_mode=target_mode, _copy_table=False) + return cls( + (size1D, size2D, size3D), + table, + channels=channels, + target_mode=target_mode, + _copy_table=False, + ) - def transform(self, callback, with_normals=False, channels=None, - target_mode=None): + def transform(self, callback, with_normals=False, channels=None, target_mode=None): """Transforms the table values using provided callback and returns a new LUT with altered values. @@ -450,24 +492,31 @@ class Color3DLUT(MultibandFilter): for b in range(size3D): for g in range(size2D): for r in range(size1D): - values = self.table[idx_in:idx_in + ch_in] + values = self.table[idx_in : idx_in + ch_in] if with_normals: - values = callback(r / (size1D-1), g / (size2D-1), - b / (size3D-1), *values) + values = callback( + r / (size1D - 1), + g / (size2D - 1), + b / (size3D - 1), + *values + ) else: values = callback(*values) - table[idx_out:idx_out + ch_out] = values + table[idx_out : idx_out + ch_out] = values idx_in += ch_in idx_out += ch_out - return type(self)(self.size, table, channels=ch_out, - target_mode=target_mode or self.mode, - _copy_table=False) + return type(self)( + self.size, + table, + channels=ch_out, + target_mode=target_mode or self.mode, + _copy_table=False, + ) def __repr__(self): r = [ - "{} from {}".format(self.__class__.__name__, - self.table.__class__.__name__), + "{} from {}".format(self.__class__.__name__, self.table.__class__.__name__), "size={:d}x{:d}x{:d}".format(*self.size), "channels={:d}".format(self.channels), ] @@ -479,5 +528,11 @@ class Color3DLUT(MultibandFilter): from . import Image return image.color_lut_3d( - self.mode or image.mode, Image.LINEAR, self.channels, - self.size[0], self.size[1], self.size[2], self.table) + self.mode or image.mode, + Image.LINEAR, + self.channels, + self.size[0], + self.size[1], + self.size[2], + self.table, + ) diff --git a/src/PIL/ImageFont.py b/src/PIL/ImageFont.py index 580aa8744..f43f95b9a 100644 --- a/src/PIL/ImageFont.py +++ b/src/PIL/ImageFont.py @@ -98,7 +98,7 @@ class ImageFont(object): self.info.append(s) # read PILfont metrics - data = file.read(256*20) + data = file.read(256 * 20) # check image if image.mode not in ("1", "L"): @@ -119,11 +119,11 @@ class ImageFont(object): # Wrapper for FreeType fonts. Application code should use the # truetype factory function to create font objects. + class FreeTypeFont(object): "FreeType font wrapper (requires _imagingft service)" - def __init__(self, font=None, size=10, index=0, encoding="", - layout_engine=None): + def __init__(self, font=None, size=10, index=0, encoding="", layout_engine=None): # FIXME: use service provider instead self.path = font @@ -135,60 +135,271 @@ class FreeTypeFont(object): layout_engine = LAYOUT_BASIC if core.HAVE_RAQM: layout_engine = LAYOUT_RAQM - if layout_engine == LAYOUT_RAQM and not core.HAVE_RAQM: + elif layout_engine == LAYOUT_RAQM and not core.HAVE_RAQM: layout_engine = LAYOUT_BASIC self.layout_engine = layout_engine - if isPath(font): - self.font = core.getfont(font, size, index, encoding, - layout_engine=layout_engine) - else: - self.font_bytes = font.read() + def load_from_bytes(f): + self.font_bytes = f.read() self.font = core.getfont( - "", size, index, encoding, self.font_bytes, layout_engine) + "", size, index, encoding, self.font_bytes, layout_engine + ) + + if isPath(font): + if sys.platform == "win32": + font_bytes_path = font if isinstance(font, bytes) else font.encode() + try: + font_bytes_path.decode("ascii") + except UnicodeDecodeError: + # FreeType cannot load fonts with non-ASCII characters on Windows + # So load it into memory first + with open(font, "rb") as f: + load_from_bytes(f) + return + self.font = core.getfont( + font, size, index, encoding, layout_engine=layout_engine + ) + else: + load_from_bytes(font) def _multiline_split(self, text): split_character = "\n" if isinstance(text, str) else b"\n" return text.split(split_character) def getname(self): + """ + :return: A tuple of the font family (e.g. Helvetica) and the font style + (e.g. Bold) + """ return self.font.family, self.font.style def getmetrics(self): + """ + :return: A tuple of the font ascent (the distance from the baseline to + the highest outline point) and descent (the distance from the + baseline to the lowest outline point, a negative value) + """ return self.font.ascent, self.font.descent def getsize(self, text, direction=None, features=None, language=None): + """ + Returns width and height (in pixels) of given text if rendered in font with + provided direction, features, and language. + + :param text: Text to measure. + + :param direction: Direction of the text. It can be 'rtl' (right to + left), 'ltr' (left to right) or 'ttb' (top to bottom). + Requires libraqm. + + .. versionadded:: 4.2.0 + + :param features: A list of OpenType font features to be used during text + layout. This is usually used to turn on optional + font features that are not enabled by default, + for example 'dlig' or 'ss01', but can be also + used to turn off default font features for + example '-liga' to disable ligatures or '-kern' + to disable kerning. To get all supported + features, see + https://docs.microsoft.com/en-us/typography/opentype/spec/featurelist + Requires libraqm. + + .. versionadded:: 4.2.0 + + :param language: Language of the text. Different languages may use + different glyph shapes or ligatures. This parameter tells + the font which language the text is in, and to apply the + correct substitutions as appropriate, if available. + It should be a `BCP 47 language code + ` + Requires libraqm. + + .. versionadded:: 6.0.0 + + :return: (width, height) + """ size, offset = self.font.getsize(text, direction, features, language) return (size[0] + offset[0], size[1] + offset[1]) - def getsize_multiline(self, text, direction=None, spacing=4, - features=None, language=None): + def getsize_multiline( + self, text, direction=None, spacing=4, features=None, language=None + ): + """ + Returns width and height (in pixels) of given text if rendered in font + with provided direction, features, and language, while respecting + newline characters. + + :param text: Text to measure. + + :param direction: Direction of the text. It can be 'rtl' (right to + left), 'ltr' (left to right) or 'ttb' (top to bottom). + Requires libraqm. + + :param spacing: The vertical gap between lines, defaulting to 4 pixels. + + :param features: A list of OpenType font features to be used during text + layout. This is usually used to turn on optional + font features that are not enabled by default, + for example 'dlig' or 'ss01', but can be also + used to turn off default font features for + example '-liga' to disable ligatures or '-kern' + to disable kerning. To get all supported + features, see + https://docs.microsoft.com/en-us/typography/opentype/spec/featurelist + Requires libraqm. + + :param language: Language of the text. Different languages may use + different glyph shapes or ligatures. This parameter tells + the font which language the text is in, and to apply the + correct substitutions as appropriate, if available. + It should be a `BCP 47 language code + ` + Requires libraqm. + + .. versionadded:: 6.0.0 + + :return: (width, height) + """ max_width = 0 lines = self._multiline_split(text) - line_spacing = self.getsize('A')[1] + spacing + line_spacing = self.getsize("A")[1] + spacing for line in lines: line_width, line_height = self.getsize(line, direction, features, language) max_width = max(max_width, line_width) - return max_width, len(lines)*line_spacing - spacing + return max_width, len(lines) * line_spacing - spacing def getoffset(self, text): + """ + Returns the offset of given text. This is the gap between the + starting coordinate and the first marking. Note that this gap is + included in the result of :py:func:`~PIL.ImageFont.FreeTypeFont.getsize`. + + :param text: Text to measure. + + :return: A tuple of the x and y offset + """ return self.font.getsize(text)[1] def getmask(self, text, mode="", direction=None, features=None, language=None): - return self.getmask2(text, mode, direction=direction, features=features, - language=language)[0] + """ + Create a bitmap for the text. - def getmask2(self, text, mode="", fill=Image.core.fill, direction=None, - features=None, language=None, *args, **kwargs): + If the font uses antialiasing, the bitmap should have mode ``L`` and use a + maximum value of 255. Otherwise, it should have mode ``1``. + + :param text: Text to render. + :param mode: Used by some graphics drivers to indicate what mode the + driver prefers; if empty, the renderer may return either + mode. Note that the mode is always a string, to simplify + C-level implementations. + + .. versionadded:: 1.1.5 + + :param direction: Direction of the text. It can be 'rtl' (right to + left), 'ltr' (left to right) or 'ttb' (top to bottom). + Requires libraqm. + + .. versionadded:: 4.2.0 + + :param features: A list of OpenType font features to be used during text + layout. This is usually used to turn on optional + font features that are not enabled by default, + for example 'dlig' or 'ss01', but can be also + used to turn off default font features for + example '-liga' to disable ligatures or '-kern' + to disable kerning. To get all supported + features, see + https://docs.microsoft.com/en-us/typography/opentype/spec/featurelist + Requires libraqm. + + .. versionadded:: 4.2.0 + + :param language: Language of the text. Different languages may use + different glyph shapes or ligatures. This parameter tells + the font which language the text is in, and to apply the + correct substitutions as appropriate, if available. + It should be a `BCP 47 language code + ` + Requires libraqm. + + .. versionadded:: 6.0.0 + + :return: An internal PIL storage memory instance as defined by the + :py:mod:`PIL.Image.core` interface module. + """ + return self.getmask2( + text, mode, direction=direction, features=features, language=language + )[0] + + def getmask2( + self, + text, + mode="", + fill=Image.core.fill, + direction=None, + features=None, + language=None, + *args, + **kwargs + ): + """ + Create a bitmap for the text. + + If the font uses antialiasing, the bitmap should have mode ``L`` and use a + maximum value of 255. Otherwise, it should have mode ``1``. + + :param text: Text to render. + :param mode: Used by some graphics drivers to indicate what mode the + driver prefers; if empty, the renderer may return either + mode. Note that the mode is always a string, to simplify + C-level implementations. + + .. versionadded:: 1.1.5 + + :param direction: Direction of the text. It can be 'rtl' (right to + left), 'ltr' (left to right) or 'ttb' (top to bottom). + Requires libraqm. + + .. versionadded:: 4.2.0 + + :param features: A list of OpenType font features to be used during text + layout. This is usually used to turn on optional + font features that are not enabled by default, + for example 'dlig' or 'ss01', but can be also + used to turn off default font features for + example '-liga' to disable ligatures or '-kern' + to disable kerning. To get all supported + features, see + https://docs.microsoft.com/en-us/typography/opentype/spec/featurelist + Requires libraqm. + + .. versionadded:: 4.2.0 + + :param language: Language of the text. Different languages may use + different glyph shapes or ligatures. This parameter tells + the font which language the text is in, and to apply the + correct substitutions as appropriate, if available. + It should be a `BCP 47 language code + ` + Requires libraqm. + + .. versionadded:: 6.0.0 + + :return: A tuple of an internal PIL storage memory instance as defined by the + :py:mod:`PIL.Image.core` interface module, and the text offset, the + gap between the starting coordinate and the first marking + """ size, offset = self.font.getsize(text, direction, features, language) im = fill("L", size, 0) self.font.render(text, im.id, mode == "1", direction, features, language) return im, offset - def font_variant(self, font=None, size=None, index=None, encoding=None, - layout_engine=None): + def font_variant( + self, font=None, size=None, index=None, encoding=None, layout_engine=None + ): """ Create a copy of this FreeTypeFont object, using any specified arguments to override the settings. @@ -203,9 +414,62 @@ class FreeTypeFont(object): size=self.size if size is None else size, index=self.index if index is None else index, encoding=self.encoding if encoding is None else encoding, - layout_engine=layout_engine or self.layout_engine + layout_engine=layout_engine or self.layout_engine, ) + def get_variation_names(self): + """ + :returns: A list of the named styles in a variation font. + :exception IOError: If the font is not a variation font. + """ + try: + names = self.font.getvarnames() + except AttributeError: + raise NotImplementedError("FreeType 2.9.1 or greater is required") + return [name.replace(b"\x00", b"") for name in names] + + def set_variation_by_name(self, name): + """ + :param name: The name of the style. + :exception IOError: If the font is not a variation font. + """ + names = self.get_variation_names() + if not isinstance(name, bytes): + name = name.encode() + index = names.index(name) + + if index == getattr(self, "_last_variation_index", None): + # When the same name is set twice in a row, + # there is an 'unknown freetype error' + # https://savannah.nongnu.org/bugs/?56186 + return + self._last_variation_index = index + + self.font.setvarname(index) + + def get_variation_axes(self): + """ + :returns: A list of the axes in a variation font. + :exception IOError: If the font is not a variation font. + """ + try: + axes = self.font.getvaraxes() + except AttributeError: + raise NotImplementedError("FreeType 2.9.1 or greater is required") + for axis in axes: + axis["name"] = axis["name"].replace(b"\x00", b"") + return axes + + def set_variation_by_axes(self, axes): + """ + :param axes: A list of values for each axis. + :exception IOError: If the font is not a variation font. + """ + try: + self.font.setvaraxes(axes) + except AttributeError: + raise NotImplementedError("FreeType 2.9.1 or greater is required") + class TransposedFont(object): "Wrapper for writing rotated or mirrored text" @@ -250,8 +514,7 @@ def load(filename): return f -def truetype(font=None, size=10, index=0, encoding="", - layout_engine=None): +def truetype(font=None, size=10, index=0, encoding="", layout_engine=None): """ Load a TrueType or OpenType font from a file or file-like object, and create a font object. @@ -276,9 +539,14 @@ def truetype(font=None, size=10, index=0, encoding="", :exception IOError: If the file could not be read. """ - try: + def freetype(font): return FreeTypeFont(font, size, index, encoding, layout_engine) + + try: + return freetype(font) except IOError: + if not isPath(font): + raise ttf_filename = os.path.basename(font) dirs = [] @@ -289,17 +557,19 @@ def truetype(font=None, size=10, index=0, encoding="", windir = os.environ.get("WINDIR") if windir: dirs.append(os.path.join(windir, "fonts")) - elif sys.platform in ('linux', 'linux2'): + elif sys.platform in ("linux", "linux2"): lindirs = os.environ.get("XDG_DATA_DIRS", "") if not lindirs: # According to the freedesktop spec, XDG_DATA_DIRS should # default to /usr/share - lindirs = '/usr/share' - dirs += [os.path.join(lindir, "fonts") - for lindir in lindirs.split(":")] - elif sys.platform == 'darwin': - dirs += ['/Library/Fonts', '/System/Library/Fonts', - os.path.expanduser('~/Library/Fonts')] + lindirs = "/usr/share" + dirs += [os.path.join(lindir, "fonts") for lindir in lindirs.split(":")] + elif sys.platform == "darwin": + dirs += [ + "/Library/Fonts", + "/System/Library/Fonts", + os.path.expanduser("~/Library/Fonts"), + ] ext = os.path.splitext(ttf_filename)[1] first_font_with_a_different_extension = None @@ -307,21 +577,15 @@ def truetype(font=None, size=10, index=0, encoding="", for walkroot, walkdir, walkfilenames in os.walk(directory): for walkfilename in walkfilenames: if ext and walkfilename == ttf_filename: + return freetype(os.path.join(walkroot, walkfilename)) + elif not ext and os.path.splitext(walkfilename)[0] == ttf_filename: fontpath = os.path.join(walkroot, walkfilename) - return FreeTypeFont(fontpath, size, index, - encoding, layout_engine) - elif (not ext and - os.path.splitext(walkfilename)[0] == ttf_filename): - fontpath = os.path.join(walkroot, walkfilename) - if os.path.splitext(fontpath)[1] == '.ttf': - return FreeTypeFont(fontpath, size, index, - encoding, layout_engine) - if not ext \ - and first_font_with_a_different_extension is None: + if os.path.splitext(fontpath)[1] == ".ttf": + return freetype(fontpath) + if not ext and first_font_with_a_different_extension is None: first_font_with_a_different_extension = fontpath if first_font_with_a_different_extension: - return FreeTypeFont(first_font_with_a_different_extension, size, - index, encoding, layout_engine) + return freetype(first_font_with_a_different_extension) raise @@ -357,10 +621,13 @@ def load_default(): """ from io import BytesIO import base64 + f = ImageFont() f._load_pilfont_data( # courB08 - BytesIO(base64.b64decode(b''' + BytesIO( + base64.b64decode( + b""" UElMZm9udAo7Ozs7OzsxMDsKREFUQQoAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA @@ -452,7 +719,13 @@ AJsAEQAGAAAAAP/6AAX//wCbAAoAoAAPAAYAAAAA//oABQABAKAACgClABEABgAA////+AAGAAAA pQAKAKwAEgAGAAD////4AAYAAACsAAoAswASAAYAAP////gABgAAALMACgC6ABIABgAA////+QAG AAAAugAKAMEAEQAGAAD////4AAYAAgDBAAoAyAAUAAYAAP////kABQACAMgACgDOABMABgAA//// +QAGAAIAzgAKANUAEw== -''')), Image.open(BytesIO(base64.b64decode(b''' +""" + ) + ), + Image.open( + BytesIO( + base64.b64decode( + b""" iVBORw0KGgoAAAANSUhEUgAAAx4AAAAUAQAAAAArMtZoAAAEwElEQVR4nABlAJr/AHVE4czCI/4u Mc4b7vuds/xzjz5/3/7u/n9vMe7vnfH/9++vPn/xyf5zhxzjt8GHw8+2d83u8x27199/nxuQ6Od9 M43/5z2I+9n9ZtmDBwMQECDRQw/eQIQohJXxpBCNVE6QCCAAAAD//wBlAJr/AgALyj1t/wINwq0g @@ -476,5 +749,9 @@ evta/58PTEWzr21hufPjA8N+qlnBwAAAAAD//2JiWLci5v1+HmFXDqcnULE/MxgYGBj+f6CaJQAA AAD//2Ji2FrkY3iYpYC5qDeGgeEMAwPDvwQBBoYvcTwOVLMEAAAA//9isDBgkP///0EOg9z35v// Gc/eeW7BwPj5+QGZhANUswMAAAD//2JgqGBgYGBgqEMXlvhMPUsAAAAA//8iYDd1AAAAAP//AwDR w7IkEbzhVQAAAABJRU5ErkJggg== -''')))) +""" + ) + ) + ), + ) return f diff --git a/src/PIL/ImageGrab.py b/src/PIL/ImageGrab.py index d0fe76ef4..faaa65467 100644 --- a/src/PIL/ImageGrab.py +++ b/src/PIL/ImageGrab.py @@ -18,8 +18,6 @@ from . import Image import sys -if sys.platform not in ["win32", "darwin"]: - raise ImportError("ImageGrab is macOS and Windows only") if sys.platform == "win32": grabber = Image.core.grabscreen @@ -27,23 +25,30 @@ elif sys.platform == "darwin": import os import tempfile import subprocess +else: + raise ImportError("ImageGrab is macOS and Windows only") -def grab(bbox=None): +def grab(bbox=None, include_layered_windows=False): if sys.platform == "darwin": - fh, filepath = tempfile.mkstemp('.png') + fh, filepath = tempfile.mkstemp(".png") os.close(fh) - subprocess.call(['screencapture', '-x', filepath]) + subprocess.call(["screencapture", "-x", filepath]) im = Image.open(filepath) im.load() os.unlink(filepath) else: - size, data = grabber() + size, data = grabber(include_layered_windows) im = Image.frombytes( - "RGB", size, data, + "RGB", + size, + data, # RGB, 32-bit line padding, origin lower left corner - "raw", "BGR", (size[0]*3 + 3) & -4, -1 - ) + "raw", + "BGR", + (size[0] * 3 + 3) & -4, + -1, + ) if bbox: im = im.crop(bbox) return im @@ -51,15 +56,16 @@ def grab(bbox=None): def grabclipboard(): if sys.platform == "darwin": - fh, filepath = tempfile.mkstemp('.jpg') + fh, filepath = tempfile.mkstemp(".jpg") os.close(fh) commands = [ - "set theFile to (open for access POSIX file \"" - + filepath + "\" with write permission)", + 'set theFile to (open for access POSIX file "' + + filepath + + '" with write permission)', "try", " write (the clipboard as JPEG picture) to theFile", "end try", - "close access theFile" + "close access theFile", ] script = ["osascript"] for command in commands: @@ -77,5 +83,6 @@ def grabclipboard(): if isinstance(data, bytes): from . import BmpImagePlugin import io + return BmpImagePlugin.DibImageFile(io.BytesIO(data)) return data diff --git a/src/PIL/ImageMath.py b/src/PIL/ImageMath.py index 68247c290..392151c10 100644 --- a/src/PIL/ImageMath.py +++ b/src/PIL/ImageMath.py @@ -22,6 +22,7 @@ try: import builtins except ImportError: import __builtin__ + builtins = __builtin__ VERBOSE = 0 @@ -61,7 +62,7 @@ class _Operand(object): out = Image.new(mode or im1.mode, im1.size, None) im1.load() try: - op = getattr(_imagingmath, op+"_"+im1.mode) + op = getattr(_imagingmath, op + "_" + im1.mode) except AttributeError: raise TypeError("bad operand type for '%s'" % op) _imagingmath.unop(op, out.im.id, im1.im.id) @@ -78,8 +79,7 @@ class _Operand(object): raise ValueError("mode mismatch") if im1.size != im2.size: # crop both arguments to a common size - size = (min(im1.size[0], im2.size[0]), - min(im1.size[1], im2.size[1])) + size = (min(im1.size[0], im2.size[0]), min(im1.size[1], im2.size[1])) if im1.size != size: im1 = im1.crop((0, 0) + size) if im2.size != size: @@ -90,7 +90,7 @@ class _Operand(object): im1.load() im2.load() try: - op = getattr(_imagingmath, op+"_"+im1.mode) + op = getattr(_imagingmath, op + "_" + im1.mode) except AttributeError: raise TypeError("bad operand type for '%s'" % op) _imagingmath.binop(op, out.im.id, im1.im.id, im2.im.id) diff --git a/src/PIL/ImageMode.py b/src/PIL/ImageMode.py index 2b3377a14..596be7b9d 100644 --- a/src/PIL/ImageMode.py +++ b/src/PIL/ImageMode.py @@ -37,20 +37,28 @@ def getmode(mode): # initialize mode cache from . import Image + modes = {} # core modes for m, (basemode, basetype, bands) in Image._MODEINFO.items(): modes[m] = ModeDescriptor(m, bands, basemode, basetype) # extra experimental modes - modes["RGBa"] = ModeDescriptor("RGBa", - ("R", "G", "B", "a"), "RGB", "L") + modes["RGBa"] = ModeDescriptor("RGBa", ("R", "G", "B", "a"), "RGB", "L") modes["LA"] = ModeDescriptor("LA", ("L", "A"), "L", "L") modes["La"] = ModeDescriptor("La", ("L", "a"), "L", "L") modes["PA"] = ModeDescriptor("PA", ("P", "A"), "RGB", "L") # mapping modes - modes["I;16"] = ModeDescriptor("I;16", "I", "L", "L") - modes["I;16L"] = ModeDescriptor("I;16L", "I", "L", "L") - modes["I;16B"] = ModeDescriptor("I;16B", "I", "L", "L") + for i16mode in ( + "I;16", + "I;16S", + "I;16L", + "I;16LS", + "I;16B", + "I;16BS", + "I;16N", + "I;16NS", + ): + modes[i16mode] = ModeDescriptor(i16mode, ("I",), "L", "L") # set global mode cache atomically _modes = modes return _modes[mode] diff --git a/src/PIL/ImageMorph.py b/src/PIL/ImageMorph.py index 54ceb7905..058bb3728 100644 --- a/src/PIL/ImageMorph.py +++ b/src/PIL/ImageMorph.py @@ -12,6 +12,19 @@ import re LUT_SIZE = 1 << 9 +# fmt: off +ROTATION_MATRIX = [ + 6, 3, 0, + 7, 4, 1, + 8, 5, 2, +] +MIRROR_MATRIX = [ + 2, 1, 0, + 5, 4, 3, + 8, 7, 6, +] +# fmt: on + class LutBuilder(object): """A class for building a MorphLut from a descriptive language @@ -48,6 +61,7 @@ class LutBuilder(object): lut = lb.build_lut() """ + def __init__(self, patterns=None, op_name=None): if patterns is not None: self.patterns = patterns @@ -56,20 +70,19 @@ class LutBuilder(object): self.lut = None if op_name is not None: known_patterns = { - 'corner': ['1:(... ... ...)->0', - '4:(00. 01. ...)->1'], - 'dilation4': ['4:(... .0. .1.)->1'], - 'dilation8': ['4:(... .0. .1.)->1', - '4:(... .0. ..1)->1'], - 'erosion4': ['4:(... .1. .0.)->0'], - 'erosion8': ['4:(... .1. .0.)->0', - '4:(... .1. ..0)->0'], - 'edge': ['1:(... ... ...)->0', - '4:(.0. .1. ...)->1', - '4:(01. .1. ...)->1'] + "corner": ["1:(... ... ...)->0", "4:(00. 01. ...)->1"], + "dilation4": ["4:(... .0. .1.)->1"], + "dilation8": ["4:(... .0. .1.)->1", "4:(... .0. ..1)->1"], + "erosion4": ["4:(... .1. .0.)->0"], + "erosion8": ["4:(... .1. .0.)->0", "4:(... .1. ..0)->0"], + "edge": [ + "1:(... ... ...)->0", + "4:(.0. .1. ...)->1", + "4:(01. .1. ...)->1", + ], } if op_name not in known_patterns: - raise Exception('Unknown pattern '+op_name+'!') + raise Exception("Unknown pattern " + op_name + "!") self.patterns = known_patterns[op_name] @@ -88,8 +101,8 @@ class LutBuilder(object): """string_permute takes a pattern and a permutation and returns the string permuted according to the permutation list. """ - assert(len(permutation) == 9) - return ''.join(pattern[p] for p in permutation) + assert len(permutation) == 9 + return "".join(pattern[p] for p in permutation) def _pattern_permute(self, basic_pattern, options, basic_result): """pattern_permute takes a basic pattern and its result and clones @@ -98,32 +111,25 @@ class LutBuilder(object): patterns = [(basic_pattern, basic_result)] # rotations - if '4' in options: + if "4" in options: res = patterns[-1][1] for i in range(4): patterns.append( - (self._string_permute(patterns[-1][0], [6, 3, 0, - 7, 4, 1, - 8, 5, 2]), res)) + (self._string_permute(patterns[-1][0], ROTATION_MATRIX), res) + ) # mirror - if 'M' in options: + if "M" in options: n = len(patterns) for pattern, res in patterns[0:n]: - patterns.append( - (self._string_permute(pattern, [2, 1, 0, - 5, 4, 3, - 8, 7, 6]), res)) + patterns.append((self._string_permute(pattern, MIRROR_MATRIX), res)) # negate - if 'N' in options: + if "N" in options: n = len(patterns) for pattern, res in patterns[0:n]: # Swap 0 and 1 - pattern = (pattern - .replace('0', 'Z') - .replace('1', '0') - .replace('Z', '1')) - res = 1-int(res) + pattern = pattern.replace("0", "Z").replace("1", "0").replace("Z", "1") + res = 1 - int(res) patterns.append((pattern, res)) return patterns @@ -138,22 +144,21 @@ class LutBuilder(object): # Parse and create symmetries of the patterns strings for p in self.patterns: - m = re.search( - r'(\w*):?\s*\((.+?)\)\s*->\s*(\d)', p.replace('\n', '')) + m = re.search(r"(\w*):?\s*\((.+?)\)\s*->\s*(\d)", p.replace("\n", "")) if not m: - raise Exception('Syntax error in pattern "'+p+'"') + raise Exception('Syntax error in pattern "' + p + '"') options = m.group(1) pattern = m.group(2) result = int(m.group(3)) # Get rid of spaces - pattern = pattern.replace(' ', '').replace('\n', '') + pattern = pattern.replace(" ", "").replace("\n", "") patterns += self._pattern_permute(pattern, options, result) # compile the patterns into regular expressions for speed for i, pattern in enumerate(patterns): - p = pattern[0].replace('.', 'X').replace('X', '[01]') + p = pattern[0].replace(".", "X").replace("X", "[01]") p = re.compile(p) patterns[i] = (p, pattern[1]) @@ -163,7 +168,7 @@ class LutBuilder(object): for i in range(LUT_SIZE): # Build the bit pattern bitpattern = bin(i)[2:] - bitpattern = ('0'*(9-len(bitpattern)) + bitpattern)[::-1] + bitpattern = ("0" * (9 - len(bitpattern)) + bitpattern)[::-1] for p, r in patterns: if p.match(bitpattern): @@ -175,10 +180,7 @@ class LutBuilder(object): class MorphOp(object): """A class for binary morphological operators""" - def __init__(self, - lut=None, - op_name=None, - patterns=None): + def __init__(self, lut=None, op_name=None, patterns=None): """Create a binary morphological operator""" self.lut = lut if op_name is not None: @@ -192,13 +194,12 @@ class MorphOp(object): Returns a tuple of the number of changed pixels and the morphed image""" if self.lut is None: - raise Exception('No operator loaded') + raise Exception("No operator loaded") - if image.mode != 'L': - raise Exception('Image must be binary, meaning it must use mode L') + if image.mode != "L": + raise Exception("Image must be binary, meaning it must use mode L") outimage = Image.new(image.mode, image.size, None) - count = _imagingmorph.apply( - bytes(self.lut), image.im.id, outimage.im.id) + count = _imagingmorph.apply(bytes(self.lut), image.im.id, outimage.im.id) return count, outimage def match(self, image): @@ -208,10 +209,10 @@ class MorphOp(object): Returns a list of tuples of (x,y) coordinates of all matching pixels. See :ref:`coordinate-system`.""" if self.lut is None: - raise Exception('No operator loaded') + raise Exception("No operator loaded") - if image.mode != 'L': - raise Exception('Image must be binary, meaning it must use mode L') + if image.mode != "L": + raise Exception("Image must be binary, meaning it must use mode L") return _imagingmorph.match(bytes(self.lut), image.im.id) def get_on_pixels(self, image): @@ -220,24 +221,24 @@ class MorphOp(object): Returns a list of tuples of (x,y) coordinates of all matching pixels. See :ref:`coordinate-system`.""" - if image.mode != 'L': - raise Exception('Image must be binary, meaning it must use mode L') + if image.mode != "L": + raise Exception("Image must be binary, meaning it must use mode L") return _imagingmorph.get_on_pixels(image.im.id) def load_lut(self, filename): """Load an operator from an mrl file""" - with open(filename, 'rb') as f: + with open(filename, "rb") as f: self.lut = bytearray(f.read()) if len(self.lut) != LUT_SIZE: self.lut = None - raise Exception('Wrong size operator file!') + raise Exception("Wrong size operator file!") def save_lut(self, filename): """Save an operator to an mrl file""" if self.lut is None: - raise Exception('No operator loaded') - with open(filename, 'wb') as f: + raise Exception("No operator loaded") + with open(filename, "wb") as f: f.write(self.lut) def set_lut(self, lut): diff --git a/src/PIL/ImageOps.py b/src/PIL/ImageOps.py index ab6a3d2c4..bc976f69b 100644 --- a/src/PIL/ImageOps.py +++ b/src/PIL/ImageOps.py @@ -26,6 +26,7 @@ import functools # # helpers + def _border(border): if isinstance(border, tuple): if len(border) == 2: @@ -40,6 +41,7 @@ def _border(border): def _color(color, mode): if isStringType(color): from . import ImageColor + color = ImageColor.getcolor(color, mode) return color @@ -55,6 +57,7 @@ def _lut(image, lut): else: raise IOError("not supported for this image mode") + # # actions @@ -75,7 +78,7 @@ def autocontrast(image, cutoff=0, ignore=None): histogram = image.histogram() lut = [] for layer in range(0, len(histogram), 256): - h = histogram[layer:layer+256] + h = histogram[layer : layer + 256] if ignore is not None: # get rid of outliers try: @@ -135,8 +138,7 @@ def autocontrast(image, cutoff=0, ignore=None): return _lut(image, lut) -def colorize(image, black, white, mid=None, blackpoint=0, - whitepoint=255, midpoint=127): +def colorize(image, black, white, mid=None, blackpoint=0, whitepoint=255, midpoint=127): """ Colorize grayscale image. This function calculates a color wedge which maps all black pixels in @@ -276,9 +278,7 @@ def crop(image, border=0): :return: An image. """ left, top, right, bottom = _border(border) - return image.crop( - (left, top, image.size[0]-right, image.size[1]-bottom) - ) + return image.crop((left, top, image.size[0] - right, image.size[1] - bottom)) def scale(image, factor, resample=Image.NEAREST): @@ -298,8 +298,7 @@ def scale(image, factor, resample=Image.NEAREST): elif factor <= 0: raise ValueError("the factor must be greater than 0") else: - size = (int(round(factor * image.width)), - int(round(factor * image.height))) + size = (int(round(factor * image.width)), int(round(factor * image.height))) return image.resize(size, resample) @@ -314,9 +313,7 @@ def deform(image, deformer, resample=Image.BILINEAR): in the PIL.Image.transform function. :return: An image. """ - return image.transform( - image.size, Image.MESH, deformer.getmesh(image), resample - ) + return image.transform(image.size, Image.MESH, deformer.getmesh(image), resample) def equalize(image, mask=None): @@ -335,7 +332,7 @@ def equalize(image, mask=None): h = image.histogram(mask) lut = [] for b in range(0, len(h), 256): - histo = [_f for _f in h[b:b+256] if _f] + histo = [_f for _f in h[b : b + 256] if _f] if len(histo) <= 1: lut.extend(list(range(256))) else: @@ -346,7 +343,7 @@ def equalize(image, mask=None): n = step // 2 for i in range(256): lut.append(n // step) - n = n + h[i+b] + n = n + h[i + b] return _lut(image, lut) @@ -417,8 +414,10 @@ def fit(image, size, method=Image.NEAREST, bleed=0.0, centering=(0.5, 0.5)): # number of pixels to trim off on Top and Bottom, Left and Right bleed_pixels = (bleed * image.size[0], bleed * image.size[1]) - live_size = (image.size[0] - bleed_pixels[0] * 2, - image.size[1] - bleed_pixels[1] * 2) + live_size = ( + image.size[0] - bleed_pixels[0] * 2, + image.size[1] - bleed_pixels[1] * 2, + ) # calculate the aspect ratio of the live_size live_size_ratio = float(live_size[0]) / live_size[1] @@ -437,13 +436,10 @@ def fit(image, size, method=Image.NEAREST, bleed=0.0, centering=(0.5, 0.5)): crop_height = live_size[0] / output_ratio # make the crop - crop_left = bleed_pixels[0] + (live_size[0]-crop_width) * centering[0] - crop_top = bleed_pixels[1] + (live_size[1]-crop_height) * centering[1] + crop_left = bleed_pixels[0] + (live_size[0] - crop_width) * centering[0] + crop_top = bleed_pixels[1] + (live_size[1] - crop_height) * centering[1] - crop = ( - crop_left, crop_top, - crop_left + crop_width, crop_top + crop_height - ) + crop = (crop_left, crop_top, crop_left + crop_width, crop_top + crop_height) # resize the image and return it return image.resize(size, method, box=crop) @@ -478,7 +474,7 @@ def invert(image): """ lut = [] for i in range(256): - lut.append(255-i) + lut.append(255 - i) return _lut(image, lut) @@ -501,7 +497,7 @@ def posterize(image, bits): :return: An image. """ lut = [] - mask = ~(2**(8-bits)-1) + mask = ~(2 ** (8 - bits) - 1) for i in range(256): lut.append(i & mask) return _lut(image, lut) @@ -520,7 +516,7 @@ def solarize(image, threshold=128): if i < threshold: lut.append(i) else: - lut.append(255-i) + lut.append(255 - i) return _lut(image, lut) @@ -541,7 +537,7 @@ def exif_transpose(image): 5: Image.TRANSPOSE, 6: Image.ROTATE_270, 7: Image.TRANSVERSE, - 8: Image.ROTATE_90 + 8: Image.ROTATE_90, }.get(orientation) if method is not None: transposed_image = image.transpose(method) diff --git a/src/PIL/ImagePalette.py b/src/PIL/ImagePalette.py index 81e99abbf..a61cc328c 100644 --- a/src/PIL/ImagePalette.py +++ b/src/PIL/ImagePalette.py @@ -38,11 +38,12 @@ class ImagePalette(object): def __init__(self, mode="RGB", palette=None, size=0): self.mode = mode self.rawmode = None # if set, palette contains raw data - self.palette = palette or bytearray(range(256))*len(self.mode) + self.palette = palette or bytearray(range(256)) * len(self.mode) self.colors = {} self.dirty = None - if ((size == 0 and len(self.mode)*256 != len(self.palette)) or - (size != 0 and size != len(self.palette))): + if (size == 0 and len(self.mode) * 256 != len(self.palette)) or ( + size != 0 and size != len(self.palette) + ): raise ValueError("wrong palette size") def copy(self): @@ -78,7 +79,7 @@ class ImagePalette(object): if isinstance(self.palette, bytes): return self.palette arr = array.array("B", self.palette) - if hasattr(arr, 'tobytes'): + if hasattr(arr, "tobytes"): return arr.tobytes() return arr.tostring() @@ -104,8 +105,8 @@ class ImagePalette(object): raise ValueError("cannot allocate more than 256 colors") self.colors[color] = index self.palette[index] = color[0] - self.palette[index+256] = color[1] - self.palette[index+512] = color[2] + self.palette[index + 256] = color[1] + self.palette[index + 512] = color[2] self.dirty = 1 return index else: @@ -124,7 +125,7 @@ class ImagePalette(object): fp.write("# Mode: %s\n" % self.mode) for i in range(256): fp.write("%d" % i) - for j in range(i*len(self.mode), (i+1)*len(self.mode)): + for j in range(i * len(self.mode), (i + 1) * len(self.mode)): try: fp.write(" %d" % self.palette[j]) except IndexError: @@ -136,6 +137,7 @@ class ImagePalette(object): # -------------------------------------------------------------------- # Internal + def raw(rawmode, data): palette = ImagePalette() palette.rawmode = rawmode @@ -147,11 +149,12 @@ def raw(rawmode, data): # -------------------------------------------------------------------- # Factories + def make_linear_lut(black, white): lut = [] if black == 0: for i in range(256): - lut.append(white*i//255) + lut.append(white * i // 255) else: raise NotImplementedError # FIXME return lut @@ -172,8 +175,9 @@ def negative(mode="RGB"): def random(mode="RGB"): from random import randint + palette = [] - for i in range(256*len(mode)): + for i in range(256 * len(mode)): palette.append(randint(0, 255)) return ImagePalette(mode, palette) @@ -199,7 +203,7 @@ def load(filename): for paletteHandler in [ GimpPaletteFile.GimpPaletteFile, GimpGradientFile.GimpGradientFile, - PaletteFile.PaletteFile + PaletteFile.PaletteFile, ]: try: fp.seek(0) diff --git a/src/PIL/ImageQt.py b/src/PIL/ImageQt.py index 02ce6354e..b615d6dd4 100644 --- a/src/PIL/ImageQt.py +++ b/src/PIL/ImageQt.py @@ -22,12 +22,7 @@ from io import BytesIO import sys import warnings -qt_versions = [ - ['5', 'PyQt5'], - ['side2', 'PySide2'], - ['4', 'PyQt4'], - ['side', 'PySide'] -] +qt_versions = [["5", "PyQt5"], ["side2", "PySide2"], ["4", "PyQt4"], ["side", "PySide"]] WARNING_TEXT = ( "Support for EOL {} is deprecated and will be removed in a future version. " @@ -35,22 +30,21 @@ WARNING_TEXT = ( ) # If a version has already been imported, attempt it first -qt_versions.sort(key=lambda qt_version: qt_version[1] in sys.modules, - reverse=True) +qt_versions.sort(key=lambda qt_version: qt_version[1] in sys.modules, reverse=True) for qt_version, qt_module in qt_versions: try: - if qt_module == 'PyQt5': + if qt_module == "PyQt5": from PyQt5.QtGui import QImage, qRgba, QPixmap from PyQt5.QtCore import QBuffer, QIODevice - elif qt_module == 'PySide2': + elif qt_module == "PySide2": from PySide2.QtGui import QImage, qRgba, QPixmap from PySide2.QtCore import QBuffer, QIODevice - elif qt_module == 'PyQt4': + elif qt_module == "PyQt4": from PyQt4.QtGui import QImage, qRgba, QPixmap from PyQt4.QtCore import QBuffer, QIODevice warnings.warn(WARNING_TEXT.format(qt_module), DeprecationWarning) - elif qt_module == 'PySide': + elif qt_module == "PySide": from PySide.QtGui import QImage, qRgba, QPixmap from PySide.QtCore import QBuffer, QIODevice @@ -68,7 +62,7 @@ def rgb(r, g, b, a=255): """(Internal) Turns an RGB color into a Qt compatible color integer.""" # use qRgb to pack the colors, and then turn the resulting long # into a negative integer with the same bitpattern. - return (qRgba(r, g, b, a) & 0xffffffff) + return qRgba(r, g, b, a) & 0xFFFFFFFF def fromqimage(im): @@ -81,9 +75,9 @@ def fromqimage(im): # preserve alpha channel with png # otherwise ppm is more friendly with Image.open if im.hasAlphaChannel(): - im.save(buffer, 'png') + im.save(buffer, "png") else: - im.save(buffer, 'ppm') + im.save(buffer, "ppm") b = BytesIO() try: @@ -116,11 +110,7 @@ def align8to32(bytes, width, mode): converts each scanline of data from 8 bit to 32 bit aligned """ - bits_per_pixel = { - '1': 1, - 'L': 8, - 'P': 8, - }[mode] + bits_per_pixel = {"1": 1, "L": 8, "P": 8}[mode] # calculate bytes per line and the extra padding if needed bits_per_line = bits_per_pixel * width @@ -135,10 +125,12 @@ def align8to32(bytes, width, mode): new_data = [] for i in range(len(bytes) // bytes_per_line): - new_data.append(bytes[i*bytes_per_line:(i+1)*bytes_per_line] - + b'\x00' * extra_padding) + new_data.append( + bytes[i * bytes_per_line : (i + 1) * bytes_per_line] + + b"\x00" * extra_padding + ) - return b''.join(new_data) + return b"".join(new_data) def _toqclass_helper(im): @@ -167,7 +159,7 @@ def _toqclass_helper(im): colortable = [] palette = im.getpalette() for i in range(0, len(palette), 3): - colortable.append(rgb(*palette[i:i+3])) + colortable.append(rgb(*palette[i : i + 3])) elif im.mode == "RGB": data = im.tobytes("raw", "BGRX") format = QImage.Format_RGB32 @@ -183,14 +175,12 @@ def _toqclass_helper(im): raise ValueError("unsupported image mode %r" % im.mode) __data = data or align8to32(im.tobytes(), im.size[0], im.mode) - return { - 'data': __data, 'im': im, 'format': format, 'colortable': colortable - } + return {"data": __data, "im": im, "format": format, "colortable": colortable} if qt_is_installed: - class ImageQt(QImage): + class ImageQt(QImage): def __init__(self, im): """ An PIL image wrapper for Qt. This is a subclass of PyQt's QImage @@ -204,12 +194,16 @@ if qt_is_installed: # All QImage constructors that take data operate on an existing # buffer, so this buffer has to hang on for the life of the image. # Fixes https://github.com/python-pillow/Pillow/issues/1370 - self.__data = im_data['data'] - QImage.__init__(self, - self.__data, im_data['im'].size[0], - im_data['im'].size[1], im_data['format']) - if im_data['colortable']: - self.setColorTable(im_data['colortable']) + self.__data = im_data["data"] + QImage.__init__( + self, + self.__data, + im_data["im"].size[0], + im_data["im"].size[1], + im_data["format"], + ) + if im_data["colortable"]: + self.setColorTable(im_data["colortable"]) def toqimage(im): @@ -222,8 +216,8 @@ def toqpixmap(im): # result = QPixmap(im_data['im'].size[0], im_data['im'].size[1]) # result.loadFromData(im_data['data']) # Fix some strange bug that causes - if im.mode == 'RGB': - im = im.convert('RGBA') + if im.mode == "RGB": + im = im.convert("RGBA") qimage = toqimage(im) return QPixmap.fromImage(qimage) diff --git a/src/PIL/ImageSequence.py b/src/PIL/ImageSequence.py index 1fc6e5de1..84199fe27 100644 --- a/src/PIL/ImageSequence.py +++ b/src/PIL/ImageSequence.py @@ -32,7 +32,7 @@ class Iterator(object): if not hasattr(im, "seek"): raise AttributeError("im must have seek method") self.im = im - self.position = 0 + self.position = getattr(self.im, "_min_frame", 0) def __getitem__(self, ix): try: diff --git a/src/PIL/ImageShow.py b/src/PIL/ImageShow.py index bbd841db7..82c60489b 100644 --- a/src/PIL/ImageShow.py +++ b/src/PIL/ImageShow.py @@ -63,16 +63,12 @@ class Viewer(object): def show(self, image, **options): # save temporary image to disk - if image.mode[:4] == "I;16": - # @PIL88 @PIL101 - # "I;16" isn't an 'official' mode, but we still want to - # provide a simple way to show 16-bit images. - base = "L" - # FIXME: auto-contrast if max() > 255? - else: + if not ( + image.mode in ("1", "RGBA") or (self.format == "PNG" and image.mode == "LA") + ): base = Image.getmodebase(image.mode) - if base != image.mode and image.mode != "1" and image.mode != "RGBA": - image = image.convert(base) + if image.mode != base: + image = image.convert(base) return self.show_image(image, **options) @@ -101,6 +97,7 @@ class Viewer(object): os.system(self.get_command(file, **options)) return 1 + # -------------------------------------------------------------------- @@ -110,9 +107,11 @@ if sys.platform == "win32": format = "BMP" def get_command(self, file, **options): - return ('start "Pillow" /WAIT "%s" ' - '&& ping -n 2 127.0.0.1 >NUL ' - '&& del /f "%s"' % (file, file)) + return ( + 'start "Pillow" /WAIT "%s" ' + "&& ping -n 2 127.0.0.1 >NUL " + '&& del /f "%s"' % (file, file) + ) register(WindowsViewer) @@ -120,28 +119,30 @@ elif sys.platform == "darwin": class MacViewer(Viewer): format = "PNG" - options = {'compress_level': 1} + options = {"compress_level": 1} def get_command(self, file, **options): # on darwin open returns immediately resulting in the temp # file removal while app is opening - command = "open -a /Applications/Preview.app" - command = "(%s %s; sleep 20; rm -f %s)&" % (command, quote(file), - quote(file)) + command = "open -a Preview.app" + command = "(%s %s; sleep 20; rm -f %s)&" % ( + command, + quote(file), + quote(file), + ) return command def show_file(self, file, **options): """Display given file""" fd, path = tempfile.mkstemp() - with os.fdopen(fd, 'w') as f: + with os.fdopen(fd, "w") as f: f.write(file) with open(path, "r") as f: - subprocess.Popen([ - 'im=$(cat);' - 'open -a /Applications/Preview.app $im;' - 'sleep 20;' - 'rm -f $im' - ], shell=True, stdin=f) + subprocess.Popen( + ["im=$(cat); open -a Preview.app $im; sleep 20; rm -f $im"], + shell=True, + stdin=f, + ) os.remove(path) return 1 @@ -163,7 +164,7 @@ else: class UnixViewer(Viewer): format = "PNG" - options = {'compress_level': 1} + options = {"compress_level": 1} def get_command(self, file, **options): command = self.get_command_ex(file, **options)[0] @@ -172,15 +173,13 @@ else: def show_file(self, file, **options): """Display given file""" fd, path = tempfile.mkstemp() - with os.fdopen(fd, 'w') as f: + with os.fdopen(fd, "w") as f: f.write(file) with open(path, "r") as f: command = self.get_command_ex(file, **options)[0] - subprocess.Popen([ - 'im=$(cat);' + - command+' $im;' - 'rm -f $im' - ], shell=True, stdin=f) + subprocess.Popen( + ["im=$(cat);" + command + " $im;" "rm -f $im"], shell=True, stdin=f + ) os.remove(path) return 1 diff --git a/src/PIL/ImageStat.py b/src/PIL/ImageStat.py index c926a7416..52b9961ee 100644 --- a/src/PIL/ImageStat.py +++ b/src/PIL/ImageStat.py @@ -27,7 +27,6 @@ import functools class Stat(object): - def __init__(self, image_or_list, mask=None): try: if mask: @@ -71,7 +70,7 @@ class Stat(object): v = [] for i in range(0, len(self.h), 256): - v.append(functools.reduce(operator.add, self.h[i:i+256])) + v.append(functools.reduce(operator.add, self.h[i : i + 256])) return v def _getsum(self): @@ -110,10 +109,10 @@ class Stat(object): v = [] for i in self.bands: s = 0 - half = self.count[i]//2 + half = self.count[i] // 2 b = i * 256 for j in range(256): - s = s + self.h[b+j] + s = s + self.h[b + j] if s > half: break v.append(j) @@ -133,7 +132,7 @@ class Stat(object): v = [] for i in self.bands: n = self.count[i] - v.append((self.sum2[i]-(self.sum[i]**2.0)/n)/n) + v.append((self.sum2[i] - (self.sum[i] ** 2.0) / n) / n) return v def _getstddev(self): diff --git a/src/PIL/ImageTk.py b/src/PIL/ImageTk.py index cc0c5294c..fd480007a 100644 --- a/src/PIL/ImageTk.py +++ b/src/PIL/ImageTk.py @@ -67,6 +67,7 @@ def _get_image_from_kw(kw): # -------------------------------------------------------------------- # PhotoImage + class PhotoImage(object): """ A Tkinter-compatible photo image. This can be used @@ -183,17 +184,18 @@ class PhotoImage(object): # activate Tkinter hook try: from . import _imagingtk + try: - if hasattr(tk, 'interp'): + if hasattr(tk, "interp"): # Required for PyPy, which always has CFFI installed from cffi import FFI + ffi = FFI() # PyPy is using an FFI CDATA element # (Pdb) self.tk.interp # - _imagingtk.tkinit( - int(ffi.cast("uintptr_t", tk.interp)), 1) + _imagingtk.tkinit(int(ffi.cast("uintptr_t", tk.interp)), 1) else: _imagingtk.tkinit(tk.interpaddr(), 1) except AttributeError: @@ -202,6 +204,7 @@ class PhotoImage(object): except (ImportError, AttributeError, tkinter.TclError): raise # configuration problem; cannot attach to Tkinter + # -------------------------------------------------------------------- # BitmapImage @@ -275,10 +278,13 @@ class BitmapImage(object): def getimage(photo): - """ This function is unimplemented """ - """Copies the contents of a PhotoImage to a PIL image memory.""" - photo.tk.call("PyImagingPhotoGet", photo) + im = Image.new("RGBA", (photo.width(), photo.height())) + block = im.im + + photo.tk.call("PyImagingPhotoGet", photo, block.id) + + return im def _show(image, title): @@ -290,8 +296,7 @@ def _show(image, title): self.image = BitmapImage(im, foreground="white", master=master) else: self.image = PhotoImage(im, master=master) - tkinter.Label.__init__(self, master, image=self.image, - bg="black", bd=0) + tkinter.Label.__init__(self, master, image=self.image, bg="black", bd=0) if not tkinter._default_root: raise IOError("tkinter not initialized") diff --git a/src/PIL/ImageTransform.py b/src/PIL/ImageTransform.py index c3f6af8b5..77791ab72 100644 --- a/src/PIL/ImageTransform.py +++ b/src/PIL/ImageTransform.py @@ -46,6 +46,7 @@ class AffineTransform(Transform): :param matrix: A 6-tuple (a, b, c, d, e, f) containing the first two rows from an affine transform matrix. """ + method = Image.AFFINE @@ -67,6 +68,7 @@ class ExtentTransform(Transform): :param bbox: A 4-tuple (x0, y0, x1, y1) which specifies two points in the input image's coordinate system. See :ref:`coordinate-system`. """ + method = Image.EXTENT @@ -83,6 +85,7 @@ class QuadTransform(Transform): upper left, lower left, lower right, and upper right corner of the source quadrilateral. """ + method = Image.QUAD @@ -95,4 +98,5 @@ class MeshTransform(Transform): :param data: A list of (bbox, quad) tuples. """ + method = Image.MESH diff --git a/src/PIL/ImageWin.py b/src/PIL/ImageWin.py index 9b86270bc..ed2c18ec4 100644 --- a/src/PIL/ImageWin.py +++ b/src/PIL/ImageWin.py @@ -26,6 +26,7 @@ class HDC(object): :py:meth:`~PIL.ImageWin.Dib.draw` and :py:meth:`~PIL.ImageWin.Dib.expose` methods. """ + def __init__(self, dc): self.dc = dc @@ -39,6 +40,7 @@ class HWND(object): :py:meth:`~PIL.ImageWin.Dib.draw` and :py:meth:`~PIL.ImageWin.Dib.expose` methods, instead of a DC. """ + def __init__(self, wnd): self.wnd = wnd @@ -190,7 +192,7 @@ class Window(object): def __init__(self, title="PIL", width=None, height=None): self.hwnd = Image.core.createwindow( title, self.__dispatcher, width or 0, height or 0 - ) + ) def __dispatcher(self, action, *args): return getattr(self, "ui_handle_" + action)(*args) diff --git a/src/PIL/ImtImagePlugin.py b/src/PIL/ImtImagePlugin.py index 18b7dd839..a9e991fbe 100644 --- a/src/PIL/ImtImagePlugin.py +++ b/src/PIL/ImtImagePlugin.py @@ -33,6 +33,7 @@ field = re.compile(br"([a-z]*) ([^ \r\n]*)") ## # Image plugin for IM Tools images. + class ImtImageFile(ImageFile.ImageFile): format = "IMT" @@ -55,12 +56,12 @@ class ImtImageFile(ImageFile.ImageFile): if not s: break - if s == b'\x0C': + if s == b"\x0C": # image data begins - self.tile = [("raw", (0, 0)+self.size, - self.fp.tell(), - (self.mode, 0, 1))] + self.tile = [ + ("raw", (0, 0) + self.size, self.fp.tell(), (self.mode, 0, 1)) + ] break diff --git a/src/PIL/IptcImagePlugin.py b/src/PIL/IptcImagePlugin.py index 371bb3acf..8b2f2ef32 100644 --- a/src/PIL/IptcImagePlugin.py +++ b/src/PIL/IptcImagePlugin.py @@ -26,10 +26,7 @@ import tempfile # PIL.__version__ instead. __version__ = "0.3" -COMPRESSION = { - 1: "raw", - 5: "jpeg" -} +COMPRESSION = {1: "raw", 5: "jpeg"} PAD = o8(0) * 4 @@ -37,13 +34,14 @@ PAD = o8(0) * 4 # # Helpers + def i(c): return i32((PAD + c)[-4:]) def dump(c): for i in c: - print("%02x" % i8(i), end=' ') + print("%02x" % i8(i), end=" ") print() @@ -51,6 +49,7 @@ def dump(c): # Image plugin for IPTC/NAA datastreams. To read IPTC/NAA fields # from TIFF and JPEG files, use the getiptcinfo function. + class IptcImageFile(ImageFile.ImageFile): format = "IPTC" @@ -79,7 +78,7 @@ class IptcImageFile(ImageFile.ImageFile): elif size == 128: size = 0 elif size > 128: - size = i(self.fp.read(size-128)) + size = i(self.fp.read(size - 128)) else: size = i16(s[3:]) @@ -109,7 +108,7 @@ class IptcImageFile(ImageFile.ImageFile): layers = i8(self.info[(3, 60)][0]) component = i8(self.info[(3, 60)][1]) if (3, 65) in self.info: - id = i8(self.info[(3, 65)][0])-1 + id = i8(self.info[(3, 65)][0]) - 1 else: id = 0 if layers == 1 and not component: @@ -130,8 +129,9 @@ class IptcImageFile(ImageFile.ImageFile): # tile if tag == (8, 10): - self.tile = [("iptc", (compression, offset), - (0, 0, self.size[0], self.size[1]))] + self.tile = [ + ("iptc", (compression, offset), (0, 0, self.size[0], self.size[1])) + ] def load(self): @@ -216,6 +216,7 @@ def getiptcinfo(im): # create an IptcImagePlugin object without initializing it class FakeImage(object): pass + im = FakeImage() im.__class__ = IptcImageFile diff --git a/src/PIL/Jpeg2KImagePlugin.py b/src/PIL/Jpeg2KImagePlugin.py index 9645f8ef0..2a6b77c34 100644 --- a/src/PIL/Jpeg2KImagePlugin.py +++ b/src/PIL/Jpeg2KImagePlugin.py @@ -27,30 +27,29 @@ def _parse_codestream(fp): count from the SIZ marker segment, returning a PIL (size, mode) tuple.""" hdr = fp.read(2) - lsiz = struct.unpack('>H', hdr)[0] + lsiz = struct.unpack(">H", hdr)[0] siz = hdr + fp.read(lsiz - 2) - lsiz, rsiz, xsiz, ysiz, xosiz, yosiz, xtsiz, ytsiz, \ - xtosiz, ytosiz, csiz \ - = struct.unpack_from('>HHIIIIIIIIH', siz) - ssiz = [None]*csiz - xrsiz = [None]*csiz - yrsiz = [None]*csiz + lsiz, rsiz, xsiz, ysiz, xosiz, yosiz, _, _, _, _, csiz = struct.unpack_from( + ">HHIIIIIIIIH", siz + ) + ssiz = [None] * csiz + xrsiz = [None] * csiz + yrsiz = [None] * csiz for i in range(csiz): - ssiz[i], xrsiz[i], yrsiz[i] \ - = struct.unpack_from('>BBB', siz, 36 + 3 * i) + ssiz[i], xrsiz[i], yrsiz[i] = struct.unpack_from(">BBB", siz, 36 + 3 * i) size = (xsiz - xosiz, ysiz - yosiz) if csiz == 1: - if (yrsiz[0] & 0x7f) > 8: - mode = 'I;16' + if (yrsiz[0] & 0x7F) > 8: + mode = "I;16" else: - mode = 'L' + mode = "L" elif csiz == 2: - mode = 'LA' + mode = "LA" elif csiz == 3: - mode = 'RGB' + mode = "RGB" elif csiz == 4: - mode = 'RGBA' + mode = "RGBA" else: mode = None @@ -65,28 +64,28 @@ def _parse_jp2_header(fp): header = None mimetype = None while True: - lbox, tbox = struct.unpack('>I4s', fp.read(8)) + lbox, tbox = struct.unpack(">I4s", fp.read(8)) if lbox == 1: - lbox = struct.unpack('>Q', fp.read(8))[0] + lbox = struct.unpack(">Q", fp.read(8))[0] hlen = 16 else: hlen = 8 if lbox < hlen: - raise SyntaxError('Invalid JP2 header length') + raise SyntaxError("Invalid JP2 header length") - if tbox == b'jp2h': + if tbox == b"jp2h": header = fp.read(lbox - hlen) break - elif tbox == b'ftyp': - if fp.read(4) == b'jpx ': - mimetype = 'image/jpx' + elif tbox == b"ftyp": + if fp.read(4) == b"jpx ": + mimetype = "image/jpx" fp.seek(lbox - hlen - 4, os.SEEK_CUR) else: fp.seek(lbox - hlen, os.SEEK_CUR) if header is None: - raise SyntaxError('could not find JP2 header') + raise SyntaxError("could not find JP2 header") size = None mode = None @@ -95,58 +94,57 @@ def _parse_jp2_header(fp): hio = io.BytesIO(header) while True: - lbox, tbox = struct.unpack('>I4s', hio.read(8)) + lbox, tbox = struct.unpack(">I4s", hio.read(8)) if lbox == 1: - lbox = struct.unpack('>Q', hio.read(8))[0] + lbox = struct.unpack(">Q", hio.read(8))[0] hlen = 16 else: hlen = 8 content = hio.read(lbox - hlen) - if tbox == b'ihdr': - height, width, nc, bpc, c, unkc, ipr \ - = struct.unpack('>IIHBBBB', content) + if tbox == b"ihdr": + height, width, nc, bpc, c, unkc, ipr = struct.unpack(">IIHBBBB", content) size = (width, height) if unkc: - if nc == 1 and (bpc & 0x7f) > 8: - mode = 'I;16' + if nc == 1 and (bpc & 0x7F) > 8: + mode = "I;16" elif nc == 1: - mode = 'L' + mode = "L" elif nc == 2: - mode = 'LA' + mode = "LA" elif nc == 3: - mode = 'RGB' + mode = "RGB" elif nc == 4: - mode = 'RGBA' + mode = "RGBA" break - elif tbox == b'colr': - meth, prec, approx = struct.unpack_from('>BBB', content) + elif tbox == b"colr": + meth, prec, approx = struct.unpack_from(">BBB", content) if meth == 1: - cs = struct.unpack_from('>I', content, 3)[0] - if cs == 16: # sRGB - if nc == 1 and (bpc & 0x7f) > 8: - mode = 'I;16' + cs = struct.unpack_from(">I", content, 3)[0] + if cs == 16: # sRGB + if nc == 1 and (bpc & 0x7F) > 8: + mode = "I;16" elif nc == 1: - mode = 'L' + mode = "L" elif nc == 3: - mode = 'RGB' + mode = "RGB" elif nc == 4: - mode = 'RGBA' + mode = "RGBA" break elif cs == 17: # grayscale - if nc == 1 and (bpc & 0x7f) > 8: - mode = 'I;16' + if nc == 1 and (bpc & 0x7F) > 8: + mode = "I;16" elif nc == 1: - mode = 'L' + mode = "L" elif nc == 2: - mode = 'LA' + mode = "LA" break elif cs == 18: # sYCC if nc == 3: - mode = 'RGB' + mode = "RGB" elif nc == 4: - mode = 'RGBA' + mode = "RGBA" break if size is None or mode is None: @@ -154,6 +152,7 @@ def _parse_jp2_header(fp): return (size, mode, mimetype) + ## # Image plugin for JPEG2000 images. @@ -164,21 +163,21 @@ class Jpeg2KImageFile(ImageFile.ImageFile): def _open(self): sig = self.fp.read(4) - if sig == b'\xff\x4f\xff\x51': + if sig == b"\xff\x4f\xff\x51": self.codec = "j2k" self._size, self.mode = _parse_codestream(self.fp) else: sig = sig + self.fp.read(8) - if sig == b'\x00\x00\x00\x0cjP \x0d\x0a\x87\x0a': + if sig == b"\x00\x00\x00\x0cjP \x0d\x0a\x87\x0a": self.codec = "jp2" header = _parse_jp2_header(self.fp) self._size, self.mode, self.custom_mimetype = header else: - raise SyntaxError('not a JPEG 2000 file') + raise SyntaxError("not a JPEG 2000 file") if self.size is None or self.mode is None: - raise SyntaxError('unable to determine size/mode') + raise SyntaxError("unable to determine size/mode") self.reduce = 0 self.layers = 0 @@ -199,15 +198,23 @@ class Jpeg2KImageFile(ImageFile.ImageFile): except Exception: length = -1 - self.tile = [('jpeg2k', (0, 0) + self.size, 0, - (self.codec, self.reduce, self.layers, fd, length))] + self.tile = [ + ( + "jpeg2k", + (0, 0) + self.size, + 0, + (self.codec, self.reduce, self.layers, fd, length), + ) + ] def load(self): if self.reduce: power = 1 << self.reduce adjust = power >> 1 - self._size = (int((self.size[0] + adjust) / power), - int((self.size[1] + adjust) / power)) + self._size = ( + int((self.size[0] + adjust) / power), + int((self.size[1] + adjust) / power), + ) if self.tile: # Update the reduce and layers settings @@ -219,40 +226,47 @@ class Jpeg2KImageFile(ImageFile.ImageFile): def _accept(prefix): - return (prefix[:4] == b'\xff\x4f\xff\x51' or - prefix[:12] == b'\x00\x00\x00\x0cjP \x0d\x0a\x87\x0a') + return ( + prefix[:4] == b"\xff\x4f\xff\x51" + or prefix[:12] == b"\x00\x00\x00\x0cjP \x0d\x0a\x87\x0a" + ) # ------------------------------------------------------------ # Save support + def _save(im, fp, filename): - if filename.endswith('.j2k'): - kind = 'j2k' + if filename.endswith(".j2k"): + kind = "j2k" else: - kind = 'jp2' + kind = "jp2" # Get the keyword arguments info = im.encoderinfo - offset = info.get('offset', None) - tile_offset = info.get('tile_offset', None) - tile_size = info.get('tile_size', None) - quality_mode = info.get('quality_mode', 'rates') - quality_layers = info.get('quality_layers', None) + offset = info.get("offset", None) + tile_offset = info.get("tile_offset", None) + tile_size = info.get("tile_size", None) + quality_mode = info.get("quality_mode", "rates") + quality_layers = info.get("quality_layers", None) if quality_layers is not None and not ( - isinstance(quality_layers, (list, tuple)) and - all([isinstance(quality_layer, (int, float)) - for quality_layer in quality_layers]) + isinstance(quality_layers, (list, tuple)) + and all( + [ + isinstance(quality_layer, (int, float)) + for quality_layer in quality_layers + ] + ) ): - raise ValueError('quality_layers must be a sequence of numbers') + raise ValueError("quality_layers must be a sequence of numbers") - num_resolutions = info.get('num_resolutions', 0) - cblk_size = info.get('codeblock_size', None) - precinct_size = info.get('precinct_size', None) - irreversible = info.get('irreversible', False) - progression = info.get('progression', 'LRCP') - cinema_mode = info.get('cinema_mode', 'no') + num_resolutions = info.get("num_resolutions", 0) + cblk_size = info.get("codeblock_size", None) + precinct_size = info.get("precinct_size", None) + irreversible = info.get("irreversible", False) + progression = info.get("progression", "LRCP") + cinema_mode = info.get("cinema_mode", "no") fd = -1 if hasattr(fp, "fileno"): @@ -273,10 +287,11 @@ def _save(im, fp, filename): irreversible, progression, cinema_mode, - fd + fd, ) - ImageFile._save(im, fp, [('jpeg2k', (0, 0)+im.size, 0, kind)]) + ImageFile._save(im, fp, [("jpeg2k", (0, 0) + im.size, 0, kind)]) + # ------------------------------------------------------------ # Registry stuff @@ -285,7 +300,8 @@ def _save(im, fp, filename): Image.register_open(Jpeg2KImageFile.format, Jpeg2KImageFile, _accept) Image.register_save(Jpeg2KImageFile.format, _save) -Image.register_extensions(Jpeg2KImageFile.format, - [".jp2", ".j2k", ".jpc", ".jpf", ".jpx", ".j2c"]) +Image.register_extensions( + Jpeg2KImageFile.format, [".jp2", ".j2k", ".jpc", ".jpf", ".jpx", ".j2c"] +) -Image.register_mime(Jpeg2KImageFile.format, 'image/jp2') +Image.register_mime(Jpeg2KImageFile.format, "image/jp2") diff --git a/src/PIL/JpegImagePlugin.py b/src/PIL/JpegImagePlugin.py index 3caedbd92..c8f987415 100644 --- a/src/PIL/JpegImagePlugin.py +++ b/src/PIL/JpegImagePlugin.py @@ -51,8 +51,9 @@ __version__ = "0.6" # # Parser + def Skip(self, marker): - n = i16(self.fp.read(2))-2 + n = i16(self.fp.read(2)) - 2 ImageFile._safe_read(self.fp, n) @@ -61,7 +62,7 @@ def APP(self, marker): # Application marker. Store these in the APP dictionary. # Also look for well-known application markers. - n = i16(self.fp.read(2))-2 + n = i16(self.fp.read(2)) - 2 s = ImageFile._safe_read(self.fp, n) app = "APP%d" % (marker & 15) @@ -110,7 +111,7 @@ def APP(self, marker): # parse the image resource block offset = 0 photoshop = {} - while blocks[offset:offset+4] == b"8BIM": + while blocks[offset : offset + 4] == b"8BIM": offset += 4 # resource code code = i16(blocks, offset) @@ -124,19 +125,19 @@ def APP(self, marker): # resource data block size = i32(blocks, offset) offset += 4 - data = blocks[offset:offset+size] + data = blocks[offset : offset + size] if code == 0x03ED: # ResolutionInfo data = { - 'XResolution': i32(data[:4]) / 65536, - 'DisplayedUnitsX': i16(data[4:8]), - 'YResolution': i32(data[8:12]) / 65536, - 'DisplayedUnitsY': i16(data[12:]), + "XResolution": i32(data[:4]) / 65536, + "DisplayedUnitsX": i16(data[4:8]), + "YResolution": i32(data[8:12]) / 65536, + "DisplayedUnitsY": i16(data[12:]), } photoshop[code] = data offset = offset + size if offset & 1: offset += 1 - self.info["photoshop"] = photoshop + self.info["photoshop"] = photoshop elif marker == 0xFFEE and s[:5] == b"Adobe": self.info["adobe"] = i16(s, 5) # extract Adobe custom properties @@ -177,7 +178,7 @@ def APP(self, marker): def COM(self, marker): # # Comment marker. Store these in the APP dictionary. - n = i16(self.fp.read(2))-2 + n = i16(self.fp.read(2)) - 2 s = ImageFile._safe_read(self.fp, n) self.app["COM"] = s # compatibility @@ -192,7 +193,7 @@ def SOF(self, marker): # mode. Note that this could be made a bit brighter, by # looking for JFIF and Adobe APP markers. - n = i16(self.fp.read(2))-2 + n = i16(self.fp.read(2)) - 2 s = ImageFile._safe_read(self.fp, n) self._size = i16(s[3:]), i16(s[1:]) @@ -227,9 +228,9 @@ def SOF(self, marker): self.icclist = None for i in range(6, len(s), 3): - t = s[i:i+3] + t = s[i : i + 3] # 4-tuples: id, vsamp, hsamp, qtable - self.layer.append((t[0], i8(t[1])//16, i8(t[1]) & 15, i8(t[2]))) + self.layer.append((t[0], i8(t[1]) // 16, i8(t[1]) & 15, i8(t[2]))) def DQT(self, marker): @@ -241,13 +242,13 @@ def DQT(self, marker): # FIXME: The quantization tables can be used to estimate the # compression quality. - n = i16(self.fp.read(2))-2 + n = i16(self.fp.read(2)) - 2 s = ImageFile._safe_read(self.fp, n) while len(s): if len(s) < 65: raise SyntaxError("bad quantization table marker") v = i8(s[0]) - if v//16 == 0: + if v // 16 == 0: self.quantization[v & 15] = array.array("B", s[1:65]) s = s[65:] else: @@ -321,7 +322,7 @@ MARKER = { 0xFFFB: ("JPG11", "Extension 11", None), 0xFFFC: ("JPG12", "Extension 12", None), 0xFFFD: ("JPG13", "Extension 13", None), - 0xFFFE: ("COM", "Comment", COM) + 0xFFFE: ("COM", "Comment", COM), } @@ -332,6 +333,7 @@ def _accept(prefix): ## # Image plugin for JPEG and JFIF images. + class JpegImageFile(ImageFile.ImageFile): format = "JPEG" @@ -375,8 +377,7 @@ class JpegImageFile(ImageFile.ImageFile): rawmode = self.mode if self.mode == "CMYK": rawmode = "CMYK;I" # assume adobe conventions - self.tile = [("jpeg", (0, 0) + self.size, 0, - (rawmode, ""))] + self.tile = [("jpeg", (0, 0) + self.size, 0, (rawmode, ""))] # self.__offset = self.fp.tell() break s = self.fp.read(1) @@ -424,8 +425,13 @@ class JpegImageFile(ImageFile.ImageFile): for s in [8, 4, 2, 1]: if scale >= s: break - e = e[0], e[1], (e[2]-e[0]+s-1)//s+e[0], (e[3]-e[1]+s-1)//s+e[1] - self._size = ((self.size[0]+s-1)//s, (self.size[1]+s-1)//s) + e = ( + e[0], + e[1], + (e[2] - e[0] + s - 1) // s + e[0], + (e[3] - e[1] + s - 1) // s + e[1], + ) + self._size = ((self.size[0] + s - 1) // s, (self.size[1] + s - 1) // s) scale = s self.tile = [(d, e, o, a)] @@ -440,6 +446,7 @@ class JpegImageFile(ImageFile.ImageFile): import subprocess import tempfile import os + f, path = tempfile.mkstemp() os.close(f) if os.path.exists(self.filename): @@ -505,7 +512,7 @@ def _getmp(self): return None file_contents = io.BytesIO(data) head = file_contents.read(8) - endianness = '>' if head[:4] == b'\x4d\x4d\x00\x2a' else '<' + endianness = ">" if head[:4] == b"\x4d\x4d\x00\x2a" else "<" # process dictionary try: info = TiffImagePlugin.ImageFileDirectory_v2(head) @@ -525,37 +532,33 @@ def _getmp(self): rawmpentries = mp[0xB002] for entrynum in range(0, quant): unpackedentry = struct.unpack_from( - '{}LLLHH'.format(endianness), rawmpentries, entrynum * 16) - labels = ('Attribute', 'Size', 'DataOffset', 'EntryNo1', - 'EntryNo2') + "{}LLLHH".format(endianness), rawmpentries, entrynum * 16 + ) + labels = ("Attribute", "Size", "DataOffset", "EntryNo1", "EntryNo2") mpentry = dict(zip(labels, unpackedentry)) mpentryattr = { - 'DependentParentImageFlag': bool(mpentry['Attribute'] & - (1 << 31)), - 'DependentChildImageFlag': bool(mpentry['Attribute'] & - (1 << 30)), - 'RepresentativeImageFlag': bool(mpentry['Attribute'] & - (1 << 29)), - 'Reserved': (mpentry['Attribute'] & (3 << 27)) >> 27, - 'ImageDataFormat': (mpentry['Attribute'] & (7 << 24)) >> 24, - 'MPType': mpentry['Attribute'] & 0x00FFFFFF + "DependentParentImageFlag": bool(mpentry["Attribute"] & (1 << 31)), + "DependentChildImageFlag": bool(mpentry["Attribute"] & (1 << 30)), + "RepresentativeImageFlag": bool(mpentry["Attribute"] & (1 << 29)), + "Reserved": (mpentry["Attribute"] & (3 << 27)) >> 27, + "ImageDataFormat": (mpentry["Attribute"] & (7 << 24)) >> 24, + "MPType": mpentry["Attribute"] & 0x00FFFFFF, } - if mpentryattr['ImageDataFormat'] == 0: - mpentryattr['ImageDataFormat'] = 'JPEG' + if mpentryattr["ImageDataFormat"] == 0: + mpentryattr["ImageDataFormat"] = "JPEG" else: raise SyntaxError("unsupported picture format in MPO") mptypemap = { - 0x000000: 'Undefined', - 0x010001: 'Large Thumbnail (VGA Equivalent)', - 0x010002: 'Large Thumbnail (Full HD Equivalent)', - 0x020001: 'Multi-Frame Image (Panorama)', - 0x020002: 'Multi-Frame Image: (Disparity)', - 0x020003: 'Multi-Frame Image: (Multi-Angle)', - 0x030000: 'Baseline MP Primary Image' + 0x000000: "Undefined", + 0x010001: "Large Thumbnail (VGA Equivalent)", + 0x010002: "Large Thumbnail (Full HD Equivalent)", + 0x020001: "Multi-Frame Image (Panorama)", + 0x020002: "Multi-Frame Image: (Disparity)", + 0x020003: "Multi-Frame Image: (Multi-Angle)", + 0x030000: "Baseline MP Primary Image", } - mpentryattr['MPType'] = mptypemap.get(mpentryattr['MPType'], - 'Unknown') - mpentry['Attribute'] = mpentryattr + mpentryattr["MPType"] = mptypemap.get(mpentryattr["MPType"], "Unknown") + mpentry["Attribute"] = mpentryattr mpentries.append(mpentry) mp[0xB002] = mpentries except KeyError: @@ -578,19 +581,24 @@ RAWMODE = { "YCbCr": "YCbCr", } -zigzag_index = (0, 1, 5, 6, 14, 15, 27, 28, # noqa: E128 - 2, 4, 7, 13, 16, 26, 29, 42, - 3, 8, 12, 17, 25, 30, 41, 43, - 9, 11, 18, 24, 31, 40, 44, 53, - 10, 19, 23, 32, 39, 45, 52, 54, - 20, 22, 33, 38, 46, 51, 55, 60, - 21, 34, 37, 47, 50, 56, 59, 61, - 35, 36, 48, 49, 57, 58, 62, 63) +# fmt: off +zigzag_index = ( + 0, 1, 5, 6, 14, 15, 27, 28, + 2, 4, 7, 13, 16, 26, 29, 42, + 3, 8, 12, 17, 25, 30, 41, 43, + 9, 11, 18, 24, 31, 40, 44, 53, + 10, 19, 23, 32, 39, 45, 52, 54, + 20, 22, 33, 38, 46, 51, 55, 60, + 21, 34, 37, 47, 50, 56, 59, 61, + 35, 36, 48, 49, 57, 58, 62, 63, +) -samplings = {(1, 1, 1, 1, 1, 1): 0, - (2, 1, 1, 1, 1, 1): 1, - (2, 2, 1, 1, 1, 1): 2, - } +samplings = { + (1, 1, 1, 1, 1, 1): 0, + (2, 1, 1, 1, 1, 1): 1, + (2, 2, 1, 1, 1, 1): 2, +} +# fmt: on def convert_dict_qtables(qtables): @@ -608,7 +616,7 @@ def get_sampling(im): # NOTE: currently Pillow can't encode JPEG to YCCK format. # If YCCK support is added in the future, subsampling code will have # to be updated (here and in JpegEncode.c) to deal with 4 layers. - if not hasattr(im, 'layers') or im.layers in (1, 4): + if not hasattr(im, "layers") or im.layers in (1, 4): return -1 sampling = im.layer[0][1:3] + im.layer[1][1:3] + im.layer[2][1:3] return samplings.get(sampling, -1) @@ -636,15 +644,15 @@ def _save(im, fp, filename): elif quality in presets: preset = presets[quality] quality = 0 - subsampling = preset.get('subsampling', -1) - qtables = preset.get('quantization') + subsampling = preset.get("subsampling", -1) + qtables = preset.get("quantization") elif not isinstance(quality, int): raise ValueError("Invalid quality setting") else: if subsampling in presets: - subsampling = presets[subsampling].get('subsampling', -1) + subsampling = presets[subsampling].get("subsampling", -1) if isStringType(qtables) and qtables in presets: - qtables = presets[qtables].get('quantization') + qtables = presets[qtables].get("quantization") if subsampling == "4:4:4": subsampling = 0 @@ -658,8 +666,7 @@ def _save(im, fp, filename): subsampling = 2 elif subsampling == "keep": if im.format != "JPEG": - raise ValueError( - "Cannot use 'keep' when original image is not a JPEG") + raise ValueError("Cannot use 'keep' when original image is not a JPEG") subsampling = get_sampling(im) def validate_qtables(qtables): @@ -667,12 +674,15 @@ def _save(im, fp, filename): return qtables if isStringType(qtables): try: - lines = [int(num) for line in qtables.splitlines() - for num in line.split('#', 1)[0].split()] + lines = [ + int(num) + for line in qtables.splitlines() + for num in line.split("#", 1)[0].split() + ] except ValueError: raise ValueError("Invalid quantization table") else: - qtables = [lines[s:s+64] for s in range(0, len(lines), 64)] + qtables = [lines[s : s + 64] for s in range(0, len(lines), 64)] if isinstance(qtables, (tuple, list, dict)): if isinstance(qtables, dict): qtables = convert_dict_qtables(qtables) @@ -684,7 +694,7 @@ def _save(im, fp, filename): try: if len(table) != 64: raise TypeError - table = array.array('B', table) + table = array.array("B", table) except TypeError: raise ValueError("Invalid quantization table") else: @@ -693,8 +703,7 @@ def _save(im, fp, filename): if qtables == "keep": if im.format != "JPEG": - raise ValueError( - "Cannot use 'keep' when original image is not a JPEG") + raise ValueError("Cannot use 'keep' when original image is not a JPEG") qtables = getattr(im, "quantization", None) qtables = validate_qtables(qtables) @@ -712,15 +721,20 @@ def _save(im, fp, filename): i = 1 for marker in markers: size = struct.pack(">H", 2 + ICC_OVERHEAD_LEN + len(marker)) - extra += (b"\xFF\xE2" + size + b"ICC_PROFILE\0" + o8(i) + - o8(len(markers)) + marker) + extra += ( + b"\xFF\xE2" + + size + + b"ICC_PROFILE\0" + + o8(i) + + o8(len(markers)) + + marker + ) i += 1 # "progressive" is the official name, but older documentation # says "progression" # FIXME: issue a warning if the wrong form is used (post-1.1.7) - progressive = (info.get("progressive", False) or - info.get("progression", False)) + progressive = info.get("progressive", False) or info.get("progression", False) optimize = info.get("optimize", False) @@ -735,12 +749,13 @@ def _save(im, fp, filename): info.get("smooth", 0), optimize, info.get("streamtype", 0), - dpi[0], dpi[1], + dpi[0], + dpi[1], subsampling, qtables, extra, - exif - ) + exif, + ) # if we optimize, libjpeg needs a buffer big enough to hold the whole image # in a shot. Guessing on the size, at im.size bytes. (raw pixel size is @@ -749,7 +764,7 @@ def _save(im, fp, filename): bufsize = 0 if optimize or progressive: # CMYK can be bigger - if im.mode == 'CMYK': + if im.mode == "CMYK": bufsize = 4 * im.size[0] * im.size[1] # keep sets quality to 0, but the actual value may be high. elif quality >= 95 or quality == 0: @@ -759,16 +774,16 @@ def _save(im, fp, filename): # The EXIF info needs to be written as one block, + APP1, + one spare byte. # Ensure that our buffer is big enough. Same with the icc_profile block. - bufsize = max(ImageFile.MAXBLOCK, bufsize, len(exif) + 5, - len(extra) + 1) + bufsize = max(ImageFile.MAXBLOCK, bufsize, len(exif) + 5, len(extra) + 1) - ImageFile._save(im, fp, [("jpeg", (0, 0)+im.size, 0, rawmode)], bufsize) + ImageFile._save(im, fp, [("jpeg", (0, 0) + im.size, 0, rawmode)], bufsize) def _save_cjpeg(im, fp, filename): # ALTERNATIVE: handle JPEGs via the IJG command line utilities. import os import subprocess + tempfile = im._dump() subprocess.check_call(["cjpeg", "-outfile", filename, tempfile]) try: @@ -786,14 +801,17 @@ def jpeg_factory(fp=None, filename=None): if mpheader[45057] > 1: # It's actually an MPO from .MpoImagePlugin import MpoImageFile + # Don't reload everything, just convert it. im = MpoImageFile.adopt(im, mpheader) except (TypeError, IndexError): # It is really a JPEG pass except SyntaxError: - warnings.warn("Image appears to be a malformed MPO file, it will be " - "interpreted as a base JPEG file") + warnings.warn( + "Image appears to be a malformed MPO file, it will be " + "interpreted as a base JPEG file" + ) return im @@ -803,7 +821,6 @@ def jpeg_factory(fp=None, filename=None): Image.register_open(JpegImageFile.format, jpeg_factory, _accept) Image.register_save(JpegImageFile.format, _save) -Image.register_extensions(JpegImageFile.format, - [".jfif", ".jpe", ".jpg", ".jpeg"]) +Image.register_extensions(JpegImageFile.format, [".jfif", ".jpe", ".jpg", ".jpeg"]) Image.register_mime(JpegImageFile.format, "image/jpeg") diff --git a/src/PIL/JpegPresets.py b/src/PIL/JpegPresets.py index f7a533c18..387844f8e 100644 --- a/src/PIL/JpegPresets.py +++ b/src/PIL/JpegPresets.py @@ -67,6 +67,7 @@ https://web.archive.org/web/20120328125543/http://www.jpegcameras.com/libjpeg/li """ +# fmt: off presets = { # noqa: E128 'web_low': {'subsampling': 2, # "4:2:0" 'quantization': [ @@ -240,3 +241,4 @@ presets = { # noqa: E128 15, 12, 12, 12, 12, 12, 12, 12] ]}, } +# fmt: on diff --git a/src/PIL/McIdasImagePlugin.py b/src/PIL/McIdasImagePlugin.py index 2cdb6f828..df94f59b8 100644 --- a/src/PIL/McIdasImagePlugin.py +++ b/src/PIL/McIdasImagePlugin.py @@ -31,6 +31,7 @@ def _accept(s): ## # Image plugin for McIdas area images. + class McIdasImageFile(ImageFile.ImageFile): format = "MCIDAS" @@ -64,7 +65,7 @@ class McIdasImageFile(ImageFile.ImageFile): self._size = w[10], w[9] offset = w[34] + w[15] - stride = w[15] + w[10]*w[11]*w[14] + stride = w[15] + w[10] * w[11] * w[14] self.tile = [("raw", (0, 0) + self.size, offset, (rawmode, stride, 1))] diff --git a/src/PIL/MicImagePlugin.py b/src/PIL/MicImagePlugin.py index 8c7707dae..1807e8a0e 100644 --- a/src/PIL/MicImagePlugin.py +++ b/src/PIL/MicImagePlugin.py @@ -37,6 +37,7 @@ def _accept(prefix): ## # Image plugin for Microsoft's Image Composer file format. + class MicImageFile(TiffImagePlugin.TiffImageFile): format = "MIC" diff --git a/src/PIL/MpegImagePlugin.py b/src/PIL/MpegImagePlugin.py index 7f419c5dc..9c662fcc2 100644 --- a/src/PIL/MpegImagePlugin.py +++ b/src/PIL/MpegImagePlugin.py @@ -25,8 +25,8 @@ __version__ = "0.1" # # Bitstream parser -class BitStream(object): +class BitStream(object): def __init__(self, fp): self.fp = fp self.bits = 0 @@ -61,6 +61,7 @@ class BitStream(object): # Image plugin for MPEG streams. This plugin can identify a stream, # but it cannot read it. + class MpegImageFile(ImageFile.ImageFile): format = "MPEG" diff --git a/src/PIL/MpoImagePlugin.py b/src/PIL/MpoImagePlugin.py index ead2f1fff..81b37172a 100644 --- a/src/PIL/MpoImagePlugin.py +++ b/src/PIL/MpoImagePlugin.py @@ -38,6 +38,7 @@ def _save(im, fp, filename): ## # Image plugin for MPO images. + class MpoImageFile(JpegImagePlugin.JpegImageFile): format = "MPO" @@ -52,13 +53,14 @@ class MpoImageFile(JpegImagePlugin.JpegImageFile): def _after_jpeg_open(self, mpheader=None): self.mpinfo = mpheader if mpheader is not None else self._getmp() self.__framecount = self.mpinfo[0xB001] - self.__mpoffsets = [mpent['DataOffset'] + self.info['mpoffset'] - for mpent in self.mpinfo[0xB002]] + self.__mpoffsets = [ + mpent["DataOffset"] + self.info["mpoffset"] for mpent in self.mpinfo[0xB002] + ] self.__mpoffsets[0] = 0 # Note that the following assertion will only be invalid if something # gets broken within JpegImagePlugin. assert self.__framecount == len(self.__mpoffsets) - del self.info['mpoffset'] # no longer needed + del self.info["mpoffset"] # no longer needed self.__fp = self.fp # FIXME: hack self.__fp.seek(self.__mpoffsets[0]) # get ready to read first frame self.__frame = 0 @@ -87,7 +89,7 @@ class MpoImageFile(JpegImagePlugin.JpegImageFile): if "parsed_exif" in self.info: del self.info["parsed_exif"] if i16(self.fp.read(2)) == 0xFFE1: # APP1 - n = i16(self.fp.read(2))-2 + n = i16(self.fp.read(2)) - 2 self.info["exif"] = ImageFile._safe_read(self.fp, n) exif = self._getexif() @@ -96,9 +98,7 @@ class MpoImageFile(JpegImagePlugin.JpegImageFile): elif "exif" in self.info: del self.info["exif"] - self.tile = [ - ("jpeg", (0, 0) + self.size, self.offset, (self.mode, "")) - ] + self.tile = [("jpeg", (0, 0) + self.size, self.offset, (self.mode, ""))] self.__frame = frame def tell(self): diff --git a/src/PIL/MspImagePlugin.py b/src/PIL/MspImagePlugin.py index 711f8f09a..f9be687e5 100644 --- a/src/PIL/MspImagePlugin.py +++ b/src/PIL/MspImagePlugin.py @@ -45,6 +45,7 @@ def _accept(prefix): # Image plugin for Windows MSP images. This plugin supports both # uncompressed (Windows 1.0). + class MspImageFile(ImageFile.ImageFile): format = "MSP" @@ -60,7 +61,7 @@ class MspImageFile(ImageFile.ImageFile): # Header checksum checksum = 0 for i in range(0, 32, 2): - checksum = checksum ^ i16(s[i:i+2]) + checksum = checksum ^ i16(s[i : i + 2]) if checksum != 0: raise SyntaxError("bad MSP checksum") @@ -68,9 +69,9 @@ class MspImageFile(ImageFile.ImageFile): self._size = i16(s[4:]), i16(s[6:]) if s[:4] == b"DanM": - self.tile = [("raw", (0, 0)+self.size, 32, ("1", 0, 1))] + self.tile = [("raw", (0, 0) + self.size, 32, ("1", 0, 1))] else: - self.tile = [("MSP", (0, 0)+self.size, 32, None)] + self.tile = [("MSP", (0, 0) + self.size, 32, None)] class MspDecoder(ImageFile.PyDecoder): @@ -113,11 +114,12 @@ class MspDecoder(ImageFile.PyDecoder): def decode(self, buffer): img = io.BytesIO() - blank_line = bytearray((0xff,)*((self.state.xsize+7)//8)) + blank_line = bytearray((0xFF,) * ((self.state.xsize + 7) // 8)) try: self.fd.seek(32) - rowmap = struct.unpack_from("<%dH" % (self.state.ysize), - self.fd.read(self.state.ysize*2)) + rowmap = struct.unpack_from( + "<%dH" % (self.state.ysize), self.fd.read(self.state.ysize * 2) + ) except struct.error: raise IOError("Truncated MSP file in row map") @@ -129,8 +131,8 @@ class MspDecoder(ImageFile.PyDecoder): row = self.fd.read(rowlen) if len(row) != rowlen: raise IOError( - "Truncated MSP file, expected %d bytes on row %s", - (rowlen, x)) + "Truncated MSP file, expected %d bytes on row %s", (rowlen, x) + ) idx = 0 while idx < rowlen: runtype = i8(row[idx]) @@ -141,7 +143,7 @@ class MspDecoder(ImageFile.PyDecoder): idx += 2 else: runcount = runtype - img.write(row[idx:idx+runcount]) + img.write(row[idx : idx + runcount]) idx += runcount except struct.error: @@ -152,7 +154,7 @@ class MspDecoder(ImageFile.PyDecoder): return 0, 0 -Image.register_decoder('MSP', MspDecoder) +Image.register_decoder("MSP", MspDecoder) # @@ -183,7 +185,7 @@ def _save(im, fp, filename): fp.write(o16(h)) # image body - ImageFile._save(im, fp, [("raw", (0, 0)+im.size, 32, ("1", 0, 1))]) + ImageFile._save(im, fp, [("raw", (0, 0) + im.size, 32, ("1", 0, 1))]) # diff --git a/src/PIL/PSDraw.py b/src/PIL/PSDraw.py index d2ded6fea..f542e1505 100644 --- a/src/PIL/PSDraw.py +++ b/src/PIL/PSDraw.py @@ -38,16 +38,18 @@ class PSDraw(object): if not py3 or self.fp == sys.stdout: self.fp.write(to_write) else: - self.fp.write(bytes(to_write, 'UTF-8')) + self.fp.write(bytes(to_write, "UTF-8")) def begin_document(self, id=None): """Set up printing of a document. (Write Postscript DSC header.)""" # FIXME: incomplete - self._fp_write("%!PS-Adobe-3.0\n" - "save\n" - "/showpage { } def\n" - "%%EndComments\n" - "%%BeginDocument\n") + self._fp_write( + "%!PS-Adobe-3.0\n" + "save\n" + "/showpage { } def\n" + "%%EndComments\n" + "%%BeginDocument\n" + ) # self._fp_write(ERROR_PS) # debugging! self._fp_write(EDROFF_PS) self._fp_write(VDI_PS) @@ -56,9 +58,7 @@ class PSDraw(object): def end_document(self): """Ends printing. (Write Postscript DSC footer.)""" - self._fp_write("%%EndDocument\n" - "restore showpage\n" - "%%End\n") + self._fp_write("%%EndDocument\nrestore showpage\n%%End\n") if hasattr(self.fp, "flush"): self.fp.flush() @@ -71,8 +71,7 @@ class PSDraw(object): """ if font not in self.isofont: # reencode font - self._fp_write("/PSDraw-%s ISOLatin1Encoding /%s E\n" % - (font, font)) + self._fp_write("/PSDraw-%s ISOLatin1Encoding /%s E\n" % (font, font)) self.isofont[font] = 1 # rough self._fp_write("/F0 %d /PSDraw-%s F\n" % (size, font)) @@ -142,6 +141,7 @@ class PSDraw(object): EpsImagePlugin._save(im, self.fp, None, 0) self._fp_write("\ngrestore\n") + # -------------------------------------------------------------------- # Postscript driver diff --git a/src/PIL/PaletteFile.py b/src/PIL/PaletteFile.py index 9ed69d687..8a3d45ff2 100644 --- a/src/PIL/PaletteFile.py +++ b/src/PIL/PaletteFile.py @@ -19,6 +19,7 @@ from ._binary import o8 ## # File handler for Teragon-style palette files. + class PaletteFile(object): rawmode = "RGB" diff --git a/src/PIL/PalmImagePlugin.py b/src/PIL/PalmImagePlugin.py index 096867207..dd068d794 100644 --- a/src/PIL/PalmImagePlugin.py +++ b/src/PIL/PalmImagePlugin.py @@ -14,6 +14,7 @@ from ._binary import o8, o16be as o16b # PIL.__version__ instead. __version__ = "1.0" +# fmt: off _Palm8BitColormapValues = ( # noqa: E131 (255, 255, 255), (255, 204, 255), (255, 153, 255), (255, 102, 255), (255, 51, 255), (255, 0, 255), (255, 255, 204), (255, 204, 204), @@ -79,6 +80,7 @@ _Palm8BitColormapValues = ( # noqa: E131 (0, 0, 0), (0, 0, 0), (0, 0, 0), (0, 0, 0), (0, 0, 0), (0, 0, 0), (0, 0, 0), (0, 0, 0), (0, 0, 0), (0, 0, 0), (0, 0, 0), (0, 0, 0)) +# fmt: on # so build a prototype image to be used for palette resampling @@ -88,7 +90,7 @@ def build_prototype_image(): palettedata = () for colormapValue in _Palm8BitColormapValues: palettedata += colormapValue - palettedata += (0, 0, 0)*(256 - len(_Palm8BitColormapValues)) + palettedata += (0, 0, 0) * (256 - len(_Palm8BitColormapValues)) image.putpalette(palettedata) return image @@ -100,17 +102,9 @@ Palm8BitColormapImage = build_prototype_image() # # -------------------------------------------------------------------- -_FLAGS = { - "custom-colormap": 0x4000, - "is-compressed": 0x8000, - "has-transparent": 0x2000, - } +_FLAGS = {"custom-colormap": 0x4000, "is-compressed": 0x8000, "has-transparent": 0x2000} -_COMPRESSION_TYPES = { - "none": 0xFF, - "rle": 0x01, - "scanline": 0x00, - } +_COMPRESSION_TYPES = {"none": 0xFF, "rle": 0x01, "scanline": 0x00} # @@ -119,6 +113,7 @@ _COMPRESSION_TYPES = { ## # (Internal) Image save plugin for the Palm format. + def _save(im, fp, filename): if im.mode == "P": @@ -130,28 +125,24 @@ def _save(im, fp, filename): bpp = 8 version = 1 - elif (im.mode == "L" and - "bpp" in im.encoderinfo and - im.encoderinfo["bpp"] in (1, 2, 4)): + elif im.mode == "L": + if im.encoderinfo.get("bpp") in (1, 2, 4): + # this is 8-bit grayscale, so we shift it to get the high-order bits, + # and invert it because + # Palm does greyscale from white (0) to black (1) + bpp = im.encoderinfo["bpp"] + im = im.point( + lambda x, shift=8 - bpp, maxval=(1 << bpp) - 1: maxval - (x >> shift) + ) + elif im.info.get("bpp") in (1, 2, 4): + # here we assume that even though the inherent mode is 8-bit grayscale, + # only the lower bpp bits are significant. + # We invert them to match the Palm. + bpp = im.info["bpp"] + im = im.point(lambda x, maxval=(1 << bpp) - 1: maxval - (x & maxval)) + else: + raise IOError("cannot write mode %s as Palm" % im.mode) - # this is 8-bit grayscale, so we shift it to get the high-order bits, - # and invert it because - # Palm does greyscale from white (0) to black (1) - bpp = im.encoderinfo["bpp"] - im = im.point( - lambda x, shift=8-bpp, maxval=(1 << bpp)-1: maxval - (x >> shift)) - # we ignore the palette here - im.mode = "P" - rawmode = "P;" + str(bpp) - version = 1 - - elif im.mode == "L" and "bpp" in im.info and im.info["bpp"] in (1, 2, 4): - - # here we assume that even though the inherent mode is 8-bit grayscale, - # only the lower bpp bits are significant. - # We invert them to match the Palm. - bpp = im.info["bpp"] - im = im.point(lambda x, maxval=(1 << bpp)-1: maxval - (x & maxval)) # we ignore the palette here im.mode = "P" rawmode = "P;" + str(bpp) @@ -177,7 +168,7 @@ def _save(im, fp, filename): cols = im.size[0] rows = im.size[1] - rowbytes = int((cols + (16//bpp - 1)) / (16 // bpp)) * 2 + rowbytes = int((cols + (16 // bpp - 1)) / (16 // bpp)) * 2 transparent_index = 0 compression_type = _COMPRESSION_TYPES["none"] @@ -201,7 +192,7 @@ def _save(im, fp, filename): fp.write(o16b(offset)) fp.write(o8(transparent_index)) fp.write(o8(compression_type)) - fp.write(o16b(0)) # reserved by Palm + fp.write(o16b(0)) # reserved by Palm # now write colormap if necessary @@ -209,20 +200,21 @@ def _save(im, fp, filename): fp.write(o16b(256)) for i in range(256): fp.write(o8(i)) - if colormapmode == 'RGB': + if colormapmode == "RGB": fp.write( - o8(colormap[3 * i]) + - o8(colormap[3 * i + 1]) + - o8(colormap[3 * i + 2])) - elif colormapmode == 'RGBA': + o8(colormap[3 * i]) + + o8(colormap[3 * i + 1]) + + o8(colormap[3 * i + 2]) + ) + elif colormapmode == "RGBA": fp.write( - o8(colormap[4 * i]) + - o8(colormap[4 * i + 1]) + - o8(colormap[4 * i + 2])) + o8(colormap[4 * i]) + + o8(colormap[4 * i + 1]) + + o8(colormap[4 * i + 2]) + ) # now convert data to raw form - ImageFile._save( - im, fp, [("raw", (0, 0)+im.size, 0, (rawmode, rowbytes, 1))]) + ImageFile._save(im, fp, [("raw", (0, 0) + im.size, 0, (rawmode, rowbytes, 1))]) if hasattr(fp, "flush"): fp.flush() diff --git a/src/PIL/PcdImagePlugin.py b/src/PIL/PcdImagePlugin.py index 7e8fa3128..6f01845ec 100644 --- a/src/PIL/PcdImagePlugin.py +++ b/src/PIL/PcdImagePlugin.py @@ -28,6 +28,7 @@ __version__ = "0.1" # image from the file; higher resolutions are encoded in a proprietary # encoding. + class PcdImageFile(ImageFile.ImageFile): format = "PCD" @@ -51,7 +52,7 @@ class PcdImageFile(ImageFile.ImageFile): self.mode = "RGB" self._size = 768, 512 # FIXME: not correct for rotated images! - self.tile = [("pcd", (0, 0)+self.size, 96*2048, None)] + self.tile = [("pcd", (0, 0) + self.size, 96 * 2048, None)] def load_end(self): if self.tile_post_rotate: diff --git a/src/PIL/PcfFontFile.py b/src/PIL/PcfFontFile.py index b50fe72da..beaf5f58b 100644 --- a/src/PIL/PcfFontFile.py +++ b/src/PIL/PcfFontFile.py @@ -25,31 +25,32 @@ from ._binary import i8, i16le as l16, i32le as l32, i16be as b16, i32be as b32 PCF_MAGIC = 0x70636601 # "\x01fcp" -PCF_PROPERTIES = (1 << 0) -PCF_ACCELERATORS = (1 << 1) -PCF_METRICS = (1 << 2) -PCF_BITMAPS = (1 << 3) -PCF_INK_METRICS = (1 << 4) -PCF_BDF_ENCODINGS = (1 << 5) -PCF_SWIDTHS = (1 << 6) -PCF_GLYPH_NAMES = (1 << 7) -PCF_BDF_ACCELERATORS = (1 << 8) +PCF_PROPERTIES = 1 << 0 +PCF_ACCELERATORS = 1 << 1 +PCF_METRICS = 1 << 2 +PCF_BITMAPS = 1 << 3 +PCF_INK_METRICS = 1 << 4 +PCF_BDF_ENCODINGS = 1 << 5 +PCF_SWIDTHS = 1 << 6 +PCF_GLYPH_NAMES = 1 << 7 +PCF_BDF_ACCELERATORS = 1 << 8 BYTES_PER_ROW = [ - lambda bits: ((bits+7) >> 3), - lambda bits: ((bits+15) >> 3) & ~1, - lambda bits: ((bits+31) >> 3) & ~3, - lambda bits: ((bits+63) >> 3) & ~7, + lambda bits: ((bits + 7) >> 3), + lambda bits: ((bits + 15) >> 3) & ~1, + lambda bits: ((bits + 31) >> 3) & ~3, + lambda bits: ((bits + 63) >> 3) & ~7, ] def sz(s, o): - return s[o:s.index(b"\0", o)] + return s[o : s.index(b"\0", o)] ## # Font file plugin for the X11 PCF format. + class PcfFontFile(FontFile.FontFile): name = "name" @@ -83,7 +84,7 @@ class PcfFontFile(FontFile.FontFile): ix = encoding[ch] if ix is not None: x, y, l, r, w, a, d, f = metrics[ix] - glyph = (w, 0), (l, d-y, x+l, d), (0, 0, x, y), bitmaps[ix] + glyph = (w, 0), (l, d - y, x + l, d), (0, 0, x, y), bitmaps[ix] self.glyph[ch] = glyph def _getformat(self, tag): @@ -141,7 +142,7 @@ class PcfFontFile(FontFile.FontFile): append = metrics.append - if (format & 0xff00) == 0x100: + if (format & 0xFF00) == 0x100: # "compressed" metrics for i in range(i16(fp.read(2))): @@ -152,10 +153,7 @@ class PcfFontFile(FontFile.FontFile): descent = i8(fp.read(1)) - 128 xsize = right - left ysize = ascent + descent - append( - (xsize, ysize, left, right, width, - ascent, descent, 0) - ) + append((xsize, ysize, left, right, width, ascent, descent, 0)) else: @@ -169,10 +167,7 @@ class PcfFontFile(FontFile.FontFile): attributes = i16(fp.read(2)) xsize = right - left ysize = ascent + descent - append( - (xsize, ysize, left, right, width, - ascent, descent, attributes) - ) + append((xsize, ysize, left, right, width, ascent, descent, attributes)) return metrics @@ -199,7 +194,7 @@ class PcfFontFile(FontFile.FontFile): bitmapSizes.append(i32(fp.read(4))) # byteorder = format & 4 # non-zero => MSB - bitorder = format & 8 # non-zero => MSB + bitorder = format & 8 # non-zero => MSB padindex = format & 3 bitmapsize = bitmapSizes[padindex] @@ -214,10 +209,8 @@ class PcfFontFile(FontFile.FontFile): for i in range(nbitmaps): x, y, l, r, w, a, d, f = metrics[i] - b, e = offsets[i], offsets[i+1] - bitmaps.append( - Image.frombytes("1", (x, y), data[b:e], "raw", mode, pad(x)) - ) + b, e = offsets[i], offsets[i + 1] + bitmaps.append(Image.frombytes("1", (x, y), data[b:e], "raw", mode, pad(x))) return bitmaps @@ -239,7 +232,7 @@ class PcfFontFile(FontFile.FontFile): encodingOffset = i16(fp.read(2)) if encodingOffset != 0xFFFF: try: - encoding[i+firstCol] = encodingOffset + encoding[i + firstCol] = encodingOffset except IndexError: break # only load ISO-8859-1 glyphs diff --git a/src/PIL/PcxImagePlugin.py b/src/PIL/PcxImagePlugin.py index 02dbe26e0..90778aa3c 100644 --- a/src/PIL/PcxImagePlugin.py +++ b/src/PIL/PcxImagePlugin.py @@ -44,6 +44,7 @@ def _accept(prefix): ## # Image plugin for Paintbrush images. + class PcxImageFile(ImageFile.ImageFile): format = "PCX" @@ -57,7 +58,7 @@ class PcxImageFile(ImageFile.ImageFile): raise SyntaxError("not a PCX file") # image - bbox = i16(s, 4), i16(s, 6), i16(s, 8)+1, i16(s, 10)+1 + bbox = i16(s, 4), i16(s, 6), i16(s, 8) + 1, i16(s, 10) + 1 if bbox[2] <= bbox[0] or bbox[3] <= bbox[1]: raise SyntaxError("bad PCX image size") logger.debug("BBox: %s %s %s %s", *bbox) @@ -67,8 +68,13 @@ class PcxImageFile(ImageFile.ImageFile): bits = i8(s[3]) planes = i8(s[65]) stride = i16(s, 66) - logger.debug("PCX version %s, bits %s, planes %s, stride %s", - version, bits, planes, stride) + logger.debug( + "PCX version %s, bits %s, planes %s, stride %s", + version, + bits, + planes, + stride, + ) self.info["dpi"] = i16(s, 12), i16(s, 14) @@ -88,7 +94,7 @@ class PcxImageFile(ImageFile.ImageFile): if len(s) == 769 and i8(s[0]) == 12: # check if the palette is linear greyscale for i in range(256): - if s[i*3+1:i*3+4] != o8(i)*3: + if s[i * 3 + 1 : i * 3 + 4] != o8(i) * 3: mode = rawmode = "P" break if mode == "P": @@ -103,13 +109,14 @@ class PcxImageFile(ImageFile.ImageFile): raise IOError("unknown PCX mode") self.mode = mode - self._size = bbox[2]-bbox[0], bbox[3]-bbox[1] + self._size = bbox[2] - bbox[0], bbox[3] - bbox[1] bbox = (0, 0) + self.size logger.debug("size: %sx%s", *self.size) self.tile = [("pcx", bbox, self.fp.tell(), (rawmode, planes * stride))] + # -------------------------------------------------------------------- # save PCX files @@ -138,8 +145,12 @@ def _save(im, fp, filename): # Ideally it should be passed in in the state, but the bytes value # gets overwritten. - logger.debug("PcxImagePlugin._save: xwidth: %d, bits: %d, stride: %d", - im.size[0], bits, stride) + logger.debug( + "PcxImagePlugin._save: xwidth: %d, bits: %d, stride: %d", + im.size[0], + bits, + stride, + ) # under windows, we could determine the current screen size with # "Image.core.display_mode()[1]", but I think that's overkill... @@ -150,17 +161,30 @@ def _save(im, fp, filename): # PCX header fp.write( - o8(10) + o8(version) + o8(1) + o8(bits) + o16(0) + - o16(0) + o16(im.size[0]-1) + o16(im.size[1]-1) + o16(dpi[0]) + - o16(dpi[1]) + b"\0"*24 + b"\xFF"*24 + b"\0" + o8(planes) + - o16(stride) + o16(1) + o16(screen[0]) + o16(screen[1]) + - b"\0"*54 - ) + o8(10) + + o8(version) + + o8(1) + + o8(bits) + + o16(0) + + o16(0) + + o16(im.size[0] - 1) + + o16(im.size[1] - 1) + + o16(dpi[0]) + + o16(dpi[1]) + + b"\0" * 24 + + b"\xFF" * 24 + + b"\0" + + o8(planes) + + o16(stride) + + o16(1) + + o16(screen[0]) + + o16(screen[1]) + + b"\0" * 54 + ) assert fp.tell() == 128 - ImageFile._save(im, fp, [("pcx", (0, 0)+im.size, 0, - (rawmode, bits*planes))]) + ImageFile._save(im, fp, [("pcx", (0, 0) + im.size, 0, (rawmode, bits * planes))]) if im.mode == "P": # colour palette @@ -170,7 +194,8 @@ def _save(im, fp, filename): # greyscale palette fp.write(o8(12)) for i in range(256): - fp.write(o8(i)*3) + fp.write(o8(i) * 3) + # -------------------------------------------------------------------- # registry diff --git a/src/PIL/PdfImagePlugin.py b/src/PIL/PdfImagePlugin.py index 702aaa392..a45478821 100644 --- a/src/PIL/PdfImagePlugin.py +++ b/src/PIL/PdfImagePlugin.py @@ -48,6 +48,7 @@ def _save_all(im, fp, filename): ## # (Internal) Image save plugin for the PDF format. + def _save(im, fp, filename, save_all=False): is_appending = im.encoderinfo.get("append", False) if is_appending: @@ -58,16 +59,16 @@ def _save(im, fp, filename, save_all=False): resolution = im.encoderinfo.get("resolution", 72.0) info = { - "title": None if is_appending else os.path.splitext( - os.path.basename(filename) - )[0], + "title": None + if is_appending + else os.path.splitext(os.path.basename(filename))[0], "author": None, "subject": None, "keywords": None, "creator": None, "producer": None, "creationDate": None if is_appending else time.gmtime(), - "modDate": None if is_appending else time.gmtime() + "modDate": None if is_appending else time.gmtime(), } for k, default in info.items(): v = im.encoderinfo.get(k) if k in im.encoderinfo else default @@ -142,7 +143,7 @@ def _save(im, fp, filename, save_all=False): PdfParser.PdfName("Indexed"), PdfParser.PdfName("DeviceRGB"), 255, - PdfParser.PdfBinary(palette) + PdfParser.PdfBinary(palette), ] procset = "ImageI" # indexed color elif im.mode == "RGB": @@ -166,16 +167,15 @@ def _save(im, fp, filename, save_all=False): # FIXME: the hex encoder doesn't support packed 1-bit # images; do things the hard way... data = im.tobytes("raw", "1") - im = Image.new("L", (len(data), 1), None) + im = Image.new("L", im.size) im.putdata(data) - ImageFile._save(im, op, [("hex", (0, 0)+im.size, 0, im.mode)]) + ImageFile._save(im, op, [("hex", (0, 0) + im.size, 0, im.mode)]) elif filter == "DCTDecode": Image.SAVE["JPEG"](im, op, filename) elif filter == "FlateDecode": - ImageFile._save(im, op, [("zip", (0, 0)+im.size, 0, im.mode)]) + ImageFile._save(im, op, [("zip", (0, 0) + im.size, 0, im.mode)]) elif filter == "RunLengthDecode": - ImageFile._save(im, op, - [("packbits", (0, 0)+im.size, 0, im.mode)]) + ImageFile._save(im, op, [("packbits", (0, 0) + im.size, 0, im.mode)]) else: raise ValueError("unsupported PDF filter (%s)" % filter) @@ -184,48 +184,46 @@ def _save(im, fp, filename, save_all=False): width, height = im.size - existing_pdf.write_obj(image_refs[pageNumber], - stream=op.getvalue(), - Type=PdfParser.PdfName("XObject"), - Subtype=PdfParser.PdfName("Image"), - Width=width, # * 72.0 / resolution, - Height=height, # * 72.0 / resolution, - Filter=PdfParser.PdfName(filter), - BitsPerComponent=bits, - DecodeParams=params, - ColorSpace=colorspace) + existing_pdf.write_obj( + image_refs[pageNumber], + stream=op.getvalue(), + Type=PdfParser.PdfName("XObject"), + Subtype=PdfParser.PdfName("Image"), + Width=width, # * 72.0 / resolution, + Height=height, # * 72.0 / resolution, + Filter=PdfParser.PdfName(filter), + BitsPerComponent=bits, + DecodeParams=params, + ColorSpace=colorspace, + ) # # page - existing_pdf.write_page(page_refs[pageNumber], - Resources=PdfParser.PdfDict( - ProcSet=[ - PdfParser.PdfName("PDF"), - PdfParser.PdfName(procset) - ], - XObject=PdfParser.PdfDict( - image=image_refs[pageNumber] - ) - ), - MediaBox=[ - 0, - 0, - int(width * 72.0 / resolution), - int(height * 72.0 / resolution) - ], - Contents=contents_refs[pageNumber]) + existing_pdf.write_page( + page_refs[pageNumber], + Resources=PdfParser.PdfDict( + ProcSet=[PdfParser.PdfName("PDF"), PdfParser.PdfName(procset)], + XObject=PdfParser.PdfDict(image=image_refs[pageNumber]), + ), + MediaBox=[ + 0, + 0, + int(width * 72.0 / resolution), + int(height * 72.0 / resolution), + ], + Contents=contents_refs[pageNumber], + ) # # page contents page_contents = PdfParser.make_bytes( - "q %d 0 0 %d 0 0 cm /image Do Q\n" % ( - int(width * 72.0 / resolution), - int(height * 72.0 / resolution))) + "q %d 0 0 %d 0 0 cm /image Do Q\n" + % (int(width * 72.0 / resolution), int(height * 72.0 / resolution)) + ) - existing_pdf.write_obj(contents_refs[pageNumber], - stream=page_contents) + existing_pdf.write_obj(contents_refs[pageNumber], stream=page_contents) pageNumber += 1 @@ -236,6 +234,7 @@ def _save(im, fp, filename, save_all=False): fp.flush() existing_pdf.close() + # # -------------------------------------------------------------------- diff --git a/src/PIL/PdfParser.py b/src/PIL/PdfParser.py index 8f90b668d..65cf77668 100644 --- a/src/PIL/PdfParser.py +++ b/src/PIL/PdfParser.py @@ -15,11 +15,15 @@ except ImportError: if py3: # Python 3.x + def make_bytes(s): return s.encode("us-ascii") + + else: # Python 2.x + def make_bytes(s): # pragma: no cover - return s # pragma: no cover + return s # pragma: no cover # see 7.9.2.2 Text String Type on page 86 and D.3 PDFDocEncoding Character Set @@ -74,8 +78,8 @@ PDFDocEncoding = { def decode_text(b): - if b[:len(codecs.BOM_UTF16_BE)] == codecs.BOM_UTF16_BE: - return b[len(codecs.BOM_UTF16_BE):].decode("utf_16_be") + if b[: len(codecs.BOM_UTF16_BE)] == codecs.BOM_UTF16_BE: + return b[len(codecs.BOM_UTF16_BE) :].decode("utf_16_be") elif py3: # Python 3.x return "".join(PDFDocEncoding.get(byte, chr(byte)) for byte in b) else: # Python 2.x @@ -85,6 +89,7 @@ def decode_text(b): class PdfFormatError(RuntimeError): """An error that probably indicates a syntactic or semantic error in the PDF file structure""" + pass @@ -93,8 +98,9 @@ def check_format_condition(condition, error_message): raise PdfFormatError(error_message) -class IndirectReference(collections.namedtuple("IndirectReferenceTuple", - ["object_id", "generation"])): +class IndirectReference( + collections.namedtuple("IndirectReferenceTuple", ["object_id", "generation"]) +): def __str__(self): return "%s %s R" % self @@ -102,9 +108,11 @@ class IndirectReference(collections.namedtuple("IndirectReferenceTuple", return self.__str__().encode("us-ascii") def __eq__(self, other): - return other.__class__ is self.__class__ and \ - other.object_id == self.object_id and \ - other.generation == self.generation + return ( + other.__class__ is self.__class__ + and other.object_id == self.object_id + and other.generation == self.generation + ) def __ne__(self, other): return not (self == other) @@ -120,9 +128,9 @@ class IndirectObjectDef(IndirectReference): class XrefTable: def __init__(self): - self.existing_entries = {} # object ID => (offset, generation) - self.new_entries = {} # object ID => (offset, generation) - self.deleted_entries = {0: 65536} # object ID => generation + self.existing_entries = {} # object ID => (offset, generation) + self.new_entries = {} # object ID => (offset, generation) + self.deleted_entries = {0: 65536} # object ID => generation self.reading_finished = False def __setitem__(self, key, value): @@ -150,26 +158,27 @@ class XrefTable: elif key in self.deleted_entries: generation = self.deleted_entries[key] else: - raise IndexError("object ID " + str(key) + - " cannot be deleted because it doesn't exist") + raise IndexError( + "object ID " + str(key) + " cannot be deleted because it doesn't exist" + ) def __contains__(self, key): return key in self.existing_entries or key in self.new_entries def __len__(self): - return len(set(self.existing_entries.keys()) | - set(self.new_entries.keys()) | - set(self.deleted_entries.keys())) + return len( + set(self.existing_entries.keys()) + | set(self.new_entries.keys()) + | set(self.deleted_entries.keys()) + ) def keys(self): return ( - set(self.existing_entries.keys()) - - set(self.deleted_entries.keys()) + set(self.existing_entries.keys()) - set(self.deleted_entries.keys()) ) | set(self.new_entries.keys()) def write(self, f): - keys = sorted(set(self.new_entries.keys()) | - set(self.deleted_entries.keys())) + keys = sorted(set(self.new_entries.keys()) | set(self.deleted_entries.keys())) deleted_keys = sorted(set(self.deleted_entries.keys())) startxref = f.tell() f.write(b"xref\n") @@ -177,7 +186,7 @@ class XrefTable: # find a contiguous sequence of object IDs prev = None for index, key in enumerate(keys): - if prev is None or prev+1 == key: + if prev is None or prev + 1 == key: prev = key else: contiguous_keys = keys[:index] @@ -186,25 +195,27 @@ class XrefTable: else: contiguous_keys = keys keys = None - f.write(make_bytes("%d %d\n" % - (contiguous_keys[0], len(contiguous_keys)))) + f.write(make_bytes("%d %d\n" % (contiguous_keys[0], len(contiguous_keys)))) for object_id in contiguous_keys: if object_id in self.new_entries: - f.write(make_bytes("%010d %05d n \n" % - self.new_entries[object_id])) + f.write(make_bytes("%010d %05d n \n" % self.new_entries[object_id])) else: this_deleted_object_id = deleted_keys.pop(0) - check_format_condition(object_id == this_deleted_object_id, - "expected the next deleted object " - "ID to be %s, instead found %s" % - (object_id, this_deleted_object_id)) + check_format_condition( + object_id == this_deleted_object_id, + "expected the next deleted object ID to be %s, instead found %s" + % (object_id, this_deleted_object_id), + ) try: next_in_linked_list = deleted_keys[0] except IndexError: next_in_linked_list = 0 - f.write(make_bytes("%010d %05d f \n" % - (next_in_linked_list, - self.deleted_entries[object_id]))) + f.write( + make_bytes( + "%010d %05d f \n" + % (next_in_linked_list, self.deleted_entries[object_id]) + ) + ) return startxref @@ -221,8 +232,9 @@ class PdfName: return self.name.decode("us-ascii") def __eq__(self, other): - return (isinstance(other, PdfName) and other.name == self.name) or \ - other == self.name + return ( + isinstance(other, PdfName) and other.name == self.name + ) or other == self.name def __hash__(self): return hash(self.name) @@ -282,18 +294,18 @@ class PdfDict(UserDict): if value.startswith("D:"): value = value[2:] - relationship = 'Z' + relationship = "Z" if len(value) > 17: relationship = value[14] offset = int(value[15:17]) * 60 if len(value) > 20: offset += int(value[18:20]) - format = '%Y%m%d%H%M%S'[:len(value) - 2] - value = time.strptime(value[:len(format)+2], format) - if relationship in ['+', '-']: + format = "%Y%m%d%H%M%S"[: len(value) - 2] + value = time.strptime(value[: len(format) + 2], format) + if relationship in ["+", "-"]: offset *= 60 - if relationship == '+': + if relationship == "+": offset *= -1 value = time.gmtime(calendar.timegm(value) + offset) return value @@ -320,9 +332,12 @@ class PdfBinary: self.data = data if py3: # Python 3.x + def __bytes__(self): return make_bytes("<%s>" % "".join("%02X" % b for b in self.data)) + else: # Python 2.x + def __str__(self): return "<%s>" % "".join("%02X" % ord(b) for b in self.data) @@ -345,8 +360,8 @@ class PdfStream: return zlib.decompress(self.buf, bufsize=int(expected_length)) else: raise NotImplementedError( - "stream filter %s unknown/unsupported" % - repr(self.dictionary.Filter)) + "stream filter %s unknown/unsupported" % repr(self.dictionary.Filter) + ) def pdf_repr(x): @@ -361,13 +376,14 @@ def pdf_repr(x): elif isinstance(x, int): return str(x).encode("us-ascii") elif isinstance(x, time.struct_time): - return b'(D:'+time.strftime('%Y%m%d%H%M%SZ', x).encode("us-ascii")+b')' + return b"(D:" + time.strftime("%Y%m%d%H%M%SZ", x).encode("us-ascii") + b")" elif isinstance(x, dict): return bytes(PdfDict(x)) elif isinstance(x, list): return bytes(PdfArray(x)) - elif ((py3 and isinstance(x, str)) or - (not py3 and isinstance(x, unicode))): # noqa: F821 + elif (py3 and isinstance(x, str)) or ( + not py3 and isinstance(x, unicode) # noqa: F821 + ): return pdf_repr(encode_text(x)) elif isinstance(x, bytes): # XXX escape more chars? handle binary garbage @@ -385,11 +401,9 @@ class PdfParser: Supports PDF up to 1.4 """ - def __init__(self, filename=None, f=None, - buf=None, start_offset=0, mode="rb"): + def __init__(self, filename=None, f=None, buf=None, start_offset=0, mode="rb"): if buf and f: - raise RuntimeError( - "specify buf or f or filename, but not both buf and f") + raise RuntimeError("specify buf or f or filename, but not both buf and f") self.filename = filename self.buf = buf self.f = f @@ -463,13 +477,13 @@ class PdfParser: self.root_ref = self.next_object_id(self.f.tell()) self.pages_ref = self.next_object_id(0) self.rewrite_pages() - self.write_obj(self.root_ref, - Type=PdfName(b"Catalog"), - Pages=self.pages_ref) - self.write_obj(self.pages_ref, - Type=PdfName(b"Pages"), - Count=len(self.pages), - Kids=self.pages) + self.write_obj(self.root_ref, Type=PdfName(b"Catalog"), Pages=self.pages_ref) + self.write_obj( + self.pages_ref, + Type=PdfName(b"Pages"), + Count=len(self.pages), + Kids=self.pages, + ) return self.root_ref def rewrite_pages(self): @@ -515,8 +529,11 @@ class PdfParser: if self.info: trailer_dict[b"Info"] = self.info_ref self.last_xref_section_offset = start_xref - self.f.write(b"trailer\n" + bytes(PdfDict(trailer_dict)) + - make_bytes("\nstartxref\n%d\n%%%%EOF" % start_xref)) + self.f.write( + b"trailer\n" + + bytes(PdfDict(trailer_dict)) + + make_bytes("\nstartxref\n%d\n%%%%EOF" % start_xref) + ) def write_page(self, ref, *objs, **dict_obj): if isinstance(ref, int): @@ -578,12 +595,14 @@ class PdfParser: else: self.info = PdfDict(self.read_indirect(self.info_ref)) check_format_condition(b"Type" in self.root, "/Type missing in Root") - check_format_condition(self.root[b"Type"] == b"Catalog", - "/Type in Root is not /Catalog") + check_format_condition( + self.root[b"Type"] == b"Catalog", "/Type in Root is not /Catalog" + ) check_format_condition(b"Pages" in self.root, "/Pages missing in Root") - check_format_condition(isinstance(self.root[b"Pages"], - IndirectReference), - "/Pages in Root is not an indirect reference") + check_format_condition( + isinstance(self.root[b"Pages"], IndirectReference), + "/Pages in Root is not an indirect reference", + ) self.pages_ref = self.root[b"Pages"] self.page_tree_root = self.read_indirect(self.pages_ref) self.pages = self.linearize_page_tree(self.page_tree_root) @@ -611,13 +630,34 @@ class PdfParser: newline_only = br"[\r\n]+" newline = whitespace_optional + newline_only + whitespace_optional re_trailer_end = re.compile( - whitespace_mandatory + br"trailer" + whitespace_optional + - br"\<\<(.*\>\>)" + newline + br"startxref" + newline + br"([0-9]+)" + - newline + br"%%EOF" + whitespace_optional + br"$", re.DOTALL) + whitespace_mandatory + + br"trailer" + + whitespace_optional + + br"\<\<(.*\>\>)" + + newline + + br"startxref" + + newline + + br"([0-9]+)" + + newline + + br"%%EOF" + + whitespace_optional + + br"$", + re.DOTALL, + ) re_trailer_prev = re.compile( - whitespace_optional + br"trailer" + whitespace_optional + - br"\<\<(.*?\>\>)" + newline + br"startxref" + newline + br"([0-9]+)" + - newline + br"%%EOF" + whitespace_optional, re.DOTALL) + whitespace_optional + + br"trailer" + + whitespace_optional + + br"\<\<(.*?\>\>)" + + newline + + br"startxref" + + newline + + br"([0-9]+)" + + newline + + br"%%EOF" + + whitespace_optional, + re.DOTALL, + ) def read_trailer(self): search_start_offset = len(self.buf) - 16384 @@ -629,7 +669,7 @@ class PdfParser: last_match = m while m: last_match = m - m = self.re_trailer_end.search(self.buf, m.start()+16) + m = self.re_trailer_end.search(self.buf, m.start() + 16) if not m: m = last_match trailer_data = m.group(1) @@ -641,26 +681,29 @@ class PdfParser: self.read_prev_trailer(self.trailer_dict[b"Prev"]) def read_prev_trailer(self, xref_section_offset): - trailer_offset = self.read_xref_table( - xref_section_offset=xref_section_offset) + trailer_offset = self.read_xref_table(xref_section_offset=xref_section_offset) m = self.re_trailer_prev.search( - self.buf[trailer_offset:trailer_offset+16384]) + self.buf[trailer_offset : trailer_offset + 16384] + ) check_format_condition(m, "previous trailer not found") trailer_data = m.group(1) - check_format_condition(int(m.group(2)) == xref_section_offset, - "xref section offset in previous trailer " - "doesn't match what was expected") + check_format_condition( + int(m.group(2)) == xref_section_offset, + "xref section offset in previous trailer doesn't match what was expected", + ) trailer_dict = self.interpret_trailer(trailer_data) if b"Prev" in trailer_dict: self.read_prev_trailer(trailer_dict[b"Prev"]) re_whitespace_optional = re.compile(whitespace_optional) re_name = re.compile( - whitespace_optional + br"/([!-$&'*-.0-;=?-Z\\^-z|~]+)(?=" + - delimiter_or_ws + br")") + whitespace_optional + + br"/([!-$&'*-.0-;=?-Z\\^-z|~]+)(?=" + + delimiter_or_ws + + br")" + ) re_dict_start = re.compile(whitespace_optional + br"\<\<") - re_dict_end = re.compile( - whitespace_optional + br"\>\>" + whitespace_optional) + re_dict_end = re.compile(whitespace_optional + br"\>\>" + whitespace_optional) @classmethod def interpret_trailer(cls, trailer_data): @@ -672,19 +715,21 @@ class PdfParser: m = cls.re_dict_end.match(trailer_data, offset) check_format_condition( m and m.end() == len(trailer_data), - "name not found in trailer, remaining data: " + - repr(trailer_data[offset:])) + "name not found in trailer, remaining data: " + + repr(trailer_data[offset:]), + ) break key = cls.interpret_name(m.group(1)) value, offset = cls.get_value(trailer_data, m.end()) trailer[key] = value check_format_condition( b"Size" in trailer and isinstance(trailer[b"Size"], int), - "/Size not in trailer or not an integer") + "/Size not in trailer or not an integer", + ) check_format_condition( - b"Root" in trailer and - isinstance(trailer[b"Root"], IndirectReference), - "/Root not in trailer or not an indirect reference") + b"Root" in trailer and isinstance(trailer[b"Root"], IndirectReference), + "/Root not in trailer or not an indirect reference", + ) return trailer re_hashes_in_name = re.compile(br"([^#]*)(#([0-9a-fA-F]{2}))?") @@ -694,8 +739,7 @@ class PdfParser: name = b"" for m in cls.re_hashes_in_name.finditer(raw): if m.group(3): - name += m.group(1) + \ - bytearray.fromhex(m.group(3).decode("us-ascii")) + name += m.group(1) + bytearray.fromhex(m.group(3).decode("us-ascii")) else: name += m.group(1) if as_text: @@ -703,37 +747,54 @@ class PdfParser: else: return bytes(name) - re_null = re.compile( - whitespace_optional + br"null(?=" + delimiter_or_ws + br")") - re_true = re.compile( - whitespace_optional + br"true(?=" + delimiter_or_ws + br")") - re_false = re.compile( - whitespace_optional + br"false(?=" + delimiter_or_ws + br")") + re_null = re.compile(whitespace_optional + br"null(?=" + delimiter_or_ws + br")") + re_true = re.compile(whitespace_optional + br"true(?=" + delimiter_or_ws + br")") + re_false = re.compile(whitespace_optional + br"false(?=" + delimiter_or_ws + br")") re_int = re.compile( - whitespace_optional + br"([-+]?[0-9]+)(?=" + delimiter_or_ws + br")") + whitespace_optional + br"([-+]?[0-9]+)(?=" + delimiter_or_ws + br")" + ) re_real = re.compile( - whitespace_optional + br"([-+]?([0-9]+\.[0-9]*|[0-9]*\.[0-9]+))(?=" + - delimiter_or_ws + br")") + whitespace_optional + + br"([-+]?([0-9]+\.[0-9]*|[0-9]*\.[0-9]+))(?=" + + delimiter_or_ws + + br")" + ) re_array_start = re.compile(whitespace_optional + br"\[") re_array_end = re.compile(whitespace_optional + br"]") re_string_hex = re.compile( - whitespace_optional + br"\<(" + whitespace_or_hex + br"*)\>") + whitespace_optional + br"\<(" + whitespace_or_hex + br"*)\>" + ) re_string_lit = re.compile(whitespace_optional + br"\(") re_indirect_reference = re.compile( - whitespace_optional + br"([-+]?[0-9]+)" + whitespace_mandatory + - br"([-+]?[0-9]+)" + whitespace_mandatory + br"R(?=" + delimiter_or_ws + - br")") + whitespace_optional + + br"([-+]?[0-9]+)" + + whitespace_mandatory + + br"([-+]?[0-9]+)" + + whitespace_mandatory + + br"R(?=" + + delimiter_or_ws + + br")" + ) re_indirect_def_start = re.compile( - whitespace_optional + br"([-+]?[0-9]+)" + whitespace_mandatory + - br"([-+]?[0-9]+)" + whitespace_mandatory + br"obj(?=" + - delimiter_or_ws + br")") + whitespace_optional + + br"([-+]?[0-9]+)" + + whitespace_mandatory + + br"([-+]?[0-9]+)" + + whitespace_mandatory + + br"obj(?=" + + delimiter_or_ws + + br")" + ) re_indirect_def_end = re.compile( - whitespace_optional + br"endobj(?=" + delimiter_or_ws + br")") + whitespace_optional + br"endobj(?=" + delimiter_or_ws + br")" + ) re_comment = re.compile( - br"(" + whitespace_optional + br"%[^\r\n]*" + newline + br")*") + br"(" + whitespace_optional + br"%[^\r\n]*" + newline + br")*" + ) re_stream_start = re.compile(whitespace_optional + br"stream\r?\n") re_stream_end = re.compile( - whitespace_optional + br"endstream(?=" + delimiter_or_ws + br")") + whitespace_optional + br"endstream(?=" + delimiter_or_ws + br")" + ) @classmethod def get_value(cls, data, offset, expect_indirect=None, max_nesting=-1): @@ -746,32 +807,37 @@ class PdfParser: if m: check_format_condition( int(m.group(1)) > 0, - "indirect object definition: object ID must be greater than 0") + "indirect object definition: object ID must be greater than 0", + ) check_format_condition( int(m.group(2)) >= 0, - "indirect object definition: generation must be non-negative") + "indirect object definition: generation must be non-negative", + ) check_format_condition( - expect_indirect is None or expect_indirect == - IndirectReference(int(m.group(1)), int(m.group(2))), - "indirect object definition different than expected") - object, offset = cls.get_value( - data, m.end(), max_nesting=max_nesting-1) + expect_indirect is None + or expect_indirect + == IndirectReference(int(m.group(1)), int(m.group(2))), + "indirect object definition different than expected", + ) + object, offset = cls.get_value(data, m.end(), max_nesting=max_nesting - 1) if offset is None: return object, None m = cls.re_indirect_def_end.match(data, offset) - check_format_condition( - m, "indirect object definition end not found") + check_format_condition(m, "indirect object definition end not found") return object, m.end() check_format_condition( - not expect_indirect, "indirect object definition not found") + not expect_indirect, "indirect object definition not found" + ) m = cls.re_indirect_reference.match(data, offset) if m: check_format_condition( int(m.group(1)) > 0, - "indirect object reference: object ID must be greater than 0") + "indirect object reference: object ID must be greater than 0", + ) check_format_condition( int(m.group(2)) >= 0, - "indirect object reference: generation must be non-negative") + "indirect object reference: generation must be non-negative", + ) return IndirectReference(int(m.group(1)), int(m.group(2))), m.end() m = cls.re_dict_start.match(data, offset) if m: @@ -779,12 +845,10 @@ class PdfParser: result = {} m = cls.re_dict_end.match(data, offset) while not m: - key, offset = cls.get_value( - data, offset, max_nesting=max_nesting-1) + key, offset = cls.get_value(data, offset, max_nesting=max_nesting - 1) if offset is None: return result, None - value, offset = cls.get_value( - data, offset, max_nesting=max_nesting-1) + value, offset = cls.get_value(data, offset, max_nesting=max_nesting - 1) result[key] = value if offset is None: return result, None @@ -796,9 +860,10 @@ class PdfParser: stream_len = int(result[b"Length"]) except (TypeError, KeyError, ValueError): raise PdfFormatError( - "bad or missing Length in stream dict (%r)" % - result.get(b"Length", None)) - stream_data = data[m.end():m.end() + stream_len] + "bad or missing Length in stream dict (%r)" + % result.get(b"Length", None) + ) + stream_data = data[m.end() : m.end() + stream_len] m = cls.re_stream_end.match(data, m.end() + stream_len) check_format_condition(m, "stream end not found") offset = m.end() @@ -812,8 +877,7 @@ class PdfParser: result = [] m = cls.re_array_end.match(data, offset) while not m: - value, offset = cls.get_value( - data, offset, max_nesting=max_nesting-1) + value, offset = cls.get_value(data, offset, max_nesting=max_nesting - 1) result.append(value) if offset is None: return result, None @@ -841,10 +905,9 @@ class PdfParser: m = cls.re_string_hex.match(data, offset) if m: # filter out whitespace - hex_string = bytearray([ - b for b in m.group(1) - if b in b"0123456789abcdefABCDEF" - ]) + hex_string = bytearray( + [b for b in m.group(1) if b in b"0123456789abcdefABCDEF"] + ) if len(hex_string) % 2 == 1: # append a 0 if the length is not even - yes, at the end hex_string.append(ord(b"0")) @@ -853,11 +916,11 @@ class PdfParser: if m: return cls.get_literal_string(data, m.end()) # return None, offset # fallback (only for debugging) - raise PdfFormatError( - "unrecognized object: " + repr(data[offset:offset+32])) + raise PdfFormatError("unrecognized object: " + repr(data[offset : offset + 32])) re_lit_str_token = re.compile( - br"(\\[nrtbf()\\])|(\\[0-9]{1,3})|(\\(\r\n|\r|\n))|(\r\n|\r|\n)|(\()|(\))") + br"(\\[nrtbf()\\])|(\\[0-9]{1,3})|(\\(\r\n|\r|\n))|(\r\n|\r|\n)|(\()|(\))" + ) escaped_chars = { b"n": b"\n", b"r": b"\r", @@ -875,14 +938,14 @@ class PdfParser: ord(b"("): b"(", ord(b")"): b")", ord(b"\\"): b"\\", - } + } @classmethod def get_literal_string(cls, data, offset): nesting_depth = 0 result = bytearray() for m in cls.re_lit_str_token.finditer(data, offset): - result.extend(data[offset:m.start()]) + result.extend(data[offset : m.start()]) if m.group(1): result.extend(cls.escaped_chars[m.group(1)[1]]) elif m.group(2): @@ -902,30 +965,36 @@ class PdfParser: offset = m.end() raise PdfFormatError("unfinished literal string") - re_xref_section_start = re.compile( - whitespace_optional + br"xref" + newline) + re_xref_section_start = re.compile(whitespace_optional + br"xref" + newline) re_xref_subsection_start = re.compile( - whitespace_optional + br"([0-9]+)" + whitespace_mandatory + - br"([0-9]+)" + whitespace_optional + newline_only) + whitespace_optional + + br"([0-9]+)" + + whitespace_mandatory + + br"([0-9]+)" + + whitespace_optional + + newline_only + ) re_xref_entry = re.compile(br"([0-9]{10}) ([0-9]{5}) ([fn])( \r| \n|\r\n)") def read_xref_table(self, xref_section_offset): subsection_found = False m = self.re_xref_section_start.match( - self.buf, xref_section_offset + self.start_offset) + self.buf, xref_section_offset + self.start_offset + ) check_format_condition(m, "xref section start not found") offset = m.end() while True: m = self.re_xref_subsection_start.match(self.buf, offset) if not m: check_format_condition( - subsection_found, "xref subsection start not found") + subsection_found, "xref subsection start not found" + ) break subsection_found = True offset = m.end() first_object = int(m.group(1)) num_objects = int(m.group(2)) - for i in range(first_object, first_object+num_objects): + for i in range(first_object, first_object + num_objects): m = self.re_xref_entry.match(self.buf, offset) check_format_condition(m, "xref entry not found") offset = m.end() @@ -934,9 +1003,9 @@ class PdfParser: if not is_free: new_entry = (int(m.group(1)), generation) check_format_condition( - i not in self.xref_table or - self.xref_table[i] == new_entry, - "xref entry duplicated (and not identical)") + i not in self.xref_table or self.xref_table[i] == new_entry, + "xref entry duplicated (and not identical)", + ) self.xref_table[i] = new_entry return offset @@ -946,10 +1015,14 @@ class PdfParser: generation == ref[1], "expected to find generation %s for object ID %s in xref table, " "instead found generation %s at offset %s" - % (ref[1], ref[0], generation, offset)) - value = self.get_value(self.buf, offset + self.start_offset, - expect_indirect=IndirectReference(*ref), - max_nesting=max_nesting)[0] + % (ref[1], ref[0], generation, offset), + ) + value = self.get_value( + self.buf, + offset + self.start_offset, + expect_indirect=IndirectReference(*ref), + max_nesting=max_nesting, + )[0] self.cached_objects[ref] = value return value @@ -957,7 +1030,8 @@ class PdfParser: if node is None: node = self.page_tree_root check_format_condition( - node[b"Type"] == b"Pages", "/Type of page tree node is not /Pages") + node[b"Type"] == b"Pages", "/Type of page tree node is not /Pages" + ) pages = [] for kid in node[b"Kids"]: kid_object = self.read_indirect(kid) diff --git a/src/PIL/PixarImagePlugin.py b/src/PIL/PixarImagePlugin.py index b4f19a96c..dc71ca17a 100644 --- a/src/PIL/PixarImagePlugin.py +++ b/src/PIL/PixarImagePlugin.py @@ -30,6 +30,7 @@ __version__ = "0.1" # # helpers + def _accept(prefix): return prefix[:4] == b"\200\350\000\000" @@ -37,6 +38,7 @@ def _accept(prefix): ## # Image plugin for PIXAR raster images. + class PixarImageFile(ImageFile.ImageFile): format = "PIXAR" @@ -62,7 +64,7 @@ class PixarImageFile(ImageFile.ImageFile): # FIXME: to be continued... # create tile descriptor (assuming "dumped") - self.tile = [("raw", (0, 0)+self.size, 1024, (self.mode, 0, 1))] + self.tile = [("raw", (0, 0) + self.size, 1024, (self.mode, 0, 1))] # diff --git a/src/PIL/PngImagePlugin.py b/src/PIL/PngImagePlugin.py index 4e192ecd6..10e18e4a0 100644 --- a/src/PIL/PngImagePlugin.py +++ b/src/PIL/PngImagePlugin.py @@ -55,29 +55,29 @@ _MAGIC = b"\211PNG\r\n\032\n" _MODES = { # supported bits/color combinations, and corresponding modes/rawmodes # Greyscale - (1, 0): ("1", "1"), - (2, 0): ("L", "L;2"), - (4, 0): ("L", "L;4"), - (8, 0): ("L", "L"), + (1, 0): ("1", "1"), + (2, 0): ("L", "L;2"), + (4, 0): ("L", "L;4"), + (8, 0): ("L", "L"), (16, 0): ("I", "I;16B"), # Truecolour - (8, 2): ("RGB", "RGB"), + (8, 2): ("RGB", "RGB"), (16, 2): ("RGB", "RGB;16B"), # Indexed-colour - (1, 3): ("P", "P;1"), - (2, 3): ("P", "P;2"), - (4, 3): ("P", "P;4"), - (8, 3): ("P", "P"), + (1, 3): ("P", "P;1"), + (2, 3): ("P", "P;2"), + (4, 3): ("P", "P;4"), + (8, 3): ("P", "P"), # Greyscale with alpha - (8, 4): ("LA", "LA"), + (8, 4): ("LA", "LA"), (16, 4): ("RGBA", "LA;16B"), # LA;16B->LA not yet available # Truecolour with alpha - (8, 6): ("RGBA", "RGBA"), + (8, 6): ("RGBA", "RGBA"), (16, 6): ("RGBA", "RGBA;16B"), } -_simple_palette = re.compile(b'^\xff*\x00\xff*$') +_simple_palette = re.compile(b"^\xff*\x00\xff*$") # Maximum decompressed size for a iTXt or zTXt chunk. # Eliminates decompression bombs where compressed chunks can expand 1000x @@ -95,14 +95,14 @@ def _safe_zlib_decompress(s): def _crc32(data, seed=0): - return zlib.crc32(data, seed) & 0xffffffff + return zlib.crc32(data, seed) & 0xFFFFFFFF # -------------------------------------------------------------------- # Support classes. Suitable for PNG and related formats like MNG etc. -class ChunkStream(object): +class ChunkStream(object): def __init__(self, fp): self.fp = fp @@ -144,7 +144,7 @@ class ChunkStream(object): """Call the appropriate chunk handler""" logger.debug("STREAM %r %s %s", cid, pos, length) - return getattr(self, "chunk_" + cid.decode('ascii'))(pos, length) + return getattr(self, "chunk_" + cid.decode("ascii"))(pos, length) def crc(self, cid, data): """Read and verify checksum""" @@ -160,11 +160,9 @@ class ChunkStream(object): crc1 = _crc32(data, _crc32(cid)) crc2 = i32(self.fp.read(4)) if crc1 != crc2: - raise SyntaxError("broken PNG file (bad header checksum in %r)" - % cid) + raise SyntaxError("broken PNG file (bad header checksum in %r)" % cid) except struct.error: - raise SyntaxError("broken PNG file (incomplete checksum in %r)" - % cid) + raise SyntaxError("broken PNG file (incomplete checksum in %r)" % cid) def crc_skip(self, cid, data): """Read checksum. Used if the C module is not present""" @@ -198,6 +196,7 @@ class iTXt(str): keeping their extra information """ + @staticmethod def __new__(cls, text, lang=None, tkey=None): """ @@ -253,11 +252,12 @@ class PngInfo(object): tkey = tkey.encode("utf-8", "strict") if zip: - self.add(b"iTXt", key + b"\0\x01\0" + lang + b"\0" + tkey + b"\0" + - zlib.compress(value)) + self.add( + b"iTXt", + key + b"\0\x01\0" + lang + b"\0" + tkey + b"\0" + zlib.compress(value), + ) else: - self.add(b"iTXt", key + b"\0\0\0" + lang + b"\0" + tkey + b"\0" + - value) + self.add(b"iTXt", key + b"\0\0\0" + lang + b"\0" + tkey + b"\0" + value) def add_text(self, key, value, zip=False): """Appends a text chunk. @@ -274,12 +274,12 @@ class PngInfo(object): # The tEXt chunk stores latin-1 text if not isinstance(value, bytes): try: - value = value.encode('latin-1', 'strict') + value = value.encode("latin-1", "strict") except UnicodeError: return self.add_itxt(key, value, zip=zip) if not isinstance(key, bytes): - key = key.encode('latin-1', 'strict') + key = key.encode("latin-1", "strict") if zip: self.add(b"zTXt", key + b"\0\0" + zlib.compress(value)) @@ -290,8 +290,8 @@ class PngInfo(object): # -------------------------------------------------------------------- # PNG image stream (IHDR/IEND) -class PngStream(ChunkStream): +class PngStream(ChunkStream): def __init__(self, fp): ChunkStream.__init__(self, fp) @@ -310,8 +310,10 @@ class PngStream(ChunkStream): def check_text_memory(self, chunklen): self.text_memory += chunklen if self.text_memory > MAX_TEXT_MEMORY: - raise ValueError("Too much memory used in text chunks: " - "%s>MAX_TEXT_MEMORY" % self.text_memory) + raise ValueError( + "Too much memory used in text chunks: %s>MAX_TEXT_MEMORY" + % self.text_memory + ) def chunk_iCCP(self, pos, length): @@ -327,10 +329,11 @@ class PngStream(ChunkStream): logger.debug("Compression method %s", i8(s[i])) comp_method = i8(s[i]) if comp_method != 0: - raise SyntaxError("Unknown compression method %s in iCCP chunk" % - comp_method) + raise SyntaxError( + "Unknown compression method %s in iCCP chunk" % comp_method + ) try: - icc_profile = _safe_zlib_decompress(s[i+2:]) + icc_profile = _safe_zlib_decompress(s[i + 2 :]) except ValueError: if ImageFile.LOAD_TRUNCATED_IMAGES: icc_profile = None @@ -359,7 +362,7 @@ class PngStream(ChunkStream): def chunk_IDAT(self, pos, length): # image data - self.im_tile = [("zip", (0, 0)+self.im_size, pos, self.im_rawmode)] + self.im_tile = [("zip", (0, 0) + self.im_size, pos, self.im_rawmode)] self.im_idat = length raise EOFError @@ -408,8 +411,8 @@ class PngStream(ChunkStream): # WP x,y, Red x,y, Green x,y Blue x,y s = ImageFile._safe_read(self.fp, length) - raw_vals = struct.unpack('>%dI' % (len(s) // 4), s) - self.im_info['chromaticity'] = tuple(elt/100000.0 for elt in raw_vals) + raw_vals = struct.unpack(">%dI" % (len(s) // 4), s) + self.im_info["chromaticity"] = tuple(elt / 100000.0 for elt in raw_vals) return s def chunk_sRGB(self, pos, length): @@ -420,7 +423,7 @@ class PngStream(ChunkStream): # 3 absolute colorimetric s = ImageFile._safe_read(self.fp, length) - self.im_info['srgb'] = i8(s) + self.im_info["srgb"] = i8(s) return s def chunk_pHYs(self, pos, length): @@ -448,8 +451,8 @@ class PngStream(ChunkStream): v = b"" if k: if py3: - k = k.decode('latin-1', 'strict') - v = v.decode('latin-1', 'replace') + k = k.decode("latin-1", "strict") + v = v.decode("latin-1", "replace") self.im_info[k] = self.im_text[k] = v self.check_text_memory(len(v)) @@ -470,8 +473,9 @@ class PngStream(ChunkStream): else: comp_method = 0 if comp_method != 0: - raise SyntaxError("Unknown compression method %s in zTXt chunk" % - comp_method) + raise SyntaxError( + "Unknown compression method %s in zTXt chunk" % comp_method + ) try: v = _safe_zlib_decompress(v[1:]) except ValueError: @@ -484,8 +488,8 @@ class PngStream(ChunkStream): if k: if py3: - k = k.decode('latin-1', 'strict') - v = v.decode('latin-1', 'replace') + k = k.decode("latin-1", "strict") + v = v.decode("latin-1", "replace") self.im_info[k] = self.im_text[k] = v self.check_text_memory(len(v)) @@ -536,19 +540,20 @@ class PngStream(ChunkStream): def chunk_eXIf(self, pos, length): s = ImageFile._safe_read(self.fp, length) - self.im_info["exif"] = b"Exif\x00\x00"+s + self.im_info["exif"] = b"Exif\x00\x00" + s return s # APNG chunks def chunk_acTL(self, pos, length): s = ImageFile._safe_read(self.fp, length) - self.im_custom_mimetype = 'image/apng' + self.im_custom_mimetype = "image/apng" return s # -------------------------------------------------------------------- # PNG reader + def _accept(prefix): return prefix[:8] == _MAGIC @@ -556,6 +561,7 @@ def _accept(prefix): ## # Image plugin for PNG images. + class PngImageFile(ImageFile.ImageFile): format = "PNG" @@ -711,20 +717,20 @@ class PngImageFile(ImageFile.ImageFile): _OUTMODES = { # supported PIL modes, and corresponding rawmodes/bits/color combinations - "1": ("1", b'\x01\x00'), - "L;1": ("L;1", b'\x01\x00'), - "L;2": ("L;2", b'\x02\x00'), - "L;4": ("L;4", b'\x04\x00'), - "L": ("L", b'\x08\x00'), - "LA": ("LA", b'\x08\x04'), - "I": ("I;16B", b'\x10\x00'), - "I;16": ("I;16B", b'\x10\x00'), - "P;1": ("P;1", b'\x01\x03'), - "P;2": ("P;2", b'\x02\x03'), - "P;4": ("P;4", b'\x04\x03'), - "P": ("P", b'\x08\x03'), - "RGB": ("RGB", b'\x08\x02'), - "RGBA": ("RGBA", b'\x08\x06'), + "1": ("1", b"\x01\x00"), + "L;1": ("L;1", b"\x01\x00"), + "L;2": ("L;2", b"\x02\x00"), + "L;4": ("L;4", b"\x04\x00"), + "L": ("L", b"\x08\x00"), + "LA": ("LA", b"\x08\x04"), + "I": ("I;16B", b"\x10\x00"), + "I;16": ("I;16B", b"\x10\x00"), + "P;1": ("P;1", b"\x01\x03"), + "P;2": ("P;2", b"\x02\x03"), + "P;4": ("P;4", b"\x04\x03"), + "P": ("P", b"\x08\x03"), + "RGB": ("RGB", b"\x08\x02"), + "RGBA": ("RGBA", b"\x08\x06"), } @@ -765,7 +771,7 @@ def _save(im, fp, filename, chunk=putchunk): else: # check palette contents if im.palette: - colors = max(min(len(im.palette.getdata()[1])//3, 256), 2) + colors = max(min(len(im.palette.getdata()[1]) // 3, 256), 2) else: colors = 256 @@ -781,10 +787,12 @@ def _save(im, fp, filename, chunk=putchunk): mode = "%s;%d" % (mode, bits) # encoder options - im.encoderconfig = (im.encoderinfo.get("optimize", False), - im.encoderinfo.get("compress_level", -1), - im.encoderinfo.get("compress_type", -1), - im.encoderinfo.get("dictionary", b"")) + im.encoderconfig = ( + im.encoderinfo.get("optimize", False), + im.encoderinfo.get("compress_level", -1), + im.encoderinfo.get("compress_type", -1), + im.encoderinfo.get("dictionary", b""), + ) # get the corresponding PNG mode try: @@ -797,12 +805,16 @@ def _save(im, fp, filename, chunk=putchunk): fp.write(_MAGIC) - chunk(fp, b"IHDR", - o32(im.size[0]), o32(im.size[1]), # 0: size - mode, # 8: depth/type - b'\0', # 10: compression - b'\0', # 11: filter category - b'\0') # 12: interlace flag + chunk( + fp, + b"IHDR", + o32(im.size[0]), # 0: size + o32(im.size[1]), + mode, # 8: depth/type + b"\0", # 10: compression + b"\0", # 11: filter category + b"\0", # 12: interlace flag + ) chunks = [b"cHRM", b"gAMA", b"sBIT", b"sRGB", b"tIME"] @@ -836,21 +848,20 @@ def _save(im, fp, filename, chunk=putchunk): palette_byte_number = (2 ** bits) * 3 palette_bytes = im.im.getpalette("RGB")[:palette_byte_number] while len(palette_bytes) < palette_byte_number: - palette_bytes += b'\0' + palette_bytes += b"\0" chunk(fp, b"PLTE", palette_bytes) - transparency = im.encoderinfo.get('transparency', - im.info.get('transparency', None)) + transparency = im.encoderinfo.get("transparency", im.info.get("transparency", None)) if transparency or transparency == 0: if im.mode == "P": # limit to actual palette size - alpha_bytes = 2**bits + alpha_bytes = 2 ** bits if isinstance(transparency, bytes): chunk(fp, b"tRNS", transparency[:alpha_bytes]) else: transparency = max(0, min(255, transparency)) - alpha = b'\xFF' * transparency + b'\0' + alpha = b"\xFF" * transparency + b"\0" chunk(fp, b"tRNS", alpha[:alpha_bytes]) elif im.mode in ("1", "L", "I"): transparency = max(0, min(65535, transparency)) @@ -866,15 +877,18 @@ def _save(im, fp, filename, chunk=putchunk): else: if im.mode == "P" and im.im.getpalettemode() == "RGBA": alpha = im.im.getpalette("RGBA", "A") - alpha_bytes = 2**bits + alpha_bytes = 2 ** bits chunk(fp, b"tRNS", alpha[:alpha_bytes]) dpi = im.encoderinfo.get("dpi") if dpi: - chunk(fp, b"pHYs", - o32(int(dpi[0] / 0.0254 + 0.5)), - o32(int(dpi[1] / 0.0254 + 0.5)), - b'\x01') + chunk( + fp, + b"pHYs", + o32(int(dpi[0] / 0.0254 + 0.5)), + o32(int(dpi[1] / 0.0254 + 0.5)), + b"\x01", + ) info = im.encoderinfo.get("pnginfo") if info: @@ -892,8 +906,7 @@ def _save(im, fp, filename, chunk=putchunk): exif = exif[6:] chunk(fp, b"eXIf", exif) - ImageFile._save(im, _idat(fp, chunk), - [("zip", (0, 0)+im.size, 0, rawmode)]) + ImageFile._save(im, _idat(fp, chunk), [("zip", (0, 0) + im.size, 0, rawmode)]) chunk(fp, b"IEND", b"") @@ -904,6 +917,7 @@ def _save(im, fp, filename, chunk=putchunk): # -------------------------------------------------------------------- # PNG chunk converter + def getchunks(im, **params): """Return a list of PNG chunks representing this image.""" diff --git a/src/PIL/PpmImagePlugin.py b/src/PIL/PpmImagePlugin.py index a1ac8d69e..c3e9eed6d 100644 --- a/src/PIL/PpmImagePlugin.py +++ b/src/PIL/PpmImagePlugin.py @@ -24,7 +24,7 @@ __version__ = "0.2" # # -------------------------------------------------------------------- -b_whitespace = b'\x20\x09\x0a\x0b\x0c\x0d' +b_whitespace = b"\x20\x09\x0a\x0b\x0c\x0d" MODES = { # standard @@ -36,7 +36,7 @@ MODES = { # PIL extensions (for test purposes only) b"PyP": "P", b"PyRGBA": "RGBA", - b"PyCMYK": "CMYK" + b"PyCMYK": "CMYK", } @@ -47,6 +47,7 @@ def _accept(prefix): ## # Image plugin for PBM, PGM, and PPM images. + class PpmImageFile(ImageFile.ImageFile): format = "PPM" @@ -57,10 +58,10 @@ class PpmImageFile(ImageFile.ImageFile): c = self.fp.read(1) if not c or c in b_whitespace: break - if c > b'\x79': + if c > b"\x79": raise ValueError("Expected ASCII value, found binary") s = s + c - if (len(s) > 9): + if len(s) > 9: raise ValueError("Expected int, got > 9 digits") return s @@ -92,8 +93,7 @@ class PpmImageFile(ImageFile.ImageFile): if s not in b_whitespace: break if s == b"": - raise ValueError( - "File does not extend beyond magic number") + raise ValueError("File does not extend beyond magic number") if s != b"#": break s = self.fp.readline() @@ -107,32 +107,30 @@ class PpmImageFile(ImageFile.ImageFile): elif ix == 2: # maxgrey if s > 255: - if not mode == 'L': + if not mode == "L": raise ValueError("Too many colors for band: %s" % s) - if s < 2**16: - self.mode = 'I' - rawmode = 'I;16B' + if s < 2 ** 16: + self.mode = "I" + rawmode = "I;16B" else: - self.mode = 'I' - rawmode = 'I;32B' + self.mode = "I" + rawmode = "I;32B" self._size = xsize, ysize - self.tile = [("raw", - (0, 0, xsize, ysize), - self.fp.tell(), - (rawmode, 0, 1))] + self.tile = [("raw", (0, 0, xsize, ysize), self.fp.tell(), (rawmode, 0, 1))] # # -------------------------------------------------------------------- + def _save(im, fp, filename): if im.mode == "1": rawmode, head = "1;I", b"P4" elif im.mode == "L": rawmode, head = "L", b"P5" elif im.mode == "I": - if im.getextrema()[1] < 2**16: + if im.getextrema()[1] < 2 ** 16: rawmode, head = "I;16B", b"P5" else: rawmode, head = "I;32B", b"P5" @@ -142,7 +140,7 @@ def _save(im, fp, filename): rawmode, head = "RGB", b"P6" else: raise IOError("cannot write mode %s as PPM" % im.mode) - fp.write(head + ("\n%d %d\n" % im.size).encode('ascii')) + fp.write(head + ("\n%d %d\n" % im.size).encode("ascii")) if head == b"P6": fp.write(b"255\n") if head == b"P5": @@ -152,11 +150,12 @@ def _save(im, fp, filename): fp.write(b"65535\n") elif rawmode == "I;32B": fp.write(b"2147483648\n") - ImageFile._save(im, fp, [("raw", (0, 0)+im.size, 0, (rawmode, 0, 1))]) + ImageFile._save(im, fp, [("raw", (0, 0) + im.size, 0, (rawmode, 0, 1))]) # ALTERNATIVE: save via builtin debug function # im._dump(filename) + # # -------------------------------------------------------------------- diff --git a/src/PIL/PsdImagePlugin.py b/src/PIL/PsdImagePlugin.py index 6623f8f87..576ea9024 100644 --- a/src/PIL/PsdImagePlugin.py +++ b/src/PIL/PsdImagePlugin.py @@ -34,13 +34,14 @@ MODES = { (4, 8): ("CMYK", 4), (7, 8): ("L", 1), # FIXME: multilayer (8, 8): ("L", 1), # duotone - (9, 8): ("LAB", 3) + (9, 8): ("LAB", 3), } # --------------------------------------------------------------------. # read PSD images + def _accept(prefix): return prefix[:4] == b"8BPS" @@ -48,10 +49,12 @@ def _accept(prefix): ## # Image plugin for Photoshop images. + class PsdImageFile(ImageFile.ImageFile): format = "PSD" format_description = "Adobe Photoshop" + _close_exclusive_fp_after_loading = False def _open(self): @@ -101,7 +104,7 @@ class PsdImageFile(ImageFile.ImageFile): if not (len(name) & 1): read(1) # padding data = read(i32(read(4))) - if (len(data) & 1): + if len(data) & 1: read(1) # padding self.resources.append((id, name, data)) if id == 1039: # ICC profile @@ -126,7 +129,7 @@ class PsdImageFile(ImageFile.ImageFile): self.tile = _maketile(self.fp, mode, (0, 0) + self.size, channels) # keep the file open - self._fp = self.fp + self.__fp = self.fp self.frame = 1 self._min_frame = 1 @@ -144,11 +147,11 @@ class PsdImageFile(ImageFile.ImageFile): # seek to given layer (1..max) try: - name, mode, bbox, tile = self.layers[layer-1] + name, mode, bbox, tile = self.layers[layer - 1] self.mode = mode self.tile = tile self.frame = layer - self.fp = self._fp + self.fp = self.__fp return name, bbox except IndexError: raise EOFError("no such layer") @@ -159,13 +162,21 @@ class PsdImageFile(ImageFile.ImageFile): def load_prepare(self): # create image memory if necessary - if not self.im or\ - self.im.mode != self.mode or self.im.size != self.size: + if not self.im or self.im.mode != self.mode or self.im.size != self.size: self.im = Image.core.fill(self.mode, self.size, 0) # create palette (optional) if self.mode == "P": Image.Image.load(self) + def _close__fp(self): + try: + if self.__fp != self.fp: + self.__fp.close() + except AttributeError: + pass + finally: + self.__fp = None + def _layerinfo(file): # read layerinfo block @@ -229,7 +240,7 @@ def _layerinfo(file): if length: # Don't know the proper encoding, # Latin-1 should be a good guess - name = read(length).decode('latin-1', 'replace') + name = read(length).decode("latin-1", "replace") combined += length + 1 file.seek(size - combined, io.SEEK_CUR) @@ -270,7 +281,7 @@ def _maketile(file, mode, bbox, channels): if mode == "CMYK": layer += ";I" tile.append(("raw", bbox, offset, layer)) - offset = offset + xsize*ysize + offset = offset + xsize * ysize elif compression == 1: # @@ -283,11 +294,9 @@ def _maketile(file, mode, bbox, channels): layer = mode[channel] if mode == "CMYK": layer += ";I" - tile.append( - ("packbits", bbox, offset, layer) - ) + tile.append(("packbits", bbox, offset, layer)) for y in range(ysize): - offset = offset + i16(bytecount[i:i+2]) + offset = offset + i16(bytecount[i : i + 2]) i += 2 file.seek(offset) @@ -297,6 +306,7 @@ def _maketile(file, mode, bbox, channels): return tile + # -------------------------------------------------------------------- # registry diff --git a/src/PIL/PyAccess.py b/src/PIL/PyAccess.py index 5df1d400c..fa5bac433 100644 --- a/src/PIL/PyAccess.py +++ b/src/PIL/PyAccess.py @@ -42,13 +42,12 @@ ffi.cdef(defs) class PyAccess(object): - def __init__(self, img, readonly=False): vals = dict(img.im.unsafe_ptrs) self.readonly = readonly - self.image8 = ffi.cast('unsigned char **', vals['image8']) - self.image32 = ffi.cast('int **', vals['image32']) - self.image = ffi.cast('unsigned char **', vals['image']) + self.image8 = ffi.cast("unsigned char **", vals["image8"]) + self.image32 = ffi.cast("int **", vals["image32"]) + self.image = ffi.cast("unsigned char **", vals["image"]) self.xsize, self.ysize = img.im.size # Keep pointer to im object to prevent dereferencing. @@ -75,7 +74,7 @@ class PyAccess(object): :param color: The pixel value. """ if self.readonly: - raise ValueError('Attempt to putpixel a read only image') + raise ValueError("Attempt to putpixel a read only image") (x, y) = xy if x < 0: x = self.xsize + x @@ -83,8 +82,11 @@ class PyAccess(object): y = self.ysize + y (x, y) = self.check_xy((x, y)) - if self._im.mode == "P" and \ - isinstance(color, (list, tuple)) and len(color) in [3, 4]: + if ( + self._im.mode == "P" + and isinstance(color, (list, tuple)) + and len(color) in [3, 4] + ): # RGB or RGBA value for a P image color = self._palette.getcolor(color) @@ -115,12 +117,13 @@ class PyAccess(object): def check_xy(self, xy): (x, y) = xy if not (0 <= x < self.xsize and 0 <= y < self.ysize): - raise ValueError('pixel location out of range') + raise ValueError("pixel location out of range") return xy class _PyAccess32_2(PyAccess): """ PA, LA, stored in first and last bytes of a 32 bit word """ + def _post_init(self, *args, **kwargs): self.pixels = ffi.cast("struct Pixel_RGBA **", self.image32) @@ -156,6 +159,7 @@ class _PyAccess32_3(PyAccess): class _PyAccess32_4(PyAccess): """ RGBA etc, all 4 bytes of a 32 bit word """ + def _post_init(self, *args, **kwargs): self.pixels = ffi.cast("struct Pixel_RGBA **", self.image32) @@ -174,6 +178,7 @@ class _PyAccess32_4(PyAccess): class _PyAccess8(PyAccess): """ 1, L, P, 8 bit images stored as uint8 """ + def _post_init(self, *args, **kwargs): self.pixels = self.image8 @@ -191,8 +196,9 @@ class _PyAccess8(PyAccess): class _PyAccessI16_N(PyAccess): """ I;16 access, native bitendian without conversion """ + def _post_init(self, *args, **kwargs): - self.pixels = ffi.cast('unsigned short **', self.image) + self.pixels = ffi.cast("unsigned short **", self.image) def get_pixel(self, x, y): return self.pixels[y][x] @@ -208,8 +214,9 @@ class _PyAccessI16_N(PyAccess): class _PyAccessI16_L(PyAccess): """ I;16L access, with conversion """ + def _post_init(self, *args, **kwargs): - self.pixels = ffi.cast('struct Pixel_I16 **', self.image) + self.pixels = ffi.cast("struct Pixel_I16 **", self.image) def get_pixel(self, x, y): pixel = self.pixels[y][x] @@ -228,8 +235,9 @@ class _PyAccessI16_L(PyAccess): class _PyAccessI16_B(PyAccess): """ I;16B access, with conversion """ + def _post_init(self, *args, **kwargs): - self.pixels = ffi.cast('struct Pixel_I16 **', self.image) + self.pixels = ffi.cast("struct Pixel_I16 **", self.image) def get_pixel(self, x, y): pixel = self.pixels[y][x] @@ -248,6 +256,7 @@ class _PyAccessI16_B(PyAccess): class _PyAccessI32_N(PyAccess): """ Signed Int32 access, native endian """ + def _post_init(self, *args, **kwargs): self.pixels = self.image32 @@ -260,15 +269,15 @@ class _PyAccessI32_N(PyAccess): class _PyAccessI32_Swap(PyAccess): """ I;32L/B access, with byteswapping conversion """ + def _post_init(self, *args, **kwargs): self.pixels = self.image32 def reverse(self, i): - orig = ffi.new('int *', i) - chars = ffi.cast('unsigned char *', orig) - chars[0], chars[1], chars[2], chars[3] = chars[3], chars[2], \ - chars[1], chars[0] - return ffi.cast('int *', chars)[0] + orig = ffi.new("int *", i) + chars = ffi.cast("unsigned char *", orig) + chars[0], chars[1], chars[2], chars[3] = chars[3], chars[2], chars[1], chars[0] + return ffi.cast("int *", chars)[0] def get_pixel(self, x, y): return self.reverse(self.pixels[y][x]) @@ -279,8 +288,9 @@ class _PyAccessI32_Swap(PyAccess): class _PyAccessF(PyAccess): """ 32 bit float access """ + def _post_init(self, *args, **kwargs): - self.pixels = ffi.cast('float **', self.image32) + self.pixels = ffi.cast("float **", self.image32) def get_pixel(self, x, y): return self.pixels[y][x] @@ -294,38 +304,39 @@ class _PyAccessF(PyAccess): self.pixels[y][x] = color[0] -mode_map = {'1': _PyAccess8, - 'L': _PyAccess8, - 'P': _PyAccess8, - 'LA': _PyAccess32_2, - 'La': _PyAccess32_2, - 'PA': _PyAccess32_2, - 'RGB': _PyAccess32_3, - 'LAB': _PyAccess32_3, - 'HSV': _PyAccess32_3, - 'YCbCr': _PyAccess32_3, - 'RGBA': _PyAccess32_4, - 'RGBa': _PyAccess32_4, - 'RGBX': _PyAccess32_4, - 'CMYK': _PyAccess32_4, - 'F': _PyAccessF, - 'I': _PyAccessI32_N, - } +mode_map = { + "1": _PyAccess8, + "L": _PyAccess8, + "P": _PyAccess8, + "LA": _PyAccess32_2, + "La": _PyAccess32_2, + "PA": _PyAccess32_2, + "RGB": _PyAccess32_3, + "LAB": _PyAccess32_3, + "HSV": _PyAccess32_3, + "YCbCr": _PyAccess32_3, + "RGBA": _PyAccess32_4, + "RGBa": _PyAccess32_4, + "RGBX": _PyAccess32_4, + "CMYK": _PyAccess32_4, + "F": _PyAccessF, + "I": _PyAccessI32_N, +} -if sys.byteorder == 'little': - mode_map['I;16'] = _PyAccessI16_N - mode_map['I;16L'] = _PyAccessI16_N - mode_map['I;16B'] = _PyAccessI16_B +if sys.byteorder == "little": + mode_map["I;16"] = _PyAccessI16_N + mode_map["I;16L"] = _PyAccessI16_N + mode_map["I;16B"] = _PyAccessI16_B - mode_map['I;32L'] = _PyAccessI32_N - mode_map['I;32B'] = _PyAccessI32_Swap + mode_map["I;32L"] = _PyAccessI32_N + mode_map["I;32B"] = _PyAccessI32_Swap else: - mode_map['I;16'] = _PyAccessI16_L - mode_map['I;16L'] = _PyAccessI16_L - mode_map['I;16B'] = _PyAccessI16_N + mode_map["I;16"] = _PyAccessI16_L + mode_map["I;16L"] = _PyAccessI16_L + mode_map["I;16B"] = _PyAccessI16_N - mode_map['I;32L'] = _PyAccessI32_Swap - mode_map['I;32B'] = _PyAccessI32_N + mode_map["I;32L"] = _PyAccessI32_Swap + mode_map["I;32B"] = _PyAccessI32_N def new(img, readonly=False): diff --git a/src/PIL/SgiImagePlugin.py b/src/PIL/SgiImagePlugin.py index 37867bdb7..e6d1b11c0 100644 --- a/src/PIL/SgiImagePlugin.py +++ b/src/PIL/SgiImagePlugin.py @@ -46,7 +46,7 @@ MODES = { (1, 3, 3): "RGB", (2, 3, 3): "RGB;16B", (1, 3, 4): "RGBA", - (2, 3, 4): "RGBA;16B" + (2, 3, 4): "RGBA;16B", } @@ -100,8 +100,8 @@ class SgiImageFile(ImageFile.ImageFile): self._size = xsize, ysize self.mode = rawmode.split(";")[0] - if self.mode == 'RGB': - self.custom_mimetype = 'image/rgb' + if self.mode == "RGB": + self.custom_mimetype = "image/rgb" # orientation -1 : scanlines begins at the bottom-left corner orientation = -1 @@ -110,19 +110,21 @@ class SgiImageFile(ImageFile.ImageFile): if compression == 0: pagesize = xsize * ysize * bpc if bpc == 2: - self.tile = [("SGI16", (0, 0) + self.size, - headlen, (self.mode, 0, orientation))] + self.tile = [ + ("SGI16", (0, 0) + self.size, headlen, (self.mode, 0, orientation)) + ] else: self.tile = [] offset = headlen for layer in self.mode: self.tile.append( - ("raw", (0, 0) + self.size, - offset, (layer, 0, orientation))) + ("raw", (0, 0) + self.size, offset, (layer, 0, orientation)) + ) offset += pagesize elif compression == 1: - self.tile = [("sgi_rle", (0, 0) + self.size, - headlen, (rawmode, orientation, bpc))] + self.tile = [ + ("sgi_rle", (0, 0) + self.size, headlen, (rawmode, orientation, bpc)) + ] def _save(im, fp, filename): @@ -161,8 +163,9 @@ def _save(im, fp, filename): # assert we've got the right number of bands. if len(im.getbands()) != z: - raise ValueError("incorrect number of bands in SGI write: %s vs %s" % - (z, len(im.getbands()))) + raise ValueError( + "incorrect number of bands in SGI write: %s vs %s" % (z, len(im.getbands())) + ) # Minimum Byte value pinmin = 0 @@ -171,30 +174,30 @@ def _save(im, fp, filename): # Image name (79 characters max, truncated below in write) imgName = os.path.splitext(os.path.basename(filename))[0] if py3: - imgName = imgName.encode('ascii', 'ignore') + imgName = imgName.encode("ascii", "ignore") # Standard representation of pixel in the file colormap = 0 - fp.write(struct.pack('>h', magicNumber)) + fp.write(struct.pack(">h", magicNumber)) fp.write(o8(rle)) fp.write(o8(bpc)) - fp.write(struct.pack('>H', dim)) - fp.write(struct.pack('>H', x)) - fp.write(struct.pack('>H', y)) - fp.write(struct.pack('>H', z)) - fp.write(struct.pack('>l', pinmin)) - fp.write(struct.pack('>l', pinmax)) - fp.write(struct.pack('4s', b'')) # dummy - fp.write(struct.pack('79s', imgName)) # truncates to 79 chars - fp.write(struct.pack('s', b'')) # force null byte after imgname - fp.write(struct.pack('>l', colormap)) - fp.write(struct.pack('404s', b'')) # dummy + fp.write(struct.pack(">H", dim)) + fp.write(struct.pack(">H", x)) + fp.write(struct.pack(">H", y)) + fp.write(struct.pack(">H", z)) + fp.write(struct.pack(">l", pinmin)) + fp.write(struct.pack(">l", pinmax)) + fp.write(struct.pack("4s", b"")) # dummy + fp.write(struct.pack("79s", imgName)) # truncates to 79 chars + fp.write(struct.pack("s", b"")) # force null byte after imgname + fp.write(struct.pack(">l", colormap)) + fp.write(struct.pack("404s", b"")) # dummy - rawmode = 'L' + rawmode = "L" if bpc == 2: - rawmode = 'L;16B' + rawmode = "L;16B" for channel in im.split(): - fp.write(channel.tobytes('raw', rawmode, 0, orientation)) + fp.write(channel.tobytes("raw", rawmode, 0, orientation)) fp.close() @@ -209,13 +212,15 @@ class SGI16Decoder(ImageFile.PyDecoder): self.fd.seek(512) for band in range(zsize): - channel = Image.new('L', (self.state.xsize, self.state.ysize)) - channel.frombytes(self.fd.read(2 * pagesize), 'raw', - 'L;16B', stride, orientation) + channel = Image.new("L", (self.state.xsize, self.state.ysize)) + channel.frombytes( + self.fd.read(2 * pagesize), "raw", "L;16B", stride, orientation + ) self.im.putband(channel.im, band) return -1, 0 + # # registry @@ -225,7 +230,6 @@ Image.register_open(SgiImageFile.format, SgiImageFile, _accept) Image.register_save(SgiImageFile.format, _save) Image.register_mime(SgiImageFile.format, "image/sgi") -Image.register_extensions(SgiImageFile.format, - [".bw", ".rgb", ".rgba", ".sgi"]) +Image.register_extensions(SgiImageFile.format, [".bw", ".rgb", ".rgba", ".sgi"]) # End of file diff --git a/src/PIL/SpiderImagePlugin.py b/src/PIL/SpiderImagePlugin.py index b601847cb..7b0ed3dbd 100644 --- a/src/PIL/SpiderImagePlugin.py +++ b/src/PIL/SpiderImagePlugin.py @@ -44,7 +44,7 @@ import sys def isInt(f): try: i = int(f) - if f-i == 0: + if f - i == 0: return 1 else: return 0 @@ -60,8 +60,9 @@ iforms = [1, 3, -11, -12, -21, -22] # Returns no. of bytes in the header, if it is a valid Spider header, # otherwise returns 0 + def isSpiderHeader(t): - h = (99,) + t # add 1 value so can use spider header index start=1 + h = (99,) + t # add 1 value so can use spider header index start=1 # header values 1,2,5,12,13,22,23 should be integers for i in [1, 2, 5, 12, 13, 22, 23]: if not isInt(h[i]): @@ -71,9 +72,9 @@ def isSpiderHeader(t): if iform not in iforms: return 0 # check other header values - labrec = int(h[13]) # no. records in file header - labbyt = int(h[22]) # total no. of bytes in header - lenbyt = int(h[23]) # record length in bytes + labrec = int(h[13]) # no. records in file header + labbyt = int(h[22]) # total no. of bytes in header + lenbyt = int(h[23]) # record length in bytes if labbyt != (labrec * lenbyt): return 0 # looks like a valid header @@ -81,12 +82,12 @@ def isSpiderHeader(t): def isSpiderImage(filename): - with open(filename, 'rb') as fp: - f = fp.read(92) # read 23 * 4 bytes - t = struct.unpack('>23f', f) # try big-endian first + with open(filename, "rb") as fp: + f = fp.read(92) # read 23 * 4 bytes + t = struct.unpack(">23f", f) # try big-endian first hdrlen = isSpiderHeader(t) if hdrlen == 0: - t = struct.unpack('<23f', f) # little-endian + t = struct.unpack("<23f", f) # little-endian hdrlen = isSpiderHeader(t) return hdrlen @@ -104,18 +105,18 @@ class SpiderImageFile(ImageFile.ImageFile): try: self.bigendian = 1 - t = struct.unpack('>27f', f) # try big-endian first + t = struct.unpack(">27f", f) # try big-endian first hdrlen = isSpiderHeader(t) if hdrlen == 0: self.bigendian = 0 - t = struct.unpack('<27f', f) # little-endian + t = struct.unpack("<27f", f) # little-endian hdrlen = isSpiderHeader(t) if hdrlen == 0: raise SyntaxError("not a valid Spider file") except struct.error: raise SyntaxError("not a valid Spider file") - h = (99,) + t # add 1 value : spider header index starts at 1 + h = (99,) + t # add 1 value : spider header index starts at 1 iform = int(h[5]) if iform != 1: raise SyntaxError("not a Spider 2D image") @@ -149,9 +150,7 @@ class SpiderImageFile(ImageFile.ImageFile): self.rawmode = "F;32F" self.mode = "F" - self.tile = [ - ("raw", (0, 0) + self.size, offset, - (self.rawmode, 0, 1))] + self.tile = [("raw", (0, 0) + self.size, offset, (self.rawmode, 0, 1))] self.__fp = self.fp # FIXME: hack @property @@ -184,13 +183,14 @@ class SpiderImageFile(ImageFile.ImageFile): (minimum, maximum) = self.getextrema() m = 1 if maximum != minimum: - m = depth / (maximum-minimum) + m = depth / (maximum - minimum) b = -m * minimum return self.point(lambda i, m=m, b=b: i * m + b).convert("L") # returns a ImageTk.PhotoImage object, after rescaling to 0..255 def tkPhotoImage(self): from PIL import ImageTk + return ImageTk.PhotoImage(self.convert2byte(), palette=256) def _close__fp(self): @@ -223,7 +223,7 @@ def loadImageSeries(filelist=None): if not isSpiderImage(img): print(img + " is not a Spider image file") continue - im.info['filename'] = img + im.info["filename"] = img imglist.append(im) return imglist @@ -231,6 +231,7 @@ def loadImageSeries(filelist=None): # -------------------------------------------------------------------- # For saving images in Spider format + def makeSpiderHeader(im): nsam, nrow = im.size lenbyt = nsam * 4 # There are labrec records in the header @@ -247,10 +248,10 @@ def makeSpiderHeader(im): return [] # NB these are Fortran indices - hdr[1] = 1.0 # nslice (=1 for an image) - hdr[2] = float(nrow) # number of rows per slice - hdr[5] = 1.0 # iform for 2D image - hdr[12] = float(nsam) # number of pixels per line + hdr[1] = 1.0 # nslice (=1 for an image) + hdr[2] = float(nrow) # number of rows per slice + hdr[5] = 1.0 # iform for 2D image + hdr[12] = float(nsam) # number of pixels per line hdr[13] = float(labrec) # number of records in file header hdr[22] = float(labbyt) # total number of bytes in header hdr[23] = float(lenbyt) # record length in bytes @@ -261,13 +262,13 @@ def makeSpiderHeader(im): # pack binary data into a string hdrstr = [] for v in hdr: - hdrstr.append(struct.pack('f', v)) + hdrstr.append(struct.pack("f", v)) return hdrstr def _save(im, fp, filename): if im.mode[0] != "F": - im = im.convert('F') + im = im.convert("F") hdr = makeSpiderHeader(im) if len(hdr) < 256: @@ -277,7 +278,7 @@ def _save(im, fp, filename): fp.writelines(hdr) rawmode = "F;32NF" # 32-bit native floating point - ImageFile._save(im, fp, [("raw", (0, 0)+im.size, 0, (rawmode, 0, 1))]) + ImageFile._save(im, fp, [("raw", (0, 0) + im.size, 0, (rawmode, 0, 1))]) def _save_spider(im, fp, filename): @@ -286,6 +287,7 @@ def _save_spider(im, fp, filename): Image.register_extension(SpiderImageFile.format, ext) _save(im, fp, filename) + # -------------------------------------------------------------------- @@ -308,7 +310,7 @@ if __name__ == "__main__": print("format: " + str(im.format)) print("size: " + str(im.size)) print("mode: " + str(im.mode)) - print("max, min: ", end=' ') + print("max, min: ", end=" ") print(im.getextrema()) if len(sys.argv) > 2: @@ -317,6 +319,7 @@ if __name__ == "__main__": # perform some image operation im = im.transpose(Image.FLIP_LEFT_RIGHT) print( - "saving a flipped version of %s as %s " % - (os.path.basename(filename), outfile)) + "saving a flipped version of %s as %s " + % (os.path.basename(filename), outfile) + ) im.save(outfile, SpiderImageFile.format) diff --git a/src/PIL/SunImagePlugin.py b/src/PIL/SunImagePlugin.py index 485099fd4..74fa5f7bd 100644 --- a/src/PIL/SunImagePlugin.py +++ b/src/PIL/SunImagePlugin.py @@ -26,12 +26,13 @@ __version__ = "0.3" def _accept(prefix): - return len(prefix) >= 4 and i32(prefix) == 0x59a66a95 + return len(prefix) >= 4 and i32(prefix) == 0x59A66A95 ## # Image plugin for Sun raster files. + class SunImageFile(ImageFile.ImageFile): format = "SUN" @@ -56,7 +57,7 @@ class SunImageFile(ImageFile.ImageFile): # HEAD s = self.fp.read(32) - if i32(s) != 0x59a66a95: + if i32(s) != 0x59A66A95: raise SyntaxError("not an SUN raster file") offset = 32 @@ -82,9 +83,9 @@ class SunImageFile(ImageFile.ImageFile): self.mode, rawmode = "RGB", "BGR" elif depth == 32: if file_type == 3: - self.mode, rawmode = 'RGB', 'RGBX' + self.mode, rawmode = "RGB", "RGBX" else: - self.mode, rawmode = 'RGB', 'BGRX' + self.mode, rawmode = "RGB", "BGRX" else: raise SyntaxError("Unsupported Mode/Bit Depth") @@ -96,11 +97,10 @@ class SunImageFile(ImageFile.ImageFile): raise SyntaxError("Unsupported Palette Type") offset = offset + palette_length - self.palette = ImagePalette.raw("RGB;L", - self.fp.read(palette_length)) + self.palette = ImagePalette.raw("RGB;L", self.fp.read(palette_length)) if self.mode == "L": self.mode = "P" - rawmode = rawmode.replace('L', 'P') + rawmode = rawmode.replace("L", "P") # 16 bit boundaries on stride stride = ((self.size[0] * depth + 15) // 16) * 2 @@ -124,11 +124,12 @@ class SunImageFile(ImageFile.ImageFile): # (https://www.fileformat.info/format/sunraster/egff.htm) if file_type in (0, 1, 3, 4, 5): - self.tile = [("raw", (0, 0)+self.size, offset, (rawmode, stride))] + self.tile = [("raw", (0, 0) + self.size, offset, (rawmode, stride))] elif file_type == 2: - self.tile = [("sun_rle", (0, 0)+self.size, offset, rawmode)] + self.tile = [("sun_rle", (0, 0) + self.size, offset, rawmode)] else: - raise SyntaxError('Unsupported Sun Raster file type') + raise SyntaxError("Unsupported Sun Raster file type") + # # registry diff --git a/src/PIL/TarIO.py b/src/PIL/TarIO.py index a421b12a5..b5227e484 100644 --- a/src/PIL/TarIO.py +++ b/src/PIL/TarIO.py @@ -23,8 +23,8 @@ from . import ContainerIO # A file object that provides read access to a given member of a TAR # file. -class TarIO(ContainerIO.ContainerIO): +class TarIO(ContainerIO.ContainerIO): def __init__(self, tarfile, file): """ Create file object. @@ -40,8 +40,8 @@ class TarIO(ContainerIO.ContainerIO): if len(s) != 512: raise IOError("unexpected end of tar file") - name = s[:100].decode('utf-8') - i = name.find('\0') + name = s[:100].decode("utf-8") + i = name.find("\0") if i == 0: raise IOError("cannot find subfile") if i > 0: @@ -65,6 +65,7 @@ class TarIO(ContainerIO.ContainerIO): self.close() if sys.version_info.major >= 3: + def __del__(self): self.close() diff --git a/src/PIL/TgaImagePlugin.py b/src/PIL/TgaImagePlugin.py index ae9697b29..270754101 100644 --- a/src/PIL/TgaImagePlugin.py +++ b/src/PIL/TgaImagePlugin.py @@ -34,9 +34,9 @@ __version__ = "0.3" MODES = { # map imagetype/depth to rawmode - (1, 8): "P", - (3, 1): "1", - (3, 8): "L", + (1, 8): "P", + (3, 1): "1", + (3, 8): "L", (3, 16): "LA", (2, 16): "BGR;5", (2, 24): "BGR", @@ -47,6 +47,7 @@ MODES = { ## # Image plugin for Targa files. + class TgaImageFile(ImageFile.ImageFile): format = "TGA" @@ -69,9 +70,12 @@ class TgaImageFile(ImageFile.ImageFile): self._size = i16(s[12:]), i16(s[14:]) # validate header fields - if colormaptype not in (0, 1) or\ - self.size[0] <= 0 or self.size[1] <= 0 or\ - depth not in (1, 8, 16, 24, 32): + if ( + colormaptype not in (0, 1) + or self.size[0] <= 0 + or self.size[1] <= 0 + or depth not in (1, 8, 16, 24, 32) + ): raise SyntaxError("not a TGA file") # image mode @@ -112,27 +116,43 @@ class TgaImageFile(ImageFile.ImageFile): start, size, mapdepth = i16(s[3:]), i16(s[5:]), i16(s[7:]) if mapdepth == 16: self.palette = ImagePalette.raw( - "BGR;16", b"\0"*2*start + self.fp.read(2*size)) + "BGR;16", b"\0" * 2 * start + self.fp.read(2 * size) + ) elif mapdepth == 24: self.palette = ImagePalette.raw( - "BGR", b"\0"*3*start + self.fp.read(3*size)) + "BGR", b"\0" * 3 * start + self.fp.read(3 * size) + ) elif mapdepth == 32: self.palette = ImagePalette.raw( - "BGRA", b"\0"*4*start + self.fp.read(4*size)) + "BGRA", b"\0" * 4 * start + self.fp.read(4 * size) + ) # setup tile descriptor try: rawmode = MODES[(imagetype & 7, depth)] if imagetype & 8: # compressed - self.tile = [("tga_rle", (0, 0)+self.size, - self.fp.tell(), (rawmode, orientation, depth))] + self.tile = [ + ( + "tga_rle", + (0, 0) + self.size, + self.fp.tell(), + (rawmode, orientation, depth), + ) + ] else: - self.tile = [("raw", (0, 0)+self.size, - self.fp.tell(), (rawmode, 0, orientation))] + self.tile = [ + ( + "raw", + (0, 0) + self.size, + self.fp.tell(), + (rawmode, 0, orientation), + ) + ] except KeyError: pass # cannot decode + # # -------------------------------------------------------------------- # Write TGA file @@ -158,14 +178,12 @@ def _save(im, fp, filename): if "rle" in im.encoderinfo: rle = im.encoderinfo["rle"] else: - compression = im.encoderinfo.get("compression", - im.info.get("compression")) + compression = im.encoderinfo.get("compression", im.info.get("compression")) rle = compression == "tga_rle" if rle: imagetype += 8 - id_section = im.encoderinfo.get("id_section", - im.info.get("id_section", "")) + id_section = im.encoderinfo.get("id_section", im.info.get("id_section", "")) id_len = len(id_section) if id_len > 255: id_len = 255 @@ -182,23 +200,24 @@ def _save(im, fp, filename): else: flags = 0 - orientation = im.encoderinfo.get("orientation", - im.info.get("orientation", -1)) + orientation = im.encoderinfo.get("orientation", im.info.get("orientation", -1)) if orientation > 0: flags = flags | 0x20 - fp.write(o8(id_len) + - o8(colormaptype) + - o8(imagetype) + - o16(colormapfirst) + - o16(colormaplength) + - o8(colormapentry) + - o16(0) + - o16(0) + - o16(im.size[0]) + - o16(im.size[1]) + - o8(bits) + - o8(flags)) + fp.write( + o8(id_len) + + o8(colormaptype) + + o8(imagetype) + + o16(colormapfirst) + + o16(colormaplength) + + o8(colormapentry) + + o16(0) + + o16(0) + + o16(im.size[0]) + + o16(im.size[1]) + + o8(bits) + + o8(flags) + ) if id_section: fp.write(id_section) @@ -208,16 +227,17 @@ def _save(im, fp, filename): if rle: ImageFile._save( - im, - fp, - [("tga_rle", (0, 0) + im.size, 0, (rawmode, orientation))]) + im, fp, [("tga_rle", (0, 0) + im.size, 0, (rawmode, orientation))] + ) else: ImageFile._save( - im, fp, [("raw", (0, 0) + im.size, 0, (rawmode, 0, orientation))]) + im, fp, [("raw", (0, 0) + im.size, 0, (rawmode, 0, orientation))] + ) # write targa version 2 footer fp.write(b"\000" * 8 + b"TRUEVISION-XFILE." + b"\000") + # # -------------------------------------------------------------------- # Registry diff --git a/src/PIL/TiffImagePlugin.py b/src/PIL/TiffImagePlugin.py index 44dde1995..835c460a5 100644 --- a/src/PIL/TiffImagePlugin.py +++ b/src/PIL/TiffImagePlugin.py @@ -153,7 +153,6 @@ OPEN_INFO = { (MM, 1, (1,), 1, (1,), ()): ("1", "1"), (II, 1, (1,), 2, (1,), ()): ("1", "1;R"), (MM, 1, (1,), 2, (1,), ()): ("1", "1;R"), - (II, 0, (1,), 1, (2,), ()): ("L", "L;2I"), (MM, 0, (1,), 1, (2,), ()): ("L", "L;2I"), (II, 0, (1,), 2, (2,), ()): ("L", "L;2IR"), @@ -162,7 +161,6 @@ OPEN_INFO = { (MM, 1, (1,), 1, (2,), ()): ("L", "L;2"), (II, 1, (1,), 2, (2,), ()): ("L", "L;2R"), (MM, 1, (1,), 2, (2,), ()): ("L", "L;2R"), - (II, 0, (1,), 1, (4,), ()): ("L", "L;4I"), (MM, 0, (1,), 1, (4,), ()): ("L", "L;4I"), (II, 0, (1,), 2, (4,), ()): ("L", "L;4IR"), @@ -171,7 +169,6 @@ OPEN_INFO = { (MM, 1, (1,), 1, (4,), ()): ("L", "L;4"), (II, 1, (1,), 2, (4,), ()): ("L", "L;4R"), (MM, 1, (1,), 2, (4,), ()): ("L", "L;4R"), - (II, 0, (1,), 1, (8,), ()): ("L", "L;I"), (MM, 0, (1,), 1, (8,), ()): ("L", "L;I"), (II, 0, (1,), 2, (8,), ()): ("L", "L;IR"), @@ -180,14 +177,11 @@ OPEN_INFO = { (MM, 1, (1,), 1, (8,), ()): ("L", "L"), (II, 1, (1,), 2, (8,), ()): ("L", "L;R"), (MM, 1, (1,), 2, (8,), ()): ("L", "L;R"), - (II, 1, (1,), 1, (12,), ()): ("I;16", "I;12"), - (II, 1, (1,), 1, (16,), ()): ("I;16", "I;16"), (MM, 1, (1,), 1, (16,), ()): ("I;16B", "I;16B"), (II, 1, (2,), 1, (16,), ()): ("I", "I;16S"), (MM, 1, (2,), 1, (16,), ()): ("I", "I;16BS"), - (II, 0, (3,), 1, (32,), ()): ("F", "F;32F"), (MM, 0, (3,), 1, (32,), ()): ("F", "F;32BF"), (II, 1, (1,), 1, (32,), ()): ("I", "I;32N"), @@ -195,10 +189,8 @@ OPEN_INFO = { (MM, 1, (2,), 1, (32,), ()): ("I", "I;32BS"), (II, 1, (3,), 1, (32,), ()): ("F", "F;32F"), (MM, 1, (3,), 1, (32,), ()): ("F", "F;32BF"), - (II, 1, (1,), 1, (8, 8), (2,)): ("LA", "LA"), (MM, 1, (1,), 1, (8, 8), (2,)): ("LA", "LA"), - (II, 2, (1,), 1, (8, 8, 8), ()): ("RGB", "RGB"), (MM, 2, (1,), 1, (8, 8, 8), ()): ("RGB", "RGB"), (II, 2, (1,), 2, (8, 8, 8), ()): ("RGB", "RGB;R"), @@ -225,7 +217,6 @@ OPEN_INFO = { (MM, 2, (1,), 1, (8, 8, 8, 8, 8, 8), (2, 0, 0)): ("RGBA", "RGBAXX"), (II, 2, (1,), 1, (8, 8, 8, 8), (999,)): ("RGBA", "RGBA"), # Corel Draw 10 (MM, 2, (1,), 1, (8, 8, 8, 8), (999,)): ("RGBA", "RGBA"), # Corel Draw 10 - (II, 2, (1,), 1, (16, 16, 16), ()): ("RGB", "RGB;16L"), (MM, 2, (1,), 1, (16, 16, 16), ()): ("RGB", "RGB;16B"), (II, 2, (1,), 1, (16, 16, 16, 16), ()): ("RGBA", "RGBA;16L"), @@ -236,7 +227,6 @@ OPEN_INFO = { (MM, 2, (1,), 1, (16, 16, 16, 16), (1,)): ("RGBA", "RGBa;16B"), (II, 2, (1,), 1, (16, 16, 16, 16), (2,)): ("RGBA", "RGBA;16L"), (MM, 2, (1,), 1, (16, 16, 16, 16), (2,)): ("RGBA", "RGBA;16B"), - (II, 3, (1,), 1, (1,), ()): ("P", "P;1"), (MM, 3, (1,), 1, (1,), ()): ("P", "P;1"), (II, 3, (1,), 2, (1,), ()): ("P", "P;1R"), @@ -255,19 +245,17 @@ OPEN_INFO = { (MM, 3, (1,), 1, (8, 8), (2,)): ("PA", "PA"), (II, 3, (1,), 2, (8,), ()): ("P", "P;R"), (MM, 3, (1,), 2, (8,), ()): ("P", "P;R"), - (II, 5, (1,), 1, (8, 8, 8, 8), ()): ("CMYK", "CMYK"), (MM, 5, (1,), 1, (8, 8, 8, 8), ()): ("CMYK", "CMYK"), (II, 5, (1,), 1, (8, 8, 8, 8, 8), (0,)): ("CMYK", "CMYKX"), (MM, 5, (1,), 1, (8, 8, 8, 8, 8), (0,)): ("CMYK", "CMYKX"), (II, 5, (1,), 1, (8, 8, 8, 8, 8, 8), (0, 0)): ("CMYK", "CMYKXX"), (MM, 5, (1,), 1, (8, 8, 8, 8, 8, 8), (0, 0)): ("CMYK", "CMYKXX"), - + (II, 5, (1,), 1, (16, 16, 16, 16), ()): ("CMYK", "CMYK;16L"), # JPEG compressed images handled by LibTiff and auto-converted to RGBX # Minimal Baseline TIFF requires YCbCr images to have 3 SamplesPerPixel (II, 6, (1,), 1, (8, 8, 8), ()): ("RGB", "RGBX"), (MM, 6, (1,), 1, (8, 8, 8), ()): ("RGB", "RGBX"), - (II, 8, (1,), 1, (8, 8, 8), ()): ("LAB", "LAB"), (MM, 8, (1,), 1, (8, 8, 8), ()): ("LAB", "LAB"), } @@ -313,7 +301,7 @@ class IFDRational(Rational): """ - __slots__ = ('_numerator', '_denominator', '_val') + __slots__ = ("_numerator", "_denominator", "_val") def __init__(self, value, denominator=1): """ @@ -337,7 +325,7 @@ class IFDRational(Rational): return if denominator == 0: - self._val = float('nan') + self._val = float("nan") return elif denominator == 1: @@ -378,6 +366,7 @@ class IFDRational(Rational): def _delegate(op): def delegate(self, *args): return getattr(self._val, op)(*args) + return delegate """ a = ['add','radd', 'sub', 'rsub','div', 'rdiv', 'mul', 'rmul', @@ -388,34 +377,34 @@ class IFDRational(Rational): print("\n".join("__%s__ = _delegate('__%s__')" % (s,s) for s in a)) """ - __add__ = _delegate('__add__') - __radd__ = _delegate('__radd__') - __sub__ = _delegate('__sub__') - __rsub__ = _delegate('__rsub__') - __div__ = _delegate('__div__') - __rdiv__ = _delegate('__rdiv__') - __mul__ = _delegate('__mul__') - __rmul__ = _delegate('__rmul__') - __truediv__ = _delegate('__truediv__') - __rtruediv__ = _delegate('__rtruediv__') - __floordiv__ = _delegate('__floordiv__') - __rfloordiv__ = _delegate('__rfloordiv__') - __mod__ = _delegate('__mod__') - __rmod__ = _delegate('__rmod__') - __pow__ = _delegate('__pow__') - __rpow__ = _delegate('__rpow__') - __pos__ = _delegate('__pos__') - __neg__ = _delegate('__neg__') - __abs__ = _delegate('__abs__') - __trunc__ = _delegate('__trunc__') - __lt__ = _delegate('__lt__') - __gt__ = _delegate('__gt__') - __le__ = _delegate('__le__') - __ge__ = _delegate('__ge__') - __nonzero__ = _delegate('__nonzero__') - __ceil__ = _delegate('__ceil__') - __floor__ = _delegate('__floor__') - __round__ = _delegate('__round__') + __add__ = _delegate("__add__") + __radd__ = _delegate("__radd__") + __sub__ = _delegate("__sub__") + __rsub__ = _delegate("__rsub__") + __div__ = _delegate("__div__") + __rdiv__ = _delegate("__rdiv__") + __mul__ = _delegate("__mul__") + __rmul__ = _delegate("__rmul__") + __truediv__ = _delegate("__truediv__") + __rtruediv__ = _delegate("__rtruediv__") + __floordiv__ = _delegate("__floordiv__") + __rfloordiv__ = _delegate("__rfloordiv__") + __mod__ = _delegate("__mod__") + __rmod__ = _delegate("__rmod__") + __pow__ = _delegate("__pow__") + __rpow__ = _delegate("__rpow__") + __pos__ = _delegate("__pos__") + __neg__ = _delegate("__neg__") + __abs__ = _delegate("__abs__") + __trunc__ = _delegate("__trunc__") + __lt__ = _delegate("__lt__") + __gt__ = _delegate("__gt__") + __le__ = _delegate("__le__") + __ge__ = _delegate("__ge__") + __nonzero__ = _delegate("__nonzero__") + __ceil__ = _delegate("__ceil__") + __floor__ = _delegate("__floor__") + __round__ = _delegate("__round__") class ImageFileDirectory_v2(MutableMapping): @@ -449,6 +438,7 @@ class ImageFileDirectory_v2(MutableMapping): .. versionadded:: 3.0.0 """ + """ Documentation: @@ -508,7 +498,7 @@ class ImageFileDirectory_v2(MutableMapping): self._tags_v1 = {} # will remain empty if legacy_api is false self._tags_v2 = {} # main tag storage self._tagdata = {} - self.tagtype = {} # added 2008-06-05 by Florian Hoech + self.tagtype = {} # added 2008-06-05 by Florian Hoech self._next = None self._offset = None @@ -521,8 +511,7 @@ class ImageFileDirectory_v2(MutableMapping): Returns the complete tag dictionary, with named tags where possible. """ - return dict((TiffTags.lookup(code).name, value) - for code, value in self.items()) + return {TiffTags.lookup(code).name: value for code, value in self.items()} def __len__(self): return len(set(self._tagdata) | set(self._tags_v2)) @@ -535,13 +524,14 @@ class ImageFileDirectory_v2(MutableMapping): self[tag] = handler(self, data, self.legacy_api) # check type val = self._tags_v2[tag] if self.legacy_api and not isinstance(val, (tuple, bytes)): - val = val, + val = (val,) return val def __contains__(self, tag): return tag in self._tags_v2 or tag in self._tagdata if not py3: + def has_key(self, tag): return tag in self @@ -551,7 +541,7 @@ class ImageFileDirectory_v2(MutableMapping): def _setitem(self, tag, value, legacy_api): basetypes = (Number, bytes, str) if not py3: - basetypes += unicode, # noqa: F821 + basetypes += (unicode,) # noqa: F821 info = TiffTags.lookup(tag) values = [value] if isinstance(value, basetypes) else value @@ -579,11 +569,11 @@ class ImageFileDirectory_v2(MutableMapping): self.tagtype[tag] = TiffTags.ASCII if self.tagtype[tag] == TiffTags.UNDEFINED and py3: - values = [value.encode("ascii", 'replace') if isinstance( - value, str) else value] + values = [ + value.encode("ascii", "replace") if isinstance(value, str) else value + ] elif self.tagtype[tag] == TiffTags.RATIONAL: - values = [float(v) if isinstance(v, int) else v - for v in values] + values = [float(v) if isinstance(v, int) else v for v in values] values = tuple(info.cvt_enum(value) for value in values) @@ -594,22 +584,23 @@ class ImageFileDirectory_v2(MutableMapping): # Spec'd length == 1, Actual > 1, Warn and truncate. Formerly barfed. # No Spec, Actual length 1, Formerly (<4.2) returned a 1 element tuple. # Don't mess with the legacy api, since it's frozen. - if (info.length == 1) or \ - (info.length is None and len(values) == 1 and not legacy_api): + if (info.length == 1) or ( + info.length is None and len(values) == 1 and not legacy_api + ): # Don't mess with the legacy api, since it's frozen. if legacy_api and self.tagtype[tag] in [ TiffTags.RATIONAL, - TiffTags.SIGNED_RATIONAL + TiffTags.SIGNED_RATIONAL, ]: # rationals - values = values, + values = (values,) try: dest[tag], = values except ValueError: # We've got a builtin tag with 1 expected entry warnings.warn( - "Metadata Warning, tag %s had too many entries: " - "%s, expected 1" % ( - tag, len(values))) + "Metadata Warning, tag %s had too many entries: %s, expected 1" + % (tag, len(values)) + ) dest[tag] = values[0] else: @@ -634,36 +625,51 @@ class ImageFileDirectory_v2(MutableMapping): def _register_loader(idx, size): def decorator(func): from .TiffTags import TYPES + if func.__name__.startswith("load_"): TYPES[idx] = func.__name__[5:].replace("_", " ") _load_dispatch[idx] = size, func # noqa: F821 return func + return decorator def _register_writer(idx): def decorator(func): _write_dispatch[idx] = func # noqa: F821 return func + return decorator def _register_basic(idx_fmt_name): from .TiffTags import TYPES + idx, fmt, name = idx_fmt_name TYPES[idx] = name size = struct.calcsize("=" + fmt) - _load_dispatch[idx] = size, lambda self, data, legacy_api=True: ( # noqa: F821 - self._unpack("{}{}".format(len(data) // size, fmt), data)) + _load_dispatch[idx] = ( # noqa: F821 + size, + lambda self, data, legacy_api=True: ( + self._unpack("{}{}".format(len(data) // size, fmt), data) + ), + ) _write_dispatch[idx] = lambda self, *values: ( # noqa: F821 - b"".join(self._pack(fmt, value) for value in values)) + b"".join(self._pack(fmt, value) for value in values) + ) - list(map(_register_basic, - [(TiffTags.SHORT, "H", "short"), - (TiffTags.LONG, "L", "long"), - (TiffTags.SIGNED_BYTE, "b", "signed byte"), - (TiffTags.SIGNED_SHORT, "h", "signed short"), - (TiffTags.SIGNED_LONG, "l", "signed long"), - (TiffTags.FLOAT, "f", "float"), - (TiffTags.DOUBLE, "d", "double")])) + list( + map( + _register_basic, + [ + (TiffTags.SHORT, "H", "short"), + (TiffTags.LONG, "L", "long"), + (TiffTags.SIGNED_BYTE, "b", "signed byte"), + (TiffTags.SIGNED_SHORT, "h", "signed short"), + (TiffTags.SIGNED_LONG, "l", "signed long"), + (TiffTags.FLOAT, "f", "float"), + (TiffTags.DOUBLE, "d", "double"), + ], + ) + ) @_register_loader(1, 1) # Basic type, except for the legacy API. def load_byte(self, data, legacy_api=True): @@ -683,21 +689,23 @@ class ImageFileDirectory_v2(MutableMapping): def write_string(self, value): # remerge of https://github.com/python-pillow/Pillow/pull/1416 if sys.version_info.major == 2: - value = value.decode('ascii', 'replace') - return b"" + value.encode('ascii', 'replace') + b"\0" + value = value.decode("ascii", "replace") + return b"" + value.encode("ascii", "replace") + b"\0" @_register_loader(5, 8) def load_rational(self, data, legacy_api=True): vals = self._unpack("{}L".format(len(data) // 4), data) - def combine(a, b): return (a, b) if legacy_api else IFDRational(a, b) - return tuple(combine(num, denom) - for num, denom in zip(vals[::2], vals[1::2])) + def combine(a, b): + return (a, b) if legacy_api else IFDRational(a, b) + + return tuple(combine(num, denom) for num, denom in zip(vals[::2], vals[1::2])) @_register_writer(5) def write_rational(self, *values): - return b"".join(self._pack("2L", *_limit_rational(frac, 2 ** 31)) - for frac in values) + return b"".join( + self._pack("2L", *_limit_rational(frac, 2 ** 31)) for frac in values + ) @_register_loader(7, 1) def load_undefined(self, data, legacy_api=True): @@ -711,21 +719,24 @@ class ImageFileDirectory_v2(MutableMapping): def load_signed_rational(self, data, legacy_api=True): vals = self._unpack("{}l".format(len(data) // 4), data) - def combine(a, b): return (a, b) if legacy_api else IFDRational(a, b) - return tuple(combine(num, denom) - for num, denom in zip(vals[::2], vals[1::2])) + def combine(a, b): + return (a, b) if legacy_api else IFDRational(a, b) + + return tuple(combine(num, denom) for num, denom in zip(vals[::2], vals[1::2])) @_register_writer(10) def write_signed_rational(self, *values): - return b"".join(self._pack("2L", *_limit_rational(frac, 2 ** 30)) - for frac in values) + return b"".join( + self._pack("2L", *_limit_rational(frac, 2 ** 30)) for frac in values + ) def _ensure_read(self, fp, size): ret = fp.read(size) if len(ret) != size: - raise IOError("Corrupt EXIF data. " + - "Expecting to read %d bytes but only got %d. " % - (size, len(ret))) + raise IOError( + "Corrupt EXIF data. " + + "Expecting to read %d bytes but only got %d. " % (size, len(ret)) + ) return ret def load(self, fp): @@ -735,13 +746,14 @@ class ImageFileDirectory_v2(MutableMapping): try: for i in range(self._unpack("H", self._ensure_read(fp, 2))[0]): - tag, typ, count, data = self._unpack("HHL4s", - self._ensure_read(fp, 12)) + tag, typ, count, data = self._unpack("HHL4s", self._ensure_read(fp, 12)) if DEBUG: tagname = TiffTags.lookup(tag).name typname = TYPES.get(typ, "unknown") - print("tag: %s (%d) - type: %s (%d)" % - (tagname, tag, typname, typ), end=" ") + print( + "tag: %s (%d) - type: %s (%d)" % (tagname, tag, typname, typ), + end=" ", + ) try: unit_size, handler = self._load_dispatch[typ] @@ -754,8 +766,10 @@ class ImageFileDirectory_v2(MutableMapping): here = fp.tell() offset, = self._unpack("L", data) if DEBUG: - print("Tag Location: %s - Data Location: %s" % - (here, offset), end=" ") + print( + "Tag Location: %s - Data Location: %s" % (here, offset), + end=" ", + ) fp.seek(offset) data = ImageFile._safe_read(fp, size) fp.seek(here) @@ -763,9 +777,11 @@ class ImageFileDirectory_v2(MutableMapping): data = data[:size] if len(data) != size: - warnings.warn("Possibly corrupt EXIF data. " - "Expecting to read %d bytes but only got %d." - " Skipping tag %s" % (size, len(data), tag)) + warnings.warn( + "Possibly corrupt EXIF data. " + "Expecting to read %d bytes but only got %d." + " Skipping tag %s" % (size, len(data), tag) + ) continue if not data: @@ -806,8 +822,10 @@ class ImageFileDirectory_v2(MutableMapping): if DEBUG: tagname = TiffTags.lookup(tag).name typname = TYPES.get(typ, "unknown") - print("save: %s (%d) - type: %s (%d)" % - (tagname, tag, typname, typ), end=" ") + print( + "save: %s (%d) - type: %s (%d)" % (tagname, tag, typname, typ), + end=" ", + ) if len(data) >= 16: print("- value: " % len(data)) else: @@ -822,16 +840,14 @@ class ImageFileDirectory_v2(MutableMapping): if len(data) <= 4: entries.append((tag, typ, count, data.ljust(4, b"\0"), b"")) else: - entries.append((tag, typ, count, self._pack("L", offset), - data)) + entries.append((tag, typ, count, self._pack("L", offset), data)) offset += (len(data) + 1) // 2 * 2 # pad to word # update strip offset data to point beyond auxiliary data if stripoffsets is not None: tag, typ, count, value, data = entries[stripoffsets] if data: - raise NotImplementedError( - "multistrip support not yet implemented") + raise NotImplementedError("multistrip support not yet implemented") value = self._pack("L", self._unpack("L", value)[0] + offset) entries[stripoffsets] = tag, typ, count, value, data @@ -892,6 +908,7 @@ class ImageFileDirectory_v1(ImageFileDirectory_v2): .. deprecated:: 3.0.0 """ + def __init__(self, *args, **kwargs): ImageFileDirectory_v2.__init__(self, *args, **kwargs) self._legacy_api = True @@ -956,7 +973,7 @@ class ImageFileDirectory_v1(ImageFileDirectory_v2): self._setitem(tag, handler(self, data, legacy), legacy) val = self._tags_v1[tag] if not isinstance(val, (tuple, bytes)): - val = val, + val = (val,) return val @@ -967,6 +984,7 @@ ImageFileDirectory = ImageFileDirectory_v1 ## # Image plugin for TIFF files. + class TiffImageFile(ImageFile.ImageFile): format = "TIFF" @@ -1031,9 +1049,10 @@ class TiffImageFile(ImageFile.ImageFile): if not self.__next: raise EOFError("no more images in TIFF file") if DEBUG: - print("Seeking to frame %s, on frame %s, " - "__next %s, location: %s" % - (frame, self.__frame, self.__next, self.fp.tell())) + print( + "Seeking to frame %s, on frame %s, __next %s, location: %s" + % (frame, self.__frame, self.__next, self.fp.tell()) + ) # reset python3 buffered io handle in case fp # was passed to libtiff, invalidating the buffer self.fp.tell() @@ -1066,9 +1085,9 @@ class TiffImageFile(ImageFile.ImageFile): @size.setter def size(self, value): warnings.warn( - 'Setting the size of a TIFF image directly is deprecated, and will' - ' be removed in a future version. Use the resize method instead.', - DeprecationWarning + "Setting the size of a TIFF image directly is deprecated, and will" + " be removed in a future version. Use the resize method instead.", + DeprecationWarning, ) self._size = value @@ -1102,7 +1121,7 @@ class TiffImageFile(ImageFile.ImageFile): # (self._compression, (extents tuple), # 0, (rawmode, self._compression, fp)) extents = self.tile[0][1] - args = list(self.tile[0][3]) + [self.tag_v2.offset] + args = list(self.tile[0][3]) # To be nice on memory footprint, if there's a # file descriptor, use that instead of reading @@ -1123,8 +1142,9 @@ class TiffImageFile(ImageFile.ImageFile): if fp: args[2] = fp - decoder = Image._getdecoder(self.mode, 'libtiff', tuple(args), - self.decoderconfig) + decoder = Image._getdecoder( + self.mode, "libtiff", tuple(args), self.decoderconfig + ) try: decoder.setimage(self.im, extents) except ValueError: @@ -1206,8 +1226,7 @@ class TiffImageFile(ImageFile.ImageFile): print("- size:", self.size) sampleFormat = self.tag_v2.get(SAMPLEFORMAT, (1,)) - if (len(sampleFormat) > 1 - and max(sampleFormat) == min(sampleFormat) == 1): + if len(sampleFormat) > 1 and max(sampleFormat) == min(sampleFormat) == 1: # SAMPLEFORMAT is properly per band, so an RGB image will # be (1,1,1). But, we don't support per band pixel types, # and anything more than one band is a uint8. So, just @@ -1230,8 +1249,14 @@ class TiffImageFile(ImageFile.ImageFile): bps_tuple = bps_tuple * bps_count # mode: check photometric interpretation and bits per pixel - key = (self.tag_v2.prefix, photo, sampleFormat, fillorder, - bps_tuple, extra_tuple) + key = ( + self.tag_v2.prefix, + photo, + sampleFormat, + fillorder, + bps_tuple, + extra_tuple, + ) if DEBUG: print("format key:", key) try: @@ -1267,7 +1292,7 @@ class TiffImageFile(ImageFile.ImageFile): # build tile descriptors x = y = layer = 0 self.tile = [] - self.use_load_libtiff = READ_LIBTIFF or self._compression != 'raw' + self.use_load_libtiff = READ_LIBTIFF or self._compression != "raw" if self.use_load_libtiff: # Decoder expects entire file as one tile. # There's a buffer size limit in load (64k) @@ -1294,20 +1319,17 @@ class TiffImageFile(ImageFile.ImageFile): # we're expecting image byte order. So, if the rawmode # contains I;16, we need to convert from native to image # byte order. - if rawmode == 'I;16': - rawmode = 'I;16N' - if ';16B' in rawmode: - rawmode = rawmode.replace(';16B', ';16N') - if ';16L' in rawmode: - rawmode = rawmode.replace(';16L', ';16N') + if rawmode == "I;16": + rawmode = "I;16N" + if ";16B" in rawmode: + rawmode = rawmode.replace(";16B", ";16N") + if ";16L" in rawmode: + rawmode = rawmode.replace(";16L", ";16N") # Offset in the tile tuple is 0, we go from 0,0 to # w,h, and we only do this once -- eds - a = (rawmode, self._compression, False) - self.tile.append( - (self._compression, - (0, 0, xsize, ysize), - 0, a)) + a = (rawmode, self._compression, False, self.tag_v2.offset) + self.tile.append(("libtiff", (0, 0, xsize, ysize), 0, a)) elif STRIPOFFSETS in self.tag_v2 or TILEOFFSETS in self.tag_v2: # striped image @@ -1336,9 +1358,13 @@ class TiffImageFile(ImageFile.ImageFile): a = (tile_rawmode, int(stride), 1) self.tile.append( - (self._compression, - (x, y, min(x+w, xsize), min(y+h, ysize)), - offset, a)) + ( + self._compression, + (x, y, min(x + w, xsize), min(y + h, ysize)), + offset, + a, + ) + ) x = x + w if x >= self.size[0]: x, y = 0, y + h @@ -1352,11 +1378,11 @@ class TiffImageFile(ImageFile.ImageFile): # Fix up info. if ICCPROFILE in self.tag_v2: - self.info['icc_profile'] = self.tag_v2[ICCPROFILE] + self.info["icc_profile"] = self.tag_v2[ICCPROFILE] # fixup palette descriptor - if self.mode == "P": + if self.mode in ["P", "PA"]: palette = [o8(b // 256) for b in self.tag_v2[COLORMAP]] self.palette = ImagePalette.raw("RGB;L", b"".join(palette)) @@ -1395,7 +1421,6 @@ SAVE_INFO = { "CMYK": ("CMYK", II, 5, 1, (8, 8, 8, 8), None), "YCbCr": ("YCbCr", II, 6, 1, (8, 8, 8), None), "LAB": ("LAB", II, 8, 1, (8, 8, 8), None), - "I;32BS": ("I;32BS", MM, 1, 2, (32,), None), "I;16B": ("I;16B", MM, 1, 1, (16,), None), "I;16BS": ("I;16BS", MM, 1, 2, (16,), None), @@ -1412,14 +1437,14 @@ def _save(im, fp, filename): ifd = ImageFileDirectory_v2(prefix=prefix) - compression = im.encoderinfo.get('compression', im.info.get('compression')) + compression = im.encoderinfo.get("compression", im.info.get("compression")) if compression is None: - compression = 'raw' + compression = "raw" - libtiff = WRITE_LIBTIFF or compression != 'raw' + libtiff = WRITE_LIBTIFF or compression != "raw" # required for color libtiff images - ifd[PLANAR_CONFIGURATION] = getattr(im, '_planar_configuration', 1) + ifd[PLANAR_CONFIGURATION] = getattr(im, "_planar_configuration", 1) ifd[IMAGEWIDTH] = im.size[0] ifd[IMAGELENGTH] = im.size[1] @@ -1439,10 +1464,16 @@ def _save(im, fp, filename): # additions written by Greg Couch, gregc@cgl.ucsf.edu # inspired by image-sig posting from Kevin Cazabon, kcazabon@home.com - if hasattr(im, 'tag_v2'): + if hasattr(im, "tag_v2"): # preserve tags from original TIFF image file - for key in (RESOLUTION_UNIT, X_RESOLUTION, Y_RESOLUTION, - IPTC_NAA_CHUNK, PHOTOSHOP_CHUNK, XMP): + for key in ( + RESOLUTION_UNIT, + X_RESOLUTION, + Y_RESOLUTION, + IPTC_NAA_CHUNK, + PHOTOSHOP_CHUNK, + XMP, + ): if key in im.tag_v2: ifd[key] = im.tag_v2[key] ifd.tagtype[key] = im.tag_v2.tagtype[key] @@ -1452,16 +1483,18 @@ def _save(im, fp, filename): if "icc_profile" in im.info: ifd[ICCPROFILE] = im.info["icc_profile"] - for key, name in [(IMAGEDESCRIPTION, "description"), - (X_RESOLUTION, "resolution"), - (Y_RESOLUTION, "resolution"), - (X_RESOLUTION, "x_resolution"), - (Y_RESOLUTION, "y_resolution"), - (RESOLUTION_UNIT, "resolution_unit"), - (SOFTWARE, "software"), - (DATE_TIME, "date_time"), - (ARTIST, "artist"), - (COPYRIGHT, "copyright")]: + for key, name in [ + (IMAGEDESCRIPTION, "description"), + (X_RESOLUTION, "resolution"), + (Y_RESOLUTION, "resolution"), + (X_RESOLUTION, "x_resolution"), + (Y_RESOLUTION, "y_resolution"), + (RESOLUTION_UNIT, "resolution_unit"), + (SOFTWARE, "software"), + (DATE_TIME, "date_time"), + (ARTIST, "artist"), + (COPYRIGHT, "copyright"), + ]: if name in im.encoderinfo: ifd[key] = im.encoderinfo[name] @@ -1482,11 +1515,11 @@ def _save(im, fp, filename): ifd[PHOTOMETRIC_INTERPRETATION] = photo - if im.mode == "P": + if im.mode in ["P", "PA"]: lut = im.im.getpalette("RGB", "RGB;L") ifd[COLORMAP] = tuple(i8(v) * 256 for v in lut) # data orientation - stride = len(bits) * ((im.size[0]*bits[0]+7)//8) + stride = len(bits) * ((im.size[0] * bits[0] + 7) // 8) ifd[ROWSPERSTRIP] = im.size[1] ifd[STRIPBYTECOUNTS] = stride * im.size[1] ifd[STRIPOFFSETS] = 0 # this is adjusted by IFD writer @@ -1515,11 +1548,11 @@ def _save(im, fp, filename): # the original file, e.g x,y resolution so that we can # save(load('')) == original file. legacy_ifd = {} - if hasattr(im, 'tag'): + if hasattr(im, "tag"): legacy_ifd = im.tag.to_v2() - for tag, value in itertools.chain(ifd.items(), - getattr(im, 'tag_v2', {}).items(), - legacy_ifd.items()): + for tag, value in itertools.chain( + ifd.items(), getattr(im, "tag_v2", {}).items(), legacy_ifd.items() + ): # Libtiff can only process certain core items without adding # them to the custom dictionary. # Support for custom items has only been been added @@ -1527,14 +1560,17 @@ def _save(im, fp, filename): if tag not in TiffTags.LIBTIFF_CORE: if TiffTags.lookup(tag).type == TiffTags.UNDEFINED: continue - if (distutils.version.StrictVersion(_libtiff_version()) < - distutils.version.StrictVersion("4.0")) \ - or not (isinstance(value, (int, float, str, bytes)) or - (not py3 and isinstance(value, unicode))): # noqa: F821 + if ( + distutils.version.StrictVersion(_libtiff_version()) + < distutils.version.StrictVersion("4.0") + ) or not ( + isinstance(value, (int, float, str, bytes)) + or (not py3 and isinstance(value, unicode)) # noqa: F821 + ): continue if tag not in atts and tag not in blocklist: if isinstance(value, str if py3 else unicode): # noqa: F821 - atts[tag] = value.encode('ascii', 'replace') + b"\0" + atts[tag] = value.encode("ascii", "replace") + b"\0" elif isinstance(value, IFDRational): atts[tag] = float(value) else: @@ -1547,15 +1583,15 @@ def _save(im, fp, filename): # we're storing image byte order. So, if the rawmode # contains I;16, we need to convert from native to image # byte order. - if im.mode in ('I;16B', 'I;16'): - rawmode = 'I;16N' + if im.mode in ("I;16B", "I;16"): + rawmode = "I;16N" a = (rawmode, compression, _fp, filename, atts) - e = Image._getencoder(im.mode, 'libtiff', a, im.encoderconfig) - e.setimage(im.im, (0, 0)+im.size) + e = Image._getencoder(im.mode, "libtiff", a, im.encoderconfig) + e.setimage(im.im, (0, 0) + im.size) while True: # undone, change to self.decodermaxblock: - l, s, d = e.encode(16*1024) + l, s, d = e.encode(16 * 1024) if not _fp: fp.write(d) if s: @@ -1566,9 +1602,9 @@ def _save(im, fp, filename): else: offset = ifd.save(fp) - ImageFile._save(im, fp, [ - ("raw", (0, 0)+im.size, offset, (rawmode, stride, 1)) - ]) + ImageFile._save( + im, fp, [("raw", (0, 0) + im.size, offset, (rawmode, stride, 1))] + ) # -- helper for multi-page save -- if "_debug_multipage" in im.encoderinfo: @@ -1602,7 +1638,7 @@ class AppendingTiffWriter: Tags = {273, 288, 324, 519, 520, 521} def __init__(self, fn, new=False): - if hasattr(fn, 'read'): + if hasattr(fn, "read"): self.f = fn self.close_fp = False else: @@ -1653,8 +1689,7 @@ class AppendingTiffWriter: return if IIMM != self.IIMM: - raise RuntimeError("IIMM of new page doesn't match IIMM of " - "first page") + raise RuntimeError("IIMM of new page doesn't match IIMM of first page") IFDoffset = self.readLong() IFDoffset += self.offsetOfNewPage @@ -1728,34 +1763,29 @@ class AppendingTiffWriter: self.f.seek(-2, os.SEEK_CUR) bytesWritten = self.f.write(struct.pack(self.longFmt, value)) if bytesWritten is not None and bytesWritten != 4: - raise RuntimeError("wrote only %u bytes but wanted 4" % - bytesWritten) + raise RuntimeError("wrote only %u bytes but wanted 4" % bytesWritten) def rewriteLastShort(self, value): self.f.seek(-2, os.SEEK_CUR) bytesWritten = self.f.write(struct.pack(self.shortFmt, value)) if bytesWritten is not None and bytesWritten != 2: - raise RuntimeError("wrote only %u bytes but wanted 2" % - bytesWritten) + raise RuntimeError("wrote only %u bytes but wanted 2" % bytesWritten) def rewriteLastLong(self, value): self.f.seek(-4, os.SEEK_CUR) bytesWritten = self.f.write(struct.pack(self.longFmt, value)) if bytesWritten is not None and bytesWritten != 4: - raise RuntimeError("wrote only %u bytes but wanted 4" % - bytesWritten) + raise RuntimeError("wrote only %u bytes but wanted 4" % bytesWritten) def writeShort(self, value): bytesWritten = self.f.write(struct.pack(self.shortFmt, value)) if bytesWritten is not None and bytesWritten != 2: - raise RuntimeError("wrote only %u bytes but wanted 2" % - bytesWritten) + raise RuntimeError("wrote only %u bytes but wanted 2" % bytesWritten) def writeLong(self, value): bytesWritten = self.f.write(struct.pack(self.longFmt, value)) if bytesWritten is not None and bytesWritten != 4: - raise RuntimeError("wrote only %u bytes but wanted 4" % - bytesWritten) + raise RuntimeError("wrote only %u bytes but wanted 4" % bytesWritten) def close(self): self.finalize() @@ -1765,12 +1795,11 @@ class AppendingTiffWriter: numTags = self.readShort() for i in range(numTags): - tag, fieldType, count = struct.unpack(self.tagFormat, - self.f.read(8)) + tag, fieldType, count = struct.unpack(self.tagFormat, self.f.read(8)) fieldSize = self.fieldSizes[fieldType] totalSize = fieldSize * count - isLocal = (totalSize <= 4) + isLocal = totalSize <= 4 if not isLocal: offset = self.readLong() offset += self.offsetOfNewPage @@ -1780,13 +1809,15 @@ class AppendingTiffWriter: curPos = self.f.tell() if isLocal: - self.fixOffsets(count, isShort=(fieldSize == 2), - isLong=(fieldSize == 4)) + self.fixOffsets( + count, isShort=(fieldSize == 2), isLong=(fieldSize == 4) + ) self.f.seek(curPos + 4) else: self.f.seek(offset) - self.fixOffsets(count, isShort=(fieldSize == 2), - isLong=(fieldSize == 4)) + self.fixOffsets( + count, isShort=(fieldSize == 2), isLong=(fieldSize == 4) + ) self.f.seek(curPos) offset = curPos = None @@ -1829,7 +1860,7 @@ def _save_all(im, fp, filename): cur_idx = im.tell() try: with AppendingTiffWriter(fp) as tf: - for ims in [im]+append_images: + for ims in [im] + append_images: ims.encoderinfo = encoderinfo ims.encoderconfig = encoderconfig if not hasattr(ims, "n_frames"): diff --git a/src/PIL/TiffTags.py b/src/PIL/TiffTags.py index 3e0291512..d0c98aa5a 100644 --- a/src/PIL/TiffTags.py +++ b/src/PIL/TiffTags.py @@ -23,10 +23,8 @@ from collections import namedtuple class TagInfo(namedtuple("_TagInfo", "value name type length enum")): __slots__ = [] - def __new__(cls, value=None, name="unknown", - type=None, length=None, enum=None): - return super(TagInfo, cls).__new__( - cls, value, name, type, length, enum or {}) + def __new__(cls, value=None, name="unknown", type=None, length=None, enum=None): + return super(TagInfo, cls).__new__(cls, value, name, type, length, enum or {}) def cvt_enum(self, value): # Using get will call hash(value), which can be expensive @@ -44,7 +42,7 @@ def lookup(tag): """ - return TAGS_V2.get(tag, TagInfo(tag, TAGS.get(tag, 'unknown'))) + return TAGS_V2.get(tag, TagInfo(tag, TAGS.get(tag, "unknown"))) ## @@ -73,27 +71,47 @@ FLOAT = 11 DOUBLE = 12 TAGS_V2 = { - 254: ("NewSubfileType", LONG, 1), 255: ("SubfileType", SHORT, 1), 256: ("ImageWidth", LONG, 1), 257: ("ImageLength", LONG, 1), 258: ("BitsPerSample", SHORT, 0), - 259: ("Compression", SHORT, 1, - {"Uncompressed": 1, "CCITT 1d": 2, "Group 3 Fax": 3, - "Group 4 Fax": 4, "LZW": 5, "JPEG": 6, "PackBits": 32773}), - - 262: ("PhotometricInterpretation", SHORT, 1, - {"WhiteIsZero": 0, "BlackIsZero": 1, "RGB": 2, "RGB Palette": 3, - "Transparency Mask": 4, "CMYK": 5, "YCbCr": 6, "CieLAB": 8, - "CFA": 32803, # TIFF/EP, Adobe DNG - "LinearRaw": 32892}), # Adobe DNG + 259: ( + "Compression", + SHORT, + 1, + { + "Uncompressed": 1, + "CCITT 1d": 2, + "Group 3 Fax": 3, + "Group 4 Fax": 4, + "LZW": 5, + "JPEG": 6, + "PackBits": 32773, + }, + ), + 262: ( + "PhotometricInterpretation", + SHORT, + 1, + { + "WhiteIsZero": 0, + "BlackIsZero": 1, + "RGB": 2, + "RGB Palette": 3, + "Transparency Mask": 4, + "CMYK": 5, + "YCbCr": 6, + "CieLAB": 8, + "CFA": 32803, # TIFF/EP, Adobe DNG + "LinearRaw": 32892, # Adobe DNG + }, + ), 263: ("Threshholding", SHORT, 1), 264: ("CellWidth", SHORT, 1), 265: ("CellLength", SHORT, 1), 266: ("FillOrder", SHORT, 1), 269: ("DocumentName", ASCII, 1), - 270: ("ImageDescription", ASCII, 1), 271: ("Make", ASCII, 1), 272: ("Model", ASCII, 1), @@ -102,7 +120,6 @@ TAGS_V2 = { 277: ("SamplesPerPixel", SHORT, 1), 278: ("RowsPerStrip", LONG, 1), 279: ("StripByteCounts", LONG, 0), - 280: ("MinSampleValue", LONG, 0), 281: ("MaxSampleValue", SHORT, 0), 282: ("XResolution", RATIONAL, 1), @@ -113,31 +130,26 @@ TAGS_V2 = { 287: ("YPosition", RATIONAL, 1), 288: ("FreeOffsets", LONG, 1), 289: ("FreeByteCounts", LONG, 1), - 290: ("GrayResponseUnit", SHORT, 1), 291: ("GrayResponseCurve", SHORT, 0), 292: ("T4Options", LONG, 1), 293: ("T6Options", LONG, 1), 296: ("ResolutionUnit", SHORT, 1, {"none": 1, "inch": 2, "cm": 3}), 297: ("PageNumber", SHORT, 2), - 301: ("TransferFunction", SHORT, 0), 305: ("Software", ASCII, 1), 306: ("DateTime", ASCII, 1), - 315: ("Artist", ASCII, 1), 316: ("HostComputer", ASCII, 1), 317: ("Predictor", SHORT, 1, {"none": 1, "Horizontal Differencing": 2}), 318: ("WhitePoint", RATIONAL, 2), 319: ("PrimaryChromaticities", RATIONAL, 6), - 320: ("ColorMap", SHORT, 0), 321: ("HalftoneHints", SHORT, 2), 322: ("TileWidth", LONG, 1), 323: ("TileLength", LONG, 1), 324: ("TileOffsets", LONG, 0), 325: ("TileByteCounts", LONG, 0), - 332: ("InkSet", SHORT, 1), 333: ("InkNames", ASCII, 1), 334: ("NumberOfInks", SHORT, 1), @@ -145,13 +157,10 @@ TAGS_V2 = { 337: ("TargetPrinter", ASCII, 1), 338: ("ExtraSamples", SHORT, 0), 339: ("SampleFormat", SHORT, 0), - 340: ("SMinSampleValue", DOUBLE, 0), 341: ("SMaxSampleValue", DOUBLE, 0), 342: ("TransferRange", SHORT, 6), - 347: ("JPEGTables", UNDEFINED, 1), - # obsolete JPEG tags 512: ("JPEGProc", SHORT, 1), 513: ("JPEGInterchangeFormat", LONG, 1), @@ -162,22 +171,17 @@ TAGS_V2 = { 519: ("JPEGQTables", LONG, 0), 520: ("JPEGDCTables", LONG, 0), 521: ("JPEGACTables", LONG, 0), - 529: ("YCbCrCoefficients", RATIONAL, 3), 530: ("YCbCrSubSampling", SHORT, 2), 531: ("YCbCrPositioning", SHORT, 1), 532: ("ReferenceBlackWhite", RATIONAL, 6), - - 700: ('XMP', BYTE, 1), - + 700: ("XMP", BYTE, 1), 33432: ("Copyright", ASCII, 1), - 34377: ('PhotoshopInfo', BYTE, 1), - + 34377: ("PhotoshopInfo", BYTE, 1), # FIXME add more tags here 34665: ("ExifIFD", SHORT, 1), - 34675: ('ICCProfile', UNDEFINED, 1), - 34853: ('GPSInfoIFD', BYTE, 1), - + 34675: ("ICCProfile", UNDEFINED, 1), + 34853: ("GPSInfoIFD", BYTE, 1), # MPInfo 45056: ("MPFVersion", UNDEFINED, 1), 45057: ("NumberOfImages", LONG, 1), @@ -198,159 +202,157 @@ TAGS_V2 = { 45579: ("YawAngle", SIGNED_RATIONAL, 1), 45580: ("PitchAngle", SIGNED_RATIONAL, 1), 45581: ("RollAngle", SIGNED_RATIONAL, 1), - 50741: ("MakerNoteSafety", SHORT, 1, {"Unsafe": 0, "Safe": 1}), 50780: ("BestQualityScale", RATIONAL, 1), 50838: ("ImageJMetaDataByteCounts", LONG, 0), # Can be more than one - 50839: ("ImageJMetaData", UNDEFINED, 1) # see Issue #2006 + 50839: ("ImageJMetaData", UNDEFINED, 1), # see Issue #2006 } # Legacy Tags structure # these tags aren't included above, but were in the previous versions -TAGS = {347: 'JPEGTables', - 700: 'XMP', - - # Additional Exif Info - 32932: 'Wang Annotation', - 33434: 'ExposureTime', - 33437: 'FNumber', - 33445: 'MD FileTag', - 33446: 'MD ScalePixel', - 33447: 'MD ColorTable', - 33448: 'MD LabName', - 33449: 'MD SampleInfo', - 33450: 'MD PrepDate', - 33451: 'MD PrepTime', - 33452: 'MD FileUnits', - 33550: 'ModelPixelScaleTag', - 33723: 'IptcNaaInfo', - 33918: 'INGR Packet Data Tag', - 33919: 'INGR Flag Registers', - 33920: 'IrasB Transformation Matrix', - 33922: 'ModelTiepointTag', - 34264: 'ModelTransformationTag', - 34377: 'PhotoshopInfo', - 34735: 'GeoKeyDirectoryTag', - 34736: 'GeoDoubleParamsTag', - 34737: 'GeoAsciiParamsTag', - 34850: 'ExposureProgram', - 34852: 'SpectralSensitivity', - 34855: 'ISOSpeedRatings', - 34856: 'OECF', - 34864: 'SensitivityType', - 34865: 'StandardOutputSensitivity', - 34866: 'RecommendedExposureIndex', - 34867: 'ISOSpeed', - 34868: 'ISOSpeedLatitudeyyy', - 34869: 'ISOSpeedLatitudezzz', - 34908: 'HylaFAX FaxRecvParams', - 34909: 'HylaFAX FaxSubAddress', - 34910: 'HylaFAX FaxRecvTime', - 36864: 'ExifVersion', - 36867: 'DateTimeOriginal', - 36868: 'DateTImeDigitized', - 37121: 'ComponentsConfiguration', - 37122: 'CompressedBitsPerPixel', - 37724: 'ImageSourceData', - 37377: 'ShutterSpeedValue', - 37378: 'ApertureValue', - 37379: 'BrightnessValue', - 37380: 'ExposureBiasValue', - 37381: 'MaxApertureValue', - 37382: 'SubjectDistance', - 37383: 'MeteringMode', - 37384: 'LightSource', - 37385: 'Flash', - 37386: 'FocalLength', - 37396: 'SubjectArea', - 37500: 'MakerNote', - 37510: 'UserComment', - 37520: 'SubSec', - 37521: 'SubSecTimeOriginal', - 37522: 'SubsecTimeDigitized', - 40960: 'FlashPixVersion', - 40961: 'ColorSpace', - 40962: 'PixelXDimension', - 40963: 'PixelYDimension', - 40964: 'RelatedSoundFile', - 40965: 'InteroperabilityIFD', - 41483: 'FlashEnergy', - 41484: 'SpatialFrequencyResponse', - 41486: 'FocalPlaneXResolution', - 41487: 'FocalPlaneYResolution', - 41488: 'FocalPlaneResolutionUnit', - 41492: 'SubjectLocation', - 41493: 'ExposureIndex', - 41495: 'SensingMethod', - 41728: 'FileSource', - 41729: 'SceneType', - 41730: 'CFAPattern', - 41985: 'CustomRendered', - 41986: 'ExposureMode', - 41987: 'WhiteBalance', - 41988: 'DigitalZoomRatio', - 41989: 'FocalLengthIn35mmFilm', - 41990: 'SceneCaptureType', - 41991: 'GainControl', - 41992: 'Contrast', - 41993: 'Saturation', - 41994: 'Sharpness', - 41995: 'DeviceSettingDescription', - 41996: 'SubjectDistanceRange', - 42016: 'ImageUniqueID', - 42032: 'CameraOwnerName', - 42033: 'BodySerialNumber', - 42034: 'LensSpecification', - 42035: 'LensMake', - 42036: 'LensModel', - 42037: 'LensSerialNumber', - 42112: 'GDAL_METADATA', - 42113: 'GDAL_NODATA', - 42240: 'Gamma', - 50215: 'Oce Scanjob Description', - 50216: 'Oce Application Selector', - 50217: 'Oce Identification Number', - 50218: 'Oce ImageLogic Characteristics', - - # Adobe DNG - 50706: 'DNGVersion', - 50707: 'DNGBackwardVersion', - 50708: 'UniqueCameraModel', - 50709: 'LocalizedCameraModel', - 50710: 'CFAPlaneColor', - 50711: 'CFALayout', - 50712: 'LinearizationTable', - 50713: 'BlackLevelRepeatDim', - 50714: 'BlackLevel', - 50715: 'BlackLevelDeltaH', - 50716: 'BlackLevelDeltaV', - 50717: 'WhiteLevel', - 50718: 'DefaultScale', - 50719: 'DefaultCropOrigin', - 50720: 'DefaultCropSize', - 50721: 'ColorMatrix1', - 50722: 'ColorMatrix2', - 50723: 'CameraCalibration1', - 50724: 'CameraCalibration2', - 50725: 'ReductionMatrix1', - 50726: 'ReductionMatrix2', - 50727: 'AnalogBalance', - 50728: 'AsShotNeutral', - 50729: 'AsShotWhiteXY', - 50730: 'BaselineExposure', - 50731: 'BaselineNoise', - 50732: 'BaselineSharpness', - 50733: 'BayerGreenSplit', - 50734: 'LinearResponseLimit', - 50735: 'CameraSerialNumber', - 50736: 'LensInfo', - 50737: 'ChromaBlurRadius', - 50738: 'AntiAliasStrength', - 50740: 'DNGPrivateData', - 50778: 'CalibrationIlluminant1', - 50779: 'CalibrationIlluminant2', - 50784: 'Alias Layer Metadata' - } +TAGS = { + 347: "JPEGTables", + 700: "XMP", + # Additional Exif Info + 32932: "Wang Annotation", + 33434: "ExposureTime", + 33437: "FNumber", + 33445: "MD FileTag", + 33446: "MD ScalePixel", + 33447: "MD ColorTable", + 33448: "MD LabName", + 33449: "MD SampleInfo", + 33450: "MD PrepDate", + 33451: "MD PrepTime", + 33452: "MD FileUnits", + 33550: "ModelPixelScaleTag", + 33723: "IptcNaaInfo", + 33918: "INGR Packet Data Tag", + 33919: "INGR Flag Registers", + 33920: "IrasB Transformation Matrix", + 33922: "ModelTiepointTag", + 34264: "ModelTransformationTag", + 34377: "PhotoshopInfo", + 34735: "GeoKeyDirectoryTag", + 34736: "GeoDoubleParamsTag", + 34737: "GeoAsciiParamsTag", + 34850: "ExposureProgram", + 34852: "SpectralSensitivity", + 34855: "ISOSpeedRatings", + 34856: "OECF", + 34864: "SensitivityType", + 34865: "StandardOutputSensitivity", + 34866: "RecommendedExposureIndex", + 34867: "ISOSpeed", + 34868: "ISOSpeedLatitudeyyy", + 34869: "ISOSpeedLatitudezzz", + 34908: "HylaFAX FaxRecvParams", + 34909: "HylaFAX FaxSubAddress", + 34910: "HylaFAX FaxRecvTime", + 36864: "ExifVersion", + 36867: "DateTimeOriginal", + 36868: "DateTImeDigitized", + 37121: "ComponentsConfiguration", + 37122: "CompressedBitsPerPixel", + 37724: "ImageSourceData", + 37377: "ShutterSpeedValue", + 37378: "ApertureValue", + 37379: "BrightnessValue", + 37380: "ExposureBiasValue", + 37381: "MaxApertureValue", + 37382: "SubjectDistance", + 37383: "MeteringMode", + 37384: "LightSource", + 37385: "Flash", + 37386: "FocalLength", + 37396: "SubjectArea", + 37500: "MakerNote", + 37510: "UserComment", + 37520: "SubSec", + 37521: "SubSecTimeOriginal", + 37522: "SubsecTimeDigitized", + 40960: "FlashPixVersion", + 40961: "ColorSpace", + 40962: "PixelXDimension", + 40963: "PixelYDimension", + 40964: "RelatedSoundFile", + 40965: "InteroperabilityIFD", + 41483: "FlashEnergy", + 41484: "SpatialFrequencyResponse", + 41486: "FocalPlaneXResolution", + 41487: "FocalPlaneYResolution", + 41488: "FocalPlaneResolutionUnit", + 41492: "SubjectLocation", + 41493: "ExposureIndex", + 41495: "SensingMethod", + 41728: "FileSource", + 41729: "SceneType", + 41730: "CFAPattern", + 41985: "CustomRendered", + 41986: "ExposureMode", + 41987: "WhiteBalance", + 41988: "DigitalZoomRatio", + 41989: "FocalLengthIn35mmFilm", + 41990: "SceneCaptureType", + 41991: "GainControl", + 41992: "Contrast", + 41993: "Saturation", + 41994: "Sharpness", + 41995: "DeviceSettingDescription", + 41996: "SubjectDistanceRange", + 42016: "ImageUniqueID", + 42032: "CameraOwnerName", + 42033: "BodySerialNumber", + 42034: "LensSpecification", + 42035: "LensMake", + 42036: "LensModel", + 42037: "LensSerialNumber", + 42112: "GDAL_METADATA", + 42113: "GDAL_NODATA", + 42240: "Gamma", + 50215: "Oce Scanjob Description", + 50216: "Oce Application Selector", + 50217: "Oce Identification Number", + 50218: "Oce ImageLogic Characteristics", + # Adobe DNG + 50706: "DNGVersion", + 50707: "DNGBackwardVersion", + 50708: "UniqueCameraModel", + 50709: "LocalizedCameraModel", + 50710: "CFAPlaneColor", + 50711: "CFALayout", + 50712: "LinearizationTable", + 50713: "BlackLevelRepeatDim", + 50714: "BlackLevel", + 50715: "BlackLevelDeltaH", + 50716: "BlackLevelDeltaV", + 50717: "WhiteLevel", + 50718: "DefaultScale", + 50719: "DefaultCropOrigin", + 50720: "DefaultCropSize", + 50721: "ColorMatrix1", + 50722: "ColorMatrix2", + 50723: "CameraCalibration1", + 50724: "CameraCalibration2", + 50725: "ReductionMatrix1", + 50726: "ReductionMatrix2", + 50727: "AnalogBalance", + 50728: "AsShotNeutral", + 50729: "AsShotWhiteXY", + 50730: "BaselineExposure", + 50731: "BaselineNoise", + 50732: "BaselineSharpness", + 50733: "BayerGreenSplit", + 50734: "LinearResponseLimit", + 50735: "CameraSerialNumber", + 50736: "LensInfo", + 50737: "ChromaBlurRadius", + 50738: "AntiAliasStrength", + 50740: "DNGPrivateData", + 50778: "CalibrationIlluminant1", + 50779: "CalibrationIlluminant2", + 50784: "Alias Layer Metadata", +} def _populate(): @@ -433,13 +435,48 @@ TYPES = {} # some of these are not in our TAGS_V2 dict and were included from tiff.h # This list also exists in encode.c -LIBTIFF_CORE = {255, 256, 257, 258, 259, 262, 263, 266, 274, 277, - 278, 280, 281, 340, 341, 282, 283, 284, 286, 287, - 296, 297, 321, 320, 338, 32995, 322, 323, 32998, - 32996, 339, 32997, 330, 531, 530, 301, 532, 333, - # as above - 269 # this has been in our tests forever, and works - } +LIBTIFF_CORE = { + 255, + 256, + 257, + 258, + 259, + 262, + 263, + 266, + 274, + 277, + 278, + 280, + 281, + 340, + 341, + 282, + 283, + 284, + 286, + 287, + 296, + 297, + 321, + 320, + 338, + 32995, + 322, + 323, + 32998, + 32996, + 339, + 32997, + 330, + 531, + 530, + 301, + 532, + 333, + # as above + 269, # this has been in our tests forever, and works +} LIBTIFF_CORE.remove(320) # Array of short, crashes LIBTIFF_CORE.remove(301) # Array of short, crashes diff --git a/src/PIL/WalImageFile.py b/src/PIL/WalImageFile.py index 6602cc86b..e2e1cd4f5 100644 --- a/src/PIL/WalImageFile.py +++ b/src/PIL/WalImageFile.py @@ -28,6 +28,7 @@ try: import builtins except ImportError: import __builtin__ + builtins = __builtin__ @@ -46,7 +47,7 @@ def open(filename): def imopen(fp): # read header fields - header = fp.read(32+24+32+12) + header = fp.read(32 + 24 + 32 + 12) size = i32(header, 32), i32(header, 36) offset = i32(header, 40) @@ -62,7 +63,7 @@ def open(filename): # strings are null-terminated im.info["name"] = header[:32].split(b"\0", 1)[0] - next_name = header[56:56+32].split(b"\0", 1)[0] + next_name = header[56 : 56 + 32].split(b"\0", 1)[0] if next_name: im.info["next_name"] = next_name diff --git a/src/PIL/WebPImagePlugin.py b/src/PIL/WebPImagePlugin.py index f2a99bb9d..17c493650 100644 --- a/src/PIL/WebPImagePlugin.py +++ b/src/PIL/WebPImagePlugin.py @@ -1,28 +1,23 @@ from . import Image, ImageFile + try: from . import _webp + SUPPORTED = True except ImportError: SUPPORTED = False from io import BytesIO -_VALID_WEBP_MODES = { - "RGBX": True, - "RGBA": True, - "RGB": True, - } +_VALID_WEBP_MODES = {"RGBX": True, "RGBA": True, "RGB": True} -_VALID_WEBP_LEGACY_MODES = { - "RGB": True, - "RGBA": True, - } +_VALID_WEBP_LEGACY_MODES = {"RGB": True, "RGBA": True} _VP8_MODES_BY_IDENTIFIER = { b"VP8 ": "RGB", b"VP8X": "RGBA", b"VP8L": "RGBA", # lossless - } +} def _accept(prefix): @@ -32,8 +27,9 @@ def _accept(prefix): if is_riff_file_format and is_webp_file and is_valid_vp8_mode: if not SUPPORTED: - return "image file could not be identified " \ - "because WEBP support not installed" + return ( + "image file could not be identified because WEBP support not installed" + ) return True @@ -45,8 +41,9 @@ class WebPImageFile(ImageFile.ImageFile): def _open(self): if not _webp.HAVE_WEBPANIM: # Legacy mode - data, width, height, self.mode, icc_profile, exif = \ - _webp.WebPDecode(self.fp.read()) + data, width, height, self.mode, icc_profile, exif = _webp.WebPDecode( + self.fp.read() + ) if icc_profile: self.info["icc_profile"] = icc_profile if exif: @@ -62,18 +59,18 @@ class WebPImageFile(ImageFile.ImageFile): self._decoder = _webp.WebPAnimDecoder(self.fp.read()) # Get info from decoder - width, height, loop_count, bgcolor, frame_count, mode = \ - self._decoder.get_info() + width, height, loop_count, bgcolor, frame_count, mode = self._decoder.get_info() self._size = width, height self.info["loop"] = loop_count - bg_a, bg_r, bg_g, bg_b = \ - (bgcolor >> 24) & 0xFF, \ - (bgcolor >> 16) & 0xFF, \ - (bgcolor >> 8) & 0xFF, \ - bgcolor & 0xFF + bg_a, bg_r, bg_g, bg_b = ( + (bgcolor >> 24) & 0xFF, + (bgcolor >> 16) & 0xFF, + (bgcolor >> 8) & 0xFF, + bgcolor & 0xFF, + ) self.info["background"] = (bg_r, bg_g, bg_b, bg_a) self._n_frames = frame_count - self.mode = 'RGB' if mode == 'RGBX' else mode + self.mode = "RGB" if mode == "RGBX" else mode self.rawmode = mode self.tile = [] @@ -132,7 +129,7 @@ class WebPImageFile(ImageFile.ImageFile): # Check if an error occurred if ret is None: - self._reset() # Reset just to be safe + self._reset() # Reset just to be safe self.seek(0) raise EOFError("failed to decode next frame in WebP file") @@ -147,11 +144,11 @@ class WebPImageFile(ImageFile.ImageFile): def _seek(self, frame): if self.__physical_frame == frame: - return # Nothing to do + return # Nothing to do if frame < self.__physical_frame: - self._reset() # Rewind to beginning + self._reset() # Rewind to beginning while self.__physical_frame < frame: - self._get_next() # Advance to the requested frame + self._get_next() # Advance to the requested frame def load(self): if _webp.HAVE_WEBPANIM: @@ -186,7 +183,7 @@ def _save_all(im, fp, filename): # If total frame count is 1, then save using the legacy API, which # will preserve non-alpha modes total = 0 - for ims in [im]+append_images: + for ims in [im] + append_images: total += getattr(ims, "n_frames", 1) if total == 1: _save(im, fp, filename) @@ -202,7 +199,7 @@ def _save_all(im, fp, filename): # info["background"]. So it must be converted to an RGBA value palette = im.getpalette() if palette: - r, g, b = palette[background*3:(background+1)*3] + r, g, b = palette[background * 3 : (background + 1) * 3] background = (r, g, b, 0) duration = im.encoderinfo.get("duration", 0) @@ -230,10 +227,15 @@ def _save_all(im, fp, filename): kmax = 17 if lossless else 5 # Validate background color - if (not isinstance(background, (list, tuple)) or len(background) != 4 or - not all(v >= 0 and v < 256 for v in background)): - raise IOError("Background color is not an RGBA tuple clamped " - "to (0-255): %s" % str(background)) + if ( + not isinstance(background, (list, tuple)) + or len(background) != 4 + or not all(v >= 0 and v < 256 for v in background) + ): + raise IOError( + "Background color is not an RGBA tuple clamped to (0-255): %s" + % str(background) + ) # Convert to packed uint bg_r, bg_g, bg_b, bg_a = background @@ -241,13 +243,15 @@ def _save_all(im, fp, filename): # Setup the WebP animation encoder enc = _webp.WebPAnimEncoder( - im.size[0], im.size[1], + im.size[0], + im.size[1], background, loop, minimize_size, - kmin, kmax, + kmin, + kmax, allow_mixed, - verbose + verbose, ) # Add each frame @@ -255,7 +259,7 @@ def _save_all(im, fp, filename): timestamp = 0 cur_idx = im.tell() try: - for ims in [im]+append_images: + for ims in [im] + append_images: # Get # of frames in this image nfr = getattr(ims, "n_frames", 1) @@ -267,25 +271,28 @@ def _save_all(im, fp, filename): frame = ims rawmode = ims.mode if ims.mode not in _VALID_WEBP_MODES: - alpha = 'A' in ims.mode or 'a' in ims.mode \ - or (ims.mode == 'P' and - 'A' in ims.im.getpalettemode()) - rawmode = 'RGBA' if alpha else 'RGB' + alpha = ( + "A" in ims.mode + or "a" in ims.mode + or (ims.mode == "P" and "A" in ims.im.getpalettemode()) + ) + rawmode = "RGBA" if alpha else "RGB" frame = ims.convert(rawmode) - if rawmode == 'RGB': + if rawmode == "RGB": # For faster conversion, use RGBX - rawmode = 'RGBX' + rawmode = "RGBX" # Append the frame to the animation encoder enc.add( - frame.tobytes('raw', rawmode), + frame.tobytes("raw", rawmode), timestamp, - frame.size[0], frame.size[1], + frame.size[0], + frame.size[1], rawmode, lossless, quality, - method + method, ) # Update timestamp and frame index @@ -299,11 +306,7 @@ def _save_all(im, fp, filename): im.seek(cur_idx) # Force encoder to flush frames - enc.add( - None, - timestamp, - 0, 0, "", lossless, quality, 0 - ) + enc.add(None, timestamp, 0, 0, "", lossless, quality, 0) # Get the final output from the encoder data = enc.assemble(icc_profile, exif, xmp) @@ -323,9 +326,12 @@ def _save(im, fp, filename): xmp = im.encoderinfo.get("xmp", "") if im.mode not in _VALID_WEBP_LEGACY_MODES: - alpha = 'A' in im.mode or 'a' in im.mode \ - or (im.mode == 'P' and 'A' in im.im.getpalettemode()) - im = im.convert('RGBA' if alpha else 'RGB') + alpha = ( + "A" in im.mode + or "a" in im.mode + or (im.mode == "P" and "A" in im.im.getpalettemode()) + ) + im = im.convert("RGBA" if alpha else "RGB") data = _webp.WebPEncode( im.tobytes(), @@ -336,7 +342,7 @@ def _save(im, fp, filename): im.mode, icc_profile, exif, - xmp + xmp, ) if data is None: raise IOError("cannot write file as WebP (encoder returned None)") diff --git a/src/PIL/WmfImagePlugin.py b/src/PIL/WmfImagePlugin.py index 413bd1847..36ae37138 100644 --- a/src/PIL/WmfImagePlugin.py +++ b/src/PIL/WmfImagePlugin.py @@ -22,8 +22,7 @@ from __future__ import print_function from . import Image, ImageFile -from ._binary import i16le as word, si16le as short, \ - i32le as dword, si32le as _long +from ._binary import i16le as word, si16le as short, i32le as dword, si32le as _long from ._util import py3 @@ -51,7 +50,6 @@ if hasattr(Image.core, "drawwmf"): # install default handler (windows only) class WmfHandler(object): - def open(self, im): im.mode = "RGB" self.bbox = im.info["wmf_bbox"] @@ -59,10 +57,14 @@ if hasattr(Image.core, "drawwmf"): def load(self, im): im.fp.seek(0) # rewind return Image.frombytes( - "RGB", im.size, + "RGB", + im.size, Image.core.drawwmf(im.fp.read(), im.size, self.bbox), - "raw", "BGR", (im.size[0]*3 + 3) & -4, -1 - ) + "raw", + "BGR", + (im.size[0] * 3 + 3) & -4, + -1, + ) register_handler(WmfHandler()) @@ -73,14 +75,14 @@ if hasattr(Image.core, "drawwmf"): def _accept(prefix): return ( - prefix[:6] == b"\xd7\xcd\xc6\x9a\x00\x00" or - prefix[:4] == b"\x01\x00\x00\x00" - ) + prefix[:6] == b"\xd7\xcd\xc6\x9a\x00\x00" or prefix[:4] == b"\x01\x00\x00\x00" + ) ## # Image plugin for Windows metafiles. + class WmfStubImageFile(ImageFile.StubImageFile): format = "WMF" @@ -160,6 +162,7 @@ def _save(im, fp, filename): raise IOError("WMF save handler not installed") _handler.save(im, fp, filename) + # # -------------------------------------------------------------------- # Registry stuff diff --git a/src/PIL/XVThumbImagePlugin.py b/src/PIL/XVThumbImagePlugin.py index ad913b2a8..aa3536d85 100644 --- a/src/PIL/XVThumbImagePlugin.py +++ b/src/PIL/XVThumbImagePlugin.py @@ -31,7 +31,9 @@ PALETTE = b"" for r in range(8): for g in range(8): for b in range(4): - PALETTE = PALETTE + (o8((r*255)//7)+o8((g*255)//7)+o8((b*255)//3)) + PALETTE = PALETTE + ( + o8((r * 255) // 7) + o8((g * 255) // 7) + o8((b * 255) // 3) + ) def _accept(prefix): @@ -41,6 +43,7 @@ def _accept(prefix): ## # Image plugin for XV thumbnail images. + class XVThumbImageFile(ImageFile.ImageFile): format = "XVThumb" @@ -71,10 +74,7 @@ class XVThumbImageFile(ImageFile.ImageFile): self.palette = ImagePalette.raw("RGB", PALETTE) - self.tile = [ - ("raw", (0, 0)+self.size, - self.fp.tell(), (self.mode, 0, 1) - )] + self.tile = [("raw", (0, 0) + self.size, self.fp.tell(), (self.mode, 0, 1))] # -------------------------------------------------------------------- diff --git a/src/PIL/XbmImagePlugin.py b/src/PIL/XbmImagePlugin.py index af5adccd2..3afac688d 100644 --- a/src/PIL/XbmImagePlugin.py +++ b/src/PIL/XbmImagePlugin.py @@ -45,6 +45,7 @@ def _accept(prefix): ## # Image plugin for X11 bitmaps. + class XbmImageFile(ImageFile.ImageFile): format = "XBM" @@ -60,14 +61,12 @@ class XbmImageFile(ImageFile.ImageFile): ysize = int(m.group("height")) if m.group("hotspot"): - self.info["hotspot"] = ( - int(m.group("xhot")), int(m.group("yhot")) - ) + self.info["hotspot"] = (int(m.group("xhot")), int(m.group("yhot"))) self.mode = "1" self._size = xsize, ysize - self.tile = [("xbm", (0, 0)+self.size, m.end(), None)] + self.tile = [("xbm", (0, 0) + self.size, m.end(), None)] def _save(im, fp, filename): @@ -75,17 +74,17 @@ def _save(im, fp, filename): if im.mode != "1": raise IOError("cannot write mode %s as XBM" % im.mode) - fp.write(("#define im_width %d\n" % im.size[0]).encode('ascii')) - fp.write(("#define im_height %d\n" % im.size[1]).encode('ascii')) + fp.write(("#define im_width %d\n" % im.size[0]).encode("ascii")) + fp.write(("#define im_height %d\n" % im.size[1]).encode("ascii")) hotspot = im.encoderinfo.get("hotspot") if hotspot: - fp.write(("#define im_x_hot %d\n" % hotspot[0]).encode('ascii')) - fp.write(("#define im_y_hot %d\n" % hotspot[1]).encode('ascii')) + fp.write(("#define im_x_hot %d\n" % hotspot[0]).encode("ascii")) + fp.write(("#define im_y_hot %d\n" % hotspot[1]).encode("ascii")) fp.write(b"static char im_bits[] = {\n") - ImageFile._save(im, fp, [("xbm", (0, 0)+im.size, 0, None)]) + ImageFile._save(im, fp, [("xbm", (0, 0) + im.size, 0, None)]) fp.write(b"};\n") diff --git a/src/PIL/XpmImagePlugin.py b/src/PIL/XpmImagePlugin.py index 9cecdbca2..fa2c4caa5 100644 --- a/src/PIL/XpmImagePlugin.py +++ b/src/PIL/XpmImagePlugin.py @@ -24,7 +24,7 @@ from ._binary import i8, o8 __version__ = "0.2" # XPM header -xpm_head = re.compile(b"\"([0-9]*) ([0-9]*) ([0-9]*) ([0-9]*)") +xpm_head = re.compile(b'"([0-9]*) ([0-9]*) ([0-9]*) ([0-9]*)') def _accept(prefix): @@ -34,6 +34,7 @@ def _accept(prefix): ## # Image plugin for X11 pixel maps. + class XpmImageFile(ImageFile.ImageFile): format = "XPM" @@ -69,9 +70,9 @@ class XpmImageFile(ImageFile.ImageFile): for i in range(pal): s = self.fp.readline() - if s[-2:] == b'\r\n': + if s[-2:] == b"\r\n": s = s[:-2] - elif s[-1:] in b'\r\n': + elif s[-1:] in b"\r\n": s = s[:-1] c = i8(s[1]) @@ -82,15 +83,15 @@ class XpmImageFile(ImageFile.ImageFile): if s[i] == b"c": # process colour key - rgb = s[i+1] + rgb = s[i + 1] if rgb == b"None": self.info["transparency"] = c elif rgb[0:1] == b"#": # FIXME: handle colour names (see ImagePalette.py) rgb = int(rgb[1:], 16) - palette[c] = (o8((rgb >> 16) & 255) + - o8((rgb >> 8) & 255) + - o8(rgb & 255)) + palette[c] = ( + o8((rgb >> 16) & 255) + o8((rgb >> 8) & 255) + o8(rgb & 255) + ) else: # unknown colour raise ValueError("cannot read this XPM file") @@ -104,7 +105,7 @@ class XpmImageFile(ImageFile.ImageFile): self.mode = "P" self.palette = ImagePalette.raw("RGB", b"".join(palette)) - self.tile = [("raw", (0, 0)+self.size, self.fp.tell(), ("P", 0, 1))] + self.tile = [("raw", (0, 0) + self.size, self.fp.tell(), ("P", 0, 1))] def load_read(self, bytes): @@ -116,10 +117,11 @@ class XpmImageFile(ImageFile.ImageFile): s = [None] * ysize for i in range(ysize): - s[i] = self.fp.readline()[1:xsize+1].ljust(xsize) + s[i] = self.fp.readline()[1 : xsize + 1].ljust(xsize) return b"".join(s) + # # Registry diff --git a/src/PIL/__init__.py b/src/PIL/__init__.py index ec0611b68..59eccc9b5 100644 --- a/src/PIL/__init__.py +++ b/src/PIL/__init__.py @@ -24,48 +24,50 @@ PILLOW_VERSION = __version__ = _version.__version__ del _version -_plugins = ['BlpImagePlugin', - 'BmpImagePlugin', - 'BufrStubImagePlugin', - 'CurImagePlugin', - 'DcxImagePlugin', - 'DdsImagePlugin', - 'EpsImagePlugin', - 'FitsStubImagePlugin', - 'FliImagePlugin', - 'FpxImagePlugin', - 'FtexImagePlugin', - 'GbrImagePlugin', - 'GifImagePlugin', - 'GribStubImagePlugin', - 'Hdf5StubImagePlugin', - 'IcnsImagePlugin', - 'IcoImagePlugin', - 'ImImagePlugin', - 'ImtImagePlugin', - 'IptcImagePlugin', - 'JpegImagePlugin', - 'Jpeg2KImagePlugin', - 'McIdasImagePlugin', - 'MicImagePlugin', - 'MpegImagePlugin', - 'MpoImagePlugin', - 'MspImagePlugin', - 'PalmImagePlugin', - 'PcdImagePlugin', - 'PcxImagePlugin', - 'PdfImagePlugin', - 'PixarImagePlugin', - 'PngImagePlugin', - 'PpmImagePlugin', - 'PsdImagePlugin', - 'SgiImagePlugin', - 'SpiderImagePlugin', - 'SunImagePlugin', - 'TgaImagePlugin', - 'TiffImagePlugin', - 'WebPImagePlugin', - 'WmfImagePlugin', - 'XbmImagePlugin', - 'XpmImagePlugin', - 'XVThumbImagePlugin'] +_plugins = [ + "BlpImagePlugin", + "BmpImagePlugin", + "BufrStubImagePlugin", + "CurImagePlugin", + "DcxImagePlugin", + "DdsImagePlugin", + "EpsImagePlugin", + "FitsStubImagePlugin", + "FliImagePlugin", + "FpxImagePlugin", + "FtexImagePlugin", + "GbrImagePlugin", + "GifImagePlugin", + "GribStubImagePlugin", + "Hdf5StubImagePlugin", + "IcnsImagePlugin", + "IcoImagePlugin", + "ImImagePlugin", + "ImtImagePlugin", + "IptcImagePlugin", + "JpegImagePlugin", + "Jpeg2KImagePlugin", + "McIdasImagePlugin", + "MicImagePlugin", + "MpegImagePlugin", + "MpoImagePlugin", + "MspImagePlugin", + "PalmImagePlugin", + "PcdImagePlugin", + "PcxImagePlugin", + "PdfImagePlugin", + "PixarImagePlugin", + "PngImagePlugin", + "PpmImagePlugin", + "PsdImagePlugin", + "SgiImagePlugin", + "SpiderImagePlugin", + "SunImagePlugin", + "TgaImagePlugin", + "TiffImagePlugin", + "WebPImagePlugin", + "WmfImagePlugin", + "XbmImagePlugin", + "XpmImagePlugin", + "XVThumbImagePlugin", +] diff --git a/src/PIL/__main__.py b/src/PIL/__main__.py new file mode 100644 index 000000000..a05323f93 --- /dev/null +++ b/src/PIL/__main__.py @@ -0,0 +1,3 @@ +from .features import pilinfo + +pilinfo() diff --git a/src/PIL/_binary.py b/src/PIL/_binary.py index 767c13b9d..e5ee0bf28 100644 --- a/src/PIL/_binary.py +++ b/src/PIL/_binary.py @@ -15,12 +15,16 @@ from struct import unpack_from, pack from ._util import py3 if py3: + def i8(c): return c if c.__class__ is int else c[0] def o8(i): return bytes((i & 255,)) + + else: + def i8(c): return ord(c) @@ -33,8 +37,8 @@ def i16le(c, o=0): """ Converts a 2-bytes (16 bits) string to an unsigned integer. - c: string containing bytes to convert - o: offset of bytes to convert in string + :param c: string containing bytes to convert + :param o: offset of bytes to convert in string """ return unpack_from(" 2: else: from Tkinter import tkinter as tk -if hasattr(sys, 'pypy_find_executable'): +if hasattr(sys, "pypy_find_executable"): # Tested with packages at https://bitbucket.org/pypy/pypy/downloads. # PyPies 1.6, 2.0 do not have tkinter built in. PyPy3-2.3.1 gives an # OSError trying to import tkinter. Otherwise: diff --git a/src/PIL/_util.py b/src/PIL/_util.py index cb307050c..59964c7ef 100644 --- a/src/PIL/_util.py +++ b/src/PIL/_util.py @@ -5,6 +5,7 @@ py3 = sys.version_info.major >= 3 py36 = sys.version_info[0:2] >= (3, 6) if py3: + def isStringType(t): return isinstance(t, str) @@ -13,10 +14,15 @@ if py3: def isPath(f): return isinstance(f, (bytes, str, Path)) + else: + def isPath(f): return isinstance(f, (bytes, str)) + + else: + def isStringType(t): return isinstance(t, basestring) # noqa: F821 diff --git a/src/PIL/_version.py b/src/PIL/_version.py index 7db210d63..c1fe7c797 100644 --- a/src/PIL/_version.py +++ b/src/PIL/_version.py @@ -1,2 +1,2 @@ # Master version for Pillow -__version__ = '6.1.0.dev0' +__version__ = "6.1.0.dev0" diff --git a/src/PIL/features.py b/src/PIL/features.py index 6530038b7..eff259dfc 100644 --- a/src/PIL/features.py +++ b/src/PIL/features.py @@ -1,3 +1,10 @@ +from __future__ import print_function, unicode_literals + +import collections +import os +import sys + +import PIL from . import Image modules = { @@ -26,12 +33,7 @@ def get_supported_modules(): return [f for f in modules if check_module(f)] -codecs = { - "jpg": "jpeg", - "jpg_2000": "jpeg2k", - "zlib": "zip", - "libtiff": "libtiff" -} +codecs = {"jpg": "jpeg", "jpg_2000": "jpeg2k", "zlib": "zip", "libtiff": "libtiff"} def check_codec(feature): @@ -48,8 +50,8 @@ def get_supported_codecs(): features = { - "webp_anim": ("PIL._webp", 'HAVE_WEBPANIM'), - "webp_mux": ("PIL._webp", 'HAVE_WEBPMUX'), + "webp_anim": ("PIL._webp", "HAVE_WEBPANIM"), + "webp_mux": ("PIL._webp", "HAVE_WEBPMUX"), "transp_webp": ("PIL._webp", "HAVE_TRANSPARENCY"), "raqm": ("PIL._imagingft", "HAVE_RAQM"), "libjpeg_turbo": ("PIL._imaging", "HAVE_LIBJPEGTURBO"), @@ -63,7 +65,7 @@ def check_feature(feature): module, flag = features[feature] try: - imported_module = __import__(module, fromlist=['PIL']) + imported_module = __import__(module, fromlist=["PIL"]) return getattr(imported_module, flag) except ImportError: return None @@ -74,9 +76,14 @@ def get_supported_features(): def check(feature): - return (feature in modules and check_module(feature) or - feature in codecs and check_codec(feature) or - feature in features and check_feature(feature)) + return ( + feature in modules + and check_module(feature) + or feature in codecs + and check_codec(feature) + or feature in features + and check_feature(feature) + ) def get_supported(): @@ -84,3 +91,78 @@ def get_supported(): ret.extend(get_supported_features()) ret.extend(get_supported_codecs()) return ret + + +def pilinfo(out=None): + if out is None: + out = sys.stdout + + Image.init() + + print("-" * 68, file=out) + print("Pillow {}".format(PIL.__version__), file=out) + print("-" * 68, file=out) + print( + "Python modules loaded from {}".format(os.path.dirname(Image.__file__)), + file=out, + ) + print( + "Binary modules loaded from {}".format(os.path.dirname(Image.core.__file__)), + file=out, + ) + print("-" * 68, file=out) + + v = sys.version.splitlines() + print("Python {}".format(v[0].strip()), file=out) + for v in v[1:]: + print(" {}".format(v.strip()), file=out) + print("-" * 68, file=out) + + for name, feature in [ + ("pil", "PIL CORE"), + ("tkinter", "TKINTER"), + ("freetype2", "FREETYPE2"), + ("littlecms2", "LITTLECMS2"), + ("webp", "WEBP"), + ("transp_webp", "WEBP Transparency"), + ("webp_mux", "WEBPMUX"), + ("webp_anim", "WEBP Animation"), + ("jpg", "JPEG"), + ("jpg_2000", "OPENJPEG (JPEG2000)"), + ("zlib", "ZLIB (PNG/ZIP)"), + ("libtiff", "LIBTIFF"), + ("raqm", "RAQM (Bidirectional Text)"), + ]: + if check(name): + print("---", feature, "support ok", file=out) + else: + print("***", feature, "support not installed", file=out) + print("-" * 68, file=out) + + extensions = collections.defaultdict(list) + for ext, i in Image.EXTENSION.items(): + extensions[i].append(ext) + + for i in sorted(Image.ID): + line = "{}".format(i) + if i in Image.MIME: + line = "{} {}".format(line, Image.MIME[i]) + print(line, file=out) + + if i in extensions: + print("Extensions: {}".format(", ".join(sorted(extensions[i]))), file=out) + + features = [] + if i in Image.OPEN: + features.append("open") + if i in Image.SAVE: + features.append("save") + if i in Image.SAVE_ALL: + features.append("save_all") + if i in Image.DECODERS: + features.append("decode") + if i in Image.ENCODERS: + features.append("encode") + + print("Features: {}".format(", ".join(features)), file=out) + print("-" * 68, file=out) diff --git a/src/Tk/tkImaging.c b/src/Tk/tkImaging.c index 991cf1c95..bb0fd33a3 100644 --- a/src/Tk/tkImaging.c +++ b/src/Tk/tkImaging.c @@ -149,17 +149,18 @@ PyImagingPhotoPut(ClientData clientdata, Tcl_Interp* interp, return TCL_OK; } -/* Warning -- this does not work at all */ static int PyImagingPhotoGet(ClientData clientdata, Tcl_Interp* interp, int argc, const char **argv) { + Imaging im; Tk_PhotoHandle photo; Tk_PhotoImageBlock block; + int x, y, z; - if (argc != 2) { + if (argc != 3) { TCL_APPEND_RESULT(interp, "usage: ", argv[0], - " srcPhoto", (char *) NULL); + " srcPhoto destImage", (char *) NULL); return TCL_ERROR; } @@ -172,21 +173,26 @@ PyImagingPhotoGet(ClientData clientdata, Tcl_Interp* interp, return TCL_ERROR; } + /* get PIL Image handle */ + im = ImagingFind(argv[2]); + if (!im) { + TCL_APPEND_RESULT(interp, "bad name", (char*) NULL); + return TCL_ERROR; + } + TK_PHOTO_GET_IMAGE(photo, &block); - printf("pixelPtr = %p\n", block.pixelPtr); - printf("width = %d\n", block.width); - printf("height = %d\n", block.height); - printf("pitch = %d\n", block.pitch); - printf("pixelSize = %d\n", block.pixelSize); - printf("offset = %d %d %d %d\n", block.offset[0], block.offset[1], - block.offset[2], block.offset[3]); + for (y = 0; y < block.height; y++) { + UINT8* out = (UINT8*)im->image32[y]; + for (x = 0; x < block.pitch; x += block.pixelSize) { + for (z=0; z < block.pixelSize; z++) { + int offset = block.offset[z]; + out[x + offset] = block.pixelPtr[y * block.pitch + x + offset]; + } + } + } - TCL_APPEND_RESULT( - interp, "this function is not yet supported", (char *) NULL - ); - - return TCL_ERROR; + return TCL_OK; } diff --git a/src/_imaging.c b/src/_imaging.c index 037348baa..d6cf562da 100644 --- a/src/_imaging.c +++ b/src/_imaging.c @@ -86,6 +86,9 @@ #include "py3.h" +#define _USE_MATH_DEFINES +#include + /* Configuration stuff. Feel free to undef things you don't need. */ #define WITH_IMAGECHOPS /* ImageChops support */ #define WITH_IMAGEDRAW /* ImageDraw support */ @@ -1176,59 +1179,68 @@ _getpixel(ImagingObject* self, PyObject* args) return getpixel(self->image, self->access, x, y); } +union hist_extrema { + UINT8 u[2]; + INT32 i[2]; + FLOAT32 f[2]; +}; + +static union hist_extrema* +parse_histogram_extremap(ImagingObject* self, PyObject* extremap, + union hist_extrema* ep) +{ + int i0, i1; + double f0, f1; + + if (extremap) { + switch (self->image->type) { + case IMAGING_TYPE_UINT8: + if (!PyArg_ParseTuple(extremap, "ii", &i0, &i1)) + return NULL; + ep->u[0] = CLIP8(i0); + ep->u[1] = CLIP8(i1); + break; + case IMAGING_TYPE_INT32: + if (!PyArg_ParseTuple(extremap, "ii", &i0, &i1)) + return NULL; + ep->i[0] = i0; + ep->i[1] = i1; + break; + case IMAGING_TYPE_FLOAT32: + if (!PyArg_ParseTuple(extremap, "dd", &f0, &f1)) + return NULL; + ep->f[0] = (FLOAT32) f0; + ep->f[1] = (FLOAT32) f1; + break; + default: + return NULL; + } + } else { + return NULL; + } + return ep; +} + static PyObject* _histogram(ImagingObject* self, PyObject* args) { ImagingHistogram h; PyObject* list; int i; - union { - UINT8 u[2]; - INT32 i[2]; - FLOAT32 f[2]; - } extrema; - void* ep; - int i0, i1; - double f0, f1; + union hist_extrema extrema; + union hist_extrema* ep; PyObject* extremap = NULL; ImagingObject* maskp = NULL; if (!PyArg_ParseTuple(args, "|OO!", &extremap, &Imaging_Type, &maskp)) - return NULL; - - if (extremap) { - ep = &extrema; - switch (self->image->type) { - case IMAGING_TYPE_UINT8: - if (!PyArg_ParseTuple(extremap, "ii", &i0, &i1)) - return NULL; - /* FIXME: clip */ - extrema.u[0] = i0; - extrema.u[1] = i1; - break; - case IMAGING_TYPE_INT32: - if (!PyArg_ParseTuple(extremap, "ii", &i0, &i1)) - return NULL; - extrema.i[0] = i0; - extrema.i[1] = i1; - break; - case IMAGING_TYPE_FLOAT32: - if (!PyArg_ParseTuple(extremap, "dd", &f0, &f1)) - return NULL; - extrema.f[0] = (FLOAT32) f0; - extrema.f[1] = (FLOAT32) f1; - break; - default: - ep = NULL; - break; - } - } else - ep = NULL; + return NULL; + /* Using a var to avoid allocations. */ + ep = parse_histogram_extremap(self, extremap, &extrema); h = ImagingGetHistogram(self->image, (maskp) ? maskp->image : NULL, ep); if (!h) - return NULL; + return NULL; /* Build an integer list containing the histogram */ list = PyList_New(h->bands * 256); @@ -1243,11 +1255,59 @@ _histogram(ImagingObject* self, PyObject* args) PyList_SetItem(list, i, item); } + /* Destroy the histogram structure */ ImagingHistogramDelete(h); return list; } +static PyObject* +_entropy(ImagingObject* self, PyObject* args) +{ + ImagingHistogram h; + int idx, length; + long sum; + double entropy, fsum, p; + union hist_extrema extrema; + union hist_extrema* ep; + + PyObject* extremap = NULL; + ImagingObject* maskp = NULL; + if (!PyArg_ParseTuple(args, "|OO!", &extremap, &Imaging_Type, &maskp)) + return NULL; + + /* Using a local var to avoid allocations. */ + ep = parse_histogram_extremap(self, extremap, &extrema); + h = ImagingGetHistogram(self->image, (maskp) ? maskp->image : NULL, ep); + + if (!h) + return NULL; + + /* Calculate the histogram entropy */ + /* First, sum the histogram data */ + length = h->bands * 256; + sum = 0; + for (idx = 0; idx < length; idx++) { + sum += h->histogram[idx]; + } + + /* Next, normalize the histogram data, */ + /* using the histogram sum value */ + fsum = (double)sum; + entropy = 0.0; + for (idx = 0; idx < length; idx++) { + p = (double)h->histogram[idx] / fsum; + if (p != 0.0) { + entropy += p * log(p) * M_LOG2E; + } + } + + /* Destroy the histogram structure */ + ImagingHistogramDelete(h); + + return PyFloat_FromDouble(-entropy); +} + #ifdef WITH_MODEFILTER static PyObject* _modefilter(ImagingObject* self, PyObject* args) @@ -1588,7 +1648,7 @@ _putpalette(ImagingObject* self, PyObject* args) ImagingPaletteDelete(self->image->palette); - strcpy(self->image->mode, "P"); + strcpy(self->image->mode, strlen(self->image->mode) == 2 ? "PA" : "P"); self->image->palette = ImagingPaletteNew("RGB"); @@ -3193,6 +3253,7 @@ static struct PyMethodDef methods[] = { {"expand", (PyCFunction)_expand_image, 1}, {"filter", (PyCFunction)_filter, 1}, {"histogram", (PyCFunction)_histogram, 1}, + {"entropy", (PyCFunction)_entropy, 1}, #ifdef WITH_MODEFILTER {"modefilter", (PyCFunction)_modefilter, 1}, #endif @@ -3625,6 +3686,12 @@ _set_blocks_max(PyObject* self, PyObject* args) "blocks_max should be greater than 0"); return NULL; } + else if ( blocks_max > SIZE_MAX/sizeof(ImagingDefaultArena.blocks_pool[0])) { + PyErr_SetString(PyExc_ValueError, + "blocks_max is too large"); + return NULL; + } + if ( ! ImagingMemorySetBlocksMax(&ImagingDefaultArena, blocks_max)) { ImagingError_MemoryError(); @@ -3912,4 +3979,3 @@ init_imaging(void) setup_module(m); } #endif - diff --git a/src/_imagingft.c b/src/_imagingft.c index 6b65e2c33..2567e30d1 100644 --- a/src/_imagingft.c +++ b/src/_imagingft.c @@ -25,6 +25,8 @@ #include #include FT_FREETYPE_H #include FT_GLYPH_H +#include FT_MULTIPLE_MASTERS_H +#include FT_SFNT_NAMES_H #define KEEP_PY_UNICODE #include "py3.h" @@ -54,7 +56,7 @@ typedef struct { - int index, x_offset, x_advance, y_offset; + int index, x_offset, x_advance, y_offset, y_advance; unsigned int cluster; } GlyphInfo; @@ -79,6 +81,9 @@ typedef struct { static PyTypeObject Font_Type; +typedef bool (*t_raqm_version_atleast)(unsigned int major, + unsigned int minor, + unsigned int micro); typedef raqm_t* (*t_raqm_create)(void); typedef int (*t_raqm_set_text)(raqm_t *rq, const uint32_t *text, @@ -107,6 +112,7 @@ typedef void (*t_raqm_destroy) (raqm_t *rq); typedef struct { void* raqm; int version; + t_raqm_version_atleast version_atleast; t_raqm_create create; t_raqm_set_text set_text; t_raqm_set_text_utf8 set_text_utf8; @@ -162,6 +168,7 @@ setraqm(void) } #if !defined(_MSC_VER) + p_raqm.version_atleast = (t_raqm_version_atleast)dlsym(p_raqm.raqm, "raqm_version_atleast"); p_raqm.create = (t_raqm_create)dlsym(p_raqm.raqm, "raqm_create"); p_raqm.set_text = (t_raqm_set_text)dlsym(p_raqm.raqm, "raqm_set_text"); p_raqm.set_text_utf8 = (t_raqm_set_text_utf8)dlsym(p_raqm.raqm, "raqm_set_text_utf8"); @@ -194,6 +201,7 @@ setraqm(void) return 2; } #else + p_raqm.version_atleast = (t_raqm_version_atleast)GetProcAddress(p_raqm.raqm, "raqm_version_atleast"); p_raqm.create = (t_raqm_create)GetProcAddress(p_raqm.raqm, "raqm_create"); p_raqm.set_text = (t_raqm_set_text)GetProcAddress(p_raqm.raqm, "raqm_set_text"); p_raqm.set_text_utf8 = (t_raqm_set_text_utf8)GetProcAddress(p_raqm.raqm, "raqm_set_text_utf8"); @@ -257,7 +265,7 @@ getfont(PyObject* self_, PyObject* args, PyObject* kw) return NULL; } - if (!PyArg_ParseTupleAndKeywords(args, kw, "eti|is"PY_ARG_BYTES_LENGTH"i", + if (!PyArg_ParseTupleAndKeywords(args, kw, "etn|ns"PY_ARG_BYTES_LENGTH"n", kwlist, Py_FileSystemDefaultEncoding, &filename, &size, &index, &encoding, &font_bytes, @@ -307,6 +315,7 @@ getfont(PyObject* self_, PyObject* args, PyObject* kw) if (error) { if (self->font_bytes) { PyMem_Free(self->font_bytes); + self->font_bytes = NULL; } Py_DECREF(self); return geterror(error); @@ -345,13 +354,11 @@ static size_t text_layout_raqm(PyObject* string, FontObject* self, const char* dir, PyObject *features, const char* lang, GlyphInfo **glyph_info, int mask) { - int i = 0; + size_t i = 0, count = 0, start = 0; raqm_t *rq; - size_t count = 0; raqm_glyph_t *glyphs = NULL; raqm_glyph_t_01 *glyphs_01 = NULL; raqm_direction_t direction; - size_t start = 0; rq = (*p_raqm.create)(); if (rq == NULL) { @@ -409,9 +416,13 @@ text_layout_raqm(PyObject* string, FontObject* self, const char* dir, PyObject * direction = RAQM_DIRECTION_RTL; else if (strcmp(dir, "ltr") == 0) direction = RAQM_DIRECTION_LTR; - else if (strcmp(dir, "ttb") == 0) + else if (strcmp(dir, "ttb") == 0) { direction = RAQM_DIRECTION_TTB; - else { + if (p_raqm.version_atleast == NULL || !(*p_raqm.version_atleast)(0, 7, 0)) { + PyErr_SetString(PyExc_ValueError, "libraqm 0.7 or greater required for 'ttb' direction"); + goto failed; + } + } else { PyErr_SetString(PyExc_ValueError, "direction must be either 'rtl', 'ltr' or 'ttb'"); goto failed; } @@ -423,15 +434,15 @@ text_layout_raqm(PyObject* string, FontObject* self, const char* dir, PyObject * } if (features != Py_None) { - int len; + int j, len; PyObject *seq = PySequence_Fast(features, "expected a sequence"); if (!seq) { goto failed; } len = PySequence_Size(seq); - for (i = 0; i < len; i++) { - PyObject *item = PySequence_Fast_GET_ITEM(seq, i); + for (j = 0; j < len; j++) { + PyObject *item = PySequence_Fast_GET_ITEM(seq, j); char *feature = NULL; Py_ssize_t size = 0; PyObject *bytes; @@ -475,7 +486,7 @@ text_layout_raqm(PyObject* string, FontObject* self, const char* dir, PyObject * goto failed; } - if (p_raqm.version == 1){ + if (p_raqm.version == 1) { glyphs_01 = (*p_raqm.get_glyphs_01)(rq, &count); if (glyphs_01 == NULL) { PyErr_SetString(PyExc_ValueError, "raqm_get_glyphs() failed."); @@ -498,12 +509,13 @@ text_layout_raqm(PyObject* string, FontObject* self, const char* dir, PyObject * goto failed; } - if (p_raqm.version == 1){ + if (p_raqm.version == 1) { for (i = 0; i < count; i++) { (*glyph_info)[i].index = glyphs_01[i].index; (*glyph_info)[i].x_offset = glyphs_01[i].x_offset; (*glyph_info)[i].x_advance = glyphs_01[i].x_advance; (*glyph_info)[i].y_offset = glyphs_01[i].y_offset; + (*glyph_info)[i].y_advance = glyphs_01[i].y_advance; (*glyph_info)[i].cluster = glyphs_01[i].cluster; } } else { @@ -512,6 +524,7 @@ text_layout_raqm(PyObject* string, FontObject* self, const char* dir, PyObject * (*glyph_info)[i].x_offset = glyphs[i].x_offset; (*glyph_info)[i].x_advance = glyphs[i].x_advance; (*glyph_info)[i].y_offset = glyphs[i].y_offset; + (*glyph_info)[i].y_advance = glyphs[i].y_advance; (*glyph_info)[i].cluster = glyphs[i].cluster; } } @@ -578,9 +591,11 @@ text_layout_fallback(PyObject* string, FontObject* self, const char* dir, PyObje if (FT_Get_Kerning(self->face, last_index, (*glyph_info)[i].index, ft_kerning_default,&delta) == 0) (*glyph_info)[i-1].x_advance += PIXEL(delta.x); + (*glyph_info)[i-1].y_advance += PIXEL(delta.y); } (*glyph_info)[i].x_advance = glyph->metrics.horiAdvance; + (*glyph_info)[i].y_advance = glyph->metrics.vertAdvance; last_index = (*glyph_info)[i].index; (*glyph_info)[i].cluster = ch; } @@ -604,12 +619,13 @@ text_layout(PyObject* string, FontObject* self, const char* dir, PyObject *featu static PyObject* font_getsize(FontObject* self, PyObject* args) { - int i, x, y_max, y_min; + int x_position, x_max, x_min, y_max, y_min; FT_Face face; int xoffset, yoffset; + int horizontal_dir; const char *dir = NULL; const char *lang = NULL; - size_t count; + size_t i, count; GlyphInfo *glyph_info = NULL; PyObject *features = Py_None; @@ -619,18 +635,18 @@ font_getsize(FontObject* self, PyObject* args) if (!PyArg_ParseTuple(args, "O|zOz:getsize", &string, &dir, &features, &lang)) return NULL; - face = NULL; - xoffset = yoffset = 0; - y_max = y_min = 0; - count = text_layout(string, self, dir, features, lang, &glyph_info, 0); if (PyErr_Occurred()) { return NULL; } + face = NULL; + xoffset = yoffset = 0; + x_position = x_max = x_min = y_max = y_min = 0; - for (x = i = 0; i < count; i++) { - int index, error; + horizontal_dir = dir && strcmp(dir, "ttb") == 0 ? 0 : 1; + for (i = 0; i < count; i++) { + int index, error, offset, x_advanced; FT_BBox bbox; FT_Glyph glyph; face = self->face; @@ -642,35 +658,62 @@ font_getsize(FontObject* self, PyObject* args) if (error) return geterror(error); - if (i == 0 && face->glyph->metrics.horiBearingX < 0) { - xoffset = face->glyph->metrics.horiBearingX; - x -= xoffset; - } - - x += glyph_info[i].x_advance; - - if (i == count - 1) - { - int offset; - offset = glyph_info[i].x_advance - - face->glyph->metrics.width - - face->glyph->metrics.horiBearingX; - if (offset < 0) - x -= offset; + if (i == 0) { + if (horizontal_dir) { + if (face->glyph->metrics.horiBearingX < 0) { + xoffset = face->glyph->metrics.horiBearingX; + x_position -= xoffset; + } + } else { + if (face->glyph->metrics.vertBearingY < 0) { + yoffset = face->glyph->metrics.vertBearingY; + y_max -= yoffset; + } + } } FT_Get_Glyph(face->glyph, &glyph); FT_Glyph_Get_CBox(glyph, FT_GLYPH_BBOX_SUBPIXELS, &bbox); - bbox.yMax -= glyph_info[i].y_offset; - bbox.yMin -= glyph_info[i].y_offset; - if (bbox.yMax > y_max) - y_max = bbox.yMax; - if (bbox.yMin < y_min) - y_min = bbox.yMin; + if (horizontal_dir) { + x_position += glyph_info[i].x_advance; - /* find max distance of baseline from top */ - if (face->glyph->metrics.horiBearingY > yoffset) - yoffset = face->glyph->metrics.horiBearingY; + x_advanced = x_position; + offset = glyph_info[i].x_advance - + face->glyph->metrics.width - + face->glyph->metrics.horiBearingX; + if (offset < 0) + x_advanced -= offset; + if (x_advanced > x_max) + x_max = x_advanced; + + bbox.yMax += glyph_info[i].y_offset; + bbox.yMin += glyph_info[i].y_offset; + if (bbox.yMax > y_max) + y_max = bbox.yMax; + if (bbox.yMin < y_min) + y_min = bbox.yMin; + + // find max distance of baseline from top + if (face->glyph->metrics.horiBearingY > yoffset) + yoffset = face->glyph->metrics.horiBearingY; + } else { + y_max -= glyph_info[i].y_advance; + + if (i == count - 1) { + // trim end gap from final glyph + int offset; + offset = -glyph_info[i].y_advance - + face->glyph->metrics.height - + face->glyph->metrics.vertBearingY; + if (offset < 0) + y_max -= offset; + } + + if (bbox.xMax > x_max) + x_max = bbox.xMax; + if (i == 0 || bbox.xMin < x_min) + x_min = bbox.xMin; + } FT_Done_Glyph(glyph); } @@ -681,20 +724,28 @@ font_getsize(FontObject* self, PyObject* args) } if (face) { + if (horizontal_dir) { + // left bearing + if (xoffset < 0) + x_max -= xoffset; + else + xoffset = 0; - /* left bearing */ - if (xoffset < 0) - x -= xoffset; - else - xoffset = 0; - /* difference between the font ascender and the distance of - * the baseline from the top */ - yoffset = PIXEL(self->face->size->metrics.ascender - yoffset); + /* difference between the font ascender and the distance of + * the baseline from the top */ + yoffset = PIXEL(self->face->size->metrics.ascender - yoffset); + } else { + // top bearing + if (yoffset < 0) + y_max -= yoffset; + else + yoffset = 0; + } } return Py_BuildValue( "(ii)(ii)", - PIXEL(x), PIXEL(y_max - y_min), + PIXEL(x_max - x_min), PIXEL(y_max - y_min), PIXEL(xoffset), yoffset ); } @@ -702,9 +753,10 @@ font_getsize(FontObject* self, PyObject* args) static PyObject* font_render(FontObject* self, PyObject* args) { - int i, x, y; + int x; + unsigned int y; Imaging im; - int index, error, ascender; + int index, error, ascender, horizontal_dir; int load_flags; unsigned char *source; FT_GlyphSlot glyph; @@ -715,9 +767,11 @@ font_render(FontObject* self, PyObject* args) int mask = 0; int temp; int xx, x0, x1; + int yy; + unsigned int bitmap_y; const char *dir = NULL; const char *lang = NULL; - size_t count; + size_t i, count; GlyphInfo *glyph_info; PyObject *features = NULL; @@ -748,30 +802,34 @@ font_render(FontObject* self, PyObject* args) return geterror(error); glyph = self->face->glyph; - temp = (glyph->bitmap.rows - glyph->bitmap_top); + temp = glyph->bitmap.rows - glyph->bitmap_top; temp -= PIXEL(glyph_info[i].y_offset); if (temp > ascender) ascender = temp; } - for (x = i = 0; i < count; i++) { - if (i == 0 && self->face->glyph->metrics.horiBearingX < 0) - x = -self->face->glyph->metrics.horiBearingX; - + x = y = 0; + horizontal_dir = dir && strcmp(dir, "ttb") == 0 ? 0 : 1; + for (i = 0; i < count; i++) { index = glyph_info[i].index; error = FT_Load_Glyph(self->face, index, load_flags); if (error) return geterror(error); - if (i == 0 && self->face->glyph->metrics.horiBearingX < 0) { - x = -self->face->glyph->metrics.horiBearingX; - } - glyph = self->face->glyph; + if (horizontal_dir) { + if (i == 0 && self->face->glyph->metrics.horiBearingX < 0) { + x = -self->face->glyph->metrics.horiBearingX; + } + xx = PIXEL(x) + glyph->bitmap_left; + xx += PIXEL(glyph_info[i].x_offset); + } else { + if (self->face->glyph->metrics.vertBearingX < 0) { + x = -self->face->glyph->metrics.vertBearingX; + } + xx = im->xsize / 2 - glyph->bitmap.width / 2; + } - source = (unsigned char*) glyph->bitmap.buffer; - xx = PIXEL(x) + glyph->bitmap_left; - xx += PIXEL(glyph_info[i].x_offset); x0 = 0; x1 = glyph->bitmap.width; if (xx < 0) @@ -779,51 +837,205 @@ font_render(FontObject* self, PyObject* args) if (xx + x1 > im->xsize) x1 = im->xsize - xx; - if (mask) { - /* use monochrome mask (on palette images, etc) */ - for (y = 0; y < glyph->bitmap.rows; y++) { - int yy = y + im->ysize - (PIXEL(glyph->metrics.horiBearingY) + ascender); + source = (unsigned char*) glyph->bitmap.buffer; + for (bitmap_y = 0; bitmap_y < glyph->bitmap.rows; bitmap_y++) { + if (horizontal_dir) { + yy = bitmap_y + im->ysize - (PIXEL(glyph->metrics.horiBearingY) + ascender); yy -= PIXEL(glyph_info[i].y_offset); - if (yy >= 0 && yy < im->ysize) { - /* blend this glyph into the buffer */ - unsigned char *target = im->image8[yy] + xx; - int i, j, m = 128; - for (i = j = 0; j < x1; j++) { - if (j >= x0 && (source[i] & m)) + } else { + yy = bitmap_y + PIXEL(y + glyph->metrics.vertBearingY) + ascender; + yy += PIXEL(glyph_info[i].y_offset); + } + if (yy >= 0 && yy < im->ysize) { + // blend this glyph into the buffer + unsigned char *target = im->image8[yy] + xx; + if (mask) { + // use monochrome mask (on palette images, etc) + int j, k, m = 128; + for (j = k = 0; j < x1; j++) { + if (j >= x0 && (source[k] & m)) target[j] = 255; if (!(m >>= 1)) { m = 128; - i++; + k++; } } - } - source += glyph->bitmap.pitch; - } - } else { - /* use antialiased rendering */ - for (y = 0; y < glyph->bitmap.rows; y++) { - int yy = y + im->ysize - (PIXEL(glyph->metrics.horiBearingY) + ascender); - yy -= PIXEL(glyph_info[i].y_offset); - if (yy >= 0 && yy < im->ysize) { - /* blend this glyph into the buffer */ - - int i; - unsigned char *target = im->image8[yy] + xx; - for (i = x0; i < x1; i++) { - if (target[i] < source[i]) - target[i] = source[i]; + } else { + // use antialiased rendering + int k; + for (k = x0; k < x1; k++) { + if (target[k] < source[k]) + target[k] = source[k]; } } - source += glyph->bitmap.pitch; } + source += glyph->bitmap.pitch; } x += glyph_info[i].x_advance; + y -= glyph_info[i].y_advance; } PyMem_Del(glyph_info); Py_RETURN_NONE; } +#if FREETYPE_MAJOR > 2 ||\ + (FREETYPE_MAJOR == 2 && FREETYPE_MINOR > 9) ||\ + (FREETYPE_MAJOR == 2 && FREETYPE_MINOR == 9 && FREETYPE_PATCH == 1) + static PyObject* + font_getvarnames(FontObject* self, PyObject* args) + { + int error; + FT_UInt i, j, num_namedstyles, name_count; + FT_MM_Var *master; + FT_SfntName name; + PyObject *list_names, *list_name; + + error = FT_Get_MM_Var(self->face, &master); + if (error) + return geterror(error); + + num_namedstyles = master->num_namedstyles; + list_names = PyList_New(num_namedstyles); + + name_count = FT_Get_Sfnt_Name_Count(self->face); + for (i = 0; i < name_count; i++) { + error = FT_Get_Sfnt_Name(self->face, i, &name); + if (error) + return geterror(error); + + for (j = 0; j < num_namedstyles; j++) { + if (PyList_GetItem(list_names, j) != NULL) + continue; + + if (master->namedstyle[j].strid == name.name_id) { + list_name = Py_BuildValue(PY_ARG_BYTES_LENGTH, + name.string, name.string_len); + PyList_SetItem(list_names, j, list_name); + break; + } + } + } + + FT_Done_MM_Var(library, master); + + return list_names; + } + + static PyObject* + font_getvaraxes(FontObject* self, PyObject* args) + { + int error; + FT_UInt i, j, num_axis, name_count; + FT_MM_Var* master; + FT_Var_Axis axis; + FT_SfntName name; + PyObject *list_axes, *list_axis, *axis_name; + error = FT_Get_MM_Var(self->face, &master); + if (error) + return geterror(error); + + num_axis = master->num_axis; + name_count = FT_Get_Sfnt_Name_Count(self->face); + + list_axes = PyList_New(num_axis); + for (i = 0; i < num_axis; i++) { + axis = master->axis[i]; + + list_axis = PyDict_New(); + PyDict_SetItemString(list_axis, "minimum", + PyInt_FromLong(axis.minimum / 65536)); + PyDict_SetItemString(list_axis, "default", + PyInt_FromLong(axis.def / 65536)); + PyDict_SetItemString(list_axis, "maximum", + PyInt_FromLong(axis.maximum / 65536)); + + for (j = 0; j < name_count; j++) { + error = FT_Get_Sfnt_Name(self->face, j, &name); + if (error) + return geterror(error); + + if (name.name_id == axis.strid) { + axis_name = Py_BuildValue(PY_ARG_BYTES_LENGTH, + name.string, name.string_len); + PyDict_SetItemString(list_axis, "name", axis_name); + break; + } + } + + PyList_SetItem(list_axes, i, list_axis); + } + + FT_Done_MM_Var(library, master); + + return list_axes; + } + + static PyObject* + font_setvarname(FontObject* self, PyObject* args) + { + int error; + + int instance_index; + if (!PyArg_ParseTuple(args, "i", &instance_index)) + return NULL; + + error = FT_Set_Named_Instance(self->face, instance_index); + if (error) + return geterror(error); + + Py_INCREF(Py_None); + return Py_None; + } + + static PyObject* + font_setvaraxes(FontObject* self, PyObject* args) + { + int error; + + PyObject *axes, *item; + Py_ssize_t i, num_coords; + FT_Fixed *coords; + FT_Fixed coord; + if (!PyArg_ParseTuple(args, "O", &axes)) + return NULL; + + if (!PyList_Check(axes)) { + PyErr_SetString(PyExc_TypeError, "argument must be a list"); + return NULL; + } + + num_coords = PyObject_Length(axes); + coords = malloc(2 * sizeof(coords)); + if (coords == NULL) { + return PyErr_NoMemory(); + } + for (i = 0; i < num_coords; i++) { + item = PyList_GET_ITEM(axes, i); + if (PyFloat_Check(item)) + coord = PyFloat_AS_DOUBLE(item); + else if (PyInt_Check(item)) + coord = (float) PyInt_AS_LONG(item); + else if (PyNumber_Check(item)) + coord = PyFloat_AsDouble(item); + else { + free(coords); + PyErr_SetString(PyExc_TypeError, "list must contain numbers"); + return NULL; + } + coords[i] = coord * 65536; + } + + error = FT_Set_Var_Design_Coordinates(self->face, num_coords, coords); + free(coords); + if (error) + return geterror(error); + + Py_INCREF(Py_None); + return Py_None; + } +#endif + static void font_dealloc(FontObject* self) { @@ -839,6 +1051,14 @@ font_dealloc(FontObject* self) static PyMethodDef font_methods[] = { {"render", (PyCFunction) font_render, METH_VARARGS}, {"getsize", (PyCFunction) font_getsize, METH_VARARGS}, +#if FREETYPE_MAJOR > 2 ||\ + (FREETYPE_MAJOR == 2 && FREETYPE_MINOR > 9) ||\ + (FREETYPE_MAJOR == 2 && FREETYPE_MINOR == 9 && FREETYPE_PATCH == 1) + {"getvarnames", (PyCFunction) font_getvarnames, METH_VARARGS }, + {"getvaraxes", (PyCFunction) font_getvaraxes, METH_VARARGS }, + {"setvarname", (PyCFunction) font_setvarname, METH_VARARGS}, + {"setvaraxes", (PyCFunction) font_setvaraxes, METH_VARARGS}, +#endif {NULL, NULL} }; diff --git a/src/decode.c b/src/decode.c index 562bc7436..79133f48f 100644 --- a/src/decode.c +++ b/src/decode.c @@ -48,7 +48,7 @@ typedef struct { PyObject_HEAD int (*decode)(Imaging im, ImagingCodecState state, - UINT8* buffer, int bytes); + UINT8* buffer, Py_ssize_t bytes); int (*cleanup)(ImagingCodecState state); struct ImagingCodecStateInstance state; Imaging im; @@ -503,9 +503,9 @@ PyImaging_LibTiffDecoderNew(PyObject* self, PyObject* args) char* rawmode; char* compname; int fp; - int ifdoffset; + uint32 ifdoffset; - if (! PyArg_ParseTuple(args, "sssii", &mode, &rawmode, &compname, &fp, &ifdoffset)) + if (! PyArg_ParseTuple(args, "sssiI", &mode, &rawmode, &compname, &fp, &ifdoffset)) return NULL; TRACE(("new tiff decoder %s\n", compname)); diff --git a/src/display.c b/src/display.c index 49143b72e..ab005d4b4 100644 --- a/src/display.c +++ b/src/display.c @@ -323,11 +323,16 @@ PyObject* PyImaging_GrabScreenWin32(PyObject* self, PyObject* args) { int width, height; + int includeLayeredWindows = 0; HBITMAP bitmap; BITMAPCOREHEADER core; HDC screen, screen_copy; + DWORD rop; PyObject* buffer; + if (!PyArg_ParseTuple(args, "|i", &includeLayeredWindows)) + return NULL; + /* step 1: create a memory DC large enough to hold the entire screen */ @@ -346,7 +351,10 @@ PyImaging_GrabScreenWin32(PyObject* self, PyObject* args) /* step 2: copy bits into memory DC bitmap */ - if (!BitBlt(screen_copy, 0, 0, width, height, screen, 0, 0, SRCCOPY)) + rop = SRCCOPY; + if (includeLayeredWindows) + rop |= CAPTUREBLT; + if (!BitBlt(screen_copy, 0, 0, width, height, screen, 0, 0, rop)) goto error; /* step 3: extract bits from bitmap */ diff --git a/src/encode.c b/src/encode.c index 6832f90c8..40fbd4595 100644 --- a/src/encode.c +++ b/src/encode.c @@ -126,7 +126,7 @@ _encode(ImagingEncoderObject* encoder, PyObject* args) Py_ssize_t bufsize = 16384; - if (!PyArg_ParseTuple(args, "|i", &bufsize)) + if (!PyArg_ParseTuple(args, "|n", &bufsize)) return NULL; buf = PyBytes_FromStringAndSize(NULL, bufsize); @@ -180,7 +180,7 @@ _encode_to_file(ImagingEncoderObject* encoder, PyObject* args) Py_ssize_t fh; Py_ssize_t bufsize = 16384; - if (!PyArg_ParseTuple(args, "i|i", &fh, &bufsize)) + if (!PyArg_ParseTuple(args, "n|n", &fh, &bufsize)) return NULL; /* Allocate an encoder buffer */ @@ -229,7 +229,7 @@ _setimage(ImagingEncoderObject* encoder, PyObject* args) x0 = y0 = x1 = y1 = 0; /* FIXME: should publish the ImagingType descriptor */ - if (!PyArg_ParseTuple(args, "O|(iiii)", &op, &x0, &y0, &x1, &y1)) + if (!PyArg_ParseTuple(args, "O|(nnnn)", &op, &x0, &y0, &x1, &y1)) return NULL; im = PyImaging_AsImaging(op); if (!im) @@ -409,7 +409,7 @@ PyImaging_GifEncoderNew(PyObject* self, PyObject* args) char *rawmode; Py_ssize_t bits = 8; Py_ssize_t interlace = 0; - if (!PyArg_ParseTuple(args, "ss|ii", &mode, &rawmode, &bits, &interlace)) + if (!PyArg_ParseTuple(args, "ss|nn", &mode, &rawmode, &bits, &interlace)) return NULL; encoder = PyImaging_EncoderNew(sizeof(GIFENCODERSTATE)); @@ -441,7 +441,7 @@ PyImaging_PcxEncoderNew(PyObject* self, PyObject* args) char *rawmode; Py_ssize_t bits = 8; - if (!PyArg_ParseTuple(args, "ss|ii", &mode, &rawmode, &bits)) { + if (!PyArg_ParseTuple(args, "ss|n", &mode, &rawmode, &bits)) { return NULL; } @@ -474,7 +474,7 @@ PyImaging_RawEncoderNew(PyObject* self, PyObject* args) Py_ssize_t stride = 0; Py_ssize_t ystep = 1; - if (!PyArg_ParseTuple(args, "ss|ii", &mode, &rawmode, &stride, &ystep)) + if (!PyArg_ParseTuple(args, "ss|nn", &mode, &rawmode, &stride, &ystep)) return NULL; encoder = PyImaging_EncoderNew(0); @@ -506,7 +506,7 @@ PyImaging_TgaRleEncoderNew(PyObject* self, PyObject* args) char *rawmode; Py_ssize_t ystep = 1; - if (!PyArg_ParseTuple(args, "ss|i", &mode, &rawmode, &ystep)) + if (!PyArg_ParseTuple(args, "ss|n", &mode, &rawmode, &ystep)) return NULL; encoder = PyImaging_EncoderNew(0); @@ -567,7 +567,7 @@ PyImaging_ZipEncoderNew(PyObject* self, PyObject* args) Py_ssize_t compress_type = -1; char* dictionary = NULL; Py_ssize_t dictionary_size = 0; - if (!PyArg_ParseTuple(args, "ss|iii"PY_ARG_BYTES_LENGTH, &mode, &rawmode, + if (!PyArg_ParseTuple(args, "ss|nnn"PY_ARG_BYTES_LENGTH, &mode, &rawmode, &optimize, &compress_level, &compress_type, &dictionary, &dictionary_size)) @@ -717,7 +717,7 @@ PyImaging_JpegEncoderNew(PyObject* self, PyObject* args) char* rawExif = NULL; Py_ssize_t rawExifLen = 0; - if (!PyArg_ParseTuple(args, "ss|iiiiiiiiO"PY_ARG_BYTES_LENGTH""PY_ARG_BYTES_LENGTH, + if (!PyArg_ParseTuple(args, "ss|nnnnnnnnO"PY_ARG_BYTES_LENGTH""PY_ARG_BYTES_LENGTH, &mode, &rawmode, &quality, &progressive, &smooth, &optimize, &streamtype, &xdpi, &ydpi, &subsampling, &qtables, &extra, &extra_size, @@ -823,7 +823,7 @@ PyImaging_LibTiffEncoderNew(PyObject* self, PyObject* args) PyObject *keys, *values; - if (! PyArg_ParseTuple(args, "sssisO", &mode, &rawmode, &compname, &fp, &filename, &dir)) { + if (! PyArg_ParseTuple(args, "sssnsO", &mode, &rawmode, &compname, &fp, &filename, &dir)) { return NULL; } @@ -995,7 +995,7 @@ PyImaging_Jpeg2KEncoderNew(PyObject *self, PyObject *args) OPJ_CINEMA_MODE cine_mode; Py_ssize_t fd = -1; - if (!PyArg_ParseTuple(args, "ss|OOOsOIOOOssi", &mode, &format, + if (!PyArg_ParseTuple(args, "ss|OOOsOnOOOssn", &mode, &format, &offset, &tile_offset, &tile_size, &quality_mode, &quality_layers, &num_resolutions, &cblk_size, &precinct_size, diff --git a/src/libImaging/BcnDecode.c b/src/libImaging/BcnDecode.c index 1472b0296..c2c4f21e7 100644 --- a/src/libImaging/BcnDecode.c +++ b/src/libImaging/BcnDecode.c @@ -844,7 +844,7 @@ static int decode_bcn(Imaging im, ImagingCodecState state, const UINT8* src, int return (int)(ptr - src); } -int ImagingBcnDecode(Imaging im, ImagingCodecState state, UINT8* buf, int bytes) { +int ImagingBcnDecode(Imaging im, ImagingCodecState state, UINT8* buf, Py_ssize_t bytes) { int N = state->state & 0xf; int width = state->xsize; int height = state->ysize; diff --git a/src/libImaging/BitDecode.c b/src/libImaging/BitDecode.c index a78183542..7120b3321 100644 --- a/src/libImaging/BitDecode.c +++ b/src/libImaging/BitDecode.c @@ -20,7 +20,7 @@ int -ImagingBitDecode(Imaging im, ImagingCodecState state, UINT8* buf, int bytes) +ImagingBitDecode(Imaging im, ImagingCodecState state, UINT8* buf, Py_ssize_t bytes) { BITSTATE* bitstate = state->context; UINT8* ptr; diff --git a/src/libImaging/BoxBlur.c b/src/libImaging/BoxBlur.c index 1a415ed16..9537c4f98 100644 --- a/src/libImaging/BoxBlur.c +++ b/src/libImaging/BoxBlur.c @@ -1,4 +1,3 @@ -#include "Python.h" #include "Imaging.h" diff --git a/src/libImaging/Draw.c b/src/libImaging/Draw.c index d0f374fe1..6bcd1d2b3 100644 --- a/src/libImaging/Draw.c +++ b/src/libImaging/Draw.c @@ -765,115 +765,150 @@ ellipse(Imaging im, int x0, int y0, int x1, int y1, int width, int mode, int op) { float i; - int j; + int inner; int n; - int cx, cy; + int maxEdgeCount; int w, h; - int x = 0, y = 0; + int x, y; + int cx, cy; int lx = 0, ly = 0; int sx = 0, sy = 0; + int lx_inner = 0, ly_inner = 0; + int sx_inner = 0, sy_inner = 0; DRAW* draw; INT32 ink; + Edge* e; DRAWINIT(); - if (width == 0) { - width = 1; + while (end < start) + end += 360; + + if (end - start > 360) { + // no need to go in loops + end = start + 361; } - for (j = 0; j < width; j++) { + w = x1 - x0; + h = y1 - y0; + if (w <= 0 || h <= 0) + return 0; - w = x1 - x0; - h = y1 - y0; - if (w < 0 || h < 0) + cx = (x0 + x1) / 2; + cy = (y0 + y1) / 2; + + if (!fill && width <= 1) { + for (i = start; i < end+1; i++) { + if (i > end) { + i = end; + } + ellipsePoint(cx, cy, w, h, i, &x, &y); + if (i != start) + draw->line(im, lx, ly, x, y, ink); + else + sx = x, sy = y; + lx = x, ly = y; + } + + if (i != start) { + if (mode == PIESLICE) { + if (x != cx || y != cy) { + draw->line(im, x, y, cx, cy, ink); + draw->line(im, cx, cy, sx, sy, ink); + } + } else if (mode == CHORD) { + if (x != sx || y != sy) + draw->line(im, x, y, sx, sy, ink); + } + } + } else { + inner = (mode == ARC || !fill) ? 1 : 0; + + // Build edge list + // malloc check UNDONE, FLOAT? + maxEdgeCount = end - start; + if (inner) { + maxEdgeCount *= 2; + } + maxEdgeCount += 3; + e = calloc(maxEdgeCount, sizeof(Edge)); + if (!e) { + ImagingError_MemoryError(); + return -1; + } + + // Outer circle + n = 0; + for (i = start; i < end+1; i++) { + if (i > end) { + i = end; + } + ellipsePoint(cx, cy, w, h, i, &x, &y); + if (i == start) { + sx = x, sy = y; + } else { + add_edge(&e[n++], lx, ly, x, y); + } + lx = x, ly = y; + } + if (n == 0) return 0; - cx = (x0 + x1) / 2; - cy = (y0 + y1) / 2; + if (inner) { + // Inner circle + x0 += width - 1; + y0 += width - 1; + x1 -= width - 1; + y1 -= width - 1; - while (end < start) - end += 360; - - if (end - start > 360) { - /* no need to go in loops */ - end = start + 361; - } - - if (mode != ARC && fill) { - - /* Build edge list */ - /* malloc check UNDONE, FLOAT? */ - Edge* e = calloc((end - start + 3), sizeof(Edge)); - if (!e) { - ImagingError_MemoryError(); - return -1; - } - n = 0; - - for (i = start; i < end+1; i++) { - if (i > end) { - i = end; - } - ellipsePoint(cx, cy, w, h, i, &x, &y); - if (i != start) - add_edge(&e[n++], lx, ly, x, y); - else - sx = x, sy = y; - lx = x, ly = y; - } - - if (n > 0) { - /* close and draw polygon */ - if (mode == PIESLICE) { - if (x != cx || y != cy) { - add_edge(&e[n++], x, y, cx, cy); - add_edge(&e[n++], cx, cy, sx, sy); + w = x1 - x0; + h = y1 - y0; + if (w <= 0 || h <= 0) { + // ARC with no gap in the middle is a PIESLICE + mode = PIESLICE; + inner = 0; + } else { + for (i = start; i < end+1; i++) { + if (i > end) { + i = end; } - } else { - if (x != sx || y != sy) - add_edge(&e[n++], x, y, sx, sy); - } - draw->polygon(im, n, e, ink, 0); - } - - free(e); - - } else { - - for (i = start; i < end+1; i++) { - if (i > end) { - i = end; - } - ellipsePoint(cx, cy, w, h, i, &x, &y); - if (i != start) - draw->line(im, lx, ly, x, y, ink); - else - sx = x, sy = y; - lx = x, ly = y; - } - - if (i != start) { - if (mode == PIESLICE) { - if (j == 0 && (x != cx || y != cy)) { - if (width == 1) { - draw->line(im, x, y, cx, cy, ink); - draw->line(im, cx, cy, sx, sy, ink); - } else { - ImagingDrawWideLine(im, x, y, cx, cy, &ink, width, op); - ImagingDrawWideLine(im, cx, cy, sx, sy, &ink, width, op); - } - } - } else if (mode == CHORD) { - if (x != sx || y != sy) - draw->line(im, x, y, sx, sy, ink); + ellipsePoint(cx, cy, w, h, i, &x, &y); + if (i == start) + sx_inner = x, sy_inner = y; + else + add_edge(&e[n++], lx_inner, ly_inner, x, y); + lx_inner = x, ly_inner = y; } } } - x0++; - y0++; - x1--; - y1--; + + if (end - start < 360) { + // Close polygon + if (mode == PIESLICE) { + if (x != cx || y != cy) { + add_edge(&e[n++], sx, sy, cx, cy); + add_edge(&e[n++], cx, cy, lx, ly); + if (inner) { + ImagingDrawWideLine(im, sx, sy, cx, cy, &ink, width, op); + ImagingDrawWideLine(im, cx, cy, lx, ly, &ink, width, op); + } + } + } else if (mode == CHORD) { + add_edge(&e[n++], sx, sy, lx, ly); + if (inner) { + add_edge(&e[n++], sx_inner, sy_inner, lx_inner, ly_inner); + } + } else if (mode == ARC) { + add_edge(&e[n++], sx, sy, sx_inner, sy_inner); + add_edge(&e[n++], lx, ly, lx_inner, ly_inner); + } + } + + draw->polygon(im, n, e, ink, 0); + + free(e); } + return 0; } diff --git a/src/libImaging/FliDecode.c b/src/libImaging/FliDecode.c index 6d22c6c4e..e21259900 100644 --- a/src/libImaging/FliDecode.c +++ b/src/libImaging/FliDecode.c @@ -26,7 +26,7 @@ int -ImagingFliDecode(Imaging im, ImagingCodecState state, UINT8* buf, int bytes) +ImagingFliDecode(Imaging im, ImagingCodecState state, UINT8* buf, Py_ssize_t bytes) { UINT8* ptr; int framesize; diff --git a/src/libImaging/GifDecode.c b/src/libImaging/GifDecode.c index 58cf54400..68429ab26 100644 --- a/src/libImaging/GifDecode.c +++ b/src/libImaging/GifDecode.c @@ -58,7 +58,7 @@ int -ImagingGifDecode(Imaging im, ImagingCodecState state, UINT8* buffer, int bytes) +ImagingGifDecode(Imaging im, ImagingCodecState state, UINT8* buffer, Py_ssize_t bytes) { UINT8* p; UINT8* out; diff --git a/src/libImaging/HexDecode.c b/src/libImaging/HexDecode.c index 14f5241dc..8bd9bf67f 100644 --- a/src/libImaging/HexDecode.c +++ b/src/libImaging/HexDecode.c @@ -21,7 +21,7 @@ (v >= 'A' && v <= 'F') ? v - 'A' + 10 : -1) int -ImagingHexDecode(Imaging im, ImagingCodecState state, UINT8* buf, int bytes) +ImagingHexDecode(Imaging im, ImagingCodecState state, UINT8* buf, Py_ssize_t bytes) { UINT8* ptr; int a, b; diff --git a/src/libImaging/Imaging.h b/src/libImaging/Imaging.h index e705e0a60..25c15e758 100644 --- a/src/libImaging/Imaging.h +++ b/src/libImaging/Imaging.h @@ -413,22 +413,22 @@ typedef int (*ImagingCodec)(Imaging im, ImagingCodecState state, UINT8* buffer, int bytes); extern int ImagingBcnDecode(Imaging im, ImagingCodecState state, - UINT8* buffer, int bytes); + UINT8* buffer, Py_ssize_t bytes); extern int ImagingBitDecode(Imaging im, ImagingCodecState state, - UINT8* buffer, int bytes); + UINT8* buffer, Py_ssize_t bytes); extern int ImagingEpsEncode(Imaging im, ImagingCodecState state, UINT8* buffer, int bytes); extern int ImagingFliDecode(Imaging im, ImagingCodecState state, - UINT8* buffer, int bytes); + UINT8* buffer, Py_ssize_t bytes); extern int ImagingGifDecode(Imaging im, ImagingCodecState state, - UINT8* buffer, int bytes); + UINT8* buffer, Py_ssize_t bytes); extern int ImagingGifEncode(Imaging im, ImagingCodecState state, UINT8* buffer, int bytes); extern int ImagingHexDecode(Imaging im, ImagingCodecState state, - UINT8* buffer, int bytes); + UINT8* buffer, Py_ssize_t bytes); #ifdef HAVE_LIBJPEG extern int ImagingJpegDecode(Imaging im, ImagingCodecState state, - UINT8* buffer, int bytes); + UINT8* buffer, Py_ssize_t bytes); extern int ImagingJpegDecodeCleanup(ImagingCodecState state); extern int ImagingJpegUseJCSExtensions(void); @@ -437,7 +437,7 @@ extern int ImagingJpegEncode(Imaging im, ImagingCodecState state, #endif #ifdef HAVE_OPENJPEG extern int ImagingJpeg2KDecode(Imaging im, ImagingCodecState state, - UINT8* buffer, int bytes); + UINT8* buffer, Py_ssize_t bytes); extern int ImagingJpeg2KDecodeCleanup(ImagingCodecState state); extern int ImagingJpeg2KEncode(Imaging im, ImagingCodecState state, UINT8* buffer, int bytes); @@ -445,44 +445,44 @@ extern int ImagingJpeg2KEncodeCleanup(ImagingCodecState state); #endif #ifdef HAVE_LIBTIFF extern int ImagingLibTiffDecode(Imaging im, ImagingCodecState state, - UINT8* buffer, int bytes); + UINT8* buffer, Py_ssize_t bytes); extern int ImagingLibTiffEncode(Imaging im, ImagingCodecState state, UINT8* buffer, int bytes); #endif #ifdef HAVE_LIBMPEG extern int ImagingMpegDecode(Imaging im, ImagingCodecState state, - UINT8* buffer, int bytes); + UINT8* buffer, Py_ssize_t bytes); #endif extern int ImagingMspDecode(Imaging im, ImagingCodecState state, - UINT8* buffer, int bytes); + UINT8* buffer, Py_ssize_t bytes); extern int ImagingPackbitsDecode(Imaging im, ImagingCodecState state, - UINT8* buffer, int bytes); + UINT8* buffer, Py_ssize_t bytes); extern int ImagingPcdDecode(Imaging im, ImagingCodecState state, - UINT8* buffer, int bytes); + UINT8* buffer, Py_ssize_t bytes); extern int ImagingPcxDecode(Imaging im, ImagingCodecState state, - UINT8* buffer, int bytes); + UINT8* buffer, Py_ssize_t bytes); extern int ImagingPcxEncode(Imaging im, ImagingCodecState state, UINT8* buffer, int bytes); extern int ImagingRawDecode(Imaging im, ImagingCodecState state, - UINT8* buffer, int bytes); + UINT8* buffer, Py_ssize_t bytes); extern int ImagingRawEncode(Imaging im, ImagingCodecState state, UINT8* buffer, int bytes); extern int ImagingSgiRleDecode(Imaging im, ImagingCodecState state, - UINT8* buffer, int bytes); + UINT8* buffer, Py_ssize_t bytes); extern int ImagingSgiRleDecodeCleanup(ImagingCodecState state); extern int ImagingSunRleDecode(Imaging im, ImagingCodecState state, - UINT8* buffer, int bytes); + UINT8* buffer, Py_ssize_t bytes); extern int ImagingTgaRleDecode(Imaging im, ImagingCodecState state, - UINT8* buffer, int bytes); + UINT8* buffer, Py_ssize_t bytes); extern int ImagingTgaRleEncode(Imaging im, ImagingCodecState state, UINT8* buffer, int bytes); extern int ImagingXbmDecode(Imaging im, ImagingCodecState state, - UINT8* buffer, int bytes); + UINT8* buffer, Py_ssize_t bytes); extern int ImagingXbmEncode(Imaging im, ImagingCodecState state, UINT8* buffer, int bytes); #ifdef HAVE_LIBZ extern int ImagingZipDecode(Imaging im, ImagingCodecState state, - UINT8* buffer, int bytes); + UINT8* buffer, Py_ssize_t bytes); extern int ImagingZipDecodeCleanup(ImagingCodecState state); extern int ImagingZipEncode(Imaging im, ImagingCodecState state, UINT8* buffer, int bytes); diff --git a/src/libImaging/Jpeg2KDecode.c b/src/libImaging/Jpeg2KDecode.c index 9140e0074..f2e437dda 100644 --- a/src/libImaging/Jpeg2KDecode.c +++ b/src/libImaging/Jpeg2KDecode.c @@ -769,7 +769,7 @@ j2k_decode_entry(Imaging im, ImagingCodecState state) } int -ImagingJpeg2KDecode(Imaging im, ImagingCodecState state, UINT8* buf, int bytes) +ImagingJpeg2KDecode(Imaging im, ImagingCodecState state, UINT8* buf, Py_ssize_t bytes) { if (bytes){ diff --git a/src/libImaging/Jpeg2KEncode.c b/src/libImaging/Jpeg2KEncode.c index 92e55e190..aef1a4b94 100644 --- a/src/libImaging/Jpeg2KEncode.c +++ b/src/libImaging/Jpeg2KEncode.c @@ -194,7 +194,7 @@ static void j2k_set_cinema_params(Imaging im, int components, opj_cparameters_t *params) { float rate; - unsigned n; + int n; /* These settings have been copied from opj_compress in the OpenJPEG sources. */ @@ -522,7 +522,7 @@ j2k_encode_entry(Imaging im, ImagingCodecState state) tile_ndx = 0; for (y = 0; y < tiles_y; ++y) { - unsigned ty0 = params.cp_ty0 + y * tile_height; + int ty0 = params.cp_ty0 + y * tile_height; unsigned ty1 = ty0 + tile_height; unsigned pixy, pixh; @@ -535,7 +535,7 @@ j2k_encode_entry(Imaging im, ImagingCodecState state) pixh = ty1 - ty0; for (x = 0; x < tiles_x; ++x) { - unsigned tx0 = params.cp_tx0 + x * tile_width; + int tx0 = params.cp_tx0 + x * tile_width; unsigned tx1 = tx0 + tile_width; unsigned pixx, pixw; unsigned data_size; diff --git a/src/libImaging/JpegDecode.c b/src/libImaging/JpegDecode.c index 33cc5d095..39d96de53 100644 --- a/src/libImaging/JpegDecode.c +++ b/src/libImaging/JpegDecode.c @@ -144,7 +144,7 @@ output(j_common_ptr cinfo) /* -------------------------------------------------------------------- */ int -ImagingJpegDecode(Imaging im, ImagingCodecState state, UINT8* buf, int bytes) +ImagingJpegDecode(Imaging im, ImagingCodecState state, UINT8* buf, Py_ssize_t bytes) { JPEGSTATE* context = (JPEGSTATE*) state->context; int ok; diff --git a/src/libImaging/Pack.c b/src/libImaging/Pack.c index 56aebf58e..000ee384a 100644 --- a/src/libImaging/Pack.c +++ b/src/libImaging/Pack.c @@ -251,6 +251,15 @@ ImagingPackRGB(UINT8* out, const UINT8* in, int pixels) { int i = 0; /* RGB triplets */ +#ifdef __sparc + /* SPARC CPUs cannot read integers from nonaligned addresses. */ + for (; i < pixels; i++) { + out[0] = in[R]; + out[1] = in[G]; + out[2] = in[B]; + out += 3; in += 4; + } +#else for (; i < pixels-1; i++) { ((UINT32*)out)[0] = ((UINT32*)in)[i]; out += 3; @@ -261,6 +270,7 @@ ImagingPackRGB(UINT8* out, const UINT8* in, int pixels) out[2] = in[i*4+B]; out += 3; } +#endif } void diff --git a/src/libImaging/PackDecode.c b/src/libImaging/PackDecode.c index aea8f04e3..ef54f3c9a 100644 --- a/src/libImaging/PackDecode.c +++ b/src/libImaging/PackDecode.c @@ -18,7 +18,7 @@ int ImagingPackbitsDecode(Imaging im, ImagingCodecState state, - UINT8* buf, int bytes) + UINT8* buf, Py_ssize_t bytes) { UINT8 n; UINT8* ptr; diff --git a/src/libImaging/PcdDecode.c b/src/libImaging/PcdDecode.c index f92343890..8ff264edf 100644 --- a/src/libImaging/PcdDecode.c +++ b/src/libImaging/PcdDecode.c @@ -24,7 +24,7 @@ int -ImagingPcdDecode(Imaging im, ImagingCodecState state, UINT8* buf, int bytes) +ImagingPcdDecode(Imaging im, ImagingCodecState state, UINT8* buf, Py_ssize_t bytes) { int x; int chunk; diff --git a/src/libImaging/PcxDecode.c b/src/libImaging/PcxDecode.c index e5417f1bd..4a5931bee 100644 --- a/src/libImaging/PcxDecode.c +++ b/src/libImaging/PcxDecode.c @@ -17,7 +17,7 @@ #include "Imaging.h" int -ImagingPcxDecode(Imaging im, ImagingCodecState state, UINT8* buf, int bytes) +ImagingPcxDecode(Imaging im, ImagingCodecState state, UINT8* buf, Py_ssize_t bytes) { UINT8 n; UINT8* ptr; diff --git a/src/libImaging/RawDecode.c b/src/libImaging/RawDecode.c index 40c0cb79a..774d4245b 100644 --- a/src/libImaging/RawDecode.c +++ b/src/libImaging/RawDecode.c @@ -20,7 +20,7 @@ int -ImagingRawDecode(Imaging im, ImagingCodecState state, UINT8* buf, int bytes) +ImagingRawDecode(Imaging im, ImagingCodecState state, UINT8* buf, Py_ssize_t bytes) { enum { LINE = 1, SKIP }; RAWSTATE* rawstate = state->context; diff --git a/src/libImaging/SgiRleDecode.c b/src/libImaging/SgiRleDecode.c index 9d8e56376..f87c473e3 100644 --- a/src/libImaging/SgiRleDecode.c +++ b/src/libImaging/SgiRleDecode.c @@ -90,7 +90,7 @@ static int expandrow2(UINT16* dest, UINT16* src, int n, int z) int ImagingSgiRleDecode(Imaging im, ImagingCodecState state, - UINT8* buf, int bytes) + UINT8* buf, Py_ssize_t bytes) { UINT8 *ptr; SGISTATE *c; diff --git a/src/libImaging/Storage.c b/src/libImaging/Storage.c index 7e0c14339..389089e11 100644 --- a/src/libImaging/Storage.c +++ b/src/libImaging/Storage.c @@ -405,7 +405,7 @@ ImagingAllocateArray(Imaging im, int dirty, int block_size) // printf("NEW size: %dx%d, ls: %d, lpb: %d, blocks: %d\n", // im->xsize, im->ysize, aligned_linesize, lines_per_block, blocks_count); - /* One extra ponter is always NULL */ + /* One extra pointer is always NULL */ im->blocks = calloc(sizeof(*im->blocks), blocks_count + 1); if ( ! im->blocks) { return (Imaging) ImagingError_MemoryError(); diff --git a/src/libImaging/SunRleDecode.c b/src/libImaging/SunRleDecode.c index 50d816e38..e627c2c9a 100644 --- a/src/libImaging/SunRleDecode.c +++ b/src/libImaging/SunRleDecode.c @@ -20,7 +20,7 @@ int -ImagingSunRleDecode(Imaging im, ImagingCodecState state, UINT8* buf, int bytes) +ImagingSunRleDecode(Imaging im, ImagingCodecState state, UINT8* buf, Py_ssize_t bytes) { int n; UINT8* ptr; diff --git a/src/libImaging/TgaRleDecode.c b/src/libImaging/TgaRleDecode.c index cad6bc3bc..d1971e546 100644 --- a/src/libImaging/TgaRleDecode.c +++ b/src/libImaging/TgaRleDecode.c @@ -20,7 +20,7 @@ int ImagingTgaRleDecode(Imaging im, ImagingCodecState state, - UINT8* buf, int bytes) + UINT8* buf, Py_ssize_t bytes) { int n, depth; UINT8* ptr; diff --git a/src/libImaging/TiffDecode.c b/src/libImaging/TiffDecode.c index 381f795e0..50bc2bfc2 100644 --- a/src/libImaging/TiffDecode.c +++ b/src/libImaging/TiffDecode.c @@ -147,7 +147,7 @@ void _tiffUnmapProc(thandle_t hdata, tdata_t base, toff_t size) { (void) hdata; (void) base; (void) size; } -int ImagingLibTiffInit(ImagingCodecState state, int fp, int offset) { +int ImagingLibTiffInit(ImagingCodecState state, int fp, uint32 offset) { TIFFSTATE *clientstate = (TIFFSTATE *)state->context; TRACE(("initing libtiff\n")); @@ -194,6 +194,9 @@ int ReadTile(TIFF* tiff, UINT32 col, UINT32 row, UINT32* buffer) { } swap_line = (UINT32*)malloc(swap_line_size); + if (swap_line == NULL) { + return -1; + } /* * For some reason the TIFFReadRGBATile() function chooses the * lower left corner as the origin. Vertically mirror scanlines. @@ -275,7 +278,7 @@ int ReadStrip(TIFF* tiff, UINT32 row, UINT32* buffer) { return 0; } -int ImagingLibTiffDecode(Imaging im, ImagingCodecState state, UINT8* buffer, int bytes) { +int ImagingLibTiffDecode(Imaging im, ImagingCodecState state, UINT8* buffer, Py_ssize_t bytes) { TIFFSTATE *clientstate = (TIFFSTATE *)state->context; char *filename = "tempfile.tif"; char *mode = "r"; diff --git a/src/libImaging/TiffDecode.h b/src/libImaging/TiffDecode.h index e29a6c88f..1c1337715 100644 --- a/src/libImaging/TiffDecode.h +++ b/src/libImaging/TiffDecode.h @@ -43,7 +43,7 @@ typedef struct { -extern int ImagingLibTiffInit(ImagingCodecState state, int fp, int offset); +extern int ImagingLibTiffInit(ImagingCodecState state, int fp, uint32 offset); extern int ImagingLibTiffEncodeInit(ImagingCodecState state, char *filename, int fp); extern int ImagingLibTiffMergeFieldInfo(ImagingCodecState state, TIFFDataType field_type, int key); extern int ImagingLibTiffSetField(ImagingCodecState state, ttag_t tag, ...); diff --git a/src/libImaging/Unpack.c b/src/libImaging/Unpack.c index ccfeabaf9..c1e8f25d0 100644 --- a/src/libImaging/Unpack.c +++ b/src/libImaging/Unpack.c @@ -480,6 +480,16 @@ void ImagingUnpackRGB(UINT8* _out, const UINT8* in, int pixels) { int i = 0; +#ifdef __sparc + /* SPARC CPUs cannot read integers from nonaligned addresses. */ + for (; i < pixels; i++) { + _out[R] = in[0]; + _out[G] = in[1]; + _out[B] = in[2]; + _out[A] = 255; + _out += 4; in += 3; + } +#else UINT32* out = (UINT32*) _out; /* RGB triplets */ for (; i < pixels-1; i++) { @@ -490,6 +500,7 @@ ImagingUnpackRGB(UINT8* _out, const UINT8* in, int pixels) out[i] = MAKE_UINT32(in[0], in[1], in[2], 255); in += 3; } +#endif } void @@ -1085,22 +1096,44 @@ static void copy4skip1(UINT8* _out, const UINT8* in, int pixels) { int i; +#ifdef __sparc + /* SPARC CPUs cannot read integers from nonaligned addresses. */ + for (i = 0; i < pixels; i++) { + _out[0] = in[0]; + _out[1] = in[1]; + _out[2] = in[2]; + _out[3] = in[3]; + _out += 4; in += 5; + } +#else UINT32* out = (UINT32*) _out; for (i = 0; i < pixels; i++) { out[i] = *(UINT32*)&in[0]; in += 5; } +#endif } static void copy4skip2(UINT8* _out, const UINT8* in, int pixels) { int i; +#ifdef __sparc + /* SPARC CPUs cannot read integers from nonaligned addresses. */ + for (i = 0; i < pixels; i++) { + _out[0] = in[0]; + _out[1] = in[1]; + _out[2] = in[2]; + _out[3] = in[3]; + _out += 4; in += 6; + } +#else UINT32* out = (UINT32*) _out; for (i = 0; i < pixels; i++) { out[i] = *(UINT32*)&in[0]; in += 6; } +#endif } @@ -1280,7 +1313,7 @@ static struct { {"1", "1;I", 1, unpack1I}, {"1", "1;R", 1, unpack1R}, {"1", "1;IR", 1, unpack1IR}, - {"1", "1;8", 1, unpack18}, + {"1", "1;8", 8, unpack18}, /* greyscale */ {"L", "L;2", 2, unpackL2}, @@ -1333,7 +1366,7 @@ static struct { {"RGB", "RGBX;L", 32, unpackRGBAL}, {"RGB", "RGBA;L", 32, unpackRGBAL}, {"RGB", "BGRX", 32, ImagingUnpackBGRX}, - {"RGB", "XRGB", 24, ImagingUnpackXRGB}, + {"RGB", "XRGB", 32, ImagingUnpackXRGB}, {"RGB", "XBGR", 32, ImagingUnpackXBGR}, {"RGB", "YCC;P", 24, ImagingUnpackYCC}, {"RGB", "R", 8, band0}, @@ -1369,12 +1402,12 @@ static struct { {"RGBA", "A", 8, band3}, #ifdef WORDS_BIGENDIAN - {"RGB", "RGB;16N", 64, unpackRGB16B}, + {"RGB", "RGB;16N", 48, unpackRGB16B}, {"RGBA", "RGBa;16N", 64, unpackRGBa16B}, {"RGBA", "RGBA;16N", 64, unpackRGBA16B}, {"RGBX", "RGBX;16N", 64, unpackRGBA16B}, #else - {"RGB", "RGB;16N", 64, unpackRGB16L}, + {"RGB", "RGB;16N", 48, unpackRGB16L}, {"RGBA", "RGBa;16N", 64, unpackRGBa16L}, {"RGBA", "RGBA;16N", 64, unpackRGBA16L}, {"RGBX", "RGBX;16N", 64, unpackRGBA16B}, @@ -1403,7 +1436,7 @@ static struct { {"RGBX", "RGBX;16L", 64, unpackRGBA16L}, {"RGBX", "RGBX;16B", 64, unpackRGBA16B}, {"RGBX", "BGRX", 32, ImagingUnpackBGRX}, - {"RGBX", "XRGB", 24, ImagingUnpackXRGB}, + {"RGBX", "XRGB", 32, ImagingUnpackXRGB}, {"RGBX", "XBGR", 32, ImagingUnpackXBGR}, {"RGBX", "YCC;P", 24, ImagingUnpackYCC}, {"RGBX", "R", 8, band0}, @@ -1417,6 +1450,8 @@ static struct { {"CMYK", "CMYKXX", 48, copy4skip2}, {"CMYK", "CMYK;I", 32, unpackCMYKI}, {"CMYK", "CMYK;L", 32, unpackRGBAL}, + {"CMYK", "CMYK;16L", 64, unpackRGBA16L}, + {"CMYK", "CMYK;16B", 64, unpackRGBA16B}, {"CMYK", "C", 8, band0}, {"CMYK", "M", 8, band1}, {"CMYK", "Y", 8, band2}, @@ -1426,6 +1461,12 @@ static struct { {"CMYK", "Y;I", 8, band2I}, {"CMYK", "K;I", 8, band3I}, +#ifdef WORDS_BIGENDIAN + {"CMYK", "CMYK;16N", 64, unpackRGBA16B}, +#else + {"CMYK", "CMYK;16N", 64, unpackRGBA16L}, +#endif + /* video (YCbCr) */ {"YCbCr", "YCbCr", 24, ImagingUnpackRGB}, {"YCbCr", "YCbCr;L", 24, unpackRGBL}, diff --git a/src/libImaging/UnsharpMask.c b/src/libImaging/UnsharpMask.c index ec3bb23cc..a034bebf2 100644 --- a/src/libImaging/UnsharpMask.c +++ b/src/libImaging/UnsharpMask.c @@ -6,7 +6,6 @@ /* Originally released under LGPL. Graciously donated to PIL for distribution under the standard PIL license in 2009." */ -#include "Python.h" #include "Imaging.h" diff --git a/src/libImaging/XbmDecode.c b/src/libImaging/XbmDecode.c index 8a203841b..75b4961ab 100644 --- a/src/libImaging/XbmDecode.c +++ b/src/libImaging/XbmDecode.c @@ -21,7 +21,7 @@ (v >= 'A' && v <= 'F') ? v - 'A' + 10 : 0) int -ImagingXbmDecode(Imaging im, ImagingCodecState state, UINT8* buf, int bytes) +ImagingXbmDecode(Imaging im, ImagingCodecState state, UINT8* buf, Py_ssize_t bytes) { enum { BYTE = 1, SKIP }; diff --git a/src/libImaging/ZipDecode.c b/src/libImaging/ZipDecode.c index e96e3200c..43601c38e 100644 --- a/src/libImaging/ZipDecode.c +++ b/src/libImaging/ZipDecode.c @@ -41,7 +41,7 @@ static int get_row_len(ImagingCodecState state, int pass) /* -------------------------------------------------------------------- */ int -ImagingZipDecode(Imaging im, ImagingCodecState state, UINT8* buf, int bytes) +ImagingZipDecode(Imaging im, ImagingCodecState state, UINT8* buf, Py_ssize_t bytes) { ZIPSTATE* context = (ZIPSTATE*) state->context; int err; diff --git a/src/path.c b/src/path.c index eb1e065f9..5f0541b0b 100644 --- a/src/path.c +++ b/src/path.c @@ -133,8 +133,8 @@ PyPath_Flatten(PyObject* data, double **pxy) /* Assume the buffer contains floats */ Py_buffer buffer; if (PyImaging_GetBuffer(data, &buffer) == 0) { - int n = buffer.len / (2 * sizeof(float)); float *ptr = (float*) buffer.buf; + n = buffer.len / (2 * sizeof(float)); xy = alloc_array(n); if (!xy) return -1; diff --git a/tox.ini b/tox.ini index fce4a4206..08fbebf05 100644 --- a/tox.ini +++ b/tox.ini @@ -24,9 +24,11 @@ deps = [testenv:lint] commands = + black --check --diff . flake8 --statistics --count check-manifest deps = + black check-manifest flake8 skip_install = true diff --git a/winbuild/appveyor_install_msys2_deps.sh b/winbuild/appveyor_install_msys2_deps.sh index f8cb8c641..7c1a2907d 100644 --- a/winbuild/appveyor_install_msys2_deps.sh +++ b/winbuild/appveyor_install_msys2_deps.sh @@ -5,7 +5,8 @@ pacman -S --noconfirm mingw32/mingw-w64-i686-python3-pip \ mingw32/mingw-w64-i686-python3-setuptools \ mingw32/mingw-w64-i686-python2-pip \ mingw32/mingw-w64-i686-python2-setuptools \ - mingw-w64-i686-libjpeg-turbo + mingw-w64-i686-libjpeg-turbo \ + mingw-w64-i686-libimagequant C:/msys64/mingw32/bin/python3 -m pip install --upgrade pip diff --git a/winbuild/appveyor_install_pypy.cmd b/winbuild/appveyor_install_pypy.cmd index 8c36268f0..fc56d0e56 100644 --- a/winbuild/appveyor_install_pypy.cmd +++ b/winbuild/appveyor_install_pypy.cmd @@ -1,3 +1,3 @@ -curl -fsSL -o pypy2.zip https://bitbucket.org/pypy/pypy/downloads/pypy2.7-v7.1.0-win32.zip +curl -fsSL -o pypy2.zip https://bitbucket.org/pypy/pypy/downloads/pypy2.7-v7.1.1-win32.zip 7z x pypy2.zip -oc:\ -c:\Python37\Scripts\virtualenv.exe -p c:\pypy2.7-v7.1.0-win32\pypy.exe c:\vp\pypy2 +c:\Python37\Scripts\virtualenv.exe -p c:\pypy2.7-v7.1.1-win32\pypy.exe c:\vp\pypy2 diff --git a/winbuild/build.py b/winbuild/build.py index 01c847bae..d0490e6d8 100755 --- a/winbuild/build.py +++ b/winbuild/build.py @@ -6,18 +6,29 @@ import sys import getopt import os -from config import (compilers, compiler_from_env, pythons, pyversion_from_env, - bit_from_env, VIRT_BASE, X64_EXT) +from config import ( + compilers, + compiler_from_env, + pythons, + pyversion_from_env, + bit_from_env, + VIRT_BASE, + X64_EXT, +) def setup_vms(): ret = [] for py in pythons: - for arch in ('', X64_EXT): - ret.append("virtualenv -p c:/Python%s%s/python.exe --clear %s%s%s" - % (py, arch, VIRT_BASE, py, arch)) - ret.append(r"%s%s%s\Scripts\pip.exe install pytest pytest-cov" % - (VIRT_BASE, py, arch)) + for arch in ("", X64_EXT): + ret.append( + "virtualenv -p c:/Python%s%s/python.exe --clear %s%s%s" + % (py, arch, VIRT_BASE, py, arch) + ) + ret.append( + r"%s%s%s\Scripts\pip.exe install pytest pytest-cov" + % (VIRT_BASE, py, arch) + ) return "\n".join(ret) @@ -25,16 +36,17 @@ def run_script(params): (version, script) = params try: print("Running %s" % version) - filename = 'build_pillow_%s.cmd' % version - with open(filename, 'w') as f: + filename = "build_pillow_%s.cmd" % version + with open(filename, "w") as f: f.write(script) - command = ['powershell', "./%s" % filename] - proc = subprocess.Popen(command, - stdin=subprocess.PIPE, - stdout=subprocess.PIPE, - stderr=subprocess.PIPE, - ) + command = ["powershell", "./%s" % filename] + proc = subprocess.Popen( + command, + stdin=subprocess.PIPE, + stdout=subprocess.PIPE, + stderr=subprocess.PIPE, + ) (trace, stderr) = proc.communicate() status = proc.returncode print("-- stderr --") @@ -55,7 +67,9 @@ set MPLSRC=%%~dp0\.. set INCLIB=%%~dp0\depends set BLDOPT=%s cd /D %%MPLSRC%% -""" % (op) +""" % ( + op + ) def footer(): @@ -66,10 +80,13 @@ exit def vc_setup(compiler, bit): script = "" - if compiler['vc_version'] == '2015': + if compiler["vc_version"] == "2015": arch = "x86" if bit == 32 else "x86_amd64" - script = r""" -call "C:\Program Files (x86)\Microsoft Visual Studio 14.0\VC\vcvarsall.bat" %s""" % arch + script = ( + r""" +call "C:\Program Files (x86)\Microsoft Visual Studio 14.0\VC\vcvarsall.bat" %s""" + % arch + ) return script @@ -77,27 +94,27 @@ def build_one(py_ver, compiler, bit): # UNDONE virtual envs if we're not running on AppVeyor args = {} args.update(compiler) - if 'PYTHON' in os.environ: - args['python_path'] = "%PYTHON%" + if "PYTHON" in os.environ: + args["python_path"] = "%PYTHON%" else: - args['python_path'] = "%s%s\\Scripts" % (VIRT_BASE, py_ver) + args["python_path"] = "%s%s\\Scripts" % (VIRT_BASE, py_ver) - args['executable'] = "python.exe" - if 'EXECUTABLE' in os.environ: - args['executable'] = "%EXECUTABLE%" + args["executable"] = "python.exe" + if "EXECUTABLE" in os.environ: + args["executable"] = "%EXECUTABLE%" - args['py_ver'] = py_ver - if '27' in py_ver: - args['tcl_ver'] = '85' + args["py_ver"] = py_ver + if "27" in py_ver: + args["tcl_ver"] = "85" else: - args['tcl_ver'] = '86' + args["tcl_ver"] = "86" - if compiler['vc_version'] == '2015': - args['imaging_libs'] = ' build_ext --add-imaging-libs=msvcrt' + if compiler["vc_version"] == "2015": + args["imaging_libs"] = " build_ext --add-imaging-libs=msvcrt" else: - args['imaging_libs'] = '' + args["imaging_libs"] = "" - args['vc_setup'] = vc_setup(compiler, bit) + args["vc_setup"] = vc_setup(compiler, bit) script = r""" setlocal EnableDelayedExpansion @@ -119,34 +136,44 @@ endlocal def clean(): try: - shutil.rmtree('../build') + shutil.rmtree("../build") except Exception: # could already be removed pass - run_script(('virtualenvs', setup_vms())) + run_script(("virtualenvs", setup_vms())) def main(op): scripts = [] for py_version, py_info in pythons.items(): - py_compilers = compilers[py_info['compiler']][py_info['vc']] - scripts.append((py_version, - "\n".join([header(op), - build_one(py_version, - py_compilers[32], 32), - footer()]))) + py_compilers = compilers[py_info["compiler"]][py_info["vc"]] + scripts.append( + ( + py_version, + "\n".join( + [header(op), build_one(py_version, py_compilers[32], 32), footer()] + ), + ) + ) - scripts.append(("%s%s" % (py_version, X64_EXT), - "\n".join([header(op), - build_one("%sx64" % py_version, - py_compilers[64], 64), - footer()]))) + scripts.append( + ( + "%s%s" % (py_version, X64_EXT), + "\n".join( + [ + header(op), + build_one("%sx64" % py_version, py_compilers[64], 64), + footer(), + ] + ), + ) + ) results = map(run_script, scripts) for (version, status, trace, err) in results: - print("Compiled %s: %s" % (version, status and 'ERR' or 'OK')) + print("Compiled %s: %s" % (version, status and "ERR" or "OK")) def run_one(op): @@ -155,27 +182,28 @@ def run_one(op): py_version = pyversion_from_env() bit = bit_from_env() - run_script((py_version, - "\n".join([header(op), - build_one(py_version, compiler, bit), - footer()]) - )) + run_script( + ( + py_version, + "\n".join([header(op), build_one(py_version, compiler, bit), footer()]), + ) + ) -if __name__ == '__main__': - opts, args = getopt.getopt(sys.argv[1:], '', ['clean', 'dist', 'wheel']) +if __name__ == "__main__": + opts, args = getopt.getopt(sys.argv[1:], "", ["clean", "dist", "wheel"]) opts = dict(opts) - if '--clean' in opts: + if "--clean" in opts: clean() - op = 'install' - if '--dist' in opts: + op = "install" + if "--dist" in opts: op = "bdist_wininst --user-access-control=auto" - elif '--wheel' in opts: + elif "--wheel" in opts: op = "bdist_wheel" - if 'PYTHON' in os.environ: + if "PYTHON" in os.environ: run_one(op) else: main(op) diff --git a/winbuild/build_dep.py b/winbuild/build_dep.py index 1bf8b51a8..13ac7472e 100644 --- a/winbuild/build_dep.py +++ b/winbuild/build_dep.py @@ -3,8 +3,7 @@ from untar import untar import os from fetch import fetch -from config import (compilers, all_compilers, compiler_from_env, bit_from_env, - libs) +from config import compilers, all_compilers, compiler_from_env, bit_from_env, libs from build import vc_setup @@ -12,8 +11,8 @@ def _relpath(*args): return os.path.join(os.getcwd(), *args) -build_dir = _relpath('build') -inc_dir = _relpath('depends') +build_dir = _relpath("build") +inc_dir = _relpath("depends") def check_sig(filename, signame): @@ -32,37 +31,40 @@ def mkdirs(): pass for compiler in all_compilers(): try: - os.mkdir(os.path.join(inc_dir, compiler['inc_dir'])) + os.mkdir(os.path.join(inc_dir, compiler["inc_dir"])) except OSError: pass def extract(src, dest): - if '.zip' in src: + if ".zip" in src: return unzip(src, dest) - if '.tar.gz' in src or '.tgz' in src: + if ".tar.gz" in src or ".tgz" in src: return untar(src, dest) def extract_libs(): for name, lib in libs.items(): - filename = lib['filename'] + filename = lib["filename"] if not os.path.exists(filename): - filename = fetch(lib['url']) - if name == 'openjpeg': + filename = fetch(lib["url"]) + if name == "openjpeg": for compiler in all_compilers(): - if not os.path.exists(os.path.join( - build_dir, lib['dir']+compiler['inc_dir'])): + if not os.path.exists( + os.path.join(build_dir, lib["dir"] + compiler["inc_dir"]) + ): extract(filename, build_dir) - os.rename(os.path.join(build_dir, lib['dir']), - os.path.join( - build_dir, lib['dir']+compiler['inc_dir'])) + os.rename( + os.path.join(build_dir, lib["dir"]), + os.path.join(build_dir, lib["dir"] + compiler["inc_dir"]), + ) else: extract(filename, build_dir) def extract_openjpeg(compiler): - return r""" + return ( + r""" rem build openjpeg setlocal @echo on @@ -72,12 +74,15 @@ copy /Y /B openjpeg-2.0.0-win32-x86\include\openjpeg-2.0 %%INCLIB%%\openjpeg-2. copy /Y /B openjpeg-2.0.0-win32-x86\bin\ %%INCLIB%% copy /Y /B openjpeg-2.0.0-win32-x86\lib\ %%INCLIB%% endlocal -""" % compiler +""" + % compiler + ) def cp_tk(ver_85, ver_86): - versions = {'ver_85': ver_85, 'ver_86': ver_86} - return r""" + versions = {"ver_85": ver_85, "ver_86": ver_86} + return ( + r""" mkdir %%INCLIB%%\tcl85\include\X11 copy /Y /B %%BUILD%%\tcl%(ver_85)s\generic\*.h %%INCLIB%%\tcl85\include\ copy /Y /B %%BUILD%%\tk%(ver_85)s\generic\*.h %%INCLIB%%\tcl85\include\ @@ -87,7 +92,9 @@ mkdir %%INCLIB%%\tcl86\include\X11 copy /Y /B %%BUILD%%\tcl%(ver_86)s\generic\*.h %%INCLIB%%\tcl86\include\ copy /Y /B %%BUILD%%\tk%(ver_86)s\generic\*.h %%INCLIB%%\tcl86\include\ copy /Y /B %%BUILD%%\tk%(ver_86)s\xlib\X11\* %%INCLIB%%\tcl86\include\X11\ -""" % versions +""" + % versions + ) def header(): @@ -96,15 +103,21 @@ set MSBUILD=C:\Windows\Microsoft.NET\Framework64\v4.0.30319\MSBuild.exe set CMAKE="cmake.exe" set INCLIB=%~dp0\depends set BUILD=%~dp0\build -""" + "\n".join(r'set %s=%%BUILD%%\%s' % (k.upper(), v['dir']) - for (k, v) in libs.items() if v['dir']) +""" + "\n".join( + r"set %s=%%BUILD%%\%s" % (k.upper(), v["dir"]) + for (k, v) in libs.items() + if v["dir"] + ) def setup_compiler(compiler): - return r"""setlocal EnableDelayedExpansion + return ( + r"""setlocal EnableDelayedExpansion call "%%ProgramFiles%%\Microsoft SDKs\Windows\%(env_version)s\Bin\SetEnv.Cmd" /Release %(env_flags)s set INCLIB=%%INCLIB%%\%(inc_dir)s -""" % compiler # noqa: E501 +""" # noqa: E501 + % compiler + ) def end_compiler(): @@ -113,31 +126,43 @@ endlocal """ -def nmake_openjpeg(compiler): - atts = {'op_ver': '2.1'} +def nmake_openjpeg(compiler, bit): + if compiler["env_version"] == "v7.0": + return "" + + atts = {"op_ver": "2.3.1"} atts.update(compiler) - return r""" + return ( + r""" rem build openjpeg setlocal +""" + + vc_setup(compiler, bit) + + r""" @echo on cd /D %%OPENJPEG%%%(inc_dir)s -%%CMAKE%% -DBUILD_THIRDPARTY:BOOL=OFF -G "NMake Makefiles" . +%%CMAKE%% -DBUILD_THIRDPARTY:BOOL=OFF -DBUILD_SHARED_LIBS:BOOL=OFF -G "NMake Makefiles" . nmake -f Makefile clean nmake -f Makefile copy /Y /B bin\* %%INCLIB%% mkdir %%INCLIB%%\openjpeg-%(op_ver)s copy /Y /B src\lib\openjp2\*.h %%INCLIB%%\openjpeg-%(op_ver)s endlocal -""" % atts +""" # noqa: E501 + % atts + ) def nmake_libs(compiler, bit): # undone -- pre, makes, headers, libs - script = r""" + script = ( + r""" rem Build libjpeg setlocal -""" + vc_setup(compiler, bit) + r""" +""" + + vc_setup(compiler, bit) + + r""" cd /D %%JPEG%% nmake -f makefile.vc setup-vc6 nmake -f makefile.vc clean @@ -161,7 +186,9 @@ endlocal rem Build webp setlocal -""" + vc_setup(compiler, bit) + r""" +""" + + vc_setup(compiler, bit) + + r""" cd /D %%WEBP%% rd /S /Q %%WEBP%%\output\release-static nmake -f Makefile.vc CFG=release-static RTLIBCFG=static OBJDIR=output all @@ -172,7 +199,9 @@ endlocal rem Build libtiff setlocal -""" + vc_setup(compiler, bit) + r""" +""" + + vc_setup(compiler, bit) + + r""" rem do after building jpeg and zlib copy %%~dp0\nmake.opt %%TIFF%% @@ -184,6 +213,7 @@ copy /Y /B libtiff\*.lib %%INCLIB%% copy /Y /B libtiff\tiff*.h %%INCLIB%% endlocal """ + ) return script % compiler @@ -196,36 +226,49 @@ set DefaultPlatformToolset=v100 """ properties = r"""/p:Configuration="Release" /p:Platform=%(platform)s""" if bit == 64: - script += r'copy /Y /B ' +\ - r'"C:\Program Files (x86)\Microsoft SDKs\Windows\v7.1A\Lib\x64\*.Lib" ' +\ - r'%%FREETYPE%%\builds\windows\vc2010' + script += ( + r"copy /Y /B " + + r'"C:\Program Files (x86)\Microsoft SDKs\Windows\v7.1A\Lib\x64\*.Lib" ' + + r"%%FREETYPE%%\builds\windows\vc2010" + ) properties += r" /p:_IsNativeEnvironment=false" - script += r""" -%%MSBUILD%% %%FREETYPE%%\builds\windows\vc2010\freetype.sln /t:Clean;Build """+properties+r""" /m + script += ( + r""" +%%MSBUILD%% %%FREETYPE%%\builds\windows\vc2010\freetype.sln /t:Clean;Build """ + + properties + + r""" /m xcopy /Y /E /Q %%FREETYPE%%\include %%INCLIB%% """ + ) freetypeReleaseDir = r"%%FREETYPE%%\objs\%(platform)s\Release" - script += r""" -copy /Y /B """+freetypeReleaseDir+r"""\freetype.lib %%INCLIB%%\freetype.lib -copy /Y /B """+freetypeReleaseDir+r"""\freetype.dll %%INCLIB%%\..\freetype.dll + script += ( + r""" +copy /Y /B """ + + freetypeReleaseDir + + r"""\freetype.lib %%INCLIB%%\freetype.lib +copy /Y /B """ + + freetypeReleaseDir + + r"""\freetype.dll %%INCLIB%%\..\freetype.dll endlocal """ - return script % compiler # noqa: E501 + ) + return script % compiler def build_lcms2(compiler): - if compiler['env_version'] == 'v7.1': + if compiler["env_version"] == "v7.1": return build_lcms_71(compiler) return build_lcms_70(compiler) def build_lcms_70(compiler): """Link error here on x64""" - if compiler['platform'] == 'x64': - return '' + if compiler["platform"] == "x64": + return "" """Build LCMS on VC2008. This version is only 32bit/Win32""" - return r""" + return ( + r""" rem Build lcms2 setlocal rd /S /Q %%LCMS%%\Lib @@ -235,11 +278,14 @@ rd /S /Q %%LCMS%%\Projects\VC%(vc_version)s\Release xcopy /Y /E /Q %%LCMS%%\include %%INCLIB%% copy /Y /B %%LCMS%%\Lib\MS\*.lib %%INCLIB%% endlocal -""" % compiler # noqa: E501 +""" # noqa: E501 + % compiler + ) def build_lcms_71(compiler): - return r""" + return ( + r""" rem Build lcms2 setlocal rd /S /Q %%LCMS%%\Lib @@ -249,21 +295,26 @@ rd /S /Q %%LCMS%%\Projects\VC%(vc_version)s\Release xcopy /Y /E /Q %%LCMS%%\include %%INCLIB%% copy /Y /B %%LCMS%%\Lib\MS\*.lib %%INCLIB%% endlocal -""" % compiler # noqa: E501 +""" # noqa: E501 + % compiler + ) def build_ghostscript(compiler, bit): - script = r""" + script = ( + r""" rem Build gs setlocal -""" + vc_setup(compiler, bit) + r""" -set MSVC_VERSION=""" + { - "2010": "90", - "2015": "14" - }[compiler['vc_version']] + r""" +""" + + vc_setup(compiler, bit) + + r""" +set MSVC_VERSION=""" + + {"2010": "90", "2015": "14"}[compiler["vc_version"]] + + r""" set RCOMP="C:\Program Files (x86)\Microsoft SDKs\Windows\v7.1A\Bin\RC.Exe" cd /D %%GHOSTSCRIPT%% """ + ) if bit == 64: script += r""" set WIN64="" @@ -273,7 +324,7 @@ nmake -f psi/msvc.mak copy /Y /B bin\ C:\Python27\ endlocal """ - return script % compiler # noqa: E501 + return script % compiler def add_compiler(compiler, bit): @@ -284,24 +335,22 @@ def add_compiler(compiler, bit): script.append(msbuild_freetype(compiler, bit)) script.append(build_lcms2(compiler)) - # script.append(nmake_openjpeg(compiler)) + script.append(nmake_openjpeg(compiler, bit)) script.append(build_ghostscript(compiler, bit)) script.append(end_compiler()) mkdirs() extract_libs() -script = [header(), - cp_tk(libs['tk-8.5']['version'], - libs['tk-8.6']['version'])] +script = [header(), cp_tk(libs["tk-8.5"]["version"], libs["tk-8.6"]["version"])] -if 'PYTHON' in os.environ: +if "PYTHON" in os.environ: add_compiler(compiler_from_env(), bit_from_env()) else: # for compiler in all_compilers(): # add_compiler(compiler) add_compiler(compilers[7.0][2010][32], 32) -with open('build_deps.cmd', 'w') as f: +with open("build_deps.cmd", "w") as f: f.write("\n".join(script)) diff --git a/winbuild/config.py b/winbuild/config.py index 3b13951f1..cd7cc2698 100644 --- a/winbuild/config.py +++ b/winbuild/config.py @@ -1,83 +1,85 @@ import os -SF_MIRROR = 'http://iweb.dl.sourceforge.net' -PILLOW_DEPENDS_DIR = 'C:\\pillow-depends\\' +SF_MIRROR = "http://iweb.dl.sourceforge.net" +PILLOW_DEPENDS_DIR = "C:\\pillow-depends\\" -pythons = {'27': {'compiler': 7, 'vc': 2010}, - 'pypy2': {'compiler': 7, 'vc': 2010}, - '35': {'compiler': 7.1, 'vc': 2015}, - '36': {'compiler': 7.1, 'vc': 2015}, - '37': {'compiler': 7.1, 'vc': 2015}} +pythons = { + "27": {"compiler": 7, "vc": 2010}, + "pypy2": {"compiler": 7, "vc": 2010}, + "35": {"compiler": 7.1, "vc": 2015}, + "36": {"compiler": 7.1, "vc": 2015}, + "37": {"compiler": 7.1, "vc": 2015}, +} VIRT_BASE = "c:/vp/" -X64_EXT = os.environ.get('X64_EXT', "x64") +X64_EXT = os.environ.get("X64_EXT", "x64") libs = { # 'openjpeg': { # 'filename': 'openjpeg-2.0.0-win32-x86.zip', # 'version': '2.0' # }, - 'zlib': { - 'url': 'http://zlib.net/zlib1211.zip', - 'filename': PILLOW_DEPENDS_DIR + 'zlib1211.zip', - 'dir': 'zlib-1.2.11', + "zlib": { + "url": "http://zlib.net/zlib1211.zip", + "filename": PILLOW_DEPENDS_DIR + "zlib1211.zip", + "dir": "zlib-1.2.11", }, - 'jpeg': { - 'url': 'http://www.ijg.org/files/jpegsr9c.zip', - 'filename': PILLOW_DEPENDS_DIR + 'jpegsr9c.zip', - 'dir': 'jpeg-9c', + "jpeg": { + "url": "http://www.ijg.org/files/jpegsr9c.zip", + "filename": PILLOW_DEPENDS_DIR + "jpegsr9c.zip", + "dir": "jpeg-9c", }, - 'tiff': { - 'url': 'ftp://download.osgeo.org/libtiff/tiff-4.0.10.tar.gz', - 'filename': PILLOW_DEPENDS_DIR + 'tiff-4.0.10.tar.gz', - 'dir': 'tiff-4.0.10', + "tiff": { + "url": "ftp://download.osgeo.org/libtiff/tiff-4.0.10.tar.gz", + "filename": PILLOW_DEPENDS_DIR + "tiff-4.0.10.tar.gz", + "dir": "tiff-4.0.10", }, - 'freetype': { - 'url': 'https://download.savannah.gnu.org/releases/freetype/freetype-2.10.0.tar.gz', # noqa: E501 - 'filename': PILLOW_DEPENDS_DIR + 'freetype-2.10.0.tar.gz', - 'dir': 'freetype-2.10.0', + "freetype": { + "url": "https://download.savannah.gnu.org/releases/freetype/freetype-2.10.0.tar.gz", # noqa: E501 + "filename": PILLOW_DEPENDS_DIR + "freetype-2.10.0.tar.gz", + "dir": "freetype-2.10.0", }, - 'lcms': { - 'url': SF_MIRROR+'/project/lcms/lcms/2.7/lcms2-2.7.zip', - 'filename': PILLOW_DEPENDS_DIR + 'lcms2-2.7.zip', - 'dir': 'lcms2-2.7', + "lcms": { + "url": SF_MIRROR + "/project/lcms/lcms/2.7/lcms2-2.7.zip", + "filename": PILLOW_DEPENDS_DIR + "lcms2-2.7.zip", + "dir": "lcms2-2.7", }, - 'ghostscript': { - 'url': 'https://github.com/ArtifexSoftware/ghostpdl-downloads/releases/download/gs927/ghostscript-9.27.tar.gz', # noqa: E501 - 'filename': PILLOW_DEPENDS_DIR + 'ghostscript-9.27.tar.gz', - 'dir': 'ghostscript-9.27', + "ghostscript": { + "url": "https://github.com/ArtifexSoftware/ghostpdl-downloads/releases/download/gs927/ghostscript-9.27.tar.gz", # noqa: E501 + "filename": PILLOW_DEPENDS_DIR + "ghostscript-9.27.tar.gz", + "dir": "ghostscript-9.27", }, - 'tcl-8.5': { - 'url': SF_MIRROR+'/project/tcl/Tcl/8.5.19/tcl8519-src.zip', - 'filename': PILLOW_DEPENDS_DIR + 'tcl8519-src.zip', - 'dir': '', + "tcl-8.5": { + "url": SF_MIRROR + "/project/tcl/Tcl/8.5.19/tcl8519-src.zip", + "filename": PILLOW_DEPENDS_DIR + "tcl8519-src.zip", + "dir": "", }, - 'tk-8.5': { - 'url': SF_MIRROR+'/project/tcl/Tcl/8.5.19/tk8519-src.zip', - 'filename': PILLOW_DEPENDS_DIR + 'tk8519-src.zip', - 'dir': '', - 'version': '8.5.19', + "tk-8.5": { + "url": SF_MIRROR + "/project/tcl/Tcl/8.5.19/tk8519-src.zip", + "filename": PILLOW_DEPENDS_DIR + "tk8519-src.zip", + "dir": "", + "version": "8.5.19", }, - 'tcl-8.6': { - 'url': SF_MIRROR+'/project/tcl/Tcl/8.6.9/tcl869-src.zip', - 'filename': PILLOW_DEPENDS_DIR + 'tcl869-src.zip', - 'dir': '', + "tcl-8.6": { + "url": SF_MIRROR + "/project/tcl/Tcl/8.6.9/tcl869-src.zip", + "filename": PILLOW_DEPENDS_DIR + "tcl869-src.zip", + "dir": "", }, - 'tk-8.6': { - 'url': SF_MIRROR+'/project/tcl/Tcl/8.6.9/tk869-src.zip', - 'filename': PILLOW_DEPENDS_DIR + 'tk869-src.zip', - 'dir': '', - 'version': '8.6.9', + "tk-8.6": { + "url": SF_MIRROR + "/project/tcl/Tcl/8.6.9/tk869-src.zip", + "filename": PILLOW_DEPENDS_DIR + "tk869-src.zip", + "dir": "", + "version": "8.6.9", }, - 'webp': { - 'url': 'http://downloads.webmproject.org/releases/webp/libwebp-1.0.2.tar.gz', - 'filename': PILLOW_DEPENDS_DIR + 'libwebp-1.0.2.tar.gz', - 'dir': 'libwebp-1.0.2', + "webp": { + "url": "http://downloads.webmproject.org/releases/webp/libwebp-1.0.2.tar.gz", + "filename": PILLOW_DEPENDS_DIR + "libwebp-1.0.2.tar.gz", + "dir": "libwebp-1.0.2", }, - 'openjpeg': { - 'url': SF_MIRROR+'/project/openjpeg/openjpeg/2.3.0/openjpeg-2.3.0.tar.gz', - 'filename': PILLOW_DEPENDS_DIR + 'openjpeg-2.3.0.tar.gz', - 'dir': 'openjpeg-2.3.0', + "openjpeg": { + "url": "https://github.com/uclouvain/openjpeg/archive/v2.3.1.tar.gz", + "filename": PILLOW_DEPENDS_DIR + "openjpeg-2.3.1.tar.gz", + "dir": "openjpeg-2.3.1", }, } @@ -85,63 +87,63 @@ compilers = { 7: { 2010: { 64: { - 'env_version': 'v7.0', - 'vc_version': '2010', - 'env_flags': '/x64 /xp', - 'inc_dir': 'msvcr90-x64', - 'platform': 'x64', - 'webp_platform': 'x64', + "env_version": "v7.0", + "vc_version": "2010", + "env_flags": "/x64 /xp", + "inc_dir": "msvcr90-x64", + "platform": "x64", + "webp_platform": "x64", }, 32: { - 'env_version': 'v7.0', - 'vc_version': '2010', - 'env_flags': '/x86 /xp', - 'inc_dir': 'msvcr90-x32', - 'platform': 'Win32', - 'webp_platform': 'x86', - } + "env_version": "v7.0", + "vc_version": "2010", + "env_flags": "/x86 /xp", + "inc_dir": "msvcr90-x32", + "platform": "Win32", + "webp_platform": "x86", + }, } }, 7.1: { 2015: { 64: { - 'env_version': 'v7.1', - 'vc_version': '2015', - 'env_flags': '/x64 /vista', - 'inc_dir': 'msvcr10-x64', - 'platform': 'x64', - 'webp_platform': 'x64', + "env_version": "v7.1", + "vc_version": "2015", + "env_flags": "/x64 /vista", + "inc_dir": "msvcr10-x64", + "platform": "x64", + "webp_platform": "x64", }, 32: { - 'env_version': 'v7.1', - 'vc_version': '2015', - 'env_flags': '/x86 /vista', - 'inc_dir': 'msvcr10-x32', - 'platform': 'Win32', - 'webp_platform': 'x86', - } + "env_version": "v7.1", + "vc_version": "2015", + "env_flags": "/x86 /vista", + "inc_dir": "msvcr10-x32", + "platform": "Win32", + "webp_platform": "x86", + }, } - } + }, } def pyversion_from_env(): - py = os.environ['PYTHON'] + py = os.environ["PYTHON"] - py_version = '27' + py_version = "27" for k in pythons: if k in py: py_version = k break - if '64' in py: - py_version = '%s%s' % (py_version, X64_EXT) + if "64" in py: + py_version = "%s%s" % (py_version, X64_EXT) return py_version def compiler_from_env(): - py = os.environ['PYTHON'] + py = os.environ["PYTHON"] for k, v in pythons.items(): if k in py: @@ -149,13 +151,13 @@ def compiler_from_env(): break bit = bit_from_env() - return compilers[py_info['compiler']][py_info['vc']][bit] + return compilers[py_info["compiler"]][py_info["vc"]][bit] def bit_from_env(): - py = os.environ['PYTHON'] + py = os.environ["PYTHON"] - return 64 if '64' in py else 32 + return 64 if "64" in py else 32 def all_compilers(): diff --git a/winbuild/fetch.py b/winbuild/fetch.py index 830a64ee5..804e4ef0c 100644 --- a/winbuild/fetch.py +++ b/winbuild/fetch.py @@ -5,7 +5,7 @@ import urllib.request def fetch(url): - name = urllib.parse.urlsplit(url)[2].split('/')[-1] + name = urllib.parse.urlsplit(url)[2].split("/")[-1] if not os.path.exists(name): print("Fetching", url) @@ -14,10 +14,10 @@ def fetch(url): except urllib.error.URLError: r = urllib.request.urlopen(url) content = r.read() - with open(name, 'wb') as fd: + with open(name, "wb") as fd: fd.write(content) return name -if __name__ == '__main__': +if __name__ == "__main__": fetch(sys.argv[1]) diff --git a/winbuild/fixproj.py b/winbuild/fixproj.py deleted file mode 100644 index 9b5203fb8..000000000 --- a/winbuild/fixproj.py +++ /dev/null @@ -1,8 +0,0 @@ -import sys - -with open(sys.argv[1], 'r') as fd: - content = '\n'.join(line.strip() for line in fd if line.strip()) -if len(sys.argv) == 3: - content = content.replace('Win32', sys.argv[2]).replace('x64', sys.argv[2]) -with open(sys.argv[1], 'w') as fd: - fd.write(content) diff --git a/winbuild/get_pythons.py b/winbuild/get_pythons.py index 376f056b7..63326fa82 100644 --- a/winbuild/get_pythons.py +++ b/winbuild/get_pythons.py @@ -1,12 +1,14 @@ from fetch import fetch import os -if __name__ == '__main__': - for version in ['2.7.15', '3.4.4']: - for platform in ['', '.amd64']: - for extension in ['', '.asc']: - fetch('https://www.python.org/ftp/python/%s/python-%s%s.msi%s' - % (version, version, platform, extension)) +if __name__ == "__main__": + for version in ["2.7.15", "3.4.4"]: + for platform in ["", ".amd64"]: + for extension in ["", ".asc"]: + fetch( + "https://www.python.org/ftp/python/%s/python-%s%s.msi%s" + % (version, version, platform, extension) + ) # find pip, if it's not in the path! - os.system('pip install virtualenv') + os.system("pip install virtualenv") diff --git a/winbuild/test.py b/winbuild/test.py index 84e071308..bb68fca27 100755 --- a/winbuild/test.py +++ b/winbuild/test.py @@ -12,16 +12,14 @@ def test_one(params): python, architecture = params try: print("Running: %s, %s" % params) - command = [r'%s\%s%s\Scripts\python.exe' % - (VIRT_BASE, python, architecture), - 'test-installed.py', - '--processes=-0', - '--process-timeout=30', - ] - command.extend(glob.glob('Tests/test*.py')) - proc = subprocess.Popen(command, - stdin=subprocess.PIPE, - stdout=subprocess.PIPE) + command = [ + r"%s\%s%s\Scripts\python.exe" % (VIRT_BASE, python, architecture), + "test-installed.py", + "--processes=-0", + "--process-timeout=30", + ] + command.extend(glob.glob("Tests/test*.py")) + proc = subprocess.Popen(command, stdin=subprocess.PIPE, stdout=subprocess.PIPE) (trace, stderr) = proc.communicate() status = proc.returncode print("Done with %s, %s -- %s" % (python, architecture, status)) @@ -31,16 +29,17 @@ def test_one(params): return (python, architecture, -1, str(msg)) -if __name__ == '__main__': +if __name__ == "__main__": - os.chdir('..') - matrix = [(python, architecture) for python in pythons - for architecture in ('', X64_EXT)] + os.chdir("..") + matrix = [ + (python, architecture) for python in pythons for architecture in ("", X64_EXT) + ] results = map(test_one, matrix) for (python, architecture, status, trace) in results: - print("%s%s: %s" % (python, architecture, status and 'ERR' or 'PASS')) + print("%s%s: %s" % (python, architecture, status and "ERR" or "PASS")) res = all(status for (python, architecture, status, trace) in results) sys.exit(res) diff --git a/winbuild/untar.py b/winbuild/untar.py index d85be384c..f2713b2f2 100644 --- a/winbuild/untar.py +++ b/winbuild/untar.py @@ -3,9 +3,9 @@ import tarfile def untar(src, dest): - with tarfile.open(src, 'r:gz') as tgz: + with tarfile.open(src, "r:gz") as tgz: tgz.extractall(dest) -if __name__ == '__main__': +if __name__ == "__main__": untar(sys.argv[1], sys.argv[2]) diff --git a/winbuild/unzip.py b/winbuild/unzip.py index 5a464757c..eb17a2e63 100644 --- a/winbuild/unzip.py +++ b/winbuild/unzip.py @@ -7,5 +7,5 @@ def unzip(src, dest): zf.extractall(dest) -if __name__ == '__main__': +if __name__ == "__main__": unzip(sys.argv[1], sys.argv[2])