mirror of
				https://github.com/python-pillow/Pillow.git
				synced 2025-10-31 16:07:30 +03:00 
			
		
		
		
	Merge branch 'main' into context_manager
This commit is contained in:
		
						commit
						a8c88c213c
					
				
							
								
								
									
										8
									
								
								.github/workflows/test-docker.yml
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										8
									
								
								.github/workflows/test-docker.yml
									
									
									
									
										vendored
									
									
								
							|  | @ -35,10 +35,6 @@ jobs: | |||
|       matrix: | ||||
|         os: ["ubuntu-latest"] | ||||
|         docker: [ | ||||
|           # Run slower jobs first to give them a headstart and reduce waiting time | ||||
|           ubuntu-24.04-noble-ppc64le, | ||||
|           ubuntu-24.04-noble-s390x, | ||||
|           # Then run the remainder | ||||
|           alpine, | ||||
|           amazon-2-amd64, | ||||
|           amazon-2023-amd64, | ||||
|  | @ -56,9 +52,13 @@ jobs: | |||
|         dockerTag: [main] | ||||
|         include: | ||||
|           - docker: "ubuntu-24.04-noble-ppc64le" | ||||
|             os: "ubuntu-22.04" | ||||
|             qemu-arch: "ppc64le" | ||||
|             dockerTag: main | ||||
|           - docker: "ubuntu-24.04-noble-s390x" | ||||
|             os: "ubuntu-22.04" | ||||
|             qemu-arch: "s390x" | ||||
|             dockerTag: main | ||||
|           - docker: "ubuntu-24.04-noble-arm64v8" | ||||
|             os: "ubuntu-24.04-arm" | ||||
|             dockerTag: main | ||||
|  |  | |||
							
								
								
									
										6
									
								
								.github/workflows/wheels-dependencies.sh
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										6
									
								
								.github/workflows/wheels-dependencies.sh
									
									
									
									
										vendored
									
									
								
							|  | @ -38,11 +38,11 @@ ARCHIVE_SDIR=pillow-depends-main | |||
| 
 | ||||
| # Package versions for fresh source builds | ||||
| FREETYPE_VERSION=2.13.3 | ||||
| HARFBUZZ_VERSION=10.1.0 | ||||
| LIBPNG_VERSION=1.6.45 | ||||
| HARFBUZZ_VERSION=10.2.0 | ||||
| LIBPNG_VERSION=1.6.46 | ||||
| JPEGTURBO_VERSION=3.1.0 | ||||
| OPENJPEG_VERSION=2.5.3 | ||||
| XZ_VERSION=5.6.3 | ||||
| XZ_VERSION=5.6.4 | ||||
| TIFF_VERSION=4.6.0 | ||||
| LCMS2_VERSION=2.16 | ||||
| ZLIB_NG_VERSION=2.2.3 | ||||
|  |  | |||
|  | @ -3,26 +3,25 @@ from __future__ import annotations | |||
| import zlib | ||||
| from io import BytesIO | ||||
| 
 | ||||
| import pytest | ||||
| 
 | ||||
| from PIL import Image, ImageFile, PngImagePlugin | ||||
| 
 | ||||
| TEST_FILE = "Tests/images/png_decompression_dos.png" | ||||
| 
 | ||||
| 
 | ||||
| def test_ignore_dos_text() -> None: | ||||
|     ImageFile.LOAD_TRUNCATED_IMAGES = True | ||||
| def test_ignore_dos_text(monkeypatch: pytest.MonkeyPatch) -> None: | ||||
|     monkeypatch.setattr(ImageFile, "LOAD_TRUNCATED_IMAGES", True) | ||||
| 
 | ||||
|     try: | ||||
|         im = Image.open(TEST_FILE) | ||||
|     with Image.open(TEST_FILE) as im: | ||||
|         im.load() | ||||
|     finally: | ||||
|         ImageFile.LOAD_TRUNCATED_IMAGES = False | ||||
| 
 | ||||
|     assert isinstance(im, PngImagePlugin.PngImageFile) | ||||
|     for s in im.text.values(): | ||||
|         assert len(s) < 1024 * 1024, "Text chunk larger than 1M" | ||||
|         assert isinstance(im, PngImagePlugin.PngImageFile) | ||||
|         for s in im.text.values(): | ||||
|             assert len(s) < 1024 * 1024, "Text chunk larger than 1M" | ||||
| 
 | ||||
|     for s in im.info.values(): | ||||
|         assert len(s) < 1024 * 1024, "Text chunk larger than 1M" | ||||
|         for s in im.info.values(): | ||||
|             assert len(s) < 1024 * 1024, "Text chunk larger than 1M" | ||||
| 
 | ||||
| 
 | ||||
| def test_dos_text() -> None: | ||||
|  |  | |||
|  | @ -12,19 +12,16 @@ ORIGINAL_LIMIT = Image.MAX_IMAGE_PIXELS | |||
| 
 | ||||
| 
 | ||||
| class TestDecompressionBomb: | ||||
|     def teardown_method(self) -> None: | ||||
|         Image.MAX_IMAGE_PIXELS = ORIGINAL_LIMIT | ||||
| 
 | ||||
|     def test_no_warning_small_file(self) -> None: | ||||
|         # Implicit assert: no warning. | ||||
|         # A warning would cause a failure. | ||||
|         with Image.open(TEST_FILE): | ||||
|             pass | ||||
| 
 | ||||
|     def test_no_warning_no_limit(self) -> None: | ||||
|     def test_no_warning_no_limit(self, monkeypatch: pytest.MonkeyPatch) -> None: | ||||
|         # Arrange | ||||
|         # Turn limit off | ||||
|         Image.MAX_IMAGE_PIXELS = None | ||||
|         monkeypatch.setattr(Image, "MAX_IMAGE_PIXELS", None) | ||||
|         assert Image.MAX_IMAGE_PIXELS is None | ||||
| 
 | ||||
|         # Act / Assert | ||||
|  | @ -33,18 +30,18 @@ class TestDecompressionBomb: | |||
|         with Image.open(TEST_FILE): | ||||
|             pass | ||||
| 
 | ||||
|     def test_warning(self) -> None: | ||||
|     def test_warning(self, monkeypatch: pytest.MonkeyPatch) -> None: | ||||
|         # Set limit to trigger warning on the test file | ||||
|         Image.MAX_IMAGE_PIXELS = 128 * 128 - 1 | ||||
|         monkeypatch.setattr(Image, "MAX_IMAGE_PIXELS", 128 * 128 - 1) | ||||
|         assert Image.MAX_IMAGE_PIXELS == 128 * 128 - 1 | ||||
| 
 | ||||
|         with pytest.warns(Image.DecompressionBombWarning): | ||||
|             with Image.open(TEST_FILE): | ||||
|                 pass | ||||
| 
 | ||||
|     def test_exception(self) -> None: | ||||
|     def test_exception(self, monkeypatch: pytest.MonkeyPatch) -> None: | ||||
|         # Set limit to trigger exception on the test file | ||||
|         Image.MAX_IMAGE_PIXELS = 64 * 128 - 1 | ||||
|         monkeypatch.setattr(Image, "MAX_IMAGE_PIXELS", 64 * 128 - 1) | ||||
|         assert Image.MAX_IMAGE_PIXELS == 64 * 128 - 1 | ||||
| 
 | ||||
|         with pytest.raises(Image.DecompressionBombError): | ||||
|  | @ -66,9 +63,9 @@ class TestDecompressionBomb: | |||
|             with pytest.raises(Image.DecompressionBombError): | ||||
|                 im.seek(1) | ||||
| 
 | ||||
|     def test_exception_gif_zero_width(self) -> None: | ||||
|     def test_exception_gif_zero_width(self, monkeypatch: pytest.MonkeyPatch) -> None: | ||||
|         # Set limit to trigger exception on the test file | ||||
|         Image.MAX_IMAGE_PIXELS = 4 * 64 * 128 | ||||
|         monkeypatch.setattr(Image, "MAX_IMAGE_PIXELS", 4 * 64 * 128) | ||||
|         assert Image.MAX_IMAGE_PIXELS == 4 * 64 * 128 | ||||
| 
 | ||||
|         with pytest.raises(Image.DecompressionBombError): | ||||
|  |  | |||
|  | @ -39,25 +39,22 @@ def test_sanity() -> None: | |||
|         assert im.is_animated | ||||
| 
 | ||||
| 
 | ||||
| def test_prefix_chunk() -> None: | ||||
|     ImageFile.LOAD_TRUNCATED_IMAGES = True | ||||
|     try: | ||||
|         with Image.open(animated_test_file_with_prefix_chunk) as im: | ||||
|             assert isinstance(im, FliImagePlugin.FliImageFile) | ||||
| def test_prefix_chunk(monkeypatch: pytest.MonkeyPatch) -> None: | ||||
|     monkeypatch.setattr(ImageFile, "LOAD_TRUNCATED_IMAGES", True) | ||||
|     with Image.open(animated_test_file_with_prefix_chunk) as im: | ||||
|         assert isinstance(im, FliImagePlugin.FliImageFile) | ||||
| 
 | ||||
|             assert im.mode == "P" | ||||
|             assert im.size == (320, 200) | ||||
|             assert im.format == "FLI" | ||||
|             assert im.info["duration"] == 171 | ||||
|             assert im.is_animated | ||||
|         assert im.mode == "P" | ||||
|         assert im.size == (320, 200) | ||||
|         assert im.format == "FLI" | ||||
|         assert im.info["duration"] == 171 | ||||
|         assert im.is_animated | ||||
| 
 | ||||
|             palette = im.getpalette() | ||||
|             assert palette is not None | ||||
|             assert palette[3:6] == [255, 255, 255] | ||||
|             assert palette[381:384] == [204, 204, 12] | ||||
|             assert palette[765:] == [252, 0, 0] | ||||
|     finally: | ||||
|         ImageFile.LOAD_TRUNCATED_IMAGES = False | ||||
|         palette = im.getpalette() | ||||
|         assert palette is not None | ||||
|         assert palette[3:6] == [255, 255, 255] | ||||
|         assert palette[381:384] == [204, 204, 12] | ||||
|         assert palette[765:] == [252, 0, 0] | ||||
| 
 | ||||
| 
 | ||||
| @pytest.mark.skipif(is_pypy(), reason="Requires CPython") | ||||
|  |  | |||
|  | @ -113,7 +113,7 @@ def test_palette_not_needed_for_second_frame() -> None: | |||
|         assert_image_similar(im, hopper("L").convert("RGB"), 8) | ||||
| 
 | ||||
| 
 | ||||
| def test_strategy() -> None: | ||||
| def test_strategy(monkeypatch: pytest.MonkeyPatch) -> None: | ||||
|     with Image.open("Tests/images/iss634.gif") as im: | ||||
|         expected_rgb_always = im.convert("RGB") | ||||
| 
 | ||||
|  | @ -123,35 +123,36 @@ def test_strategy() -> None: | |||
|         im.seek(1) | ||||
|         expected_different = im.convert("RGB") | ||||
| 
 | ||||
|     try: | ||||
|         GifImagePlugin.LOADING_STRATEGY = GifImagePlugin.LoadingStrategy.RGB_ALWAYS | ||||
|         with Image.open("Tests/images/iss634.gif") as im: | ||||
|             assert im.mode == "RGB" | ||||
|             assert_image_equal(im, expected_rgb_always) | ||||
|     monkeypatch.setattr( | ||||
|         GifImagePlugin, "LOADING_STRATEGY", GifImagePlugin.LoadingStrategy.RGB_ALWAYS | ||||
|     ) | ||||
|     with Image.open("Tests/images/iss634.gif") as im: | ||||
|         assert im.mode == "RGB" | ||||
|         assert_image_equal(im, expected_rgb_always) | ||||
| 
 | ||||
|         with Image.open("Tests/images/chi.gif") as im: | ||||
|             assert im.mode == "RGBA" | ||||
|             assert_image_equal(im, expected_rgb_always_rgba) | ||||
|     with Image.open("Tests/images/chi.gif") as im: | ||||
|         assert im.mode == "RGBA" | ||||
|         assert_image_equal(im, expected_rgb_always_rgba) | ||||
| 
 | ||||
|         GifImagePlugin.LOADING_STRATEGY = ( | ||||
|             GifImagePlugin.LoadingStrategy.RGB_AFTER_DIFFERENT_PALETTE_ONLY | ||||
|         ) | ||||
|         # Stay in P mode with only a global palette | ||||
|         with Image.open("Tests/images/chi.gif") as im: | ||||
|             assert im.mode == "P" | ||||
|     monkeypatch.setattr( | ||||
|         GifImagePlugin, | ||||
|         "LOADING_STRATEGY", | ||||
|         GifImagePlugin.LoadingStrategy.RGB_AFTER_DIFFERENT_PALETTE_ONLY, | ||||
|     ) | ||||
|     # Stay in P mode with only a global palette | ||||
|     with Image.open("Tests/images/chi.gif") as im: | ||||
|         assert im.mode == "P" | ||||
| 
 | ||||
|             im.seek(1) | ||||
|             assert im.mode == "P" | ||||
|             assert_image_equal(im.convert("RGB"), expected_different) | ||||
|         im.seek(1) | ||||
|         assert im.mode == "P" | ||||
|         assert_image_equal(im.convert("RGB"), expected_different) | ||||
| 
 | ||||
|         # Change to RGB mode when a frame has an individual palette | ||||
|         with Image.open("Tests/images/iss634.gif") as im: | ||||
|             assert im.mode == "P" | ||||
|     # Change to RGB mode when a frame has an individual palette | ||||
|     with Image.open("Tests/images/iss634.gif") as im: | ||||
|         assert im.mode == "P" | ||||
| 
 | ||||
|             im.seek(1) | ||||
|             assert im.mode == "RGB" | ||||
|     finally: | ||||
|         GifImagePlugin.LOADING_STRATEGY = GifImagePlugin.LoadingStrategy.RGB_AFTER_FIRST | ||||
|         im.seek(1) | ||||
|         assert im.mode == "RGB" | ||||
| 
 | ||||
| 
 | ||||
| def test_optimize() -> None: | ||||
|  | @ -583,17 +584,15 @@ def test_dispose_background_transparency() -> None: | |||
| def test_transparent_dispose( | ||||
|     loading_strategy: GifImagePlugin.LoadingStrategy, | ||||
|     expected_colors: tuple[tuple[int | tuple[int, int, int, int], ...]], | ||||
|     monkeypatch: pytest.MonkeyPatch, | ||||
| ) -> None: | ||||
|     GifImagePlugin.LOADING_STRATEGY = loading_strategy | ||||
|     try: | ||||
|         with Image.open("Tests/images/transparent_dispose.gif") as img: | ||||
|             for frame in range(3): | ||||
|                 img.seek(frame) | ||||
|                 for x in range(3): | ||||
|                     color = img.getpixel((x, 0)) | ||||
|                     assert color == expected_colors[frame][x] | ||||
|     finally: | ||||
|         GifImagePlugin.LOADING_STRATEGY = GifImagePlugin.LoadingStrategy.RGB_AFTER_FIRST | ||||
|     monkeypatch.setattr(GifImagePlugin, "LOADING_STRATEGY", loading_strategy) | ||||
|     with Image.open("Tests/images/transparent_dispose.gif") as img: | ||||
|         for frame in range(3): | ||||
|             img.seek(frame) | ||||
|             for x in range(3): | ||||
|                 color = img.getpixel((x, 0)) | ||||
|                 assert color == expected_colors[frame][x] | ||||
| 
 | ||||
| 
 | ||||
| def test_dispose_previous() -> None: | ||||
|  | @ -1445,25 +1444,24 @@ def test_lzw_bits() -> None: | |||
|     ), | ||||
| ) | ||||
| def test_extents( | ||||
|     test_file: str, loading_strategy: GifImagePlugin.LoadingStrategy | ||||
|     test_file: str, | ||||
|     loading_strategy: GifImagePlugin.LoadingStrategy, | ||||
|     monkeypatch: pytest.MonkeyPatch, | ||||
| ) -> None: | ||||
|     GifImagePlugin.LOADING_STRATEGY = loading_strategy | ||||
|     try: | ||||
|         with Image.open("Tests/images/" + test_file) as im: | ||||
|             assert isinstance(im, GifImagePlugin.GifImageFile) | ||||
|             assert im.size == (100, 100) | ||||
|     monkeypatch.setattr(GifImagePlugin, "LOADING_STRATEGY", loading_strategy) | ||||
|     with Image.open("Tests/images/" + test_file) as im: | ||||
|         assert isinstance(im, GifImagePlugin.GifImageFile) | ||||
|         assert im.size == (100, 100) | ||||
| 
 | ||||
