mirror of
				https://github.com/python-pillow/Pillow.git
				synced 2025-10-30 15:37:55 +03:00 
			
		
		
		
	Merge branch 'main' into progress
This commit is contained in:
		
						commit
						24b9ca7a47
					
				|  | @ -9,6 +9,6 @@ from PIL import Image | ||||||
| 
 | 
 | ||||||
| def test_j2k_overflow(tmp_path: Path) -> None: | def test_j2k_overflow(tmp_path: Path) -> None: | ||||||
|     im = Image.new("RGBA", (1024, 131584)) |     im = Image.new("RGBA", (1024, 131584)) | ||||||
|     target = str(tmp_path / "temp.jpc") |     target = tmp_path / "temp.jpc" | ||||||
|     with pytest.raises(OSError): |     with pytest.raises(OSError): | ||||||
|         im.save(target) |         im.save(target) | ||||||
|  |  | ||||||
|  | @ -32,7 +32,7 @@ pytestmark = pytest.mark.skipif(sys.maxsize <= 2**32, reason="requires 64-bit sy | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
| def _write_png(tmp_path: Path, xdim: int, ydim: int) -> None: | def _write_png(tmp_path: Path, xdim: int, ydim: int) -> None: | ||||||
|     f = str(tmp_path / "temp.png") |     f = tmp_path / "temp.png" | ||||||
|     im = Image.new("L", (xdim, ydim), 0) |     im = Image.new("L", (xdim, ydim), 0) | ||||||
|     im.save(f) |     im.save(f) | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
|  | @ -28,7 +28,7 @@ pytestmark = pytest.mark.skipif(sys.maxsize <= 2**32, reason="requires 64-bit sy | ||||||
| def _write_png(tmp_path: Path, xdim: int, ydim: int) -> None: | def _write_png(tmp_path: Path, xdim: int, ydim: int) -> None: | ||||||
|     dtype = np.uint8 |     dtype = np.uint8 | ||||||
|     a = np.zeros((xdim, ydim), dtype=dtype) |     a = np.zeros((xdim, ydim), dtype=dtype) | ||||||
|     f = str(tmp_path / "temp.png") |     f = tmp_path / "temp.png" | ||||||
|     im = Image.fromarray(a, "L") |     im = Image.fromarray(a, "L") | ||||||
|     im.save(f) |     im.save(f) | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
|  | @ -13,6 +13,7 @@ import tempfile | ||||||
| from collections.abc import Sequence | from collections.abc import Sequence | ||||||
| from functools import lru_cache | from functools import lru_cache | ||||||
| from io import BytesIO | from io import BytesIO | ||||||
|  | from pathlib import Path | ||||||
| from typing import Any, Callable | from typing import Any, Callable | ||||||
| 
 | 
 | ||||||
| import pytest | import pytest | ||||||
|  | @ -95,7 +96,10 @@ def assert_image_equal(a: Image.Image, b: Image.Image, msg: str | None = None) - | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
| def assert_image_equal_tofile( | def assert_image_equal_tofile( | ||||||
|     a: Image.Image, filename: str, msg: str | None = None, mode: str | None = None |     a: Image.Image, | ||||||
|  |     filename: str | Path, | ||||||
|  |     msg: str | None = None, | ||||||
|  |     mode: str | None = None, | ||||||
| ) -> None: | ) -> None: | ||||||
|     with Image.open(filename) as img: |     with Image.open(filename) as img: | ||||||
|         if mode: |         if mode: | ||||||
|  | @ -136,7 +140,7 @@ def assert_image_similar( | ||||||
| 
 | 
 | ||||||
| def assert_image_similar_tofile( | def assert_image_similar_tofile( | ||||||
|     a: Image.Image, |     a: Image.Image, | ||||||
|     filename: str, |     filename: str | Path, | ||||||
|     epsilon: float, |     epsilon: float, | ||||||
|     msg: str | None = None, |     msg: str | None = None, | ||||||
| ) -> None: | ) -> None: | ||||||
|  |  | ||||||
|  | @ -346,7 +346,7 @@ def test_apng_sequence_errors(test_file: str) -> None: | ||||||
| 
 | 
 | ||||||
| def test_apng_save(tmp_path: Path) -> None: | def test_apng_save(tmp_path: Path) -> None: | ||||||
|     with Image.open("Tests/images/apng/single_frame.png") as im: |     with Image.open("Tests/images/apng/single_frame.png") as im: | ||||||
|         test_file = str(tmp_path / "temp.png") |         test_file = tmp_path / "temp.png" | ||||||
|         im.save(test_file, save_all=True) |         im.save(test_file, save_all=True) | ||||||
| 
 | 
 | ||||||
|     with Image.open(test_file) as im: |     with Image.open(test_file) as im: | ||||||
|  | @ -376,7 +376,7 @@ def test_apng_save(tmp_path: Path) -> None: | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
| def test_apng_save_alpha(tmp_path: Path) -> None: | def test_apng_save_alpha(tmp_path: Path) -> None: | ||||||
|     test_file = str(tmp_path / "temp.png") |     test_file = tmp_path / "temp.png" | ||||||
| 
 | 
 | ||||||
|     im = Image.new("RGBA", (1, 1), (255, 0, 0, 255)) |     im = Image.new("RGBA", (1, 1), (255, 0, 0, 255)) | ||||||
|     im2 = Image.new("RGBA", (1, 1), (255, 0, 0, 127)) |     im2 = Image.new("RGBA", (1, 1), (255, 0, 0, 127)) | ||||||
|  | @ -394,7 +394,7 @@ def test_apng_save_split_fdat(tmp_path: Path) -> None: | ||||||
|     # frames with image data spanning multiple fdAT chunks (in this case |     # frames with image data spanning multiple fdAT chunks (in this case | ||||||
|     # both the default image and first animation frame will span multiple |     # both the default image and first animation frame will span multiple | ||||||
|     # data chunks) |     # data chunks) | ||||||
|     test_file = str(tmp_path / "temp.png") |     test_file = tmp_path / "temp.png" | ||||||
|     with Image.open("Tests/images/old-style-jpeg-compression.png") as im: |     with Image.open("Tests/images/old-style-jpeg-compression.png") as im: | ||||||
|         frames = [im.copy(), Image.new("RGBA", im.size, (255, 0, 0, 255))] |         frames = [im.copy(), Image.new("RGBA", im.size, (255, 0, 0, 255))] | ||||||
|         im.save( |         im.save( | ||||||
|  | @ -409,7 +409,7 @@ def test_apng_save_split_fdat(tmp_path: Path) -> None: | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
| def test_apng_save_duration_loop(tmp_path: Path) -> None: | def test_apng_save_duration_loop(tmp_path: Path) -> None: | ||||||
|     test_file = str(tmp_path / "temp.png") |     test_file = tmp_path / "temp.png" | ||||||
|     with Image.open("Tests/images/apng/delay.png") as im: |     with Image.open("Tests/images/apng/delay.png") as im: | ||||||
|         frames = [] |         frames = [] | ||||||
|         durations = [] |         durations = [] | ||||||
|  | @ -472,7 +472,7 @@ def test_apng_save_duration_loop(tmp_path: Path) -> None: | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
| def test_apng_save_disposal(tmp_path: Path) -> None: | def test_apng_save_disposal(tmp_path: Path) -> None: | ||||||
|     test_file = str(tmp_path / "temp.png") |     test_file = tmp_path / "temp.png" | ||||||
|     size = (128, 64) |     size = (128, 64) | ||||||
|     red = Image.new("RGBA", size, (255, 0, 0, 255)) |     red = Image.new("RGBA", size, (255, 0, 0, 255)) | ||||||
|     green = Image.new("RGBA", size, (0, 255, 0, 255)) |     green = Image.new("RGBA", size, (0, 255, 0, 255)) | ||||||
|  | @ -573,7 +573,7 @@ def test_apng_save_disposal(tmp_path: Path) -> None: | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
| def test_apng_save_disposal_previous(tmp_path: Path) -> None: | def test_apng_save_disposal_previous(tmp_path: Path) -> None: | ||||||
|     test_file = str(tmp_path / "temp.png") |     test_file = tmp_path / "temp.png" | ||||||
|     size = (128, 64) |     size = (128, 64) | ||||||
|     blue = Image.new("RGBA", size, (0, 0, 255, 255)) |     blue = Image.new("RGBA", size, (0, 0, 255, 255)) | ||||||
|     red = Image.new("RGBA", size, (255, 0, 0, 255)) |     red = Image.new("RGBA", size, (255, 0, 0, 255)) | ||||||
|  | @ -595,7 +595,7 @@ def test_apng_save_disposal_previous(tmp_path: Path) -> None: | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
| def test_apng_save_blend(tmp_path: Path) -> None: | def test_apng_save_blend(tmp_path: Path) -> None: | ||||||
|     test_file = str(tmp_path / "temp.png") |     test_file = tmp_path / "temp.png" | ||||||
|     size = (128, 64) |     size = (128, 64) | ||||||
|     red = Image.new("RGBA", size, (255, 0, 0, 255)) |     red = Image.new("RGBA", size, (255, 0, 0, 255)) | ||||||
|     green = Image.new("RGBA", size, (0, 255, 0, 255)) |     green = Image.new("RGBA", size, (0, 255, 0, 255)) | ||||||
|  | @ -715,7 +715,7 @@ def test_save_all_progress() -> None: | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
| def test_apng_save_size(tmp_path: Path) -> None: | def test_apng_save_size(tmp_path: Path) -> None: | ||||||
|     test_file = str(tmp_path / "temp.png") |     test_file = tmp_path / "temp.png" | ||||||
| 
 | 
 | ||||||
|     im = Image.new("L", (100, 100)) |     im = Image.new("L", (100, 100)) | ||||||
|     im.save(test_file, save_all=True, append_images=[Image.new("L", (200, 200))]) |     im.save(test_file, save_all=True, append_images=[Image.new("L", (200, 200))]) | ||||||
|  | @ -739,7 +739,7 @@ def test_seek_after_close() -> None: | ||||||
| def test_different_modes_in_later_frames( | def test_different_modes_in_later_frames( | ||||||
|     mode: str, default_image: bool, duplicate: bool, tmp_path: Path |     mode: str, default_image: bool, duplicate: bool, tmp_path: Path | ||||||
| ) -> None: | ) -> None: | ||||||
|     test_file = str(tmp_path / "temp.png") |     test_file = tmp_path / "temp.png" | ||||||
| 
 | 
 | ||||||
|     im = Image.new("L", (1, 1)) |     im = Image.new("L", (1, 1)) | ||||||
|     im.save( |     im.save( | ||||||
|  | @ -753,7 +753,7 @@ def test_different_modes_in_later_frames( | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
| def test_different_durations(tmp_path: Path) -> None: | def test_different_durations(tmp_path: Path) -> None: | ||||||
|     test_file = str(tmp_path / "temp.png") |     test_file = tmp_path / "temp.png" | ||||||
| 
 | 
 | ||||||
|     with Image.open("Tests/images/apng/different_durations.png") as im: |     with Image.open("Tests/images/apng/different_durations.png") as im: | ||||||
|         for _ in range(3): |         for _ in range(3): | ||||||
|  |  | ||||||
|  | @ -46,7 +46,7 @@ def test_invalid_file() -> None: | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
| def test_save(tmp_path: Path) -> None: | def test_save(tmp_path: Path) -> None: | ||||||
|     f = str(tmp_path / "temp.blp") |     f = tmp_path / "temp.blp" | ||||||
| 
 | 
 | ||||||
|     for version in ("BLP1", "BLP2"): |     for version in ("BLP1", "BLP2"): | ||||||
|         im = hopper("P") |         im = hopper("P") | ||||||
|  | @ -56,7 +56,7 @@ def test_save(tmp_path: Path) -> None: | ||||||
|             assert_image_equal(im.convert("RGB"), reloaded) |             assert_image_equal(im.convert("RGB"), reloaded) | ||||||
| 
 | 
 | ||||||
|         with Image.open("Tests/images/transparent.png") as im: |         with Image.open("Tests/images/transparent.png") as im: | ||||||
|             f = str(tmp_path / "temp.blp") |             f = tmp_path / "temp.blp" | ||||||
|             im.convert("P").save(f, blp_version=version) |             im.convert("P").save(f, blp_version=version) | ||||||
| 
 | 
 | ||||||
|             with Image.open(f) as reloaded: |             with Image.open(f) as reloaded: | ||||||
|  |  | ||||||
|  | @ -17,7 +17,7 @@ from .helper import ( | ||||||
| 
 | 
 | ||||||
| def test_sanity(tmp_path: Path) -> None: | def test_sanity(tmp_path: Path) -> None: | ||||||
|     def roundtrip(im: Image.Image) -> None: |     def roundtrip(im: Image.Image) -> None: | ||||||
|         outfile = str(tmp_path / "temp.bmp") |         outfile = tmp_path / "temp.bmp" | ||||||
| 
 | 
 | ||||||
|         im.save(outfile, "BMP") |         im.save(outfile, "BMP") | ||||||
| 
 | 
 | ||||||
|  | @ -66,7 +66,7 @@ def test_small_palette(tmp_path: Path) -> None: | ||||||
|     colors = [0, 0, 0, 125, 125, 125, 255, 255, 255] |     colors = [0, 0, 0, 125, 125, 125, 255, 255, 255] | ||||||
|     im.putpalette(colors) |     im.putpalette(colors) | ||||||
| 
 | 
 | ||||||
|     out = str(tmp_path / "temp.bmp") |     out = tmp_path / "temp.bmp" | ||||||
|     im.save(out) |     im.save(out) | ||||||
| 
 | 
 | ||||||
|     with Image.open(out) as reloaded: |     with Image.open(out) as reloaded: | ||||||
|  | @ -74,7 +74,7 @@ def test_small_palette(tmp_path: Path) -> None: | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
| def test_save_too_large(tmp_path: Path) -> None: | def test_save_too_large(tmp_path: Path) -> None: | ||||||
|     outfile = str(tmp_path / "temp.bmp") |     outfile = tmp_path / "temp.bmp" | ||||||
|     with Image.new("RGB", (1, 1)) as im: |     with Image.new("RGB", (1, 1)) as im: | ||||||
|         im._size = (37838, 37838) |         im._size = (37838, 37838) | ||||||
|         with pytest.raises(ValueError): |         with pytest.raises(ValueError): | ||||||
|  | @ -96,7 +96,7 @@ def test_dpi() -> None: | ||||||
| def test_save_bmp_with_dpi(tmp_path: Path) -> None: | def test_save_bmp_with_dpi(tmp_path: Path) -> None: | ||||||
|     # Test for #1301 |     # Test for #1301 | ||||||
|     # Arrange |     # Arrange | ||||||
|     outfile = str(tmp_path / "temp.jpg") |     outfile = tmp_path / "temp.jpg" | ||||||
|     with Image.open("Tests/images/hopper.bmp") as im: |     with Image.open("Tests/images/hopper.bmp") as im: | ||||||
|         assert im.info["dpi"] == (95.98654816726399, 95.98654816726399) |         assert im.info["dpi"] == (95.98654816726399, 95.98654816726399) | ||||||
| 
 | 
 | ||||||
|  | @ -112,7 +112,7 @@ def test_save_bmp_with_dpi(tmp_path: Path) -> None: | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
| def test_save_float_dpi(tmp_path: Path) -> None: | def test_save_float_dpi(tmp_path: Path) -> None: | ||||||
|     outfile = str(tmp_path / "temp.bmp") |     outfile = tmp_path / "temp.bmp" | ||||||
|     with Image.open("Tests/images/hopper.bmp") as im: |     with Image.open("Tests/images/hopper.bmp") as im: | ||||||
|         im.save(outfile, dpi=(72.21216100543306, 72.21216100543306)) |         im.save(outfile, dpi=(72.21216100543306, 72.21216100543306)) | ||||||
|         with Image.open(outfile) as reloaded: |         with Image.open(outfile) as reloaded: | ||||||
|  | @ -152,7 +152,7 @@ def test_dib_header_size(header_size: int, path: str) -> None: | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
| def test_save_dib(tmp_path: Path) -> None: | def test_save_dib(tmp_path: Path) -> None: | ||||||
|     outfile = str(tmp_path / "temp.dib") |     outfile = tmp_path / "temp.dib" | ||||||
| 
 | 
 | ||||||
|     with Image.open("Tests/images/clipboard.dib") as im: |     with Image.open("Tests/images/clipboard.dib") as im: | ||||||
|         im.save(outfile) |         im.save(outfile) | ||||||
|  |  | ||||||
|  | @ -43,7 +43,7 @@ def test_load() -> None: | ||||||
| def test_save(tmp_path: Path) -> None: | def test_save(tmp_path: Path) -> None: | ||||||
|     # Arrange |     # Arrange | ||||||
|     im = hopper() |     im = hopper() | ||||||
|     tmpfile = str(tmp_path / "temp.bufr") |     tmpfile = tmp_path / "temp.bufr" | ||||||
| 
 | 
 | ||||||
|     # Act / Assert: stub cannot save without an implemented handler |     # Act / Assert: stub cannot save without an implemented handler | ||||||
|     with pytest.raises(OSError): |     with pytest.raises(OSError): | ||||||
|  | @ -79,7 +79,7 @@ def test_handler(tmp_path: Path) -> None: | ||||||
|         im.load() |         im.load() | ||||||
|         assert handler.is_loaded() |         assert handler.is_loaded() | ||||||
| 
 | 
 | ||||||
|         temp_file = str(tmp_path / "temp.bufr") |         temp_file = tmp_path / "temp.bufr" | ||||||
|         im.save(temp_file) |         im.save(temp_file) | ||||||
|         assert handler.saved |         assert handler.saved | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
|  | @ -9,7 +9,13 @@ import pytest | ||||||
| 
 | 
 | ||||||
| from PIL import DdsImagePlugin, Image | from PIL import DdsImagePlugin, Image | ||||||
| 
 | 
 | ||||||
| from .helper import assert_image_equal, assert_image_equal_tofile, hopper | from .helper import ( | ||||||
|  |     assert_image_equal, | ||||||
|  |     assert_image_equal_tofile, | ||||||
|  |     assert_image_similar, | ||||||
|  |     assert_image_similar_tofile, | ||||||
|  |     hopper, | ||||||
|  | ) | ||||||
| 
 | 
 | ||||||
| TEST_FILE_DXT1 = "Tests/images/dxt1-rgb-4bbp-noalpha_MipMaps-1.dds" | TEST_FILE_DXT1 = "Tests/images/dxt1-rgb-4bbp-noalpha_MipMaps-1.dds" | ||||||
| TEST_FILE_DXT3 = "Tests/images/dxt3-argb-8bbp-explicitalpha_MipMaps-1.dds" | TEST_FILE_DXT3 = "Tests/images/dxt3-argb-8bbp-explicitalpha_MipMaps-1.dds" | ||||||
|  | @ -109,6 +115,32 @@ def test_sanity_ati1_bc4u(image_path: str) -> None: | ||||||
|         assert_image_equal_tofile(im, TEST_FILE_ATI1.replace(".dds", ".png")) |         assert_image_equal_tofile(im, TEST_FILE_ATI1.replace(".dds", ".png")) | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
|  | def test_dx10_bc2(tmp_path: Path) -> None: | ||||||
|  |     out = tmp_path / "temp.dds" | ||||||
|  |     with Image.open(TEST_FILE_DXT3) as im: | ||||||
|  |         im.save(out, pixel_format="BC2") | ||||||
|  | 
 | ||||||
|  |     with Image.open(out) as reloaded: | ||||||
|  |         assert reloaded.format == "DDS" | ||||||
|  |         assert reloaded.mode == "RGBA" | ||||||
|  |         assert reloaded.size == (256, 256) | ||||||
|  | 
 | ||||||
|  |         assert_image_similar(im, reloaded, 3.81) | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | def test_dx10_bc3(tmp_path: Path) -> None: | ||||||
|  |     out = tmp_path / "temp.dds" | ||||||
|  |     with Image.open(TEST_FILE_DXT5) as im: | ||||||
|  |         im.save(out, pixel_format="BC3") | ||||||
|  | 
 | ||||||
|  |     with Image.open(out) as reloaded: | ||||||
|  |         assert reloaded.format == "DDS" | ||||||
|  |         assert reloaded.mode == "RGBA" | ||||||
|  |         assert reloaded.size == (256, 256) | ||||||
|  | 
 | ||||||
|  |         assert_image_similar(im, reloaded, 3.69) | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
| @pytest.mark.parametrize( | @pytest.mark.parametrize( | ||||||
|     "image_path", |     "image_path", | ||||||
|     ( |     ( | ||||||
|  | @ -368,9 +400,9 @@ def test_not_implemented(test_file: str) -> None: | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
| def test_save_unsupported_mode(tmp_path: Path) -> None: | def test_save_unsupported_mode(tmp_path: Path) -> None: | ||||||
|     out = str(tmp_path / "temp.dds") |     out = tmp_path / "temp.dds" | ||||||
|     im = hopper("HSV") |     im = hopper("HSV") | ||||||
|     with pytest.raises(OSError): |     with pytest.raises(OSError, match="cannot write mode HSV as DDS"): | ||||||
|         im.save(out) |         im.save(out) | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
|  | @ -384,10 +416,98 @@ def test_save_unsupported_mode(tmp_path: Path) -> None: | ||||||
|     ], |     ], | ||||||
| ) | ) | ||||||
| def test_save(mode: str, test_file: str, tmp_path: Path) -> None: | def test_save(mode: str, test_file: str, tmp_path: Path) -> None: | ||||||
|     out = str(tmp_path / "temp.dds") |     out = tmp_path / "temp.dds" | ||||||
|     with Image.open(test_file) as im: |     with Image.open(test_file) as im: | ||||||
|         assert im.mode == mode |         assert im.mode == mode | ||||||
|         im.save(out) |         im.save(out) | ||||||
| 
 | 
 | ||||||
|  |         assert_image_equal_tofile(im, out) | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | def test_save_unsupported_pixel_format(tmp_path: Path) -> None: | ||||||
|  |     out = tmp_path / "temp.dds" | ||||||
|  |     im = hopper() | ||||||
|  |     with pytest.raises(OSError, match="cannot write pixel format UNKNOWN"): | ||||||
|  |         im.save(out, pixel_format="UNKNOWN") | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | def test_save_dxt1(tmp_path: Path) -> None: | ||||||
|  |     # RGB | ||||||
|  |     out = tmp_path / "temp.dds" | ||||||
|  |     with Image.open(TEST_FILE_DXT1) as im: | ||||||
|  |         im.convert("RGB").save(out, pixel_format="DXT1") | ||||||
|  |     assert_image_similar_tofile(im, out, 1.84) | ||||||
|  | 
 | ||||||
|  |     # RGBA | ||||||
|  |     im_alpha = im.copy() | ||||||
|  |     im_alpha.putpixel((0, 0), (0, 0, 0, 0)) | ||||||
|  |     im_alpha.save(out, pixel_format="DXT1") | ||||||
|     with Image.open(out) as reloaded: |     with Image.open(out) as reloaded: | ||||||
|             assert_image_equal(im, reloaded) |         assert reloaded.getpixel((0, 0)) == (0, 0, 0, 0) | ||||||
|  | 
 | ||||||
|  |     # L | ||||||
|  |     im_l = im.convert("L") | ||||||
|  |     im_l.save(out, pixel_format="DXT1") | ||||||
|  |     assert_image_similar_tofile(im_l.convert("RGBA"), out, 6.07) | ||||||
|  | 
 | ||||||
|  |     # LA | ||||||
|  |     im_alpha.convert("LA").save(out, pixel_format="DXT1") | ||||||
|  |     with Image.open(out) as reloaded: | ||||||
|  |         assert reloaded.getpixel((0, 0)) == (0, 0, 0, 0) | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | def test_save_dxt3(tmp_path: Path) -> None: | ||||||
|  |     # RGB | ||||||
|  |     out = tmp_path / "temp.dds" | ||||||
|  |     with Image.open(TEST_FILE_DXT3) as im: | ||||||
|  |         im_rgb = im.convert("RGB") | ||||||
|  |     im_rgb.save(out, pixel_format="DXT3") | ||||||
|  |     assert_image_similar_tofile(im_rgb.convert("RGBA"), out, 1.26) | ||||||
|  | 
 | ||||||
|  |     # RGBA | ||||||
|  |     im.save(out, pixel_format="DXT3") | ||||||
|  |     assert_image_similar_tofile(im, out, 3.81) | ||||||
|  | 
 | ||||||
|  |     # L | ||||||
|  |     im_l = im.convert("L") | ||||||
|  |     im_l.save(out, pixel_format="DXT3") | ||||||
|  |     assert_image_similar_tofile(im_l.convert("RGBA"), out, 5.89) | ||||||
|  | 
 | ||||||
|  |     # LA | ||||||
|  |     im_la = im.convert("LA") | ||||||
|  |     im_la.save(out, pixel_format="DXT3") | ||||||
|  |     assert_image_similar_tofile(im_la.convert("RGBA"), out, 8.44) | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | def test_save_dxt5(tmp_path: Path) -> None: | ||||||
|  |     # RGB | ||||||
|  |     out = tmp_path / "temp.dds" | ||||||
|  |     with Image.open(TEST_FILE_DXT1) as im: | ||||||
|  |         im.convert("RGB").save(out, pixel_format="DXT5") | ||||||
|  |     assert_image_similar_tofile(im, out, 1.84) | ||||||
|  | 
 | ||||||
|  |     # RGBA | ||||||
|  |     with Image.open(TEST_FILE_DXT5) as im_rgba: | ||||||
|  |         im_rgba.save(out, pixel_format="DXT5") | ||||||
|  |     assert_image_similar_tofile(im_rgba, out, 3.69) | ||||||
|  | 
 | ||||||
|  |     # L | ||||||
|  |     im_l = im.convert("L") | ||||||
|  |     im_l.save(out, pixel_format="DXT5") | ||||||
|  |     assert_image_similar_tofile(im_l.convert("RGBA"), out, 6.07) | ||||||
|  | 
 | ||||||
|  |     # LA | ||||||
|  |     im_la = im_rgba.convert("LA") | ||||||
|  |     im_la.save(out, pixel_format="DXT5") | ||||||
|  |     assert_image_similar_tofile(im_la.convert("RGBA"), out, 8.32) | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | def test_save_dx10_bc5(tmp_path: Path) -> None: | ||||||
|  |     out = tmp_path / "temp.dds" | ||||||
|  |     with Image.open(TEST_FILE_DX10_BC5_TYPELESS) as im: | ||||||
|  |         im.save(out, pixel_format="BC5") | ||||||
|  |     assert_image_similar_tofile(im, out, 9.56) | ||||||
|  | 
 | ||||||
|  |     im = hopper("L") | ||||||
|  |     with pytest.raises(OSError, match="only RGB mode can be written as BC5"): | ||||||
|  |         im.save(out, pixel_format="BC5") | ||||||
|  |  | ||||||
|  | @ -239,7 +239,7 @@ def test_transparency() -> None: | ||||||
| def test_file_object(tmp_path: Path) -> None: | def test_file_object(tmp_path: Path) -> None: | ||||||
|     # issue 479 |     # issue 479 | ||||||
|     with Image.open(FILE1) as image1: |     with Image.open(FILE1) as image1: | ||||||
|         with open(str(tmp_path / "temp.eps"), "wb") as fh: |         with open(tmp_path / "temp.eps", "wb") as fh: | ||||||
|             image1.save(fh, "EPS") |             image1.save(fh, "EPS") | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
|  | @ -274,7 +274,7 @@ def test_1(filename: str) -> None: | ||||||
| 
 | 
 | ||||||
| def test_image_mode_not_supported(tmp_path: Path) -> None: | def test_image_mode_not_supported(tmp_path: Path) -> None: | ||||||
|     im = hopper("RGBA") |     im = hopper("RGBA") | ||||||
|     tmpfile = str(tmp_path / "temp.eps") |     tmpfile = tmp_path / "temp.eps" | ||||||
|     with pytest.raises(ValueError): |     with pytest.raises(ValueError): | ||||||
|         im.save(tmpfile) |         im.save(tmpfile) | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
|  | @ -228,7 +228,7 @@ def test_optimize_if_palette_can_be_reduced_by_half() -> None: | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
| def test_full_palette_second_frame(tmp_path: Path) -> None: | def test_full_palette_second_frame(tmp_path: Path) -> None: | ||||||
|     out = str(tmp_path / "temp.gif") |     out = tmp_path / "temp.gif" | ||||||
|     im = Image.new("P", (1, 256)) |     im = Image.new("P", (1, 256)) | ||||||
| 
 | 
 | ||||||
|     full_palette_im = Image.new("P", (1, 256)) |     full_palette_im = Image.new("P", (1, 256)) | ||||||
|  | @ -249,7 +249,7 @@ def test_full_palette_second_frame(tmp_path: Path) -> None: | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
| def test_roundtrip(tmp_path: Path) -> None: | def test_roundtrip(tmp_path: Path) -> None: | ||||||
|     out = str(tmp_path / "temp.gif") |     out = tmp_path / "temp.gif" | ||||||
|     im = hopper() |     im = hopper() | ||||||
|     im.save(out) |     im.save(out) | ||||||
|     with Image.open(out) as reread: |     with Image.open(out) as reread: | ||||||
|  | @ -258,7 +258,7 @@ def test_roundtrip(tmp_path: Path) -> None: | ||||||
| 
 | 
 | ||||||
| def test_roundtrip2(tmp_path: Path) -> None: | def test_roundtrip2(tmp_path: Path) -> None: | ||||||
|     # see https://github.com/python-pillow/Pillow/issues/403 |     # see https://github.com/python-pillow/Pillow/issues/403 | ||||||
|     out = str(tmp_path / "temp.gif") |     out = tmp_path / "temp.gif" | ||||||
|     with Image.open(TEST_GIF) as im: |     with Image.open(TEST_GIF) as im: | ||||||
|         im2 = im.copy() |         im2 = im.copy() | ||||||
|         im2.save(out) |         im2.save(out) | ||||||
|  | @ -268,7 +268,7 @@ def test_roundtrip2(tmp_path: Path) -> None: | ||||||
| 
 | 
 | ||||||
| def test_roundtrip_save_all(tmp_path: Path) -> None: | def test_roundtrip_save_all(tmp_path: Path) -> None: | ||||||
|     # Single frame image |     # Single frame image | ||||||
|     out = str(tmp_path / "temp.gif") |     out = tmp_path / "temp.gif" | ||||||
|     im = hopper() |     im = hopper() | ||||||
|     im.save(out, save_all=True) |     im.save(out, save_all=True) | ||||||
|     with Image.open(out) as reread: |     with Image.open(out) as reread: | ||||||
|  | @ -276,7 +276,7 @@ def test_roundtrip_save_all(tmp_path: Path) -> None: | ||||||
| 
 | 
 | ||||||
|     # Multiframe image |     # Multiframe image | ||||||
|     with Image.open("Tests/images/dispose_bgnd.gif") as im: |     with Image.open("Tests/images/dispose_bgnd.gif") as im: | ||||||
|         out = str(tmp_path / "temp.gif") |         out = tmp_path / "temp.gif" | ||||||
|         im.save(out, save_all=True) |         im.save(out, save_all=True) | ||||||
| 
 | 
 | ||||||
|     with Image.open(out) as reread: |     with Image.open(out) as reread: | ||||||
|  | @ -284,7 +284,7 @@ def test_roundtrip_save_all(tmp_path: Path) -> None: | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
| def test_roundtrip_save_all_1(tmp_path: Path) -> None: | def test_roundtrip_save_all_1(tmp_path: Path) -> None: | ||||||
|     out = str(tmp_path / "temp.gif") |     out = tmp_path / "temp.gif" | ||||||
|     im = Image.new("1", (1, 1)) |     im = Image.new("1", (1, 1)) | ||||||
|     im2 = Image.new("1", (1, 1), 1) |     im2 = Image.new("1", (1, 1), 1) | ||||||
|     im.save(out, save_all=True, append_images=[im2]) |     im.save(out, save_all=True, append_images=[im2]) | ||||||
|  | @ -377,7 +377,7 @@ def test_headers_saving_for_animated_gifs(tmp_path: Path) -> None: | ||||||
|     with Image.open("Tests/images/dispose_bgnd.gif") as im: |     with Image.open("Tests/images/dispose_bgnd.gif") as im: | ||||||
|         info = im.info.copy() |         info = im.info.copy() | ||||||
| 
 | 
 | ||||||
|         out = str(tmp_path / "temp.gif") |         out = tmp_path / "temp.gif" | ||||||
|         im.save(out, save_all=True) |         im.save(out, save_all=True) | ||||||
|     with Image.open(out) as reread: |     with Image.open(out) as reread: | ||||||
|         for header in important_headers: |         for header in important_headers: | ||||||
|  | @ -393,7 +393,7 @@ def test_palette_handling(tmp_path: Path) -> None: | ||||||
|         im = im.resize((100, 100), Image.Resampling.LANCZOS) |         im = im.resize((100, 100), Image.Resampling.LANCZOS) | ||||||
|         im2 = im.convert("P", palette=Image.Palette.ADAPTIVE, colors=256) |         im2 = im.convert("P", palette=Image.Palette.ADAPTIVE, colors=256) | ||||||
| 
 | 
 | ||||||
|         f = str(tmp_path / "temp.gif") |         f = tmp_path / "temp.gif" | ||||||
|         im2.save(f, optimize=True) |         im2.save(f, optimize=True) | ||||||
| 
 | 
 | ||||||
|     with Image.open(f) as reloaded: |     with Image.open(f) as reloaded: | ||||||
|  | @ -404,7 +404,7 @@ def test_palette_434(tmp_path: Path) -> None: | ||||||
|     # see https://github.com/python-pillow/Pillow/issues/434 |     # see https://github.com/python-pillow/Pillow/issues/434 | ||||||
| 
 | 
 | ||||||
|     def roundtrip(im: Image.Image, **kwargs: bool) -> Image.Image: |     def roundtrip(im: Image.Image, **kwargs: bool) -> Image.Image: | ||||||
|         out = str(tmp_path / "temp.gif") |         out = tmp_path / "temp.gif" | ||||||
|         im.copy().save(out, "GIF", **kwargs) |         im.copy().save(out, "GIF", **kwargs) | ||||||
|         reloaded = Image.open(out) |         reloaded = Image.open(out) | ||||||
| 
 | 
 | ||||||
|  | @ -647,7 +647,7 @@ def test_previous_frame_loaded() -> None: | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
| def test_save_dispose(tmp_path: Path) -> None: | def test_save_dispose(tmp_path: Path) -> None: | ||||||
|     out = str(tmp_path / "temp.gif") |     out = tmp_path / "temp.gif" | ||||||
|     im_list = [ |     im_list = [ | ||||||
|         Image.new("L", (100, 100), "#000"), |         Image.new("L", (100, 100), "#000"), | ||||||
|         Image.new("L", (100, 100), "#111"), |         Image.new("L", (100, 100), "#111"), | ||||||
|  | @ -675,7 +675,7 @@ def test_save_dispose(tmp_path: Path) -> None: | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
| def test_dispose2_palette(tmp_path: Path) -> None: | def test_dispose2_palette(tmp_path: Path) -> None: | ||||||
|     out = str(tmp_path / "temp.gif") |     out = tmp_path / "temp.gif" | ||||||
| 
 | 
 | ||||||
|     # Four colors: white, gray, black, red |     # Four colors: white, gray, black, red | ||||||
|     circles = [(255, 255, 255), (153, 153, 153), (0, 0, 0), (255, 0, 0)] |     circles = [(255, 255, 255), (153, 153, 153), (0, 0, 0), (255, 0, 0)] | ||||||
|  | @ -709,7 +709,7 @@ def test_dispose2_palette(tmp_path: Path) -> None: | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
| def test_dispose2_diff(tmp_path: Path) -> None: | def test_dispose2_diff(tmp_path: Path) -> None: | ||||||
|     out = str(tmp_path / "temp.gif") |     out = tmp_path / "temp.gif" | ||||||
| 
 | 
 | ||||||
|     # 4 frames: red/blue, red/red, blue/blue, red/blue |     # 4 frames: red/blue, red/red, blue/blue, red/blue | ||||||
|     circles = [ |     circles = [ | ||||||
|  | @ -751,7 +751,7 @@ def test_dispose2_diff(tmp_path: Path) -> None: | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
| def test_dispose2_background(tmp_path: Path) -> None: | def test_dispose2_background(tmp_path: Path) -> None: | ||||||
|     out = str(tmp_path / "temp.gif") |     out = tmp_path / "temp.gif" | ||||||
| 
 | 
 | ||||||
|     im_list = [] |     im_list = [] | ||||||
| 
 | 
 | ||||||
|  | @ -777,7 +777,7 @@ def test_dispose2_background(tmp_path: Path) -> None: | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
| def test_dispose2_background_frame(tmp_path: Path) -> None: | def test_dispose2_background_frame(tmp_path: Path) -> None: | ||||||
|     out = str(tmp_path / "temp.gif") |     out = tmp_path / "temp.gif" | ||||||
| 
 | 
 | ||||||
|     im_list = [Image.new("RGBA", (1, 20))] |     im_list = [Image.new("RGBA", (1, 20))] | ||||||
| 
 | 
 | ||||||
|  | @ -795,7 +795,7 @@ def test_dispose2_background_frame(tmp_path: Path) -> None: | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
| def test_dispose2_previous_frame(tmp_path: Path) -> None: | def test_dispose2_previous_frame(tmp_path: Path) -> None: | ||||||
|     out = str(tmp_path / "temp.gif") |     out = tmp_path / "temp.gif" | ||||||
| 
 | 
 | ||||||
|     im = Image.new("P", (100, 100)) |     im = Image.new("P", (100, 100)) | ||||||
|     im.info["transparency"] = 0 |     im.info["transparency"] = 0 | ||||||
|  | @ -814,7 +814,7 @@ def test_dispose2_previous_frame(tmp_path: Path) -> None: | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
| def test_dispose2_without_transparency(tmp_path: Path) -> None: | def test_dispose2_without_transparency(tmp_path: Path) -> None: | ||||||
|     out = str(tmp_path / "temp.gif") |     out = tmp_path / "temp.gif" | ||||||
| 
 | 
 | ||||||
|     im = Image.new("P", (100, 100)) |     im = Image.new("P", (100, 100)) | ||||||
| 
 | 
 | ||||||
|  | @ -829,7 +829,7 @@ def test_dispose2_without_transparency(tmp_path: Path) -> None: | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
| def test_transparency_in_second_frame(tmp_path: Path) -> None: | def test_transparency_in_second_frame(tmp_path: Path) -> None: | ||||||
|     out = str(tmp_path / "temp.gif") |     out = tmp_path / "temp.gif" | ||||||
|     with Image.open("Tests/images/different_transparency.gif") as im: |     with Image.open("Tests/images/different_transparency.gif") as im: | ||||||
|         assert im.info["transparency"] == 0 |         assert im.info["transparency"] == 0 | ||||||
| 
 | 
 | ||||||
|  | @ -859,7 +859,7 @@ def test_no_transparency_in_second_frame() -> None: | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
| def test_remapped_transparency(tmp_path: Path) -> None: | def test_remapped_transparency(tmp_path: Path) -> None: | ||||||
|     out = str(tmp_path / "temp.gif") |     out = tmp_path / "temp.gif" | ||||||
| 
 | 
 | ||||||
|     im = Image.new("P", (1, 2)) |     im = Image.new("P", (1, 2)) | ||||||
|     im2 = im.copy() |     im2 = im.copy() | ||||||
|  | @ -877,7 +877,7 @@ def test_remapped_transparency(tmp_path: Path) -> None: | ||||||
| def test_duration(tmp_path: Path) -> None: | def test_duration(tmp_path: Path) -> None: | ||||||
|     duration = 1000 |     duration = 1000 | ||||||
| 
 | 
 | ||||||
|     out = str(tmp_path / "temp.gif") |     out = tmp_path / "temp.gif" | ||||||
|     im = Image.new("L", (100, 100), "#000") |     im = Image.new("L", (100, 100), "#000") | ||||||
| 
 | 
 | ||||||
|     # Check that the argument has priority over the info settings |     # Check that the argument has priority over the info settings | ||||||
|  | @ -891,7 +891,7 @@ def test_duration(tmp_path: Path) -> None: | ||||||
| def test_multiple_duration(tmp_path: Path) -> None: | def test_multiple_duration(tmp_path: Path) -> None: | ||||||
|     duration_list = [1000, 2000, 3000] |     duration_list = [1000, 2000, 3000] | ||||||
| 
 | 
 | ||||||
|     out = str(tmp_path / "temp.gif") |     out = tmp_path / "temp.gif" | ||||||
|     im_list = [ |     im_list = [ | ||||||
|         Image.new("L", (100, 100), "#000"), |         Image.new("L", (100, 100), "#000"), | ||||||
|         Image.new("L", (100, 100), "#111"), |         Image.new("L", (100, 100), "#111"), | ||||||
|  | @ -926,7 +926,7 @@ def test_multiple_duration(tmp_path: Path) -> None: | ||||||
| def test_roundtrip_info_duration(tmp_path: Path) -> None: | def test_roundtrip_info_duration(tmp_path: Path) -> None: | ||||||
|     duration_list = [100, 500, 500] |     duration_list = [100, 500, 500] | ||||||
| 
 | 
 | ||||||
|     out = str(tmp_path / "temp.gif") |     out = tmp_path / "temp.gif" | ||||||
|     with Image.open("Tests/images/transparent_dispose.gif") as im: |     with Image.open("Tests/images/transparent_dispose.gif") as im: | ||||||
|         assert [ |         assert [ | ||||||
|             frame.info["duration"] for frame in ImageSequence.Iterator(im) |             frame.info["duration"] for frame in ImageSequence.Iterator(im) | ||||||
|  | @ -941,7 +941,7 @@ def test_roundtrip_info_duration(tmp_path: Path) -> None: | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
| def test_roundtrip_info_duration_combined(tmp_path: Path) -> None: | def test_roundtrip_info_duration_combined(tmp_path: Path) -> None: | ||||||
|     out = str(tmp_path / "temp.gif") |     out = tmp_path / "temp.gif" | ||||||
|     with Image.open("Tests/images/duplicate_frame.gif") as im: |     with Image.open("Tests/images/duplicate_frame.gif") as im: | ||||||
|         assert [frame.info["duration"] for frame in ImageSequence.Iterator(im)] == [ |         assert [frame.info["duration"] for frame in ImageSequence.Iterator(im)] == [ | ||||||
|             1000, |             1000, | ||||||
|  | @ -959,7 +959,7 @@ def test_roundtrip_info_duration_combined(tmp_path: Path) -> None: | ||||||
| def test_identical_frames(tmp_path: Path) -> None: | def test_identical_frames(tmp_path: Path) -> None: | ||||||
|     duration_list = [1000, 1500, 2000, 4000] |     duration_list = [1000, 1500, 2000, 4000] | ||||||
| 
 | 
 | ||||||
|     out = str(tmp_path / "temp.gif") |     out = tmp_path / "temp.gif" | ||||||
|     im_list = [ |     im_list = [ | ||||||
|         Image.new("L", (100, 100), "#000"), |         Image.new("L", (100, 100), "#000"), | ||||||
|         Image.new("L", (100, 100), "#000"), |         Image.new("L", (100, 100), "#000"), | ||||||
|  | @ -992,7 +992,7 @@ def test_identical_frames(tmp_path: Path) -> None: | ||||||
| def test_identical_frames_to_single_frame( | def test_identical_frames_to_single_frame( | ||||||
|     duration: int | list[int], tmp_path: Path |     duration: int | list[int], tmp_path: Path | ||||||
| ) -> None: | ) -> None: | ||||||
|     out = str(tmp_path / "temp.gif") |     out = tmp_path / "temp.gif" | ||||||
|     im_list = [ |     im_list = [ | ||||||
|         Image.new("L", (100, 100), "#000"), |         Image.new("L", (100, 100), "#000"), | ||||||
|         Image.new("L", (100, 100), "#000"), |         Image.new("L", (100, 100), "#000"), | ||||||
|  | @ -1009,7 +1009,7 @@ def test_identical_frames_to_single_frame( | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
| def test_loop_none(tmp_path: Path) -> None: | def test_loop_none(tmp_path: Path) -> None: | ||||||
|     out = str(tmp_path / "temp.gif") |     out = tmp_path / "temp.gif" | ||||||
|     im = Image.new("L", (100, 100), "#000") |     im = Image.new("L", (100, 100), "#000") | ||||||
|     im.save(out, loop=None) |     im.save(out, loop=None) | ||||||
|     with Image.open(out) as reread: |     with Image.open(out) as reread: | ||||||
|  | @ -1019,7 +1019,7 @@ def test_loop_none(tmp_path: Path) -> None: | ||||||
| def test_number_of_loops(tmp_path: Path) -> None: | def test_number_of_loops(tmp_path: Path) -> None: | ||||||
|     number_of_loops = 2 |     number_of_loops = 2 | ||||||
| 
 | 
 | ||||||
|     out = str(tmp_path / "temp.gif") |     out = tmp_path / "temp.gif" | ||||||
|     im = Image.new("L", (100, 100), "#000") |     im = Image.new("L", (100, 100), "#000") | ||||||
|     im.save(out, loop=number_of_loops) |     im.save(out, loop=number_of_loops) | ||||||
|     with Image.open(out) as reread: |     with Image.open(out) as reread: | ||||||
|  | @ -1035,7 +1035,7 @@ def test_number_of_loops(tmp_path: Path) -> None: | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
| def test_background(tmp_path: Path) -> None: | def test_background(tmp_path: Path) -> None: | ||||||
|     out = str(tmp_path / "temp.gif") |     out = tmp_path / "temp.gif" | ||||||
|     im = Image.new("L", (100, 100), "#000") |     im = Image.new("L", (100, 100), "#000") | ||||||
|     im.info["background"] = 1 |     im.info["background"] = 1 | ||||||
|     im.save(out) |     im.save(out) | ||||||
|  | @ -1044,7 +1044,7 @@ def test_background(tmp_path: Path) -> None: | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
| def test_webp_background(tmp_path: Path) -> None: | def test_webp_background(tmp_path: Path) -> None: | ||||||
|     out = str(tmp_path / "temp.gif") |     out = tmp_path / "temp.gif" | ||||||
| 
 | 
 | ||||||
|     # Test opaque WebP background |     # Test opaque WebP background | ||||||
|     if features.check("webp"): |     if features.check("webp"): | ||||||
|  | @ -1062,7 +1062,7 @@ def test_comment(tmp_path: Path) -> None: | ||||||
|     with Image.open(TEST_GIF) as im: |     with Image.open(TEST_GIF) as im: | ||||||
|         assert im.info["comment"] == b"File written by Adobe Photoshop\xa8 4.0" |         assert im.info["comment"] == b"File written by Adobe Photoshop\xa8 4.0" | ||||||
| 
 | 
 | ||||||
|     out = str(tmp_path / "temp.gif") |     out = tmp_path / "temp.gif" | ||||||
|     im = Image.new("L", (100, 100), "#000") |     im = Image.new("L", (100, 100), "#000") | ||||||
|     im.info["comment"] = b"Test comment text" |     im.info["comment"] = b"Test comment text" | ||||||
|     im.save(out) |     im.save(out) | ||||||
|  | @ -1079,7 +1079,7 @@ def test_comment(tmp_path: Path) -> None: | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
| def test_comment_over_255(tmp_path: Path) -> None: | def test_comment_over_255(tmp_path: Path) -> None: | ||||||
|     out = str(tmp_path / "temp.gif") |     out = tmp_path / "temp.gif" | ||||||
|     im = Image.new("L", (100, 100), "#000") |     im = Image.new("L", (100, 100), "#000") | ||||||
|     comment = b"Test comment text" |     comment = b"Test comment text" | ||||||
|     while len(comment) < 256: |     while len(comment) < 256: | ||||||
|  | @ -1105,7 +1105,7 @@ def test_read_multiple_comment_blocks() -> None: | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
| def test_empty_string_comment(tmp_path: Path) -> None: | def test_empty_string_comment(tmp_path: Path) -> None: | ||||||
|     out = str(tmp_path / "temp.gif") |     out = tmp_path / "temp.gif" | ||||||
|     with Image.open("Tests/images/chi.gif") as im: |     with Image.open("Tests/images/chi.gif") as im: | ||||||
|         assert "comment" in im.info |         assert "comment" in im.info | ||||||
| 
 | 
 | ||||||
|  | @ -1139,7 +1139,7 @@ def test_retain_comment_in_subsequent_frames(tmp_path: Path) -> None: | ||||||
|         assert "comment" not in im.info |         assert "comment" not in im.info | ||||||
| 
 | 
 | ||||||
|     # Test that a saved image keeps the comment |     # Test that a saved image keeps the comment | ||||||
|     out = str(tmp_path / "temp.gif") |     out = tmp_path / "temp.gif" | ||||||
|     with Image.open("Tests/images/dispose_prev.gif") as im: |     with Image.open("Tests/images/dispose_prev.gif") as im: | ||||||
|         im.save(out, save_all=True, comment="Test") |         im.save(out, save_all=True, comment="Test") | ||||||
| 
 | 
 | ||||||
|  | @ -1149,7 +1149,7 @@ def test_retain_comment_in_subsequent_frames(tmp_path: Path) -> None: | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
| def test_version(tmp_path: Path) -> None: | def test_version(tmp_path: Path) -> None: | ||||||
|     out = str(tmp_path / "temp.gif") |     out = tmp_path / "temp.gif" | ||||||
| 
 | 
 | ||||||
|     def assert_version_after_save(im: Image.Image, version: bytes) -> None: |     def assert_version_after_save(im: Image.Image, version: bytes) -> None: | ||||||
|         im.save(out) |         im.save(out) | ||||||
|  | @ -1179,7 +1179,7 @@ def test_version(tmp_path: Path) -> None: | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
| def test_append_images(tmp_path: Path) -> None: | def test_append_images(tmp_path: Path) -> None: | ||||||
|     out = str(tmp_path / "temp.gif") |     out = tmp_path / "temp.gif" | ||||||
| 
 | 
 | ||||||
|     # Test appending single frame images |     # Test appending single frame images | ||||||
|     im = Image.new("RGB", (100, 100), "#f00") |     im = Image.new("RGB", (100, 100), "#f00") | ||||||
|  | @ -1208,7 +1208,7 @@ def test_append_images(tmp_path: Path) -> None: | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
| def test_append_different_size_image(tmp_path: Path) -> None: | def test_append_different_size_image(tmp_path: Path) -> None: | ||||||
|     out = str(tmp_path / "temp.gif") |     out = tmp_path / "temp.gif" | ||||||
| 
 | 
 | ||||||
|     im = Image.new("RGB", (100, 100)) |     im = Image.new("RGB", (100, 100)) | ||||||
|     bigger_im = Image.new("RGB", (200, 200), "#f00") |     bigger_im = Image.new("RGB", (200, 200), "#f00") | ||||||
|  | @ -1235,7 +1235,7 @@ def test_transparent_optimize(tmp_path: Path) -> None: | ||||||
|     im.frombytes(data) |     im.frombytes(data) | ||||||
|     im.putpalette(palette) |     im.putpalette(palette) | ||||||
| 
 | 
 | ||||||
|     out = str(tmp_path / "temp.gif") |     out = tmp_path / "temp.gif" | ||||||
|     im.save(out, transparency=im.getpixel((252, 0))) |     im.save(out, transparency=im.getpixel((252, 0))) | ||||||
| 
 | 
 | ||||||
|     with Image.open(out) as reloaded: |     with Image.open(out) as reloaded: | ||||||
|  | @ -1243,7 +1243,7 @@ def test_transparent_optimize(tmp_path: Path) -> None: | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
| def test_removed_transparency(tmp_path: Path) -> None: | def test_removed_transparency(tmp_path: Path) -> None: | ||||||
|     out = str(tmp_path / "temp.gif") |     out = tmp_path / "temp.gif" | ||||||
|     im = Image.new("RGB", (256, 1)) |     im = Image.new("RGB", (256, 1)) | ||||||
| 
 | 
 | ||||||
|     for x in range(256): |     for x in range(256): | ||||||
|  | @ -1258,7 +1258,7 @@ def test_removed_transparency(tmp_path: Path) -> None: | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
| def test_rgb_transparency(tmp_path: Path) -> None: | def test_rgb_transparency(tmp_path: Path) -> None: | ||||||
|     out = str(tmp_path / "temp.gif") |     out = tmp_path / "temp.gif" | ||||||
| 
 | 
 | ||||||
|     # Single frame |     # Single frame | ||||||
|     im = Image.new("RGB", (1, 1)) |     im = Image.new("RGB", (1, 1)) | ||||||
|  | @ -1280,7 +1280,7 @@ def test_rgb_transparency(tmp_path: Path) -> None: | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
| def test_rgba_transparency(tmp_path: Path) -> None: | def test_rgba_transparency(tmp_path: Path) -> None: | ||||||
|     out = str(tmp_path / "temp.gif") |     out = tmp_path / "temp.gif" | ||||||
| 
 | 
 | ||||||
|     im = hopper("P") |     im = hopper("P") | ||||||
|     im.save(out, save_all=True, append_images=[Image.new("RGBA", im.size)]) |     im.save(out, save_all=True, append_images=[Image.new("RGBA", im.size)]) | ||||||
|  | @ -1290,14 +1290,14 @@ def test_rgba_transparency(tmp_path: Path) -> None: | ||||||
|         assert_image_equal(hopper("P").convert("RGB"), reloaded) |         assert_image_equal(hopper("P").convert("RGB"), reloaded) | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
| def test_background_outside_palettte(tmp_path: Path) -> None: | def test_background_outside_palettte() -> None: | ||||||
|     with Image.open("Tests/images/background_outside_palette.gif") as im: |     with Image.open("Tests/images/background_outside_palette.gif") as im: | ||||||
|         im.seek(1) |         im.seek(1) | ||||||
|         assert im.info["background"] == 255 |         assert im.info["background"] == 255 | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
| def test_bbox(tmp_path: Path) -> None: | def test_bbox(tmp_path: Path) -> None: | ||||||
|     out = str(tmp_path / "temp.gif") |     out = tmp_path / "temp.gif" | ||||||
| 
 | 
 | ||||||
|     im = Image.new("RGB", (100, 100), "#fff") |     im = Image.new("RGB", (100, 100), "#fff") | ||||||
|     ims = [Image.new("RGB", (100, 100), "#000")] |     ims = [Image.new("RGB", (100, 100), "#000")] | ||||||
|  | @ -1308,7 +1308,7 @@ def test_bbox(tmp_path: Path) -> None: | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
| def test_bbox_alpha(tmp_path: Path) -> None: | def test_bbox_alpha(tmp_path: Path) -> None: | ||||||
|     out = str(tmp_path / "temp.gif") |     out = tmp_path / "temp.gif" | ||||||
| 
 | 
 | ||||||
|     im = Image.new("RGBA", (1, 2), (255, 0, 0, 255)) |     im = Image.new("RGBA", (1, 2), (255, 0, 0, 255)) | ||||||
|     im.putpixel((0, 1), (255, 0, 0, 0)) |     im.putpixel((0, 1), (255, 0, 0, 0)) | ||||||
|  | @ -1327,7 +1327,7 @@ def test_palette_save_L(tmp_path: Path) -> None: | ||||||
|     palette = im.getpalette() |     palette = im.getpalette() | ||||||
|     assert palette is not None |     assert palette is not None | ||||||
| 
 | 
 | ||||||
|     out = str(tmp_path / "temp.gif") |     out = tmp_path / "temp.gif" | ||||||
|     im_l.save(out, palette=bytes(palette)) |     im_l.save(out, palette=bytes(palette)) | ||||||
| 
 | 
 | ||||||
|     with Image.open(out) as reloaded: |     with Image.open(out) as reloaded: | ||||||
|  | @ -1338,7 +1338,7 @@ def test_palette_save_P(tmp_path: Path) -> None: | ||||||
|     im = Image.new("P", (1, 2)) |     im = Image.new("P", (1, 2)) | ||||||
|     im.putpixel((0, 1), 1) |     im.putpixel((0, 1), 1) | ||||||
| 
 | 
 | ||||||
|     out = str(tmp_path / "temp.gif") |     out = tmp_path / "temp.gif" | ||||||
|     im.save(out, palette=bytes((1, 2, 3, 4, 5, 6))) |     im.save(out, palette=bytes((1, 2, 3, 4, 5, 6))) | ||||||
| 
 | 
 | ||||||
|     with Image.open(out) as reloaded: |     with Image.open(out) as reloaded: | ||||||
|  | @ -1354,7 +1354,7 @@ def test_palette_save_duplicate_entries(tmp_path: Path) -> None: | ||||||
| 
 | 
 | ||||||
|     im.putpalette((0, 0, 0, 0, 0, 0)) |     im.putpalette((0, 0, 0, 0, 0, 0)) | ||||||
| 
 | 
 | ||||||
|     out = str(tmp_path / "temp.gif") |     out = tmp_path / "temp.gif" | ||||||
|     im.save(out, palette=[0, 0, 0, 0, 0, 0, 1, 1, 1]) |     im.save(out, palette=[0, 0, 0, 0, 0, 0, 1, 1, 1]) | ||||||
| 
 | 
 | ||||||
|     with Image.open(out) as reloaded: |     with Image.open(out) as reloaded: | ||||||
|  | @ -1369,7 +1369,7 @@ def test_palette_save_all_P(tmp_path: Path) -> None: | ||||||
|         frame.putpalette(color) |         frame.putpalette(color) | ||||||
|         frames.append(frame) |         frames.append(frame) | ||||||
| 
 | 
 | ||||||
|     out = str(tmp_path / "temp.gif") |     out = tmp_path / "temp.gif" | ||||||
|     frames[0].save( |     frames[0].save( | ||||||
|         out, save_all=True, palette=[255, 0, 0, 0, 255, 0], append_images=frames[1:] |         out, save_all=True, palette=[255, 0, 0, 0, 255, 0], append_images=frames[1:] | ||||||
|     ) |     ) | ||||||
|  | @ -1392,7 +1392,7 @@ def test_palette_save_ImagePalette(tmp_path: Path) -> None: | ||||||
|     im = hopper("P") |     im = hopper("P") | ||||||
|     palette = ImagePalette.ImagePalette("RGB", list(range(256))[::-1] * 3) |     palette = ImagePalette.ImagePalette("RGB", list(range(256))[::-1] * 3) | ||||||
| 
 | 
 | ||||||
|     out = str(tmp_path / "temp.gif") |     out = tmp_path / "temp.gif" | ||||||
|     im.save(out, palette=palette) |     im.save(out, palette=palette) | ||||||
| 
 | 
 | ||||||
|     with Image.open(out) as reloaded: |     with Image.open(out) as reloaded: | ||||||
|  | @ -1405,7 +1405,7 @@ def test_save_I(tmp_path: Path) -> None: | ||||||
| 
 | 
 | ||||||
|     im = hopper("I") |     im = hopper("I") | ||||||
| 
 | 
 | ||||||
|     out = str(tmp_path / "temp.gif") |     out = tmp_path / "temp.gif" | ||||||
|     im.save(out) |     im.save(out) | ||||||
| 
 | 
 | ||||||
|     with Image.open(out) as reloaded: |     with Image.open(out) as reloaded: | ||||||
|  | @ -1489,7 +1489,7 @@ def test_missing_background() -> None: | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
| def test_saving_rgba(tmp_path: Path) -> None: | def test_saving_rgba(tmp_path: Path) -> None: | ||||||
|     out = str(tmp_path / "temp.gif") |     out = tmp_path / "temp.gif" | ||||||
|     with Image.open("Tests/images/transparent.png") as im: |     with Image.open("Tests/images/transparent.png") as im: | ||||||
|         im.save(out) |         im.save(out) | ||||||
| 
 | 
 | ||||||
|  | @ -1500,7 +1500,7 @@ def test_saving_rgba(tmp_path: Path) -> None: | ||||||
| 
 | 
 | ||||||
| @pytest.mark.parametrize("params", ({}, {"disposal": 2, "optimize": False})) | @pytest.mark.parametrize("params", ({}, {"disposal": 2, "optimize": False})) | ||||||
| def test_p_rgba(tmp_path: Path, params: dict[str, Any]) -> None: | def test_p_rgba(tmp_path: Path, params: dict[str, Any]) -> None: | ||||||
|     out = str(tmp_path / "temp.gif") |     out = tmp_path / "temp.gif" | ||||||
| 
 | 
 | ||||||
|     im1 = Image.new("P", (100, 100)) |     im1 = Image.new("P", (100, 100)) | ||||||
|     d = ImageDraw.Draw(im1) |     d = ImageDraw.Draw(im1) | ||||||
|  |  | ||||||
|  | @ -32,3 +32,4 @@ def test_get_palette() -> None: | ||||||
| 
 | 
 | ||||||
|     # Assert |     # Assert | ||||||
|     assert mode == "RGB" |     assert mode == "RGB" | ||||||
|  |     assert len(palette) / 3 == 8 | ||||||
|  |  | ||||||
|  | @ -43,7 +43,7 @@ def test_load() -> None: | ||||||
| def test_save(tmp_path: Path) -> None: | def test_save(tmp_path: Path) -> None: | ||||||
|     # Arrange |     # Arrange | ||||||
|     im = hopper() |     im = hopper() | ||||||
|     tmpfile = str(tmp_path / "temp.grib") |     tmpfile = tmp_path / "temp.grib" | ||||||
| 
 | 
 | ||||||
|     # Act / Assert: stub cannot save without an implemented handler |     # Act / Assert: stub cannot save without an implemented handler | ||||||
|     with pytest.raises(OSError): |     with pytest.raises(OSError): | ||||||
|  | @ -79,7 +79,7 @@ def test_handler(tmp_path: Path) -> None: | ||||||
|         im.load() |         im.load() | ||||||
|         assert handler.is_loaded() |         assert handler.is_loaded() | ||||||
| 
 | 
 | ||||||
|         temp_file = str(tmp_path / "temp.grib") |         temp_file = tmp_path / "temp.grib" | ||||||
|         im.save(temp_file) |         im.save(temp_file) | ||||||
|         assert handler.saved |         assert handler.saved | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
|  | @ -81,7 +81,7 @@ def test_handler(tmp_path: Path) -> None: | ||||||
|         im.load() |         im.load() | ||||||
|         assert handler.is_loaded() |         assert handler.is_loaded() | ||||||
| 
 | 
 | ||||||
|         temp_file = str(tmp_path / "temp.h5") |         temp_file = tmp_path / "temp.h5" | ||||||
|         im.save(temp_file) |         im.save(temp_file) | ||||||
|         assert handler.saved |         assert handler.saved | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
|  | @ -43,7 +43,7 @@ def test_load() -> None: | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
| def test_save(tmp_path: Path) -> None: | def test_save(tmp_path: Path) -> None: | ||||||
|     temp_file = str(tmp_path / "temp.icns") |     temp_file = tmp_path / "temp.icns" | ||||||
| 
 | 
 | ||||||
|     with Image.open(TEST_FILE) as im: |     with Image.open(TEST_FILE) as im: | ||||||
|         im.save(temp_file) |         im.save(temp_file) | ||||||
|  | @ -60,7 +60,7 @@ def test_save(tmp_path: Path) -> None: | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
| def test_save_append_images(tmp_path: Path) -> None: | def test_save_append_images(tmp_path: Path) -> None: | ||||||
|     temp_file = str(tmp_path / "temp.icns") |     temp_file = tmp_path / "temp.icns" | ||||||
|     provided_im = Image.new("RGBA", (32, 32), (255, 0, 0, 128)) |     provided_im = Image.new("RGBA", (32, 32), (255, 0, 0, 128)) | ||||||
| 
 | 
 | ||||||
|     with Image.open(TEST_FILE) as im: |     with Image.open(TEST_FILE) as im: | ||||||
|  |  | ||||||
|  | @ -41,7 +41,7 @@ def test_black_and_white() -> None: | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
| def test_palette(tmp_path: Path) -> None: | def test_palette(tmp_path: Path) -> None: | ||||||
|     temp_file = str(tmp_path / "temp.ico") |     temp_file = tmp_path / "temp.ico" | ||||||
| 
 | 
 | ||||||
|     im = Image.new("P", (16, 16)) |     im = Image.new("P", (16, 16)) | ||||||
|     im.save(temp_file) |     im.save(temp_file) | ||||||
|  | @ -88,7 +88,7 @@ def test_save_to_bytes() -> None: | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
| def test_getpixel(tmp_path: Path) -> None: | def test_getpixel(tmp_path: Path) -> None: | ||||||
|     temp_file = str(tmp_path / "temp.ico") |     temp_file = tmp_path / "temp.ico" | ||||||
| 
 | 
 | ||||||
|     im = hopper() |     im = hopper() | ||||||
|     im.save(temp_file, "ico", sizes=[(32, 32), (64, 64)]) |     im.save(temp_file, "ico", sizes=[(32, 32), (64, 64)]) | ||||||
|  | @ -101,8 +101,8 @@ def test_getpixel(tmp_path: Path) -> None: | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
| def test_no_duplicates(tmp_path: Path) -> None: | def test_no_duplicates(tmp_path: Path) -> None: | ||||||
|     temp_file = str(tmp_path / "temp.ico") |     temp_file = tmp_path / "temp.ico" | ||||||
|     temp_file2 = str(tmp_path / "temp2.ico") |     temp_file2 = tmp_path / "temp2.ico" | ||||||
| 
 | 
 | ||||||
|     im = hopper() |     im = hopper() | ||||||
|     sizes = [(32, 32), (64, 64)] |     sizes = [(32, 32), (64, 64)] | ||||||
|  | @ -115,8 +115,8 @@ def test_no_duplicates(tmp_path: Path) -> None: | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
| def test_different_bit_depths(tmp_path: Path) -> None: | def test_different_bit_depths(tmp_path: Path) -> None: | ||||||
|     temp_file = str(tmp_path / "temp.ico") |     temp_file = tmp_path / "temp.ico" | ||||||
|     temp_file2 = str(tmp_path / "temp2.ico") |     temp_file2 = tmp_path / "temp2.ico" | ||||||
| 
 | 
 | ||||||
|     im = hopper() |     im = hopper() | ||||||
|     im.save(temp_file, "ico", bitmap_format="bmp", sizes=[(128, 128)]) |     im.save(temp_file, "ico", bitmap_format="bmp", sizes=[(128, 128)]) | ||||||
|  | @ -132,8 +132,8 @@ def test_different_bit_depths(tmp_path: Path) -> None: | ||||||
|     assert os.path.getsize(temp_file) != os.path.getsize(temp_file2) |     assert os.path.getsize(temp_file) != os.path.getsize(temp_file2) | ||||||
| 
 | 
 | ||||||
|     # Test that only matching sizes of different bit depths are saved |     # Test that only matching sizes of different bit depths are saved | ||||||
|     temp_file3 = str(tmp_path / "temp3.ico") |     temp_file3 = tmp_path / "temp3.ico" | ||||||
|     temp_file4 = str(tmp_path / "temp4.ico") |     temp_file4 = tmp_path / "temp4.ico" | ||||||
| 
 | 
 | ||||||
|     im.save(temp_file3, "ico", bitmap_format="bmp", sizes=[(128, 128)]) |     im.save(temp_file3, "ico", bitmap_format="bmp", sizes=[(128, 128)]) | ||||||
|     im.save( |     im.save( | ||||||
|  | @ -186,7 +186,7 @@ def test_save_256x256(tmp_path: Path) -> None: | ||||||
|     """Issue #2264 https://github.com/python-pillow/Pillow/issues/2264""" |     """Issue #2264 https://github.com/python-pillow/Pillow/issues/2264""" | ||||||
|     # Arrange |     # Arrange | ||||||
|     with Image.open("Tests/images/hopper_256x256.ico") as im: |     with Image.open("Tests/images/hopper_256x256.ico") as im: | ||||||
|         outfile = str(tmp_path / "temp_saved_hopper_256x256.ico") |         outfile = tmp_path / "temp_saved_hopper_256x256.ico" | ||||||
| 
 | 
 | ||||||
|         # Act |         # Act | ||||||
|         im.save(outfile) |         im.save(outfile) | ||||||
|  | @ -202,7 +202,7 @@ def test_only_save_relevant_sizes(tmp_path: Path) -> None: | ||||||
|     """ |     """ | ||||||
|     # Arrange |     # Arrange | ||||||
|     with Image.open("Tests/images/python.ico") as im:  # 16x16, 32x32, 48x48 |     with Image.open("Tests/images/python.ico") as im:  # 16x16, 32x32, 48x48 | ||||||
|         outfile = str(tmp_path / "temp_saved_python.ico") |         outfile = tmp_path / "temp_saved_python.ico" | ||||||
|         # Act |         # Act | ||||||
|         im.save(outfile) |         im.save(outfile) | ||||||
| 
 | 
 | ||||||
|  | @ -215,7 +215,7 @@ def test_save_append_images(tmp_path: Path) -> None: | ||||||
|     # append_images should be used for scaled down versions of the image |     # append_images should be used for scaled down versions of the image | ||||||
|     im = hopper("RGBA") |     im = hopper("RGBA") | ||||||
|     provided_im = Image.new("RGBA", (32, 32), (255, 0, 0)) |     provided_im = Image.new("RGBA", (32, 32), (255, 0, 0)) | ||||||
|     outfile = str(tmp_path / "temp_saved_multi_icon.ico") |     outfile = tmp_path / "temp_saved_multi_icon.ico" | ||||||
|     im.save(outfile, sizes=[(32, 32), (128, 128)], append_images=[provided_im]) |     im.save(outfile, sizes=[(32, 32), (128, 128)], append_images=[provided_im]) | ||||||
| 
 | 
 | ||||||
|     with Image.open(outfile) as reread: |     with Image.open(outfile) as reread: | ||||||
|  | @ -235,7 +235,7 @@ def test_unexpected_size() -> None: | ||||||
| 
 | 
 | ||||||
| def test_draw_reloaded(tmp_path: Path) -> None: | def test_draw_reloaded(tmp_path: Path) -> None: | ||||||
|     with Image.open(TEST_ICO_FILE) as im: |     with Image.open(TEST_ICO_FILE) as im: | ||||||
|         outfile = str(tmp_path / "temp_saved_hopper_draw.ico") |         outfile = tmp_path / "temp_saved_hopper_draw.ico" | ||||||
| 
 | 
 | ||||||
|         draw = ImageDraw.Draw(im) |         draw = ImageDraw.Draw(im) | ||||||
|         draw.line((0, 0) + im.size, "#f00") |         draw.line((0, 0) + im.size, "#f00") | ||||||
|  |  | ||||||
|  | @ -23,7 +23,7 @@ def test_sanity() -> None: | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
| def test_name_limit(tmp_path: Path) -> None: | def test_name_limit(tmp_path: Path) -> None: | ||||||
|     out = str(tmp_path / ("name_limit_test" * 7 + ".im")) |     out = tmp_path / ("name_limit_test" * 7 + ".im") | ||||||
|     with Image.open(TEST_IM) as im: |     with Image.open(TEST_IM) as im: | ||||||
|         im.save(out) |         im.save(out) | ||||||
|     assert filecmp.cmp(out, "Tests/images/hopper_long_name.im") |     assert filecmp.cmp(out, "Tests/images/hopper_long_name.im") | ||||||
|  | @ -87,7 +87,7 @@ def test_eoferror() -> None: | ||||||
| 
 | 
 | ||||||
| @pytest.mark.parametrize("mode", ("RGB", "P", "PA")) | @pytest.mark.parametrize("mode", ("RGB", "P", "PA")) | ||||||
| def test_roundtrip(mode: str, tmp_path: Path) -> None: | def test_roundtrip(mode: str, tmp_path: Path) -> None: | ||||||
|     out = str(tmp_path / "temp.im") |     out = tmp_path / "temp.im" | ||||||
|     im = hopper(mode) |     im = hopper(mode) | ||||||
|     im.save(out) |     im.save(out) | ||||||
|     assert_image_equal_tofile(im, out) |     assert_image_equal_tofile(im, out) | ||||||
|  | @ -98,7 +98,7 @@ def test_small_palette(tmp_path: Path) -> None: | ||||||
|     colors = [0, 1, 2] |     colors = [0, 1, 2] | ||||||
|     im.putpalette(colors) |     im.putpalette(colors) | ||||||
| 
 | 
 | ||||||
|     out = str(tmp_path / "temp.im") |     out = tmp_path / "temp.im" | ||||||
|     im.save(out) |     im.save(out) | ||||||
| 
 | 
 | ||||||
|     with Image.open(out) as reloaded: |     with Image.open(out) as reloaded: | ||||||
|  | @ -106,7 +106,7 @@ def test_small_palette(tmp_path: Path) -> None: | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
| def test_save_unsupported_mode(tmp_path: Path) -> None: | def test_save_unsupported_mode(tmp_path: Path) -> None: | ||||||
|     out = str(tmp_path / "temp.im") |     out = tmp_path / "temp.im" | ||||||
|     im = hopper("HSV") |     im = hopper("HSV") | ||||||
|     with pytest.raises(ValueError): |     with pytest.raises(ValueError): | ||||||
|         im.save(out) |         im.save(out) | ||||||
|  |  | ||||||
|  | @ -83,7 +83,7 @@ class TestFileJpeg: | ||||||
| 
 | 
 | ||||||
|     @pytest.mark.parametrize("size", ((1, 0), (0, 1), (0, 0))) |     @pytest.mark.parametrize("size", ((1, 0), (0, 1), (0, 0))) | ||||||
|     def test_zero(self, size: tuple[int, int], tmp_path: Path) -> None: |     def test_zero(self, size: tuple[int, int], tmp_path: Path) -> None: | ||||||
|         f = str(tmp_path / "temp.jpg") |         f = tmp_path / "temp.jpg" | ||||||
|         im = Image.new("RGB", size) |         im = Image.new("RGB", size) | ||||||
|         with pytest.raises(ValueError): |         with pytest.raises(ValueError): | ||||||
|             im.save(f) |             im.save(f) | ||||||
|  | @ -194,7 +194,7 @@ class TestFileJpeg: | ||||||
|             icc_profile = im1.info["icc_profile"] |             icc_profile = im1.info["icc_profile"] | ||||||
|             assert len(icc_profile) == 3144 |             assert len(icc_profile) == 3144 | ||||||
|             # Roundtrip via physical file. |             # Roundtrip via physical file. | ||||||
|             f = str(tmp_path / "temp.jpg") |             f = tmp_path / "temp.jpg" | ||||||
|             im1.save(f, icc_profile=icc_profile) |             im1.save(f, icc_profile=icc_profile) | ||||||
|         with Image.open(f) as im2: |         with Image.open(f) as im2: | ||||||
|             assert im2.info.get("icc_profile") == icc_profile |             assert im2.info.get("icc_profile") == icc_profile | ||||||
|  | @ -238,7 +238,7 @@ class TestFileJpeg: | ||||||
|         # Sometimes the meta data on the icc_profile block is bigger than |         # Sometimes the meta data on the icc_profile block is bigger than | ||||||
|         # Image.MAXBLOCK or the image size. |         # Image.MAXBLOCK or the image size. | ||||||
|         with Image.open("Tests/images/icc_profile_big.jpg") as im: |         with Image.open("Tests/images/icc_profile_big.jpg") as im: | ||||||
|             f = str(tmp_path / "temp.jpg") |             f = tmp_path / "temp.jpg" | ||||||
|             icc_profile = im.info["icc_profile"] |             icc_profile = im.info["icc_profile"] | ||||||
|             # Should not raise OSError for image with icc larger than image size. |             # Should not raise OSError for image with icc larger than image size. | ||||||
|             im.save( |             im.save( | ||||||
|  | @ -250,11 +250,11 @@ class TestFileJpeg: | ||||||
|             ) |             ) | ||||||
| 
 | 
 | ||||||
|         with Image.open("Tests/images/flower2.jpg") as im: |         with Image.open("Tests/images/flower2.jpg") as im: | ||||||
|             f = str(tmp_path / "temp2.jpg") |             f = tmp_path / "temp2.jpg" | ||||||
|             im.save(f, progressive=True, quality=94, icc_profile=b" " * 53955) |             im.save(f, progressive=True, quality=94, icc_profile=b" " * 53955) | ||||||
| 
 | 
 | ||||||
|         with Image.open("Tests/images/flower2.jpg") as im: |         with Image.open("Tests/images/flower2.jpg") as im: | ||||||
|             f = str(tmp_path / "temp3.jpg") |             f = tmp_path / "temp3.jpg" | ||||||
|             im.save(f, progressive=True, quality=94, exif=b" " * 43668) |             im.save(f, progressive=True, quality=94, exif=b" " * 43668) | ||||||
| 
 | 
 | ||||||
|     def test_optimize(self) -> None: |     def test_optimize(self) -> None: | ||||||
|  | @ -268,7 +268,7 @@ class TestFileJpeg: | ||||||
| 
 | 
 | ||||||
|     def test_optimize_large_buffer(self, tmp_path: Path) -> None: |     def test_optimize_large_buffer(self, tmp_path: Path) -> None: | ||||||
|         # https://github.com/python-pillow/Pillow/issues/148 |         # https://github.com/python-pillow/Pillow/issues/148 | ||||||
|         f = str(tmp_path / "temp.jpg") |         f = tmp_path / "temp.jpg" | ||||||
|         # this requires ~ 1.5x Image.MAXBLOCK |         # this requires ~ 1.5x Image.MAXBLOCK | ||||||
|         im = Image.new("RGB", (4096, 4096), 0xFF3333) |         im = Image.new("RGB", (4096, 4096), 0xFF3333) | ||||||
|         im.save(f, format="JPEG", optimize=True) |         im.save(f, format="JPEG", optimize=True) | ||||||
|  | @ -288,13 +288,13 @@ class TestFileJpeg: | ||||||
|         assert im1_bytes >= im3_bytes |         assert im1_bytes >= im3_bytes | ||||||
| 
 | 
 | ||||||
|     def test_progressive_large_buffer(self, tmp_path: Path) -> None: |     def test_progressive_large_buffer(self, tmp_path: Path) -> None: | ||||||
|         f = str(tmp_path / "temp.jpg") |         f = tmp_path / "temp.jpg" | ||||||
|         # this requires ~ 1.5x Image.MAXBLOCK |         # this requires ~ 1.5x Image.MAXBLOCK | ||||||
|         im = Image.new("RGB", (4096, 4096), 0xFF3333) |         im = Image.new("RGB", (4096, 4096), 0xFF3333) | ||||||
|         im.save(f, format="JPEG", progressive=True) |         im.save(f, format="JPEG", progressive=True) | ||||||
| 
 | 
 | ||||||
|     def test_progressive_large_buffer_highest_quality(self, tmp_path: Path) -> None: |     def test_progressive_large_buffer_highest_quality(self, tmp_path: Path) -> None: | ||||||
|         f = str(tmp_path / "temp.jpg") |         f = tmp_path / "temp.jpg" | ||||||
|         im = self.gen_random_image((255, 255)) |         im = self.gen_random_image((255, 255)) | ||||||
|         # this requires more bytes than pixels in the image |         # this requires more bytes than pixels in the image | ||||||
|         im.save(f, format="JPEG", progressive=True, quality=100) |         im.save(f, format="JPEG", progressive=True, quality=100) | ||||||
|  | @ -307,7 +307,7 @@ class TestFileJpeg: | ||||||
| 
 | 
 | ||||||
|     def test_large_exif(self, tmp_path: Path) -> None: |     def test_large_exif(self, tmp_path: Path) -> None: | ||||||
|         # https://github.com/python-pillow/Pillow/issues/148 |         # https://github.com/python-pillow/Pillow/issues/148 | ||||||
|         f = str(tmp_path / "temp.jpg") |         f = tmp_path / "temp.jpg" | ||||||
|         im = hopper() |         im = hopper() | ||||||
|         im.save(f, "JPEG", quality=90, exif=b"1" * 65533) |         im.save(f, "JPEG", quality=90, exif=b"1" * 65533) | ||||||
| 
 | 
 | ||||||
|  | @ -335,7 +335,7 @@ class TestFileJpeg: | ||||||
|             assert exif[gps_index] == expected_exif_gps |             assert exif[gps_index] == expected_exif_gps | ||||||
| 
 | 
 | ||||||
|         # Writing |         # Writing | ||||||
|         f = str(tmp_path / "temp.jpg") |         f = tmp_path / "temp.jpg" | ||||||
|         exif = Image.Exif() |         exif = Image.Exif() | ||||||
|         exif[gps_index] = expected_exif_gps |         exif[gps_index] = expected_exif_gps | ||||||
|         hopper().save(f, exif=exif) |         hopper().save(f, exif=exif) | ||||||
|  | @ -505,15 +505,15 @@ class TestFileJpeg: | ||||||
|     def test_quality_keep(self, tmp_path: Path) -> None: |     def test_quality_keep(self, tmp_path: Path) -> None: | ||||||
|         # RGB |         # RGB | ||||||
|         with Image.open("Tests/images/hopper.jpg") as im: |         with Image.open("Tests/images/hopper.jpg") as im: | ||||||
|             f = str(tmp_path / "temp.jpg") |             f = tmp_path / "temp.jpg" | ||||||
|             im.save(f, quality="keep") |             im.save(f, quality="keep") | ||||||
|         # Grayscale |         # Grayscale | ||||||
|         with Image.open("Tests/images/hopper_gray.jpg") as im: |         with Image.open("Tests/images/hopper_gray.jpg") as im: | ||||||
|             f = str(tmp_path / "temp.jpg") |             f = tmp_path / "temp.jpg" | ||||||
|             im.save(f, quality="keep") |             im.save(f, quality="keep") | ||||||
|         # CMYK |         # CMYK | ||||||
|         with Image.open("Tests/images/pil_sample_cmyk.jpg") as im: |         with Image.open("Tests/images/pil_sample_cmyk.jpg") as im: | ||||||
|             f = str(tmp_path / "temp.jpg") |             f = tmp_path / "temp.jpg" | ||||||
|             im.save(f, quality="keep") |             im.save(f, quality="keep") | ||||||
| 
 | 
 | ||||||
|     def test_junk_jpeg_header(self) -> None: |     def test_junk_jpeg_header(self) -> None: | ||||||
|  | @ -726,7 +726,7 @@ class TestFileJpeg: | ||||||
| 
 | 
 | ||||||
|     def test_MAXBLOCK_scaling(self, tmp_path: Path) -> None: |     def test_MAXBLOCK_scaling(self, tmp_path: Path) -> None: | ||||||
|         im = self.gen_random_image((512, 512)) |         im = self.gen_random_image((512, 512)) | ||||||
|         f = str(tmp_path / "temp.jpeg") |         f = tmp_path / "temp.jpeg" | ||||||
|         im.save(f, quality=100, optimize=True) |         im.save(f, quality=100, optimize=True) | ||||||
| 
 | 
 | ||||||
|         with Image.open(f) as reloaded: |         with Image.open(f) as reloaded: | ||||||
|  | @ -762,7 +762,7 @@ class TestFileJpeg: | ||||||
| 
 | 
 | ||||||
|     def test_save_tiff_with_dpi(self, tmp_path: Path) -> None: |     def test_save_tiff_with_dpi(self, tmp_path: Path) -> None: | ||||||
|         # Arrange |         # Arrange | ||||||
|         outfile = str(tmp_path / "temp.tif") |         outfile = tmp_path / "temp.tif" | ||||||
|         with Image.open("Tests/images/hopper.tif") as im: |         with Image.open("Tests/images/hopper.tif") as im: | ||||||
|             # Act |             # Act | ||||||
|             im.save(outfile, "JPEG", dpi=im.info["dpi"]) |             im.save(outfile, "JPEG", dpi=im.info["dpi"]) | ||||||
|  | @ -773,7 +773,7 @@ class TestFileJpeg: | ||||||
|                 assert im.info["dpi"] == reloaded.info["dpi"] |                 assert im.info["dpi"] == reloaded.info["dpi"] | ||||||
| 
 | 
 | ||||||
|     def test_save_dpi_rounding(self, tmp_path: Path) -> None: |     def test_save_dpi_rounding(self, tmp_path: Path) -> None: | ||||||
|         outfile = str(tmp_path / "temp.jpg") |         outfile = tmp_path / "temp.jpg" | ||||||
|         with Image.open("Tests/images/hopper.jpg") as im: |         with Image.open("Tests/images/hopper.jpg") as im: | ||||||
|             im.save(outfile, dpi=(72.2, 72.2)) |             im.save(outfile, dpi=(72.2, 72.2)) | ||||||
| 
 | 
 | ||||||
|  | @ -859,7 +859,7 @@ class TestFileJpeg: | ||||||
|             exif = im.getexif() |             exif = im.getexif() | ||||||
|             assert exif[282] == 180 |             assert exif[282] == 180 | ||||||
| 
 | 
 | ||||||
|             out = str(tmp_path / "out.jpg") |             out = tmp_path / "out.jpg" | ||||||
|             with warnings.catch_warnings(): |             with warnings.catch_warnings(): | ||||||
|                 warnings.simplefilter("error") |                 warnings.simplefilter("error") | ||||||
| 
 | 
 | ||||||
|  | @ -1005,7 +1005,7 @@ class TestFileJpeg: | ||||||
|                 assert im.getxmp() == {"xmpmeta": None} |                 assert im.getxmp() == {"xmpmeta": None} | ||||||
| 
 | 
 | ||||||
|     def test_save_xmp(self, tmp_path: Path) -> None: |     def test_save_xmp(self, tmp_path: Path) -> None: | ||||||
|         f = str(tmp_path / "temp.jpg") |         f = tmp_path / "temp.jpg" | ||||||
|         im = hopper() |         im = hopper() | ||||||
|         im.save(f, xmp=b"XMP test") |         im.save(f, xmp=b"XMP test") | ||||||
|         with Image.open(f) as reloaded: |         with Image.open(f) as reloaded: | ||||||
|  | @ -1094,7 +1094,7 @@ class TestFileJpeg: | ||||||
| @skip_unless_feature("jpg") | @skip_unless_feature("jpg") | ||||||
| class TestFileCloseW32: | class TestFileCloseW32: | ||||||
|     def test_fd_leak(self, tmp_path: Path) -> None: |     def test_fd_leak(self, tmp_path: Path) -> None: | ||||||
|         tmpfile = str(tmp_path / "temp.jpg") |         tmpfile = tmp_path / "temp.jpg" | ||||||
| 
 | 
 | ||||||
|         with Image.open("Tests/images/hopper.jpg") as im: |         with Image.open("Tests/images/hopper.jpg") as im: | ||||||
|             im.save(tmpfile) |             im.save(tmpfile) | ||||||
|  |  | ||||||
|  | @ -99,7 +99,7 @@ def test_bytesio(card: ImageFile.ImageFile) -> None: | ||||||
| def test_lossless(card: ImageFile.ImageFile, tmp_path: Path) -> None: | def test_lossless(card: ImageFile.ImageFile, tmp_path: Path) -> None: | ||||||
|     with Image.open("Tests/images/test-card-lossless.jp2") as im: |     with Image.open("Tests/images/test-card-lossless.jp2") as im: | ||||||
|         im.load() |         im.load() | ||||||
|         outfile = str(tmp_path / "temp_test-card.png") |         outfile = tmp_path / "temp_test-card.png" | ||||||
|         im.save(outfile) |         im.save(outfile) | ||||||
|     assert_image_similar(im, card, 1.0e-3) |     assert_image_similar(im, card, 1.0e-3) | ||||||
| 
 | 
 | ||||||
|  | @ -213,7 +213,7 @@ def test_header_errors() -> None: | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
| def test_layers_type(card: ImageFile.ImageFile, tmp_path: Path) -> None: | def test_layers_type(card: ImageFile.ImageFile, tmp_path: Path) -> None: | ||||||
|     outfile = str(tmp_path / "temp_layers.jp2") |     outfile = tmp_path / "temp_layers.jp2" | ||||||
|     for quality_layers in [[100, 50, 10], (100, 50, 10), None]: |     for quality_layers in [[100, 50, 10], (100, 50, 10), None]: | ||||||
|         card.save(outfile, quality_layers=quality_layers) |         card.save(outfile, quality_layers=quality_layers) | ||||||
| 
 | 
 | ||||||
|  | @ -289,7 +289,7 @@ def test_mct(card: ImageFile.ImageFile) -> None: | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
| def test_sgnd(tmp_path: Path) -> None: | def test_sgnd(tmp_path: Path) -> None: | ||||||
|     outfile = str(tmp_path / "temp.jp2") |     outfile = tmp_path / "temp.jp2" | ||||||
| 
 | 
 | ||||||
|     im = Image.new("L", (1, 1)) |     im = Image.new("L", (1, 1)) | ||||||
|     im.save(outfile) |     im.save(outfile) | ||||||
|  |  | ||||||
|  | @ -39,7 +39,7 @@ class LibTiffTestCase: | ||||||
|         assert im._compression == "group4" |         assert im._compression == "group4" | ||||||
| 
 | 
 | ||||||
|         # can we write it back out, in a different form. |         # can we write it back out, in a different form. | ||||||
|         out = str(tmp_path / "temp.png") |         out = tmp_path / "temp.png" | ||||||
|         im.save(out) |         im.save(out) | ||||||
| 
 | 
 | ||||||
|         out_bytes = io.BytesIO() |         out_bytes = io.BytesIO() | ||||||
|  | @ -123,7 +123,7 @@ class TestFileLibTiff(LibTiffTestCase): | ||||||
|         """Checking to see that the saved image is the same as what we wrote""" |         """Checking to see that the saved image is the same as what we wrote""" | ||||||
|         test_file = "Tests/images/hopper_g4_500.tif" |         test_file = "Tests/images/hopper_g4_500.tif" | ||||||
|         with Image.open(test_file) as orig: |         with Image.open(test_file) as orig: | ||||||
|             out = str(tmp_path / "temp.tif") |             out = tmp_path / "temp.tif" | ||||||
|             rot = orig.transpose(Image.Transpose.ROTATE_90) |             rot = orig.transpose(Image.Transpose.ROTATE_90) | ||||||
|             assert rot.size == (500, 500) |             assert rot.size == (500, 500) | ||||||
|             rot.save(out) |             rot.save(out) | ||||||
|  | @ -151,7 +151,7 @@ class TestFileLibTiff(LibTiffTestCase): | ||||||
|     @pytest.mark.parametrize("legacy_api", (False, True)) |     @pytest.mark.parametrize("legacy_api", (False, True)) | ||||||
|     def test_write_metadata(self, legacy_api: bool, tmp_path: Path) -> None: |     def test_write_metadata(self, legacy_api: bool, tmp_path: Path) -> None: | ||||||
|         """Test metadata writing through libtiff""" |         """Test metadata writing through libtiff""" | ||||||
|         f = str(tmp_path / "temp.tiff") |         f = tmp_path / "temp.tiff" | ||||||
|         with Image.open("Tests/images/hopper_g4.tif") as img: |         with Image.open("Tests/images/hopper_g4.tif") as img: | ||||||
|             img.save(f, tiffinfo=img.tag) |             img.save(f, tiffinfo=img.tag) | ||||||
| 
 | 
 | ||||||
|  | @ -247,7 +247,7 @@ class TestFileLibTiff(LibTiffTestCase): | ||||||
|             # Extra samples really doesn't make sense in this application. |             # Extra samples really doesn't make sense in this application. | ||||||
|             del new_ifd[338] |             del new_ifd[338] | ||||||
| 
 | 
 | ||||||
|             out = str(tmp_path / "temp.tif") |             out = tmp_path / "temp.tif" | ||||||
|             monkeypatch.setattr(TiffImagePlugin, "WRITE_LIBTIFF", True) |             monkeypatch.setattr(TiffImagePlugin, "WRITE_LIBTIFF", True) | ||||||
| 
 | 
 | ||||||
|             im.save(out, tiffinfo=new_ifd) |             im.save(out, tiffinfo=new_ifd) | ||||||
|  | @ -313,7 +313,7 @@ class TestFileLibTiff(LibTiffTestCase): | ||||||
|         ) -> None: |         ) -> None: | ||||||
|             im = hopper() |             im = hopper() | ||||||
| 
 | 
 | ||||||
|             out = str(tmp_path / "temp.tif") |             out = tmp_path / "temp.tif" | ||||||
|             im.save(out, tiffinfo=tiffinfo) |             im.save(out, tiffinfo=tiffinfo) | ||||||
| 
 | 
 | ||||||
|             with Image.open(out) as reloaded: |             with Image.open(out) as reloaded: | ||||||
|  | @ -347,13 +347,13 @@ class TestFileLibTiff(LibTiffTestCase): | ||||||
|         ) |         ) | ||||||
| 
 | 
 | ||||||
|     def test_osubfiletype(self, tmp_path: Path) -> None: |     def test_osubfiletype(self, tmp_path: Path) -> None: | ||||||
|         outfile = str(tmp_path / "temp.tif") |         outfile = tmp_path / "temp.tif" | ||||||
|         with Image.open("Tests/images/g4_orientation_6.tif") as im: |         with Image.open("Tests/images/g4_orientation_6.tif") as im: | ||||||
|             im.tag_v2[OSUBFILETYPE] = 1 |             im.tag_v2[OSUBFILETYPE] = 1 | ||||||
|             im.save(outfile) |             im.save(outfile) | ||||||
| 
 | 
 | ||||||
|     def test_subifd(self, tmp_path: Path) -> None: |     def test_subifd(self, tmp_path: Path) -> None: | ||||||
|         outfile = str(tmp_path / "temp.tif") |         outfile = tmp_path / "temp.tif" | ||||||
|         with Image.open("Tests/images/g4_orientation_6.tif") as im: |         with Image.open("Tests/images/g4_orientation_6.tif") as im: | ||||||
|             im.tag_v2[SUBIFD] = 10000 |             im.tag_v2[SUBIFD] = 10000 | ||||||
| 
 | 
 | ||||||
|  | @ -365,7 +365,7 @@ class TestFileLibTiff(LibTiffTestCase): | ||||||
|     ) -> None: |     ) -> None: | ||||||
|         monkeypatch.setattr(TiffImagePlugin, "WRITE_LIBTIFF", True) |         monkeypatch.setattr(TiffImagePlugin, "WRITE_LIBTIFF", True) | ||||||
| 
 | 
 | ||||||
|         out = str(tmp_path / "temp.tif") |         out = tmp_path / "temp.tif" | ||||||
|         hopper().save(out, tiffinfo={700: b"xmlpacket tag"}) |         hopper().save(out, tiffinfo={700: b"xmlpacket tag"}) | ||||||
| 
 | 
 | ||||||
|         with Image.open(out) as reloaded: |         with Image.open(out) as reloaded: | ||||||
|  | @ -375,7 +375,7 @@ class TestFileLibTiff(LibTiffTestCase): | ||||||
|     def test_int_dpi(self, monkeypatch: pytest.MonkeyPatch, tmp_path: Path) -> None: |     def test_int_dpi(self, monkeypatch: pytest.MonkeyPatch, tmp_path: Path) -> None: | ||||||
|         # issue #1765 |         # issue #1765 | ||||||
|         im = hopper("RGB") |         im = hopper("RGB") | ||||||
|         out = str(tmp_path / "temp.tif") |         out = tmp_path / "temp.tif" | ||||||
|         monkeypatch.setattr(TiffImagePlugin, "WRITE_LIBTIFF", True) |         monkeypatch.setattr(TiffImagePlugin, "WRITE_LIBTIFF", True) | ||||||
|         im.save(out, dpi=(72, 72)) |         im.save(out, dpi=(72, 72)) | ||||||
|         with Image.open(out) as reloaded: |         with Image.open(out) as reloaded: | ||||||
|  | @ -383,7 +383,7 @@ class TestFileLibTiff(LibTiffTestCase): | ||||||
| 
 | 
 | ||||||
|     def test_g3_compression(self, tmp_path: Path) -> None: |     def test_g3_compression(self, tmp_path: Path) -> None: | ||||||
|         with Image.open("Tests/images/hopper_g4_500.tif") as i: |         with Image.open("Tests/images/hopper_g4_500.tif") as i: | ||||||
|             out = str(tmp_path / "temp.tif") |             out = tmp_path / "temp.tif" | ||||||
|             i.save(out, compression="group3") |             i.save(out, compression="group3") | ||||||
| 
 | 
 | ||||||
|             with Image.open(out) as reread: |             with Image.open(out) as reread: | ||||||
|  | @ -400,7 +400,7 @@ class TestFileLibTiff(LibTiffTestCase): | ||||||
|             assert b[0] == ord(b"\xe0") |             assert b[0] == ord(b"\xe0") | ||||||
|             assert b[1] == ord(b"\x01") |             assert b[1] == ord(b"\x01") | ||||||
| 
 | 
 | ||||||
|             out = str(tmp_path / "temp.tif") |             out = tmp_path / "temp.tif" | ||||||
|             # out = "temp.le.tif" |             # out = "temp.le.tif" | ||||||
|             im.save(out) |             im.save(out) | ||||||
|         with Image.open(out) as reread: |         with Image.open(out) as reread: | ||||||
|  | @ -420,7 +420,7 @@ class TestFileLibTiff(LibTiffTestCase): | ||||||
|             assert b[0] == ord(b"\x01") |             assert b[0] == ord(b"\x01") | ||||||
|             assert b[1] == ord(b"\xe0") |             assert b[1] == ord(b"\xe0") | ||||||
| 
 | 
 | ||||||
|             out = str(tmp_path / "temp.tif") |             out = tmp_path / "temp.tif" | ||||||
|             im.save(out) |             im.save(out) | ||||||
|             with Image.open(out) as reread: |             with Image.open(out) as reread: | ||||||
|                 assert reread.info["compression"] == im.info["compression"] |                 assert reread.info["compression"] == im.info["compression"] | ||||||
|  | @ -430,7 +430,7 @@ class TestFileLibTiff(LibTiffTestCase): | ||||||
|         """Tests String data in info directory""" |         """Tests String data in info directory""" | ||||||
|         test_file = "Tests/images/hopper_g4_500.tif" |         test_file = "Tests/images/hopper_g4_500.tif" | ||||||
|         with Image.open(test_file) as orig: |         with Image.open(test_file) as orig: | ||||||
|             out = str(tmp_path / "temp.tif") |             out = tmp_path / "temp.tif" | ||||||
| 
 | 
 | ||||||
|             orig.tag[269] = "temp.tif" |             orig.tag[269] = "temp.tif" | ||||||
|             orig.save(out) |             orig.save(out) | ||||||
|  | @ -457,7 +457,7 @@ class TestFileLibTiff(LibTiffTestCase): | ||||||
|     def test_blur(self, tmp_path: Path) -> None: |     def test_blur(self, tmp_path: Path) -> None: | ||||||
|         # test case from irc, how to do blur on b/w image |         # test case from irc, how to do blur on b/w image | ||||||
|         # and save to compressed tif. |         # and save to compressed tif. | ||||||
|         out = str(tmp_path / "temp.tif") |         out = tmp_path / "temp.tif" | ||||||
|         with Image.open("Tests/images/pport_g4.tif") as im: |         with Image.open("Tests/images/pport_g4.tif") as im: | ||||||
|             im = im.convert("L") |             im = im.convert("L") | ||||||
| 
 | 
 | ||||||
|  | @ -470,7 +470,7 @@ class TestFileLibTiff(LibTiffTestCase): | ||||||
|         # Test various tiff compressions and assert similar image content but reduced |         # Test various tiff compressions and assert similar image content but reduced | ||||||
|         # file sizes. |         # file sizes. | ||||||
|         im = hopper("RGB") |         im = hopper("RGB") | ||||||
|         out = str(tmp_path / "temp.tif") |         out = tmp_path / "temp.tif" | ||||||
|         im.save(out) |         im.save(out) | ||||||
|         size_raw = os.path.getsize(out) |         size_raw = os.path.getsize(out) | ||||||
| 
 | 
 | ||||||
|  | @ -494,7 +494,7 @@ class TestFileLibTiff(LibTiffTestCase): | ||||||
| 
 | 
 | ||||||
|     def test_tiff_jpeg_compression(self, tmp_path: Path) -> None: |     def test_tiff_jpeg_compression(self, tmp_path: Path) -> None: | ||||||
|         im = hopper("RGB") |         im = hopper("RGB") | ||||||
|         out = str(tmp_path / "temp.tif") |         out = tmp_path / "temp.tif" | ||||||
|         im.save(out, compression="tiff_jpeg") |         im.save(out, compression="tiff_jpeg") | ||||||
| 
 | 
 | ||||||
|         with Image.open(out) as reloaded: |         with Image.open(out) as reloaded: | ||||||
|  | @ -502,7 +502,7 @@ class TestFileLibTiff(LibTiffTestCase): | ||||||
| 
 | 
 | ||||||
|     def test_tiff_deflate_compression(self, tmp_path: Path) -> None: |     def test_tiff_deflate_compression(self, tmp_path: Path) -> None: | ||||||
|         im = hopper("RGB") |         im = hopper("RGB") | ||||||
|         out = str(tmp_path / "temp.tif") |         out = tmp_path / "temp.tif" | ||||||
|         im.save(out, compression="tiff_deflate") |         im.save(out, compression="tiff_deflate") | ||||||
| 
 | 
 | ||||||
|         with Image.open(out) as reloaded: |         with Image.open(out) as reloaded: | ||||||
|  | @ -510,7 +510,7 @@ class TestFileLibTiff(LibTiffTestCase): | ||||||
| 
 | 
 | ||||||
|     def test_quality(self, tmp_path: Path) -> None: |     def test_quality(self, tmp_path: Path) -> None: | ||||||
|         im = hopper("RGB") |         im = hopper("RGB") | ||||||
|         out = str(tmp_path / "temp.tif") |         out = tmp_path / "temp.tif" | ||||||
| 
 | 
 | ||||||
|         with pytest.raises(ValueError): |         with pytest.raises(ValueError): | ||||||
|             im.save(out, compression="tiff_lzw", quality=50) |             im.save(out, compression="tiff_lzw", quality=50) | ||||||
|  | @ -525,7 +525,7 @@ class TestFileLibTiff(LibTiffTestCase): | ||||||
| 
 | 
 | ||||||
|     def test_cmyk_save(self, tmp_path: Path) -> None: |     def test_cmyk_save(self, tmp_path: Path) -> None: | ||||||
|         im = hopper("CMYK") |         im = hopper("CMYK") | ||||||
|         out = str(tmp_path / "temp.tif") |         out = tmp_path / "temp.tif" | ||||||
| 
 | 
 | ||||||
|         im.save(out, compression="tiff_adobe_deflate") |         im.save(out, compression="tiff_adobe_deflate") | ||||||
|         assert_image_equal_tofile(im, out) |         assert_image_equal_tofile(im, out) | ||||||
|  | @ -534,7 +534,7 @@ class TestFileLibTiff(LibTiffTestCase): | ||||||
|     def test_palette_save( |     def test_palette_save( | ||||||
|         self, im: Image.Image, monkeypatch: pytest.MonkeyPatch, tmp_path: Path |         self, im: Image.Image, monkeypatch: pytest.MonkeyPatch, tmp_path: Path | ||||||
|     ) -> None: |     ) -> None: | ||||||
|         out = str(tmp_path / "temp.tif") |         out = tmp_path / "temp.tif" | ||||||
| 
 | 
 | ||||||
|         monkeypatch.setattr(TiffImagePlugin, "WRITE_LIBTIFF", True) |         monkeypatch.setattr(TiffImagePlugin, "WRITE_LIBTIFF", True) | ||||||
|         im.save(out) |         im.save(out) | ||||||
|  | @ -546,7 +546,7 @@ class TestFileLibTiff(LibTiffTestCase): | ||||||
|     @pytest.mark.parametrize("compression", ("tiff_ccitt", "group3", "group4")) |     @pytest.mark.parametrize("compression", ("tiff_ccitt", "group3", "group4")) | ||||||
|     def test_bw_compression_w_rgb(self, compression: str, tmp_path: Path) -> None: |     def test_bw_compression_w_rgb(self, compression: str, tmp_path: Path) -> None: | ||||||
|         im = hopper("RGB") |         im = hopper("RGB") | ||||||
|         out = str(tmp_path / "temp.tif") |         out = tmp_path / "temp.tif" | ||||||
| 
 | 
 | ||||||
|         with pytest.raises(OSError): |         with pytest.raises(OSError): | ||||||
|             im.save(out, compression=compression) |             im.save(out, compression=compression) | ||||||
|  | @ -686,7 +686,7 @@ class TestFileLibTiff(LibTiffTestCase): | ||||||
| 
 | 
 | ||||||
|     def test_save_ycbcr(self, tmp_path: Path) -> None: |     def test_save_ycbcr(self, tmp_path: Path) -> None: | ||||||
|         im = hopper("YCbCr") |         im = hopper("YCbCr") | ||||||
|         outfile = str(tmp_path / "temp.tif") |         outfile = tmp_path / "temp.tif" | ||||||
|         im.save(outfile, compression="jpeg") |         im.save(outfile, compression="jpeg") | ||||||
| 
 | 
 | ||||||
|         with Image.open(outfile) as reloaded: |         with Image.open(outfile) as reloaded: | ||||||
|  | @ -713,7 +713,7 @@ class TestFileLibTiff(LibTiffTestCase): | ||||||
|     ) -> None: |     ) -> None: | ||||||
|         # issue 1597 |         # issue 1597 | ||||||
|         with Image.open("Tests/images/rdf.tif") as im: |         with Image.open("Tests/images/rdf.tif") as im: | ||||||
|             out = str(tmp_path / "temp.tif") |             out = tmp_path / "temp.tif" | ||||||
| 
 | 
 | ||||||
|             monkeypatch.setattr(TiffImagePlugin, "WRITE_LIBTIFF", True) |             monkeypatch.setattr(TiffImagePlugin, "WRITE_LIBTIFF", True) | ||||||
|             # this shouldn't crash |             # this shouldn't crash | ||||||
|  | @ -724,7 +724,7 @@ class TestFileLibTiff(LibTiffTestCase): | ||||||
|         # Test TIFF with tag 297 (Page Number) having value of 0 0. |         # Test TIFF with tag 297 (Page Number) having value of 0 0. | ||||||
|         # The first number is the current page number. |         # The first number is the current page number. | ||||||
|         # The second is the total number of pages, zero means not available. |         # The second is the total number of pages, zero means not available. | ||||||
|         outfile = str(tmp_path / "temp.tif") |         outfile = tmp_path / "temp.tif" | ||||||
|         # Created by printing a page in Chrome to PDF, then: |         # Created by printing a page in Chrome to PDF, then: | ||||||
|         # /usr/bin/gs -q -sDEVICE=tiffg3 -sOutputFile=total-pages-zero.tif |         # /usr/bin/gs -q -sDEVICE=tiffg3 -sOutputFile=total-pages-zero.tif | ||||||
|         # -dNOPAUSE /tmp/test.pdf -c quit |         # -dNOPAUSE /tmp/test.pdf -c quit | ||||||
|  | @ -736,7 +736,7 @@ class TestFileLibTiff(LibTiffTestCase): | ||||||
|     def test_fd_duplication(self, tmp_path: Path) -> None: |     def test_fd_duplication(self, tmp_path: Path) -> None: | ||||||
|         # https://github.com/python-pillow/Pillow/issues/1651 |         # https://github.com/python-pillow/Pillow/issues/1651 | ||||||
| 
 | 
 | ||||||
|         tmpfile = str(tmp_path / "temp.tif") |         tmpfile = tmp_path / "temp.tif" | ||||||
|         with open(tmpfile, "wb") as f: |         with open(tmpfile, "wb") as f: | ||||||
|             with open("Tests/images/g4-multi.tiff", "rb") as src: |             with open("Tests/images/g4-multi.tiff", "rb") as src: | ||||||
|                 f.write(src.read()) |                 f.write(src.read()) | ||||||
|  | @ -779,7 +779,7 @@ class TestFileLibTiff(LibTiffTestCase): | ||||||
|         with Image.open("Tests/images/hopper.iccprofile.tif") as img: |         with Image.open("Tests/images/hopper.iccprofile.tif") as img: | ||||||
|             icc_profile = img.info["icc_profile"] |             icc_profile = img.info["icc_profile"] | ||||||
| 
 | 
 | ||||||
|             out = str(tmp_path / "temp.tif") |             out = tmp_path / "temp.tif" | ||||||
|             img.save(out, icc_profile=icc_profile) |             img.save(out, icc_profile=icc_profile) | ||||||
|         with Image.open(out) as reloaded: |         with Image.open(out) as reloaded: | ||||||
|             assert icc_profile == reloaded.info["icc_profile"] |             assert icc_profile == reloaded.info["icc_profile"] | ||||||
|  | @ -802,7 +802,7 @@ class TestFileLibTiff(LibTiffTestCase): | ||||||
| 
 | 
 | ||||||
|     def test_save_tiff_with_jpegtables(self, tmp_path: Path) -> None: |     def test_save_tiff_with_jpegtables(self, tmp_path: Path) -> None: | ||||||
|         # Arrange |         # Arrange | ||||||
|         outfile = str(tmp_path / "temp.tif") |         outfile = tmp_path / "temp.tif" | ||||||
| 
 | 
 | ||||||
|         # Created with ImageMagick: convert hopper.jpg hopper_jpg.tif |         # Created with ImageMagick: convert hopper.jpg hopper_jpg.tif | ||||||
|         # Contains JPEGTables (347) tag |         # Contains JPEGTables (347) tag | ||||||
|  | @ -864,7 +864,7 @@ class TestFileLibTiff(LibTiffTestCase): | ||||||
|         self, monkeypatch: pytest.MonkeyPatch, tmp_path: Path |         self, monkeypatch: pytest.MonkeyPatch, tmp_path: Path | ||||||
|     ) -> None: |     ) -> None: | ||||||
|         im = Image.new("F", (1, 1)) |         im = Image.new("F", (1, 1)) | ||||||
|         out = str(tmp_path / "temp.tif") |         out = tmp_path / "temp.tif" | ||||||
|         monkeypatch.setattr(TiffImagePlugin, "WRITE_LIBTIFF", True) |         monkeypatch.setattr(TiffImagePlugin, "WRITE_LIBTIFF", True) | ||||||
|         im.save(out) |         im.save(out) | ||||||
| 
 | 
 | ||||||
|  | @ -1008,7 +1008,7 @@ class TestFileLibTiff(LibTiffTestCase): | ||||||
|     @pytest.mark.parametrize("compression", (None, "jpeg")) |     @pytest.mark.parametrize("compression", (None, "jpeg")) | ||||||
|     def test_block_tile_tags(self, compression: str | None, tmp_path: Path) -> None: |     def test_block_tile_tags(self, compression: str | None, tmp_path: Path) -> None: | ||||||
|         im = hopper() |         im = hopper() | ||||||
|         out = str(tmp_path / "temp.tif") |         out = tmp_path / "temp.tif" | ||||||
| 
 | 
 | ||||||
|         tags = { |         tags = { | ||||||
|             TiffImagePlugin.TILEWIDTH: 256, |             TiffImagePlugin.TILEWIDTH: 256, | ||||||
|  | @ -1147,7 +1147,7 @@ class TestFileLibTiff(LibTiffTestCase): | ||||||
|     @pytest.mark.parametrize("compression", ("tiff_adobe_deflate", "jpeg")) |     @pytest.mark.parametrize("compression", ("tiff_adobe_deflate", "jpeg")) | ||||||
|     def test_save_multistrip(self, compression: str, tmp_path: Path) -> None: |     def test_save_multistrip(self, compression: str, tmp_path: Path) -> None: | ||||||
|         im = hopper("RGB").resize((256, 256)) |         im = hopper("RGB").resize((256, 256)) | ||||||
|         out = str(tmp_path / "temp.tif") |         out = tmp_path / "temp.tif" | ||||||
|         im.save(out, compression=compression) |         im.save(out, compression=compression) | ||||||
| 
 | 
 | ||||||
|         with Image.open(out) as im: |         with Image.open(out) as im: | ||||||
|  | @ -1160,7 +1160,7 @@ class TestFileLibTiff(LibTiffTestCase): | ||||||
|         self, argument: bool, tmp_path: Path, monkeypatch: pytest.MonkeyPatch |         self, argument: bool, tmp_path: Path, monkeypatch: pytest.MonkeyPatch | ||||||
|     ) -> None: |     ) -> None: | ||||||
|         im = hopper("RGB").resize((256, 256)) |         im = hopper("RGB").resize((256, 256)) | ||||||
|         out = str(tmp_path / "temp.tif") |         out = tmp_path / "temp.tif" | ||||||
| 
 | 
 | ||||||
|         if not argument: |         if not argument: | ||||||
|             monkeypatch.setattr(TiffImagePlugin, "STRIP_SIZE", 2**18) |             monkeypatch.setattr(TiffImagePlugin, "STRIP_SIZE", 2**18) | ||||||
|  | @ -1176,13 +1176,13 @@ class TestFileLibTiff(LibTiffTestCase): | ||||||
|     @pytest.mark.parametrize("compression", ("tiff_adobe_deflate", None)) |     @pytest.mark.parametrize("compression", ("tiff_adobe_deflate", None)) | ||||||
|     def test_save_zero(self, compression: str | None, tmp_path: Path) -> None: |     def test_save_zero(self, compression: str | None, tmp_path: Path) -> None: | ||||||
|         im = Image.new("RGB", (0, 0)) |         im = Image.new("RGB", (0, 0)) | ||||||
|         out = str(tmp_path / "temp.tif") |         out = tmp_path / "temp.tif" | ||||||
|         with pytest.raises(SystemError): |         with pytest.raises(SystemError): | ||||||
|             im.save(out, compression=compression) |             im.save(out, compression=compression) | ||||||
| 
 | 
 | ||||||
|     def test_save_many_compressed(self, tmp_path: Path) -> None: |     def test_save_many_compressed(self, tmp_path: Path) -> None: | ||||||
|         im = hopper() |         im = hopper() | ||||||
|         out = str(tmp_path / "temp.tif") |         out = tmp_path / "temp.tif" | ||||||
|         for _ in range(10000): |         for _ in range(10000): | ||||||
|             im.save(out, compression="jpeg") |             im.save(out, compression="jpeg") | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
|  | @ -15,7 +15,7 @@ YA_EXTRA_DIR = "Tests/images/msp" | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
| def test_sanity(tmp_path: Path) -> None: | def test_sanity(tmp_path: Path) -> None: | ||||||
|     test_file = str(tmp_path / "temp.msp") |     test_file = tmp_path / "temp.msp" | ||||||
| 
 | 
 | ||||||
|     hopper("1").save(test_file) |     hopper("1").save(test_file) | ||||||
| 
 | 
 | ||||||
|  | @ -84,7 +84,7 @@ def test_msp_v2() -> None: | ||||||
| def test_cannot_save_wrong_mode(tmp_path: Path) -> None: | def test_cannot_save_wrong_mode(tmp_path: Path) -> None: | ||||||
|     # Arrange |     # Arrange | ||||||
|     im = hopper() |     im = hopper() | ||||||
|     filename = str(tmp_path / "temp.msp") |     filename = tmp_path / "temp.msp" | ||||||
| 
 | 
 | ||||||
|     # Act/Assert |     # Act/Assert | ||||||
|     with pytest.raises(OSError): |     with pytest.raises(OSError): | ||||||
|  |  | ||||||
|  | @ -14,7 +14,7 @@ from .helper import assert_image_equal, hopper, magick_command | ||||||
| def helper_save_as_palm(tmp_path: Path, mode: str) -> None: | def helper_save_as_palm(tmp_path: Path, mode: str) -> None: | ||||||
|     # Arrange |     # Arrange | ||||||
|     im = hopper(mode) |     im = hopper(mode) | ||||||
|     outfile = str(tmp_path / ("temp_" + mode + ".palm")) |     outfile = tmp_path / ("temp_" + mode + ".palm") | ||||||
| 
 | 
 | ||||||
|     # Act |     # Act | ||||||
|     im.save(outfile) |     im.save(outfile) | ||||||
|  | @ -25,7 +25,7 @@ def helper_save_as_palm(tmp_path: Path, mode: str) -> None: | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
| def open_with_magick(magick: list[str], tmp_path: Path, f: str) -> Image.Image: | def open_with_magick(magick: list[str], tmp_path: Path, f: str) -> Image.Image: | ||||||
|     outfile = str(tmp_path / "temp.png") |     outfile = tmp_path / "temp.png" | ||||||
|     rc = subprocess.call( |     rc = subprocess.call( | ||||||
|         magick + [f, outfile], stdout=subprocess.DEVNULL, stderr=subprocess.STDOUT |         magick + [f, outfile], stdout=subprocess.DEVNULL, stderr=subprocess.STDOUT | ||||||
|     ) |     ) | ||||||
|  |  | ||||||
|  | @ -11,7 +11,7 @@ from .helper import assert_image_equal, hopper | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
| def _roundtrip(tmp_path: Path, im: Image.Image) -> None: | def _roundtrip(tmp_path: Path, im: Image.Image) -> None: | ||||||
|     f = str(tmp_path / "temp.pcx") |     f = tmp_path / "temp.pcx" | ||||||
|     im.save(f) |     im.save(f) | ||||||
|     with Image.open(f) as im2: |     with Image.open(f) as im2: | ||||||
|         assert im2.mode == im.mode |         assert im2.mode == im.mode | ||||||
|  | @ -31,7 +31,7 @@ def test_sanity(tmp_path: Path) -> None: | ||||||
|     _roundtrip(tmp_path, im) |     _roundtrip(tmp_path, im) | ||||||
| 
 | 
 | ||||||
|     # Test an unsupported mode |     # Test an unsupported mode | ||||||
|     f = str(tmp_path / "temp.pcx") |     f = tmp_path / "temp.pcx" | ||||||
|     im = hopper("RGBA") |     im = hopper("RGBA") | ||||||
|     with pytest.raises(ValueError): |     with pytest.raises(ValueError): | ||||||
|         im.save(f) |         im.save(f) | ||||||
|  |  | ||||||
|  | @ -55,7 +55,7 @@ def test_save_alpha(tmp_path: Path, mode: str) -> None: | ||||||
| 
 | 
 | ||||||
| def test_p_alpha(tmp_path: Path) -> None: | def test_p_alpha(tmp_path: Path) -> None: | ||||||
|     # Arrange |     # Arrange | ||||||
|     outfile = str(tmp_path / "temp.pdf") |     outfile = tmp_path / "temp.pdf" | ||||||
|     with Image.open("Tests/images/pil123p.png") as im: |     with Image.open("Tests/images/pil123p.png") as im: | ||||||
|         assert im.mode == "P" |         assert im.mode == "P" | ||||||
|         assert isinstance(im.info["transparency"], bytes) |         assert isinstance(im.info["transparency"], bytes) | ||||||
|  | @ -80,7 +80,7 @@ def test_monochrome(tmp_path: Path) -> None: | ||||||
| 
 | 
 | ||||||
| def test_unsupported_mode(tmp_path: Path) -> None: | def test_unsupported_mode(tmp_path: Path) -> None: | ||||||
|     im = hopper("PA") |     im = hopper("PA") | ||||||
|     outfile = str(tmp_path / "temp_PA.pdf") |     outfile = tmp_path / "temp_PA.pdf" | ||||||
| 
 | 
 | ||||||
|     with pytest.raises(ValueError): |     with pytest.raises(ValueError): | ||||||
|         im.save(outfile) |         im.save(outfile) | ||||||
|  | @ -89,7 +89,7 @@ def test_unsupported_mode(tmp_path: Path) -> None: | ||||||
| def test_resolution(tmp_path: Path) -> None: | def test_resolution(tmp_path: Path) -> None: | ||||||
|     im = hopper() |     im = hopper() | ||||||
| 
 | 
 | ||||||
|     outfile = str(tmp_path / "temp.pdf") |     outfile = tmp_path / "temp.pdf" | ||||||
|     im.save(outfile, resolution=150) |     im.save(outfile, resolution=150) | ||||||
| 
 | 
 | ||||||
|     with open(outfile, "rb") as fp: |     with open(outfile, "rb") as fp: | ||||||
|  | @ -117,7 +117,7 @@ def test_resolution(tmp_path: Path) -> None: | ||||||
| def test_dpi(params: dict[str, int | tuple[int, int]], tmp_path: Path) -> None: | def test_dpi(params: dict[str, int | tuple[int, int]], tmp_path: Path) -> None: | ||||||
|     im = hopper() |     im = hopper() | ||||||
| 
 | 
 | ||||||
|     outfile = str(tmp_path / "temp.pdf") |     outfile = tmp_path / "temp.pdf" | ||||||
|     im.save(outfile, "PDF", **params) |     im.save(outfile, "PDF", **params) | ||||||
| 
 | 
 | ||||||
|     with open(outfile, "rb") as fp: |     with open(outfile, "rb") as fp: | ||||||
|  | @ -144,7 +144,7 @@ def test_save_all(tmp_path: Path) -> None: | ||||||
| 
 | 
 | ||||||
|     # Multiframe image |     # Multiframe image | ||||||
|     with Image.open("Tests/images/dispose_bgnd.gif") as im: |     with Image.open("Tests/images/dispose_bgnd.gif") as im: | ||||||
|         outfile = str(tmp_path / "temp.pdf") |         outfile = tmp_path / "temp.pdf" | ||||||
|         im.save(outfile, save_all=True) |         im.save(outfile, save_all=True) | ||||||
| 
 | 
 | ||||||
|         assert os.path.isfile(outfile) |         assert os.path.isfile(outfile) | ||||||
|  | @ -219,7 +219,7 @@ def test_save_all_progress() -> None: | ||||||
| def test_multiframe_normal_save(tmp_path: Path) -> None: | def test_multiframe_normal_save(tmp_path: Path) -> None: | ||||||
|     # Test saving a multiframe image without save_all |     # Test saving a multiframe image without save_all | ||||||
|     with Image.open("Tests/images/dispose_bgnd.gif") as im: |     with Image.open("Tests/images/dispose_bgnd.gif") as im: | ||||||
|         outfile = str(tmp_path / "temp.pdf") |         outfile = tmp_path / "temp.pdf" | ||||||
|         im.save(outfile) |         im.save(outfile) | ||||||
| 
 | 
 | ||||||
|     assert os.path.isfile(outfile) |     assert os.path.isfile(outfile) | ||||||
|  |  | ||||||
|  | @ -68,7 +68,7 @@ def roundtrip(im: Image.Image, **options: Any) -> PngImagePlugin.PngImageFile: | ||||||
| 
 | 
 | ||||||
| @skip_unless_feature("zlib") | @skip_unless_feature("zlib") | ||||||
| class TestFilePng: | class TestFilePng: | ||||||
|     def get_chunks(self, filename: str) -> list[bytes]: |     def get_chunks(self, filename: Path) -> list[bytes]: | ||||||
|         chunks = [] |         chunks = [] | ||||||
|         with open(filename, "rb") as fp: |         with open(filename, "rb") as fp: | ||||||
|             fp.read(8) |             fp.read(8) | ||||||
|  | @ -89,7 +89,7 @@ class TestFilePng: | ||||||
|         assert version is not None |         assert version is not None | ||||||
|         assert re.search(r"\d+(\.\d+){1,3}(\.zlib\-ng)?$", version) |         assert re.search(r"\d+(\.\d+){1,3}(\.zlib\-ng)?$", version) | ||||||
| 
 | 
 | ||||||
|         test_file = str(tmp_path / "temp.png") |         test_file = tmp_path / "temp.png" | ||||||
| 
 | 
 | ||||||
|         hopper("RGB").save(test_file) |         hopper("RGB").save(test_file) | ||||||
| 
 | 
 | ||||||
|  | @ -250,7 +250,7 @@ class TestFilePng: | ||||||
|             # each palette entry |             # each palette entry | ||||||
|             assert len(im.info["transparency"]) == 256 |             assert len(im.info["transparency"]) == 256 | ||||||
| 
 | 
 | ||||||
|             test_file = str(tmp_path / "temp.png") |             test_file = tmp_path / "temp.png" | ||||||
|             im.save(test_file) |             im.save(test_file) | ||||||
| 
 | 
 | ||||||
|         # check if saved image contains same transparency |         # check if saved image contains same transparency | ||||||
|  | @ -271,7 +271,7 @@ class TestFilePng: | ||||||
|             assert im.info["transparency"] == 164 |             assert im.info["transparency"] == 164 | ||||||
|             assert im.getpixel((31, 31)) == 164 |             assert im.getpixel((31, 31)) == 164 | ||||||
| 
 | 
 | ||||||
|             test_file = str(tmp_path / "temp.png") |             test_file = tmp_path / "temp.png" | ||||||
|             im.save(test_file) |             im.save(test_file) | ||||||
| 
 | 
 | ||||||
|         # check if saved image contains same transparency |         # check if saved image contains same transparency | ||||||
|  | @ -294,7 +294,7 @@ class TestFilePng: | ||||||
|         assert im.getcolors() == [(100, (0, 0, 0, 0))] |         assert im.getcolors() == [(100, (0, 0, 0, 0))] | ||||||
| 
 | 
 | ||||||
|         im = im.convert("P") |         im = im.convert("P") | ||||||
|         test_file = str(tmp_path / "temp.png") |         test_file = tmp_path / "temp.png" | ||||||
|         im.save(test_file) |         im.save(test_file) | ||||||
| 
 | 
 | ||||||
|         # check if saved image contains same transparency |         # check if saved image contains same transparency | ||||||
|  | @ -315,7 +315,7 @@ class TestFilePng: | ||||||
|                 im_rgba = im.convert("RGBA") |                 im_rgba = im.convert("RGBA") | ||||||
|             assert im_rgba.getchannel("A").getcolors()[0][0] == num_transparent |             assert im_rgba.getchannel("A").getcolors()[0][0] == num_transparent | ||||||
| 
 | 
 | ||||||
|             test_file = str(tmp_path / "temp.png") |             test_file = tmp_path / "temp.png" | ||||||
|             im.save(test_file) |             im.save(test_file) | ||||||
| 
 | 
 | ||||||
|             with Image.open(test_file) as test_im: |             with Image.open(test_file) as test_im: | ||||||
|  | @ -329,7 +329,7 @@ class TestFilePng: | ||||||
|     def test_save_rgb_single_transparency(self, tmp_path: Path) -> None: |     def test_save_rgb_single_transparency(self, tmp_path: Path) -> None: | ||||||
|         in_file = "Tests/images/caption_6_33_22.png" |         in_file = "Tests/images/caption_6_33_22.png" | ||||||
|         with Image.open(in_file) as im: |         with Image.open(in_file) as im: | ||||||
|             test_file = str(tmp_path / "temp.png") |             test_file = tmp_path / "temp.png" | ||||||
|             im.save(test_file) |             im.save(test_file) | ||||||
| 
 | 
 | ||||||
|     def test_load_verify(self) -> None: |     def test_load_verify(self) -> None: | ||||||
|  | @ -488,7 +488,7 @@ class TestFilePng: | ||||||
|         im = hopper("P") |         im = hopper("P") | ||||||
|         im.info["transparency"] = 0 |         im.info["transparency"] = 0 | ||||||
| 
 | 
 | ||||||
|         f = str(tmp_path / "temp.png") |         f = tmp_path / "temp.png" | ||||||
|         im.save(f) |         im.save(f) | ||||||
| 
 | 
 | ||||||
|         with Image.open(f) as im2: |         with Image.open(f) as im2: | ||||||
|  | @ -549,7 +549,7 @@ class TestFilePng: | ||||||
| 
 | 
 | ||||||
|     def test_chunk_order(self, tmp_path: Path) -> None: |     def test_chunk_order(self, tmp_path: Path) -> None: | ||||||
|         with Image.open("Tests/images/icc_profile.png") as im: |         with Image.open("Tests/images/icc_profile.png") as im: | ||||||
|             test_file = str(tmp_path / "temp.png") |             test_file = tmp_path / "temp.png" | ||||||
|             im.convert("P").save(test_file, dpi=(100, 100)) |             im.convert("P").save(test_file, dpi=(100, 100)) | ||||||
| 
 | 
 | ||||||
|         chunks = self.get_chunks(test_file) |         chunks = self.get_chunks(test_file) | ||||||
|  | @ -661,7 +661,7 @@ class TestFilePng: | ||||||
|     def test_specify_bits(self, save_all: bool, tmp_path: Path) -> None: |     def test_specify_bits(self, save_all: bool, tmp_path: Path) -> None: | ||||||
|         im = hopper("P") |         im = hopper("P") | ||||||
| 
 | 
 | ||||||
|         out = str(tmp_path / "temp.png") |         out = tmp_path / "temp.png" | ||||||
|         im.save(out, bits=4, save_all=save_all) |         im.save(out, bits=4, save_all=save_all) | ||||||
| 
 | 
 | ||||||
|         with Image.open(out) as reloaded: |         with Image.open(out) as reloaded: | ||||||
|  | @ -671,8 +671,8 @@ class TestFilePng: | ||||||
|         im = Image.new("P", (1, 1)) |         im = Image.new("P", (1, 1)) | ||||||
|         im.putpalette((1, 1, 1)) |         im.putpalette((1, 1, 1)) | ||||||
| 
 | 
 | ||||||
|         out = str(tmp_path / "temp.png") |         out = tmp_path / "temp.png" | ||||||
|         im.save(str(tmp_path / "temp.png")) |         im.save(out) | ||||||
| 
 | 
 | ||||||
|         with Image.open(out) as reloaded: |         with Image.open(out) as reloaded: | ||||||
|             assert len(reloaded.png.im_palette[1]) == 3 |             assert len(reloaded.png.im_palette[1]) == 3 | ||||||
|  | @ -721,7 +721,7 @@ class TestFilePng: | ||||||
| 
 | 
 | ||||||
|     def test_exif_save(self, tmp_path: Path) -> None: |     def test_exif_save(self, tmp_path: Path) -> None: | ||||||
|         # Test exif is not saved from info |         # Test exif is not saved from info | ||||||
|         test_file = str(tmp_path / "temp.png") |         test_file = tmp_path / "temp.png" | ||||||
|         with Image.open("Tests/images/exif.png") as im: |         with Image.open("Tests/images/exif.png") as im: | ||||||
|             im.save(test_file) |             im.save(test_file) | ||||||
| 
 | 
 | ||||||
|  | @ -741,7 +741,7 @@ class TestFilePng: | ||||||
|     ) |     ) | ||||||
|     def test_exif_from_jpg(self, tmp_path: Path) -> None: |     def test_exif_from_jpg(self, tmp_path: Path) -> None: | ||||||
|         with Image.open("Tests/images/pil_sample_rgb.jpg") as im: |         with Image.open("Tests/images/pil_sample_rgb.jpg") as im: | ||||||
|             test_file = str(tmp_path / "temp.png") |             test_file = tmp_path / "temp.png" | ||||||
|             im.save(test_file, exif=im.getexif()) |             im.save(test_file, exif=im.getexif()) | ||||||
| 
 | 
 | ||||||
|         with Image.open(test_file) as reloaded: |         with Image.open(test_file) as reloaded: | ||||||
|  | @ -750,7 +750,7 @@ class TestFilePng: | ||||||
| 
 | 
 | ||||||
|     def test_exif_argument(self, tmp_path: Path) -> None: |     def test_exif_argument(self, tmp_path: Path) -> None: | ||||||
|         with Image.open(TEST_PNG_FILE) as im: |         with Image.open(TEST_PNG_FILE) as im: | ||||||
|             test_file = str(tmp_path / "temp.png") |             test_file = tmp_path / "temp.png" | ||||||
|             im.save(test_file, exif=b"exifstring") |             im.save(test_file, exif=b"exifstring") | ||||||
| 
 | 
 | ||||||
|         with Image.open(test_file) as reloaded: |         with Image.open(test_file) as reloaded: | ||||||
|  |  | ||||||
|  | @ -94,7 +94,7 @@ def test_16bit_pgm() -> None: | ||||||
| 
 | 
 | ||||||
| def test_16bit_pgm_write(tmp_path: Path) -> None: | def test_16bit_pgm_write(tmp_path: Path) -> None: | ||||||
|     with Image.open("Tests/images/16_bit_binary.pgm") as im: |     with Image.open("Tests/images/16_bit_binary.pgm") as im: | ||||||
|         filename = str(tmp_path / "temp.pgm") |         filename = tmp_path / "temp.pgm" | ||||||
|         im.save(filename, "PPM") |         im.save(filename, "PPM") | ||||||
|         assert_image_equal_tofile(im, filename) |         assert_image_equal_tofile(im, filename) | ||||||
| 
 | 
 | ||||||
|  | @ -106,7 +106,7 @@ def test_pnm(tmp_path: Path) -> None: | ||||||
|     with Image.open("Tests/images/hopper.pnm") as im: |     with Image.open("Tests/images/hopper.pnm") as im: | ||||||
|         assert_image_similar(im, hopper(), 0.0001) |         assert_image_similar(im, hopper(), 0.0001) | ||||||
| 
 | 
 | ||||||
|         filename = str(tmp_path / "temp.pnm") |         filename = tmp_path / "temp.pnm" | ||||||
|         im.save(filename) |         im.save(filename) | ||||||
| 
 | 
 | ||||||
|         assert_image_equal_tofile(im, filename) |         assert_image_equal_tofile(im, filename) | ||||||
|  | @ -117,7 +117,7 @@ def test_pfm(tmp_path: Path) -> None: | ||||||
|         assert im.info["scale"] == 1.0 |         assert im.info["scale"] == 1.0 | ||||||
|         assert_image_equal(im, hopper("F")) |         assert_image_equal(im, hopper("F")) | ||||||
| 
 | 
 | ||||||
|         filename = str(tmp_path / "tmp.pfm") |         filename = tmp_path / "tmp.pfm" | ||||||
|         im.save(filename) |         im.save(filename) | ||||||
| 
 | 
 | ||||||
|         assert_image_equal_tofile(im, filename) |         assert_image_equal_tofile(im, filename) | ||||||
|  | @ -128,7 +128,7 @@ def test_pfm_big_endian(tmp_path: Path) -> None: | ||||||
|         assert im.info["scale"] == 2.5 |         assert im.info["scale"] == 2.5 | ||||||
|         assert_image_equal(im, hopper("F")) |         assert_image_equal(im, hopper("F")) | ||||||
| 
 | 
 | ||||||
|         filename = str(tmp_path / "tmp.pfm") |         filename = tmp_path / "tmp.pfm" | ||||||
|         im.save(filename) |         im.save(filename) | ||||||
| 
 | 
 | ||||||
|         assert_image_equal_tofile(im, filename) |         assert_image_equal_tofile(im, filename) | ||||||
|  | @ -194,8 +194,8 @@ def test_16bit_plain_pgm() -> None: | ||||||
| def test_plain_data_with_comment( | def test_plain_data_with_comment( | ||||||
|     tmp_path: Path, header: bytes, data: bytes, comment_count: int |     tmp_path: Path, header: bytes, data: bytes, comment_count: int | ||||||
| ) -> None: | ) -> None: | ||||||
|     path1 = str(tmp_path / "temp1.ppm") |     path1 = tmp_path / "temp1.ppm" | ||||||
|     path2 = str(tmp_path / "temp2.ppm") |     path2 = tmp_path / "temp2.ppm" | ||||||
|     comment = b"# comment" * comment_count |     comment = b"# comment" * comment_count | ||||||
|     with open(path1, "wb") as f1, open(path2, "wb") as f2: |     with open(path1, "wb") as f1, open(path2, "wb") as f2: | ||||||
|         f1.write(header + b"\n\n" + data) |         f1.write(header + b"\n\n" + data) | ||||||
|  | @ -207,7 +207,7 @@ def test_plain_data_with_comment( | ||||||
| 
 | 
 | ||||||
| @pytest.mark.parametrize("data", (b"P1\n128 128\n", b"P3\n128 128\n255\n")) | @pytest.mark.parametrize("data", (b"P1\n128 128\n", b"P3\n128 128\n255\n")) | ||||||
| def test_plain_truncated_data(tmp_path: Path, data: bytes) -> None: | def test_plain_truncated_data(tmp_path: Path, data: bytes) -> None: | ||||||
|     path = str(tmp_path / "temp.ppm") |     path = tmp_path / "temp.ppm" | ||||||
|     with open(path, "wb") as f: |     with open(path, "wb") as f: | ||||||
|         f.write(data) |         f.write(data) | ||||||
| 
 | 
 | ||||||
|  | @ -218,7 +218,7 @@ def test_plain_truncated_data(tmp_path: Path, data: bytes) -> None: | ||||||
| 
 | 
 | ||||||
| @pytest.mark.parametrize("data", (b"P1\n128 128\n1009", b"P3\n128 128\n255\n100A")) | @pytest.mark.parametrize("data", (b"P1\n128 128\n1009", b"P3\n128 128\n255\n100A")) | ||||||
| def test_plain_invalid_data(tmp_path: Path, data: bytes) -> None: | def test_plain_invalid_data(tmp_path: Path, data: bytes) -> None: | ||||||
|     path = str(tmp_path / "temp.ppm") |     path = tmp_path / "temp.ppm" | ||||||
|     with open(path, "wb") as f: |     with open(path, "wb") as f: | ||||||
|         f.write(data) |         f.write(data) | ||||||
| 
 | 
 | ||||||
|  | @ -235,7 +235,7 @@ def test_plain_invalid_data(tmp_path: Path, data: bytes) -> None: | ||||||
|     ), |     ), | ||||||
| ) | ) | ||||||
| def test_plain_ppm_token_too_long(tmp_path: Path, data: bytes) -> None: | def test_plain_ppm_token_too_long(tmp_path: Path, data: bytes) -> None: | ||||||
|     path = str(tmp_path / "temp.ppm") |     path = tmp_path / "temp.ppm" | ||||||
|     with open(path, "wb") as f: |     with open(path, "wb") as f: | ||||||
|         f.write(data) |         f.write(data) | ||||||
| 
 | 
 | ||||||
|  | @ -245,7 +245,7 @@ def test_plain_ppm_token_too_long(tmp_path: Path, data: bytes) -> None: | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
| def test_plain_ppm_value_negative(tmp_path: Path) -> None: | def test_plain_ppm_value_negative(tmp_path: Path) -> None: | ||||||
|     path = str(tmp_path / "temp.ppm") |     path = tmp_path / "temp.ppm" | ||||||
|     with open(path, "wb") as f: |     with open(path, "wb") as f: | ||||||
|         f.write(b"P3\n128 128\n255\n-1") |         f.write(b"P3\n128 128\n255\n-1") | ||||||
| 
 | 
 | ||||||
|  | @ -255,7 +255,7 @@ def test_plain_ppm_value_negative(tmp_path: Path) -> None: | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
| def test_plain_ppm_value_too_large(tmp_path: Path) -> None: | def test_plain_ppm_value_too_large(tmp_path: Path) -> None: | ||||||
|     path = str(tmp_path / "temp.ppm") |     path = tmp_path / "temp.ppm" | ||||||
|     with open(path, "wb") as f: |     with open(path, "wb") as f: | ||||||
|         f.write(b"P3\n128 128\n255\n256") |         f.write(b"P3\n128 128\n255\n256") | ||||||
| 
 | 
 | ||||||
|  | @ -270,7 +270,7 @@ def test_magic() -> None: | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
| def test_header_with_comments(tmp_path: Path) -> None: | def test_header_with_comments(tmp_path: Path) -> None: | ||||||
|     path = str(tmp_path / "temp.ppm") |     path = tmp_path / "temp.ppm" | ||||||
|     with open(path, "wb") as f: |     with open(path, "wb") as f: | ||||||
|         f.write(b"P6 #comment\n#comment\r12#comment\r8\n128 #comment\n255\n") |         f.write(b"P6 #comment\n#comment\r12#comment\r8\n128 #comment\n255\n") | ||||||
| 
 | 
 | ||||||
|  | @ -279,7 +279,7 @@ def test_header_with_comments(tmp_path: Path) -> None: | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
| def test_non_integer_token(tmp_path: Path) -> None: | def test_non_integer_token(tmp_path: Path) -> None: | ||||||
|     path = str(tmp_path / "temp.ppm") |     path = tmp_path / "temp.ppm" | ||||||
|     with open(path, "wb") as f: |     with open(path, "wb") as f: | ||||||
|         f.write(b"P6\nTEST") |         f.write(b"P6\nTEST") | ||||||
| 
 | 
 | ||||||
|  | @ -289,7 +289,7 @@ def test_non_integer_token(tmp_path: Path) -> None: | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
| def test_header_token_too_long(tmp_path: Path) -> None: | def test_header_token_too_long(tmp_path: Path) -> None: | ||||||
|     path = str(tmp_path / "temp.ppm") |     path = tmp_path / "temp.ppm" | ||||||
|     with open(path, "wb") as f: |     with open(path, "wb") as f: | ||||||
|         f.write(b"P6\n 01234567890") |         f.write(b"P6\n 01234567890") | ||||||
| 
 | 
 | ||||||
|  | @ -300,7 +300,7 @@ def test_header_token_too_long(tmp_path: Path) -> None: | ||||||
| 
 | 
 | ||||||
| def test_truncated_file(tmp_path: Path) -> None: | def test_truncated_file(tmp_path: Path) -> None: | ||||||
|     # Test EOF in header |     # Test EOF in header | ||||||
|     path = str(tmp_path / "temp.pgm") |     path = tmp_path / "temp.pgm" | ||||||
|     with open(path, "wb") as f: |     with open(path, "wb") as f: | ||||||
|         f.write(b"P6") |         f.write(b"P6") | ||||||
| 
 | 
 | ||||||
|  | @ -316,7 +316,7 @@ def test_truncated_file(tmp_path: Path) -> None: | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
| def test_not_enough_image_data(tmp_path: Path) -> None: | def test_not_enough_image_data(tmp_path: Path) -> None: | ||||||
|     path = str(tmp_path / "temp.ppm") |     path = tmp_path / "temp.ppm" | ||||||
|     with open(path, "wb") as f: |     with open(path, "wb") as f: | ||||||
|         f.write(b"P2 1 2 255 255") |         f.write(b"P2 1 2 255 255") | ||||||
| 
 | 
 | ||||||
|  | @ -327,7 +327,7 @@ def test_not_enough_image_data(tmp_path: Path) -> None: | ||||||
| 
 | 
 | ||||||
| @pytest.mark.parametrize("maxval", (b"0", b"65536")) | @pytest.mark.parametrize("maxval", (b"0", b"65536")) | ||||||
| def test_invalid_maxval(maxval: bytes, tmp_path: Path) -> None: | def test_invalid_maxval(maxval: bytes, tmp_path: Path) -> None: | ||||||
|     path = str(tmp_path / "temp.ppm") |     path = tmp_path / "temp.ppm" | ||||||
|     with open(path, "wb") as f: |     with open(path, "wb") as f: | ||||||
|         f.write(b"P6\n3 1 " + maxval) |         f.write(b"P6\n3 1 " + maxval) | ||||||
| 
 | 
 | ||||||
|  | @ -350,7 +350,7 @@ def test_neg_ppm() -> None: | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
| def test_mimetypes(tmp_path: Path) -> None: | def test_mimetypes(tmp_path: Path) -> None: | ||||||
|     path = str(tmp_path / "temp.pgm") |     path = tmp_path / "temp.pgm" | ||||||
| 
 | 
 | ||||||
|     with open(path, "wb") as f: |     with open(path, "wb") as f: | ||||||
|         f.write(b"P4\n128 128\n255") |         f.write(b"P4\n128 128\n255") | ||||||
|  |  | ||||||
|  | @ -73,11 +73,11 @@ def test_invalid_file() -> None: | ||||||
| 
 | 
 | ||||||
| def test_write(tmp_path: Path) -> None: | def test_write(tmp_path: Path) -> None: | ||||||
|     def roundtrip(img: Image.Image) -> None: |     def roundtrip(img: Image.Image) -> None: | ||||||
|         out = str(tmp_path / "temp.sgi") |         out = tmp_path / "temp.sgi" | ||||||
|         img.save(out, format="sgi") |         img.save(out, format="sgi") | ||||||
|         assert_image_equal_tofile(img, out) |         assert_image_equal_tofile(img, out) | ||||||
| 
 | 
 | ||||||
|         out = str(tmp_path / "fp.sgi") |         out = tmp_path / "fp.sgi" | ||||||
|         with open(out, "wb") as fp: |         with open(out, "wb") as fp: | ||||||
|             img.save(fp) |             img.save(fp) | ||||||
|             assert_image_equal_tofile(img, out) |             assert_image_equal_tofile(img, out) | ||||||
|  | @ -95,7 +95,7 @@ def test_write16(tmp_path: Path) -> None: | ||||||
|     test_file = "Tests/images/hopper16.rgb" |     test_file = "Tests/images/hopper16.rgb" | ||||||
| 
 | 
 | ||||||
|     with Image.open(test_file) as im: |     with Image.open(test_file) as im: | ||||||
|         out = str(tmp_path / "temp.sgi") |         out = tmp_path / "temp.sgi" | ||||||
|         im.save(out, format="sgi", bpc=2) |         im.save(out, format="sgi", bpc=2) | ||||||
| 
 | 
 | ||||||
|         assert_image_equal_tofile(im, out) |         assert_image_equal_tofile(im, out) | ||||||
|  | @ -103,7 +103,7 @@ def test_write16(tmp_path: Path) -> None: | ||||||
| 
 | 
 | ||||||
| def test_unsupported_mode(tmp_path: Path) -> None: | def test_unsupported_mode(tmp_path: Path) -> None: | ||||||
|     im = hopper("LA") |     im = hopper("LA") | ||||||
|     out = str(tmp_path / "temp.sgi") |     out = tmp_path / "temp.sgi" | ||||||
| 
 | 
 | ||||||
|     with pytest.raises(ValueError): |     with pytest.raises(ValueError): | ||||||
|         im.save(out, format="sgi") |         im.save(out, format="sgi") | ||||||
|  |  | ||||||
|  | @ -51,7 +51,7 @@ def test_context_manager() -> None: | ||||||
| 
 | 
 | ||||||
| def test_save(tmp_path: Path) -> None: | def test_save(tmp_path: Path) -> None: | ||||||
|     # Arrange |     # Arrange | ||||||
|     temp = str(tmp_path / "temp.spider") |     temp = tmp_path / "temp.spider" | ||||||
|     im = hopper() |     im = hopper() | ||||||
| 
 | 
 | ||||||
|     # Act |     # Act | ||||||
|  |  | ||||||
|  | @ -24,7 +24,7 @@ _ORIGIN_TO_ORIENTATION = {"tl": 1, "bl": -1} | ||||||
| @pytest.mark.parametrize("mode", _MODES) | @pytest.mark.parametrize("mode", _MODES) | ||||||
| def test_sanity(mode: str, tmp_path: Path) -> None: | def test_sanity(mode: str, tmp_path: Path) -> None: | ||||||
|     def roundtrip(original_im: Image.Image) -> None: |     def roundtrip(original_im: Image.Image) -> None: | ||||||
|         out = str(tmp_path / "temp.tga") |         out = tmp_path / "temp.tga" | ||||||
| 
 | 
 | ||||||
|         original_im.save(out, rle=rle) |         original_im.save(out, rle=rle) | ||||||
|         with Image.open(out) as saved_im: |         with Image.open(out) as saved_im: | ||||||
|  | @ -65,7 +65,7 @@ def test_sanity(mode: str, tmp_path: Path) -> None: | ||||||
|                     roundtrip(original_im) |                     roundtrip(original_im) | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
| def test_palette_depth_8(tmp_path: Path) -> None: | def test_palette_depth_8() -> None: | ||||||
|     with pytest.raises(UnidentifiedImageError): |     with pytest.raises(UnidentifiedImageError): | ||||||
|         Image.open("Tests/images/p_8.tga") |         Image.open("Tests/images/p_8.tga") | ||||||
| 
 | 
 | ||||||
|  | @ -76,7 +76,7 @@ def test_palette_depth_16(tmp_path: Path) -> None: | ||||||
|         assert im.palette.mode == "RGBA" |         assert im.palette.mode == "RGBA" | ||||||
|         assert_image_equal_tofile(im.convert("RGBA"), "Tests/images/p_16.png") |         assert_image_equal_tofile(im.convert("RGBA"), "Tests/images/p_16.png") | ||||||
| 
 | 
 | ||||||
|         out = str(tmp_path / "temp.png") |         out = tmp_path / "temp.png" | ||||||
|         im.save(out) |         im.save(out) | ||||||
|         with Image.open(out) as reloaded: |         with Image.open(out) as reloaded: | ||||||
|             assert_image_equal_tofile(reloaded.convert("RGBA"), "Tests/images/p_16.png") |             assert_image_equal_tofile(reloaded.convert("RGBA"), "Tests/images/p_16.png") | ||||||
|  | @ -122,7 +122,7 @@ def test_cross_scan_line() -> None: | ||||||
| def test_save(tmp_path: Path) -> None: | def test_save(tmp_path: Path) -> None: | ||||||
|     test_file = "Tests/images/tga_id_field.tga" |     test_file = "Tests/images/tga_id_field.tga" | ||||||
|     with Image.open(test_file) as im: |     with Image.open(test_file) as im: | ||||||
|         out = str(tmp_path / "temp.tga") |         out = tmp_path / "temp.tga" | ||||||
| 
 | 
 | ||||||
|         # Save |         # Save | ||||||
|         im.save(out) |         im.save(out) | ||||||
|  | @ -141,7 +141,7 @@ def test_small_palette(tmp_path: Path) -> None: | ||||||
|     colors = [0, 0, 0] |     colors = [0, 0, 0] | ||||||
|     im.putpalette(colors) |     im.putpalette(colors) | ||||||
| 
 | 
 | ||||||
|     out = str(tmp_path / "temp.tga") |     out = tmp_path / "temp.tga" | ||||||
|     im.save(out) |     im.save(out) | ||||||
| 
 | 
 | ||||||
|     with Image.open(out) as reloaded: |     with Image.open(out) as reloaded: | ||||||
|  | @ -155,7 +155,7 @@ def test_missing_palette() -> None: | ||||||
| 
 | 
 | ||||||
| def test_save_wrong_mode(tmp_path: Path) -> None: | def test_save_wrong_mode(tmp_path: Path) -> None: | ||||||
|     im = hopper("PA") |     im = hopper("PA") | ||||||
|     out = str(tmp_path / "temp.tga") |     out = tmp_path / "temp.tga" | ||||||
| 
 | 
 | ||||||
|     with pytest.raises(OSError): |     with pytest.raises(OSError): | ||||||
|         im.save(out) |         im.save(out) | ||||||
|  | @ -172,7 +172,7 @@ def test_save_mapdepth() -> None: | ||||||
| def test_save_id_section(tmp_path: Path) -> None: | def test_save_id_section(tmp_path: Path) -> None: | ||||||
|     test_file = "Tests/images/rgb32rle.tga" |     test_file = "Tests/images/rgb32rle.tga" | ||||||
|     with Image.open(test_file) as im: |     with Image.open(test_file) as im: | ||||||
|         out = str(tmp_path / "temp.tga") |         out = tmp_path / "temp.tga" | ||||||
| 
 | 
 | ||||||
|         # Check there is no id section |         # Check there is no id section | ||||||
|         im.save(out) |         im.save(out) | ||||||
|  | @ -202,7 +202,7 @@ def test_save_id_section(tmp_path: Path) -> None: | ||||||
| 
 | 
 | ||||||
| def test_save_orientation(tmp_path: Path) -> None: | def test_save_orientation(tmp_path: Path) -> None: | ||||||
|     test_file = "Tests/images/rgb32rle.tga" |     test_file = "Tests/images/rgb32rle.tga" | ||||||
|     out = str(tmp_path / "temp.tga") |     out = tmp_path / "temp.tga" | ||||||
|     with Image.open(test_file) as im: |     with Image.open(test_file) as im: | ||||||
|         assert im.info["orientation"] == -1 |         assert im.info["orientation"] == -1 | ||||||
| 
 | 
 | ||||||
|  | @ -229,7 +229,7 @@ def test_save_rle(tmp_path: Path) -> None: | ||||||
|     with Image.open(test_file) as im: |     with Image.open(test_file) as im: | ||||||
|         assert im.info["compression"] == "tga_rle" |         assert im.info["compression"] == "tga_rle" | ||||||
| 
 | 
 | ||||||
|         out = str(tmp_path / "temp.tga") |         out = tmp_path / "temp.tga" | ||||||
| 
 | 
 | ||||||
|         # Save |         # Save | ||||||
|         im.save(out) |         im.save(out) | ||||||
|  | @ -266,7 +266,7 @@ def test_save_l_transparency(tmp_path: Path) -> None: | ||||||
|         assert im.mode == "LA" |         assert im.mode == "LA" | ||||||
|         assert im.getchannel("A").getcolors()[0][0] == num_transparent |         assert im.getchannel("A").getcolors()[0][0] == num_transparent | ||||||
| 
 | 
 | ||||||
|         out = str(tmp_path / "temp.tga") |         out = tmp_path / "temp.tga" | ||||||
|         im.save(out) |         im.save(out) | ||||||
| 
 | 
 | ||||||
|     with Image.open(out) as test_im: |     with Image.open(out) as test_im: | ||||||
|  |  | ||||||
|  | @ -31,7 +31,7 @@ except ImportError: | ||||||
| 
 | 
 | ||||||
| class TestFileTiff: | class TestFileTiff: | ||||||
|     def test_sanity(self, tmp_path: Path) -> None: |     def test_sanity(self, tmp_path: Path) -> None: | ||||||
|         filename = str(tmp_path / "temp.tif") |         filename = tmp_path / "temp.tif" | ||||||
| 
 | 
 | ||||||
|         hopper("RGB").save(filename) |         hopper("RGB").save(filename) | ||||||
| 
 | 
 | ||||||
|  | @ -112,11 +112,11 @@ class TestFileTiff: | ||||||
|             assert_image_equal_tofile(im, "Tests/images/hopper.tif") |             assert_image_equal_tofile(im, "Tests/images/hopper.tif") | ||||||
| 
 | 
 | ||||||
|         with Image.open("Tests/images/hopper_bigtiff.tif") as im: |         with Image.open("Tests/images/hopper_bigtiff.tif") as im: | ||||||
|             outfile = str(tmp_path / "temp.tif") |             outfile = tmp_path / "temp.tif" | ||||||
|             im.save(outfile, save_all=True, append_images=[im], tiffinfo=im.tag_v2) |             im.save(outfile, save_all=True, append_images=[im], tiffinfo=im.tag_v2) | ||||||
| 
 | 
 | ||||||
|     def test_bigtiff_save(self, tmp_path: Path) -> None: |     def test_bigtiff_save(self, tmp_path: Path) -> None: | ||||||
|         outfile = str(tmp_path / "temp.tif") |         outfile = tmp_path / "temp.tif" | ||||||
|         im = hopper() |         im = hopper() | ||||||
|         im.save(outfile, big_tiff=True) |         im.save(outfile, big_tiff=True) | ||||||
| 
 | 
 | ||||||
|  | @ -185,7 +185,7 @@ class TestFileTiff: | ||||||
|             assert im.info["dpi"] == (dpi, dpi) |             assert im.info["dpi"] == (dpi, dpi) | ||||||
| 
 | 
 | ||||||
|     def test_save_float_dpi(self, tmp_path: Path) -> None: |     def test_save_float_dpi(self, tmp_path: Path) -> None: | ||||||
|         outfile = str(tmp_path / "temp.tif") |         outfile = tmp_path / "temp.tif" | ||||||
|         with Image.open("Tests/images/hopper.tif") as im: |         with Image.open("Tests/images/hopper.tif") as im: | ||||||
|             dpi = (72.2, 72.2) |             dpi = (72.2, 72.2) | ||||||
|             im.save(outfile, dpi=dpi) |             im.save(outfile, dpi=dpi) | ||||||
|  | @ -220,12 +220,12 @@ class TestFileTiff: | ||||||
| 
 | 
 | ||||||
|     def test_save_rgba(self, tmp_path: Path) -> None: |     def test_save_rgba(self, tmp_path: Path) -> None: | ||||||
|         im = hopper("RGBA") |         im = hopper("RGBA") | ||||||
|         outfile = str(tmp_path / "temp.tif") |         outfile = tmp_path / "temp.tif" | ||||||
|         im.save(outfile) |         im.save(outfile) | ||||||
| 
 | 
 | ||||||
|     def test_save_unsupported_mode(self, tmp_path: Path) -> None: |     def test_save_unsupported_mode(self, tmp_path: Path) -> None: | ||||||
|         im = hopper("HSV") |         im = hopper("HSV") | ||||||
|         outfile = str(tmp_path / "temp.tif") |         outfile = tmp_path / "temp.tif" | ||||||
|         with pytest.raises(OSError): |         with pytest.raises(OSError): | ||||||
|             im.save(outfile) |             im.save(outfile) | ||||||
| 
 | 
 | ||||||
|  | @ -485,14 +485,14 @@ class TestFileTiff: | ||||||
|             assert gps[0] == b"\x03\x02\x00\x00" |             assert gps[0] == b"\x03\x02\x00\x00" | ||||||
|             assert gps[18] == "WGS-84" |             assert gps[18] == "WGS-84" | ||||||
| 
 | 
 | ||||||
|         outfile = str(tmp_path / "temp.tif") |         outfile = tmp_path / "temp.tif" | ||||||
|         with Image.open("Tests/images/ifd_tag_type.tiff") as im: |         with Image.open("Tests/images/ifd_tag_type.tiff") as im: | ||||||
|             exif = im.getexif() |             exif = im.getexif() | ||||||
|             check_exif(exif) |             check_exif(exif) | ||||||
| 
 | 
 | ||||||
|             im.save(outfile, exif=exif) |             im.save(outfile, exif=exif) | ||||||
| 
 | 
 | ||||||
|         outfile2 = str(tmp_path / "temp2.tif") |         outfile2 = tmp_path / "temp2.tif" | ||||||
|         with Image.open(outfile) as im: |         with Image.open(outfile) as im: | ||||||
|             exif = im.getexif() |             exif = im.getexif() | ||||||
|             check_exif(exif) |             check_exif(exif) | ||||||
|  | @ -504,7 +504,7 @@ class TestFileTiff: | ||||||
|             check_exif(exif) |             check_exif(exif) | ||||||
| 
 | 
 | ||||||
|     def test_modify_exif(self, tmp_path: Path) -> None: |     def test_modify_exif(self, tmp_path: Path) -> None: | ||||||
|         outfile = str(tmp_path / "temp.tif") |         outfile = tmp_path / "temp.tif" | ||||||
|         with Image.open("Tests/images/ifd_tag_type.tiff") as im: |         with Image.open("Tests/images/ifd_tag_type.tiff") as im: | ||||||
|             exif = im.getexif() |             exif = im.getexif() | ||||||
|             exif[264] = 100 |             exif[264] = 100 | ||||||
|  | @ -533,7 +533,7 @@ class TestFileTiff: | ||||||
| 
 | 
 | ||||||
|     @pytest.mark.parametrize("mode", ("1", "L")) |     @pytest.mark.parametrize("mode", ("1", "L")) | ||||||
|     def test_photometric(self, mode: str, tmp_path: Path) -> None: |     def test_photometric(self, mode: str, tmp_path: Path) -> None: | ||||||
|         filename = str(tmp_path / "temp.tif") |         filename = tmp_path / "temp.tif" | ||||||
|         im = hopper(mode) |         im = hopper(mode) | ||||||
|         im.save(filename, tiffinfo={262: 0}) |         im.save(filename, tiffinfo={262: 0}) | ||||||
|         with Image.open(filename) as reloaded: |         with Image.open(filename) as reloaded: | ||||||
|  | @ -612,7 +612,7 @@ class TestFileTiff: | ||||||
| 
 | 
 | ||||||
|     def test_with_underscores(self, tmp_path: Path) -> None: |     def test_with_underscores(self, tmp_path: Path) -> None: | ||||||
|         kwargs = {"resolution_unit": "inch", "x_resolution": 72, "y_resolution": 36} |         kwargs = {"resolution_unit": "inch", "x_resolution": 72, "y_resolution": 36} | ||||||
|         filename = str(tmp_path / "temp.tif") |         filename = tmp_path / "temp.tif" | ||||||
|         hopper("RGB").save(filename, "TIFF", **kwargs) |         hopper("RGB").save(filename, "TIFF", **kwargs) | ||||||
|         with Image.open(filename) as im: |         with Image.open(filename) as im: | ||||||
|             # legacy interface |             # legacy interface | ||||||
|  | @ -630,14 +630,14 @@ class TestFileTiff: | ||||||
|         with Image.open(infile) as im: |         with Image.open(infile) as im: | ||||||
|             assert im.getpixel((0, 0)) == pixel_value |             assert im.getpixel((0, 0)) == pixel_value | ||||||
| 
 | 
 | ||||||
|             tmpfile = str(tmp_path / "temp.tif") |             tmpfile = tmp_path / "temp.tif" | ||||||
|             im.save(tmpfile) |             im.save(tmpfile) | ||||||
| 
 | 
 | ||||||
|             assert_image_equal_tofile(im, tmpfile) |             assert_image_equal_tofile(im, tmpfile) | ||||||
| 
 | 
 | ||||||
|     def test_iptc(self, tmp_path: Path) -> None: |     def test_iptc(self, tmp_path: Path) -> None: | ||||||
|         # Do not preserve IPTC_NAA_CHUNK by default if type is LONG |         # Do not preserve IPTC_NAA_CHUNK by default if type is LONG | ||||||
|         outfile = str(tmp_path / "temp.tif") |         outfile = tmp_path / "temp.tif" | ||||||
|         with Image.open("Tests/images/hopper.tif") as im: |         with Image.open("Tests/images/hopper.tif") as im: | ||||||
|             im.load() |             im.load() | ||||||
|             assert isinstance(im, TiffImagePlugin.TiffImageFile) |             assert isinstance(im, TiffImagePlugin.TiffImageFile) | ||||||
|  | @ -652,7 +652,7 @@ class TestFileTiff: | ||||||
|             assert 33723 not in im.tag_v2 |             assert 33723 not in im.tag_v2 | ||||||
| 
 | 
 | ||||||
|     def test_rowsperstrip(self, tmp_path: Path) -> None: |     def test_rowsperstrip(self, tmp_path: Path) -> None: | ||||||
|         outfile = str(tmp_path / "temp.tif") |         outfile = tmp_path / "temp.tif" | ||||||
|         im = hopper() |         im = hopper() | ||||||
|         im.save(outfile, tiffinfo={278: 256}) |         im.save(outfile, tiffinfo={278: 256}) | ||||||
| 
 | 
 | ||||||
|  | @ -703,7 +703,7 @@ class TestFileTiff: | ||||||
|         with Image.open(infile) as im: |         with Image.open(infile) as im: | ||||||
|             assert im._planar_configuration == 2 |             assert im._planar_configuration == 2 | ||||||
| 
 | 
 | ||||||
|             outfile = str(tmp_path / "temp.tif") |             outfile = tmp_path / "temp.tif" | ||||||
|             im.save(outfile) |             im.save(outfile) | ||||||
| 
 | 
 | ||||||
|             with Image.open(outfile) as reloaded: |             with Image.open(outfile) as reloaded: | ||||||
|  | @ -718,7 +718,7 @@ class TestFileTiff: | ||||||
| 
 | 
 | ||||||
|     @pytest.mark.parametrize("mode", ("P", "PA")) |     @pytest.mark.parametrize("mode", ("P", "PA")) | ||||||
|     def test_palette(self, mode: str, tmp_path: Path) -> None: |     def test_palette(self, mode: str, tmp_path: Path) -> None: | ||||||
|         outfile = str(tmp_path / "temp.tif") |         outfile = tmp_path / "temp.tif" | ||||||
| 
 | 
 | ||||||
|         im = hopper(mode) |         im = hopper(mode) | ||||||
|         im.save(outfile) |         im.save(outfile) | ||||||
|  | @ -863,7 +863,7 @@ class TestFileTiff: | ||||||
|         im.info["icc_profile"] = "Dummy value" |         im.info["icc_profile"] = "Dummy value" | ||||||
| 
 | 
 | ||||||
|         # Try save-load round trip to make sure both handle icc_profile. |         # Try save-load round trip to make sure both handle icc_profile. | ||||||
|         tmpfile = str(tmp_path / "temp.tif") |         tmpfile = tmp_path / "temp.tif" | ||||||
|         im.save(tmpfile, "TIFF", compression="raw") |         im.save(tmpfile, "TIFF", compression="raw") | ||||||
|         with Image.open(tmpfile) as reloaded: |         with Image.open(tmpfile) as reloaded: | ||||||
|             assert b"Dummy value" == reloaded.info["icc_profile"] |             assert b"Dummy value" == reloaded.info["icc_profile"] | ||||||
|  | @ -872,7 +872,7 @@ class TestFileTiff: | ||||||
|         im = hopper() |         im = hopper() | ||||||
|         assert "icc_profile" not in im.info |         assert "icc_profile" not in im.info | ||||||
| 
 | 
 | ||||||
|         outfile = str(tmp_path / "temp.tif") |         outfile = tmp_path / "temp.tif" | ||||||
|         icc_profile = b"Dummy value" |         icc_profile = b"Dummy value" | ||||||
|         im.save(outfile, icc_profile=icc_profile) |         im.save(outfile, icc_profile=icc_profile) | ||||||
| 
 | 
 | ||||||
|  | @ -883,11 +883,11 @@ class TestFileTiff: | ||||||
|         with Image.open("Tests/images/hopper.bmp") as im: |         with Image.open("Tests/images/hopper.bmp") as im: | ||||||
|             assert im.info["compression"] == 0 |             assert im.info["compression"] == 0 | ||||||
| 
 | 
 | ||||||
|             outfile = str(tmp_path / "temp.tif") |             outfile = tmp_path / "temp.tif" | ||||||
|             im.save(outfile) |             im.save(outfile) | ||||||
| 
 | 
 | ||||||
|     def test_discard_icc_profile(self, tmp_path: Path) -> None: |     def test_discard_icc_profile(self, tmp_path: Path) -> None: | ||||||
|         outfile = str(tmp_path / "temp.tif") |         outfile = tmp_path / "temp.tif" | ||||||
| 
 | 
 | ||||||
|         with Image.open("Tests/images/icc_profile.png") as im: |         with Image.open("Tests/images/icc_profile.png") as im: | ||||||
|             assert "icc_profile" in im.info |             assert "icc_profile" in im.info | ||||||
|  | @ -940,7 +940,7 @@ class TestFileTiff: | ||||||
|             ] |             ] | ||||||
| 
 | 
 | ||||||
|     def test_tiff_chunks(self, tmp_path: Path) -> None: |     def test_tiff_chunks(self, tmp_path: Path) -> None: | ||||||
|         tmpfile = str(tmp_path / "temp.tif") |         tmpfile = tmp_path / "temp.tif" | ||||||
| 
 | 
 | ||||||
|         im = hopper() |         im = hopper() | ||||||
|         with open(tmpfile, "wb") as fp: |         with open(tmpfile, "wb") as fp: | ||||||
|  | @ -962,7 +962,7 @@ class TestFileTiff: | ||||||
| 
 | 
 | ||||||
|     def test_close_on_load_exclusive(self, tmp_path: Path) -> None: |     def test_close_on_load_exclusive(self, tmp_path: Path) -> None: | ||||||
|         # similar to test_fd_leak, but runs on unixlike os |         # similar to test_fd_leak, but runs on unixlike os | ||||||
|         tmpfile = str(tmp_path / "temp.tif") |         tmpfile = tmp_path / "temp.tif" | ||||||
| 
 | 
 | ||||||
|         with Image.open("Tests/images/uint16_1_4660.tif") as im: |         with Image.open("Tests/images/uint16_1_4660.tif") as im: | ||||||
|             im.save(tmpfile) |             im.save(tmpfile) | ||||||
|  | @ -974,7 +974,7 @@ class TestFileTiff: | ||||||
|         assert fp.closed |         assert fp.closed | ||||||
| 
 | 
 | ||||||
|     def test_close_on_load_nonexclusive(self, tmp_path: Path) -> None: |     def test_close_on_load_nonexclusive(self, tmp_path: Path) -> None: | ||||||
|         tmpfile = str(tmp_path / "temp.tif") |         tmpfile = tmp_path / "temp.tif" | ||||||
| 
 | 
 | ||||||
|         with Image.open("Tests/images/uint16_1_4660.tif") as im: |         with Image.open("Tests/images/uint16_1_4660.tif") as im: | ||||||
|             im.save(tmpfile) |             im.save(tmpfile) | ||||||
|  | @ -1025,7 +1025,7 @@ class TestFileTiff: | ||||||
| @pytest.mark.skipif(not is_win32(), reason="Windows only") | @pytest.mark.skipif(not is_win32(), reason="Windows only") | ||||||
| class TestFileTiffW32: | class TestFileTiffW32: | ||||||
|     def test_fd_leak(self, tmp_path: Path) -> None: |     def test_fd_leak(self, tmp_path: Path) -> None: | ||||||
|         tmpfile = str(tmp_path / "temp.tif") |         tmpfile = tmp_path / "temp.tif" | ||||||
| 
 | 
 | ||||||
|         # this is an mmaped file. |         # this is an mmaped file. | ||||||
|         with Image.open("Tests/images/uint16_1_4660.tif") as im: |         with Image.open("Tests/images/uint16_1_4660.tif") as im: | ||||||
|  |  | ||||||
|  | @ -56,7 +56,7 @@ def test_rt_metadata(tmp_path: Path) -> None: | ||||||
| 
 | 
 | ||||||
|     info[ImageDescription] = text_data |     info[ImageDescription] = text_data | ||||||
| 
 | 
 | ||||||
|     f = str(tmp_path / "temp.tif") |     f = tmp_path / "temp.tif" | ||||||
| 
 | 
 | ||||||
|     img.save(f, tiffinfo=info) |     img.save(f, tiffinfo=info) | ||||||
| 
 | 
 | ||||||
|  | @ -128,7 +128,7 @@ def test_read_metadata() -> None: | ||||||
| def test_write_metadata(tmp_path: Path) -> None: | def test_write_metadata(tmp_path: Path) -> None: | ||||||
|     """Test metadata writing through the python code""" |     """Test metadata writing through the python code""" | ||||||
|     with Image.open("Tests/images/hopper.tif") as img: |     with Image.open("Tests/images/hopper.tif") as img: | ||||||
|         f = str(tmp_path / "temp.tiff") |         f = tmp_path / "temp.tiff" | ||||||
|         del img.tag[278] |         del img.tag[278] | ||||||
|         img.save(f, tiffinfo=img.tag) |         img.save(f, tiffinfo=img.tag) | ||||||
| 
 | 
 | ||||||
|  | @ -163,7 +163,7 @@ def test_write_metadata(tmp_path: Path) -> None: | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
| def test_change_stripbytecounts_tag_type(tmp_path: Path) -> None: | def test_change_stripbytecounts_tag_type(tmp_path: Path) -> None: | ||||||
|     out = str(tmp_path / "temp.tiff") |     out = tmp_path / "temp.tiff" | ||||||
|     with Image.open("Tests/images/hopper.tif") as im: |     with Image.open("Tests/images/hopper.tif") as im: | ||||||
|         info = im.tag_v2 |         info = im.tag_v2 | ||||||
|         del info[278] |         del info[278] | ||||||
|  | @ -210,7 +210,7 @@ def test_no_duplicate_50741_tag() -> None: | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
| def test_iptc(tmp_path: Path) -> None: | def test_iptc(tmp_path: Path) -> None: | ||||||
|     out = str(tmp_path / "temp.tiff") |     out = tmp_path / "temp.tiff" | ||||||
|     with Image.open("Tests/images/hopper.Lab.tif") as im: |     with Image.open("Tests/images/hopper.Lab.tif") as im: | ||||||
|         im.save(out) |         im.save(out) | ||||||
| 
 | 
 | ||||||
|  | @ -227,7 +227,7 @@ def test_writing_other_types_to_ascii( | ||||||
|     info[271] = value |     info[271] = value | ||||||
| 
 | 
 | ||||||
|     im = hopper() |     im = hopper() | ||||||
|     out = str(tmp_path / "temp.tiff") |     out = tmp_path / "temp.tiff" | ||||||
|     im.save(out, tiffinfo=info) |     im.save(out, tiffinfo=info) | ||||||
| 
 | 
 | ||||||
|     with Image.open(out) as reloaded: |     with Image.open(out) as reloaded: | ||||||
|  | @ -244,7 +244,7 @@ def test_writing_other_types_to_bytes(value: int | IFDRational, tmp_path: Path) | ||||||
| 
 | 
 | ||||||
|     info[700] = value |     info[700] = value | ||||||
| 
 | 
 | ||||||
|     out = str(tmp_path / "temp.tiff") |     out = tmp_path / "temp.tiff" | ||||||
|     im.save(out, tiffinfo=info) |     im.save(out, tiffinfo=info) | ||||||
| 
 | 
 | ||||||
|     with Image.open(out) as reloaded: |     with Image.open(out) as reloaded: | ||||||
|  | @ -263,7 +263,7 @@ def test_writing_other_types_to_undefined( | ||||||
| 
 | 
 | ||||||
|     info[33723] = value |     info[33723] = value | ||||||
| 
 | 
 | ||||||
|     out = str(tmp_path / "temp.tiff") |     out = tmp_path / "temp.tiff" | ||||||
|     im.save(out, tiffinfo=info) |     im.save(out, tiffinfo=info) | ||||||
| 
 | 
 | ||||||
|     with Image.open(out) as reloaded: |     with Image.open(out) as reloaded: | ||||||
|  | @ -296,7 +296,7 @@ def test_empty_metadata() -> None: | ||||||
| 
 | 
 | ||||||
| def test_iccprofile(tmp_path: Path) -> None: | def test_iccprofile(tmp_path: Path) -> None: | ||||||
|     # https://github.com/python-pillow/Pillow/issues/1462 |     # https://github.com/python-pillow/Pillow/issues/1462 | ||||||
|     out = str(tmp_path / "temp.tiff") |     out = tmp_path / "temp.tiff" | ||||||
|     with Image.open("Tests/images/hopper.iccprofile.tif") as im: |     with Image.open("Tests/images/hopper.iccprofile.tif") as im: | ||||||
|         im.save(out) |         im.save(out) | ||||||
| 
 | 
 | ||||||
|  | @ -317,13 +317,13 @@ def test_iccprofile_binary() -> None: | ||||||
| 
 | 
 | ||||||
| def test_iccprofile_save_png(tmp_path: Path) -> None: | def test_iccprofile_save_png(tmp_path: Path) -> None: | ||||||
|     with Image.open("Tests/images/hopper.iccprofile.tif") as im: |     with Image.open("Tests/images/hopper.iccprofile.tif") as im: | ||||||
|         outfile = str(tmp_path / "temp.png") |         outfile = tmp_path / "temp.png" | ||||||
|         im.save(outfile) |         im.save(outfile) | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
| def test_iccprofile_binary_save_png(tmp_path: Path) -> None: | def test_iccprofile_binary_save_png(tmp_path: Path) -> None: | ||||||
|     with Image.open("Tests/images/hopper.iccprofile_binary.tif") as im: |     with Image.open("Tests/images/hopper.iccprofile_binary.tif") as im: | ||||||
|         outfile = str(tmp_path / "temp.png") |         outfile = tmp_path / "temp.png" | ||||||
|         im.save(outfile) |         im.save(outfile) | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
|  | @ -332,7 +332,7 @@ def test_exif_div_zero(tmp_path: Path) -> None: | ||||||
|     info = TiffImagePlugin.ImageFileDirectory_v2() |     info = TiffImagePlugin.ImageFileDirectory_v2() | ||||||
|     info[41988] = TiffImagePlugin.IFDRational(0, 0) |     info[41988] = TiffImagePlugin.IFDRational(0, 0) | ||||||
| 
 | 
 | ||||||
|     out = str(tmp_path / "temp.tiff") |     out = tmp_path / "temp.tiff" | ||||||
|     im.save(out, tiffinfo=info, compression="raw") |     im.save(out, tiffinfo=info, compression="raw") | ||||||
| 
 | 
 | ||||||
|     with Image.open(out) as reloaded: |     with Image.open(out) as reloaded: | ||||||
|  | @ -351,7 +351,7 @@ def test_ifd_unsigned_rational(tmp_path: Path) -> None: | ||||||
| 
 | 
 | ||||||
|     info[41493] = TiffImagePlugin.IFDRational(numerator, 1) |     info[41493] = TiffImagePlugin.IFDRational(numerator, 1) | ||||||
| 
 | 
 | ||||||
|     out = str(tmp_path / "temp.tiff") |     out = tmp_path / "temp.tiff" | ||||||
|     im.save(out, tiffinfo=info, compression="raw") |     im.save(out, tiffinfo=info, compression="raw") | ||||||
| 
 | 
 | ||||||
|     with Image.open(out) as reloaded: |     with Image.open(out) as reloaded: | ||||||
|  | @ -363,7 +363,7 @@ def test_ifd_unsigned_rational(tmp_path: Path) -> None: | ||||||
| 
 | 
 | ||||||
|     info[41493] = TiffImagePlugin.IFDRational(numerator, 1) |     info[41493] = TiffImagePlugin.IFDRational(numerator, 1) | ||||||
| 
 | 
 | ||||||
|     out = str(tmp_path / "temp.tiff") |     out = tmp_path / "temp.tiff" | ||||||
|     im.save(out, tiffinfo=info, compression="raw") |     im.save(out, tiffinfo=info, compression="raw") | ||||||
| 
 | 
 | ||||||
|     with Image.open(out) as reloaded: |     with Image.open(out) as reloaded: | ||||||
|  | @ -381,7 +381,7 @@ def test_ifd_signed_rational(tmp_path: Path) -> None: | ||||||
| 
 | 
 | ||||||
|     info[37380] = TiffImagePlugin.IFDRational(numerator, denominator) |     info[37380] = TiffImagePlugin.IFDRational(numerator, denominator) | ||||||
| 
 | 
 | ||||||
|     out = str(tmp_path / "temp.tiff") |     out = tmp_path / "temp.tiff" | ||||||
|     im.save(out, tiffinfo=info, compression="raw") |     im.save(out, tiffinfo=info, compression="raw") | ||||||
| 
 | 
 | ||||||
|     with Image.open(out) as reloaded: |     with Image.open(out) as reloaded: | ||||||
|  | @ -393,7 +393,7 @@ def test_ifd_signed_rational(tmp_path: Path) -> None: | ||||||
| 
 | 
 | ||||||
|     info[37380] = TiffImagePlugin.IFDRational(numerator, denominator) |     info[37380] = TiffImagePlugin.IFDRational(numerator, denominator) | ||||||
| 
 | 
 | ||||||
|     out = str(tmp_path / "temp.tiff") |     out = tmp_path / "temp.tiff" | ||||||
|     im.save(out, tiffinfo=info, compression="raw") |     im.save(out, tiffinfo=info, compression="raw") | ||||||
| 
 | 
 | ||||||
|     with Image.open(out) as reloaded: |     with Image.open(out) as reloaded: | ||||||
|  | @ -406,7 +406,7 @@ def test_ifd_signed_rational(tmp_path: Path) -> None: | ||||||
| 
 | 
 | ||||||
|     info[37380] = TiffImagePlugin.IFDRational(numerator, denominator) |     info[37380] = TiffImagePlugin.IFDRational(numerator, denominator) | ||||||
| 
 | 
 | ||||||
|     out = str(tmp_path / "temp.tiff") |     out = tmp_path / "temp.tiff" | ||||||
|     im.save(out, tiffinfo=info, compression="raw") |     im.save(out, tiffinfo=info, compression="raw") | ||||||
| 
 | 
 | ||||||
|     with Image.open(out) as reloaded: |     with Image.open(out) as reloaded: | ||||||
|  | @ -420,7 +420,7 @@ def test_ifd_signed_long(tmp_path: Path) -> None: | ||||||
| 
 | 
 | ||||||
|     info[37000] = -60000 |     info[37000] = -60000 | ||||||
| 
 | 
 | ||||||
|     out = str(tmp_path / "temp.tiff") |     out = tmp_path / "temp.tiff" | ||||||
|     im.save(out, tiffinfo=info, compression="raw") |     im.save(out, tiffinfo=info, compression="raw") | ||||||
| 
 | 
 | ||||||
|     with Image.open(out) as reloaded: |     with Image.open(out) as reloaded: | ||||||
|  | @ -446,7 +446,7 @@ def test_photoshop_info(tmp_path: Path) -> None: | ||||||
|     with Image.open("Tests/images/issue_2278.tif") as im: |     with Image.open("Tests/images/issue_2278.tif") as im: | ||||||
|         assert len(im.tag_v2[34377]) == 70 |         assert len(im.tag_v2[34377]) == 70 | ||||||
|         assert isinstance(im.tag_v2[34377], bytes) |         assert isinstance(im.tag_v2[34377], bytes) | ||||||
|         out = str(tmp_path / "temp.tiff") |         out = tmp_path / "temp.tiff" | ||||||
|         im.save(out) |         im.save(out) | ||||||
|     with Image.open(out) as reloaded: |     with Image.open(out) as reloaded: | ||||||
|         assert len(reloaded.tag_v2[34377]) == 70 |         assert len(reloaded.tag_v2[34377]) == 70 | ||||||
|  | @ -480,7 +480,7 @@ def test_tag_group_data() -> None: | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
| def test_empty_subifd(tmp_path: Path) -> None: | def test_empty_subifd(tmp_path: Path) -> None: | ||||||
|     out = str(tmp_path / "temp.jpg") |     out = tmp_path / "temp.jpg" | ||||||
| 
 | 
 | ||||||
|     im = hopper() |     im = hopper() | ||||||
|     exif = im.getexif() |     exif = im.getexif() | ||||||
|  |  | ||||||
|  | @ -42,7 +42,7 @@ def test_write_lossless_rgb(tmp_path: Path) -> None: | ||||||
|     Does it have the bits we expect? |     Does it have the bits we expect? | ||||||
|     """ |     """ | ||||||
| 
 | 
 | ||||||
|     temp_file = str(tmp_path / "temp.webp") |     temp_file = tmp_path / "temp.webp" | ||||||
|     # temp_file = "temp.webp" |     # temp_file = "temp.webp" | ||||||
| 
 | 
 | ||||||
|     pil_image = hopper("RGBA") |     pil_image = hopper("RGBA") | ||||||
|  | @ -71,7 +71,7 @@ def test_write_rgba(tmp_path: Path) -> None: | ||||||
|     Does it have the bits we expect? |     Does it have the bits we expect? | ||||||
|     """ |     """ | ||||||
| 
 | 
 | ||||||
|     temp_file = str(tmp_path / "temp.webp") |     temp_file = tmp_path / "temp.webp" | ||||||
| 
 | 
 | ||||||
|     pil_image = Image.new("RGBA", (10, 10), (255, 0, 0, 20)) |     pil_image = Image.new("RGBA", (10, 10), (255, 0, 0, 20)) | ||||||
|     pil_image.save(temp_file) |     pil_image.save(temp_file) | ||||||
|  | @ -104,7 +104,7 @@ def test_keep_rgb_values_when_transparent(tmp_path: Path) -> None: | ||||||
|     half_transparent_image.putalpha(new_alpha) |     half_transparent_image.putalpha(new_alpha) | ||||||
| 
 | 
 | ||||||
|     # save with transparent area preserved |     # save with transparent area preserved | ||||||
|     temp_file = str(tmp_path / "temp.webp") |     temp_file = tmp_path / "temp.webp" | ||||||
|     half_transparent_image.save(temp_file, exact=True, lossless=True) |     half_transparent_image.save(temp_file, exact=True, lossless=True) | ||||||
| 
 | 
 | ||||||
|     with Image.open(temp_file) as reloaded: |     with Image.open(temp_file) as reloaded: | ||||||
|  | @ -123,7 +123,7 @@ def test_write_unsupported_mode_PA(tmp_path: Path) -> None: | ||||||
|     should work, and be similar to the original file. |     should work, and be similar to the original file. | ||||||
|     """ |     """ | ||||||
| 
 | 
 | ||||||
|     temp_file = str(tmp_path / "temp.webp") |     temp_file = tmp_path / "temp.webp" | ||||||
|     file_path = "Tests/images/transparent.gif" |     file_path = "Tests/images/transparent.gif" | ||||||
|     with Image.open(file_path) as im: |     with Image.open(file_path) as im: | ||||||
|         im.save(temp_file) |         im.save(temp_file) | ||||||
|  | @ -142,10 +142,10 @@ def test_write_unsupported_mode_PA(tmp_path: Path) -> None: | ||||||
| 
 | 
 | ||||||
| def test_alpha_quality(tmp_path: Path) -> None: | def test_alpha_quality(tmp_path: Path) -> None: | ||||||
|     with Image.open("Tests/images/transparent.png") as im: |     with Image.open("Tests/images/transparent.png") as im: | ||||||
|         out = str(tmp_path / "temp.webp") |         out = tmp_path / "temp.webp" | ||||||
|         im.save(out) |         im.save(out) | ||||||
| 
 | 
 | ||||||
|         out_quality = str(tmp_path / "quality.webp") |         out_quality = tmp_path / "quality.webp" | ||||||
|         im.save(out_quality, alpha_quality=50) |         im.save(out_quality, alpha_quality=50) | ||||||
|         with Image.open(out) as reloaded: |         with Image.open(out) as reloaded: | ||||||
|             with Image.open(out_quality) as reloaded_quality: |             with Image.open(out_quality) as reloaded_quality: | ||||||
|  |  | ||||||
|  | @ -39,7 +39,7 @@ def test_write_animation_L(tmp_path: Path) -> None: | ||||||
|     with Image.open("Tests/images/iss634.gif") as orig: |     with Image.open("Tests/images/iss634.gif") as orig: | ||||||
|         assert orig.n_frames > 1 |         assert orig.n_frames > 1 | ||||||
| 
 | 
 | ||||||
|         temp_file = str(tmp_path / "temp.webp") |         temp_file = tmp_path / "temp.webp" | ||||||
|         orig.save(temp_file, save_all=True) |         orig.save(temp_file, save_all=True) | ||||||
|         with Image.open(temp_file) as im: |         with Image.open(temp_file) as im: | ||||||
|             assert im.n_frames == orig.n_frames |             assert im.n_frames == orig.n_frames | ||||||
|  | @ -67,7 +67,7 @@ def test_write_animation_RGB(tmp_path: Path) -> None: | ||||||
|     are visually similar to the originals. |     are visually similar to the originals. | ||||||
|     """ |     """ | ||||||
| 
 | 
 | ||||||
|     def check(temp_file: str) -> None: |     def check(temp_file: Path) -> None: | ||||||
|         with Image.open(temp_file) as im: |         with Image.open(temp_file) as im: | ||||||
|             assert im.n_frames == 2 |             assert im.n_frames == 2 | ||||||
| 
 | 
 | ||||||
|  | @ -87,7 +87,7 @@ def test_write_animation_RGB(tmp_path: Path) -> None: | ||||||
| 
 | 
 | ||||||
|     with Image.open("Tests/images/anim_frame1.webp") as frame1: |     with Image.open("Tests/images/anim_frame1.webp") as frame1: | ||||||
|         with Image.open("Tests/images/anim_frame2.webp") as frame2: |         with Image.open("Tests/images/anim_frame2.webp") as frame2: | ||||||
|             temp_file1 = str(tmp_path / "temp.webp") |             temp_file1 = tmp_path / "temp.webp" | ||||||
|             frame1.copy().save( |             frame1.copy().save( | ||||||
|                 temp_file1, save_all=True, append_images=[frame2], lossless=True |                 temp_file1, save_all=True, append_images=[frame2], lossless=True | ||||||
|             ) |             ) | ||||||
|  | @ -99,7 +99,7 @@ def test_write_animation_RGB(tmp_path: Path) -> None: | ||||||
|             ) -> Generator[Image.Image, None, None]: |             ) -> Generator[Image.Image, None, None]: | ||||||
|                 yield from ims |                 yield from ims | ||||||
| 
 | 
 | ||||||
|             temp_file2 = str(tmp_path / "temp_generator.webp") |             temp_file2 = tmp_path / "temp_generator.webp" | ||||||
|             frame1.copy().save( |             frame1.copy().save( | ||||||
|                 temp_file2, |                 temp_file2, | ||||||
|                 save_all=True, |                 save_all=True, | ||||||
|  | @ -116,7 +116,7 @@ def test_timestamp_and_duration(tmp_path: Path) -> None: | ||||||
|     """ |     """ | ||||||
| 
 | 
 | ||||||
|     durations = [0, 10, 20, 30, 40] |     durations = [0, 10, 20, 30, 40] | ||||||
|     temp_file = str(tmp_path / "temp.webp") |     temp_file = tmp_path / "temp.webp" | ||||||
|     with Image.open("Tests/images/anim_frame1.webp") as frame1: |     with Image.open("Tests/images/anim_frame1.webp") as frame1: | ||||||
|         with Image.open("Tests/images/anim_frame2.webp") as frame2: |         with Image.open("Tests/images/anim_frame2.webp") as frame2: | ||||||
|             frame1.save( |             frame1.save( | ||||||
|  | @ -141,7 +141,7 @@ def test_timestamp_and_duration(tmp_path: Path) -> None: | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
| def test_float_duration(tmp_path: Path) -> None: | def test_float_duration(tmp_path: Path) -> None: | ||||||
|     temp_file = str(tmp_path / "temp.webp") |     temp_file = tmp_path / "temp.webp" | ||||||
|     with Image.open("Tests/images/iss634.apng") as im: |     with Image.open("Tests/images/iss634.apng") as im: | ||||||
|         assert im.info["duration"] == 70.0 |         assert im.info["duration"] == 70.0 | ||||||
| 
 | 
 | ||||||
|  | @ -159,7 +159,7 @@ def test_seeking(tmp_path: Path) -> None: | ||||||
|     """ |     """ | ||||||
| 
 | 
 | ||||||
|     dur = 33 |     dur = 33 | ||||||
|     temp_file = str(tmp_path / "temp.webp") |     temp_file = tmp_path / "temp.webp" | ||||||
|     with Image.open("Tests/images/anim_frame1.webp") as frame1: |     with Image.open("Tests/images/anim_frame1.webp") as frame1: | ||||||
|         with Image.open("Tests/images/anim_frame2.webp") as frame2: |         with Image.open("Tests/images/anim_frame2.webp") as frame2: | ||||||
|             frame1.save( |             frame1.save( | ||||||
|  | @ -196,10 +196,10 @@ def test_alpha_quality(tmp_path: Path) -> None: | ||||||
|     with Image.open("Tests/images/transparent.png") as im: |     with Image.open("Tests/images/transparent.png") as im: | ||||||
|         first_frame = Image.new("L", im.size) |         first_frame = Image.new("L", im.size) | ||||||
| 
 | 
 | ||||||
|         out = str(tmp_path / "temp.webp") |         out = tmp_path / "temp.webp" | ||||||
|         first_frame.save(out, save_all=True, append_images=[im]) |         first_frame.save(out, save_all=True, append_images=[im]) | ||||||
| 
 | 
 | ||||||
|         out_quality = str(tmp_path / "quality.webp") |         out_quality = tmp_path / "quality.webp" | ||||||
|         first_frame.save( |         first_frame.save( | ||||||
|             out_quality, save_all=True, append_images=[im], alpha_quality=50 |             out_quality, save_all=True, append_images=[im], alpha_quality=50 | ||||||
|         ) |         ) | ||||||
|  |  | ||||||
|  | @ -13,7 +13,7 @@ RGB_MODE = "RGB" | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
| def test_write_lossless_rgb(tmp_path: Path) -> None: | def test_write_lossless_rgb(tmp_path: Path) -> None: | ||||||
|     temp_file = str(tmp_path / "temp.webp") |     temp_file = tmp_path / "temp.webp" | ||||||
| 
 | 
 | ||||||
|     hopper(RGB_MODE).save(temp_file, lossless=True) |     hopper(RGB_MODE).save(temp_file, lossless=True) | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
|  | @ -146,7 +146,7 @@ def test_write_animated_metadata(tmp_path: Path) -> None: | ||||||
|     exif_data = b"<exif_data>" |     exif_data = b"<exif_data>" | ||||||
|     xmp_data = b"<xmp_data>" |     xmp_data = b"<xmp_data>" | ||||||
| 
 | 
 | ||||||
|     temp_file = str(tmp_path / "temp.webp") |     temp_file = tmp_path / "temp.webp" | ||||||
|     with Image.open("Tests/images/anim_frame1.webp") as frame1: |     with Image.open("Tests/images/anim_frame1.webp") as frame1: | ||||||
|         with Image.open("Tests/images/anim_frame2.webp") as frame2: |         with Image.open("Tests/images/anim_frame2.webp") as frame2: | ||||||
|             frame1.save( |             frame1.save( | ||||||
|  |  | ||||||
|  | @ -59,7 +59,7 @@ def test_register_handler(tmp_path: Path) -> None: | ||||||
|     WmfImagePlugin.register_handler(handler) |     WmfImagePlugin.register_handler(handler) | ||||||
| 
 | 
 | ||||||
|     im = hopper() |     im = hopper() | ||||||
|     tmpfile = str(tmp_path / "temp.wmf") |     tmpfile = tmp_path / "temp.wmf" | ||||||
|     im.save(tmpfile) |     im.save(tmpfile) | ||||||
|     assert handler.methodCalled |     assert handler.methodCalled | ||||||
| 
 | 
 | ||||||
|  | @ -93,6 +93,6 @@ def test_load_set_dpi() -> None: | ||||||
| def test_save(ext: str, tmp_path: Path) -> None: | def test_save(ext: str, tmp_path: Path) -> None: | ||||||
|     im = hopper() |     im = hopper() | ||||||
| 
 | 
 | ||||||
|     tmpfile = str(tmp_path / ("temp" + ext)) |     tmpfile = tmp_path / ("temp" + ext) | ||||||
|     with pytest.raises(OSError): |     with pytest.raises(OSError): | ||||||
|         im.save(tmpfile) |         im.save(tmpfile) | ||||||
|  |  | ||||||
|  | @ -73,7 +73,7 @@ def test_invalid_file() -> None: | ||||||
| 
 | 
 | ||||||
| def test_save_wrong_mode(tmp_path: Path) -> None: | def test_save_wrong_mode(tmp_path: Path) -> None: | ||||||
|     im = hopper() |     im = hopper() | ||||||
|     out = str(tmp_path / "temp.xbm") |     out = tmp_path / "temp.xbm" | ||||||
| 
 | 
 | ||||||
|     with pytest.raises(OSError): |     with pytest.raises(OSError): | ||||||
|         im.save(out) |         im.save(out) | ||||||
|  | @ -81,7 +81,7 @@ def test_save_wrong_mode(tmp_path: Path) -> None: | ||||||
| 
 | 
 | ||||||
| def test_hotspot(tmp_path: Path) -> None: | def test_hotspot(tmp_path: Path) -> None: | ||||||
|     im = hopper("1") |     im = hopper("1") | ||||||
|     out = str(tmp_path / "temp.xbm") |     out = tmp_path / "temp.xbm" | ||||||
| 
 | 
 | ||||||
|     hotspot = (0, 7) |     hotspot = (0, 7) | ||||||
|     im.save(out, hotspot=hotspot) |     im.save(out, hotspot=hotspot) | ||||||
|  |  | ||||||
|  | @ -175,6 +175,13 @@ class TestImage: | ||||||
|             with Image.open(io.StringIO()):  # type: ignore[arg-type] |             with Image.open(io.StringIO()):  # type: ignore[arg-type] | ||||||
|                 pass |                 pass | ||||||
| 
 | 
 | ||||||
|  |     def test_string(self, tmp_path: Path) -> None: | ||||||
|  |         out = str(tmp_path / "temp.png") | ||||||
|  |         im = hopper() | ||||||
|  |         im.save(out) | ||||||
|  |         with Image.open(out) as reloaded: | ||||||
|  |             assert_image_equal(im, reloaded) | ||||||
|  | 
 | ||||||
|     def test_pathlib(self, tmp_path: Path) -> None: |     def test_pathlib(self, tmp_path: Path) -> None: | ||||||
|         with Image.open(Path("Tests/images/multipage-mmap.tiff")) as im: |         with Image.open(Path("Tests/images/multipage-mmap.tiff")) as im: | ||||||
|             assert im.mode == "P" |             assert im.mode == "P" | ||||||
|  | @ -187,14 +194,13 @@ class TestImage: | ||||||
|             for ext in (".jpg", ".jp2"): |             for ext in (".jpg", ".jp2"): | ||||||
|                 if ext == ".jp2" and not features.check_codec("jpg_2000"): |                 if ext == ".jp2" and not features.check_codec("jpg_2000"): | ||||||
|                     pytest.skip("jpg_2000 not available") |                     pytest.skip("jpg_2000 not available") | ||||||
|                 temp_file = str(tmp_path / ("temp." + ext)) |                 im.save(tmp_path / ("temp." + ext)) | ||||||
|                 im.save(Path(temp_file)) |  | ||||||
| 
 | 
 | ||||||
|     def test_fp_name(self, tmp_path: Path) -> None: |     def test_fp_name(self, tmp_path: Path) -> None: | ||||||
|         temp_file = str(tmp_path / "temp.jpg") |         temp_file = tmp_path / "temp.jpg" | ||||||
| 
 | 
 | ||||||
|         class FP(io.BytesIO): |         class FP(io.BytesIO): | ||||||
|             name: str |             name: Path | ||||||
| 
 | 
 | ||||||
|             if sys.version_info >= (3, 12): |             if sys.version_info >= (3, 12): | ||||||
|                 from collections.abc import Buffer |                 from collections.abc import Buffer | ||||||
|  | @ -225,7 +231,7 @@ class TestImage: | ||||||
| 
 | 
 | ||||||
|     def test_unknown_extension(self, tmp_path: Path) -> None: |     def test_unknown_extension(self, tmp_path: Path) -> None: | ||||||
|         im = hopper() |         im = hopper() | ||||||
|         temp_file = str(tmp_path / "temp.unknown") |         temp_file = tmp_path / "temp.unknown" | ||||||
|         with pytest.raises(ValueError): |         with pytest.raises(ValueError): | ||||||
|             im.save(temp_file) |             im.save(temp_file) | ||||||
| 
 | 
 | ||||||
|  | @ -245,7 +251,7 @@ class TestImage: | ||||||
|         reason="Test requires opening an mmaped file for writing", |         reason="Test requires opening an mmaped file for writing", | ||||||
|     ) |     ) | ||||||
|     def test_readonly_save(self, tmp_path: Path) -> None: |     def test_readonly_save(self, tmp_path: Path) -> None: | ||||||
|         temp_file = str(tmp_path / "temp.bmp") |         temp_file = tmp_path / "temp.bmp" | ||||||
|         shutil.copy("Tests/images/rgb32bf-rgba.bmp", temp_file) |         shutil.copy("Tests/images/rgb32bf-rgba.bmp", temp_file) | ||||||
| 
 | 
 | ||||||
|         with Image.open(temp_file) as im: |         with Image.open(temp_file) as im: | ||||||
|  | @ -728,7 +734,7 @@ class TestImage: | ||||||
|         # https://github.com/python-pillow/Pillow/issues/835 |         # https://github.com/python-pillow/Pillow/issues/835 | ||||||
|         # Arrange |         # Arrange | ||||||
|         test_file = "Tests/images/hopper.png" |         test_file = "Tests/images/hopper.png" | ||||||
|         temp_file = str(tmp_path / "temp.jpg") |         temp_file = tmp_path / "temp.jpg" | ||||||
| 
 | 
 | ||||||
|         # Act/Assert |         # Act/Assert | ||||||
|         with Image.open(test_file) as im: |         with Image.open(test_file) as im: | ||||||
|  | @ -738,7 +744,7 @@ class TestImage: | ||||||
|                 im.save(temp_file) |                 im.save(temp_file) | ||||||
| 
 | 
 | ||||||
|     def test_no_new_file_on_error(self, tmp_path: Path) -> None: |     def test_no_new_file_on_error(self, tmp_path: Path) -> None: | ||||||
|         temp_file = str(tmp_path / "temp.jpg") |         temp_file = tmp_path / "temp.jpg" | ||||||
| 
 | 
 | ||||||
|         im = Image.new("RGB", (0, 0)) |         im = Image.new("RGB", (0, 0)) | ||||||
|         with pytest.raises(ValueError): |         with pytest.raises(ValueError): | ||||||
|  | @ -805,7 +811,7 @@ class TestImage: | ||||||
|             assert exif[296] == 2 |             assert exif[296] == 2 | ||||||
|             assert exif[11] == "gThumb 3.0.1" |             assert exif[11] == "gThumb 3.0.1" | ||||||
| 
 | 
 | ||||||
|             out = str(tmp_path / "temp.jpg") |             out = tmp_path / "temp.jpg" | ||||||
|             exif[258] = 8 |             exif[258] = 8 | ||||||
|             del exif[274] |             del exif[274] | ||||||
|             del exif[282] |             del exif[282] | ||||||
|  | @ -827,7 +833,7 @@ class TestImage: | ||||||
|             assert exif[274] == 1 |             assert exif[274] == 1 | ||||||
|             assert exif[305] == "Adobe Photoshop CC 2017 (Macintosh)" |             assert exif[305] == "Adobe Photoshop CC 2017 (Macintosh)" | ||||||
| 
 | 
 | ||||||
|             out = str(tmp_path / "temp.jpg") |             out = tmp_path / "temp.jpg" | ||||||
|             exif[258] = 8 |             exif[258] = 8 | ||||||
|             del exif[306] |             del exif[306] | ||||||
|             exif[274] = 455 |             exif[274] = 455 | ||||||
|  | @ -846,7 +852,7 @@ class TestImage: | ||||||
|             exif = im.getexif() |             exif = im.getexif() | ||||||
|             assert exif == {} |             assert exif == {} | ||||||
| 
 | 
 | ||||||
|             out = str(tmp_path / "temp.webp") |             out = tmp_path / "temp.webp" | ||||||
|             exif[258] = 8 |             exif[258] = 8 | ||||||
|             exif[40963] = 455 |             exif[40963] = 455 | ||||||
|             exif[305] = "Pillow test" |             exif[305] = "Pillow test" | ||||||
|  | @ -868,7 +874,7 @@ class TestImage: | ||||||
|             exif = im.getexif() |             exif = im.getexif() | ||||||
|             assert exif == {274: 1} |             assert exif == {274: 1} | ||||||
| 
 | 
 | ||||||
|             out = str(tmp_path / "temp.png") |             out = tmp_path / "temp.png" | ||||||
|             exif[258] = 8 |             exif[258] = 8 | ||||||
|             del exif[274] |             del exif[274] | ||||||
|             exif[40963] = 455 |             exif[40963] = 455 | ||||||
|  |  | ||||||
|  | @ -118,7 +118,7 @@ def test_trns_p(tmp_path: Path) -> None: | ||||||
|     im = hopper("P") |     im = hopper("P") | ||||||
|     im.info["transparency"] = 0 |     im.info["transparency"] = 0 | ||||||
| 
 | 
 | ||||||
|     f = str(tmp_path / "temp.png") |     f = tmp_path / "temp.png" | ||||||
| 
 | 
 | ||||||
|     im_l = im.convert("L") |     im_l = im.convert("L") | ||||||
|     assert im_l.info["transparency"] == 0 |     assert im_l.info["transparency"] == 0 | ||||||
|  | @ -154,7 +154,7 @@ def test_trns_l(tmp_path: Path) -> None: | ||||||
|     im = hopper("L") |     im = hopper("L") | ||||||
|     im.info["transparency"] = 128 |     im.info["transparency"] = 128 | ||||||
| 
 | 
 | ||||||
|     f = str(tmp_path / "temp.png") |     f = tmp_path / "temp.png" | ||||||
| 
 | 
 | ||||||
|     im_la = im.convert("LA") |     im_la = im.convert("LA") | ||||||
|     assert "transparency" not in im_la.info |     assert "transparency" not in im_la.info | ||||||
|  | @ -177,7 +177,7 @@ def test_trns_RGB(tmp_path: Path) -> None: | ||||||
|     im = hopper("RGB") |     im = hopper("RGB") | ||||||
|     im.info["transparency"] = im.getpixel((0, 0)) |     im.info["transparency"] = im.getpixel((0, 0)) | ||||||
| 
 | 
 | ||||||
|     f = str(tmp_path / "temp.png") |     f = tmp_path / "temp.png" | ||||||
| 
 | 
 | ||||||
|     im_l = im.convert("L") |     im_l = im.convert("L") | ||||||
|     assert im_l.info["transparency"] == im_l.getpixel((0, 0))  # undone |     assert im_l.info["transparency"] == im_l.getpixel((0, 0))  # undone | ||||||
|  |  | ||||||
|  | @ -171,7 +171,7 @@ class TestImagingCoreResize: | ||||||
|         # platforms. So if a future Pillow change requires that the test file |         # platforms. So if a future Pillow change requires that the test file | ||||||
|         # be updated, that is okay. |         # be updated, that is okay. | ||||||
|         im = hopper().resize((64, 64)) |         im = hopper().resize((64, 64)) | ||||||
|         temp_file = str(tmp_path / "temp.gif") |         temp_file = tmp_path / "temp.gif" | ||||||
|         im.save(temp_file) |         im.save(temp_file) | ||||||
| 
 | 
 | ||||||
|         with Image.open(temp_file) as reloaded: |         with Image.open(temp_file) as reloaded: | ||||||
|  |  | ||||||
|  | @ -45,9 +45,9 @@ def test_split_merge(mode: str) -> None: | ||||||
| 
 | 
 | ||||||
| def test_split_open(tmp_path: Path) -> None: | def test_split_open(tmp_path: Path) -> None: | ||||||
|     if features.check("zlib"): |     if features.check("zlib"): | ||||||
|         test_file = str(tmp_path / "temp.png") |         test_file = tmp_path / "temp.png" | ||||||
|     else: |     else: | ||||||
|         test_file = str(tmp_path / "temp.pcx") |         test_file = tmp_path / "temp.pcx" | ||||||
| 
 | 
 | ||||||
|     def split_open(mode: str) -> int: |     def split_open(mode: str) -> int: | ||||||
|         hopper(mode).save(test_file) |         hopper(mode).save(test_file) | ||||||
|  |  | ||||||
|  | @ -124,7 +124,7 @@ def test_render_equal(layout_engine: ImageFont.Layout) -> None: | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
| def test_non_ascii_path(tmp_path: Path, layout_engine: ImageFont.Layout) -> None: | def test_non_ascii_path(tmp_path: Path, layout_engine: ImageFont.Layout) -> None: | ||||||
|     tempfile = str(tmp_path / ("temp_" + chr(128) + ".ttf")) |     tempfile = tmp_path / ("temp_" + chr(128) + ".ttf") | ||||||
|     try: |     try: | ||||||
|         shutil.copy(FONT_PATH, tempfile) |         shutil.copy(FONT_PATH, tempfile) | ||||||
|     except UnicodeEncodeError: |     except UnicodeEncodeError: | ||||||
|  |  | ||||||
|  | @ -10,7 +10,7 @@ from .helper import assert_image_equal, hopper, skip_unless_feature | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
| def test_sanity(tmp_path: Path) -> None: | def test_sanity(tmp_path: Path) -> None: | ||||||
|     test_file = str(tmp_path / "temp.im") |     test_file = tmp_path / "temp.im" | ||||||
| 
 | 
 | ||||||
|     im = hopper("RGB") |     im = hopper("RGB") | ||||||
|     im.save(test_file) |     im.save(test_file) | ||||||
|  |  | ||||||
|  | @ -88,7 +88,7 @@ if is_win32(): | ||||||
|     def test_pointer(tmp_path: Path) -> None: |     def test_pointer(tmp_path: Path) -> None: | ||||||
|         im = hopper() |         im = hopper() | ||||||
|         (width, height) = im.size |         (width, height) = im.size | ||||||
|         opath = str(tmp_path / "temp.png") |         opath = tmp_path / "temp.png" | ||||||
|         imdib = ImageWin.Dib(im) |         imdib = ImageWin.Dib(im) | ||||||
| 
 | 
 | ||||||
|         hdr = BITMAPINFOHEADER() |         hdr = BITMAPINFOHEADER() | ||||||
|  |  | ||||||
|  | @ -44,7 +44,7 @@ def test_basic(tmp_path: Path, mode: str) -> None: | ||||||
|     im_out = im_in.transform((w, h), Image.Transform.EXTENT, (0, 0, w, h)) |     im_out = im_in.transform((w, h), Image.Transform.EXTENT, (0, 0, w, h)) | ||||||
|     verify(im_out)  # transform |     verify(im_out)  # transform | ||||||
| 
 | 
 | ||||||
|     filename = str(tmp_path / "temp.im") |     filename = tmp_path / "temp.im" | ||||||
|     im_in.save(filename) |     im_in.save(filename) | ||||||
| 
 | 
 | ||||||
|     with Image.open(filename) as im_out: |     with Image.open(filename) as im_out: | ||||||
|  |  | ||||||
|  | @ -18,7 +18,7 @@ def helper_pickle_file( | ||||||
| ) -> None: | ) -> None: | ||||||
|     # Arrange |     # Arrange | ||||||
|     with Image.open(test_file) as im: |     with Image.open(test_file) as im: | ||||||
|         filename = str(tmp_path / "temp.pkl") |         filename = tmp_path / "temp.pkl" | ||||||
|         if mode: |         if mode: | ||||||
|             im = im.convert(mode) |             im = im.convert(mode) | ||||||
| 
 | 
 | ||||||
|  | @ -87,7 +87,7 @@ def test_pickle_jpeg() -> None: | ||||||
| 
 | 
 | ||||||
| def test_pickle_la_mode_with_palette(tmp_path: Path) -> None: | def test_pickle_la_mode_with_palette(tmp_path: Path) -> None: | ||||||
|     # Arrange |     # Arrange | ||||||
|     filename = str(tmp_path / "temp.pkl") |     filename = tmp_path / "temp.pkl" | ||||||
|     with Image.open("Tests/images/hopper.jpg") as im: |     with Image.open("Tests/images/hopper.jpg") as im: | ||||||
|         im = im.convert("PA") |         im = im.convert("PA") | ||||||
| 
 | 
 | ||||||
|  | @ -151,7 +151,7 @@ def test_pickle_font_string(protocol: int) -> None: | ||||||
| def test_pickle_font_file(tmp_path: Path, protocol: int) -> None: | def test_pickle_font_file(tmp_path: Path, protocol: int) -> None: | ||||||
|     # Arrange |     # Arrange | ||||||
|     font = ImageFont.truetype(FONT_PATH, FONT_SIZE) |     font = ImageFont.truetype(FONT_PATH, FONT_SIZE) | ||||||
|     filename = str(tmp_path / "temp.pkl") |     filename = tmp_path / "temp.pkl" | ||||||
| 
 | 
 | ||||||
|     # Act: roundtrip |     # Act: roundtrip | ||||||
|     with open(filename, "wb") as f: |     with open(filename, "wb") as f: | ||||||
|  |  | ||||||
|  | @ -35,7 +35,7 @@ def test_draw_postscript(tmp_path: Path) -> None: | ||||||
|     # https://pillow.readthedocs.io/en/latest/handbook/tutorial.html#drawing-postscript |     # https://pillow.readthedocs.io/en/latest/handbook/tutorial.html#drawing-postscript | ||||||
| 
 | 
 | ||||||
|     # Arrange |     # Arrange | ||||||
|     tempfile = str(tmp_path / "temp.ps") |     tempfile = tmp_path / "temp.ps" | ||||||
|     with open(tempfile, "wb") as fp: |     with open(tempfile, "wb") as fp: | ||||||
|         # Act |         # Act | ||||||
|         ps = PSDraw.PSDraw(fp) |         ps = PSDraw.PSDraw(fp) | ||||||
|  |  | ||||||
|  | @ -35,7 +35,7 @@ class TestShellInjection: | ||||||
|     @pytest.mark.skipif(not djpeg_available(), reason="djpeg not available") |     @pytest.mark.skipif(not djpeg_available(), reason="djpeg not available") | ||||||
|     def test_load_djpeg_filename(self, tmp_path: Path) -> None: |     def test_load_djpeg_filename(self, tmp_path: Path) -> None: | ||||||
|         for filename in test_filenames: |         for filename in test_filenames: | ||||||
|             src_file = str(tmp_path / filename) |             src_file = tmp_path / filename | ||||||
|             shutil.copy(TEST_JPG, src_file) |             shutil.copy(TEST_JPG, src_file) | ||||||
| 
 | 
 | ||||||
|             with Image.open(src_file) as im: |             with Image.open(src_file) as im: | ||||||
|  |  | ||||||
|  | @ -65,7 +65,7 @@ def test_ifd_rational_save( | ||||||
|     monkeypatch: pytest.MonkeyPatch, tmp_path: Path, libtiff: bool |     monkeypatch: pytest.MonkeyPatch, tmp_path: Path, libtiff: bool | ||||||
| ) -> None: | ) -> None: | ||||||
|     im = hopper() |     im = hopper() | ||||||
|     out = str(tmp_path / "temp.tiff") |     out = tmp_path / "temp.tiff" | ||||||
|     res = IFDRational(301, 1) |     res = IFDRational(301, 1) | ||||||
| 
 | 
 | ||||||
|     monkeypatch.setattr(TiffImagePlugin, "WRITE_LIBTIFF", libtiff) |     monkeypatch.setattr(TiffImagePlugin, "WRITE_LIBTIFF", libtiff) | ||||||
|  |  | ||||||
|  | @ -93,6 +93,12 @@ DXT1 and DXT5 pixel formats can be read, only in ``RGBA`` mode. | ||||||
|    in ``P`` mode. |    in ``P`` mode. | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
|  | .. versionadded:: 11.2.0 | ||||||
|  |    DXT1, DXT3, DXT5, BC2, BC3 and BC5 pixel formats can be saved:: | ||||||
|  | 
 | ||||||
|  |        im.save(out, pixel_format="DXT1") | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
| DIB | DIB | ||||||
| ^^^ | ^^^ | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
|  | @ -66,6 +66,14 @@ libjpeg library, and what version of MozJPEG is being used:: | ||||||
|     features.check_feature("mozjpeg")  # True or False |     features.check_feature("mozjpeg")  # True or False | ||||||
|     features.version_feature("mozjpeg")  # "4.1.1" for example, or None |     features.version_feature("mozjpeg")  # "4.1.1" for example, or None | ||||||
| 
 | 
 | ||||||
|  | Saving compressed DDS images | ||||||
|  | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ | ||||||
|  | 
 | ||||||
|  | Compressed DDS images can now be saved using a ``pixel_format`` argument. DXT1, DXT3, | ||||||
|  | DXT5, BC2, BC3 and BC5 are supported:: | ||||||
|  | 
 | ||||||
|  |     im.save("out.dds", pixel_format="DXT1") | ||||||
|  | 
 | ||||||
| Other Changes | Other Changes | ||||||
| ============= | ============= | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
							
								
								
									
										1
									
								
								setup.py
									
									
									
									
									
								
							
							
						
						
									
										1
									
								
								setup.py
									
									
									
									
									
								
							|  | @ -68,6 +68,7 @@ _LIB_IMAGING = ( | ||||||
|     "Reduce", |     "Reduce", | ||||||
|     "Bands", |     "Bands", | ||||||
|     "BcnDecode", |     "BcnDecode", | ||||||
|  |     "BcnEncode", | ||||||
|     "BitDecode", |     "BitDecode", | ||||||
|     "Blend", |     "Blend", | ||||||
|     "Chops", |     "Chops", | ||||||
|  |  | ||||||
|  | @ -419,6 +419,14 @@ class DdsImageFile(ImageFile.ImageFile): | ||||||
|                     self._mode = "RGBA" |                     self._mode = "RGBA" | ||||||
|                     self.pixel_format = "BC1" |                     self.pixel_format = "BC1" | ||||||
|                     n = 1 |                     n = 1 | ||||||
|  |                 elif dxgi_format in (DXGI_FORMAT.BC2_TYPELESS, DXGI_FORMAT.BC2_UNORM): | ||||||
|  |                     self._mode = "RGBA" | ||||||
|  |                     self.pixel_format = "BC2" | ||||||
|  |                     n = 2 | ||||||
|  |                 elif dxgi_format in (DXGI_FORMAT.BC3_TYPELESS, DXGI_FORMAT.BC3_UNORM): | ||||||
|  |                     self._mode = "RGBA" | ||||||
|  |                     self.pixel_format = "BC3" | ||||||
|  |                     n = 3 | ||||||
|                 elif dxgi_format in (DXGI_FORMAT.BC4_TYPELESS, DXGI_FORMAT.BC4_UNORM): |                 elif dxgi_format in (DXGI_FORMAT.BC4_TYPELESS, DXGI_FORMAT.BC4_UNORM): | ||||||
|                     self._mode = "L" |                     self._mode = "L" | ||||||
|                     self.pixel_format = "BC4" |                     self.pixel_format = "BC4" | ||||||
|  | @ -518,17 +526,58 @@ def _save(im: Image.Image, fp: IO[bytes], filename: str | bytes) -> None: | ||||||
|         msg = f"cannot write mode {im.mode} as DDS" |         msg = f"cannot write mode {im.mode} as DDS" | ||||||
|         raise OSError(msg) |         raise OSError(msg) | ||||||
| 
 | 
 | ||||||
|  |     flags = DDSD.CAPS | DDSD.HEIGHT | DDSD.WIDTH | DDSD.PIXELFORMAT | ||||||
|  |     bitcount = len(im.getbands()) * 8 | ||||||
|  |     pixel_format = im.encoderinfo.get("pixel_format") | ||||||
|  |     args: tuple[int] | str | ||||||
|  |     if pixel_format: | ||||||
|  |         codec_name = "bcn" | ||||||
|  |         flags |= DDSD.LINEARSIZE | ||||||
|  |         pitch = (im.width + 3) * 4 | ||||||
|  |         rgba_mask = [0, 0, 0, 0] | ||||||
|  |         pixel_flags = DDPF.FOURCC | ||||||
|  |         if pixel_format == "DXT1": | ||||||
|  |             fourcc = D3DFMT.DXT1 | ||||||
|  |             args = (1,) | ||||||
|  |         elif pixel_format == "DXT3": | ||||||
|  |             fourcc = D3DFMT.DXT3 | ||||||
|  |             args = (2,) | ||||||
|  |         elif pixel_format == "DXT5": | ||||||
|  |             fourcc = D3DFMT.DXT5 | ||||||
|  |             args = (3,) | ||||||
|  |         else: | ||||||
|  |             fourcc = D3DFMT.DX10 | ||||||
|  |             if pixel_format == "BC2": | ||||||
|  |                 args = (2,) | ||||||
|  |                 dxgi_format = DXGI_FORMAT.BC2_TYPELESS | ||||||
|  |             elif pixel_format == "BC3": | ||||||
|  |                 args = (3,) | ||||||
|  |                 dxgi_format = DXGI_FORMAT.BC3_TYPELESS | ||||||
|  |             elif pixel_format == "BC5": | ||||||
|  |                 args = (5,) | ||||||
|  |                 dxgi_format = DXGI_FORMAT.BC5_TYPELESS | ||||||
|  |                 if im.mode != "RGB": | ||||||
|  |                     msg = "only RGB mode can be written as BC5" | ||||||
|  |                     raise OSError(msg) | ||||||
|  |             else: | ||||||
|  |                 msg = f"cannot write pixel format {pixel_format}" | ||||||
|  |                 raise OSError(msg) | ||||||
|  |     else: | ||||||
|  |         codec_name = "raw" | ||||||
|  |         flags |= DDSD.PITCH | ||||||
|  |         pitch = (im.width * bitcount + 7) // 8 | ||||||
|  | 
 | ||||||
|         alpha = im.mode[-1] == "A" |         alpha = im.mode[-1] == "A" | ||||||
|         if im.mode[0] == "L": |         if im.mode[0] == "L": | ||||||
|             pixel_flags = DDPF.LUMINANCE |             pixel_flags = DDPF.LUMINANCE | ||||||
|         rawmode = im.mode |             args = im.mode | ||||||
|             if alpha: |             if alpha: | ||||||
|                 rgba_mask = [0x000000FF, 0x000000FF, 0x000000FF] |                 rgba_mask = [0x000000FF, 0x000000FF, 0x000000FF] | ||||||
|             else: |             else: | ||||||
|                 rgba_mask = [0xFF000000, 0xFF000000, 0xFF000000] |                 rgba_mask = [0xFF000000, 0xFF000000, 0xFF000000] | ||||||
|         else: |         else: | ||||||
|             pixel_flags = DDPF.RGB |             pixel_flags = DDPF.RGB | ||||||
|         rawmode = im.mode[::-1] |             args = im.mode[::-1] | ||||||
|             rgba_mask = [0x00FF0000, 0x0000FF00, 0x000000FF] |             rgba_mask = [0x00FF0000, 0x0000FF00, 0x000000FF] | ||||||
| 
 | 
 | ||||||
|             if alpha: |             if alpha: | ||||||
|  | @ -538,10 +587,7 @@ def _save(im: Image.Image, fp: IO[bytes], filename: str | bytes) -> None: | ||||||
|             pixel_flags |= DDPF.ALPHAPIXELS |             pixel_flags |= DDPF.ALPHAPIXELS | ||||||
|         rgba_mask.append(0xFF000000 if alpha else 0) |         rgba_mask.append(0xFF000000 if alpha else 0) | ||||||
| 
 | 
 | ||||||
|     flags = DDSD.CAPS | DDSD.HEIGHT | DDSD.WIDTH | DDSD.PITCH | DDSD.PIXELFORMAT |         fourcc = D3DFMT.UNKNOWN | ||||||
|     bitcount = len(im.getbands()) * 8 |  | ||||||
|     pitch = (im.width * bitcount + 7) // 8 |  | ||||||
| 
 |  | ||||||
|     fp.write( |     fp.write( | ||||||
|         o32(DDS_MAGIC) |         o32(DDS_MAGIC) | ||||||
|         + struct.pack( |         + struct.pack( | ||||||
|  | @ -556,11 +602,16 @@ def _save(im: Image.Image, fp: IO[bytes], filename: str | bytes) -> None: | ||||||
|         ) |         ) | ||||||
|         + struct.pack("11I", *((0,) * 11))  # reserved |         + struct.pack("11I", *((0,) * 11))  # reserved | ||||||
|         # pfsize, pfflags, fourcc, bitcount |         # pfsize, pfflags, fourcc, bitcount | ||||||
|         + struct.pack("<4I", 32, pixel_flags, 0, bitcount) |         + struct.pack("<4I", 32, pixel_flags, fourcc, bitcount) | ||||||
|         + struct.pack("<4I", *rgba_mask)  # dwRGBABitMask |         + struct.pack("<4I", *rgba_mask)  # dwRGBABitMask | ||||||
|         + struct.pack("<5I", DDSCAPS.TEXTURE, 0, 0, 0, 0) |         + struct.pack("<5I", DDSCAPS.TEXTURE, 0, 0, 0, 0) | ||||||
|     ) |     ) | ||||||
|     ImageFile._save(im, fp, [ImageFile._Tile("raw", (0, 0) + im.size, 0, rawmode)]) |     if fourcc == D3DFMT.DX10: | ||||||
|  |         fp.write( | ||||||
|  |             # dxgi_format, 2D resource, misc, array size, straight alpha | ||||||
|  |             struct.pack("<5I", dxgi_format, 3, 0, 0, 1) | ||||||
|  |         ) | ||||||
|  |     ImageFile._save(im, fp, [ImageFile._Tile(codec_name, (0, 0) + im.size, 0, args)]) | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
| def _accept(prefix: bytes) -> bool: | def _accept(prefix: bytes) -> bool: | ||||||
|  |  | ||||||
|  | @ -18,8 +18,6 @@ from __future__ import annotations | ||||||
| import re | import re | ||||||
| from typing import IO | from typing import IO | ||||||
| 
 | 
 | ||||||
| from ._binary import o8 |  | ||||||
| 
 |  | ||||||
| 
 | 
 | ||||||
| class GimpPaletteFile: | class GimpPaletteFile: | ||||||
|     """File handler for GIMP's palette format.""" |     """File handler for GIMP's palette format.""" | ||||||
|  | @ -27,13 +25,12 @@ class GimpPaletteFile: | ||||||
|     rawmode = "RGB" |     rawmode = "RGB" | ||||||
| 
 | 
 | ||||||
|     def __init__(self, fp: IO[bytes]) -> None: |     def __init__(self, fp: IO[bytes]) -> None: | ||||||
|         palette = [o8(i) * 3 for i in range(256)] |  | ||||||
| 
 |  | ||||||
|         if not fp.readline().startswith(b"GIMP Palette"): |         if not fp.readline().startswith(b"GIMP Palette"): | ||||||
|             msg = "not a GIMP palette file" |             msg = "not a GIMP palette file" | ||||||
|             raise SyntaxError(msg) |             raise SyntaxError(msg) | ||||||
| 
 | 
 | ||||||
|         for i in range(256): |         palette: list[int] = [] | ||||||
|  |         for _ in range(256): | ||||||
|             s = fp.readline() |             s = fp.readline() | ||||||
|             if not s: |             if not s: | ||||||
|                 break |                 break | ||||||
|  | @ -45,14 +42,14 @@ class GimpPaletteFile: | ||||||
|                 msg = "bad palette file" |                 msg = "bad palette file" | ||||||
|                 raise SyntaxError(msg) |                 raise SyntaxError(msg) | ||||||
| 
 | 
 | ||||||
|             v = tuple(map(int, s.split()[:3])) |             v = s.split(maxsplit=3) | ||||||
|             if len(v) != 3: |             if len(v) < 3: | ||||||
|                 msg = "bad palette entry" |                 msg = "bad palette entry" | ||||||
|                 raise ValueError(msg) |                 raise ValueError(msg) | ||||||
| 
 | 
 | ||||||
|             palette[i] = o8(v[0]) + o8(v[1]) + o8(v[2]) |             palette += (int(v[i]) for i in range(3)) | ||||||
| 
 | 
 | ||||||
|         self.palette = b"".join(palette) |         self.palette = bytes(palette) | ||||||
| 
 | 
 | ||||||
|     def getpalette(self) -> tuple[bytes, str]: |     def getpalette(self) -> tuple[bytes, str]: | ||||||
|         return self.palette, self.rawmode |         return self.palette, self.rawmode | ||||||
|  |  | ||||||
|  | @ -1608,6 +1608,10 @@ class TiffImageFile(ImageFile.ImageFile): | ||||||
|                     raise ValueError(msg) |                     raise ValueError(msg) | ||||||
|                 w = tilewidth |                 w = tilewidth | ||||||
| 
 | 
 | ||||||
|  |             if w == xsize and h == ysize and self._planar_configuration != 2: | ||||||
|  |                 # Every tile covers the image. Only use the last offset | ||||||
|  |                 offsets = offsets[-1:] | ||||||
|  | 
 | ||||||
|             for offset in offsets: |             for offset in offsets: | ||||||
|                 if x + w > xsize: |                 if x + w > xsize: | ||||||
|                     stride = w * sum(bps_tuple) / 8  # bytes per line |                     stride = w * sum(bps_tuple) / 8  # bytes per line | ||||||
|  | @ -1630,11 +1634,11 @@ class TiffImageFile(ImageFile.ImageFile): | ||||||
|                         args, |                         args, | ||||||
|                     ) |                     ) | ||||||
|                 ) |                 ) | ||||||
|                 x = x + w |                 x += w | ||||||
|                 if x >= xsize: |                 if x >= xsize: | ||||||
|                     x, y = 0, y + h |                     x, y = 0, y + h | ||||||
|                     if y >= ysize: |                     if y >= ysize: | ||||||
|                         x = y = 0 |                         y = 0 | ||||||
|                         layer += 1 |                         layer += 1 | ||||||
|         else: |         else: | ||||||
|             logger.debug("- unsupported data organization") |             logger.debug("- unsupported data organization") | ||||||
|  |  | ||||||
|  | @ -240,7 +240,7 @@ def _save_all(im: Image.Image, fp: IO[bytes], filename: str | bytes) -> None: | ||||||
|     cur_idx = im.tell() |     cur_idx = im.tell() | ||||||
|     try: |     try: | ||||||
|         for i, ims in enumerate([im] + append_images): |         for i, ims in enumerate([im] + append_images): | ||||||
|             # Get # of frames in this image |             # Get number of frames in this image | ||||||
|             nfr = getattr(ims, "n_frames", 1) |             nfr = getattr(ims, "n_frames", 1) | ||||||
| 
 | 
 | ||||||
|             for idx in range(nfr): |             for idx in range(nfr): | ||||||
|  |  | ||||||
|  | @ -4041,6 +4041,8 @@ PyImaging_ZipDecoderNew(PyObject *self, PyObject *args); | ||||||
| 
 | 
 | ||||||
| /* Encoders (in encode.c) */ | /* Encoders (in encode.c) */ | ||||||
| extern PyObject * | extern PyObject * | ||||||
|  | PyImaging_BcnEncoderNew(PyObject *self, PyObject *args); | ||||||
|  | extern PyObject * | ||||||
| PyImaging_EpsEncoderNew(PyObject *self, PyObject *args); | PyImaging_EpsEncoderNew(PyObject *self, PyObject *args); | ||||||
| extern PyObject * | extern PyObject * | ||||||
| PyImaging_GifEncoderNew(PyObject *self, PyObject *args); | PyImaging_GifEncoderNew(PyObject *self, PyObject *args); | ||||||
|  | @ -4109,6 +4111,7 @@ static PyMethodDef functions[] = { | ||||||
| 
 | 
 | ||||||
|     /* Codecs */ |     /* Codecs */ | ||||||
|     {"bcn_decoder", (PyCFunction)PyImaging_BcnDecoderNew, METH_VARARGS}, |     {"bcn_decoder", (PyCFunction)PyImaging_BcnDecoderNew, METH_VARARGS}, | ||||||
|  |     {"bcn_encoder", (PyCFunction)PyImaging_BcnEncoderNew, METH_VARARGS}, | ||||||
|     {"bit_decoder", (PyCFunction)PyImaging_BitDecoderNew, METH_VARARGS}, |     {"bit_decoder", (PyCFunction)PyImaging_BitDecoderNew, METH_VARARGS}, | ||||||
|     {"eps_encoder", (PyCFunction)PyImaging_EpsEncoderNew, METH_VARARGS}, |     {"eps_encoder", (PyCFunction)PyImaging_EpsEncoderNew, METH_VARARGS}, | ||||||
|     {"fli_decoder", (PyCFunction)PyImaging_FliDecoderNew, METH_VARARGS}, |     {"fli_decoder", (PyCFunction)PyImaging_FliDecoderNew, METH_VARARGS}, | ||||||
|  |  | ||||||
							
								
								
									
										26
									
								
								src/encode.c
									
									
									
									
									
								
							
							
						
						
									
										26
									
								
								src/encode.c
									
									
									
									
									
								
							|  | @ -27,6 +27,7 @@ | ||||||
| 
 | 
 | ||||||
| #include "thirdparty/pythoncapi_compat.h" | #include "thirdparty/pythoncapi_compat.h" | ||||||
| #include "libImaging/Imaging.h" | #include "libImaging/Imaging.h" | ||||||
|  | #include "libImaging/Bcn.h" | ||||||
| #include "libImaging/Gif.h" | #include "libImaging/Gif.h" | ||||||
| 
 | 
 | ||||||
| #ifdef HAVE_UNISTD_H | #ifdef HAVE_UNISTD_H | ||||||
|  | @ -350,6 +351,31 @@ get_packer(ImagingEncoderObject *encoder, const char *mode, const char *rawmode) | ||||||
|     return 0; |     return 0; | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | /* -------------------------------------------------------------------- */ | ||||||
|  | /* BCN                                                                  */ | ||||||
|  | /* -------------------------------------------------------------------- */ | ||||||
|  | 
 | ||||||
|  | PyObject * | ||||||
|  | PyImaging_BcnEncoderNew(PyObject *self, PyObject *args) { | ||||||
|  |     ImagingEncoderObject *encoder; | ||||||
|  | 
 | ||||||
|  |     char *mode; | ||||||
|  |     int n; | ||||||
|  |     if (!PyArg_ParseTuple(args, "si", &mode, &n)) { | ||||||
|  |         return NULL; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     encoder = PyImaging_EncoderNew(0); | ||||||
|  |     if (encoder == NULL) { | ||||||
|  |         return NULL; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     encoder->encode = ImagingBcnEncode; | ||||||
|  |     encoder->state.state = n; | ||||||
|  | 
 | ||||||
|  |     return (PyObject *)encoder; | ||||||
|  | } | ||||||
|  | 
 | ||||||
| /* -------------------------------------------------------------------- */ | /* -------------------------------------------------------------------- */ | ||||||
| /* EPS                                                                  */ | /* EPS                                                                  */ | ||||||
| /* -------------------------------------------------------------------- */ | /* -------------------------------------------------------------------- */ | ||||||
|  |  | ||||||
							
								
								
									
										298
									
								
								src/libImaging/BcnEncode.c
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										298
									
								
								src/libImaging/BcnEncode.c
									
									
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,298 @@ | ||||||
|  | /*
 | ||||||
|  |  * The Python Imaging Library | ||||||
|  |  * | ||||||
|  |  * encoder for DXT1-compressed data | ||||||
|  |  * | ||||||
|  |  * Format documentation: | ||||||
|  |  *   https://web.archive.org/web/20170802060935/http://oss.sgi.com/projects/ogl-sample/registry/EXT/texture_compression_s3tc.txt
 | ||||||
|  |  * | ||||||
|  |  */ | ||||||
|  | 
 | ||||||
|  | #include "Imaging.h" | ||||||
|  | 
 | ||||||
|  | typedef struct { | ||||||
|  |     UINT8 color[3]; | ||||||
|  | } rgb; | ||||||
|  | 
 | ||||||
|  | typedef struct { | ||||||
|  |     UINT8 color[4]; | ||||||
|  | } rgba; | ||||||
|  | 
 | ||||||
|  | static rgb | ||||||
|  | decode_565(UINT16 x) { | ||||||
|  |     rgb item; | ||||||
|  |     int r, g, b; | ||||||
|  |     r = (x & 0xf800) >> 8; | ||||||
|  |     r |= r >> 5; | ||||||
|  |     item.color[0] = r; | ||||||
|  |     g = (x & 0x7e0) >> 3; | ||||||
|  |     g |= g >> 6; | ||||||
|  |     item.color[1] = g; | ||||||
|  |     b = (x & 0x1f) << 3; | ||||||
|  |     b |= b >> 5; | ||||||
|  |     item.color[2] = b; | ||||||
|  |     return item; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | static UINT16 | ||||||
|  | encode_565(rgba item) { | ||||||
|  |     UINT8 r, g, b; | ||||||
|  |     r = item.color[0] >> (8 - 5); | ||||||
|  |     g = item.color[1] >> (8 - 6); | ||||||
|  |     b = item.color[2] >> (8 - 5); | ||||||
|  |     return (r << (5 + 6)) | (g << 5) | b; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | static void | ||||||
|  | encode_bc1_color(Imaging im, ImagingCodecState state, UINT8 *dst, int separate_alpha) { | ||||||
|  |     int i, j, k; | ||||||
|  |     UINT16 color_min = 0, color_max = 0; | ||||||
|  |     rgb color_min_rgb, color_max_rgb; | ||||||
|  |     rgba block[16], *current_rgba; | ||||||
|  | 
 | ||||||
|  |     // Determine the min and max colors in this 4x4 block
 | ||||||
|  |     int first = 1; | ||||||
|  |     int transparency = 0; | ||||||
|  |     for (i = 0; i < 4; i++) { | ||||||
|  |         for (j = 0; j < 4; j++) { | ||||||
|  |             current_rgba = &block[i + j * 4]; | ||||||
|  | 
 | ||||||
|  |             int x = state->x + i * im->pixelsize; | ||||||
|  |             int y = state->y + j; | ||||||
|  |             if (x >= state->xsize * im->pixelsize || y >= state->ysize) { | ||||||
|  |                 // The 4x4 block extends past the edge of the image
 | ||||||
|  |                 for (k = 0; k < 3; k++) { | ||||||
|  |                     current_rgba->color[k] = 0; | ||||||
|  |                 } | ||||||
|  |                 continue; | ||||||
|  |             } | ||||||
|  | 
 | ||||||
|  |             for (k = 0; k < 3; k++) { | ||||||
|  |                 current_rgba->color[k] = | ||||||
|  |                     (UINT8)im->image[y][x + (im->pixelsize == 1 ? 0 : k)]; | ||||||
|  |             } | ||||||
|  |             if (separate_alpha) { | ||||||
|  |                 if ((UINT8)im->image[y][x + 3] == 0) { | ||||||
|  |                     current_rgba->color[3] = 0; | ||||||
|  |                     transparency = 1; | ||||||
|  |                     continue; | ||||||
|  |                 } else { | ||||||
|  |                     current_rgba->color[3] = 1; | ||||||
|  |                 } | ||||||
|  |             } | ||||||
|  | 
 | ||||||
|  |             UINT16 color = encode_565(*current_rgba); | ||||||
|  |             if (first || color < color_min) { | ||||||
|  |                 color_min = color; | ||||||
|  |             } | ||||||
|  |             if (first || color > color_max) { | ||||||
|  |                 color_max = color; | ||||||
|  |             } | ||||||
|  |             first = 0; | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     if (transparency) { | ||||||
|  |         *dst++ = color_min; | ||||||
|  |         *dst++ = color_min >> 8; | ||||||
|  |     } | ||||||
|  |     *dst++ = color_max; | ||||||
|  |     *dst++ = color_max >> 8; | ||||||
|  |     if (!transparency) { | ||||||
|  |         *dst++ = color_min; | ||||||
|  |         *dst++ = color_min >> 8; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     color_min_rgb = decode_565(color_min); | ||||||
|  |     color_max_rgb = decode_565(color_max); | ||||||
|  |     for (i = 0; i < 4; i++) { | ||||||
|  |         UINT8 l = 0; | ||||||
|  |         for (j = 3; j > -1; j--) { | ||||||
|  |             current_rgba = &block[i * 4 + j]; | ||||||
|  |             if (transparency && !current_rgba->color[3]) { | ||||||
|  |                 l |= 3 << (j * 2); | ||||||
|  |                 continue; | ||||||
|  |             } | ||||||
|  | 
 | ||||||
|  |             float distance = 0; | ||||||
|  |             int total = 0; | ||||||
|  |             for (k = 0; k < 3; k++) { | ||||||
|  |                 float denom = | ||||||
|  |                     (float)abs(color_max_rgb.color[k] - color_min_rgb.color[k]); | ||||||
|  |                 if (denom != 0) { | ||||||
|  |                     distance += | ||||||
|  |                         abs(current_rgba->color[k] - color_min_rgb.color[k]) / denom; | ||||||
|  |                     total += 1; | ||||||
|  |                 } | ||||||
|  |             } | ||||||
|  |             if (total == 0) { | ||||||
|  |                 continue; | ||||||
|  |             } | ||||||
|  |             if (transparency) { | ||||||
|  |                 distance *= 4 / total; | ||||||
|  |                 if (distance < 1) { | ||||||
|  |                     // color_max
 | ||||||
|  |                 } else if (distance < 3) { | ||||||
|  |                     l |= 2 << (j * 2);  // 1/2 * color_min + 1/2 * color_max
 | ||||||
|  |                 } else { | ||||||
|  |                     l |= 1 << (j * 2);  // color_min
 | ||||||
|  |                 } | ||||||
|  |             } else { | ||||||
|  |                 distance *= 6 / total; | ||||||
|  |                 if (distance < 1) { | ||||||
|  |                     l |= 1 << (j * 2);  // color_min
 | ||||||
|  |                 } else if (distance < 3) { | ||||||
|  |                     l |= 3 << (j * 2);  // 1/3 * color_min + 2/3 * color_max
 | ||||||
|  |                 } else if (distance < 5) { | ||||||
|  |                     l |= 2 << (j * 2);  // 2/3 * color_min + 1/3 * color_max
 | ||||||
|  |                 } else { | ||||||
|  |                     // color_max
 | ||||||
|  |                 } | ||||||
|  |             } | ||||||
|  |         } | ||||||
|  |         *dst++ = l; | ||||||
|  |     } | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | static void | ||||||
|  | encode_bc2_block(Imaging im, ImagingCodecState state, UINT8 *dst) { | ||||||
|  |     int i, j; | ||||||
|  |     UINT8 block[16], current_alpha; | ||||||
|  |     for (i = 0; i < 4; i++) { | ||||||
|  |         for (j = 0; j < 4; j++) { | ||||||
|  |             int x = state->x + i * im->pixelsize; | ||||||
|  |             int y = state->y + j; | ||||||
|  |             if (x >= state->xsize * im->pixelsize || y >= state->ysize) { | ||||||
|  |                 // The 4x4 block extends past the edge of the image
 | ||||||
|  |                 block[i + j * 4] = 0; | ||||||
|  |                 continue; | ||||||
|  |             } | ||||||
|  | 
 | ||||||
|  |             current_alpha = (UINT8)im->image[y][x + 3]; | ||||||
|  |             block[i + j * 4] = current_alpha; | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     for (i = 0; i < 4; i++) { | ||||||
|  |         UINT16 l = 0; | ||||||
|  |         for (j = 3; j > -1; j--) { | ||||||
|  |             current_alpha = block[i * 4 + j]; | ||||||
|  |             l |= current_alpha << (j * 4); | ||||||
|  |         } | ||||||
|  |         *dst++ = l; | ||||||
|  |         *dst++ = l >> 8; | ||||||
|  |     } | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | static void | ||||||
|  | encode_bc3_alpha(Imaging im, ImagingCodecState state, UINT8 *dst, int o) { | ||||||
|  |     int i, j; | ||||||
|  |     UINT8 alpha_min = 0, alpha_max = 0; | ||||||
|  |     UINT8 block[16], current_alpha; | ||||||
|  | 
 | ||||||
|  |     // Determine the min and max colors in this 4x4 block
 | ||||||
|  |     int first = 1; | ||||||
|  |     for (i = 0; i < 4; i++) { | ||||||
|  |         for (j = 0; j < 4; j++) { | ||||||
|  |             int x = state->x + i * im->pixelsize; | ||||||
|  |             int y = state->y + j; | ||||||
|  |             if (x >= state->xsize * im->pixelsize || y >= state->ysize) { | ||||||
|  |                 // The 4x4 block extends past the edge of the image
 | ||||||
|  |                 block[i + j * 4] = 0; | ||||||
|  |                 continue; | ||||||
|  |             } | ||||||
|  | 
 | ||||||
|  |             current_alpha = (UINT8)im->image[y][x + o]; | ||||||
|  |             block[i + j * 4] = current_alpha; | ||||||
|  | 
 | ||||||
|  |             if (first || current_alpha < alpha_min) { | ||||||
|  |                 alpha_min = current_alpha; | ||||||
|  |             } | ||||||
|  |             if (first || current_alpha > alpha_max) { | ||||||
|  |                 alpha_max = current_alpha; | ||||||
|  |             } | ||||||
|  |             first = 0; | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     *dst++ = alpha_min; | ||||||
|  |     *dst++ = alpha_max; | ||||||
|  | 
 | ||||||
|  |     float denom = (float)abs(alpha_max - alpha_min); | ||||||
|  |     for (i = 0; i < 2; i++) { | ||||||
|  |         UINT32 l = 0; | ||||||
|  |         for (j = 7; j > -1; j--) { | ||||||
|  |             current_alpha = block[i * 8 + j]; | ||||||
|  |             if (!current_alpha) { | ||||||
|  |                 l |= 6 << (j * 3); | ||||||
|  |                 continue; | ||||||
|  |             } else if (current_alpha == 255) { | ||||||
|  |                 l |= 7 << (j * 3); | ||||||
|  |                 continue; | ||||||
|  |             } | ||||||
|  | 
 | ||||||
|  |             float distance = | ||||||
|  |                 denom == 0 ? 0 : abs(current_alpha - alpha_min) / denom * 10; | ||||||
|  |             if (distance < 3) { | ||||||
|  |                 l |= 2 << (j * 3);  // 4/5 * alpha_min + 1/5 * alpha_max
 | ||||||
|  |             } else if (distance < 5) { | ||||||
|  |                 l |= 3 << (j * 3);  // 3/5 * alpha_min + 2/5 * alpha_max
 | ||||||
|  |             } else if (distance < 7) { | ||||||
|  |                 l |= 4 << (j * 3);  // 2/5 * alpha_min + 3/5 * alpha_max
 | ||||||
|  |             } else { | ||||||
|  |                 l |= 5 << (j * 3);  // 1/5 * alpha_min + 4/5 * alpha_max
 | ||||||
|  |             } | ||||||
|  |         } | ||||||
|  |         *dst++ = l; | ||||||
|  |         *dst++ = l >> 8; | ||||||
|  |         *dst++ = l >> 16; | ||||||
|  |     } | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | int | ||||||
|  | ImagingBcnEncode(Imaging im, ImagingCodecState state, UINT8 *buf, int bytes) { | ||||||
|  |     int n = state->state; | ||||||
|  |     int has_alpha_channel = | ||||||
|  |         strcmp(im->mode, "RGBA") == 0 || strcmp(im->mode, "LA") == 0; | ||||||
|  | 
 | ||||||
|  |     UINT8 *dst = buf; | ||||||
|  | 
 | ||||||
|  |     for (;;) { | ||||||
|  |         if (n == 5) { | ||||||
|  |             encode_bc3_alpha(im, state, dst, 0); | ||||||
|  |             dst += 8; | ||||||
|  | 
 | ||||||
|  |             encode_bc3_alpha(im, state, dst, 1); | ||||||
|  |         } else { | ||||||
|  |             if (n == 2 || n == 3) { | ||||||
|  |                 if (has_alpha_channel) { | ||||||
|  |                     if (n == 2) { | ||||||
|  |                         encode_bc2_block(im, state, dst); | ||||||
|  |                     } else { | ||||||
|  |                         encode_bc3_alpha(im, state, dst, 3); | ||||||
|  |                     } | ||||||
|  |                     dst += 8; | ||||||
|  |                 } else { | ||||||
|  |                     for (int i = 0; i < 8; i++) { | ||||||
|  |                         *dst++ = 0xff; | ||||||
|  |                     } | ||||||
|  |                 } | ||||||
|  |             } | ||||||
|  |             encode_bc1_color(im, state, dst, n == 1 && has_alpha_channel); | ||||||
|  |         } | ||||||
|  |         dst += 8; | ||||||
|  | 
 | ||||||
|  |         state->x += im->pixelsize * 4; | ||||||
|  | 
 | ||||||
|  |         if (state->x >= state->xsize * im->pixelsize) { | ||||||
|  |             state->x = 0; | ||||||
|  |             state->y += 4; | ||||||
|  |             if (state->y >= state->ysize) { | ||||||
|  |                 state->errcode = IMAGING_CODEC_END; | ||||||
|  |                 break; | ||||||
|  |             } | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     return dst - buf; | ||||||
|  | } | ||||||
|  | @ -567,6 +567,8 @@ typedef int (*ImagingCodec)( | ||||||
| extern int | extern int | ||||||
| ImagingBcnDecode(Imaging im, ImagingCodecState state, UINT8 *buffer, Py_ssize_t bytes); | ImagingBcnDecode(Imaging im, ImagingCodecState state, UINT8 *buffer, Py_ssize_t bytes); | ||||||
| extern int | extern int | ||||||
|  | ImagingBcnEncode(Imaging im, ImagingCodecState state, UINT8 *buffer, int bytes); | ||||||
|  | extern int | ||||||
| ImagingBitDecode(Imaging im, ImagingCodecState state, UINT8 *buffer, Py_ssize_t bytes); | ImagingBitDecode(Imaging im, ImagingCodecState state, UINT8 *buffer, Py_ssize_t bytes); | ||||||
| extern int | extern int | ||||||
| ImagingEpsEncode(Imaging im, ImagingCodecState state, UINT8 *buffer, int bytes); | ImagingEpsEncode(Imaging im, ImagingCodecState state, UINT8 *buffer, int bytes); | ||||||
|  |  | ||||||
		Loading…
	
		Reference in New Issue
	
	Block a user