mirror of
				https://github.com/python-pillow/Pillow.git
				synced 2025-10-31 16:07:30 +03:00 
			
		
		
		
	Merge branch 'main' into comment_correct_placement
This commit is contained in:
		
						commit
						db76eaa12c
					
				
							
								
								
									
										12
									
								
								.github/workflows/test-docker.yml
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										12
									
								
								.github/workflows/test-docker.yml
									
									
									
									
										vendored
									
									
								
							|  | @ -11,9 +11,9 @@ jobs: | ||||||
|       matrix: |       matrix: | ||||||
|         docker: [ |         docker: [ | ||||||
|           # Run slower jobs first to give them a headstart and reduce waiting time |           # Run slower jobs first to give them a headstart and reduce waiting time | ||||||
|           ubuntu-20.04-focal-arm64v8, |           ubuntu-22.04-jammy-arm64v8, | ||||||
|           ubuntu-20.04-focal-ppc64le, |           ubuntu-22.04-jammy-ppc64le, | ||||||
|           ubuntu-20.04-focal-s390x, |           ubuntu-22.04-jammy-s390x, | ||||||
|           # Then run the remainder |           # Then run the remainder | ||||||
|           alpine, |           alpine, | ||||||
|           amazon-2-amd64, |           amazon-2-amd64, | ||||||
|  | @ -32,11 +32,11 @@ jobs: | ||||||
|         ] |         ] | ||||||
|         dockerTag: [main] |         dockerTag: [main] | ||||||
|         include: |         include: | ||||||
|           - docker: "ubuntu-20.04-focal-arm64v8" |           - docker: "ubuntu-22.04-jammy-arm64v8" | ||||||
|             qemu-arch: "aarch64" |             qemu-arch: "aarch64" | ||||||
|           - docker: "ubuntu-20.04-focal-ppc64le" |           - docker: "ubuntu-22.04-jammy-ppc64le" | ||||||
|             qemu-arch: "ppc64le" |             qemu-arch: "ppc64le" | ||||||
|           - docker: "ubuntu-20.04-focal-s390x" |           - docker: "ubuntu-22.04-jammy-s390x" | ||||||
|             qemu-arch: "s390x" |             qemu-arch: "s390x" | ||||||
| 
 | 
 | ||||||
|     name: ${{ matrix.docker }} |     name: ${{ matrix.docker }} | ||||||
|  |  | ||||||
							
								
								
									
										30
									
								
								CHANGES.rst
									
									
									
									
									
								
							
							
						
						
									
										30
									
								
								CHANGES.rst
									
									
									
									
									
								
							|  | @ -5,6 +5,24 @@ Changelog (Pillow) | ||||||
| 9.2.0 (unreleased) | 9.2.0 (unreleased) | ||||||
| ------------------ | ------------------ | ||||||
| 
 | 
 | ||||||
|  | - Separate multiple GIF comment blocks with newlines #6294 | ||||||
|  |   [raygard, radarhere] | ||||||
|  | 
 | ||||||
|  | - Always use GIF89a for comments #6292 | ||||||
|  |   [raygard, radarhere] | ||||||
|  | 
 | ||||||
|  | - Ignore compression value from BMP info dictionary when saving as TIFF #6231 | ||||||
|  |   [radarhere] | ||||||
|  | 
 | ||||||
|  | - If font is file-like object, do not re-read from object to get variant #6234 | ||||||
|  |   [radarhere] | ||||||
|  | 
 | ||||||
|  | - Raise ValueError when trying to access internal fp after close #6213 | ||||||
|  |   [radarhere] | ||||||
|  | 
 | ||||||
|  | - Support more affine expression forms in im.point() #6254 | ||||||
|  |   [benrg, radarhere] | ||||||
|  | 
 | ||||||
| - Populate Python palette in fromarray() #6283 | - Populate Python palette in fromarray() #6283 | ||||||
|   [radarhere] |   [radarhere] | ||||||
| 
 | 
 | ||||||
|  | @ -17,9 +35,6 @@ Changelog (Pillow) | ||||||
| - Adjust BITSPERSAMPLE to match SAMPLESPERPIXEL when opening TIFFs #6270 | - Adjust BITSPERSAMPLE to match SAMPLESPERPIXEL when opening TIFFs #6270 | ||||||
|   [radarhere] |   [radarhere] | ||||||
| 
 | 
 | ||||||
| - Do not open images with zero or negative height #6269 |  | ||||||
|   [radarhere] |  | ||||||
| 
 |  | ||||||
| - Search pkgconf system libs/cflags #6138 | - Search pkgconf system libs/cflags #6138 | ||||||
|   [jameshilliard, radarhere] |   [jameshilliard, radarhere] | ||||||
| 
 | 
 | ||||||
|  | @ -50,6 +65,15 @@ Changelog (Pillow) | ||||||
| - Deprecated PhotoImage.paste() box parameter #6178 | - Deprecated PhotoImage.paste() box parameter #6178 | ||||||
|   [radarhere] |   [radarhere] | ||||||
| 
 | 
 | ||||||
|  | 9.1.1 (2022-05-17) | ||||||
|  | ------------------ | ||||||
|  | 
 | ||||||
|  | - When reading past the end of a TGA scan line, reduce bytes left. CVE-2022-30595 | ||||||
|  |   [radarhere] | ||||||
|  | 
 | ||||||
|  | - Do not open images with zero or negative height #6269 | ||||||
|  |   [radarhere] | ||||||
|  | 
 | ||||||
| 9.1.0 (2022-04-01) | 9.1.0 (2022-04-01) | ||||||
| ------------------ | ------------------ | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
							
								
								
									
										2
									
								
								Makefile
									
									
									
									
									
								
							
							
						
						
									
										2
									
								
								Makefile
									
									
									
									
									
								
							|  | @ -85,6 +85,8 @@ release-test: | ||||||