|             # Check that n_frames does not change the size | ||||
|             assert im.n_frames == 2 | ||||
|             assert im.size == (100, 100) | ||||
|         # Check that n_frames does not change the size | ||||
|         assert im.n_frames == 2 | ||||
|         assert im.size == (100, 100) | ||||
| 
 | ||||
|             im.seek(1) | ||||
|             assert im.size == (150, 150) | ||||
|         im.seek(1) | ||||
|         assert im.size == (150, 150) | ||||
| 
 | ||||
|             im.load() | ||||
|             assert im.im.size == (150, 150) | ||||
|     finally: | ||||
|         GifImagePlugin.LOADING_STRATEGY = GifImagePlugin.LoadingStrategy.RGB_AFTER_FIRST | ||||
|         im.load() | ||||
|         assert im.im.size == (150, 150) | ||||
| 
 | ||||
| 
 | ||||
| def test_missing_background() -> None: | ||||
|  |  | |||
|  | @ -250,26 +250,23 @@ def test_draw_reloaded(tmp_path: Path) -> None: | |||
|         assert_image_equal_tofile(im, "Tests/images/hopper_draw.ico") | ||||
| 
 | ||||
| 
 | ||||
| def test_truncated_mask() -> None: | ||||
| def test_truncated_mask(monkeypatch: pytest.MonkeyPatch) -> None: | ||||
|     # 1 bpp | ||||
|     with open("Tests/images/hopper_mask.ico", "rb") as fp: | ||||
|         data = fp.read() | ||||
| 
 | ||||
