mirror of
				https://github.com/python-pillow/Pillow.git
				synced 2025-10-31 16:07:30 +03:00 
			
		
		
		
	Merge pull request #5768 from hugovk/rm-3.6
Drop support for soon-EOL Python 3.6
This commit is contained in:
		
						commit
						a70e3c828f
					
				|  | @ -13,7 +13,7 @@ environment: | ||||||
|   - PYTHON: C:/Python310 |   - PYTHON: C:/Python310 | ||||||
|     ARCHITECTURE: x86 |     ARCHITECTURE: x86 | ||||||
|     APPVEYOR_BUILD_WORKER_IMAGE: Visual Studio 2019 |     APPVEYOR_BUILD_WORKER_IMAGE: Visual Studio 2019 | ||||||
|   - PYTHON: C:/Python36-x64 |   - PYTHON: C:/Python37-x64 | ||||||
|     ARCHITECTURE: x64 |     ARCHITECTURE: x64 | ||||||
|     APPVEYOR_BUILD_WORKER_IMAGE: Visual Studio 2017 |     APPVEYOR_BUILD_WORKER_IMAGE: Visual Studio 2017 | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
|  | @ -3,7 +3,7 @@ repos: | ||||||
|     rev: 911470a610e47d9da5ea938b0887c3df62819b85  # frozen: 21.9b0 |     rev: 911470a610e47d9da5ea938b0887c3df62819b85  # frozen: 21.9b0 | ||||||
|     hooks: |     hooks: | ||||||
|       - id: black |       - id: black | ||||||
|         args: ["--target-version", "py36"] |         args: ["--target-version", "py37"] | ||||||
|         # Only .py files, until https://github.com/psf/black/issues/402 resolved |         # Only .py files, until https://github.com/psf/black/issues/402 resolved | ||||||
|         files: \.py$ |         files: \.py$ | ||||||
|         types: [] |         types: [] | ||||||
|  |  | ||||||
							
								
								
									
										2
									
								
								Makefile
									
									
									
									
									
								
							
							
						
						
									
										2
									
								
								Makefile
									
									
									
									
									
								
							|  | @ -122,5 +122,5 @@ lint: | ||||||
| 
 | 
 | ||||||
| .PHONY: lint-fix | .PHONY: lint-fix | ||||||
| lint-fix: | lint-fix: | ||||||
| 	black --target-version py36 . | 	black --target-version py37 . | ||||||
| 	isort . | 	isort . | ||||||
|  |  | ||||||
|  | @ -818,7 +818,7 @@ def test_palette_save_P(tmp_path): | ||||||
|     # Forcing a non-straight grayscale palette. |     # Forcing a non-straight grayscale palette. | ||||||
| 
 | 
 | ||||||
