This commit is contained in:
Andrew Murray 2025-07-08 16:58:34 +00:00 committed by GitHub
commit 1a43fc7379
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
7 changed files with 63 additions and 13 deletions

View File

@ -14,6 +14,7 @@ import pytest
from PIL import ( from PIL import (
AvifImagePlugin, AvifImagePlugin,
GifImagePlugin,
Image, Image,
ImageDraw, ImageDraw,
ImageFile, ImageFile,
@ -240,6 +241,7 @@ class TestFileAvif:
with Image.open("Tests/images/chi.gif") as im: with Image.open("Tests/images/chi.gif") as im:
im.save(temp_file) im.save(temp_file)
with Image.open(temp_file) as im: with Image.open(temp_file) as im:
assert isinstance(im, AvifImagePlugin.AvifImageFile)
assert im.n_frames == 1 assert im.n_frames == 1
def test_invalid_file(self) -> None: def test_invalid_file(self) -> None:
@ -598,10 +600,12 @@ class TestAvifAnimation:
""" """
with Image.open(TEST_AVIF_FILE) as im: with Image.open(TEST_AVIF_FILE) as im:
assert isinstance(im, AvifImagePlugin.AvifImageFile)
assert im.n_frames == 1 assert im.n_frames == 1
assert not im.is_animated assert not im.is_animated
with Image.open("Tests/images/avif/star.avifs") as im: with Image.open("Tests/images/avif/star.avifs") as im:
assert isinstance(im, AvifImagePlugin.AvifImageFile)
assert im.n_frames == 5 assert im.n_frames == 5
assert im.is_animated assert im.is_animated
@ -612,11 +616,13 @@ class TestAvifAnimation:
""" """
with Image.open("Tests/images/avif/star.gif") as original: with Image.open("Tests/images/avif/star.gif") as original:
assert isinstance(original, GifImagePlugin.GifImageFile)
assert original.n_frames > 1 assert original.n_frames > 1
temp_file = tmp_path / "temp.avif" temp_file = tmp_path / "temp.avif"
original.save(temp_file, save_all=True) original.save(temp_file, save_all=True)
with Image.open(temp_file) as im: with Image.open(temp_file) as im:
assert isinstance(im, AvifImagePlugin.AvifImageFile)
assert im.n_frames == original.n_frames assert im.n_frames == original.n_frames
# Compare first frame in P mode to frame from original GIF # Compare first frame in P mode to frame from original GIF
@ -636,6 +642,7 @@ class TestAvifAnimation:
def check(temp_file: Path) -> None: def check(temp_file: Path) -> None:
with Image.open(temp_file) as im: with Image.open(temp_file) as im:
assert isinstance(im, AvifImagePlugin.AvifImageFile)
assert im.n_frames == 4 assert im.n_frames == 4
# Compare first frame to original # Compare first frame to original
@ -708,6 +715,7 @@ class TestAvifAnimation:
) )
with Image.open(temp_file) as im: with Image.open(temp_file) as im:
assert isinstance(im, AvifImagePlugin.AvifImageFile)
assert im.n_frames == 5 assert im.n_frames == 5
assert im.is_animated assert im.is_animated
@ -737,6 +745,7 @@ class TestAvifAnimation:
) )
with Image.open(temp_file) as im: with Image.open(temp_file) as im:
assert isinstance(im, AvifImagePlugin.AvifImageFile)
assert im.n_frames == 5 assert im.n_frames == 5
assert im.is_animated assert im.is_animated

View File

@ -48,6 +48,7 @@ def test_sanity() -> None:
def test_prefix_chunk(monkeypatch: pytest.MonkeyPatch) -> None: def test_prefix_chunk(monkeypatch: pytest.MonkeyPatch) -> None:
monkeypatch.setattr(ImageFile, "LOAD_TRUNCATED_IMAGES", True) monkeypatch.setattr(ImageFile, "LOAD_TRUNCATED_IMAGES", True)
with Image.open(animated_test_file_with_prefix_chunk) as im: with Image.open(animated_test_file_with_prefix_chunk) as im:
assert isinstance(im, FliImagePlugin.FliImageFile)
assert im.mode == "P" assert im.mode == "P"
assert im.size == (320, 200) assert im.size == (320, 200)
assert im.format == "FLI" assert im.format == "FLI"
@ -55,6 +56,7 @@ def test_prefix_chunk(monkeypatch: pytest.MonkeyPatch) -> None:
assert im.is_animated assert im.is_animated
palette = im.getpalette() palette = im.getpalette()
assert palette is not None
assert palette[3:6] == [255, 255, 255] assert palette[3:6] == [255, 255, 255]
assert palette[381:384] == [204, 204, 12] assert palette[381:384] == [204, 204, 12]
assert palette[765:] == [252, 0, 0] assert palette[765:] == [252, 0, 0]