|     ImageFile.LOAD_TRUNCATED_IMAGES = True | ||||
|     monkeypatch.setattr(ImageFile, "LOAD_TRUNCATED_IMAGES", True) | ||||
|     data = data[:-3] | ||||
| 
 | ||||
|     try: | ||||
|         with Image.open(io.BytesIO(data)) as im: | ||||
|             assert im.mode == "1" | ||||
|     with Image.open(io.BytesIO(data)) as im: | ||||
|         assert im.mode == "1" | ||||
| 
 | ||||
|         # 32 bpp | ||||
|         output = io.BytesIO() | ||||
|         expected = hopper("RGBA") | ||||
|         expected.save(output, "ico", bitmap_format="bmp") | ||||
|     # 32 bpp | ||||
|     output = io.BytesIO() | ||||
|     expected = hopper("RGBA") | ||||
|     expected.save(output, "ico", bitmap_format="bmp") | ||||
| 
 | ||||
|         data = output.getvalue()[:-1] | ||||
|     data = output.getvalue()[:-1] | ||||
| 
 | ||||
|         with Image.open(io.BytesIO(data)) as im: | ||||
|             assert im.mode == "RGB" | ||||
|     finally: | ||||
|         ImageFile.LOAD_TRUNCATED_IMAGES = False | ||||
|     with Image.open(io.BytesIO(data)) as im: | ||||
|         assert im.mode == "RGB" | ||||
|  |  | |||
|  | @ -550,12 +550,13 @@ class TestFileJpeg: | |||
|     @mark_if_feature_version( | ||||
|         pytest.mark.valgrind_known_error, "libjpeg_turbo", "2.0", reason="Known Failing" | ||||
|     ) | ||||
|     def test_truncated_jpeg_should_read_all_the_data(self) -> None: | ||||
|     def test_truncated_jpeg_should_read_all_the_data( | ||||
|         self, monkeypatch: pytest.MonkeyPatch | ||||
|     ) -> None: | ||||
|         filename = "Tests/images/truncated_jpeg.jpg" | ||||
|         ImageFile.LOAD_TRUNCATED_IMAGES = True | ||||
|         monkeypatch.setattr(ImageFile, "LOAD_TRUNCATED_IMAGES", True) | ||||
|         with Image.open(filename) as im: | ||||
|             im.load() | ||||
|             ImageFile.LOAD_TRUNCATED_IMAGES = False | ||||
|             assert im.getbbox() is not None | ||||
| 
 | ||||