|     im = hopper("P") |     im = hopper("P") | ||||||
|     palette = bytes([255 - i // 3 for i in range(768)]) |     palette = bytes(255 - i // 3 for i in range(768)) | ||||||
| 
 | 
 | ||||||
|     out = str(tmp_path / "temp.gif") |     out = str(tmp_path / "temp.gif") | ||||||
|     im.save(out, palette=palette) |     im.save(out, palette=palette) | ||||||
|  | @ -885,7 +885,7 @@ def test_getdata(): | ||||||
|     im.putpalette(ImagePalette.ImagePalette("RGB")) |     im.putpalette(ImagePalette.ImagePalette("RGB")) | ||||||
|     im.info = {"background": 0} |     im.info = {"background": 0} | ||||||
| 
 | 
 | ||||||
|     passed_palette = bytes([255 - i // 3 for i in range(768)]) |     passed_palette = bytes(255 - i // 3 for i in range(768)) | ||||||
| 
 | 
 | ||||||
|     GifImagePlugin._FORCE_OPTIMIZE = True |     GifImagePlugin._FORCE_OPTIMIZE = True | ||||||
|     try: |     try: | ||||||
|  |  | ||||||
|  | @ -85,26 +85,26 @@ class TestFileJpeg: | ||||||
|         f = "Tests/images/pil_sample_cmyk.jpg" |         f = "Tests/images/pil_sample_cmyk.jpg" | ||||||
|         with Image.open(f) as im: |         with Image.open(f) as im: | ||||||
|             # the source image has red pixels in the upper left corner. |             # the source image has red pixels in the upper left corner. | ||||||
|             c, m, y, k = [x / 255.0 for x in im.getpixel((0, 0))] |             c, m, y, k = (x / 255.0 for x in im.getpixel((0, 0))) | ||||||
|             assert c == 0.0 |             assert c == 0.0 | ||||||
|             assert m > 0.8 |             assert m > 0.8 | ||||||
|             assert y > 0.8 |             assert y > 0.8 | ||||||
|             assert k == 0.0 |             assert k == 0.0 | ||||||
|             # the opposite corner is black |             # the opposite corner is black | ||||||
|             c, m, y, k = [ |             c, m, y, k = ( | ||||||
|                 x / 255.0 for x in im.getpixel((im.size[0] - 1, im.size[1] - 1)) |                 x / 255.0 for x in im.getpixel((im.size[0] - 1, im.size[1] - 1)) | ||||||
|             ] |             ) | ||||||
|             assert k > 0.9 |             assert k > 0.9 | ||||||
|             # roundtrip, and check again |             # roundtrip, and check again | ||||||
|             im = self.roundtrip(im) |             im = self.roundtrip(im) | ||||||
|             c, m, y, k = [x / 255.0 for x in im.getpixel((0, 0))] |             c, m, y, k = (x / 255.0 for x in im.getpixel((0, 0))) | ||||||
|             assert c == 0.0 |             assert c == 0.0 | ||||||
|             assert m > 0.8 |             assert m > 0.8 | ||||||
|             assert y > 0.8 |             assert y > 0.8 | ||||||
|             assert k == 0.0 |             assert k == 0.0 | ||||||
|             c, m, y, k = [ |             c, m, y, k = ( | ||||||
|                 x / 255.0 for x in im.getpixel((im.size[0] - 1, im.size[1] - 1)) |                 x / 255.0 for x in im.getpixel((im.size[0] - 1, im.size[1] - 1)) | ||||||
|             ] |             ) | ||||||
|             assert k > 0.9 |             assert k > 0.9 | ||||||
| 
 | 
 | ||||||
|     @pytest.mark.parametrize( |     @pytest.mark.parametrize( | ||||||
|  |  | ||||||
|  | @ -188,9 +188,7 @@ class TestFileWebp: | ||||||
| 
 | 
 | ||||||
|         with Image.open(out_gif) as reread: |         with Image.open(out_gif) as reread: | ||||||
|             reread_value = reread.convert("RGB").getpixel((1, 1)) |             reread_value = reread.convert("RGB").getpixel((1, 1)) | ||||||
|         difference = sum( |         difference = sum(abs(original_value[i] - reread_value[i]) for i in range(0, 3)) | ||||||
|             [abs(original_value[i] - reread_value[i]) for i in range(0, 3)] |  | ||||||
|         ) |  | ||||||
|         assert difference < 5 |         assert difference < 5 | ||||||
| 
 | 
 | ||||||
|     @skip_unless_feature("webp") |     @skip_unless_feature("webp") | ||||||
|  |  | ||||||
|  | @ -1,7 +1,6 @@ | ||||||
| import io | import io | ||||||
| import os | import os | ||||||
| import shutil | import shutil | ||||||
| import sys |  | ||||||
| import tempfile | import tempfile | ||||||
| 
 | 
 | ||||||
| import pytest | import pytest | ||||||
|  | @ -782,9 +781,6 @@ class TestImage: | ||||||
|                 34665: 196, |                 34665: 196, | ||||||
|             } |             } | ||||||
| 
 | 
 | ||||||
|     @pytest.mark.skipif( |  | ||||||
|         sys.version_info < (3, 7), reason="Python 3.7 or greater required" |  | ||||||
|     ) |  | ||||||
|     def test_categories_deprecation(self): |     def test_categories_deprecation(self): | ||||||
|         with pytest.warns(DeprecationWarning): |         with pytest.warns(DeprecationWarning): | ||||||
|             assert hopper().category == 0 |             assert hopper().category == 0 | ||||||
|  |  | ||||||
|  | @ -900,7 +900,7 @@ class TestImageFont: | ||||||
|             d.text((10, 10), "\U0001f469", font=font, embedded_color=True) |             d.text((10, 10), "\U0001f469", font=font, embedded_color=True) | ||||||
| 
 | 
 | ||||||
|             assert_image_similar_tofile(im, "Tests/images/cbdt_notocoloremoji.png", 6.2) |             assert_image_similar_tofile(im, "Tests/images/cbdt_notocoloremoji.png", 6.2) | ||||||
|         except IOError as e:  # pragma: no cover |         except OSError as e:  # pragma: no cover | ||||||
|             assert str(e) in ("unimplemented feature", "unknown file format") |             assert str(e) in ("unimplemented feature", "unknown file format") | ||||||
|             pytest.skip("freetype compiled without libpng or CBDT support") |             pytest.skip("freetype compiled without libpng or CBDT support") | ||||||
| 
 | 
 | ||||||
|  | @ -920,7 +920,7 @@ class TestImageFont: | ||||||
|             assert_image_similar_tofile( |             assert_image_similar_tofile( | ||||||
|                 im, "Tests/images/cbdt_notocoloremoji_mask.png", 6.2 |                 im, "Tests/images/cbdt_notocoloremoji_mask.png", 6.2 | ||||||
|             ) |             ) | ||||||
|         except IOError as e:  # pragma: no cover |         except OSError as e:  # pragma: no cover | ||||||
|             assert str(e) in ("unimplemented feature", "unknown file format") |             assert str(e) in ("unimplemented feature", "unknown file format") | ||||||
|             pytest.skip("freetype compiled without libpng or CBDT support") |             pytest.skip("freetype compiled without libpng or CBDT support") | ||||||
| 
 | 
 | ||||||
|  | @ -938,7 +938,7 @@ class TestImageFont: | ||||||
|             d.text((50, 50), "\uE901", font=font, embedded_color=True) |             d.text((50, 50), "\uE901", font=font, embedded_color=True) | ||||||
| 
 | 
 | ||||||
|             assert_image_similar_tofile(im, "Tests/images/chromacheck-sbix.png", 1) |             assert_image_similar_tofile(im, "Tests/images/chromacheck-sbix.png", 1) | ||||||
|         except IOError as e:  # pragma: no cover |         except OSError as e:  # pragma: no cover | ||||||
|             assert str(e) in ("unimplemented feature", "unknown file format") |             assert str(e) in ("unimplemented feature", "unknown file format") | ||||||
|             pytest.skip("freetype compiled without libpng or SBIX support") |             pytest.skip("freetype compiled without libpng or SBIX support") | ||||||
| 
 | 
 | ||||||
|  | @ -956,7 +956,7 @@ class TestImageFont: | ||||||
|             d.text((50, 50), "\uE901", (100, 0, 0), font=font) |             d.text((50, 50), "\uE901", (100, 0, 0), font=font) | ||||||
| 
 | 
 | ||||||
|             assert_image_similar_tofile(im, "Tests/images/chromacheck-sbix_mask.png", 1) |             assert_image_similar_tofile(im, "Tests/images/chromacheck-sbix_mask.png", 1) | ||||||
|         except IOError as e:  # pragma: no cover |         except OSError as e:  # pragma: no cover | ||||||
|             assert str(e) in ("unimplemented feature", "unknown file format") |             assert str(e) in ("unimplemented feature", "unknown file format") | ||||||
|             pytest.skip("freetype compiled without libpng or SBIX support") |             pytest.skip("freetype compiled without libpng or SBIX support") | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
|  | @ -3,7 +3,7 @@ from PIL import Image, ImageMath | ||||||
| 
 | 
 | ||||||
| def pixel(im): | def pixel(im): | ||||||
|     if hasattr(im, "im"): |     if hasattr(im, "im"): | ||||||
|         return "{} {}".format(im.mode, repr(im.getpixel((0, 0)))) |         return f"{im.mode} {repr(im.getpixel((0, 0)))}" | ||||||
|     else: |     else: | ||||||
|         if isinstance(im, int): |         if isinstance(im, int): | ||||||
|             return int(im)  # hack to deal with booleans |             return int(im)  # hack to deal with booleans | ||||||
|  |  | ||||||
|  | @ -18,7 +18,9 @@ Pillow supports these Python versions. | ||||||
| +----------------------+-----+-----+-----+-----+-----+-----+-----+-----+ | +----------------------+-----+-----+-----+-----+-----+-----+-----+-----+ | ||||||
| |        Python        |3.10 | 3.9 | 3.8 | 3.7 | 3.6 | 3.5 | 3.4 | 2.7 | | |        Python        |3.10 | 3.9 | 3.8 | 3.7 | 3.6 | 3.5 | 3.4 | 2.7 | | ||||||
| +======================+=====+=====+=====+=====+=====+=====+=====+=====+ | +======================+=====+=====+=====+=====+=====+=====+=====+=====+ | ||||||
| | Pillow >= 8.3.2      | Yes | Yes | Yes | Yes | Yes |     |     |     | | | Pillow >= 9.0        | Yes | Yes | Yes | Yes |     |     |     |     | | ||||||
|  | +----------------------+-----+-----+-----+-----+-----+-----+-----+-----+ | ||||||
|  | | Pillow 8.3.2 - 8.4   | Yes | Yes | Yes | Yes | Yes |     |     |     | | ||||||
| +----------------------+-----+-----+-----+-----+-----+-----+-----+-----+ | +----------------------+-----+-----+-----+-----+-----+-----+-----+-----+ | ||||||
| | Pillow 8.0 - 8.3.1   |     | Yes | Yes | Yes | Yes |     |     |     | | | Pillow 8.0 - 8.3.1   |     | Yes | Yes | Yes | Yes |     |     |     | | ||||||
| +----------------------+-----+-----+-----+-----+-----+-----+-----+-----+ | +----------------------+-----+-----+-----+-----+-----+-----+-----+-----+ | ||||||
|  | @ -443,42 +445,42 @@ Continuous Integration Targets | ||||||
| 
 | 
 | ||||||
| These platforms are built and tested for every change. | These platforms are built and tested for every change. | ||||||
| 
 | 
 | ||||||
| +----------------------------------+---------------------------------+---------------------+ | +----------------------------------+----------------------------+---------------------+ | ||||||
| | Operating system                 | Tested Python versions          | Tested architecture | | | Operating system                 | Tested Python versions     | Tested architecture | | ||||||
| +==================================+=================================+=====================+ | +==================================+============================+=====================+ | ||||||
| | Alpine                           | 3.9                             | x86-64              | | | Alpine                           | 3.9                        | x86-64              | | ||||||
| +----------------------------------+---------------------------------+---------------------+ | +----------------------------------+----------------------------+---------------------+ | ||||||
| | Amazon Linux 2                   | 3.7                             | x86-64              | | | Amazon Linux 2                   | 3.7                        | x86-64              | | ||||||
| +----------------------------------+---------------------------------+---------------------+ | +----------------------------------+----------------------------+---------------------+ | ||||||
| | Arch                             | 3.9                             | x86-64              | | | Arch                             | 3.9                        | x86-64              | | ||||||
| +----------------------------------+---------------------------------+---------------------+ | +----------------------------------+----------------------------+---------------------+ | ||||||
| | CentOS 7                         | 3.6                             | x86-64              | | | CentOS 7                         | 3.9                        | x86-64              | | ||||||
| +----------------------------------+---------------------------------+---------------------+ | +----------------------------------+----------------------------+---------------------+ | ||||||
| | CentOS 8                         | 3.6                             | x86-64              | | | CentOS 8                         | 3.9                        | x86-64              | | ||||||
| +----------------------------------+---------------------------------+---------------------+ | +----------------------------------+----------------------------+---------------------+ | ||||||
| | CentOS Stream 8                  | 3.6                             | x86-64              | | | CentOS Stream 8                  | 3.9                        | x86-64              | | ||||||
| +----------------------------------+---------------------------------+---------------------+ | +----------------------------------+----------------------------+---------------------+ | ||||||
| | Debian 10 Buster                 | 3.7                             | x86                 | | | Debian 10 Buster                 | 3.7                        | x86                 | | ||||||
| +----------------------------------+---------------------------------+---------------------+ | +----------------------------------+----------------------------+---------------------+ | ||||||
| | Fedora 34                        | 3.9                             | x86-64              | | | Fedora 34                        | 3.9                        | x86-64              | | ||||||
| +----------------------------------+---------------------------------+---------------------+ | +----------------------------------+----------------------------+---------------------+ | ||||||
| | Fedora 35                        | 3.10                            | x86-64              | | | Fedora 35                        | 3.10                       | x86-64              | | ||||||
| +----------------------------------+---------------------------------+---------------------+ | +----------------------------------+----------------------------+---------------------+ | ||||||
| | macOS 10.15 Catalina             | 3.6, 3.7, 3.8, 3.9, 3.10, PyPy3 | x86-64              | | | macOS 10.15 Catalina             | 3.7, 3.8, 3.9, 3.10, PyPy3 | x86-64              | | ||||||
| +----------------------------------+---------------------------------+---------------------+ | +----------------------------------+----------------------------+---------------------+ | ||||||
| | Ubuntu Linux 18.04 LTS (Bionic)  | 3.6                             | x86-64              | | | Ubuntu Linux 18.04 LTS (Bionic)  | 3.9                        | x86-64              | | ||||||
| +----------------------------------+---------------------------------+---------------------+ | +----------------------------------+----------------------------+---------------------+ | ||||||
| | Ubuntu Linux 20.04 LTS (Focal)   | 3.6, 3.7, 3.8, 3.9, 3.10, PyPy3 | x86-64              | | | Ubuntu Linux 20.04 LTS (Focal)   | 3.7, 3.8, 3.9, 3.10, PyPy3 | x86-64              | | ||||||
| |                                  +---------------------------------+---------------------+ | |                                  +----------------------------+---------------------+ | ||||||
| |                                  | 3.8                             | arm64v8, ppc64le,   | | |                                  | 3.8                        | arm64v8, ppc64le,   | | ||||||
| |                                  |                                 | s390x               | | |                                  |                            | s390x               | | ||||||
| +----------------------------------+---------------------------------+---------------------+ | +----------------------------------+----------------------------+---------------------+ | ||||||
| | Windows Server 2016              | 3.6                             | x86-64              | | | Windows Server 2016              | 3.7                        | x86-64              | | ||||||
| +----------------------------------+---------------------------------+---------------------+ | +----------------------------------+----------------------------+---------------------+ | ||||||
| | Windows Server 2019              | 3.6, 3.7, 3.8, 3.9, 3.10, PyPy3 | x86, x86-64         | | | Windows Server 2019              | 3.7, 3.8, 3.9, 3.10, PyPy3 | x86, x86-64         | | ||||||
| |                                  +---------------------------------+---------------------+ | |                                  +----------------------------+---------------------+ | ||||||
| |                                  | 3.9/MinGW                       | x86, x86-64         | | |                                  | 3.9/MinGW                  | x86, x86-64         | | ||||||
| +----------------------------------+---------------------------------+---------------------+ | +----------------------------------+----------------------------+---------------------+ | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
| Other Platforms | Other Platforms | ||||||
|  |  | ||||||
|  | @ -4,6 +4,11 @@ | ||||||
| Backwards Incompatible Changes | Backwards Incompatible Changes | ||||||
| ============================== | ============================== | ||||||
| 
 | 
 | ||||||
|  | Python 3.6 | ||||||
|  | ^^^^^^^^^^ | ||||||
|  | 
 | ||||||
|  | Pillow has dropped support for Python 3.6, which reached end-of-life on 2021-12-23. | ||||||
|  | 
 | ||||||
| PILLOW_VERSION constant | PILLOW_VERSION constant | ||||||
| ^^^^^^^^^^^^^^^^^^^^^^^ | ^^^^^^^^^^^^^^^^^^^^^^^ | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
|  | @ -12,7 +12,6 @@ classifiers = | ||||||
|     License :: OSI Approved :: Historical Permission Notice and Disclaimer (HPND) |     License :: OSI Approved :: Historical Permission Notice and Disclaimer (HPND) | ||||||
|     Programming Language :: Python :: 3 |     Programming Language :: Python :: 3 | ||||||
|     Programming Language :: Python :: 3 :: Only |     Programming Language :: Python :: 3 :: Only | ||||||
|     Programming Language :: Python :: 3.6 |  | ||||||
|     Programming Language :: Python :: 3.7 |     Programming Language :: Python :: 3.7 | ||||||
|     Programming Language :: Python :: 3.8 |     Programming Language :: Python :: 3.8 | ||||||
|     Programming Language :: Python :: 3.9 |     Programming Language :: Python :: 3.9 | ||||||
|  | @ -34,7 +33,7 @@ project_urls = | ||||||
|     Twitter=https://twitter.com/PythonPillow |     Twitter=https://twitter.com/PythonPillow | ||||||
| 
 | 
 | ||||||
| [options] | [options] | ||||||
| python_requires = >=3.6 | python_requires = >=3.7 | ||||||
| 
 | 
 | ||||||
| [flake8] | [flake8] | ||||||
| extend-ignore = E203 | extend-ignore = E203 | ||||||
|  |  | ||||||
|  | @ -324,7 +324,7 @@ class GifImageFile(ImageFile.ImageFile): | ||||||
|         if not self.im and "transparency" in self.info: |         if not self.im and "transparency" in self.info: | ||||||
|             self.im = Image.core.fill(self.mode, self.size, self.info["transparency"]) |             self.im = Image.core.fill(self.mode, self.size, self.info["transparency"]) | ||||||
| 
 | 
 | ||||||
|         super(GifImageFile, self).load_prepare() |         super().load_prepare() | ||||||
| 
 | 
 | ||||||
|     def tell(self): |     def tell(self): | ||||||
|         return self.__frame |         return self.__frame | ||||||
|  |  | ||||||
|  | @ -51,26 +51,18 @@ from . import ImageMode, TiffTags, UnidentifiedImageError, __version__, _plugins | ||||||
| from ._binary import i32le | from ._binary import i32le | ||||||
| from ._util import deferred_error, isPath | from ._util import deferred_error, isPath | ||||||
| 
 | 
 | ||||||
| if sys.version_info >= (3, 7): |  | ||||||
| 
 | 
 | ||||||
|     def __getattr__(name): | def __getattr__(name): | ||||||
|         categories = {"NORMAL": 0, "SEQUENCE": 1, "CONTAINER": 2} |     categories = {"NORMAL": 0, "SEQUENCE": 1, "CONTAINER": 2} | ||||||
|         if name in categories: |     if name in categories: | ||||||
|             warnings.warn( |         warnings.warn( | ||||||
|                 "Image categories are deprecated and will be removed in Pillow 10 " |             "Image categories are deprecated and will be removed in Pillow 10 " | ||||||
|                 "(2023-07-01). Use is_animated instead.", |             "(2023-07-01). Use is_animated instead.", | ||||||
|                 DeprecationWarning, |             DeprecationWarning, | ||||||
|                 stacklevel=2, |             stacklevel=2, | ||||||
|             ) |         ) | ||||||
|             return categories[name] |         return categories[name] | ||||||
|         raise AttributeError(f"module '{__name__}' has no attribute '{name}'") |     raise AttributeError(f"module '{__name__}' has no attribute '{name}'") | ||||||
| 
 |  | ||||||
| 
 |  | ||||||
| else: |  | ||||||
|     # categories |  | ||||||
|     NORMAL = 0 |  | ||||||
|     SEQUENCE = 1 |  | ||||||
|     CONTAINER = 2 |  | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
| logger = logging.getLogger(__name__) | logger = logging.getLogger(__name__) | ||||||
|  | @ -926,12 +918,8 @@ class Image: | ||||||
|                     transparency = convert_transparency(matrix, transparency) |                     transparency = convert_transparency(matrix, transparency) | ||||||
|                 elif len(mode) == 3: |                 elif len(mode) == 3: | ||||||
|                     transparency = tuple( |                     transparency = tuple( | ||||||
|                         [ |                         convert_transparency(matrix[i * 4 : i * 4 + 4], transparency) | ||||||
|                             convert_transparency( |                         for i in range(0, len(transparency)) | ||||||
|                                 matrix[i * 4 : i * 4 + 4], transparency |  | ||||||
|                             ) |  | ||||||
|                             for i in range(0, len(transparency)) |  | ||||||
|                         ] |  | ||||||
|                     ) |                     ) | ||||||
|                 new.info["transparency"] = transparency |                 new.info["transparency"] = transparency | ||||||
|             return new |             return new | ||||||
|  | @ -1934,7 +1922,7 @@ class Image: | ||||||
|             message = f"Unknown resampling filter ({resample})." |             message = f"Unknown resampling filter ({resample})." | ||||||
| 
 | 
 | ||||||
|             filters = [ |             filters = [ | ||||||
|                 "{} ({})".format(filter[1], filter[0]) |                 f"{filter[1]} ({filter[0]})" | ||||||
|                 for filter in ( |                 for filter in ( | ||||||
|                     (NEAREST, "Image.NEAREST"), |                     (NEAREST, "Image.NEAREST"), | ||||||
|                     (LANCZOS, "Image.LANCZOS"), |                     (LANCZOS, "Image.LANCZOS"), | ||||||
|  | @ -2529,7 +2517,7 @@ class Image: | ||||||
|                 message = f"Unknown resampling filter ({resample})." |                 message = f"Unknown resampling filter ({resample})." | ||||||
| 
 | 
 | ||||||
|             filters = [ |             filters = [ | ||||||
|                 "{} ({})".format(filter[1], filter[0]) |                 f"{filter[1]} ({filter[0]})" | ||||||
|                 for filter in ( |                 for filter in ( | ||||||
|                     (NEAREST, "Image.NEAREST"), |                     (NEAREST, "Image.NEAREST"), | ||||||
|                     (BILINEAR, "Image.BILINEAR"), |                     (BILINEAR, "Image.BILINEAR"), | ||||||
|  |  | ||||||
|  | @ -174,13 +174,11 @@ class ImageDraw: | ||||||
|                         angle -= 90 |                         angle -= 90 | ||||||
|                         distance = width / 2 - 1 |                         distance = width / 2 - 1 | ||||||
|                         return tuple( |                         return tuple( | ||||||
|                             [ |                             p + (math.floor(p_d) if p_d > 0 else math.ceil(p_d)) | ||||||
|                                 p + (math.floor(p_d) if p_d > 0 else math.ceil(p_d)) |                             for p, p_d in ( | ||||||
|                                 for p, p_d in ( |                                 (x, distance * math.cos(math.radians(angle))), | ||||||
|                                     (x, distance * math.cos(math.radians(angle))), |                                 (y, distance * math.sin(math.radians(angle))), | ||||||
|                                     (y, distance * math.sin(math.radians(angle))), |                             ) | ||||||
|                                 ) |  | ||||||
|                             ] |  | ||||||
|                         ) |                         ) | ||||||
| 
 | 
 | ||||||
|                     flipped = ( |                     flipped = ( | ||||||
|  | @ -979,6 +977,6 @@ def _color_diff(color1, color2): | ||||||
|     Uses 1-norm distance to calculate difference between two values. |     Uses 1-norm distance to calculate difference between two values. | ||||||
|     """ |     """ | ||||||
|     if isinstance(color2, tuple): |     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: |     else: | ||||||
|         return abs(color1 - color2) |         return abs(color1 - color2) | ||||||
|  |  | ||||||
|  | @ -425,7 +425,7 @@ class PdfParser: | ||||||
|         self.f.write(b"%PDF-1.4\n") |         self.f.write(b"%PDF-1.4\n") | ||||||
| 
 | 
 | ||||||
|     def write_comment(self, s): |     def write_comment(self, s): | ||||||
|         self.f.write(f"% {s}\n".encode("utf-8")) |         self.f.write(f"% {s}\n".encode()) | ||||||
| 
 | 
 | ||||||
|     def write_catalog(self): |     def write_catalog(self): | ||||||
|         self.del_root() |         self.del_root() | ||||||
|  | @ -862,7 +862,7 @@ class PdfParser: | ||||||
|         if m: |         if m: | ||||||
|             # filter out whitespace |             # filter out whitespace | ||||||
|             hex_string = bytearray( |             hex_string = bytearray( | ||||||
|                 [b for b in m.group(1) if b in b"0123456789abcdefABCDEF"] |                 b for b in m.group(1) if b in b"0123456789abcdefABCDEF" | ||||||
|             ) |             ) | ||||||
|             if len(hex_string) % 2 == 1: |             if len(hex_string) % 2 == 1: | ||||||
|                 # append a 0 if the length is not even - yes, at the end |                 # append a 0 if the length is not even - yes, at the end | ||||||
|  |  | ||||||
|  | @ -674,7 +674,7 @@ class ImageFileDirectory_v2(MutableMapping): | ||||||
|         _load_dispatch[idx] = (  # noqa: F821 |         _load_dispatch[idx] = (  # noqa: F821 | ||||||
|             size, |             size, | ||||||
|             lambda self, data, legacy_api=True: ( |             lambda self, data, legacy_api=True: ( | ||||||
|                 self._unpack("{}{}".format(len(data) // size, fmt), data) |                 self._unpack(f"{len(data) // size}{fmt}", data) | ||||||
|             ), |             ), | ||||||
|         ) |         ) | ||||||
|         _write_dispatch[idx] = lambda self, *values: (  # noqa: F821 |         _write_dispatch[idx] = lambda self, *values: (  # noqa: F821 | ||||||
|  | @ -718,7 +718,7 @@ class ImageFileDirectory_v2(MutableMapping): | ||||||
| 
 | 
 | ||||||
|     @_register_loader(5, 8) |     @_register_loader(5, 8) | ||||||
|     def load_rational(self, data, legacy_api=True): |     def load_rational(self, data, legacy_api=True): | ||||||
|         vals = self._unpack("{}L".format(len(data) // 4), data) |         vals = self._unpack(f"{len(data) // 4}L", data) | ||||||
| 
 | 
 | ||||||
|         def combine(a, b): |         def combine(a, b): | ||||||
|             return (a, b) if legacy_api else IFDRational(a, b) |             return (a, b) if legacy_api else IFDRational(a, b) | ||||||
|  | @ -741,7 +741,7 @@ class ImageFileDirectory_v2(MutableMapping): | ||||||
| 
 | 
 | ||||||
|     @_register_loader(10, 8) |     @_register_loader(10, 8) | ||||||
|     def load_signed_rational(self, data, legacy_api=True): |     def load_signed_rational(self, data, legacy_api=True): | ||||||
|         vals = self._unpack("{}l".format(len(data) // 4), data) |         vals = self._unpack(f"{len(data) // 4}l", data) | ||||||
| 
 | 
 | ||||||
|         def combine(a, b): |         def combine(a, b): | ||||||
|             return (a, b) if legacy_api else IFDRational(a, b) |             return (a, b) if legacy_api else IFDRational(a, b) | ||||||
|  |  | ||||||
|  | @ -9,12 +9,6 @@ | ||||||
| 
 | 
 | ||||||
| #include "Python.h" | #include "Python.h" | ||||||
| 
 | 
 | ||||||
| /* Workaround issue #2479 */ |  | ||||||
| #if PY_VERSION_HEX < 0x03070000 && defined(PySlice_GetIndicesEx) && \ |  | ||||||
|     !defined(PYPY_VERSION) |  | ||||||
| #undef PySlice_GetIndicesEx |  | ||||||
| #endif |  | ||||||
| 
 |  | ||||||
| /* Check that we have an ANSI compliant compiler */ | /* Check that we have an ANSI compliant compiler */ | ||||||
| #ifndef HAVE_PROTOTYPES | #ifndef HAVE_PROTOTYPES | ||||||
| #error Sorry, this library requires support for ANSI prototypes. | #error Sorry, this library requires support for ANSI prototypes. | ||||||
|  |  | ||||||
							
								
								
									
										2
									
								
								tox.ini
									
									
									
									
									
								
							
							
						
						
									
										2
									
								
								tox.ini
									
									
									
									
									
								
							|  | @ -6,7 +6,7 @@ | ||||||
| [tox] | [tox] | ||||||
| envlist = | envlist = | ||||||
|     lint |     lint | ||||||
|     py{36,37,38,39,310,py3} |     py{37,38,39,310,py3} | ||||||
| minversion = 1.9 | minversion = 1.9 | ||||||
| 
 | 
 | ||||||
| [testenv] | [testenv] | ||||||
|  |  | ||||||
|  | @ -474,8 +474,6 @@ def build_pillow(): | ||||||
|         cmd_cd("{pillow_dir}"), |         cmd_cd("{pillow_dir}"), | ||||||
|         *prefs["header"], |         *prefs["header"], | ||||||
|         cmd_set("DISTUTILS_USE_SDK", "1"),  # use same compiler to build Pillow |         cmd_set("DISTUTILS_USE_SDK", "1"),  # use same compiler to build Pillow | ||||||
|         cmd_set("MSSdk", "1"),  # for PyPy3.6 |  | ||||||
|         cmd_set("py_vcruntime_redist", "true"),  # use /MD, not /MT |  | ||||||
|         r'"{python_dir}\{python_exe}" setup.py build_ext --vendor-raqm --vendor-fribidi %*',  # noqa: E501 |         r'"{python_dir}\{python_exe}" setup.py build_ext --vendor-raqm --vendor-fribidi %*',  # noqa: E501 | ||||||
|     ] |     ] | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
		Loading…
	
		Reference in New Issue
	
	Block a user