| sdist: | sdist: | ||||||
| 	python3 -m build --help > /dev/null 2>&1 || python3 -m pip install build | 	python3 -m build --help > /dev/null 2>&1 || python3 -m pip install build | ||||||
| 	python3 -m build --sdist | 	python3 -m build --sdist | ||||||
|  | 	python3 -m twine --help > /dev/null 2>&1 || python3 -m pip install twine | ||||||
|  | 	python3 -m twine check --strict dist/* | ||||||
| 
 | 
 | ||||||
| .PHONY: test | .PHONY: test | ||||||
| test: | test: | ||||||
|  |  | ||||||
|  | @ -24,7 +24,6 @@ Released quarterly on January 2nd, April 1st, July 1st and October 15th. | ||||||
| * [ ] Create and check source distribution: | * [ ] Create and check source distribution: | ||||||
|   ```bash |   ```bash | ||||||
|   make sdist |   make sdist | ||||||
|   python3 -m twine check --strict dist/* |  | ||||||
|   ``` |   ``` | ||||||
| * [ ] Create [binary distributions](https://github.com/python-pillow/Pillow/blob/main/RELEASING.md#binary-distributions) | * [ ] Create [binary distributions](https://github.com/python-pillow/Pillow/blob/main/RELEASING.md#binary-distributions) | ||||||
| * [ ] Check and upload all binaries and source distributions e.g.: | * [ ] Check and upload all binaries and source distributions e.g.: | ||||||
|  | @ -61,7 +60,6 @@ Released as needed for security, installation or critical bug fixes. | ||||||
| * [ ] Create and check source distribution: | * [ ] Create and check source distribution: | ||||||
|   ```bash |   ```bash | ||||||
|   make sdist |   make sdist | ||||||
|   python3 -m twine check --strict dist/* |  | ||||||
|   ``` |   ``` | ||||||
| * [ ] Create [binary distributions](https://github.com/python-pillow/Pillow/blob/main/RELEASING.md#binary-distributions) | * [ ] Create [binary distributions](https://github.com/python-pillow/Pillow/blob/main/RELEASING.md#binary-distributions) | ||||||
| * [ ] Check and upload all binaries and source distributions e.g.: | * [ ] Check and upload all binaries and source distributions e.g.: | ||||||
|  | @ -91,7 +89,6 @@ Released as needed privately to individual vendors for critical security-related | ||||||
| * [ ] Create and check source distribution: | * [ ] Create and check source distribution: | ||||||
|   ```bash |   ```bash | ||||||
|   make sdist |   make sdist | ||||||
|   python3 -m twine check --strict dist/* |  | ||||||
|   ``` |   ``` | ||||||
| * [ ] Create [binary distributions](https://github.com/python-pillow/Pillow/blob/main/RELEASING.md#binary-distributions) | * [ ] Create [binary distributions](https://github.com/python-pillow/Pillow/blob/main/RELEASING.md#binary-distributions) | ||||||
| * [ ] Publish the [release on GitHub](https://github.com/python-pillow/Pillow/releases) | * [ ] Publish the [release on GitHub](https://github.com/python-pillow/Pillow/releases) | ||||||
|  |  | ||||||
							
								
								
									
										
											BIN
										
									
								
								Tests/images/cross_scan_line_truncated.tga
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										
											BIN
										
									
								
								Tests/images/cross_scan_line_truncated.tga
									
									
									
									
									
										Normal file
									
								
							
										
											Binary file not shown.
										
									
								
							
							
								
								
									
										
											BIN
										
									
								
								Tests/images/multiple_comments.gif
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										
											BIN
										
									
								
								Tests/images/multiple_comments.gif
									
									
									
									
									
										Normal file
									
								
							
										
											Binary file not shown.
										
									
								
							| After Width: | Height: | Size: 1.5 KiB | 
|  | @ -637,6 +637,15 @@ def test_apng_save_blend(tmp_path): | ||||||
|         assert im.getpixel((0, 0)) == (0, 255, 0, 255) |         assert im.getpixel((0, 0)) == (0, 255, 0, 255) | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
|  | def test_seek_after_close(): | ||||||
|  |     im = Image.open("Tests/images/apng/delay.png") | ||||||
|  |     im.seek(1) | ||||||
|  |     im.close() | ||||||
|  | 
 | ||||||
|  |     with pytest.raises(ValueError): | ||||||
|  |         im.seek(0) | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
| def test_constants_deprecation(): | def test_constants_deprecation(): | ||||||
|     for enum, prefix in { |     for enum, prefix in { | ||||||
|         PngImagePlugin.Disposal: "APNG_DISPOSE_", |         PngImagePlugin.Disposal: "APNG_DISPOSE_", | ||||||
|  |  | ||||||
|  | @ -46,6 +46,15 @@ def test_closed_file(): | ||||||
|         im.close() |         im.close() | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
|  | def test_seek_after_close(): | ||||||
|  |     im = Image.open(animated_test_file) | ||||||
|  |     im.seek(1) | ||||||
|  |     im.close() | ||||||
|  | 
 | ||||||
|  |     with pytest.raises(ValueError): | ||||||
|  |         im.seek(0) | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
| def test_context_manager(): | def test_context_manager(): | ||||||
|     with warnings.catch_warnings(): |     with warnings.catch_warnings(): | ||||||
|         with Image.open(static_test_file) as im: |         with Image.open(static_test_file) as im: | ||||||
|  |  | ||||||
|  | @ -46,6 +46,19 @@ def test_closed_file(): | ||||||
|         im.close() |         im.close() | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
|  | def test_seek_after_close(): | ||||||
|  |     im = Image.open("Tests/images/iss634.gif") | ||||||
|  |     im.load() | ||||||
|  |     im.close() | ||||||
|  | 
 | ||||||
|  |     with pytest.raises(ValueError): | ||||||
|  |         im.is_animated | ||||||
|  |     with pytest.raises(ValueError): | ||||||
|  |         im.n_frames | ||||||
|  |     with pytest.raises(ValueError): | ||||||
|  |         im.seek(1) | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
| def test_context_manager(): | def test_context_manager(): | ||||||
|     with warnings.catch_warnings(): |     with warnings.catch_warnings(): | ||||||
|         with Image.open(TEST_GIF) as im: |         with Image.open(TEST_GIF) as im: | ||||||
|  | @ -794,6 +807,9 @@ def test_comment(tmp_path): | ||||||
|     with Image.open(out) as reread: |     with Image.open(out) as reread: | ||||||
|         assert reread.info["comment"] == im.info["comment"].encode() |         assert reread.info["comment"] == im.info["comment"].encode() | ||||||
| 
 | 
 | ||||||
|  |         # Test that GIF89a is used for comments | ||||||
|  |         assert reread.info["version"] == b"GIF89a" | ||||||
|  | 
 | ||||||
| 
 | 
 | ||||||
| def test_comment_over_255(tmp_path): | def test_comment_over_255(tmp_path): | ||||||
|     out = str(tmp_path / "temp.gif") |     out = str(tmp_path / "temp.gif") | ||||||
|  | @ -804,15 +820,23 @@ def test_comment_over_255(tmp_path): | ||||||
|     im.info["comment"] = comment |     im.info["comment"] = comment | ||||||
|     im.save(out) |     im.save(out) | ||||||
|     with Image.open(out) as reread: |     with Image.open(out) as reread: | ||||||
| 
 |  | ||||||
|         assert reread.info["comment"] == comment |         assert reread.info["comment"] == comment | ||||||
| 
 | 
 | ||||||
|  |         # Test that GIF89a is used for comments | ||||||
|  |         assert reread.info["version"] == b"GIF89a" | ||||||
|  | 
 | ||||||
| 
 | 
 | ||||||
| def test_zero_comment_subblocks(): | def test_zero_comment_subblocks(): | ||||||
|     with Image.open("Tests/images/hopper_zero_comment_subblocks.gif") as im: |     with Image.open("Tests/images/hopper_zero_comment_subblocks.gif") as im: | ||||||
|         assert_image_equal_tofile(im, TEST_GIF) |         assert_image_equal_tofile(im, TEST_GIF) | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
|  | def test_read_multiple_comment_blocks(): | ||||||
|  |     with Image.open("Tests/images/multiple_comments.gif") as im: | ||||||
|  |         # Multiple comment blocks in a frame are separated not concatenated | ||||||
|  |         assert im.info["comment"] == b"Test comment 1\nTest comment 2" | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
| def test_write_comment(tmp_path): | def test_write_comment(tmp_path): | ||||||
|     out = str(tmp_path / "temp.gif") |     out = str(tmp_path / "temp.gif") | ||||||
|     with Image.open("Tests/images/dispose_prev.gif") as im: |     with Image.open("Tests/images/dispose_prev.gif") as im: | ||||||
|  |  | ||||||
|  | @ -18,6 +18,7 @@ from .helper import ( | ||||||
|     hopper, |     hopper, | ||||||
|     mark_if_feature_version, |     mark_if_feature_version, | ||||||
|     skip_unless_feature, |     skip_unless_feature, | ||||||
|  |     skip_unless_feature_version, | ||||||
| ) | ) | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
|  | @ -991,6 +992,7 @@ class TestFileLibTiff(LibTiffTestCase): | ||||||
|         with Image.open(out) as im: |         with Image.open(out) as im: | ||||||
|             im.load() |             im.load() | ||||||
| 
 | 
 | ||||||
|  |     @skip_unless_feature_version("libtiff", "4.0.4") | ||||||
|     def test_realloc_overflow(self): |     def test_realloc_overflow(self): | ||||||
|         TiffImagePlugin.READ_LIBTIFF = True |         TiffImagePlugin.READ_LIBTIFF = True | ||||||
|         with Image.open("Tests/images/tiff_overflow_rows_per_strip.tif") as im: |         with Image.open("Tests/images/tiff_overflow_rows_per_strip.tif") as im: | ||||||
|  |  | ||||||
|  | @ -48,6 +48,14 @@ def test_closed_file(): | ||||||
|         im.close() |         im.close() | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
|  | def test_seek_after_close(): | ||||||
|  |     im = Image.open(test_files[0]) | ||||||
|  |     im.close() | ||||||
|  | 
 | ||||||
|  |     with pytest.raises(ValueError): | ||||||
|  |         im.seek(1) | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
| def test_context_manager(): | def test_context_manager(): | ||||||
|     with warnings.catch_warnings(): |     with warnings.catch_warnings(): | ||||||
|         with Image.open(test_files[0]) as im: |         with Image.open(test_files[0]) as im: | ||||||
|  |  | ||||||
|  | @ -101,6 +101,10 @@ def test_cross_scan_line(): | ||||||
|     with Image.open("Tests/images/cross_scan_line.tga") as im: |     with Image.open("Tests/images/cross_scan_line.tga") as im: | ||||||
|         assert_image_equal_tofile(im, "Tests/images/cross_scan_line.png") |         assert_image_equal_tofile(im, "Tests/images/cross_scan_line.png") | ||||||
| 
 | 
 | ||||||
|  |     with Image.open("Tests/images/cross_scan_line_truncated.tga") as im: | ||||||
|  |         with pytest.raises(OSError): | ||||||
|  |             im.load() | ||||||
|  | 
 | ||||||
| 
 | 
 | ||||||
| def test_save(tmp_path): | def test_save(tmp_path): | ||||||
|     test_file = "Tests/images/tga_id_field.tga" |     test_file = "Tests/images/tga_id_field.tga" | ||||||
|  |  | ||||||
|  | @ -70,6 +70,15 @@ class TestFileTiff: | ||||||
|             im.load() |             im.load() | ||||||
|             im.close() |             im.close() | ||||||
| 
 | 
 | ||||||
|  |     def test_seek_after_close(self): | ||||||
|  |         im = Image.open("Tests/images/multipage.tiff") | ||||||
|  |         im.close() | ||||||
|  | 
 | ||||||
|  |         with pytest.raises(ValueError): | ||||||
|  |             im.n_frames | ||||||
|  |         with pytest.raises(ValueError): | ||||||
|  |             im.seek(1) | ||||||
|  | 
 | ||||||
|     def test_context_manager(self): |     def test_context_manager(self): | ||||||
|         with warnings.catch_warnings(): |         with warnings.catch_warnings(): | ||||||
|             with Image.open("Tests/images/multipage.tiff") as im: |             with Image.open("Tests/images/multipage.tiff") as im: | ||||||
|  | @ -706,6 +715,13 @@ class TestFileTiff: | ||||||
|         with Image.open(outfile) as reloaded: |         with Image.open(outfile) as reloaded: | ||||||
|             assert reloaded.info["icc_profile"] == icc_profile |             assert reloaded.info["icc_profile"] == icc_profile | ||||||
| 
 | 
 | ||||||
|  |     def test_save_bmp_compression(self, tmp_path): | ||||||
|  |         with Image.open("Tests/images/hopper.bmp") as im: | ||||||
|  |             assert im.info["compression"] == 0 | ||||||
|  | 
 | ||||||
|  |             outfile = str(tmp_path / "temp.tif") | ||||||
|  |             im.save(outfile) | ||||||
|  | 
 | ||||||
|     def test_discard_icc_profile(self, tmp_path): |     def test_discard_icc_profile(self, tmp_path): | ||||||
|         outfile = str(tmp_path / "temp.tif") |         outfile = str(tmp_path / "temp.tif") | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
|  | @ -1,5 +1,7 @@ | ||||||
| import pytest | import pytest | ||||||
| 
 | 
 | ||||||
|  | from PIL import Image | ||||||
|  | 
 | ||||||
| from .helper import assert_image_equal, hopper | from .helper import assert_image_equal, hopper | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
|  | @ -17,11 +19,24 @@ def test_sanity(): | ||||||
|         im.point(list(range(256))) |         im.point(list(range(256))) | ||||||
|     im.point(lambda x: x * 1) |     im.point(lambda x: x * 1) | ||||||
|     im.point(lambda x: x + 1) |     im.point(lambda x: x + 1) | ||||||
|  |     im.point(lambda x: x - 1) | ||||||
|     im.point(lambda x: x * 1 + 1) |     im.point(lambda x: x * 1 + 1) | ||||||
|  |     im.point(lambda x: 0.1 + 0.2 * x) | ||||||
|  |     im.point(lambda x: -x) | ||||||
|  |     im.point(lambda x: x - 0.5) | ||||||
|  |     im.point(lambda x: 1 - x / 2) | ||||||
|  |     im.point(lambda x: (2 + x) / 3) | ||||||
|  |     im.point(lambda x: 0.5) | ||||||
|  |     im.point(lambda x: x / 1) | ||||||
|  |     im.point(lambda x: x + x) | ||||||
|     with pytest.raises(TypeError): |     with pytest.raises(TypeError): | ||||||
|         im.point(lambda x: x - 1) |         im.point(lambda x: x * x) | ||||||
|     with pytest.raises(TypeError): |     with pytest.raises(TypeError): | ||||||
|         im.point(lambda x: x / 1) |         im.point(lambda x: x / x) | ||||||
|  |     with pytest.raises(TypeError): | ||||||
|  |         im.point(lambda x: 1 / x) | ||||||
|  |     with pytest.raises(TypeError): | ||||||
|  |         im.point(lambda x: x // 2) | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
| def test_16bit_lut(): | def test_16bit_lut(): | ||||||
|  | @ -47,3 +62,8 @@ def test_f_mode(): | ||||||
|     im = hopper("F") |     im = hopper("F") | ||||||
|     with pytest.raises(ValueError): |     with pytest.raises(ValueError): | ||||||
|         im.point(None) |         im.point(None) | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | def test_coerce_e_deprecation(): | ||||||
|  |     with pytest.warns(DeprecationWarning): | ||||||
|  |         assert Image.coerce_e(2).data == 2 | ||||||
|  |  | ||||||
|  | @ -65,9 +65,12 @@ class TestImageFont: | ||||||
|         return font_bytes |         return font_bytes | ||||||
| 
 | 
 | ||||||
|     def test_font_with_filelike(self): |     def test_font_with_filelike(self): | ||||||
|         ImageFont.truetype( |         ttf = ImageFont.truetype( | ||||||
|             self._font_as_bytes(), FONT_SIZE, layout_engine=self.LAYOUT_ENGINE |             self._font_as_bytes(), FONT_SIZE, layout_engine=self.LAYOUT_ENGINE | ||||||
|         ) |         ) | ||||||
|  |         ttf_copy = ttf.font_variant() | ||||||
|  |         assert ttf_copy.font_bytes == ttf.font_bytes | ||||||
|  | 
 | ||||||
|         self._render(self._font_as_bytes()) |         self._render(self._font_as_bytes()) | ||||||
|         # Usage note:  making two fonts from the same buffer fails. |         # Usage note:  making two fonts from the same buffer fails. | ||||||
|         # shared_bytes = self._font_as_bytes() |         # shared_bytes = self._font_as_bytes() | ||||||
|  |  | ||||||
|  | @ -1,7 +1,7 @@ | ||||||
| #!/bin/bash | #!/bin/bash | ||||||
| # install openjpeg | # install openjpeg | ||||||
| 
 | 
 | ||||||
| archive=openjpeg-2.4.0 | archive=openjpeg-2.5.0 | ||||||
| 
 | 
 | ||||||
| ./download-and-extract.sh $archive https://raw.githubusercontent.com/python-pillow/pillow-depends/main/$archive.tar.gz | ./download-and-extract.sh $archive https://raw.githubusercontent.com/python-pillow/pillow-depends/main/$archive.tar.gz | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
|  | @ -170,6 +170,14 @@ in Pillow 10 (2023-07-01). Upgrade to | ||||||
| `PyQt6 <https://www.riverbankcomputing.com/static/Docs/PyQt6/>`_ or | `PyQt6 <https://www.riverbankcomputing.com/static/Docs/PyQt6/>`_ or | ||||||
| `PySide6 <https://doc.qt.io/qtforpython/>`_ instead. | `PySide6 <https://doc.qt.io/qtforpython/>`_ instead. | ||||||
| 
 | 
 | ||||||
|  | Image.coerce_e | ||||||
|  | ~~~~~~~~~~~~~~ | ||||||
|  | 
 | ||||||
|  | .. deprecated:: 9.2.0 | ||||||
|  | 
 | ||||||
|  | This undocumented method has been deprecated and will be removed in Pillow 10 | ||||||
|  | (2023-07-01). | ||||||
|  | 
 | ||||||
| Removed features | Removed features | ||||||
| ---------------- | ---------------- | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
|  | @ -181,7 +181,8 @@ Many of Pillow's features require external libraries: | ||||||
| 
 | 
 | ||||||
| * **openjpeg** provides JPEG 2000 functionality. | * **openjpeg** provides JPEG 2000 functionality. | ||||||
| 
 | 
 | ||||||
|   * Pillow has been tested with openjpeg **2.0.0**, **2.1.0**, **2.3.1** and **2.4.0**. |   * Pillow has been tested with openjpeg **2.0.0**, **2.1.0**, **2.3.1**, | ||||||
|  |     **2.4.0** and **2.5.0**. | ||||||
|   * Pillow does **not** support the earlier **1.5** series which ships |   * Pillow does **not** support the earlier **1.5** series which ships | ||||||
|     with Debian Jessie. |     with Debian Jessie. | ||||||
| 
 | 
 | ||||||
|  | @ -474,11 +475,9 @@ These platforms are built and tested for every change. | ||||||
| +----------------------------------+----------------------------+---------------------+ | +----------------------------------+----------------------------+---------------------+ | ||||||
| | Ubuntu Linux 20.04 LTS (Focal)   | 3.7, 3.8, 3.9, 3.10, 3.11, | x86-64              | | | Ubuntu Linux 20.04 LTS (Focal)   | 3.7, 3.8, 3.9, 3.10, 3.11, | x86-64              | | ||||||
| |                                  | PyPy3                      |                     | | |                                  | PyPy3                      |                     | | ||||||
| |                                  +----------------------------+---------------------+ |  | ||||||
| |                                  | 3.8                        | arm64v8, ppc64le,   | |  | ||||||
| |                                  |                            | s390x               | |  | ||||||
| +----------------------------------+----------------------------+---------------------+ | +----------------------------------+----------------------------+---------------------+ | ||||||
| | Ubuntu Linux 22.04 LTS (Jammy)   | 3.10                       | x86-64              | | | Ubuntu Linux 22.04 LTS (Jammy)   | 3.10                       | arm64v8, ppc64le,   | | ||||||
|  | |                                  |                            | s390x, x86-64       | | ||||||
| +----------------------------------+----------------------------+---------------------+ | +----------------------------------+----------------------------+---------------------+ | ||||||
| | Windows Server 2016              | 3.7                        | x86-64              | | | Windows Server 2016              | 3.7                        | x86-64              | | ||||||
| +----------------------------------+----------------------------+---------------------+ | +----------------------------------+----------------------------+---------------------+ | ||||||
|  |  | ||||||
|  | @ -174,7 +174,7 @@ Previously, if a BMP file was too large, an ``OSError`` would be raised. Now, | ||||||
| Dark theme for docs | Dark theme for docs | ||||||
| ^^^^^^^^^^^^^^^^^^^ | ^^^^^^^^^^^^^^^^^^^ | ||||||
| 
 | 
 | ||||||
| The https://pillow.readthedocs.io documentation will use a dark theme if the the user has requested the system use one. Uses the ``prefers-color-scheme`` CSS media query. | The https://pillow.readthedocs.io documentation will use a dark theme if the user has requested the system use one. Uses the ``prefers-color-scheme`` CSS media query. | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
							
								
								
									
										16
									
								
								docs/releasenotes/9.1.1.rst
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										16
									
								
								docs/releasenotes/9.1.1.rst
									
									
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,16 @@ | ||||||
|  | 9.1.1 | ||||||
|  | ----- | ||||||
|  | 
 | ||||||
|  | Security | ||||||
|  | ======== | ||||||
|  | 
 | ||||||
|  | This release addresses several security problems. | ||||||
|  | 
 | ||||||
|  | :cve:`CVE-2022-30595`: When reading a TGA file with RLE packets that cross scan lines, | ||||||
|  | Pillow reads the information past the end of the first line without deducting that | ||||||
|  | from the length of the remaining file data. This vulnerability was introduced in Pillow | ||||||
|  | 9.1.0, and can cause a heap buffer overflow. | ||||||
|  | 
 | ||||||
|  | Opening an image with a zero or negative height has been found to bypass a | ||||||
|  | decompression bomb check. This will now raise a :py:exc:`SyntaxError` instead, in turn | ||||||
|  | raising a ``PIL.UnidentifiedImageError``. | ||||||
|  | @ -31,6 +31,14 @@ FreeTypeFont.getmask2 fill parameter | ||||||
| The undocumented ``fill`` parameter of :py:meth:`.FreeTypeFont.getmask2` | The undocumented ``fill`` parameter of :py:meth:`.FreeTypeFont.getmask2` | ||||||
| has been deprecated and will be removed in Pillow 10 (2023-07-01). | has been deprecated and will be removed in Pillow 10 (2023-07-01). | ||||||
| 
 | 
 | ||||||
|  | Image.coerce_e | ||||||
|  | ~~~~~~~~~~~~~~ | ||||||
|  | 
 | ||||||
|  | .. deprecated:: 9.2.0 | ||||||
|  | 
 | ||||||
|  | This undocumented method has been deprecated and will be removed in Pillow 10 | ||||||
|  | (2023-07-01). | ||||||
|  | 
 | ||||||
| API Changes | API Changes | ||||||
| =========== | =========== | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
|  | @ -15,6 +15,7 @@ expected to be backported to earlier versions. | ||||||
|   :maxdepth: 2 |   :maxdepth: 2 | ||||||
| 
 | 
 | ||||||
|   9.2.0 |   9.2.0 | ||||||
|  |   9.1.1 | ||||||
|   9.1.0 |   9.1.0 | ||||||
|   9.0.1 |   9.0.1 | ||||||
|   9.0.0 |   9.0.0 | ||||||
|  |  | ||||||
|  | @ -57,7 +57,7 @@ class DcxImageFile(PcxImageFile): | ||||||
|                 break |                 break | ||||||
|             self._offset.append(offset) |             self._offset.append(offset) | ||||||
| 
 | 
 | ||||||
|         self.__fp = self.fp |         self._fp = self.fp | ||||||
|         self.frame = None |         self.frame = None | ||||||
|         self.n_frames = len(self._offset) |         self.n_frames = len(self._offset) | ||||||
|         self.is_animated = self.n_frames > 1 |         self.is_animated = self.n_frames > 1 | ||||||
|  | @ -67,22 +67,13 @@ class DcxImageFile(PcxImageFile): | ||||||
|         if not self._seek_check(frame): |         if not self._seek_check(frame): | ||||||
|             return |             return | ||||||
|         self.frame = frame |         self.frame = frame | ||||||
|         self.fp = self.__fp |         self.fp = self._fp | ||||||
|         self.fp.seek(self._offset[frame]) |         self.fp.seek(self._offset[frame]) | ||||||
|         PcxImageFile._open(self) |         PcxImageFile._open(self) | ||||||
| 
 | 
 | ||||||
|     def tell(self): |     def tell(self): | ||||||
|         return self.frame |         return self.frame | ||||||
| 
 | 
 | ||||||
|     def _close__fp(self): |  | ||||||
|         try: |  | ||||||
|             if self.__fp != self.fp: |  | ||||||
|                 self.__fp.close() |  | ||||||
|         except AttributeError: |  | ||||||
|             pass |  | ||||||
|         finally: |  | ||||||
|             self.__fp = None |  | ||||||
| 
 |  | ||||||
| 
 | 
 | ||||||
| Image.register_open(DcxImageFile.format, DcxImageFile, _accept) | Image.register_open(DcxImageFile.format, DcxImageFile, _accept) | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
|  | @ -91,7 +91,7 @@ class FliImageFile(ImageFile.ImageFile): | ||||||
| 
 | 
 | ||||||
|         # set things up to decode first frame |         # set things up to decode first frame | ||||||
|         self.__frame = -1 |         self.__frame = -1 | ||||||
|         self.__fp = self.fp |         self._fp = self.fp | ||||||
|         self.__rewind = self.fp.tell() |         self.__rewind = self.fp.tell() | ||||||
|         self.seek(0) |         self.seek(0) | ||||||
| 
 | 
 | ||||||
|  | @ -125,7 +125,7 @@ class FliImageFile(ImageFile.ImageFile): | ||||||
|     def _seek(self, frame): |     def _seek(self, frame): | ||||||
|         if frame == 0: |         if frame == 0: | ||||||
|             self.__frame = -1 |             self.__frame = -1 | ||||||
|             self.__fp.seek(self.__rewind) |             self._fp.seek(self.__rewind) | ||||||
|             self.__offset = 128 |             self.__offset = 128 | ||||||
|         else: |         else: | ||||||
|             # ensure that the previous frame was loaded |             # ensure that the previous frame was loaded | ||||||
|  | @ -136,7 +136,7 @@ class FliImageFile(ImageFile.ImageFile): | ||||||
|         self.__frame = frame |         self.__frame = frame | ||||||
| 
 | 
 | ||||||
|         # move to next frame |         # move to next frame | ||||||
|         self.fp = self.__fp |         self.fp = self._fp | ||||||
|         self.fp.seek(self.__offset) |         self.fp.seek(self.__offset) | ||||||
| 
 | 
 | ||||||
|         s = self.fp.read(4) |         s = self.fp.read(4) | ||||||
|  | @ -153,15 +153,6 @@ class FliImageFile(ImageFile.ImageFile): | ||||||
|     def tell(self): |     def tell(self): | ||||||
|         return self.__frame |         return self.__frame | ||||||
| 
 | 
 | ||||||
|     def _close__fp(self): |  | ||||||
|         try: |  | ||||||
|             if self.__fp != self.fp: |  | ||||||
|                 self.__fp.close() |  | ||||||
|         except AttributeError: |  | ||||||
|             pass |  | ||||||
|         finally: |  | ||||||
|             self.__fp = None |  | ||||||
| 
 |  | ||||||
| 
 | 
 | ||||||
| # | # | ||||||
| # registry | # registry | ||||||
|  |  | ||||||
|  | @ -102,7 +102,7 @@ class GifImageFile(ImageFile.ImageFile): | ||||||
|                 p = ImagePalette.raw("RGB", p) |                 p = ImagePalette.raw("RGB", p) | ||||||
|                 self.global_palette = self.palette = p |                 self.global_palette = self.palette = p | ||||||
| 
 | 
 | ||||||
|         self.__fp = self.fp  # FIXME: hack |         self._fp = self.fp  # FIXME: hack | ||||||
|         self.__rewind = self.fp.tell() |         self.__rewind = self.fp.tell() | ||||||
|         self._n_frames = None |         self._n_frames = None | ||||||
|         self._is_animated = None |         self._is_animated = None | ||||||
|  | @ -161,7 +161,7 @@ class GifImageFile(ImageFile.ImageFile): | ||||||
|             self.__offset = 0 |             self.__offset = 0 | ||||||
|             self.dispose = None |             self.dispose = None | ||||||
|             self.__frame = -1 |             self.__frame = -1 | ||||||
|             self.__fp.seek(self.__rewind) |             self._fp.seek(self.__rewind) | ||||||
|             self.disposal_method = 0 |             self.disposal_method = 0 | ||||||
|         else: |         else: | ||||||
|             # ensure that the previous frame was loaded |             # ensure that the previous frame was loaded | ||||||
|  | @ -171,7 +171,7 @@ class GifImageFile(ImageFile.ImageFile): | ||||||
|         if frame != self.__frame + 1: |         if frame != self.__frame + 1: | ||||||
|             raise ValueError(f"cannot seek to frame {frame}") |             raise ValueError(f"cannot seek to frame {frame}") | ||||||
| 
 | 
 | ||||||
|         self.fp = self.__fp |         self.fp = self._fp | ||||||
|         if self.__offset: |         if self.__offset: | ||||||
|             # backup to last frame |             # backup to last frame | ||||||
|             self.fp.seek(self.__offset) |             self.fp.seek(self.__offset) | ||||||
|  | @ -228,12 +228,18 @@ class GifImageFile(ImageFile.ImageFile): | ||||||
|                     # |                     # | ||||||
|                     # comment extension |                     # comment extension | ||||||
|                     # |                     # | ||||||
|  |                     comment = b"" | ||||||
|  | 
 | ||||||
|  |                     # Collect one comment block | ||||||
|                     while block: |                     while block: | ||||||
|                         if "comment" in info: |                         comment += block | ||||||
|                             info["comment"] += block |  | ||||||
|                         else: |  | ||||||
|                             info["comment"] = block |  | ||||||
|                         block = self.data() |                         block = self.data() | ||||||
|  | 
 | ||||||
|  |                     if "comment" in info: | ||||||
|  |                         # If multiple comment blocks in frame, separate with \n | ||||||
|  |                         info["comment"] += b"\n" + comment | ||||||
|  |                     else: | ||||||
|  |                         info["comment"] = comment | ||||||
|                     s = None |                     s = None | ||||||
|                     continue |                     continue | ||||||
|                 elif s[0] == 255: |                 elif s[0] == 255: | ||||||
|  | @ -281,7 +287,7 @@ class GifImageFile(ImageFile.ImageFile): | ||||||
|             s = None |             s = None | ||||||
| 
 | 
 | ||||||
|         if interlace is None: |         if interlace is None: | ||||||
|             # self.__fp = None |             # self._fp = None | ||||||
|             raise EOFError |             raise EOFError | ||||||
|         if not update_image: |         if not update_image: | ||||||
|             return |             return | ||||||
|  | @ -443,15 +449,6 @@ class GifImageFile(ImageFile.ImageFile): | ||||||
|     def tell(self): |     def tell(self): | ||||||
|         return self.__frame |         return self.__frame | ||||||
| 
 | 
 | ||||||
|     def _close__fp(self): |  | ||||||
|         try: |  | ||||||
|             if self.__fp != self.fp: |  | ||||||
|                 self.__fp.close() |  | ||||||
|         except AttributeError: |  | ||||||
|             pass |  | ||||||
|         finally: |  | ||||||
|             self.__fp = None |  | ||||||
| 
 |  | ||||||
| 
 | 
 | ||||||
| # -------------------------------------------------------------------- | # -------------------------------------------------------------------- | ||||||
| # Write GIF files | # Write GIF files | ||||||
|  | @ -903,17 +900,16 @@ def _get_global_header(im, info): | ||||||
|     # https://www.matthewflickinger.com/lab/whatsinagif/bits_and_bytes.asp |     # https://www.matthewflickinger.com/lab/whatsinagif/bits_and_bytes.asp | ||||||
| 
 | 
 | ||||||
|     version = b"87a" |     version = b"87a" | ||||||
|     for extensionKey in ["transparency", "duration", "loop", "comment"]: |     if im.info.get("version") == b"89a" or ( | ||||||
|         if info and extensionKey in info: |         info | ||||||
|             if (extensionKey == "duration" and info[extensionKey] == 0) or ( |         and ( | ||||||
|                 extensionKey == "comment" and not (1 <= len(info[extensionKey]) <= 255) |             "transparency" in info | ||||||
|             ): |             or "loop" in info | ||||||
|                 continue |             or info.get("duration") | ||||||
|             version = b"89a" |             or info.get("comment") | ||||||
|             break |         ) | ||||||
|     else: |     ): | ||||||
|         if im.info.get("version") == b"89a": |         version = b"89a" | ||||||
|             version = b"89a" |  | ||||||
| 
 | 
 | ||||||
|     background = _get_background(im, info.get("background")) |     background = _get_background(im, info.get("background")) | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
|  | @ -245,7 +245,7 @@ class ImImageFile(ImageFile.ImageFile): | ||||||
| 
 | 
 | ||||||
|         self.__offset = offs = self.fp.tell() |         self.__offset = offs = self.fp.tell() | ||||||
| 
 | 
 | ||||||
|         self.__fp = self.fp  # FIXME: hack |         self._fp = self.fp  # FIXME: hack | ||||||
| 
 | 
 | ||||||
|         if self.rawmode[:2] == "F;": |         if self.rawmode[:2] == "F;": | ||||||
| 
 | 
 | ||||||
|  | @ -294,22 +294,13 @@ class ImImageFile(ImageFile.ImageFile): | ||||||
|         size = ((self.size[0] * bits + 7) // 8) * self.size[1] |         size = ((self.size[0] * bits + 7) // 8) * self.size[1] | ||||||
|         offs = self.__offset + frame * size |         offs = self.__offset + frame * size | ||||||
| 
 | 
 | ||||||
|         self.fp = self.__fp |         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): |     def tell(self): | ||||||
|         return self.frame |         return self.frame | ||||||
| 
 | 
 | ||||||
|     def _close__fp(self): |  | ||||||
|         try: |  | ||||||
|             if self.__fp != self.fp: |  | ||||||
|                 self.__fp.close() |  | ||||||
|         except AttributeError: |  | ||||||
|             pass |  | ||||||
|         finally: |  | ||||||
|             self.__fp = None |  | ||||||
| 
 |  | ||||||
| 
 | 
 | ||||||
| # | # | ||||||
| # -------------------------------------------------------------------- | # -------------------------------------------------------------------- | ||||||
|  |  | ||||||
|  | @ -29,7 +29,6 @@ import builtins | ||||||
| import io | import io | ||||||
| import logging | import logging | ||||||
| import math | import math | ||||||
| import numbers |  | ||||||
| import os | import os | ||||||
| import re | import re | ||||||
| import struct | import struct | ||||||
|  | @ -432,44 +431,50 @@ def _getencoder(mode, encoder_name, args, extra=()): | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
| def coerce_e(value): | def coerce_e(value): | ||||||
|     return value if isinstance(value, _E) else _E(value) |     deprecate("coerce_e", 10) | ||||||
|  |     return value if isinstance(value, _E) else _E(1, value) | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
|  | # _E(scale, offset) represents the affine transformation scale * x + offset. | ||||||
|  | # The "data" field is named for compatibility with the old implementation, | ||||||
|  | # and should be renamed once coerce_e is removed. | ||||||
| class _E: | class _E: | ||||||
|     def __init__(self, data): |     def __init__(self, scale, data): | ||||||
|  |         self.scale = scale | ||||||
|         self.data = data |         self.data = data | ||||||
| 
 | 
 | ||||||
|  |     def __neg__(self): | ||||||
|  |         return _E(-self.scale, -self.data) | ||||||
|  | 
 | ||||||
|     def __add__(self, other): |     def __add__(self, other): | ||||||
|         return _E((self.data, "__add__", coerce_e(other).data)) |         if isinstance(other, _E): | ||||||
|  |             return _E(self.scale + other.scale, self.data + other.data) | ||||||
|  |         return _E(self.scale, self.data + other) | ||||||
|  | 
 | ||||||
|  |     __radd__ = __add__ | ||||||
|  | 
 | ||||||
|  |     def __sub__(self, other): | ||||||
|  |         return self + -other | ||||||
|  | 
 | ||||||
|  |     def __rsub__(self, other): | ||||||
|  |         return other + -self | ||||||
| 
 | 
 | ||||||
|     def __mul__(self, other): |     def __mul__(self, other): | ||||||
|         return _E((self.data, "__mul__", coerce_e(other).data)) |         if isinstance(other, _E): | ||||||
|  |             return NotImplemented | ||||||
|  |         return _E(self.scale * other, self.data * other) | ||||||
|  | 
 | ||||||
|  |     __rmul__ = __mul__ | ||||||
|  | 
 | ||||||
|  |     def __truediv__(self, other): | ||||||
|  |         if isinstance(other, _E): | ||||||
|  |             return NotImplemented | ||||||
|  |         return _E(self.scale / other, self.data / other) | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
| def _getscaleoffset(expr): | def _getscaleoffset(expr): | ||||||
|     stub = ["stub"] |     a = expr(_E(1, 0)) | ||||||
|     data = expr(_E(stub)).data |     return (a.scale, a.data) if isinstance(a, _E) else (0, a) | ||||||
|     try: |  | ||||||
|         (a, b, c) = data  # simplified syntax |  | ||||||
|         if a is stub and b == "__mul__" and isinstance(c, numbers.Number): |  | ||||||
|             return c, 0.0 |  | ||||||
|         if a is stub and b == "__add__" and isinstance(c, numbers.Number): |  | ||||||
|             return 1.0, c |  | ||||||
|     except TypeError: |  | ||||||
|         pass |  | ||||||
|     try: |  | ||||||
|         ((a, b, c), d, e) = data  # full syntax |  | ||||||
|         if ( |  | ||||||
|             a is stub |  | ||||||
|             and b == "__mul__" |  | ||||||
|             and isinstance(c, numbers.Number) |  | ||||||
|             and d == "__add__" |  | ||||||
|             and isinstance(e, numbers.Number) |  | ||||||
|         ): |  | ||||||
|             return c, e |  | ||||||
|     except TypeError: |  | ||||||
|         pass |  | ||||||
|     raise ValueError("illegal expression") |  | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
| # -------------------------------------------------------------------- | # -------------------------------------------------------------------- | ||||||
|  | @ -544,8 +549,10 @@ class Image: | ||||||
| 
 | 
 | ||||||
|     def __exit__(self, *args): |     def __exit__(self, *args): | ||||||
|         if hasattr(self, "fp") and getattr(self, "_exclusive_fp", False): |         if hasattr(self, "fp") and getattr(self, "_exclusive_fp", False): | ||||||
|             if hasattr(self, "_close__fp"): |             if getattr(self, "_fp", False): | ||||||
|                 self._close__fp() |                 if self._fp != self.fp: | ||||||
|  |                     self._fp.close() | ||||||
|  |                 self._fp = DeferredError(ValueError("Operation on closed image")) | ||||||
|             if self.fp: |             if self.fp: | ||||||
|                 self.fp.close() |                 self.fp.close() | ||||||
|         self.fp = None |         self.fp = None | ||||||
|  | @ -563,8 +570,10 @@ class Image: | ||||||
|         more information. |         more information. | ||||||
|         """ |         """ | ||||||
|         try: |         try: | ||||||
|             if hasattr(self, "_close__fp"): |             if getattr(self, "_fp", False): | ||||||
|                 self._close__fp() |                 if self._fp != self.fp: | ||||||
|  |                     self._fp.close() | ||||||
|  |                 self._fp = DeferredError(ValueError("Operation on closed image")) | ||||||
|             if self.fp: |             if self.fp: | ||||||
|                 self.fp.close() |                 self.fp.close() | ||||||
|             self.fp = None |             self.fp = None | ||||||
|  | @ -1324,7 +1333,7 @@ class Image: | ||||||
| 
 | 
 | ||||||
|     def getextrema(self): |     def getextrema(self): | ||||||
|         """ |         """ | ||||||
|         Gets the the minimum and maximum pixel values for each band in |         Gets the minimum and maximum pixel values for each band in | ||||||
|         the image. |         the image. | ||||||
| 
 | 
 | ||||||
|         :returns: For a single-band image, a 2-tuple containing the |         :returns: For a single-band image, a 2-tuple containing the | ||||||
|  |  | ||||||
|  | @ -711,8 +711,13 @@ class FreeTypeFont: | ||||||
| 
 | 
 | ||||||
|         :return: A FreeTypeFont object. |         :return: A FreeTypeFont object. | ||||||
|         """ |         """ | ||||||
|  |         if font is None: | ||||||
|  |             try: | ||||||
|  |                 font = BytesIO(self.font_bytes) | ||||||
|  |             except AttributeError: | ||||||
|  |                 font = self.path | ||||||
|         return FreeTypeFont( |         return FreeTypeFont( | ||||||
|             font=self.path if font is None else font, |             font=font, | ||||||
|             size=self.size if size is None else size, |             size=self.size if size is None else size, | ||||||
|             index=self.index if index is None else index, |             index=self.index if index is None else index, | ||||||
|             encoding=self.encoding if encoding is None else encoding, |             encoding=self.encoding if encoding is None else encoding, | ||||||
|  |  | ||||||
|  | @ -62,7 +62,6 @@ class MicImageFile(TiffImagePlugin.TiffImageFile): | ||||||
|         if not self.images: |         if not self.images: | ||||||
|             raise SyntaxError("not an MIC file; no image entries") |             raise SyntaxError("not an MIC file; no image entries") | ||||||
| 
 | 
 | ||||||
|         self.__fp = self.fp |  | ||||||
|         self.frame = None |         self.frame = None | ||||||
|         self._n_frames = len(self.images) |         self._n_frames = len(self.images) | ||||||
|         self.is_animated = self._n_frames > 1 |         self.is_animated = self._n_frames > 1 | ||||||
|  | @ -89,15 +88,6 @@ class MicImageFile(TiffImagePlugin.TiffImageFile): | ||||||
|     def tell(self): |     def tell(self): | ||||||
|         return self.frame |         return self.frame | ||||||
| 
 | 
 | ||||||
|     def _close__fp(self): |  | ||||||
|         try: |  | ||||||
|             if self.__fp != self.fp: |  | ||||||
|                 self.__fp.close() |  | ||||||
|         except AttributeError: |  | ||||||
|             pass |  | ||||||
|         finally: |  | ||||||
|             self.__fp = None |  | ||||||
| 
 |  | ||||||
| 
 | 
 | ||||||
| # | # | ||||||
| # -------------------------------------------------------------------- | # -------------------------------------------------------------------- | ||||||
|  |  | ||||||
|  | @ -58,20 +58,20 @@ class MpoImageFile(JpegImagePlugin.JpegImageFile): | ||||||
|         assert self.n_frames == len(self.__mpoffsets) |         assert self.n_frames == len(self.__mpoffsets) | ||||||
|         del self.info["mpoffset"]  # no longer needed |         del self.info["mpoffset"]  # no longer needed | ||||||
|         self.is_animated = self.n_frames > 1 |         self.is_animated = self.n_frames > 1 | ||||||
|         self.__fp = self.fp  # FIXME: hack |         self._fp = self.fp  # FIXME: hack | ||||||
|         self.__fp.seek(self.__mpoffsets[0])  # get ready to read first frame |         self._fp.seek(self.__mpoffsets[0])  # get ready to read first frame | ||||||
|         self.__frame = 0 |         self.__frame = 0 | ||||||
|         self.offset = 0 |         self.offset = 0 | ||||||
|         # for now we can only handle reading and individual frame extraction |         # for now we can only handle reading and individual frame extraction | ||||||
|         self.readonly = 1 |         self.readonly = 1 | ||||||
| 
 | 
 | ||||||
|     def load_seek(self, pos): |     def load_seek(self, pos): | ||||||
|         self.__fp.seek(pos) |         self._fp.seek(pos) | ||||||
| 
 | 
 | ||||||
|     def seek(self, frame): |     def seek(self, frame): | ||||||
|         if not self._seek_check(frame): |         if not self._seek_check(frame): | ||||||
|             return |             return | ||||||
|         self.fp = self.__fp |         self.fp = self._fp | ||||||
|         self.offset = self.__mpoffsets[frame] |         self.offset = self.__mpoffsets[frame] | ||||||
| 
 | 
 | ||||||
|         self.fp.seek(self.offset + 2)  # skip SOI marker |         self.fp.seek(self.offset + 2)  # skip SOI marker | ||||||
|  | @ -97,15 +97,6 @@ class MpoImageFile(JpegImagePlugin.JpegImageFile): | ||||||
|     def tell(self): |     def tell(self): | ||||||
|         return self.__frame |         return self.__frame | ||||||
| 
 | 
 | ||||||
|     def _close__fp(self): |  | ||||||
|         try: |  | ||||||
|             if self.__fp != self.fp: |  | ||||||
|                 self.__fp.close() |  | ||||||
|         except AttributeError: |  | ||||||
|             pass |  | ||||||
|         finally: |  | ||||||
|             self.__fp = None |  | ||||||
| 
 |  | ||||||
|     @staticmethod |     @staticmethod | ||||||
|     def adopt(jpeg_instance, mpheader=None): |     def adopt(jpeg_instance, mpheader=None): | ||||||
|         """ |         """ | ||||||
|  |  | ||||||
|  | @ -710,7 +710,7 @@ class PngImageFile(ImageFile.ImageFile): | ||||||
| 
 | 
 | ||||||
|         if not _accept(self.fp.read(8)): |         if not _accept(self.fp.read(8)): | ||||||
|             raise SyntaxError("not a PNG file") |             raise SyntaxError("not a PNG file") | ||||||
|         self.__fp = self.fp |         self._fp = self.fp | ||||||
|         self.__frame = 0 |         self.__frame = 0 | ||||||
| 
 | 
 | ||||||
|         # |         # | ||||||
|  | @ -767,7 +767,7 @@ class PngImageFile(ImageFile.ImageFile): | ||||||
|             self._close_exclusive_fp_after_loading = False |             self._close_exclusive_fp_after_loading = False | ||||||
|             self.png.save_rewind() |             self.png.save_rewind() | ||||||
|             self.__rewind_idat = self.__prepare_idat |             self.__rewind_idat = self.__prepare_idat | ||||||
|             self.__rewind = self.__fp.tell() |             self.__rewind = self._fp.tell() | ||||||
|             if self.default_image: |             if self.default_image: | ||||||
|                 # IDAT chunk contains default image and not first animation frame |                 # IDAT chunk contains default image and not first animation frame | ||||||
|                 self.n_frames += 1 |                 self.n_frames += 1 | ||||||
|  | @ -822,7 +822,7 @@ class PngImageFile(ImageFile.ImageFile): | ||||||
|     def _seek(self, frame, rewind=False): |     def _seek(self, frame, rewind=False): | ||||||
|         if frame == 0: |         if frame == 0: | ||||||
|             if rewind: |             if rewind: | ||||||
|                 self.__fp.seek(self.__rewind) |                 self._fp.seek(self.__rewind) | ||||||
|                 self.png.rewind() |                 self.png.rewind() | ||||||
|                 self.__prepare_idat = self.__rewind_idat |                 self.__prepare_idat = self.__rewind_idat | ||||||
|                 self.im = None |                 self.im = None | ||||||
|  | @ -830,7 +830,7 @@ class PngImageFile(ImageFile.ImageFile): | ||||||
|                     self.pyaccess = None |                     self.pyaccess = None | ||||||
|                 self.info = self.png.im_info |                 self.info = self.png.im_info | ||||||
|                 self.tile = self.png.im_tile |                 self.tile = self.png.im_tile | ||||||
|                 self.fp = self.__fp |                 self.fp = self._fp | ||||||
|             self._prev_im = None |             self._prev_im = None | ||||||
|             self.dispose = None |             self.dispose = None | ||||||
|             self.default_image = self.info.get("default_image", False) |             self.default_image = self.info.get("default_image", False) | ||||||
|  | @ -849,7 +849,7 @@ class PngImageFile(ImageFile.ImageFile): | ||||||
|                 self.im.paste(self.dispose, self.dispose_extent) |                 self.im.paste(self.dispose, self.dispose_extent) | ||||||
|             self._prev_im = self.im.copy() |             self._prev_im = self.im.copy() | ||||||
| 
 | 
 | ||||||
|             self.fp = self.__fp |             self.fp = self._fp | ||||||
| 
 | 
 | ||||||
|             # advance to the next frame |             # advance to the next frame | ||||||
|             if self.__prepare_idat: |             if self.__prepare_idat: | ||||||
|  | @ -1027,15 +1027,6 @@ class PngImageFile(ImageFile.ImageFile): | ||||||
|             else {} |             else {} | ||||||
|         ) |         ) | ||||||
| 
 | 
 | ||||||
|     def _close__fp(self): |  | ||||||
|         try: |  | ||||||
|             if self.__fp != self.fp: |  | ||||||
|                 self.__fp.close() |  | ||||||
|         except AttributeError: |  | ||||||
|             pass |  | ||||||
|         finally: |  | ||||||
|             self.__fp = None |  | ||||||
| 
 |  | ||||||
| 
 | 
 | ||||||
| # -------------------------------------------------------------------- | # -------------------------------------------------------------------- | ||||||
| # PNG writer | # PNG writer | ||||||
|  |  | ||||||
|  | @ -132,7 +132,7 @@ class PsdImageFile(ImageFile.ImageFile): | ||||||
|         self.tile = _maketile(self.fp, mode, (0, 0) + self.size, channels) |         self.tile = _maketile(self.fp, mode, (0, 0) + self.size, channels) | ||||||
| 
 | 
 | ||||||
|         # keep the file open |         # keep the file open | ||||||
|         self.__fp = self.fp |         self._fp = self.fp | ||||||
|         self.frame = 1 |         self.frame = 1 | ||||||
|         self._min_frame = 1 |         self._min_frame = 1 | ||||||
| 
 | 
 | ||||||
|  | @ -146,7 +146,7 @@ class PsdImageFile(ImageFile.ImageFile): | ||||||
|             self.mode = mode |             self.mode = mode | ||||||
|             self.tile = tile |             self.tile = tile | ||||||
|             self.frame = layer |             self.frame = layer | ||||||
|             self.fp = self.__fp |             self.fp = self._fp | ||||||
|             return name, bbox |             return name, bbox | ||||||
|         except IndexError as e: |         except IndexError as e: | ||||||
|             raise EOFError("no such layer") from e |             raise EOFError("no such layer") from e | ||||||
|  | @ -155,15 +155,6 @@ class PsdImageFile(ImageFile.ImageFile): | ||||||
|         # return layer number (0=image, 1..max=layers) |         # return layer number (0=image, 1..max=layers) | ||||||
|         return self.frame |         return self.frame | ||||||
| 
 | 
 | ||||||
|     def _close__fp(self): |  | ||||||
|         try: |  | ||||||
|             if self.__fp != self.fp: |  | ||||||
|                 self.__fp.close() |  | ||||||
|         except AttributeError: |  | ||||||
|             pass |  | ||||||
|         finally: |  | ||||||
|             self.__fp = None |  | ||||||
| 
 |  | ||||||
| 
 | 
 | ||||||
| def _layerinfo(fp, ct_bytes): | def _layerinfo(fp, ct_bytes): | ||||||
|     # read layerinfo block |     # read layerinfo block | ||||||
|  |  | ||||||
|  | @ -15,7 +15,7 @@ | ||||||
| # | # | ||||||
| 
 | 
 | ||||||
| ## | ## | ||||||
| # Image plugin for the Spider image format.  This format is is used | # Image plugin for the Spider image format. This format is used | ||||||
| # by the SPIDER software, in processing image data from electron | # by the SPIDER software, in processing image data from electron | ||||||
| # microscopy and tomography. | # microscopy and tomography. | ||||||
| ## | ## | ||||||
|  | @ -149,7 +149,7 @@ class SpiderImageFile(ImageFile.ImageFile): | ||||||
|         self.mode = "F" |         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 |         self._fp = self.fp  # FIXME: hack | ||||||
| 
 | 
 | ||||||
|     @property |     @property | ||||||
|     def n_frames(self): |     def n_frames(self): | ||||||
|  | @ -172,7 +172,7 @@ class SpiderImageFile(ImageFile.ImageFile): | ||||||
|         if not self._seek_check(frame): |         if not self._seek_check(frame): | ||||||
|             return |             return | ||||||
|         self.stkoffset = self.hdrlen + frame * (self.hdrlen + self.imgbytes) |         self.stkoffset = self.hdrlen + frame * (self.hdrlen + self.imgbytes) | ||||||
|         self.fp = self.__fp |         self.fp = self._fp | ||||||
|         self.fp.seek(self.stkoffset) |         self.fp.seek(self.stkoffset) | ||||||
|         self._open() |         self._open() | ||||||
| 
 | 
 | ||||||
|  | @ -191,15 +191,6 @@ class SpiderImageFile(ImageFile.ImageFile): | ||||||
| 
 | 
 | ||||||
|         return ImageTk.PhotoImage(self.convert2byte(), palette=256) |         return ImageTk.PhotoImage(self.convert2byte(), palette=256) | ||||||
| 
 | 
 | ||||||
|     def _close__fp(self): |  | ||||||
|         try: |  | ||||||
|             if self.__fp != self.fp: |  | ||||||
|                 self.__fp.close() |  | ||||||
|         except AttributeError: |  | ||||||
|             pass |  | ||||||
|         finally: |  | ||||||
|             self.__fp = None |  | ||||||
| 
 |  | ||||||
| 
 | 
 | ||||||
| # -------------------------------------------------------------------- | # -------------------------------------------------------------------- | ||||||
| # Image series | # Image series | ||||||
|  |  | ||||||
|  | @ -1073,7 +1073,7 @@ class TiffImageFile(ImageFile.ImageFile): | ||||||
|         # setup frame pointers |         # setup frame pointers | ||||||
|         self.__first = self.__next = self.tag_v2.next |         self.__first = self.__next = self.tag_v2.next | ||||||
|         self.__frame = -1 |         self.__frame = -1 | ||||||
|         self.__fp = self.fp |         self._fp = self.fp | ||||||
|         self._frame_pos = [] |         self._frame_pos = [] | ||||||
|         self._n_frames = None |         self._n_frames = None | ||||||
| 
 | 
 | ||||||
|  | @ -1106,7 +1106,7 @@ class TiffImageFile(ImageFile.ImageFile): | ||||||
|         self.im = Image.core.new(self.mode, self.size) |         self.im = Image.core.new(self.mode, self.size) | ||||||
| 
 | 
 | ||||||
|     def _seek(self, frame): |     def _seek(self, frame): | ||||||
|         self.fp = self.__fp |         self.fp = self._fp | ||||||
| 
 | 
 | ||||||
|         # reset buffered io handle in case fp |         # reset buffered io handle in case fp | ||||||
|         # was passed to libtiff, invalidating the buffer |         # was passed to libtiff, invalidating the buffer | ||||||
|  | @ -1515,15 +1515,6 @@ class TiffImageFile(ImageFile.ImageFile): | ||||||
| 
 | 
 | ||||||
|         self._tile_orientation = self.tag_v2.get(0x0112) |         self._tile_orientation = self.tag_v2.get(0x0112) | ||||||
| 
 | 
 | ||||||
|     def _close__fp(self): |  | ||||||
|         try: |  | ||||||
|             if self.__fp != self.fp: |  | ||||||
|                 self.__fp.close() |  | ||||||
|         except AttributeError: |  | ||||||
|             pass |  | ||||||
|         finally: |  | ||||||
|             self.__fp = None |  | ||||||
| 
 |  | ||||||
| 
 | 
 | ||||||
| # | # | ||||||
| # -------------------------------------------------------------------- | # -------------------------------------------------------------------- | ||||||
|  | @ -1568,7 +1559,13 @@ def _save(im, fp, filename): | ||||||
| 
 | 
 | ||||||
|     encoderinfo = im.encoderinfo |     encoderinfo = im.encoderinfo | ||||||
|     encoderconfig = im.encoderconfig |     encoderconfig = im.encoderconfig | ||||||
|     compression = encoderinfo.get("compression", im.info.get("compression")) |     try: | ||||||
|  |         compression = encoderinfo["compression"] | ||||||
|  |     except KeyError: | ||||||
|  |         compression = im.info.get("compression") | ||||||
|  |         if isinstance(compression, int): | ||||||
|  |             # compression value may be from BMP. Ignore it | ||||||
|  |             compression = None | ||||||
|     if compression is None: |     if compression is None: | ||||||
|         compression = "raw" |         compression = "raw" | ||||||
|     elif compression == "tiff_jpeg": |     elif compression == "tiff_jpeg": | ||||||
|  |  | ||||||
|  | @ -125,7 +125,7 @@ ImagingGifDecode(Imaging im, ImagingCodecState state, UINT8 *buffer, Py_ssize_t | ||||||
| 
 | 
 | ||||||
|                     context->blocksize--; |                     context->blocksize--; | ||||||
| 
 | 
 | ||||||
|                     /* New bits are shifted in from from the left. */ |                     /* New bits are shifted in from the left. */ | ||||||
|                     context->bitbuffer |= (INT32)c << context->bitcount; |                     context->bitbuffer |= (INT32)c << context->bitcount; | ||||||
|                     context->bitcount += 8; |                     context->bitcount += 8; | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
|  | @ -1519,7 +1519,7 @@ error_0: | ||||||
| 
 | 
 | ||||||