|     def test_truncated_jpeg_throws_oserror(self) -> None: | ||||
|  | @ -1054,7 +1055,7 @@ class TestFileJpeg: | |||
|             im.save(f, xmp=b"1" * 65505) | ||||
| 
 | ||||
|     @pytest.mark.timeout(timeout=1) | ||||
|     def test_eof(self) -> None: | ||||
|     def test_eof(self, monkeypatch: pytest.MonkeyPatch) -> None: | ||||
|         # Even though this decoder never says that it is finished | ||||
|         # the image should still end when there is no new data | ||||
|         class InfiniteMockPyDecoder(ImageFile.PyDecoder): | ||||
|  | @ -1069,9 +1070,8 @@ class TestFileJpeg: | |||
|             im.tile = [ | ||||
|                 ImageFile._Tile("INFINITE", (0, 0, 128, 128), 0, ("RGB", 0, 1)), | ||||
|             ] | ||||
|             ImageFile.LOAD_TRUNCATED_IMAGES = True | ||||
|             monkeypatch.setattr(ImageFile, "LOAD_TRUNCATED_IMAGES", True) | ||||
|             im.load() | ||||
|             ImageFile.LOAD_TRUNCATED_IMAGES = False | ||||
| 
 | ||||
|     def test_separate_tables(self) -> None: | ||||
|         im = hopper() | ||||
|  |  | |||
|  | @ -182,14 +182,11 @@ def test_load_dpi() -> None: | |||
|         assert "dpi" not in im.info | ||||
| 
 | ||||
