diff --git a/.appveyor.yml b/.appveyor.yml index 0cf10597a..2a4e67dc9 100644 --- a/.appveyor.yml +++ b/.appveyor.yml @@ -52,7 +52,7 @@ install: } else { - c:\python34\python.exe c:\pillow\winbuild\build_dep.py + c:\python37\python.exe c:\pillow\winbuild\build_dep.py c:\pillow\winbuild\build_deps.cmd $host.SetShouldExit(0) } 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/.travis.yml b/.travis.yml index 9550eb8f2..43fc23613 100644 --- a/.travis.yml +++ b/.travis.yml @@ -16,50 +16,36 @@ matrix: - python: "3.6" name: "Lint" env: LINT="true" - - python: "pypy2.7-6.0" + - python: "pypy" name: "PyPy2 Xenial" - dist: xenial - - python: "pypy3.5-6.0" + - python: "pypy3" name: "PyPy3 Xenial" - dist: xenial - python: '3.7' name: "3.7 Xenial" - python: '2.7' name: "2.7 Xenial" - - python: '2.7' - name: "2.7 Trusty" - dist: trusty - python: "2.7_with_system_site_packages" # For PyQt4 name: "2.7_with_system_site_packages Xenial" services: xvfb - - python: "2.7_with_system_site_packages" # For PyQt4 - name: "2.7_with_system_site_packages Trusty" - dist: trusty - python: '3.6' - name: "3.6 Xenial" - - python: '3.6' - name: "3.6 Trusty PYTHONOPTIMIZE=1" - dist: trusty + name: "3.6 Xenial PYTHONOPTIMIZE=1" env: PYTHONOPTIMIZE=1 - python: '3.5' - name: "3.5 Xenial" - - python: '3.5' - name: "3.5 Trusty PYTHONOPTIMIZE=2" - dist: trusty + name: "3.5 Xenial PYTHONOPTIMIZE=2" env: PYTHONOPTIMIZE=2 - python: "3.8-dev" name: "3.8-dev Xenial" - env: DOCKER="alpine" DOCKER_TAG="master" - env: DOCKER="arch" DOCKER_TAG="master" # contains PyQt5 - - env: DOCKER="ubuntu-trusty-x86" DOCKER_TAG="master" - - env: DOCKER="ubuntu-xenial-amd64" DOCKER_TAG="master" + - env: DOCKER="ubuntu-16.04-xenial-amd64" DOCKER_TAG="master" + - env: DOCKER="ubuntu-18.04-bionic-amd64" DOCKER_TAG="master" - env: DOCKER="debian-stretch-x86" DOCKER_TAG="master" - env: DOCKER="centos-6-amd64" DOCKER_TAG="master" - env: DOCKER="centos-7-amd64" DOCKER_TAG="master" - env: DOCKER="amazon-1-amd64" DOCKER_TAG="master" - env: DOCKER="amazon-2-amd64" DOCKER_TAG="master" - - env: DOCKER="fedora-28-amd64" DOCKER_TAG="master" - env: DOCKER="fedora-29-amd64" DOCKER_TAG="master" + - env: DOCKER="fedora-30-amd64" DOCKER_TAG="master" services: - docker @@ -75,14 +61,6 @@ install: .travis/install.sh; fi -before_script: -# Qt needs a display for some of the tests, and it's only run on the system site packages install -- | - if [ "$TRAVIS_JOB_NAME" == "2.7_with_system_site_packages Trusty" ]; then - export DISPLAY=:99.0 - sh -e /etc/init.d/xvfb start - fi - script: - | if [ "$LINT" == "true" ]; then diff --git a/CHANGES.rst b/CHANGES.rst index cb276b0bf..599eaa345 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -2,6 +2,87 @@ Changelog (Pillow) ================== +6.1.0 (unreleased) +------------------ + +- 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 +1489,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/RELEASING.md b/RELEASING.md index f28e5f134..e1f57883c 100644 --- a/RELEASING.md +++ b/RELEASING.md @@ -102,4 +102,4 @@ Released as needed privately to individual vendors for critical security-related ## Documentation -* [ ] Make sure the default version for Read the Docs is the latest tagged release e.g. `d2d43879` (5.4.0) +* [ ] Make sure the [default version for Read the Docs](https://pillow.readthedocs.io/en/stable/) is up-to-date with the release changes diff --git a/Tests/check_large_memory.py b/Tests/check_large_memory.py index 24d687ea6..a2fcc206e 100644 --- a/Tests/check_large_memory.py +++ b/Tests/check_large_memory.py @@ -12,6 +12,12 @@ 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 @@ -32,6 +38,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__': 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/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/test_box_blur.py b/Tests/test_box_blur.py index b9939c5f2..a2012dcd9 100644 --- a/Tests/test_box_blur.py +++ b/Tests/test_box_blur.py @@ -175,7 +175,7 @@ class TestBoxBlur(PillowTestCase): delta=0, ) - def test_exteme_large_radius(self): + def test_extreme_large_radius(self): self.assertBlur( sample, 600, [ diff --git a/Tests/test_features.py b/Tests/test_features.py index 15b5982ce..642083ed5 100644 --- a/Tests/test_features.py +++ b/Tests/test_features.py @@ -1,3 +1,7 @@ +from __future__ import unicode_literals + +import io + from .helper import unittest, PillowTestCase from PIL import features @@ -63,3 +67,25 @@ 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_gif.py b/Tests/test_file_gif.py index 838894988..f9c312124 100644 --- a/Tests/test_file_gif.py +++ b/Tests/test_file_gif.py @@ -663,3 +663,9 @@ class TestFileGif(PillowTestCase): 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_icns.py b/Tests/test_file_icns.py index ac60731f9..b8e1b9878 100644 --- a/Tests/test_file_icns.py +++ b/Tests/test_file_icns.py @@ -61,11 +61,10 @@ class TestFileIcns(PillowTestCase): 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): diff --git a/Tests/test_file_ico.py b/Tests/test_file_ico.py index f6244e086..53875eb72 100644 --- a/Tests/test_file_ico.py +++ b/Tests/test_file_ico.py @@ -1,7 +1,7 @@ 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" @@ -83,3 +83,23 @@ class TestFileIco(PillowTestCase): self.assertEqual( 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..46858bb1f 100644 --- a/Tests/test_file_im.py +++ b/Tests/test_file_im.py @@ -48,7 +48,7 @@ class TestFileIm(PillowTestCase): im.seek(n_frames-1) def test_roundtrip(self): - for mode in ["RGB", "P"]: + for mode in ["RGB", "P", "PA"]: out = self.tempfile('temp.im') im = hopper(mode) im.save(out) diff --git a/Tests/test_file_jpeg.py b/Tests/test_file_jpeg.py index 25c3f754a..134d6e4df 100644 --- a/Tests/test_file_jpeg.py +++ b/Tests/test_file_jpeg.py @@ -620,6 +620,10 @@ class TestFileJpeg(PillowTestCase): '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") class TestFileCloseW32(PillowTestCase): diff --git a/Tests/test_file_libtiff.py b/Tests/test_file_libtiff.py index 0292b7733..efb30a481 100644 --- a/Tests/test_file_libtiff.py +++ b/Tests/test_file_libtiff.py @@ -631,6 +631,21 @@ 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, + [('tiff_adobe_deflate', (0, 0, 100, 40), 0, + ('RGB;16N', 'tiff_adobe_deflate', False))] + ) + 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") @@ -687,6 +702,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) 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_pdf.py b/Tests/test_file_pdf.py index bb003a32c..77400752d 100644 --- a/Tests/test_file_pdf.py +++ b/Tests/test_file_pdf.py @@ -26,6 +26,11 @@ class TestFilePdf(PillowTestCase): 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 diff --git a/Tests/test_file_psd.py b/Tests/test_file_psd.py index 3b8a7add6..c62d7ca10 100644 --- a/Tests/test_file_psd.py +++ b/Tests/test_file_psd.py @@ -17,6 +17,12 @@ 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" @@ -65,6 +71,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_tiff.py b/Tests/test_file_tiff.py index 9f2a04bb9..64d03361b 100644 --- a/Tests/test_file_tiff.py +++ b/Tests/test_file_tiff.py @@ -489,6 +489,16 @@ class TestFileTiff(PillowTestCase): 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 import os diff --git a/Tests/test_image.py b/Tests/test_image.py index 5bfcde368..a89abf3d4 100644 --- a/Tests/test_image.py +++ b/Tests/test_image.py @@ -76,6 +76,10 @@ class TestImage(PillowTestCase): @unittest.skipUnless(Image.HAS_PATHLIB, "requires pathlib/pathlib2") def test_pathlib(self): from PIL.Image import Path + im = Image.open(Path("Tests/images/multipage-mmap.tiff")) + self.assertEqual(im.mode, "P") + self.assertEqual(im.size, (10, 10)) + im = Image.open(Path("Tests/images/hopper.jpg")) self.assertEqual(im.mode, "RGB") self.assertEqual(im.size, (128, 128)) diff --git a/Tests/test_image_mode.py b/Tests/test_image_mode.py index 345c0dacb..bdfb6883b 100644 --- a/Tests/test_image_mode.py +++ b/Tests/test_image_mode.py @@ -26,6 +26,17 @@ class TestImageMode(PillowTestCase): self.assertEqual(m.basemode, "L") self.assertEqual(m.basetype, "L") + for mode in ("I;16", "I;16S", + "I;16L", "I;16LS", + "I;16B", "I;16BS", + "I;16N", "I;16NS"): + m = ImageMode.getmode(mode) + self.assertEqual(m.mode, mode) + self.assertEqual(str(m), mode) + self.assertEqual(m.bands, ("I",)) + self.assertEqual(m.basemode, "L") + self.assertEqual(m.basetype, "L") + m = ImageMode.getmode("RGB") self.assertEqual(m.mode, "RGB") self.assertEqual(str(m), "RGB") diff --git a/Tests/test_image_transform.py b/Tests/test_image_transform.py index ae1cf6e3d..384657d98 100644 --- a/Tests/test_image_transform.py +++ b/Tests/test_image_transform.py @@ -160,6 +160,15 @@ 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 diff --git a/Tests/test_imagedraw.py b/Tests/test_imagedraw.py index bceb0e3d4..cff08a39c 100644 --- a/Tests/test_imagedraw.py +++ b/Tests/test_imagedraw.py @@ -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)) @@ -239,6 +252,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)) diff --git a/Tests/test_imagefont.py b/Tests/test_imagefont.py index 33f37916c..0d51d95ca 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" @@ -131,6 +132,27 @@ class TestImageFont(PillowTestCase): 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, @@ -400,6 +422,7 @@ class TestImageFont(PillowTestCase): # Act/Assert self.assertRaises(IOError, ImageFont.load_path, filename) + self.assertRaises(IOError, ImageFont.truetype, filename) def test_default_font(self): # Arrange @@ -547,6 +570,91 @@ class TestImageFont(PillowTestCase): 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") class TestImageFont_RaqmLayout(TestImageFont): diff --git a/Tests/test_imagefontctl.py b/Tests/test_imagefontctl.py index a00971058..2127af19d 100644 --- a/Tests/test_imagefontctl.py +++ b/Tests/test_imagefontctl.py @@ -54,6 +54,18 @@ class TestImagecomplextext(PillowTestCase): self.assert_image_similar(im, target_img, .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) @@ -92,6 +104,22 @@ class TestImagecomplextext(PillowTestCase): self.assert_image_similar(im, target_img, .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) @@ -131,6 +159,18 @@ class TestImagecomplextext(PillowTestCase): self.assert_image_similar(im, target_img, .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, .5) + def test_language(self): ttf = ImageFont.truetype(FONT_PATH, FONT_SIZE) diff --git a/Tests/test_imagegrab.py b/Tests/test_imagegrab.py index a2e7a028d..97014cef8 100644 --- a/Tests/test_imagegrab.py +++ b/Tests/test_imagegrab.py @@ -9,8 +9,11 @@ try: 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": diff --git a/Tests/test_imagesequence.py b/Tests/test_imagesequence.py index 9fbf3fed8..aafb6e02e 100644 --- a/Tests/test_imagesequence.py +++ b/Tests/test_imagesequence.py @@ -32,6 +32,12 @@ class TestImageSequence(PillowTestCase): 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') for index, frame in enumerate(ImageSequence.Iterator(im)): diff --git a/Tests/test_imageshow.py b/Tests/test_imageshow.py index 899c057d6..a2b02810e 100644 --- a/Tests/test_imageshow.py +++ b/Tests/test_imageshow.py @@ -18,18 +18,19 @@ 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) diff --git a/Tests/test_imagetk.py b/Tests/test_imagetk.py index a6a4dd4ea..162c0a935 100644 --- a/Tests/test_imagetk.py +++ b/Tests/test_imagetk.py @@ -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: diff --git a/Tests/test_main.py b/Tests/test_main.py new file mode 100644 index 000000000..c9445ce9c --- /dev/null +++ b/Tests/test_main.py @@ -0,0 +1,28 @@ +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_numpy.py b/Tests/test_numpy.py index e9ef4c18f..53406c67d 100644 --- a/Tests/test_numpy.py +++ b/Tests/test_numpy.py @@ -113,12 +113,7 @@ class TestNumpy(PillowTestCase): img = Image.fromarray(arr * 255).convert('1') self.assertEqual(img.mode, '1') arr_back = numpy.array(img) - # numpy 1.8 and earlier return this as a boolean. (trusty/precise) - if arr_back.dtype == numpy.bool: - arr_bool = numpy.array([[1, 0, 0, 1, 0], [0, 1, 0, 0, 0]], numpy.bool) - numpy.testing.assert_array_equal(arr_bool, arr_back) - else: - numpy.testing.assert_array_equal(arr, arr_back) + numpy.testing.assert_array_equal(arr, arr_back) def test_save_tiff_uint16(self): # Tests that we're getting the pixel value in the right byte order. @@ -183,6 +178,14 @@ class TestNumpy(PillowTestCase): self.assertEqual(len(im.getdata()), len(arr)) + def test_roundtrip_eye(self): + for dtype in (numpy.bool, numpy.bool8, + numpy.int8, numpy.int16, numpy.int32, + numpy.uint8, numpy.uint16, numpy.uint32, + numpy.float, numpy.float32, numpy.float64): + arr = numpy.eye(10, dtype=dtype) + numpy.testing.assert_array_equal(arr, numpy.array(Image.fromarray(arr))) + def test_zero_size(self): # Shouldn't cause floating point exception # See https://github.com/python-pillow/Pillow/issues/2259 diff --git a/Tests/test_pickle.py b/Tests/test_pickle.py index 45ef0f1db..ccd08b1e1 100644 --- a/Tests/test_pickle.py +++ b/Tests/test_pickle.py @@ -75,6 +75,15 @@ class TestPickle(PillowTestCase): 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 import pickle @@ -84,6 +93,24 @@ 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_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/azure-pipelines.yml b/azure-pipelines.yml index bef5eeee6..446b3f0b0 100644 --- a/azure-pipelines.yml +++ b/azure-pipelines.yml @@ -23,13 +23,13 @@ jobs: - template: .azure-pipelines/jobs/test-docker.yml parameters: - docker: 'ubuntu-trusty-x86' - name: 'ubuntu_trusty_x86' + docker: 'ubuntu-16.04-xenial-amd64' + name: 'ubuntu_16_04_xenial_amd64' - template: .azure-pipelines/jobs/test-docker.yml parameters: - docker: 'ubuntu-xenial-amd64' - name: 'ubuntu_xenial_amd64' + docker: 'ubuntu-18.04-bionic-amd64' + name: 'ubuntu_18_04_bionic_amd64' - template: .azure-pipelines/jobs/test-docker.yml parameters: @@ -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/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/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..f3f34e30d 100644 --- a/docs/handbook/writing-your-own-file-decoder.rst +++ b/docs/handbook/writing-your-own-file-decoder.rst @@ -255,9 +255,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 9179fecc9..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 Ubuntu <= 14.04 and Debian Jessie. + 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,37 +382,35 @@ 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 | | +----------------------------------+-------------------------------+-----------------------+ -| Ubuntu Linux 14.04 LTS | 2.7, 3.5, 3.6 |x86-64 | -| +-------------------------------+-----------------------+ -| | 2.7 |x86 | -+----------------------------------+-------------------------------+-----------------------+ | Windows Server 2012 R2 | 2.7, 3.5, 3.6, 3.7 |x86, x86-64 | | +-------------------------------+-----------------------+ | | 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 ^^^^^^^^^^^^^^^ @@ -427,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..598e73abd 100644 --- a/docs/reference/Image.rst +++ b/docs/reference/Image.rst @@ -210,9 +210,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/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/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..dcb188872 100755 --- a/setup.py +++ b/setup.py @@ -439,9 +439,13 @@ 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": + 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 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 3dc13ac03..b35420328 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 @@ -425,8 +426,7 @@ def _write_multiple_frames(im, fp, palette): im_frames = [] frame_count = 0 - for imSequence in itertools.chain([im], - im.encoderinfo.get("append_images", [])): + for imSequence in itertools.chain([im], im.encoderinfo.get("append_images", [])): for im_frame in ImageSequence.Iterator(imSequence): # a copy is required here since seek can still mutate the image im_frame = _normalize_mode(im_frame.copy()) @@ -437,7 +437,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 @@ -445,44 +445,37 @@ def _write_multiple_frames(im, fp, palette): if im_frames: # delta frame previous = im_frames[-1] - if _get_palette_bytes(im_frame) == \ - _get_palette_bytes(previous['im']): - delta = ImageChops.subtract_modulo(im_frame, - previous['im']) + if _get_palette_bytes(im_frame) == _get_palette_bytes(previous["im"]): + delta = ImageChops.subtract_modulo(im_frame, previous["im"]) else: delta = ImageChops.subtract_modulo( - im_frame.convert('RGB'), previous['im'].convert('RGB')) + im_frame.convert("RGB"), previous["im"].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 - 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'] - if not frame_data['bbox']: + im_frame = frame_data["im"] + 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 @@ -541,7 +534,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 @@ -549,50 +542,53 @@ 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) 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): @@ -606,21 +602,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() @@ -666,7 +664,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 = [] @@ -674,15 +672,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 @@ -700,7 +701,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 @@ -725,9 +726,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 @@ -748,18 +749,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), ] @@ -770,13 +770,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 @@ -825,6 +827,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..ee7d2c523 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: @@ -1387,8 +1418,9 @@ class Image(object): return self.im.histogram() 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 +1478,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 +1720,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 +1749,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 +1782,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 +1824,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 +1855,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 +1864,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 +1942,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 +1976,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 +2026,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 +2040,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 +2050,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 +2142,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 +2204,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 +2249,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 +2277,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 +2313,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 +2384,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 +2392,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 +2401,7 @@ class Image(object): # -------------------------------------------------------------------- # Abstract handlers. + class ImagePointHandler(object): # used as a mixin by point transforms (for use with im.point) pass @@ -2331,6 +2418,7 @@ class ImageTransformHandler(object): # # Debugging + def _wedge(): """Create greyscale wedge (for debugging only)""" @@ -2381,13 +2469,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 +2521,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 +2573,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 +2613,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 +2635,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 +2646,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 +2655,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 +2682,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 +2698,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 +2734,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 +2792,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 +2897,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 +3011,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 +3019,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 +3072,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 +3088,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 +3138,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 +3154,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 +3175,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 +3183,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 +3281,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..b9e8f5ab6 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) @@ -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..7074a70c0 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,8 +539,11 @@ 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: ttf_filename = os.path.basename(font) @@ -289,17 +555,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 +575,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 +619,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 +717,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 +747,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 eea3d9e32..ab3e27473 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: @@ -67,16 +68,16 @@ def _save(im, fp, filename, save_all=False): raise ValueError("Resolution must be > 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 @@ -151,7 +152,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": @@ -175,16 +176,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) @@ -193,48 +193,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 / pdf_width_res), - int(height * 72.0 / pdf_height_res) - ], - 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 / pdf_width_res), + int(height * 72.0 / pdf_height_res), + ], + 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 / pdf_width_res), - int(height * 72.0 / pdf_height_res))) + "q %d 0 0 %d 0 0 cm /image Do Q\n" + % (int(width * 72.0 / pdf_width_res), int(height * 72.0 / pdf_height_res)) + ) - existing_pdf.write_obj(contents_refs[pageNumber], - stream=page_contents) + existing_pdf.write_obj(contents_refs[pageNumber], stream=page_contents) pageNumber += 1 @@ -245,6 +243,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..7596aae65 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 @@ -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)) + self.tile.append((self._compression, (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..80c86dab8 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) diff --git a/src/PIL/_tkinter_finder.py b/src/PIL/_tkinter_finder.py index 987d96211..d4f34196e 100644 --- a/src/PIL/_tkinter_finder.py +++ b/src/PIL/_tkinter_finder.py @@ -7,7 +7,7 @@ if sys.version_info.major > 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..1391ffae6 100644 --- a/src/_imaging.c +++ b/src/_imaging.c @@ -1588,7 +1588,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"); diff --git a/src/_imagingft.c b/src/_imagingft.c index 6b65e2c33..f6bd787ef 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"); @@ -345,13 +353,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 +415,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 +433,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 +485,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 +508,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 +523,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 +590,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 +618,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 +634,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 +657,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 +723,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 +752,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 +766,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 +801,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 +836,200 @@ 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)); + 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 { + 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); + if (error) + return geterror(error); + + Py_INCREF(Py_None); + return Py_None; + } +#endif + static void font_dealloc(FontObject* self) { @@ -839,6 +1045,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..10e740aaf 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; 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/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/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..a68f73993 100644 --- a/src/libImaging/TiffDecode.c +++ b/src/libImaging/TiffDecode.c @@ -275,7 +275,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/Unpack.c b/src/libImaging/Unpack.c index ccfeabaf9..02196d255 100644 --- a/src/libImaging/Unpack.c +++ b/src/libImaging/Unpack.c @@ -1280,7 +1280,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}, @@ -1369,12 +1369,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}, @@ -1417,6 +1417,7 @@ static struct { {"CMYK", "CMYKXX", 48, copy4skip2}, {"CMYK", "CMYK;I", 32, unpackCMYKI}, {"CMYK", "CMYK;L", 32, unpackRGBAL}, + {"CMYK", "CMYK;16L", 64, unpackRGBA16L}, {"CMYK", "C", 8, band0}, {"CMYK", "M", 8, band1}, {"CMYK", "Y", 8, band2}, 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/tox.ini b/tox.ini index fce4a4206..06de6f093 100644 --- a/tox.ini +++ b/tox.ini @@ -24,9 +24,11 @@ deps = [testenv:lint] commands = + black --check --diff src 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 313a14ff4..01c847bae 100755 --- a/winbuild/build.py +++ b/winbuild/build.py @@ -109,6 +109,7 @@ set INCLUDE=%%INCLUDE%%;%%INCLIB%%\%(inc_dir)s;%%INCLIB%%\tcl%(tcl_ver)s\include setlocal set LIB=%%LIB%%;C:\Python%(py_ver)s\tcl%(vc_setup)s call %(python_path)s\%(executable)s setup.py %(imaging_libs)s %%BLDOPT%% +call %(python_path)s\%(executable)s -c "from PIL import _webp;import os, shutil;shutil.copy('%%INCLIB%%\\freetype.dll', os.path.dirname(_webp.__file__));" endlocal endlocal diff --git a/winbuild/build_dep.py b/winbuild/build_dep.py index 13296dc5b..15a785267 100644 --- a/winbuild/build_dep.py +++ b/winbuild/build_dep.py @@ -113,23 +113,27 @@ 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""" 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 +""" % atts # noqa: E501 def nmake_libs(compiler, bit): @@ -187,37 +191,30 @@ endlocal return script % compiler -def msbuild_freetype(compiler): - if compiler['env_version'] == 'v7.1': - return msbuild_freetype_71(compiler) - return msbuild_freetype_70(compiler) - - -def msbuild_freetype_71(compiler): - return r""" +def msbuild_freetype(compiler, bit): + script = r""" rem Build freetype setlocal rd /S /Q %%FREETYPE%%\objs -%%MSBUILD%% %%FREETYPE%%\builds\windows\vc%(vc_version)s\freetype.sln /t:Clean;Build /p:Configuration="Release" /p:Platform=%(platform)s /m +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' + properties += r" /p:_IsNativeEnvironment=false" + script += r""" +%%MSBUILD%% %%FREETYPE%%\builds\windows\vc2010\freetype.sln /t:Clean;Build """+properties+r""" /m xcopy /Y /E /Q %%FREETYPE%%\include %%INCLIB%% -copy /Y /B %%FREETYPE%%\objs\vc%(vc_version)s\%(platform)s\*.lib %%INCLIB%%\freetype.lib +""" + 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 endlocal -""" % compiler # noqa: E501 - - -def msbuild_freetype_70(compiler): - return r""" -rem Build freetype -setlocal -py -3 %%~dp0\fixproj.py %%FREETYPE%%\builds\windows\vc%(vc_version)s\freetype.sln %(platform)s -py -3 %%~dp0\fixproj.py %%FREETYPE%%\builds\windows\vc%(vc_version)s\freetype.vcproj %(platform)s -rd /S /Q %%FREETYPE%%\objs -%%MSBUILD%% %%FREETYPE%%\builds\windows\vc%(vc_version)s\freetype.sln /t:Clean;Build /p:Configuration="LIB Release";Platform=%(platform)s /m -xcopy /Y /E /Q %%FREETYPE%%\include %%INCLIB%% -xcopy /Y /E /Q %%FREETYPE%%\objs\win32\vc%(vc_version)s %%INCLIB%% -copy /Y /B %%FREETYPE%%\objs\win32\vc%(vc_version)s\*.lib %%INCLIB%%\freetype.lib -endlocal -""" % compiler # noqa: E501 +""" + return script % compiler # noqa: E501 def build_lcms2(compiler): @@ -238,9 +235,9 @@ setlocal rd /S /Q %%LCMS%%\Lib rd /S /Q %%LCMS%%\Projects\VC%(vc_version)s\Release %%MSBUILD%% %%LCMS%%\Projects\VC%(vc_version)s\lcms2.sln /t:Clean /p:Configuration="Release" /p:Platform=Win32 /m -%%MSBUILD%% %%LCMS%%\Projects\VC%(vc_version)s\lcms2.sln /t:lcms2_static /p:Configuration="Release" /p:Platform=Win32 /m +%%MSBUILD%% %%LCMS%%\Projects\VC%(vc_version)s\lcms2.sln /t:lcms2_static /p:Configuration="Release" /p:Platform=Win32 /p:PlatformToolset=v90 /m xcopy /Y /E /Q %%LCMS%%\include %%INCLIB%% -copy /Y /B %%LCMS%%\Projects\VC%(vc_version)s\Release\*.lib %%INCLIB%% +copy /Y /B %%LCMS%%\Lib\MS\*.lib %%INCLIB%% endlocal """ % compiler # noqa: E501 @@ -265,7 +262,7 @@ rem Build gs setlocal """ + vc_setup(compiler, bit) + r""" set MSVC_VERSION=""" + { - "2008": "9", + "2010": "90", "2015": "14" }[compiler['vc_version']] + r""" set RCOMP="C:\Program Files (x86)\Microsoft SDKs\Windows\v7.1A\Bin\RC.Exe" @@ -289,9 +286,9 @@ def add_compiler(compiler, bit): # script.append(extract_openjpeg(compiler)) - script.append(msbuild_freetype(compiler)) + 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()) @@ -308,7 +305,7 @@ if 'PYTHON' in os.environ: else: # for compiler in all_compilers(): # add_compiler(compiler) - add_compiler(compilers[7.0][2008][32], 32) + add_compiler(compilers[7.0][2010][32], 32) with open('build_deps.cmd', 'w') as f: f.write("\n".join(script)) diff --git a/winbuild/config.py b/winbuild/config.py index 9001821a5..a1299f1d5 100644 --- a/winbuild/config.py +++ b/winbuild/config.py @@ -3,8 +3,8 @@ import os SF_MIRROR = 'http://iweb.dl.sourceforge.net' PILLOW_DEPENDS_DIR = 'C:\\pillow-depends\\' -pythons = {'27': {'compiler': 7, 'vc': 2008}, - 'pypy2': {'compiler': 7, 'vc': 2008}, +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}} @@ -43,9 +43,9 @@ libs = { 'dir': 'lcms2-2.7', }, 'ghostscript': { - 'url': 'https://github.com/ArtifexSoftware/ghostpdl-downloads/releases/download/gs926/ghostscript-9.26.tar.gz', # noqa: E501 - 'filename': PILLOW_DEPENDS_DIR + 'ghostscript-9.26.tar.gz', - 'dir': 'ghostscript-9.26', + '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', @@ -75,18 +75,18 @@ libs = { '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', + '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', }, } compilers = { 7: { - 2008: { + 2010: { 64: { 'env_version': 'v7.0', - 'vc_version': '2008', + 'vc_version': '2010', 'env_flags': '/x64 /xp', 'inc_dir': 'msvcr90-x64', 'platform': 'x64', @@ -94,7 +94,7 @@ compilers = { }, 32: { 'env_version': 'v7.0', - 'vc_version': '2008', + 'vc_version': '2010', 'env_flags': '/x86 /xp', 'inc_dir': 'msvcr90-x32', 'platform': 'Win32', 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)