View File

@ -293,6 +293,7 @@ def test_roundtrip_save_all(tmp_path: Path) -> None:
im.save(out, save_all=True) im.save(out, save_all=True)
with Image.open(out) as reread: with Image.open(out) as reread:
assert isinstance(reread, GifImagePlugin.GifImageFile)
assert reread.n_frames == 5 assert reread.n_frames == 5
@ -1374,6 +1375,7 @@ def test_palette_save_all_P(tmp_path: Path) -> None:
with Image.open(out) as im: with Image.open(out) as im:
# Assert that the frames are correct, and each frame has the same palette # Assert that the frames are correct, and each frame has the same palette
assert isinstance(im, GifImagePlugin.GifImageFile)
assert_image_equal(im.convert("RGB"), frames[0].convert("RGB")) assert_image_equal(im.convert("RGB"), frames[0].convert("RGB"))
assert im.palette is not None assert im.palette is not None
assert im.global_palette is not None assert im.global_palette is not None

View File

@ -331,8 +331,10 @@ class TestFileJpeg:
# Reading # Reading
with Image.open("Tests/images/exif_gps.jpg") as im: with Image.open("Tests/images/exif_gps.jpg") as im:
exif = im._getexif() assert isinstance(im, JpegImagePlugin.JpegImageFile)
assert exif[gps_index] == expected_exif_gps exif_data = im._getexif()
assert exif_data is not None
assert exif_data[gps_index] == expected_exif_gps
# Writing # Writing
f = tmp_path / "temp.jpg" f = tmp_path / "temp.jpg"
@ -341,8 +343,10 @@ class TestFileJpeg:
hopper().save(f, exif=exif) hopper().save(f, exif=exif)
with Image.open(f) as reloaded: with Image.open(f) as reloaded:
exif = reloaded._getexif() assert isinstance(reloaded, JpegImagePlugin.JpegImageFile)
assert exif[gps_index] == expected_exif_gps exif_data = reloaded._getexif()
assert exif_data is not None
assert exif_data[gps_index] == expected_exif_gps
def test_empty_exif_gps(self) -> None: def test_empty_exif_gps(self) -> None:
with Image.open("Tests/images/empty_gps_ifd.jpg") as im: with Image.open("Tests/images/empty_gps_ifd.jpg") as im:
@ -369,6 +373,7 @@ class TestFileJpeg:
exifs = [] exifs = []
for i in range(2): for i in range(2):
with Image.open("Tests/images/exif-200dpcm.jpg") as im: with Image.open("Tests/images/exif-200dpcm.jpg") as im:
assert isinstance(im, JpegImagePlugin.JpegImageFile)
exifs.append(im._getexif()) exifs.append(im._getexif())
assert exifs[0] == exifs[1] assert exifs[0] == exifs[1]
@ -402,13 +407,17 @@ class TestFileJpeg:
} }
with Image.open("Tests/images/exif_gps.jpg") as im: with Image.open("Tests/images/exif_gps.jpg") as im:
assert isinstance(im, JpegImagePlugin.JpegImageFile)
exif = im._getexif() exif = im._getexif()
assert exif is not None
for tag, value in expected_exif.items(): for tag, value in expected_exif.items():
assert value == exif[tag] assert value == exif[tag]
def test_exif_gps_typeerror(self) -> None: def test_exif_gps_typeerror(self) -> None:
with Image.open("Tests/images/exif_gps_typeerror.jpg") as im: with Image.open("Tests/images/exif_gps_typeerror.jpg") as im:
assert isinstance(im, JpegImagePlugin.JpegImageFile)
# Should not raise a TypeError # Should not raise a TypeError
im._getexif() im._getexif()
@ -488,7 +497,9 @@ class TestFileJpeg:
def test_exif(self) -> None: def test_exif(self) -> None:
with Image.open("Tests/images/pil_sample_rgb.jpg") as im: with Image.open("Tests/images/pil_sample_rgb.jpg") as im:
assert isinstance(im, JpegImagePlugin.JpegImageFile)
info = im._getexif() info = im._getexif()
assert info is not None
assert info[305] == "Adobe Photoshop CS Macintosh" assert info[305] == "Adobe Photoshop CS Macintosh"
def test_get_child_images(self) -> None: def test_get_child_images(self) -> None:
@ -691,11 +702,13 @@ class TestFileJpeg:
def test_save_multiple_16bit_qtables(self) -> None: def test_save_multiple_16bit_qtables(self) -> None:
with Image.open("Tests/images/hopper_16bit_qtables.jpg") as im: with Image.open("Tests/images/hopper_16bit_qtables.jpg") as im:
assert isinstance(im, JpegImagePlugin.JpegImageFile)
im2 = self.roundtrip(im, qtables="keep") im2 = self.roundtrip(im, qtables="keep")
assert im.quantization == im2.quantization assert im.quantization == im2.quantization
def test_save_single_16bit_qtable(self) -> None: def test_save_single_16bit_qtable(self) -> None:
with Image.open("Tests/images/hopper_16bit_qtables.jpg") as im: with Image.open("Tests/images/hopper_16bit_qtables.jpg") as im:
assert isinstance(im, JpegImagePlugin.JpegImageFile)
im2 = self.roundtrip(im, qtables={0: im.quantization[0]}) im2 = self.roundtrip(im, qtables={0: im.quantization[0]})
assert len(im2.quantization) == 1 assert len(im2.quantization) == 1
assert im2.quantization[0] == im.quantization[0] assert im2.quantization[0] == im.quantization[0]
@ -907,7 +920,10 @@ class TestFileJpeg:
# in contrast to normal 8 # in contrast to normal 8
with Image.open("Tests/images/exif-ifd-offset.jpg") as im: with Image.open("Tests/images/exif-ifd-offset.jpg") as im:
# Act / Assert # Act / Assert
assert im._getexif()[306] == "2017:03:13 23:03:09" assert isinstance(im, JpegImagePlugin.JpegImageFile)
exif = im._getexif()
assert exif is not None
assert exif[306] == "2017:03:13 23:03:09"
def test_multiple_exif(self) -> None: def test_multiple_exif(self) -> None:
with Image.open("Tests/images/multiple_exif.jpg") as im: with Image.open("Tests/images/multiple_exif.jpg") as im:

View File

@ -120,9 +120,11 @@ def test_ignore_frame_size() -> None:
# Ignore the different size of the second frame # Ignore the different size of the second frame
# since this is not a "Large Thumbnail" image # since this is not a "Large Thumbnail" image
with Image.open("Tests/images/ignore_frame_size.mpo") as im: with Image.open("Tests/images/ignore_frame_size.mpo") as im:
assert isinstance(im, MpoImagePlugin.MpoImageFile)
assert im.size == (64, 64) assert im.size == (64, 64)
im.seek(1) im.seek(1)
assert im.mpinfo is not None
assert ( assert (
im.mpinfo[0xB002][1]["Attribute"]["MPType"] im.mpinfo[0xB002][1]["Attribute"]["MPType"]
== "Multi-Frame Image: (Disparity)" == "Multi-Frame Image: (Disparity)"
@ -155,6 +157,7 @@ def test_reload_exif_after_seek() -> None:
@pytest.mark.parametrize("test_file", test_files) @pytest.mark.parametrize("test_file", test_files)
def test_mp(test_file: str) -> None: def test_mp(test_file: str) -> None:
with Image.open(test_file) as im: with Image.open(test_file) as im:
assert isinstance(im, MpoImagePlugin.MpoImageFile)
mpinfo = im._getmp() mpinfo = im._getmp()
assert mpinfo is not None assert mpinfo is not None
assert mpinfo[45056] == b"0100" assert mpinfo[45056] == b"0100"
@ -165,6 +168,7 @@ def test_mp_offset() -> None:
# This image has been manually hexedited to have an IFD offset of 10 # This image has been manually hexedited to have an IFD offset of 10
# in APP2 data, in contrast to normal 8 # in APP2 data, in contrast to normal 8
with Image.open("Tests/images/sugarshack_ifd_offset.mpo") as im: with Image.open("Tests/images/sugarshack_ifd_offset.mpo") as im:
assert isinstance(im, MpoImagePlugin.MpoImageFile)
mpinfo = im._getmp() mpinfo = im._getmp()
assert mpinfo is not None assert mpinfo is not None
assert mpinfo[45056] == b"0100" assert mpinfo[45056] == b"0100"
@ -182,6 +186,7 @@ def test_mp_no_data() -> None:
@pytest.mark.parametrize("test_file", test_files) @pytest.mark.parametrize("test_file", test_files)
def test_mp_attribute(test_file: str) -> None: def test_mp_attribute(test_file: str) -> None:
with Image.open(test_file) as im: with Image.open(test_file) as im:
assert isinstance(im, MpoImagePlugin.MpoImageFile)
mpinfo = im._getmp() mpinfo = im._getmp()
assert mpinfo is not None assert mpinfo is not None
for frame_number, mpentry in enumerate(mpinfo[0xB002]): for frame_number, mpentry in enumerate(mpinfo[0xB002]):

View File

@ -671,6 +671,9 @@ class TestFilePng:
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:
assert isinstance(reloaded, PngImagePlugin.PngImageFile)
assert reloaded.png is not None
assert reloaded.png.im_palette is not None
assert len(reloaded.png.im_palette[1]) == 48 assert len(reloaded.png.im_palette[1]) == 48
def test_plte_length(self, tmp_path: Path) -> None: def test_plte_length(self, tmp_path: Path) -> None:
@ -681,6 +684,9 @@ class TestFilePng:
im.save(out) im.save(out)
with Image.open(out) as reloaded: with Image.open(out) as reloaded:
assert isinstance(reloaded, PngImagePlugin.PngImageFile)
assert reloaded.png is not None
assert reloaded.png.im_palette is not None
assert len(reloaded.png.im_palette[1]) == 3 assert len(reloaded.png.im_palette[1]) == 3
def test_getxmp(self) -> None: def test_getxmp(self) -> None:
@ -702,13 +708,17 @@ class TestFilePng:
def test_exif(self) -> None: def test_exif(self) -> None:
# With an EXIF chunk # With an EXIF chunk
with Image.open("Tests/images/exif.png") as im: with Image.open("Tests/images/exif.png") as im:
exif = im._getexif() assert isinstance(im, PngImagePlugin.PngImageFile)
assert exif[274] == 1 exif_data = im._getexif()
assert exif_data is not None
assert exif_data[274] == 1
# With an ImageMagick zTXt chunk # With an ImageMagick zTXt chunk
with Image.open("Tests/images/exif_imagemagick.png") as im: with Image.open("Tests/images/exif_imagemagick.png") as im:
exif = im._getexif() assert isinstance(im, PngImagePlugin.PngImageFile)
assert exif[274] == 1 exif_data = im._getexif()
assert exif_data is not None
assert exif_data[274] == 1
# Assert that info still can be extracted # Assert that info still can be extracted
# when the image is no longer a PngImageFile instance # when the image is no longer a PngImageFile instance
@ -717,8 +727,10 @@ class TestFilePng:
# With a tEXt chunk # With a tEXt chunk
with Image.open("Tests/images/exif_text.png") as im: with Image.open("Tests/images/exif_text.png") as im:
exif = im._getexif() assert isinstance(im, PngImagePlugin.PngImageFile)
assert exif[274] == 1 exif_data = im._getexif()
assert exif_data is not None
assert exif_data[274] == 1
# With XMP tags # With XMP tags
with Image.open("Tests/images/xmp_tags_orientation.png") as im: with Image.open("Tests/images/xmp_tags_orientation.png") as im:
@ -740,8 +752,10 @@ class TestFilePng:
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:
exif = reloaded._getexif() assert isinstance(reloaded, PngImagePlugin.PngImageFile)
assert exif[274] == 1 exif_data = reloaded._getexif()
assert exif_data is not None
assert exif_data[274] == 1
@mark_if_feature_version( @mark_if_feature_version(
pytest.mark.valgrind_known_error, "libjpeg_turbo", "2.0", reason="Known Failing" pytest.mark.valgrind_known_error, "libjpeg_turbo", "2.0", reason="Known Failing"

View File

@ -22,11 +22,13 @@ except ImportError:
def test_read_exif_metadata() -> None: def test_read_exif_metadata() -> None:
file_path = "Tests/images/flower.webp" file_path = "Tests/images/flower.webp"
with Image.open(file_path) as image: with Image.open(file_path) as image:
assert isinstance(image, WebPImagePlugin.WebPImageFile)
assert image.format == "WEBP" assert image.format == "WEBP"
exif_data = image.info.get("exif", None) exif_data = image.info.get("exif", None)
assert exif_data assert exif_data
exif = image._getexif() exif = image._getexif()
assert exif is not None
# Camera make # Camera make
assert exif[271] == "Canon" assert exif[271] == "Canon"