| 
 | ||||
| def test_restricted_icc_profile() -> None: | ||||
|     ImageFile.LOAD_TRUNCATED_IMAGES = True | ||||
|     try: | ||||
|         # JPEG2000 image with a restricted ICC profile and a known colorspace | ||||
|         with Image.open("Tests/images/balloon_eciRGBv2_aware.jp2") as im: | ||||
|             assert im.mode == "RGB" | ||||
|     finally: | ||||
|         ImageFile.LOAD_TRUNCATED_IMAGES = False | ||||
| def test_restricted_icc_profile(monkeypatch: pytest.MonkeyPatch) -> None: | ||||
|     monkeypatch.setattr(ImageFile, "LOAD_TRUNCATED_IMAGES", True) | ||||
|     # JPEG2000 image with a restricted ICC profile and a known colorspace | ||||
|     with Image.open("Tests/images/balloon_eciRGBv2_aware.jp2") as im: | ||||
|         assert im.mode == "RGB" | ||||
| 
 | ||||
| 
 | ||||
| @pytest.mark.skipif( | ||||
|  |  | |||
|  | @ -1185,23 +1185,22 @@ class TestFileLibTiff(LibTiffTestCase): | |||
|             assert len(im.tag_v2[STRIPOFFSETS]) > 1 | ||||
| 
 | ||||
|     @pytest.mark.parametrize("argument", (True, False)) | ||||
|     def test_save_single_strip(self, argument: bool, tmp_path: Path) -> None: | ||||
|     def test_save_single_strip( | ||||
|         self, argument: bool, tmp_path: Path, monkeypatch: pytest.MonkeyPatch | ||||
|     ) -> None: | ||||
|         im = hopper("RGB").resize((256, 256)) | ||||
|         out = str(tmp_path / "temp.tif") | ||||
| 
 | ||||
|         if not argument: | ||||
|             TiffImagePlugin.STRIP_SIZE = 2**18 | ||||
|         try: | ||||
|             arguments: dict[str, str | int] = {"compression": "tiff_adobe_deflate"} | ||||
|             if argument: | ||||
|                 arguments["strip_size"] = 2**18 | ||||
|             im.save(out, "TIFF", **arguments) | ||||
|             monkeypatch.setattr(TiffImagePlugin, "STRIP_SIZE", 2**18) | ||||
|         arguments: dict[str, str | int] = {"compression": "tiff_adobe_deflate"} | ||||
|         if argument: | ||||
|             arguments["strip_size"] = 2**18 | ||||
|         im.save(out, "TIFF", **arguments) | ||||
| 
 | ||||
|             with Image.open(out) as im: | ||||
|                 assert isinstance(im, TiffImagePlugin.TiffImageFile) | ||||
|                 assert len(im.tag_v2[STRIPOFFSETS]) == 1 | ||||
|         finally: | ||||
|             TiffImagePlugin.STRIP_SIZE = 65536 | ||||
|         with Image.open(out) as im: | ||||
|             assert isinstance(im, TiffImagePlugin.TiffImageFile) | ||||
|             assert len(im.tag_v2[STRIPOFFSETS]) == 1 | ||||
| 
 | ||||
|     @pytest.mark.parametrize("compression", ("tiff_adobe_deflate", None)) | ||||
|     def test_save_zero(self, compression: str | None, tmp_path: Path) -> None: | ||||
|  |  | |||
|  | @ -378,7 +378,7 @@ class TestFilePng: | |||
|                 with pytest.raises((OSError, SyntaxError)): | ||||
|                     im.verify() | ||||
| 
 | ||||
|     def test_verify_ignores_crc_error(self) -> None: | ||||
|     def test_verify_ignores_crc_error(self, monkeypatch: pytest.MonkeyPatch) -> None: | ||||
|         # check ignores crc errors in ancillary chunks | ||||
| 
 | ||||
|         chunk_data = chunk(b"tEXt", b"spam") | ||||
|  | @ -388,24 +388,20 @@ class TestFilePng: | |||
|         with pytest.raises(SyntaxError): | ||||
|             PngImagePlugin.PngImageFile(BytesIO(image_data)) | ||||
| 
 | ||||
|         ImageFile.LOAD_TRUNCATED_IMAGES = True | ||||
|         try: | ||||
|             im = load(image_data) | ||||
|             assert im is not None | ||||
|         finally: | ||||
|             ImageFile.LOAD_TRUNCATED_IMAGES = False | ||||
|         monkeypatch.setattr(ImageFile, "LOAD_TRUNCATED_IMAGES", True) | ||||
|         im = load(image_data) | ||||
|         assert im is not None | ||||
| 
 | ||||
|     def test_verify_not_ignores_crc_error_in_required_chunk(self) -> None: | ||||
|     def test_verify_not_ignores_crc_error_in_required_chunk( | ||||
|         self, monkeypatch: pytest.MonkeyPatch | ||||
|     ) -> None: | ||||
|         # check does not ignore crc errors in required chunks | ||||
| 
 | ||||
|         image_data = MAGIC + IHDR[:-1] + b"q" + TAIL | ||||
| 
 | ||||
|         ImageFile.LOAD_TRUNCATED_IMAGES = True | ||||
|         try: | ||||
|             with pytest.raises(SyntaxError): | ||||
|                 PngImagePlugin.PngImageFile(BytesIO(image_data)) | ||||
|         finally: | ||||
|             ImageFile.LOAD_TRUNCATED_IMAGES = False | ||||
|         monkeypatch.setattr(ImageFile, "LOAD_TRUNCATED_IMAGES", True) | ||||
|         with pytest.raises(SyntaxError): | ||||
|             PngImagePlugin.PngImageFile(BytesIO(image_data)) | ||||
| 
 | ||||
|     def test_roundtrip_dpi(self) -> None: | ||||
|         # Check dpi roundtripping | ||||
|  | @ -616,7 +612,7 @@ class TestFilePng: | |||
|             (b"prIV", b"VALUE3", True), | ||||
|         ] | ||||
| 
 | ||||