| typedef struct { | typedef struct { | ||||||
|     Pixel new; |     Pixel new; | ||||||
|     Pixel furthest; |     uint32_t furthestV; | ||||||
|     uint32_t furthestDistance; |     uint32_t furthestDistance; | ||||||
|     int secondPixel; |     int secondPixel; | ||||||
| } DistanceData; | } DistanceData; | ||||||
|  | @ -1536,7 +1536,7 @@ compute_distances(const HashTable *h, const Pixel pixel, uint32_t *dist, void *u | ||||||
|     } |     } | ||||||
|     if (oldDist > data->furthestDistance) { |     if (oldDist > data->furthestDistance) { | ||||||
|         data->furthestDistance = oldDist; |         data->furthestDistance = oldDist; | ||||||
|         data->furthest.v = pixel.v; |         data->furthestV = pixel.v; | ||||||
|     } |     } | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | @ -1577,10 +1577,11 @@ quantize2( | ||||||
|     data.new.c.b = (int)(.5 + (double)mean[2] / (double)nPixels); |     data.new.c.b = (int)(.5 + (double)mean[2] / (double)nPixels); | ||||||
|     for (i = 0; i < nQuantPixels; i++) { |     for (i = 0; i < nQuantPixels; i++) { | ||||||
|         data.furthestDistance = 0; |         data.furthestDistance = 0; | ||||||
|  |         data.furthestV = pixelData[0].v; | ||||||
|         data.secondPixel = (i == 1) ? 1 : 0; |         data.secondPixel = (i == 1) ? 1 : 0; | ||||||
|         hashtable_foreach_update(h, compute_distances, &data); |         hashtable_foreach_update(h, compute_distances, &data); | ||||||
|         p[i].v = data.furthest.v; |         p[i].v = data.furthestV; | ||||||
|         data.new.v = data.furthest.v; |         data.new.v = data.furthestV; | ||||||
|     } |     } | ||||||
|     hashtable_free(h); |     hashtable_free(h); | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
|  | @ -120,6 +120,7 @@ ImagingTgaRleDecode(Imaging im, ImagingCodecState state, UINT8 *buf, Py_ssize_t | ||||||
|             } |             } | ||||||
|             memcpy(state->buffer + state->x, ptr, n); |             memcpy(state->buffer + state->x, ptr, n); | ||||||
|             ptr += n; |             ptr += n; | ||||||
|  |             bytes -= n; | ||||||
|             extra_bytes -= n; |             extra_bytes -= n; | ||||||
|         } |         } | ||||||
|     } |     } | ||||||
|  |  | ||||||
|  | @ -246,15 +246,15 @@ deps = { | ||||||
|         "libs": [r"Lib\MS\*.lib"], |         "libs": [r"Lib\MS\*.lib"], | ||||||
|     }, |     }, | ||||||
|     "openjpeg": { |     "openjpeg": { | ||||||
|         "url": "https://github.com/uclouvain/openjpeg/archive/v2.4.0.tar.gz", |         "url": "https://github.com/uclouvain/openjpeg/archive/v2.5.0.tar.gz", | ||||||
|         "filename": "openjpeg-2.4.0.tar.gz", |         "filename": "openjpeg-2.5.0.tar.gz", | ||||||
|         "dir": "openjpeg-2.4.0", |         "dir": "openjpeg-2.5.0", | ||||||
|         "build": [ |         "build": [ | ||||||
|             cmd_cmake(("-DBUILD_THIRDPARTY:BOOL=OFF", "-DBUILD_SHARED_LIBS:BOOL=OFF")), |             cmd_cmake(("-DBUILD_THIRDPARTY:BOOL=OFF", "-DBUILD_SHARED_LIBS:BOOL=OFF")), | ||||||
|             cmd_nmake(target="clean"), |             cmd_nmake(target="clean"), | ||||||
|             cmd_nmake(target="openjp2"), |             cmd_nmake(target="openjp2"), | ||||||
|             cmd_mkdir(r"{inc_dir}\openjpeg-2.4.0"), |             cmd_mkdir(r"{inc_dir}\openjpeg-2.5.0"), | ||||||
|             cmd_copy(r"src\lib\openjp2\*.h", r"{inc_dir}\openjpeg-2.4.0"), |             cmd_copy(r"src\lib\openjp2\*.h", r"{inc_dir}\openjpeg-2.5.0"), | ||||||
|         ], |         ], | ||||||
|         "libs": [r"bin\*.lib"], |         "libs": [r"bin\*.lib"], | ||||||
|     }, |     }, | ||||||
|  | @ -280,9 +280,9 @@ deps = { | ||||||
|         "libs": [r"imagequant.lib"], |         "libs": [r"imagequant.lib"], | ||||||
|     }, |     }, | ||||||
|     "harfbuzz": { |     "harfbuzz": { | ||||||
|         "url": "https://github.com/harfbuzz/harfbuzz/archive/4.2.1.zip", |         "url": "https://github.com/harfbuzz/harfbuzz/archive/4.3.0.zip", | ||||||
|         "filename": "harfbuzz-4.2.1.zip", |         "filename": "harfbuzz-4.3.0.zip", | ||||||
|         "dir": "harfbuzz-4.2.1", |         "dir": "harfbuzz-4.3.0", | ||||||
|         "build": [ |         "build": [ | ||||||
|             cmd_cmake("-DHB_HAVE_FREETYPE:BOOL=TRUE"), |             cmd_cmake("-DHB_HAVE_FREETYPE:BOOL=TRUE"), | ||||||
|             cmd_nmake(target="clean"), |             cmd_nmake(target="clean"), | ||||||
|  |  | ||||||
		Loading…
	
		Reference in New Issue
	
	Block a user