|     def test_textual_chunks_after_idat(self) -> None: | ||||
|     def test_textual_chunks_after_idat(self, monkeypatch: pytest.MonkeyPatch) -> None: | ||||
|         with Image.open("Tests/images/hopper.png") as im: | ||||
|             assert isinstance(im, PngImagePlugin.PngImageFile) | ||||
|             assert "comment" in im.text | ||||
|  | @ -632,6 +628,11 @@ class TestFilePng: | |||
|             with pytest.raises(OSError): | ||||
|                 assert isinstance(im.text, dict) | ||||
| 
 | ||||
|         # Raises an EOFError in load_end | ||||
|         with Image.open("Tests/images/hopper_idat_after_image_end.png") as im: | ||||
|             assert isinstance(im, PngImagePlugin.PngImageFile) | ||||
|             assert im.text == {"TXT": "VALUE", "ZIP": "VALUE"} | ||||
| 
 | ||||
|         # Raises a UnicodeDecodeError in load_end | ||||
|         with Image.open("Tests/images/truncated_image.png") as im: | ||||
|             assert isinstance(im, PngImagePlugin.PngImageFile) | ||||
|  | @ -639,14 +640,8 @@ class TestFilePng: | |||
|             # The file is truncated | ||||
|             with pytest.raises(OSError): | ||||
|                 im.text | ||||
|             ImageFile.LOAD_TRUNCATED_IMAGES = True | ||||
|             monkeypatch.setattr(ImageFile, "LOAD_TRUNCATED_IMAGES", True) | ||||
|             assert isinstance(im.text, dict) | ||||
|             ImageFile.LOAD_TRUNCATED_IMAGES = False | ||||
| 
 | ||||
|         # Raises an EOFError in load_end | ||||
|         with Image.open("Tests/images/hopper_idat_after_image_end.png") as im: | ||||
|             assert isinstance(im, PngImagePlugin.PngImageFile) | ||||
|             assert im.text == {"TXT": "VALUE", "ZIP": "VALUE"} | ||||
| 
 | ||||
|     def test_unknown_compression_method(self) -> None: | ||||
|         with pytest.raises(SyntaxError, match="Unknown compression method"): | ||||
|  | @ -672,15 +667,16 @@ class TestFilePng: | |||
|     @pytest.mark.parametrize( | ||||
|         "cid", (b"IHDR", b"sRGB", b"pHYs", b"acTL", b"fcTL", b"fdAT") | ||||
|     ) | ||||
|     def test_truncated_chunks(self, cid: bytes) -> None: | ||||
|     def test_truncated_chunks( | ||||
|         self, cid: bytes, monkeypatch: pytest.MonkeyPatch | ||||
|     ) -> None: | ||||
|         fp = BytesIO() | ||||
|         with PngImagePlugin.PngStream(fp) as png: | ||||
|             with pytest.raises(ValueError): | ||||
|                 png.call(cid, 0, 0) | ||||
| 
 | ||||
|             ImageFile.LOAD_TRUNCATED_IMAGES = True | ||||
|             monkeypatch.setattr(ImageFile, "LOAD_TRUNCATED_IMAGES", True) | ||||
|             png.call(cid, 0, 0) | ||||
|             ImageFile.LOAD_TRUNCATED_IMAGES = False | ||||
| 
 | ||||
|     @pytest.mark.parametrize("save_all", (True, False)) | ||||
|     def test_specify_bits(self, save_all: bool, tmp_path: Path) -> None: | ||||
|  | @ -826,17 +822,14 @@ class TestFilePng: | |||
|         with Image.open(mystdout) as reloaded: | ||||
|             assert_image_equal_tofile(reloaded, TEST_PNG_FILE) | ||||
| 
 | ||||
|     def test_truncated_end_chunk(self) -> None: | ||||
|     def test_truncated_end_chunk(self, monkeypatch: pytest.MonkeyPatch) -> None: | ||||
|         with Image.open("Tests/images/truncated_end_chunk.png") as im: | ||||
|             with pytest.raises(OSError): | ||||
|                 im.load() | ||||
| 
 | ||||
|         ImageFile.LOAD_TRUNCATED_IMAGES = True | ||||
|         try: | ||||
|             with Image.open("Tests/images/truncated_end_chunk.png") as im: | ||||
|                 assert_image_equal_tofile(im, "Tests/images/hopper.png") | ||||
|         finally: | ||||
|             ImageFile.LOAD_TRUNCATED_IMAGES = False | ||||
|         monkeypatch.setattr(ImageFile, "LOAD_TRUNCATED_IMAGES", True) | ||||
|         with Image.open("Tests/images/truncated_end_chunk.png") as im: | ||||
|             assert_image_equal_tofile(im, "Tests/images/hopper.png") | ||||
| 
 | ||||
| 
 | ||||
| @pytest.mark.skipif(is_win32(), reason="Requires Unix or macOS") | ||||
|  | @ -845,11 +838,11 @@ class TestTruncatedPngPLeaks(PillowLeakTestCase): | |||
|     mem_limit = 2 * 1024  # max increase in K | ||||
|     iterations = 100  # Leak is 56k/iteration, this will leak 5.6megs | ||||
| 
 | ||||
|     def test_leak_load(self) -> None: | ||||
|     def test_leak_load(self, monkeypatch: pytest.MonkeyPatch) -> None: | ||||
|         with open("Tests/images/hopper.png", "rb") as f: | ||||
|             DATA = BytesIO(f.read(16 * 1024)) | ||||
| 
 | ||||
|         ImageFile.LOAD_TRUNCATED_IMAGES = True | ||||
|         monkeypatch.setattr(ImageFile, "LOAD_TRUNCATED_IMAGES", True) | ||||
|         with Image.open(DATA) as im: | ||||
|             im.load() | ||||
| 
 | ||||
|  | @ -857,7 +850,4 @@ class TestTruncatedPngPLeaks(PillowLeakTestCase): | |||
|             with Image.open(DATA) as im: | ||||
|                 im.load() | ||||
| 
 | ||||
|         try: | ||||
|             self._test_leak(core) | ||||
|         finally: | ||||
|             ImageFile.LOAD_TRUNCATED_IMAGES = False | ||||
|         self._test_leak(core) | ||||
|  |  | |||
|  | @ -979,11 +979,10 @@ class TestFileTiff: | |||
| 
 | ||||
|     @pytest.mark.timeout(6) | ||||
|     @pytest.mark.filterwarnings("ignore:Truncated File Read") | ||||
|     def test_timeout(self) -> None: | ||||
|     def test_timeout(self, monkeypatch: pytest.MonkeyPatch) -> None: | ||||
|         with Image.open("Tests/images/timeout-6646305047838720") as im: | ||||
|             ImageFile.LOAD_TRUNCATED_IMAGES = True | ||||
|             monkeypatch.setattr(ImageFile, "LOAD_TRUNCATED_IMAGES", True) | ||||
|             im.load() | ||||
|             ImageFile.LOAD_TRUNCATED_IMAGES = False | ||||
| 
 | ||||
|     @pytest.mark.parametrize( | ||||
|         "test_file", | ||||
|  |  | |||
|  | @ -28,9 +28,9 @@ except ImportError: | |||
| 
 | ||||
| 
 | ||||
| class TestUnsupportedWebp: | ||||
|     def test_unsupported(self) -> None: | ||||
|     def test_unsupported(self, monkeypatch: pytest.MonkeyPatch) -> None: | ||||
|         if HAVE_WEBP: | ||||
|             WebPImagePlugin.SUPPORTED = False | ||||
|             monkeypatch.setattr(WebPImagePlugin, "SUPPORTED", False) | ||||
| 
 | ||||
|         file_path = "Tests/images/hopper.webp" | ||||
|         with pytest.warns(UserWarning): | ||||
|  | @ -38,9 +38,6 @@ class TestUnsupportedWebp: | |||
|                 with Image.open(file_path): | ||||
|                     pass | ||||
| 
 | ||||
|         if HAVE_WEBP: | ||||
|             WebPImagePlugin.SUPPORTED = True | ||||
| 
 | ||||
| 
 | ||||
| @skip_unless_feature("webp") | ||||
| class TestFileWebp: | ||||
|  |  | |||
|  | @ -191,13 +191,10 @@ class TestImageFile: | |||
|                 im.load() | ||||
| 
 | ||||
|     @skip_unless_feature("zlib") | ||||
|     def test_truncated_without_errors(self) -> None: | ||||
|     def test_truncated_without_errors(self, monkeypatch: pytest.MonkeyPatch) -> None: | ||||
|         with Image.open("Tests/images/truncated_image.png") as im: | ||||
|             ImageFile.LOAD_TRUNCATED_IMAGES = True | ||||
|             try: | ||||
|                 im.load() | ||||
|             finally: | ||||
|                 ImageFile.LOAD_TRUNCATED_IMAGES = False | ||||
|             monkeypatch.setattr(ImageFile, "LOAD_TRUNCATED_IMAGES", True) | ||||
|             im.load() | ||||
| 
 | ||||
|     @skip_unless_feature("zlib") | ||||
|     def test_broken_datastream_with_errors(self) -> None: | ||||
|  | @ -206,13 +203,12 @@ class TestImageFile: | |||
|                 im.load() | ||||
| 
 | ||||
|     @skip_unless_feature("zlib") | ||||
|     def test_broken_datastream_without_errors(self) -> None: | ||||
|     def test_broken_datastream_without_errors( | ||||
|         self, monkeypatch: pytest.MonkeyPatch | ||||
|     ) -> None: | ||||
|         with Image.open("Tests/images/broken_data_stream.png") as im: | ||||
|             ImageFile.LOAD_TRUNCATED_IMAGES = True | ||||
|             try: | ||||
|                 im.load() | ||||
|             finally: | ||||
|                 ImageFile.LOAD_TRUNCATED_IMAGES = False | ||||
|             monkeypatch.setattr(ImageFile, "LOAD_TRUNCATED_IMAGES", True) | ||||
|             im.load() | ||||
| 
 | ||||
| 
 | ||||
| class MockPyDecoder(ImageFile.PyDecoder): | ||||
|  |  | |||
|  | @ -7,36 +7,30 @@ import pytest | |||
| from PIL import Image | ||||
| 
 | ||||
| 
 | ||||
| def test_overflow() -> None: | ||||
| def test_overflow(monkeypatch: pytest.MonkeyPatch) -> None: | ||||
|     # There is the potential to overflow comparisons in map.c | ||||
|     # if there are > SIZE_MAX bytes in the image or if | ||||
|     # the file encodes an offset that makes | ||||
|     # (offset + size(bytes)) > SIZE_MAX | ||||
| 
 | ||||
|     # Note that this image triggers the decompression bomb warning: | ||||
|     max_pixels = Image.MAX_IMAGE_PIXELS | ||||
|     Image.MAX_IMAGE_PIXELS = None | ||||
|     monkeypatch.setattr(Image, "MAX_IMAGE_PIXELS", None) | ||||
| 
 | ||||
|     # This image hits the offset test. | ||||
|     with Image.open("Tests/images/l2rgb_read.bmp") as im: | ||||
|         with pytest.raises((ValueError, MemoryError, OSError)): | ||||
|             im.load() | ||||
| 
 | ||||
|     Image.MAX_IMAGE_PIXELS = max_pixels | ||||
| 
 | ||||
| 
 | ||||
| def test_tobytes() -> None: | ||||
| def test_tobytes(monkeypatch: pytest.MonkeyPatch) -> None: | ||||
|     # Note that this image triggers the decompression bomb warning: | ||||
|     max_pixels = Image.MAX_IMAGE_PIXELS | ||||
|     Image.MAX_IMAGE_PIXELS = None | ||||
|     monkeypatch.setattr(Image, "MAX_IMAGE_PIXELS", None) | ||||
| 
 | ||||
|     # Previously raised an access violation on Windows | ||||
|     with Image.open("Tests/images/l2rgb_read.bmp") as im: | ||||
|         with pytest.raises((ValueError, MemoryError, OSError)): | ||||
|             im.tobytes() | ||||
| 
 | ||||
|     Image.MAX_IMAGE_PIXELS = max_pixels | ||||
| 
 | ||||
| 
 | ||||
| @pytest.mark.skipif(sys.maxsize <= 2**32, reason="Requires 64-bit system") | ||||
| def test_ysize() -> None: | ||||
|  |  | |||
|  | @ -2,7 +2,7 @@ | |||
| # install libimagequant | ||||
| 
 | ||||
| archive_name=libimagequant | ||||
| archive_version=4.3.3 | ||||
| archive_version=4.3.4 | ||||
| 
 | ||||
| archive=$archive_name-$archive_version | ||||
| 
 | ||||
|  |  | |||
|  | @ -64,7 +64,7 @@ Many of Pillow's features require external libraries: | |||
| 
 | ||||
| * **libimagequant** provides improved color quantization | ||||
| 
 | ||||
|   * Pillow has been tested with libimagequant **2.6-4.3.3** | ||||
|   * Pillow has been tested with libimagequant **2.6-4.3.4** | ||||
|   * Libimagequant is licensed GPLv3, which is more restrictive than | ||||
|     the Pillow license, therefore we will not be distributing binaries | ||||
|     with libimagequant support enabled. | ||||
|  |  | |||
|  | @ -113,14 +113,14 @@ V = { | |||
|     "BROTLI": "1.1.0", | ||||
|     "FREETYPE": "2.13.3", | ||||
|     "FRIBIDI": "1.0.16", | ||||
|     "HARFBUZZ": "10.1.0", | ||||
|     "HARFBUZZ": "10.2.0", | ||||
|     "JPEGTURBO": "3.1.0", | ||||
|     "LCMS2": "2.16", | ||||
|     "LIBPNG": "1.6.45", | ||||
|     "LIBPNG": "1.6.46", | ||||
|     "LIBWEBP": "1.5.0", | ||||
|     "OPENJPEG": "2.5.3", | ||||
|     "TIFF": "4.6.0", | ||||
|     "XZ": "5.6.3", | ||||
|     "XZ": "5.6.4", | ||||
|     "ZLIBNG": "2.2.3", | ||||
| } | ||||
| V["LIBPNG_XY"] = "".join(V["LIBPNG"].split(".")[:2]) | ||||
|  |  | |||
		Loading…
	
		Reference in New Issue
	
	Block a user