Added type hints

This commit is contained in:
Andrew Murray 2024-09-09 22:34:46 +10:00
parent be01d536c6
commit e005bcf8f2
89 changed files with 824 additions and 300 deletions

View File

@ -98,6 +98,7 @@ 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, msg: str | None = None, mode: str | None = None
) -> None: ) -> None:
img: Image.Image
with Image.open(filename) as img: with Image.open(filename) as img:
if mode: if mode:
img = img.convert(mode) img = img.convert(mode)
@ -142,6 +143,7 @@ def assert_image_similar_tofile(
msg: str | None = None, msg: str | None = None,
mode: str | None = None, mode: str | None = None,
) -> None: ) -> None:
img: Image.Image
with Image.open(filename) as img: with Image.open(filename) as img:
if mode: if mode:
img = img.convert(mode) img = img.convert(mode)

View File

@ -92,8 +92,11 @@ def test_good() -> None:
for f in get_files("g"): for f in get_files("g"):
try: try:
im: Image.Image
with Image.open(f) as im: with Image.open(f) as im:
im.load() im.load()
compare: Image.Image
with Image.open(get_compare(f)) as compare: with Image.open(get_compare(f)) as compare:
compare.load() compare.load()
if im.mode == "P": if im.mode == "P":

View File

@ -12,6 +12,7 @@ from PIL import Image, ImageSequence, PngImagePlugin
# (referenced from https://wiki.mozilla.org/APNG_Specification) # (referenced from https://wiki.mozilla.org/APNG_Specification)
def test_apng_basic() -> None: def test_apng_basic() -> None:
with Image.open("Tests/images/apng/single_frame.png") as im: with Image.open("Tests/images/apng/single_frame.png") as im:
assert isinstance(im, PngImagePlugin.PngImageFile)
assert not im.is_animated assert not im.is_animated
assert im.n_frames == 1 assert im.n_frames == 1
assert im.get_format_mimetype() == "image/apng" assert im.get_format_mimetype() == "image/apng"
@ -20,6 +21,7 @@ def test_apng_basic() -> None:
assert im.getpixel((64, 32)) == (0, 255, 0, 255) assert im.getpixel((64, 32)) == (0, 255, 0, 255)
with Image.open("Tests/images/apng/single_frame_default.png") as im: with Image.open("Tests/images/apng/single_frame_default.png") as im:
assert isinstance(im, PngImagePlugin.PngImageFile)
assert im.is_animated assert im.is_animated
assert im.n_frames == 2 assert im.n_frames == 2
assert im.get_format_mimetype() == "image/apng" assert im.get_format_mimetype() == "image/apng"
@ -49,6 +51,7 @@ def test_apng_basic() -> None:
) )
def test_apng_fdat(filename: str) -> None: def test_apng_fdat(filename: str) -> None:
with Image.open(filename) as im: with Image.open(filename) as im:
assert isinstance(im, PngImagePlugin.PngImageFile)
im.seek(im.n_frames - 1) im.seek(im.n_frames - 1)
assert im.getpixel((0, 0)) == (0, 255, 0, 255) assert im.getpixel((0, 0)) == (0, 255, 0, 255)
assert im.getpixel((64, 32)) == (0, 255, 0, 255) assert im.getpixel((64, 32)) == (0, 255, 0, 255)
@ -56,31 +59,37 @@ def test_apng_fdat(filename: str) -> None:
def test_apng_dispose() -> None: def test_apng_dispose() -> None:
with Image.open("Tests/images/apng/dispose_op_none.png") as im: with Image.open("Tests/images/apng/dispose_op_none.png") as im:
assert isinstance(im, PngImagePlugin.PngImageFile)
im.seek(im.n_frames - 1) im.seek(im.n_frames - 1)
assert im.getpixel((0, 0)) == (0, 255, 0, 255) assert im.getpixel((0, 0)) == (0, 255, 0, 255)
assert im.getpixel((64, 32)) == (0, 255, 0, 255) assert im.getpixel((64, 32)) == (0, 255, 0, 255)
with Image.open("Tests/images/apng/dispose_op_background.png") as im: with Image.open("Tests/images/apng/dispose_op_background.png") as im:
assert isinstance(im, PngImagePlugin.PngImageFile)
im.seek(im.n_frames - 1) im.seek(im.n_frames - 1)
assert im.getpixel((0, 0)) == (0, 0, 0, 0) assert im.getpixel((0, 0)) == (0, 0, 0, 0)
assert im.getpixel((64, 32)) == (0, 0, 0, 0) assert im.getpixel((64, 32)) == (0, 0, 0, 0)
with Image.open("Tests/images/apng/dispose_op_background_final.png") as im: with Image.open("Tests/images/apng/dispose_op_background_final.png") as im:
assert isinstance(im, PngImagePlugin.PngImageFile)
im.seek(im.n_frames - 1) im.seek(im.n_frames - 1)
assert im.getpixel((0, 0)) == (0, 255, 0, 255) assert im.getpixel((0, 0)) == (0, 255, 0, 255)
assert im.getpixel((64, 32)) == (0, 255, 0, 255) assert im.getpixel((64, 32)) == (0, 255, 0, 255)
with Image.open("Tests/images/apng/dispose_op_previous.png") as im: with Image.open("Tests/images/apng/dispose_op_previous.png") as im:
assert isinstance(im, PngImagePlugin.PngImageFile)
im.seek(im.n_frames - 1) im.seek(im.n_frames - 1)
assert im.getpixel((0, 0)) == (0, 255, 0, 255) assert im.getpixel((0, 0)) == (0, 255, 0, 255)
assert im.getpixel((64, 32)) == (0, 255, 0, 255) assert im.getpixel((64, 32)) == (0, 255, 0, 255)
with Image.open("Tests/images/apng/dispose_op_previous_final.png") as im: with Image.open("Tests/images/apng/dispose_op_previous_final.png") as im:
assert isinstance(im, PngImagePlugin.PngImageFile)
im.seek(im.n_frames - 1) im.seek(im.n_frames - 1)
assert im.getpixel((0, 0)) == (0, 255, 0, 255) assert im.getpixel((0, 0)) == (0, 255, 0, 255)
assert im.getpixel((64, 32)) == (0, 255, 0, 255) assert im.getpixel((64, 32)) == (0, 255, 0, 255)
with Image.open("Tests/images/apng/dispose_op_previous_first.png") as im: with Image.open("Tests/images/apng/dispose_op_previous_first.png") as im:
assert isinstance(im, PngImagePlugin.PngImageFile)
im.seek(im.n_frames - 1) im.seek(im.n_frames - 1)
assert im.getpixel((0, 0)) == (0, 0, 0, 0) assert im.getpixel((0, 0)) == (0, 0, 0, 0)
assert im.getpixel((64, 32)) == (0, 0, 0, 0) assert im.getpixel((64, 32)) == (0, 0, 0, 0)
@ -88,21 +97,25 @@ def test_apng_dispose() -> None:
def test_apng_dispose_region() -> None: def test_apng_dispose_region() -> None:
with Image.open("Tests/images/apng/dispose_op_none_region.png") as im: with Image.open("Tests/images/apng/dispose_op_none_region.png") as im:
assert isinstance(im, PngImagePlugin.PngImageFile)
im.seek(im.n_frames - 1) im.seek(im.n_frames - 1)
assert im.getpixel((0, 0)) == (0, 255, 0, 255) assert im.getpixel((0, 0)) == (0, 255, 0, 255)
assert im.getpixel((64, 32)) == (0, 255, 0, 255) assert im.getpixel((64, 32)) == (0, 255, 0, 255)
with Image.open("Tests/images/apng/dispose_op_background_before_region.png") as im: with Image.open("Tests/images/apng/dispose_op_background_before_region.png") as im:
assert isinstance(im, PngImagePlugin.PngImageFile)
im.seek(im.n_frames - 1) im.seek(im.n_frames - 1)
assert im.getpixel((0, 0)) == (0, 0, 0, 0) assert im.getpixel((0, 0)) == (0, 0, 0, 0)
assert im.getpixel((64, 32)) == (0, 0, 0, 0) assert im.getpixel((64, 32)) == (0, 0, 0, 0)
with Image.open("Tests/images/apng/dispose_op_background_region.png") as im: with Image.open("Tests/images/apng/dispose_op_background_region.png") as im:
assert isinstance(im, PngImagePlugin.PngImageFile)
im.seek(im.n_frames - 1) im.seek(im.n_frames - 1)
assert im.getpixel((0, 0)) == (0, 0, 255, 255) assert im.getpixel((0, 0)) == (0, 0, 255, 255)
assert im.getpixel((64, 32)) == (0, 0, 0, 0) assert im.getpixel((64, 32)) == (0, 0, 0, 0)
with Image.open("Tests/images/apng/dispose_op_previous_region.png") as im: with Image.open("Tests/images/apng/dispose_op_previous_region.png") as im:
assert isinstance(im, PngImagePlugin.PngImageFile)
im.seek(im.n_frames - 1) im.seek(im.n_frames - 1)
assert im.getpixel((0, 0)) == (0, 255, 0, 255) assert im.getpixel((0, 0)) == (0, 255, 0, 255)
assert im.getpixel((64, 32)) == (0, 255, 0, 255) assert im.getpixel((64, 32)) == (0, 255, 0, 255)
@ -129,6 +142,7 @@ def test_apng_dispose_op_previous_frame() -> None:
# ], # ],
# ) # )
with Image.open("Tests/images/apng/dispose_op_previous_frame.png") as im: with Image.open("Tests/images/apng/dispose_op_previous_frame.png") as im:
assert isinstance(im, PngImagePlugin.PngImageFile)
im.seek(im.n_frames - 1) im.seek(im.n_frames - 1)
assert im.getpixel((0, 0)) == (255, 0, 0, 255) assert im.getpixel((0, 0)) == (255, 0, 0, 255)
@ -142,26 +156,31 @@ def test_apng_dispose_op_background_p_mode() -> None:
def test_apng_blend() -> None: def test_apng_blend() -> None:
with Image.open("Tests/images/apng/blend_op_source_solid.png") as im: with Image.open("Tests/images/apng/blend_op_source_solid.png") as im:
assert isinstance(im, PngImagePlugin.PngImageFile)
im.seek(im.n_frames - 1) im.seek(im.n_frames - 1)
assert im.getpixel((0, 0)) == (0, 255, 0, 255) assert im.getpixel((0, 0)) == (0, 255, 0, 255)
assert im.getpixel((64, 32)) == (0, 255, 0, 255) assert im.getpixel((64, 32)) == (0, 255, 0, 255)
with Image.open("Tests/images/apng/blend_op_source_transparent.png") as im: with Image.open("Tests/images/apng/blend_op_source_transparent.png") as im:
assert isinstance(im, PngImagePlugin.PngImageFile)
im.seek(im.n_frames - 1) im.seek(im.n_frames - 1)
assert im.getpixel((0, 0)) == (0, 0, 0, 0) assert im.getpixel((0, 0)) == (0, 0, 0, 0)
assert im.getpixel((64, 32)) == (0, 0, 0, 0) assert im.getpixel((64, 32)) == (0, 0, 0, 0)
with Image.open("Tests/images/apng/blend_op_source_near_transparent.png") as im: with Image.open("Tests/images/apng/blend_op_source_near_transparent.png") as im:
assert isinstance(im, PngImagePlugin.PngImageFile)
im.seek(im.n_frames - 1) im.seek(im.n_frames - 1)
assert im.getpixel((0, 0)) == (0, 255, 0, 2) assert im.getpixel((0, 0)) == (0, 255, 0, 2)
assert im.getpixel((64, 32)) == (0, 255, 0, 2) assert im.getpixel((64, 32)) == (0, 255, 0, 2)
with Image.open("Tests/images/apng/blend_op_over.png") as im: with Image.open("Tests/images/apng/blend_op_over.png") as im:
assert isinstance(im, PngImagePlugin.PngImageFile)
im.seek(im.n_frames - 1) im.seek(im.n_frames - 1)
assert im.getpixel((0, 0)) == (0, 255, 0, 255) assert im.getpixel((0, 0)) == (0, 255, 0, 255)
assert im.getpixel((64, 32)) == (0, 255, 0, 255) assert im.getpixel((64, 32)) == (0, 255, 0, 255)
with Image.open("Tests/images/apng/blend_op_over_near_transparent.png") as im: with Image.open("Tests/images/apng/blend_op_over_near_transparent.png") as im:
assert isinstance(im, PngImagePlugin.PngImageFile)
im.seek(im.n_frames - 1) im.seek(im.n_frames - 1)
assert im.getpixel((0, 0)) == (0, 255, 0, 97) assert im.getpixel((0, 0)) == (0, 255, 0, 97)
assert im.getpixel((64, 32)) == (0, 255, 0, 255) assert im.getpixel((64, 32)) == (0, 255, 0, 255)
@ -175,6 +194,7 @@ def test_apng_blend_transparency() -> None:
def test_apng_chunk_order() -> None: def test_apng_chunk_order() -> None:
with Image.open("Tests/images/apng/fctl_actl.png") as im: with Image.open("Tests/images/apng/fctl_actl.png") as im:
assert isinstance(im, PngImagePlugin.PngImageFile)
im.seek(im.n_frames - 1) im.seek(im.n_frames - 1)
assert im.getpixel((0, 0)) == (0, 255, 0, 255) assert im.getpixel((0, 0)) == (0, 255, 0, 255)
assert im.getpixel((64, 32)) == (0, 255, 0, 255) assert im.getpixel((64, 32)) == (0, 255, 0, 255)
@ -230,66 +250,78 @@ def test_apng_num_plays() -> None:
def test_apng_mode() -> None: def test_apng_mode() -> None:
with Image.open("Tests/images/apng/mode_16bit.png") as im: with Image.open("Tests/images/apng/mode_16bit.png") as im:
assert isinstance(im, PngImagePlugin.PngImageFile)
assert im.mode == "RGBA" assert im.mode == "RGBA"
im.seek(im.n_frames - 1) im.seek(im.n_frames - 1)
assert im.getpixel((0, 0)) == (0, 0, 128, 191) assert im.getpixel((0, 0)) == (0, 0, 128, 191)
assert im.getpixel((64, 32)) == (0, 0, 128, 191) assert im.getpixel((64, 32)) == (0, 0, 128, 191)
with Image.open("Tests/images/apng/mode_grayscale.png") as im: with Image.open("Tests/images/apng/mode_grayscale.png") as im:
assert isinstance(im, PngImagePlugin.PngImageFile)
assert im.mode == "L" assert im.mode == "L"
im.seek(im.n_frames - 1) im.seek(im.n_frames - 1)
assert im.getpixel((0, 0)) == 128 assert im.getpixel((0, 0)) == 128
assert im.getpixel((64, 32)) == 255 assert im.getpixel((64, 32)) == 255
with Image.open("Tests/images/apng/mode_grayscale_alpha.png") as im: with Image.open("Tests/images/apng/mode_grayscale_alpha.png") as im:
assert isinstance(im, PngImagePlugin.PngImageFile)
assert im.mode == "LA" assert im.mode == "LA"
im.seek(im.n_frames - 1) im.seek(im.n_frames - 1)
assert im.getpixel((0, 0)) == (128, 191) assert im.getpixel((0, 0)) == (128, 191)
assert im.getpixel((64, 32)) == (128, 191) assert im.getpixel((64, 32)) == (128, 191)
with Image.open("Tests/images/apng/mode_palette.png") as im: with Image.open("Tests/images/apng/mode_palette.png") as im:
assert isinstance(im, PngImagePlugin.PngImageFile)
assert im.mode == "P" assert im.mode == "P"
im.seek(im.n_frames - 1) im.seek(im.n_frames - 1)
im = im.convert("RGB") rgb_im = im.convert("RGB")
assert im.getpixel((0, 0)) == (0, 255, 0) assert rgb_im.getpixel((0, 0)) == (0, 255, 0)
assert im.getpixel((64, 32)) == (0, 255, 0) assert rgb_im.getpixel((64, 32)) == (0, 255, 0)
with Image.open("Tests/images/apng/mode_palette_alpha.png") as im: with Image.open("Tests/images/apng/mode_palette_alpha.png") as im:
assert isinstance(im, PngImagePlugin.PngImageFile)
assert im.mode == "P" assert im.mode == "P"
im.seek(im.n_frames - 1) im.seek(im.n_frames - 1)
im = im.convert("RGBA") rgb_im = im.convert("RGBA")
assert im.getpixel((0, 0)) == (255, 0, 0, 0) assert rgb_im.getpixel((0, 0)) == (255, 0, 0, 0)
assert im.getpixel((64, 32)) == (255, 0, 0, 0) assert rgb_im.getpixel((64, 32)) == (255, 0, 0, 0)
with Image.open("Tests/images/apng/mode_palette_1bit_alpha.png") as im: with Image.open("Tests/images/apng/mode_palette_1bit_alpha.png") as im:
assert isinstance(im, PngImagePlugin.PngImageFile)
assert im.mode == "P" assert im.mode == "P"
im.seek(im.n_frames - 1) im.seek(im.n_frames - 1)
im = im.convert("RGBA") rgb_im = im.convert("RGBA")
assert im.getpixel((0, 0)) == (0, 0, 255, 128) assert rgb_im.getpixel((0, 0)) == (0, 0, 255, 128)
assert im.getpixel((64, 32)) == (0, 0, 255, 128) assert rgb_im.getpixel((64, 32)) == (0, 0, 255, 128)
def test_apng_chunk_errors() -> None: def test_apng_chunk_errors() -> None:
with Image.open("Tests/images/apng/chunk_no_actl.png") as im: with Image.open("Tests/images/apng/chunk_no_actl.png") as im:
assert isinstance(im, PngImagePlugin.PngImageFile)
assert not im.is_animated assert not im.is_animated
with pytest.warns(UserWarning): with pytest.warns(UserWarning):
with Image.open("Tests/images/apng/chunk_multi_actl.png") as im: with Image.open("Tests/images/apng/chunk_multi_actl.png") as im:
im.load() im.load()
assert isinstance(im, PngImagePlugin.PngImageFile)
assert not im.is_animated assert not im.is_animated
with Image.open("Tests/images/apng/chunk_actl_after_idat.png") as im: with Image.open("Tests/images/apng/chunk_actl_after_idat.png") as im:
assert isinstance(im, PngImagePlugin.PngImageFile)
assert not im.is_animated assert not im.is_animated
with Image.open("Tests/images/apng/chunk_no_fctl.png") as im: with Image.open("Tests/images/apng/chunk_no_fctl.png") as im:
assert isinstance(im, PngImagePlugin.PngImageFile)
with pytest.raises(SyntaxError): with pytest.raises(SyntaxError):
im.seek(im.n_frames - 1) im.seek(im.n_frames - 1)
with Image.open("Tests/images/apng/chunk_repeat_fctl.png") as im: with Image.open("Tests/images/apng/chunk_repeat_fctl.png") as im:
assert isinstance(im, PngImagePlugin.PngImageFile)
with pytest.raises(SyntaxError): with pytest.raises(SyntaxError):
im.seek(im.n_frames - 1) im.seek(im.n_frames - 1)
with Image.open("Tests/images/apng/chunk_no_fdat.png") as im: with Image.open("Tests/images/apng/chunk_no_fdat.png") as im:
assert isinstance(im, PngImagePlugin.PngImageFile)
with pytest.raises(SyntaxError): with pytest.raises(SyntaxError):
im.seek(im.n_frames - 1) im.seek(im.n_frames - 1)
@ -297,18 +329,21 @@ def test_apng_chunk_errors() -> None:
def test_apng_syntax_errors() -> None: def test_apng_syntax_errors() -> None:
with pytest.warns(UserWarning): with pytest.warns(UserWarning):
with Image.open("Tests/images/apng/syntax_num_frames_zero.png") as im: with Image.open("Tests/images/apng/syntax_num_frames_zero.png") as im:
assert isinstance(im, PngImagePlugin.PngImageFile)
assert not im.is_animated assert not im.is_animated
with pytest.raises(OSError): with pytest.raises(OSError):
im.load() im.load()
with pytest.warns(UserWarning): with pytest.warns(UserWarning):
with Image.open("Tests/images/apng/syntax_num_frames_zero_default.png") as im: with Image.open("Tests/images/apng/syntax_num_frames_zero_default.png") as im:
assert isinstance(im, PngImagePlugin.PngImageFile)
assert not im.is_animated assert not im.is_animated
im.load() im.load()
# we can handle this case gracefully # we can handle this case gracefully
exception = None exception = None
with Image.open("Tests/images/apng/syntax_num_frames_low.png") as im: with Image.open("Tests/images/apng/syntax_num_frames_low.png") as im:
assert isinstance(im, PngImagePlugin.PngImageFile)
try: try:
im.seek(im.n_frames - 1) im.seek(im.n_frames - 1)
except Exception as e: except Exception as e:
@ -317,11 +352,13 @@ def test_apng_syntax_errors() -> None:
with pytest.raises(OSError): with pytest.raises(OSError):
with Image.open("Tests/images/apng/syntax_num_frames_high.png") as im: with Image.open("Tests/images/apng/syntax_num_frames_high.png") as im:
assert isinstance(im, PngImagePlugin.PngImageFile)
im.seek(im.n_frames - 1) im.seek(im.n_frames - 1)
im.load() im.load()
with pytest.warns(UserWarning): with pytest.warns(UserWarning):
with Image.open("Tests/images/apng/syntax_num_frames_invalid.png") as im: with Image.open("Tests/images/apng/syntax_num_frames_invalid.png") as im:
assert isinstance(im, PngImagePlugin.PngImageFile)
assert not im.is_animated assert not im.is_animated
im.load() im.load()
@ -341,6 +378,7 @@ def test_apng_syntax_errors() -> None:
def test_apng_sequence_errors(test_file: str) -> None: def test_apng_sequence_errors(test_file: str) -> None:
with pytest.raises(SyntaxError): with pytest.raises(SyntaxError):
with Image.open(f"Tests/images/apng/{test_file}") as im: with Image.open(f"Tests/images/apng/{test_file}") as im:
assert isinstance(im, PngImagePlugin.PngImageFile)
im.seek(im.n_frames - 1) im.seek(im.n_frames - 1)
im.load() im.load()
@ -351,6 +389,8 @@ def test_apng_save(tmp_path: Path) -> None:
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:
assert isinstance(im, PngImagePlugin.PngImageFile)
im.load() im.load()
assert not im.is_animated assert not im.is_animated
assert im.n_frames == 1 assert im.n_frames == 1
@ -366,6 +406,8 @@ def test_apng_save(tmp_path: Path) -> None:
) )
with Image.open(test_file) as im: with Image.open(test_file) as im:
assert isinstance(im, PngImagePlugin.PngImageFile)
im.load() im.load()
assert im.is_animated assert im.is_animated
assert im.n_frames == 2 assert im.n_frames == 2
@ -405,6 +447,8 @@ def test_apng_save_split_fdat(tmp_path: Path) -> None:
append_images=frames, append_images=frames,
) )
with Image.open(test_file) as im: with Image.open(test_file) as im:
assert isinstance(im, PngImagePlugin.PngImageFile)
exception = None exception = None
try: try:
im.seek(im.n_frames - 1) im.seek(im.n_frames - 1)
@ -452,6 +496,7 @@ def test_apng_save_duration_loop(tmp_path: Path) -> None:
test_file, save_all=True, append_images=[frame, frame], duration=[500, 100, 150] test_file, save_all=True, append_images=[frame, frame], duration=[500, 100, 150]
) )
with Image.open(test_file) as im: with Image.open(test_file) as im:
assert isinstance(im, PngImagePlugin.PngImageFile)
assert im.n_frames == 1 assert im.n_frames == 1
assert "duration" not in im.info assert "duration" not in im.info
@ -463,6 +508,7 @@ def test_apng_save_duration_loop(tmp_path: Path) -> None:
duration=[500, 100, 150], duration=[500, 100, 150],
) )
with Image.open(test_file) as im: with Image.open(test_file) as im:
assert isinstance(im, PngImagePlugin.PngImageFile)
assert im.n_frames == 2 assert im.n_frames == 2
assert im.info["duration"] == 600 assert im.info["duration"] == 600
@ -473,6 +519,7 @@ def test_apng_save_duration_loop(tmp_path: Path) -> None:
frame.info["duration"] = 300 frame.info["duration"] = 300
frame.save(test_file, save_all=True, append_images=[frame, different_frame]) frame.save(test_file, save_all=True, append_images=[frame, different_frame])
with Image.open(test_file) as im: with Image.open(test_file) as im:
assert isinstance(im, PngImagePlugin.PngImageFile)
assert im.n_frames == 2 assert im.n_frames == 2
assert im.info["duration"] == 600 assert im.info["duration"] == 600

View File

@ -166,9 +166,9 @@ def test_save_dib(tmp_path: Path) -> None:
def test_rgba_bitfields() -> None: def test_rgba_bitfields() -> None:
# This test image has been manually hexedited # This test image has been manually hexedited
# to change the bitfield compression in the header from XBGR to RGBA # to change the bitfield compression in the header from XBGR to RGBA
with Image.open("Tests/images/rgb32bf-rgba.bmp") as im: with Image.open("Tests/images/rgb32bf-rgba.bmp") as bmp_im:
# So before the comparing the image, swap the channels # So before the comparing the image, swap the channels
b, g, r = im.split()[1:] b, g, r = bmp_im.split()[1:]
im = Image.merge("RGB", (r, g, b)) im = Image.merge("RGB", (r, g, b))
assert_image_equal_tofile(im, "Tests/images/bmp/q/rgb32bf-xbgr.bmp") assert_image_equal_tofile(im, "Tests/images/bmp/q/rgb32bf-xbgr.bmp")

View File

@ -61,6 +61,7 @@ def test_handler(tmp_path: Path) -> None:
def load(self, im: ImageFile.StubImageFile) -> Image.Image: def load(self, im: ImageFile.StubImageFile) -> Image.Image:
self.loaded = True self.loaded = True
assert im.fp is not None
im.fp.close() im.fp.close()
return Image.new("RGB", (1, 1)) return Image.new("RGB", (1, 1))

View File

@ -4,8 +4,6 @@ import pytest
from PIL import ContainerIO, Image from PIL import ContainerIO, Image
from .helper import hopper
TEST_FILE = "Tests/images/dummy.container" TEST_FILE = "Tests/images/dummy.container"
@ -15,15 +13,15 @@ def test_sanity() -> None:
def test_isatty() -> None: def test_isatty() -> None:
with hopper() as im: with open(TEST_FILE, "rb") as fh:
container = ContainerIO.ContainerIO(im, 0, 0) container = ContainerIO.ContainerIO(fh, 0, 0)
assert container.isatty() is False assert container.isatty() is False
def test_seekable() -> None: def test_seekable() -> None:
with hopper() as im: with open(TEST_FILE, "rb") as fh:
container = ContainerIO.ContainerIO(im, 0, 0) container = ContainerIO.ContainerIO(fh, 0, 0)
assert container.seekable() is True assert container.seekable() is True

View File

@ -26,6 +26,7 @@ def test_invalid_file() -> None:
no_cursors_file = "Tests/images/no_cursors.cur" no_cursors_file = "Tests/images/no_cursors.cur"
cur = CurImagePlugin.CurImageFile(TEST_FILE) cur = CurImagePlugin.CurImageFile(TEST_FILE)
assert cur.fp is not None
cur.fp.close() cur.fp.close()
with open(no_cursors_file, "rb") as cur.fp: with open(no_cursors_file, "rb") as cur.fp:
with pytest.raises(TypeError): with pytest.raises(TypeError):

View File

@ -65,12 +65,14 @@ def test_tell() -> None:
def test_n_frames() -> None: def test_n_frames() -> None:
with Image.open(TEST_FILE) as im: with Image.open(TEST_FILE) as im:
assert isinstance(im, DcxImagePlugin.DcxImageFile)
assert im.n_frames == 1 assert im.n_frames == 1
assert not im.is_animated assert not im.is_animated
def test_eoferror() -> None: def test_eoferror() -> None:
with Image.open(TEST_FILE) as im: with Image.open(TEST_FILE) as im:
assert isinstance(im, DcxImagePlugin.DcxImageFile)
n_frames = im.n_frames n_frames = im.n_frames
# Test seeking past the last frame # Test seeking past the last frame

View File

@ -50,6 +50,7 @@ TEST_FILE_UNCOMPRESSED_RGB_WITH_ALPHA = "Tests/images/uncompressed_rgb.dds"
) )
def test_sanity_dxt1_bc1(image_path: str) -> None: def test_sanity_dxt1_bc1(image_path: str) -> None:
"""Check DXT1 and BC1 images can be opened""" """Check DXT1 and BC1 images can be opened"""
target: Image.Image
with Image.open(TEST_FILE_DXT1.replace(".dds", ".png")) as target: with Image.open(TEST_FILE_DXT1.replace(".dds", ".png")) as target:
target = target.convert("RGBA") target = target.convert("RGBA")
with Image.open(image_path) as im: with Image.open(image_path) as im:
@ -331,11 +332,13 @@ def test_dxt5_colorblock_alpha_issue_4142() -> None:
with Image.open("Tests/images/dxt5-colorblock-alpha-issue-4142.dds") as im: with Image.open("Tests/images/dxt5-colorblock-alpha-issue-4142.dds") as im:
px = im.getpixel((0, 0)) px = im.getpixel((0, 0))
assert isinstance(px, tuple)
assert px[0] != 0 assert px[0] != 0
assert px[1] != 0 assert px[1] != 0
assert px[2] != 0 assert px[2] != 0
px = im.getpixel((1, 0)) px = im.getpixel((1, 0))
assert isinstance(px, tuple)
assert px[0] != 0 assert px[0] != 0
assert px[1] != 0 assert px[1] != 0
assert px[2] != 0 assert px[2] != 0

View File

@ -85,6 +85,8 @@ simple_eps_file_with_long_binary_data = (
def test_sanity(filename: str, size: tuple[int, int], scale: int) -> None: def test_sanity(filename: str, size: tuple[int, int], scale: int) -> None:
expected_size = tuple(s * scale for s in size) expected_size = tuple(s * scale for s in size)
with Image.open(filename) as image: with Image.open(filename) as image:
assert isinstance(image, EpsImagePlugin.EpsImageFile)
image.load(scale=scale) image.load(scale=scale)
assert image.mode == "RGB" assert image.mode == "RGB"
assert image.size == expected_size assert image.size == expected_size
@ -94,10 +96,12 @@ def test_sanity(filename: str, size: tuple[int, int], scale: int) -> None:
@pytest.mark.skipif(not HAS_GHOSTSCRIPT, reason="Ghostscript not available") @pytest.mark.skipif(not HAS_GHOSTSCRIPT, reason="Ghostscript not available")
def test_load() -> None: def test_load() -> None:
with Image.open(FILE1) as im: with Image.open(FILE1) as im:
assert im.load()[0, 0] == (255, 255, 255) px = im.load()
assert px is not None
assert px[0, 0] == (255, 255, 255)
# Test again now that it has already been loaded once # Test again now that it has already been loaded once
assert im.load()[0, 0] == (255, 255, 255) assert px[0, 0] == (255, 255, 255)
def test_binary() -> None: def test_binary() -> None:
@ -215,6 +219,8 @@ def test_showpage() -> None:
@pytest.mark.skipif(not HAS_GHOSTSCRIPT, reason="Ghostscript not available") @pytest.mark.skipif(not HAS_GHOSTSCRIPT, reason="Ghostscript not available")
def test_transparency() -> None: def test_transparency() -> None:
with Image.open("Tests/images/reqd_showpage.eps") as plot_image: with Image.open("Tests/images/reqd_showpage.eps") as plot_image:
assert isinstance(plot_image, EpsImagePlugin.EpsImageFile)
plot_image.load(transparency=True) plot_image.load(transparency=True)
assert plot_image.mode == "RGBA" assert plot_image.mode == "RGBA"
@ -239,8 +245,8 @@ def test_bytesio_object() -> None:
with Image.open(img_bytes) as img: with Image.open(img_bytes) as img:
img.load() img.load()
with Image.open(FILE1_COMPARE) as image1_scale1_compare: with Image.open(FILE1_COMPARE) as im:
image1_scale1_compare = image1_scale1_compare.convert("RGB") image1_scale1_compare = im.convert("RGB")
image1_scale1_compare.load() image1_scale1_compare.load()
assert_image_similar(img, image1_scale1_compare, 5) assert_image_similar(img, image1_scale1_compare, 5)
@ -265,16 +271,16 @@ def test_render_scale1() -> None:
# Zero bounding box # Zero bounding box
with Image.open(FILE1) as image1_scale1: with Image.open(FILE1) as image1_scale1:
image1_scale1.load() image1_scale1.load()
with Image.open(FILE1_COMPARE) as image1_scale1_compare: with Image.open(FILE1_COMPARE) as im:
image1_scale1_compare = image1_scale1_compare.convert("RGB") image1_scale1_compare = im.convert("RGB")
image1_scale1_compare.load() image1_scale1_compare.load()
assert_image_similar(image1_scale1, image1_scale1_compare, 5) assert_image_similar(image1_scale1, image1_scale1_compare, 5)
# Non-zero bounding box # Non-zero bounding box
with Image.open(FILE2) as image2_scale1: with Image.open(FILE2) as image2_scale1:
image2_scale1.load() image2_scale1.load()
with Image.open(FILE2_COMPARE) as image2_scale1_compare: with Image.open(FILE2_COMPARE) as im:
image2_scale1_compare = image2_scale1_compare.convert("RGB") image2_scale1_compare = im.convert("RGB")
image2_scale1_compare.load() image2_scale1_compare.load()
assert_image_similar(image2_scale1, image2_scale1_compare, 10) assert_image_similar(image2_scale1, image2_scale1_compare, 10)
@ -286,17 +292,19 @@ def test_render_scale2() -> None:
# Zero bounding box # Zero bounding box
with Image.open(FILE1) as image1_scale2: with Image.open(FILE1) as image1_scale2:
assert isinstance(image1_scale2, EpsImagePlugin.EpsImageFile)
image1_scale2.load(scale=2) image1_scale2.load(scale=2)
with Image.open(FILE1_COMPARE_SCALE2) as image1_scale2_compare: with Image.open(FILE1_COMPARE_SCALE2) as im:
image1_scale2_compare = image1_scale2_compare.convert("RGB") image1_scale2_compare = im.convert("RGB")
image1_scale2_compare.load() image1_scale2_compare.load()
assert_image_similar(image1_scale2, image1_scale2_compare, 5) assert_image_similar(image1_scale2, image1_scale2_compare, 5)
# Non-zero bounding box # Non-zero bounding box
with Image.open(FILE2) as image2_scale2: with Image.open(FILE2) as image2_scale2:
assert isinstance(image2_scale2, EpsImagePlugin.EpsImageFile)
image2_scale2.load(scale=2) image2_scale2.load(scale=2)
with Image.open(FILE2_COMPARE_SCALE2) as image2_scale2_compare: with Image.open(FILE2_COMPARE_SCALE2) as im:
image2_scale2_compare = image2_scale2_compare.convert("RGB") image2_scale2_compare = im.convert("RGB")
image2_scale2_compare.load() image2_scale2_compare.load()
assert_image_similar(image2_scale2, image2_scale2_compare, 10) assert_image_similar(image2_scale2, image2_scale2_compare, 10)
@ -304,9 +312,9 @@ def test_render_scale2() -> None:
@pytest.mark.skipif(not HAS_GHOSTSCRIPT, reason="Ghostscript not available") @pytest.mark.skipif(not HAS_GHOSTSCRIPT, reason="Ghostscript not available")
@pytest.mark.parametrize("filename", (FILE1, FILE2, "Tests/images/illu10_preview.eps")) @pytest.mark.parametrize("filename", (FILE1, FILE2, "Tests/images/illu10_preview.eps"))
def test_resize(filename: str) -> None: def test_resize(filename: str) -> None:
with Image.open(filename) as im: with Image.open(filename) as img:
new_size = (100, 100) new_size = (100, 100)
im = im.resize(new_size) im = img.resize(new_size)
assert im.size == new_size assert im.size == new_size

View File

@ -21,6 +21,8 @@ animated_test_file_with_prefix_chunk = "Tests/images/2422.flc"
def test_sanity() -> None: def test_sanity() -> None:
with Image.open(static_test_file) as im: with Image.open(static_test_file) as im:
assert isinstance(im, FliImagePlugin.FliImageFile)
im.load() im.load()
assert im.mode == "P" assert im.mode == "P"
assert im.size == (128, 128) assert im.size == (128, 128)
@ -28,6 +30,8 @@ def test_sanity() -> None:
assert not im.is_animated assert not im.is_animated
with Image.open(animated_test_file) as im: with Image.open(animated_test_file) 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"
@ -39,6 +43,8 @@ def test_prefix_chunk() -> None:
ImageFile.LOAD_TRUNCATED_IMAGES = True ImageFile.LOAD_TRUNCATED_IMAGES = True
try: try:
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"
@ -46,6 +52,7 @@ def test_prefix_chunk() -> 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]
@ -110,16 +117,19 @@ def test_palette_chunk_second() -> None:
def test_n_frames() -> None: def test_n_frames() -> None:
with Image.open(static_test_file) as im: with Image.open(static_test_file) as im:
assert isinstance(im, FliImagePlugin.FliImageFile)
assert im.n_frames == 1 assert im.n_frames == 1
assert not im.is_animated assert not im.is_animated
with Image.open(animated_test_file) as im: with Image.open(animated_test_file) as im:
assert isinstance(im, FliImagePlugin.FliImageFile)
assert im.n_frames == 384 assert im.n_frames == 384
assert im.is_animated assert im.is_animated
def test_eoferror() -> None: def test_eoferror() -> None:
with Image.open(animated_test_file) as im: with Image.open(animated_test_file) as im:
assert isinstance(im, FliImagePlugin.FliImageFile)
n_frames = im.n_frames n_frames = im.n_frames
# Test seeking past the last frame # Test seeking past the last frame

View File

@ -22,10 +22,11 @@ def test_sanity() -> None:
def test_close() -> None: def test_close() -> None:
with Image.open("Tests/images/input_bw_one_band.fpx") as im: with Image.open("Tests/images/input_bw_one_band.fpx") as im:
pass assert isinstance(im, FpxImagePlugin.FpxImageFile)
assert im.ole.fp.closed assert im.ole.fp.closed
im = Image.open("Tests/images/input_bw_one_band.fpx") im = Image.open("Tests/images/input_bw_one_band.fpx")
assert isinstance(im, FpxImagePlugin.FpxImageFile)
im.close() im.close()
assert im.ole.fp.closed assert im.ole.fp.closed

View File

@ -14,10 +14,12 @@ def test_gbr_file() -> None:
def test_load() -> None: def test_load() -> None:
with Image.open("Tests/images/gbr.gbr") as im: with Image.open("Tests/images/gbr.gbr") as im:
assert im.load()[0, 0] == (0, 0, 0, 0) px = im.load()
assert px is not None
assert px[0, 0] == (0, 0, 0, 0)
# Test again now that it has already been loaded once # Test again now that it has already been loaded once
assert im.load()[0, 0] == (0, 0, 0, 0) assert px[0, 0] == (0, 0, 0, 0)
def test_multiple_load_operations() -> None: def test_multiple_load_operations() -> None:

View File

@ -81,12 +81,16 @@ def test_invalid_file() -> None:
def test_l_mode_transparency() -> None: def test_l_mode_transparency() -> None:
with Image.open("Tests/images/no_palette_with_transparency.gif") as im: with Image.open("Tests/images/no_palette_with_transparency.gif") as im:
assert im.mode == "L" assert im.mode == "L"
assert im.load()[0, 0] == 128 px = im.load()
assert px is not None
assert px[0, 0] == 128
assert im.info["transparency"] == 255 assert im.info["transparency"] == 255
im.seek(1) im.seek(1)
assert im.mode == "L" assert im.mode == "L"
assert im.load()[0, 0] == 128 px = im.load()
assert px is not None
assert px[0, 0] == 128
def test_l_mode_after_rgb() -> None: def test_l_mode_after_rgb() -> None:
@ -221,6 +225,7 @@ def test_optimize_if_palette_can_be_reduced_by_half() -> None:
out = BytesIO() out = BytesIO()
im.save(out, "GIF", optimize=optimize) im.save(out, "GIF", optimize=optimize)
with Image.open(out) as reloaded: with Image.open(out) as reloaded:
assert reloaded.palette is not None
assert len(reloaded.palette.palette) // 3 == colors assert len(reloaded.palette.palette) // 3 == colors
@ -277,6 +282,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
@ -302,10 +308,14 @@ def test_roundtrip_save_all_1(tmp_path: Path) -> None:
), ),
) )
def test_loading_multiple_palettes(path: str, mode: str) -> None: def test_loading_multiple_palettes(path: str, mode: str) -> None:
im: Image.Image
with Image.open(path) as im: with Image.open(path) as im:
assert im.mode == "P" assert im.mode == "P"
assert im.palette is not None
first_frame_colors = im.palette.colors.keys() first_frame_colors = im.palette.colors.keys()
original_color = im.convert("RGB").load()[0, 0] px = im.convert("RGB").load()
assert px is not None
original_color = px[0, 0]
im.seek(1) im.seek(1)
assert im.mode == mode assert im.mode == mode
@ -313,10 +323,14 @@ def test_loading_multiple_palettes(path: str, mode: str) -> None:
im = im.convert("RGB") im = im.convert("RGB")
# Check a color only from the old palette # Check a color only from the old palette
assert im.load()[0, 0] == original_color px = im.load()
assert px is not None
assert px[0, 0] == original_color
# Check a color from the new palette # Check a color from the new palette
assert im.load()[24, 24] not in first_frame_colors px = im.load()
assert px is not None
assert px[24, 24] not in first_frame_colors
def test_headers_saving_for_animated_gifs(tmp_path: Path) -> None: def test_headers_saving_for_animated_gifs(tmp_path: Path) -> None:
@ -334,7 +348,7 @@ def test_headers_saving_for_animated_gifs(tmp_path: Path) -> None:
def test_palette_handling(tmp_path: Path) -> None: def test_palette_handling(tmp_path: Path) -> None:
# see https://github.com/python-pillow/Pillow/issues/513 # see https://github.com/python-pillow/Pillow/issues/513
im: Image.Image
with Image.open(TEST_GIF) as im: with Image.open(TEST_GIF) as im:
im = im.convert("RGB") im = im.convert("RGB")
@ -358,8 +372,8 @@ def test_palette_434(tmp_path: Path) -> None:
return reloaded return reloaded
orig = "Tests/images/test.colors.gif" im: Image.Image
with Image.open(orig) as im: with Image.open("Tests/images/test.colors.gif") as im:
with roundtrip(im) as reloaded: with roundtrip(im) as reloaded:
assert_image_similar(im, reloaded, 1) assert_image_similar(im, reloaded, 1)
with roundtrip(im, optimize=True) as reloaded: with roundtrip(im, optimize=True) as reloaded:
@ -374,6 +388,7 @@ def test_palette_434(tmp_path: Path) -> None:
@pytest.mark.skipif(not netpbm_available(), reason="Netpbm not available") @pytest.mark.skipif(not netpbm_available(), reason="Netpbm not available")
def test_save_netpbm_bmp_mode(tmp_path: Path) -> None: def test_save_netpbm_bmp_mode(tmp_path: Path) -> None:
img: Image.Image
with Image.open(TEST_GIF) as img: with Image.open(TEST_GIF) as img:
img = img.convert("RGB") img = img.convert("RGB")
@ -386,6 +401,7 @@ def test_save_netpbm_bmp_mode(tmp_path: Path) -> None:
@pytest.mark.skipif(not netpbm_available(), reason="Netpbm not available") @pytest.mark.skipif(not netpbm_available(), reason="Netpbm not available")
def test_save_netpbm_l_mode(tmp_path: Path) -> None: def test_save_netpbm_l_mode(tmp_path: Path) -> None:
img: Image.Image
with Image.open(TEST_GIF) as img: with Image.open(TEST_GIF) as img:
img = img.convert("L") img = img.convert("L")
@ -438,10 +454,12 @@ def test_seek_rewind() -> None:
def test_n_frames(path: str, n_frames: int) -> None: def test_n_frames(path: str, n_frames: int) -> None:
# Test is_animated before n_frames # Test is_animated before n_frames
with Image.open(path) as im: with Image.open(path) as im:
assert isinstance(im, GifImagePlugin.GifImageFile)
assert im.is_animated == (n_frames != 1) assert im.is_animated == (n_frames != 1)
# Test is_animated after n_frames # Test is_animated after n_frames
with Image.open(path) as im: with Image.open(path) as im:
assert isinstance(im, GifImagePlugin.GifImageFile)
assert im.n_frames == n_frames assert im.n_frames == n_frames
assert im.is_animated == (n_frames != 1) assert im.is_animated == (n_frames != 1)
@ -451,6 +469,7 @@ def test_no_change() -> None:
with Image.open("Tests/images/dispose_bgnd.gif") as im: with Image.open("Tests/images/dispose_bgnd.gif") as im:
im.seek(1) im.seek(1)
expected = im.copy() expected = im.copy()
assert isinstance(im, GifImagePlugin.GifImageFile)
assert im.n_frames == 5 assert im.n_frames == 5
assert_image_equal(im, expected) assert_image_equal(im, expected)
@ -458,17 +477,20 @@ def test_no_change() -> None:
with Image.open("Tests/images/dispose_bgnd.gif") as im: with Image.open("Tests/images/dispose_bgnd.gif") as im:
im.seek(3) im.seek(3)
expected = im.copy() expected = im.copy()
assert isinstance(im, GifImagePlugin.GifImageFile)
assert im.is_animated assert im.is_animated
assert_image_equal(im, expected) assert_image_equal(im, expected)
with Image.open("Tests/images/comment_after_only_frame.gif") as im: with Image.open("Tests/images/comment_after_only_frame.gif") as im:
expected = Image.new("P", (1, 1)) expected = Image.new("P", (1, 1))
assert isinstance(im, GifImagePlugin.GifImageFile)
assert not im.is_animated assert not im.is_animated
assert_image_equal(im, expected) assert_image_equal(im, expected)
def test_eoferror() -> None: def test_eoferror() -> None:
with Image.open(TEST_GIF) as im: with Image.open(TEST_GIF) as im:
assert isinstance(im, GifImagePlugin.GifImageFile)
n_frames = im.n_frames n_frames = im.n_frames
# Test seeking past the last frame # Test seeking past the last frame
@ -483,11 +505,13 @@ def test_eoferror() -> None:
def test_first_frame_transparency() -> None: def test_first_frame_transparency() -> None:
with Image.open("Tests/images/first_frame_transparency.gif") as im: with Image.open("Tests/images/first_frame_transparency.gif") as im:
px = im.load() px = im.load()
assert px is not None
assert px[0, 0] == im.info["transparency"] assert px[0, 0] == im.info["transparency"]
def test_dispose_none() -> None: def test_dispose_none() -> None:
with Image.open("Tests/images/dispose_none.gif") as img: with Image.open("Tests/images/dispose_none.gif") as img:
assert isinstance(img, GifImagePlugin.GifImageFile)
try: try:
while True: while True:
img.seek(img.tell() + 1) img.seek(img.tell() + 1)
@ -511,6 +535,7 @@ def test_dispose_none_load_end() -> None:
def test_dispose_background() -> None: def test_dispose_background() -> None:
with Image.open("Tests/images/dispose_bgnd.gif") as img: with Image.open("Tests/images/dispose_bgnd.gif") as img:
assert isinstance(img, GifImagePlugin.GifImageFile)
try: try:
while True: while True:
img.seek(img.tell() + 1) img.seek(img.tell() + 1)
@ -523,7 +548,10 @@ def test_dispose_background_transparency() -> None:
with Image.open("Tests/images/dispose_bgnd_transparency.gif") as img: with Image.open("Tests/images/dispose_bgnd_transparency.gif") as img:
img.seek(2) img.seek(2)
px = img.load() px = img.load()
assert px[35, 30][3] == 0 assert px is not None
value = px[35, 30]
assert isinstance(value, tuple)
assert value[3] == 0
@pytest.mark.parametrize( @pytest.mark.parametrize(
@ -565,6 +593,7 @@ def test_transparent_dispose(
def test_dispose_previous() -> None: def test_dispose_previous() -> None:
with Image.open("Tests/images/dispose_prev.gif") as img: with Image.open("Tests/images/dispose_prev.gif") as img:
assert isinstance(img, GifImagePlugin.GifImageFile)
try: try:
while True: while True:
img.seek(img.tell() + 1) img.seek(img.tell() + 1)
@ -602,6 +631,7 @@ def test_save_dispose(tmp_path: Path) -> None:
for method in range(0, 4): for method in range(0, 4):
im_list[0].save(out, save_all=True, append_images=im_list[1:], disposal=method) im_list[0].save(out, save_all=True, append_images=im_list[1:], disposal=method)
with Image.open(out) as img: with Image.open(out) as img:
assert isinstance(img, GifImagePlugin.GifImageFile)
for _ in range(2): for _ in range(2):
img.seek(img.tell() + 1) img.seek(img.tell() + 1)
assert img.disposal_method == method assert img.disposal_method == method
@ -615,6 +645,7 @@ def test_save_dispose(tmp_path: Path) -> None:
) )
with Image.open(out) as img: with Image.open(out) as img:
assert isinstance(img, GifImagePlugin.GifImageFile)
for i in range(2): for i in range(2):
img.seek(img.tell() + 1) img.seek(img.tell() + 1)
assert img.disposal_method == i + 1 assert img.disposal_method == i + 1
@ -737,6 +768,7 @@ def test_dispose2_background_frame(tmp_path: Path) -> None:
im_list[0].save(out, save_all=True, append_images=im_list[1:], disposal=2) im_list[0].save(out, save_all=True, append_images=im_list[1:], disposal=2)
with Image.open(out) as im: with Image.open(out) as im:
assert isinstance(im, GifImagePlugin.GifImageFile)
assert im.n_frames == 3 assert im.n_frames == 3
@ -903,6 +935,8 @@ def test_identical_frames(tmp_path: Path) -> None:
out, save_all=True, append_images=im_list[1:], duration=duration_list out, save_all=True, append_images=im_list[1:], duration=duration_list
) )
with Image.open(out) as reread: with Image.open(out) as reread:
assert isinstance(reread, GifImagePlugin.GifImageFile)
# Assert that the first three frames were combined # Assert that the first three frames were combined
assert reread.n_frames == 2 assert reread.n_frames == 2
@ -932,6 +966,8 @@ def test_identical_frames_to_single_frame(
im_list[0].save(out, save_all=True, append_images=im_list[1:], duration=duration) im_list[0].save(out, save_all=True, append_images=im_list[1:], duration=duration)
with Image.open(out) as reread: with Image.open(out) as reread:
assert isinstance(reread, GifImagePlugin.GifImageFile)
# Assert that all frames were combined # Assert that all frames were combined
assert reread.n_frames == 1 assert reread.n_frames == 1
@ -979,9 +1015,9 @@ def test_webp_background(tmp_path: Path) -> None:
# Test opaque WebP background # Test opaque WebP background
if features.check("webp"): if features.check("webp"):
with Image.open("Tests/images/hopper.webp") as im: with Image.open("Tests/images/hopper.webp") as img:
assert im.info["background"] == (255, 255, 255, 255) assert img.info["background"] == (255, 255, 255, 255)
im.save(out) img.save(out)
# Test non-opaque WebP background # Test non-opaque WebP background
im = Image.new("L", (100, 100), "#000") im = Image.new("L", (100, 100), "#000")
@ -990,6 +1026,7 @@ def test_webp_background(tmp_path: Path) -> None:
def test_comment(tmp_path: Path) -> None: def test_comment(tmp_path: Path) -> None:
im: Image.Image
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"
@ -1118,6 +1155,7 @@ def test_append_images(tmp_path: Path) -> None:
im.copy().save(out, save_all=True, append_images=ims) im.copy().save(out, save_all=True, append_images=ims)
with Image.open(out) as reread: with Image.open(out) as reread:
assert isinstance(reread, GifImagePlugin.GifImageFile)
assert reread.n_frames == 3 assert reread.n_frames == 3
# Tests appending using a generator # Tests appending using a generator
@ -1127,6 +1165,7 @@ def test_append_images(tmp_path: Path) -> None:
im.save(out, save_all=True, append_images=im_generator(ims)) im.save(out, save_all=True, append_images=im_generator(ims))
with Image.open(out) as reread: with Image.open(out) as reread:
assert isinstance(reread, GifImagePlugin.GifImageFile)
assert reread.n_frames == 3 assert reread.n_frames == 3
# Tests appending single and multiple frame images # Tests appending single and multiple frame images
@ -1135,6 +1174,7 @@ def test_append_images(tmp_path: Path) -> None:
im.save(out, save_all=True, append_images=[im2]) im.save(out, save_all=True, append_images=[im2])
with Image.open(out) as reread: with Image.open(out) as reread:
assert isinstance(reread, GifImagePlugin.GifImageFile)
assert reread.n_frames == 10 assert reread.n_frames == 10
@ -1235,6 +1275,7 @@ def test_bbox(tmp_path: Path) -> None:
im.save(out, save_all=True, append_images=ims) im.save(out, save_all=True, append_images=ims)
with Image.open(out) as reread: with Image.open(out) as reread:
assert isinstance(reread, GifImagePlugin.GifImageFile)
assert reread.n_frames == 2 assert reread.n_frames == 2
@ -1247,6 +1288,7 @@ def test_bbox_alpha(tmp_path: Path) -> None:
im.save(out, save_all=True, append_images=[im2]) im.save(out, save_all=True, append_images=[im2])
with Image.open(out) as reread: with Image.open(out) as reread:
assert isinstance(reread, GifImagePlugin.GifImageFile)
assert reread.n_frames == 2 assert reread.n_frames == 2
@ -1308,6 +1350,9 @@ 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_image_equal(im.convert("RGB"), frames[0].convert("RGB")) assert_image_equal(im.convert("RGB"), frames[0].convert("RGB"))
assert isinstance(im, GifImagePlugin.GifImageFile)
assert im.palette is not None
assert im.global_palette is not None
assert im.palette.palette == im.global_palette.palette assert im.palette.palette == im.global_palette.palette
im.seek(1) im.seek(1)
@ -1373,7 +1418,9 @@ def test_getdata() -> None:
def test_lzw_bits() -> None: def test_lzw_bits() -> None:
# see https://github.com/python-pillow/Pillow/issues/2811 # see https://github.com/python-pillow/Pillow/issues/2811
with Image.open("Tests/images/issue_2811.gif") as im: with Image.open("Tests/images/issue_2811.gif") as im:
assert im.tile[0][3][0] == 11 # LZW bits args = im.tile[0][3]
assert isinstance(args, tuple)
assert args[0] == 11 # LZW bits
# codec error prepatch # codec error prepatch
im.load() im.load()
@ -1398,6 +1445,7 @@ def test_extents(
GifImagePlugin.LOADING_STRATEGY = loading_strategy GifImagePlugin.LOADING_STRATEGY = loading_strategy
try: try:
with Image.open("Tests/images/" + test_file) as im: with Image.open("Tests/images/" + test_file) as im:
assert isinstance(im, GifImagePlugin.GifImageFile)
assert im.size == (100, 100) assert im.size == (100, 100)
# Check that n_frames does not change the size # Check that n_frames does not change the size
@ -1428,4 +1476,8 @@ def test_saving_rgba(tmp_path: Path) -> None:
with Image.open(out) as reloaded: with Image.open(out) as reloaded:
reloaded_rgba = reloaded.convert("RGBA") reloaded_rgba = reloaded.convert("RGBA")
assert reloaded_rgba.load()[0, 0][3] == 0 px = reloaded_rgba.load()
assert px is not None
value = px[0, 0]
assert isinstance(value, tuple)
assert value[3] == 0

View File

@ -59,8 +59,9 @@ def test_handler(tmp_path: Path) -> None:
def open(self, im: Image.Image) -> None: def open(self, im: Image.Image) -> None:
self.opened = True self.opened = True
def load(self, im: Image.Image) -> Image.Image: def load(self, im: ImageFile.ImageFile) -> Image.Image:
self.loaded = True self.loaded = True
assert im.fp is not None
im.fp.close() im.fp.close()
return Image.new("RGB", (1, 1)) return Image.new("RGB", (1, 1))

View File

@ -61,8 +61,9 @@ def test_handler(tmp_path: Path) -> None:
def open(self, im: Image.Image) -> None: def open(self, im: Image.Image) -> None:
self.opened = True self.opened = True
def load(self, im: Image.Image) -> Image.Image: def load(self, im: ImageFile.ImageFile) -> Image.Image:
self.loaded = True self.loaded = True
assert im.fp is not None
im.fp.close() im.fp.close()
return Image.new("RGB", (1, 1)) return Image.new("RGB", (1, 1))

View File

@ -30,10 +30,14 @@ def test_sanity() -> None:
def test_load() -> None: def test_load() -> None:
with Image.open(TEST_FILE) as im: with Image.open(TEST_FILE) as im:
assert im.load()[0, 0] == (0, 0, 0, 0) px = im.load()
assert px is not None
assert px[0, 0] == (0, 0, 0, 0)
# Test again now that it has already been loaded once # Test again now that it has already been loaded once
assert im.load()[0, 0] == (0, 0, 0, 0) px = im.load()
assert px is not None
assert px[0, 0] == (0, 0, 0, 0)
def test_save(tmp_path: Path) -> None: def test_save(tmp_path: Path) -> None:
@ -63,6 +67,7 @@ def test_save_append_images(tmp_path: Path) -> None:
assert_image_similar_tofile(im, temp_file, 1) assert_image_similar_tofile(im, temp_file, 1)
with Image.open(temp_file) as reread: with Image.open(temp_file) as reread:
assert isinstance(reread, IcnsImagePlugin.IcnsImageFile)
reread.size = (16, 16, 2) reread.size = (16, 16, 2)
reread.load() reread.load()
assert_image_equal(reread, provided_im) assert_image_equal(reread, provided_im)
@ -84,6 +89,7 @@ def test_sizes() -> None:
# Check that we can load all of the sizes, and that the final pixel # Check that we can load all of the sizes, and that the final pixel
# dimensions are as expected # dimensions are as expected
with Image.open(TEST_FILE) as im: with Image.open(TEST_FILE) as im:
assert isinstance(im, IcnsImagePlugin.IcnsImageFile)
for w, h, r in im.info["sizes"]: for w, h, r in im.info["sizes"]:
wr = w * r wr = w * r
hr = h * r hr = h * r
@ -105,6 +111,7 @@ def test_older_icon() -> None:
wr = w * r wr = w * r
hr = h * r hr = h * r
with Image.open("Tests/images/pillow2.icns") as im2: with Image.open("Tests/images/pillow2.icns") as im2:
assert isinstance(im2, IcnsImagePlugin.IcnsImageFile)
im2.size = (w, h, r) im2.size = (w, h, r)
im2.load() im2.load()
assert im2.mode == "RGBA" assert im2.mode == "RGBA"
@ -122,6 +129,7 @@ def test_jp2_icon() -> None:
wr = w * r wr = w * r
hr = h * r hr = h * r
with Image.open("Tests/images/pillow3.icns") as im2: with Image.open("Tests/images/pillow3.icns") as im2:
assert isinstance(im2, IcnsImagePlugin.IcnsImageFile)
im2.size = (w, h, r) im2.size = (w, h, r)
im2.load() im2.load()
assert im2.mode == "RGBA" assert im2.mode == "RGBA"

View File

@ -24,7 +24,9 @@ def test_sanity() -> None:
def test_load() -> None: def test_load() -> None:
with Image.open(TEST_ICO_FILE) as im: with Image.open(TEST_ICO_FILE) as im:
assert im.load()[0, 0] == (1, 1, 9, 255) px = im.load()
assert px is not None
assert px[0, 0] == (1, 1, 9, 255)
def test_mask() -> None: def test_mask() -> None:
@ -75,6 +77,7 @@ def test_save_to_bytes() -> None:
# The other one # The other one
output.seek(0) output.seek(0)
with Image.open(output) as reloaded: with Image.open(output) as reloaded:
assert isinstance(reloaded, IcoImagePlugin.IcoImageFile)
reloaded.size = (32, 32) reloaded.size = (32, 32)
assert im.mode == reloaded.mode assert im.mode == reloaded.mode
@ -92,6 +95,7 @@ def test_getpixel(tmp_path: Path) -> None:
im.save(temp_file, "ico", sizes=[(32, 32), (64, 64)]) im.save(temp_file, "ico", sizes=[(32, 32), (64, 64)])
with Image.open(temp_file) as reloaded: with Image.open(temp_file) as reloaded:
assert isinstance(reloaded, IcoImagePlugin.IcoImageFile)
reloaded.load() reloaded.load()
reloaded.size = (32, 32) reloaded.size = (32, 32)
@ -165,6 +169,7 @@ def test_save_to_bytes_bmp(mode: str) -> None:
# The other one # The other one
output.seek(0) output.seek(0)
with Image.open(output) as reloaded: with Image.open(output) as reloaded:
assert isinstance(reloaded, IcoImagePlugin.IcoImageFile)
reloaded.size = (32, 32) reloaded.size = (32, 32)
assert "RGBA" == reloaded.mode assert "RGBA" == reloaded.mode
@ -176,6 +181,7 @@ def test_save_to_bytes_bmp(mode: str) -> None:
def test_incorrect_size() -> None: def test_incorrect_size() -> None:
with Image.open(TEST_ICO_FILE) as im: with Image.open(TEST_ICO_FILE) as im:
assert isinstance(im, IcoImagePlugin.IcoImageFile)
with pytest.raises(ValueError): with pytest.raises(ValueError):
im.size = (1, 1) im.size = (1, 1)
@ -217,6 +223,7 @@ def test_save_append_images(tmp_path: Path) -> None:
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:
assert isinstance(reread, IcoImagePlugin.IcoImageFile)
assert_image_equal(reread, hopper("RGBA")) assert_image_equal(reread, hopper("RGBA"))
reread.size = (32, 32) reread.size = (32, 32)
@ -253,8 +260,7 @@ def test_truncated_mask() -> None:
try: try:
with Image.open(io.BytesIO(data)) as im: with Image.open(io.BytesIO(data)) as im:
with Image.open("Tests/images/hopper_mask.png") as expected: assert im.mode == "1"
assert im.mode == "1"
# 32 bpp # 32 bpp
output = io.BytesIO() output = io.BytesIO()

View File

@ -64,12 +64,14 @@ def test_tell() -> None:
def test_n_frames() -> None: def test_n_frames() -> None:
with Image.open(TEST_IM) as im: with Image.open(TEST_IM) as im:
assert isinstance(im, ImImagePlugin.ImImageFile)
assert im.n_frames == 1 assert im.n_frames == 1
assert not im.is_animated assert not im.is_animated
def test_eoferror() -> None: def test_eoferror() -> None:
with Image.open(TEST_IM) as im: with Image.open(TEST_IM) as im:
assert isinstance(im, ImImagePlugin.ImImageFile)
n_frames = im.n_frames n_frames = im.n_frames
# Test seeking past the last frame # Test seeking past the last frame

View File

@ -91,6 +91,7 @@ class TestFileJpeg:
def test_app(self) -> None: def test_app(self) -> None:
# Test APP/COM reader (@PIL135) # Test APP/COM reader (@PIL135)
with Image.open(TEST_FILE) as im: with Image.open(TEST_FILE) as im:
assert isinstance(im, JpegImagePlugin.JpegImageFile)
assert im.applist[0] == ("APP0", b"JFIF\x00\x01\x01\x01\x00`\x00`\x00\x00") assert im.applist[0] == ("APP0", b"JFIF\x00\x01\x01\x01\x00`\x00`\x00\x00")
assert im.applist[1] == ( assert im.applist[1] == (
"COM", "COM",
@ -131,26 +132,30 @@ class TestFileJpeg:
f = "Tests/images/pil_sample_cmyk.jpg" f = "Tests/images/pil_sample_cmyk.jpg"
with Image.open(f) as im: with Image.open(f) as im:
# the source image has red pixels in the upper left corner. # the source image has red pixels in the upper left corner.
c, m, y, k = (x / 255.0 for x in im.getpixel((0, 0))) value = im.getpixel((0, 0))
assert isinstance(value, tuple)
c, m, y, k = (x / 255.0 for x in value)
assert c == 0.0 assert c == 0.0
assert m > 0.8 assert m > 0.8
assert y > 0.8 assert y > 0.8
assert k == 0.0 assert k == 0.0
# the opposite corner is black # the opposite corner is black
c, m, y, k = ( value = im.getpixel((im.size[0] - 1, im.size[1] - 1))
x / 255.0 for x in im.getpixel((im.size[0] - 1, im.size[1] - 1)) assert isinstance(value, tuple)
) c, m, y, k = (x / 255.0 for x in value)
assert k > 0.9 assert k > 0.9
# roundtrip, and check again # roundtrip, and check again
im = self.roundtrip(im) im = self.roundtrip(im)
c, m, y, k = (x / 255.0 for x in im.getpixel((0, 0))) value = im.getpixel((0, 0))
assert isinstance(value, tuple)
c, m, y, k = (x / 255.0 for x in value)
assert c == 0.0 assert c == 0.0
assert m > 0.8 assert m > 0.8
assert y > 0.8 assert y > 0.8
assert k == 0.0 assert k == 0.0
c, m, y, k = ( value = im.getpixel((im.size[0] - 1, im.size[1] - 1))
x / 255.0 for x in im.getpixel((im.size[0] - 1, im.size[1] - 1)) assert isinstance(value, tuple)
) c, m, y, k = (x / 255.0 for x in value)
assert k > 0.9 assert k > 0.9
def test_rgb(self) -> None: def test_rgb(self) -> None:
@ -309,6 +314,8 @@ class TestFileJpeg:
def test_exif_typeerror(self) -> None: def test_exif_typeerror(self) -> None:
with Image.open("Tests/images/exif_typeerror.jpg") as im: with Image.open("Tests/images/exif_typeerror.jpg") as im:
assert isinstance(im, JpegImagePlugin.JpegImageFile)
# Should not raise a TypeError # Should not raise a TypeError
im._getexif() im._getexif()
@ -324,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 = str(tmp_path / "temp.jpg") f = str(tmp_path / "temp.jpg")
@ -334,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:
@ -363,6 +374,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]
@ -396,13 +408,18 @@ 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()
@ -478,7 +495,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:
@ -490,6 +509,7 @@ class TestFileJpeg:
def test_mp(self) -> None: def test_mp(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)
assert im._getmp() is None assert im._getmp() is None
def test_quality_keep(self, tmp_path: Path) -> None: def test_quality_keep(self, tmp_path: Path) -> None:
@ -547,12 +567,14 @@ class TestFileJpeg:
f = str(tmp_path / "temp.jpg") f = str(tmp_path / "temp.jpg")
im.save(f, qtables=[[n] * 64] * n) im.save(f, qtables=[[n] * 64] * n)
with Image.open(f) as im: with Image.open(f) as im:
assert isinstance(im, JpegImagePlugin.JpegImageFile)
assert len(im.quantization) == n assert len(im.quantization) == n
reloaded = self.roundtrip(im, qtables="keep") reloaded = self.roundtrip(im, qtables="keep")
assert im.quantization == reloaded.quantization assert im.quantization == reloaded.quantization
assert max(reloaded.quantization[0]) <= 255 assert max(reloaded.quantization[0]) <= 255
with Image.open("Tests/images/hopper.jpg") as im: with Image.open("Tests/images/hopper.jpg") as im:
assert isinstance(im, JpegImagePlugin.JpegImageFile)
qtables = im.quantization qtables = im.quantization
reloaded = self.roundtrip(im, qtables=qtables, subsampling=0) reloaded = self.roundtrip(im, qtables=qtables, subsampling=0)
assert im.quantization == reloaded.quantization assert im.quantization == reloaded.quantization
@ -652,6 +674,7 @@ class TestFileJpeg:
def test_load_16bit_qtables(self) -> None: def test_load_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)
assert len(im.quantization) == 2 assert len(im.quantization) == 2
assert len(im.quantization[0]) == 64 assert len(im.quantization[0]) == 64
assert max(im.quantization[0]) > 255 assert max(im.quantization[0]) > 255
@ -659,10 +682,12 @@ 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:
im2 = self.roundtrip(im, qtables="keep") im2 = self.roundtrip(im, qtables="keep")
assert isinstance(im, JpegImagePlugin.JpegImageFile)
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]
@ -693,9 +718,10 @@ class TestFileJpeg:
@pytest.mark.skipif(not djpeg_available(), reason="djpeg not available") @pytest.mark.skipif(not djpeg_available(), reason="djpeg not available")
def test_load_djpeg(self) -> None: def test_load_djpeg(self) -> None:
with Image.open(TEST_FILE) as img: with Image.open(TEST_FILE) as im:
img.load_djpeg() assert isinstance(im, JpegImagePlugin.JpegImageFile)
assert_image_similar_tofile(img, TEST_FILE, 5) im.load_djpeg()
assert_image_similar_tofile(im, TEST_FILE, 5)
@pytest.mark.skipif(not cjpeg_available(), reason="cjpeg not available") @pytest.mark.skipif(not cjpeg_available(), reason="cjpeg not available")
def test_save_cjpeg(self, tmp_path: Path) -> None: def test_save_cjpeg(self, tmp_path: Path) -> None:
@ -868,7 +894,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:
@ -896,6 +925,7 @@ class TestFileJpeg:
def test_photoshop_malformed_and_multiple(self) -> None: def test_photoshop_malformed_and_multiple(self) -> None:
with Image.open("Tests/images/app13-multiple.jpg") as im: with Image.open("Tests/images/app13-multiple.jpg") as im:
assert isinstance(im, JpegImagePlugin.JpegImageFile)
assert "photoshop" in im.info assert "photoshop" in im.info
assert 24 == len(im.info["photoshop"]) assert 24 == len(im.info["photoshop"])
apps_13_lengths = [len(v) for k, v in im.applist if k == "APP13"] apps_13_lengths = [len(v) for k, v in im.applist if k == "APP13"]
@ -1020,7 +1050,7 @@ class TestFileJpeg:
with Image.open(TEST_FILE) as im: with Image.open(TEST_FILE) as im:
im.tile = [ im.tile = [
("INFINITE", (0, 0, 128, 128), 0, ("RGB", 0, 1)), ImageFile._Tile("INFINITE", (0, 0, 128, 128), 0, ("RGB", 0, 1)),
] ]
ImageFile.LOAD_TRUNCATED_IMAGES = True ImageFile.LOAD_TRUNCATED_IMAGES = True
im.load() im.load()
@ -1067,6 +1097,7 @@ class TestFileJpeg:
def test_deprecation(self) -> None: def test_deprecation(self) -> None:
with Image.open(TEST_FILE) as im: with Image.open(TEST_FILE) as im:
assert isinstance(im, JpegImagePlugin.JpegImageFile)
with pytest.warns(DeprecationWarning): with pytest.warns(DeprecationWarning):
assert im.huffman_ac == {} assert im.huffman_ac == {}
with pytest.warns(DeprecationWarning): with pytest.warns(DeprecationWarning):
@ -1083,8 +1114,9 @@ class TestFileCloseW32:
im.save(tmpfile) im.save(tmpfile)
im = Image.open(tmpfile) im = Image.open(tmpfile)
assert im.fp is not None
assert not im.fp.closed
fp = im.fp fp = im.fp
assert not fp.closed
with pytest.raises(OSError): with pytest.raises(OSError):
os.remove(tmpfile) os.remove(tmpfile)
im.load() im.load()

View File

@ -54,6 +54,7 @@ def test_sanity() -> None:
with Image.open("Tests/images/test-card-lossless.jp2") as im: with Image.open("Tests/images/test-card-lossless.jp2") as im:
px = im.load() px = im.load()
assert px is not None
assert px[0, 0] == (0, 0, 0) assert px[0, 0] == (0, 0, 0)
assert im.mode == "RGB" assert im.mode == "RGB"
assert im.size == (640, 480) assert im.size == (640, 480)
@ -154,7 +155,7 @@ def test_reduce() -> None:
with Image.open("Tests/images/test-card-lossless.jp2") as im: with Image.open("Tests/images/test-card-lossless.jp2") as im:
assert callable(im.reduce) assert callable(im.reduce)
im.reduce = 2 im.reduce = 2 # type: ignore[method-assign, assignment]
assert im.reduce == 2 assert im.reduce == 2
im.load() im.load()
@ -221,12 +222,14 @@ def test_layers() -> None:
out.seek(0) out.seek(0)
with Image.open(out) as im: with Image.open(out) as im:
assert isinstance(im, Jpeg2KImagePlugin.Jpeg2KImageFile)
im.layers = 1 im.layers = 1
im.load() im.load()
assert_image_similar(im, test_card, 13) assert_image_similar(im, test_card, 13)
out.seek(0) out.seek(0)
with Image.open(out) as im: with Image.open(out) as im:
assert isinstance(im, Jpeg2KImagePlugin.Jpeg2KImageFile)
im.layers = 3 im.layers = 3
im.load() im.load()
assert_image_similar(im, test_card, 0.4) assert_image_similar(im, test_card, 0.4)
@ -397,6 +400,7 @@ def test_subsampling_decode(name: str) -> None:
def test_pclr() -> None: def test_pclr() -> None:
with Image.open(f"{EXTRA_DIR}/issue104_jpxstream.jp2") as im: with Image.open(f"{EXTRA_DIR}/issue104_jpxstream.jp2") as im:
assert im.mode == "P" assert im.mode == "P"
assert im.palette is not None
assert len(im.palette.colors) == 256 assert len(im.palette.colors) == 256
assert im.palette.colors[(255, 255, 255)] == 0 assert im.palette.colors[(255, 255, 255)] == 0
@ -404,6 +408,7 @@ def test_pclr() -> None:
f"{EXTRA_DIR}/147af3f1083de4393666b7d99b01b58b_signal_sigsegv_130c531_6155_5136.jp2" f"{EXTRA_DIR}/147af3f1083de4393666b7d99b01b58b_signal_sigsegv_130c531_6155_5136.jp2"
) as im: ) as im:
assert im.mode == "P" assert im.mode == "P"
assert im.palette is not None
assert len(im.palette.colors) == 139 assert len(im.palette.colors) == 139
assert im.palette.colors[(0, 0, 0, 0)] == 0 assert im.palette.colors[(0, 0, 0, 0)] == 0

View File

@ -11,7 +11,15 @@ from typing import Any, NamedTuple
import pytest import pytest
from PIL import Image, ImageFilter, ImageOps, TiffImagePlugin, TiffTags, features from PIL import (
Image,
ImageFile,
ImageFilter,
ImageOps,
TiffImagePlugin,
TiffTags,
features,
)
from PIL.TiffImagePlugin import OSUBFILETYPE, SAMPLEFORMAT, STRIPOFFSETS, SUBIFD from PIL.TiffImagePlugin import OSUBFILETYPE, SAMPLEFORMAT, STRIPOFFSETS, SUBIFD
from .helper import ( from .helper import (
@ -27,7 +35,7 @@ from .helper import (
@skip_unless_feature("libtiff") @skip_unless_feature("libtiff")
class LibTiffTestCase: class LibTiffTestCase:
def _assert_noerr(self, tmp_path: Path, im: TiffImagePlugin.TiffImageFile) -> None: def _assert_noerr(self, tmp_path: Path, im: ImageFile.ImageFile) -> None:
"""Helper tests that assert basic sanity about the g4 tiff reading""" """Helper tests that assert basic sanity about the g4 tiff reading"""
# 1 bit # 1 bit
assert im.mode == "1" assert im.mode == "1"
@ -36,6 +44,7 @@ class LibTiffTestCase:
im.load() im.load()
im.getdata() im.getdata()
assert isinstance(im, TiffImagePlugin.TiffImageFile)
try: try:
assert im._compression == "group4" assert im._compression == "group4"
except AttributeError: except AttributeError:
@ -157,6 +166,7 @@ class TestFileLibTiff(LibTiffTestCase):
"""Test metadata writing through libtiff""" """Test metadata writing through libtiff"""
f = str(tmp_path / "temp.tiff") f = str(tmp_path / "temp.tiff")
with Image.open("Tests/images/hopper_g4.tif") as img: with Image.open("Tests/images/hopper_g4.tif") as img:
assert isinstance(img, TiffImagePlugin.TiffImageFile)
img.save(f, tiffinfo=img.tag) img.save(f, tiffinfo=img.tag)
if legacy_api: if legacy_api:
@ -174,6 +184,7 @@ class TestFileLibTiff(LibTiffTestCase):
] ]
with Image.open(f) as loaded: with Image.open(f) as loaded:
assert isinstance(loaded, TiffImagePlugin.TiffImageFile)
if legacy_api: if legacy_api:
reloaded = loaded.tag.named() reloaded = loaded.tag.named()
else: else:
@ -216,6 +227,7 @@ class TestFileLibTiff(LibTiffTestCase):
# Exclude ones that have special meaning # Exclude ones that have special meaning
# that we're already testing them # that we're already testing them
with Image.open("Tests/images/hopper_g4.tif") as im: with Image.open("Tests/images/hopper_g4.tif") as im:
assert isinstance(im, TiffImagePlugin.TiffImageFile)
for tag in im.tag_v2: for tag in im.tag_v2:
try: try:
del core_items[tag] del core_items[tag]
@ -321,6 +333,7 @@ class TestFileLibTiff(LibTiffTestCase):
im.save(out, tiffinfo=tiffinfo) im.save(out, tiffinfo=tiffinfo)
with Image.open(out) as reloaded: with Image.open(out) as reloaded:
assert isinstance(reloaded, TiffImagePlugin.TiffImageFile)
for tag, value in tiffinfo.items(): for tag, value in tiffinfo.items():
reloaded_value = reloaded.tag_v2[tag] reloaded_value = reloaded.tag_v2[tag]
if ( if (
@ -353,12 +366,14 @@ 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 = str(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:
assert isinstance(im, TiffImagePlugin.TiffImageFile)
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 = str(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:
assert isinstance(im, TiffImagePlugin.TiffImageFile)
im.tag_v2[SUBIFD] = 10000 im.tag_v2[SUBIFD] = 10000
# Should not segfault # Should not segfault
@ -373,6 +388,7 @@ class TestFileLibTiff(LibTiffTestCase):
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:
assert isinstance(reloaded, TiffImagePlugin.TiffImageFile)
if 700 in reloaded.tag_v2: if 700 in reloaded.tag_v2:
assert reloaded.tag_v2[700] == b"xmlpacket tag" assert reloaded.tag_v2[700] == b"xmlpacket tag"
@ -434,12 +450,14 @@ 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") assert isinstance(orig, TiffImagePlugin.TiffImageFile)
out = str(tmp_path / "temp.tif")
orig.tag[269] = "temp.tif" orig.tag[269] = "temp.tif"
orig.save(out) orig.save(out)
with Image.open(out) as reread: with Image.open(out) as reread:
assert isinstance(reread, TiffImagePlugin.TiffImageFile)
assert "temp.tif" == reread.tag_v2[269] assert "temp.tif" == reread.tag_v2[269]
assert "temp.tif" == reread.tag[269][0] assert "temp.tif" == reread.tag[269][0]
@ -462,8 +480,8 @@ class TestFileLibTiff(LibTiffTestCase):
# 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 = str(tmp_path / "temp.tif")
with Image.open("Tests/images/pport_g4.tif") as im: with Image.open("Tests/images/pport_g4.tif") as img:
im = im.convert("L") im = img.convert("L")
im = im.filter(ImageFilter.GaussianBlur(4)) im = im.filter(ImageFilter.GaussianBlur(4))
im.save(out, compression="tiff_adobe_deflate") im.save(out, compression="tiff_adobe_deflate")
@ -545,6 +563,7 @@ class TestFileLibTiff(LibTiffTestCase):
with Image.open(out) as reloaded: with Image.open(out) as reloaded:
# colormap/palette tag # colormap/palette tag
assert isinstance(reloaded, TiffImagePlugin.TiffImageFile)
assert len(reloaded.tag_v2[320]) == 768 assert len(reloaded.tag_v2[320]) == 768
@pytest.mark.parametrize("compression", ("tiff_ccitt", "group3", "group4")) @pytest.mark.parametrize("compression", ("tiff_ccitt", "group3", "group4"))
@ -556,8 +575,9 @@ class TestFileLibTiff(LibTiffTestCase):
im.save(out, compression=compression) im.save(out, compression=compression)
def test_fp_leak(self) -> None: def test_fp_leak(self) -> None:
im: Image.Image | None = Image.open("Tests/images/hopper_g4_500.tif") im: ImageFile.ImageFile | None = Image.open("Tests/images/hopper_g4_500.tif")
assert im is not None assert im is not None
assert im.fp is not None
fn = im.fp.fileno() fn = im.fp.fileno()
os.fstat(fn) os.fstat(fn)
@ -576,6 +596,7 @@ class TestFileLibTiff(LibTiffTestCase):
with Image.open("Tests/images/multipage.tiff") as im: with Image.open("Tests/images/multipage.tiff") as im:
# file is a multipage tiff, 10x10 green, 10x10 red, 20x20 blue # file is a multipage tiff, 10x10 green, 10x10 red, 20x20 blue
assert isinstance(im, TiffImagePlugin.TiffImageFile)
im.seek(0) im.seek(0)
assert im.size == (10, 10) assert im.size == (10, 10)
assert im.convert("RGB").getpixel((0, 0)) == (0, 128, 0) assert im.convert("RGB").getpixel((0, 0)) == (0, 128, 0)
@ -595,6 +616,7 @@ class TestFileLibTiff(LibTiffTestCase):
# issue #862 # issue #862
monkeypatch.setattr(TiffImagePlugin, "READ_LIBTIFF", True) monkeypatch.setattr(TiffImagePlugin, "READ_LIBTIFF", True)
with Image.open("Tests/images/multipage.tiff") as im: with Image.open("Tests/images/multipage.tiff") as im:
assert isinstance(im, TiffImagePlugin.TiffImageFile)
frames = im.n_frames frames = im.n_frames
assert frames == 3 assert frames == 3
for _ in range(frames): for _ in range(frames):
@ -614,6 +636,7 @@ class TestFileLibTiff(LibTiffTestCase):
def test__next(self, monkeypatch: pytest.MonkeyPatch) -> None: def test__next(self, monkeypatch: pytest.MonkeyPatch) -> None:
monkeypatch.setattr(TiffImagePlugin, "READ_LIBTIFF", True) monkeypatch.setattr(TiffImagePlugin, "READ_LIBTIFF", True)
with Image.open("Tests/images/hopper.tif") as im: with Image.open("Tests/images/hopper.tif") as im:
assert isinstance(im, TiffImagePlugin.TiffImageFile)
assert not im.tag.next assert not im.tag.next
im.load() im.load()
assert not im.tag.next assert not im.tag.next
@ -694,21 +717,25 @@ class TestFileLibTiff(LibTiffTestCase):
im.save(outfile, compression="jpeg") im.save(outfile, compression="jpeg")
with Image.open(outfile) as reloaded: with Image.open(outfile) as reloaded:
assert isinstance(reloaded, TiffImagePlugin.TiffImageFile)
assert reloaded.tag_v2[530] == (1, 1) assert reloaded.tag_v2[530] == (1, 1)
assert reloaded.tag_v2[532] == (0, 255, 128, 255, 128, 255) assert reloaded.tag_v2[532] == (0, 255, 128, 255, 128, 255)
def test_exif_ifd(self) -> None: def test_exif_ifd(self) -> None:
out = io.BytesIO() out = io.BytesIO()
with Image.open("Tests/images/tiff_adobe_deflate.tif") as im: with Image.open("Tests/images/tiff_adobe_deflate.tif") as im:
assert isinstance(im, TiffImagePlugin.TiffImageFile)
assert im.tag_v2[34665] == 125456 assert im.tag_v2[34665] == 125456
im.save(out, "TIFF") im.save(out, "TIFF")
with Image.open(out) as reloaded: with Image.open(out) as reloaded:
assert isinstance(reloaded, TiffImagePlugin.TiffImageFile)
assert 34665 not in reloaded.tag_v2 assert 34665 not in reloaded.tag_v2
im.save(out, "TIFF", tiffinfo={34665: 125456}) im.save(out, "TIFF", tiffinfo={34665: 125456})
with Image.open(out) as reloaded: with Image.open(out) as reloaded:
assert isinstance(reloaded, TiffImagePlugin.TiffImageFile)
if Image.core.libtiff_support_custom_tags: if Image.core.libtiff_support_custom_tags:
assert reloaded.tag_v2[34665] == 125456 assert reloaded.tag_v2[34665] == 125456
@ -790,6 +817,8 @@ class TestFileLibTiff(LibTiffTestCase):
def test_multipage_compression(self) -> None: def test_multipage_compression(self) -> None:
with Image.open("Tests/images/compression.tif") as im: with Image.open("Tests/images/compression.tif") as im:
assert isinstance(im, TiffImagePlugin.TiffImageFile)
im.seek(0) im.seek(0)
assert im._compression == "tiff_ccitt" assert im._compression == "tiff_ccitt"
assert im.size == (10, 10) assert im.size == (10, 10)
@ -1083,6 +1112,7 @@ class TestFileLibTiff(LibTiffTestCase):
with Image.open("Tests/images/g4_orientation_1.tif") as base_im: with Image.open("Tests/images/g4_orientation_1.tif") as base_im:
for i in range(2, 9): for i in range(2, 9):
with Image.open("Tests/images/g4_orientation_" + str(i) + ".tif") as im: with Image.open("Tests/images/g4_orientation_" + str(i) + ".tif") as im:
assert isinstance(im, TiffImagePlugin.TiffImageFile)
assert 274 in im.tag_v2 assert 274 in im.tag_v2
im.load() im.load()
@ -1094,9 +1124,10 @@ class TestFileLibTiff(LibTiffTestCase):
with Image.open("Tests/images/g4_orientation_1.tif") as base_im: with Image.open("Tests/images/g4_orientation_1.tif") as base_im:
for i in range(2, 9): for i in range(2, 9):
with Image.open("Tests/images/g4_orientation_" + str(i) + ".tif") as im: with Image.open("Tests/images/g4_orientation_" + str(i) + ".tif") as im:
im = ImageOps.exif_transpose(im) transposed_im = ImageOps.exif_transpose(im)
assert transposed_im is not None
assert_image_similar(base_im, im, 0.7) assert_image_similar(base_im, transposed_im, 0.7)
@pytest.mark.valgrind_known_error(reason="Backtrace in Python Core") @pytest.mark.valgrind_known_error(reason="Backtrace in Python Core")
def test_sampleformat_not_corrupted(self) -> None: def test_sampleformat_not_corrupted(self) -> None:

View File

@ -22,19 +22,21 @@ def test_sanity() -> None:
# Adjust for the gamma of 2.2 encoded into the file # Adjust for the gamma of 2.2 encoded into the file
lut = ImagePalette.make_gamma_lut(1 / 2.2) lut = ImagePalette.make_gamma_lut(1 / 2.2)
im = Image.merge("RGBA", [chan.point(lut) for chan in im.split()]) im1 = Image.merge("RGBA", [chan.point(lut) for chan in im.split()])
im2 = hopper("RGBA") im2 = hopper("RGBA")
assert_image_similar(im, im2, 10) assert_image_similar(im1, im2, 10)
def test_n_frames() -> None: def test_n_frames() -> None:
with Image.open(TEST_FILE) as im: with Image.open(TEST_FILE) as im:
assert isinstance(im, MicImagePlugin.MicImageFile)
assert im.n_frames == 1 assert im.n_frames == 1
def test_is_animated() -> None: def test_is_animated() -> None:
with Image.open(TEST_FILE) as im: with Image.open(TEST_FILE) as im:
assert isinstance(im, MicImagePlugin.MicImageFile)
assert not im.is_animated assert not im.is_animated
@ -55,10 +57,11 @@ def test_seek() -> None:
def test_close() -> None: def test_close() -> None:
with Image.open(TEST_FILE) as im: with Image.open(TEST_FILE) as im:
pass assert isinstance(im, MicImagePlugin.MicImageFile)
assert im.ole.fp.closed assert im.ole.fp.closed
im = Image.open(TEST_FILE) im = Image.open(TEST_FILE)
assert isinstance(im, MicImagePlugin.MicImageFile)
im.close() im.close()
assert im.ole.fp.closed assert im.ole.fp.closed

View File

@ -6,7 +6,7 @@ from typing import Any
import pytest import pytest
from PIL import Image, ImageFile, MpoImagePlugin from PIL import Image, ImageFile, JpegImagePlugin, MpoImagePlugin
from .helper import ( from .helper import (
assert_image_equal, assert_image_equal,
@ -71,6 +71,7 @@ def test_context_manager() -> None:
def test_app(test_file: str) -> None: def test_app(test_file: str) -> None:
# Test APP/COM reader (@PIL135) # Test APP/COM reader (@PIL135)
with Image.open(test_file) as im: with Image.open(test_file) as im:
assert isinstance(im, MpoImagePlugin.MpoImageFile)
assert im.applist[0][0] == "APP1" assert im.applist[0][0] == "APP1"
assert im.applist[1][0] == "APP2" assert im.applist[1][0] == "APP2"
assert ( assert (
@ -110,9 +111,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)"
@ -145,7 +148,9 @@ 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[45056] == b"0100" assert mpinfo[45056] == b"0100"
assert mpinfo[45057] == 2 assert mpinfo[45057] == 2
@ -154,7 +159,9 @@ 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[45056] == b"0100" assert mpinfo[45056] == b"0100"
assert mpinfo[45057] == 2 assert mpinfo[45057] == 2
@ -170,7 +177,9 @@ 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
for frame_number, mpentry in enumerate(mpinfo[0xB002]): for frame_number, mpentry in enumerate(mpinfo[0xB002]):
mpattr = mpentry["Attribute"] mpattr = mpentry["Attribute"]
if frame_number: if frame_number:
@ -211,12 +220,14 @@ def test_seek(test_file: str) -> None:
def test_n_frames() -> None: def test_n_frames() -> None:
with Image.open("Tests/images/sugarshack.mpo") as im: with Image.open("Tests/images/sugarshack.mpo") as im:
assert isinstance(im, MpoImagePlugin.MpoImageFile)
assert im.n_frames == 2 assert im.n_frames == 2
assert im.is_animated assert im.is_animated
def test_eoferror() -> None: def test_eoferror() -> None:
with Image.open("Tests/images/sugarshack.mpo") as im: with Image.open("Tests/images/sugarshack.mpo") as im:
assert isinstance(im, MpoImagePlugin.MpoImageFile)
n_frames = im.n_frames n_frames = im.n_frames
# Test seeking past the last frame # Test seeking past the last frame
@ -230,6 +241,8 @@ def test_eoferror() -> None:
def test_adopt_jpeg() -> None: def test_adopt_jpeg() -> None:
with Image.open("Tests/images/hopper.jpg") as im: with Image.open("Tests/images/hopper.jpg") as im:
assert isinstance(im, JpegImagePlugin.JpegImageFile)
with pytest.raises(ValueError): with pytest.raises(ValueError):
MpoImagePlugin.MpoImageFile.adopt(im) MpoImagePlugin.MpoImageFile.adopt(im)
@ -267,6 +280,7 @@ def test_save(test_file: str) -> None:
def test_save_all() -> None: def test_save_all() -> None:
im: Image.Image
for test_file in test_files: for test_file in test_files:
with Image.open(test_file) as im: with Image.open(test_file) as im:
im_reloaded = roundtrip(im, save_all=True) im_reloaded = roundtrip(im, save_all=True)

View File

@ -4,7 +4,7 @@ import re
import sys import sys
import warnings import warnings
import zlib import zlib
from io import BytesIO from io import BytesIO, TextIOWrapper
from pathlib import Path from pathlib import Path
from types import ModuleType from types import ModuleType
from typing import Any, cast from typing import Any, cast
@ -93,6 +93,7 @@ class TestFilePng:
hopper("RGB").save(test_file) hopper("RGB").save(test_file)
im: Image.Image
with Image.open(test_file) as im: with Image.open(test_file) as im:
im.load() im.load()
assert im.mode == "RGB" assert im.mode == "RGB"
@ -103,6 +104,8 @@ class TestFilePng:
for mode in ["1", "L", "P", "RGB", "I", "I;16", "I;16B"]: for mode in ["1", "L", "P", "RGB", "I", "I;16", "I;16B"]:
im = hopper(mode) im = hopper(mode)
im.save(test_file) im.save(test_file)
reloaded: Image.Image
with Image.open(test_file) as reloaded: with Image.open(test_file) as reloaded:
if mode in ("I", "I;16B"): if mode in ("I", "I;16B"):
reloaded = reloaded.convert(mode) reloaded = reloaded.convert(mode)
@ -225,11 +228,13 @@ class TestFilePng:
test_file = "Tests/images/pil123p.png" test_file = "Tests/images/pil123p.png"
with Image.open(test_file) as im: with Image.open(test_file) as im:
assert_image(im, "P", (162, 150)) assert_image(im, "P", (162, 150))
im = im.convert("RGBA") rgba_im = im.convert("RGBA")
assert_image(im, "RGBA", (162, 150)) assert_image(rgba_im, "RGBA", (162, 150))
# image has 124 unique alpha values # image has 124 unique alpha values
assert len(im.getchannel("A").getcolors()) == 124 colors = rgba_im.getchannel("A").getcolors()
assert colors is not None
assert len(colors) == 124
def test_load_transparent_rgb(self) -> None: def test_load_transparent_rgb(self) -> None:
test_file = "Tests/images/rgb_trns.png" test_file = "Tests/images/rgb_trns.png"
@ -237,11 +242,13 @@ class TestFilePng:
assert im.info["transparency"] == (0, 255, 52) assert im.info["transparency"] == (0, 255, 52)
assert_image(im, "RGB", (64, 64)) assert_image(im, "RGB", (64, 64))
im = im.convert("RGBA") rgba_im = im.convert("RGBA")
assert_image(im, "RGBA", (64, 64)) assert_image(rgba_im, "RGBA", (64, 64))
# image has 876 transparent pixels # image has 876 transparent pixels
assert im.getchannel("A").getcolors()[0][0] == 876 colors = rgba_im.getchannel("A").getcolors()
assert colors is not None
assert colors[0][0] == 876
def test_save_p_transparent_palette(self, tmp_path: Path) -> None: def test_save_p_transparent_palette(self, tmp_path: Path) -> None:
in_file = "Tests/images/pil123p.png" in_file = "Tests/images/pil123p.png"
@ -258,11 +265,13 @@ class TestFilePng:
assert len(im.info["transparency"]) == 256 assert len(im.info["transparency"]) == 256
assert_image(im, "P", (162, 150)) assert_image(im, "P", (162, 150))
im = im.convert("RGBA") rgba_im = im.convert("RGBA")
assert_image(im, "RGBA", (162, 150)) assert_image(rgba_im, "RGBA", (162, 150))
# image has 124 unique alpha values # image has 124 unique alpha values
assert len(im.getchannel("A").getcolors()) == 124 colors = rgba_im.getchannel("A").getcolors()
assert colors is not None
assert len(colors) == 124
def test_save_p_single_transparency(self, tmp_path: Path) -> None: def test_save_p_single_transparency(self, tmp_path: Path) -> None:
in_file = "Tests/images/p_trns_single.png" in_file = "Tests/images/p_trns_single.png"
@ -279,13 +288,15 @@ class TestFilePng:
assert im.info["transparency"] == 164 assert im.info["transparency"] == 164
assert im.getpixel((31, 31)) == 164 assert im.getpixel((31, 31)) == 164
assert_image(im, "P", (64, 64)) assert_image(im, "P", (64, 64))
im = im.convert("RGBA") rgba_im = im.convert("RGBA")
assert_image(im, "RGBA", (64, 64)) assert_image(rgba_im, "RGBA", (64, 64))
assert im.getpixel((31, 31)) == (0, 255, 52, 0) assert rgba_im.getpixel((31, 31)) == (0, 255, 52, 0)
# image has 876 transparent pixels # image has 876 transparent pixels
assert im.getchannel("A").getcolors()[0][0] == 876 colors = rgba_im.getchannel("A").getcolors()
assert colors is not None
assert colors[0][0] == 876
def test_save_p_transparent_black(self, tmp_path: Path) -> None: def test_save_p_transparent_black(self, tmp_path: Path) -> None:
# check if solid black image with full transparency # check if solid black image with full transparency
@ -313,7 +324,9 @@ class TestFilePng:
assert im.info["transparency"] == 255 assert im.info["transparency"] == 255
im_rgba = im.convert("RGBA") im_rgba = im.convert("RGBA")
assert im_rgba.getchannel("A").getcolors()[0][0] == num_transparent colors = im_rgba.getchannel("A").getcolors()
assert colors is not None
assert colors[0][0] == num_transparent
test_file = str(tmp_path / "temp.png") test_file = str(tmp_path / "temp.png")
im.save(test_file) im.save(test_file)
@ -324,7 +337,9 @@ class TestFilePng:
assert_image_equal(im, test_im) assert_image_equal(im, test_im)
test_im_rgba = test_im.convert("RGBA") test_im_rgba = test_im.convert("RGBA")
assert test_im_rgba.getchannel("A").getcolors()[0][0] == num_transparent colors = test_im_rgba.getchannel("A").getcolors()
assert colors is not None
assert colors[0][0] == num_transparent
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"
@ -578,6 +593,7 @@ class TestFilePng:
def test_read_private_chunks(self) -> None: def test_read_private_chunks(self) -> None:
with Image.open("Tests/images/exif.png") as im: with Image.open("Tests/images/exif.png") as im:
assert isinstance(im, PngImagePlugin.PngImageFile)
assert im.private_chunks == [(b"orNT", b"\x01")] assert im.private_chunks == [(b"orNT", b"\x01")]
def test_roundtrip_private_chunk(self) -> None: def test_roundtrip_private_chunk(self) -> None:
@ -600,6 +616,7 @@ class TestFilePng:
def test_textual_chunks_after_idat(self) -> None: def test_textual_chunks_after_idat(self) -> None:
with Image.open("Tests/images/hopper.png") as im: with Image.open("Tests/images/hopper.png") as im:
assert isinstance(im, PngImagePlugin.PngImageFile)
assert "comment" in im.text assert "comment" in im.text
for k, v in { for k, v in {
"date:create": "2014-09-04T09:37:08+03:00", "date:create": "2014-09-04T09:37:08+03:00",
@ -609,20 +626,24 @@ class TestFilePng:
# Raises a SyntaxError in load_end # Raises a SyntaxError in load_end
with Image.open("Tests/images/broken_data_stream.png") as im: with Image.open("Tests/images/broken_data_stream.png") as im:
assert isinstance(im, PngImagePlugin.PngImageFile)
with pytest.raises(OSError): with pytest.raises(OSError):
assert isinstance(im.text, dict) assert isinstance(im.text, dict)
# Raises a UnicodeDecodeError in load_end # Raises a UnicodeDecodeError in load_end
with Image.open("Tests/images/truncated_image.png") as im: with Image.open("Tests/images/truncated_image.png") as im:
assert isinstance(im, PngImagePlugin.PngImageFile)
# The file is truncated # The file is truncated
with pytest.raises(OSError): with pytest.raises(OSError):
im.text() im.text
ImageFile.LOAD_TRUNCATED_IMAGES = True ImageFile.LOAD_TRUNCATED_IMAGES = True
assert isinstance(im.text, dict) assert isinstance(im.text, dict)
ImageFile.LOAD_TRUNCATED_IMAGES = False ImageFile.LOAD_TRUNCATED_IMAGES = False
# Raises an EOFError in load_end # Raises an EOFError in load_end
with Image.open("Tests/images/hopper_idat_after_image_end.png") as im: with Image.open("Tests/images/hopper_idat_after_image_end.png") as im:
assert isinstance(im, PngImagePlugin.PngImageFile)
assert im.text == {"TXT": "VALUE", "ZIP": "VALUE"} assert im.text == {"TXT": "VALUE", "ZIP": "VALUE"}
def test_unknown_compression_method(self) -> None: def test_unknown_compression_method(self) -> None:
@ -667,6 +688,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:
@ -677,6 +701,9 @@ class TestFilePng:
im.save(str(tmp_path / "temp.png")) im.save(str(tmp_path / "temp.png"))
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:
@ -698,13 +725,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
@ -713,8 +744,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:
@ -728,6 +761,7 @@ class TestFilePng:
im.save(test_file) im.save(test_file)
with Image.open(test_file) as reloaded: with Image.open(test_file) as reloaded:
assert isinstance(reloaded, PngImagePlugin.PngImageFile)
assert reloaded._getexif() is None assert reloaded._getexif() is None
# Test passing in exif # Test passing in exif
@ -735,7 +769,9 @@ 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:
assert isinstance(reloaded, PngImagePlugin.PngImageFile)
exif = reloaded._getexif() exif = reloaded._getexif()
assert exif is not None
assert exif[274] == 1 assert exif[274] == 1
@mark_if_feature_version( @mark_if_feature_version(
@ -747,7 +783,9 @@ 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:
assert isinstance(reloaded, PngImagePlugin.PngImageFile)
exif = reloaded._getexif() exif = reloaded._getexif()
assert exif is not None
assert exif[305] == "Adobe Photoshop CS Macintosh" assert exif[305] == "Adobe Photoshop CS Macintosh"
def test_exif_argument(self, tmp_path: Path) -> None: def test_exif_argument(self, tmp_path: Path) -> None:
@ -773,10 +811,8 @@ class TestFilePng:
def test_save_stdout(self, buffer: bool) -> None: def test_save_stdout(self, buffer: bool) -> None:
old_stdout = sys.stdout old_stdout = sys.stdout
class MyStdOut: b = BytesIO()
buffer = BytesIO() mystdout: TextIOWrapper | BytesIO = TextIOWrapper(b) if buffer else b
mystdout: MyStdOut | BytesIO = MyStdOut() if buffer else BytesIO()
sys.stdout = mystdout sys.stdout = mystdout
@ -786,9 +822,7 @@ class TestFilePng:
# Reset stdout # Reset stdout
sys.stdout = old_stdout sys.stdout = old_stdout
if isinstance(mystdout, MyStdOut): with Image.open(b) as reloaded:
mystdout = mystdout.buffer
with Image.open(mystdout) as reloaded:
assert_image_equal_tofile(reloaded, TEST_PNG_FILE) assert_image_equal_tofile(reloaded, TEST_PNG_FILE)
def test_truncated_end_chunk(self) -> None: def test_truncated_end_chunk(self) -> None:

View File

@ -1,7 +1,7 @@
from __future__ import annotations from __future__ import annotations
import sys import sys
from io import BytesIO from io import BytesIO, TextIOWrapper
from pathlib import Path from pathlib import Path
import pytest import pytest
@ -79,6 +79,7 @@ def test_arbitrary_maxval(
assert im.mode == mode assert im.mode == mode
px = im.load() px = im.load()
assert px is not None
assert tuple(px[x, 0] for x in range(3)) == pixels assert tuple(px[x, 0] for x in range(3)) == pixels
@ -370,10 +371,8 @@ def test_mimetypes(tmp_path: Path) -> None:
def test_save_stdout(buffer: bool) -> None: def test_save_stdout(buffer: bool) -> None:
old_stdout = sys.stdout old_stdout = sys.stdout
class MyStdOut: b = BytesIO()
buffer = BytesIO() mystdout: TextIOWrapper | BytesIO = TextIOWrapper(b) if buffer else b
mystdout: MyStdOut | BytesIO = MyStdOut() if buffer else BytesIO()
sys.stdout = mystdout sys.stdout = mystdout
@ -383,7 +382,5 @@ def test_save_stdout(buffer: bool) -> None:
# Reset stdout # Reset stdout
sys.stdout = old_stdout sys.stdout = old_stdout
if isinstance(mystdout, MyStdOut): with Image.open(b) as reloaded:
mystdout = mystdout.buffer
with Image.open(mystdout) as reloaded:
assert_image_equal_tofile(reloaded, TEST_FILE) assert_image_equal_tofile(reloaded, TEST_FILE)

View File

@ -55,17 +55,21 @@ def test_invalid_file() -> None:
def test_n_frames() -> None: def test_n_frames() -> None:
with Image.open("Tests/images/hopper_merged.psd") as im: with Image.open("Tests/images/hopper_merged.psd") as im:
assert isinstance(im, PsdImagePlugin.PsdImageFile)
assert im.n_frames == 1 assert im.n_frames == 1
assert not im.is_animated assert not im.is_animated
for path in [test_file, "Tests/images/negative_layer_count.psd"]: for path in [test_file, "Tests/images/negative_layer_count.psd"]:
with Image.open(path) as im: with Image.open(path) as im:
assert isinstance(im, PsdImagePlugin.PsdImageFile)
assert im.n_frames == 2 assert im.n_frames == 2
assert im.is_animated assert im.is_animated
def test_eoferror() -> None: def test_eoferror() -> None:
with Image.open(test_file) as im: with Image.open(test_file) as im:
assert isinstance(im, PsdImagePlugin.PsdImageFile)
# PSD seek index starts at 1 rather than 0 # PSD seek index starts at 1 rather than 0
n_frames = im.n_frames + 1 n_frames = im.n_frames + 1
@ -115,11 +119,13 @@ def test_rgba() -> None:
def test_negative_top_left_layer() -> None: def test_negative_top_left_layer() -> None:
with Image.open("Tests/images/negative_top_left_layer.psd") as im: with Image.open("Tests/images/negative_top_left_layer.psd") as im:
assert isinstance(im, PsdImagePlugin.PsdImageFile)
assert im.layers[0][2] == (-50, -50, 50, 50) assert im.layers[0][2] == (-50, -50, 50, 50)
def test_layer_skip() -> None: def test_layer_skip() -> None:
with Image.open("Tests/images/five_channels.psd") as im: with Image.open("Tests/images/five_channels.psd") as im:
assert isinstance(im, PsdImagePlugin.PsdImageFile)
assert im.n_frames == 1 assert im.n_frames == 1
@ -171,5 +177,6 @@ def test_crashes(test_file: str, raises: type[Exception]) -> None:
def test_layer_crashes(test_file: str) -> None: def test_layer_crashes(test_file: str) -> None:
with open(test_file, "rb") as f: with open(test_file, "rb") as f:
with Image.open(f) as im: with Image.open(f) as im:
assert isinstance(im, PsdImagePlugin.PsdImageFile)
with pytest.raises(SyntaxError): with pytest.raises(SyntaxError):
im.layers im.layers

View File

@ -92,6 +92,7 @@ def test_tell() -> None:
def test_n_frames() -> None: def test_n_frames() -> None:
with Image.open(TEST_FILE) as im: with Image.open(TEST_FILE) as im:
assert isinstance(im, SpiderImagePlugin.SpiderImageFile)
assert im.n_frames == 1 assert im.n_frames == 1
assert not im.is_animated assert not im.is_animated

View File

@ -72,6 +72,7 @@ def test_palette_depth_8(tmp_path: Path) -> None:
def test_palette_depth_16(tmp_path: Path) -> None: def test_palette_depth_16(tmp_path: Path) -> None:
with Image.open("Tests/images/p_16.tga") as im: with Image.open("Tests/images/p_16.tga") as im:
assert im.palette is not 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")
@ -213,10 +214,18 @@ def test_save_orientation(tmp_path: Path) -> None:
def test_horizontal_orientations() -> None: def test_horizontal_orientations() -> None:
# These images have been manually hexedited to have the relevant orientations # These images have been manually hexedited to have the relevant orientations
with Image.open("Tests/images/rgb32rle_top_right.tga") as im: with Image.open("Tests/images/rgb32rle_top_right.tga") as im:
assert im.load()[90, 90][:3] == (0, 0, 0) px = im.load()
assert px is not None
value = px[90, 90]
assert isinstance(value, tuple)
assert value[:3] == (0, 0, 0)
with Image.open("Tests/images/rgb32rle_bottom_right.tga") as im: with Image.open("Tests/images/rgb32rle_bottom_right.tga") as im:
assert im.load()[90, 90][:3] == (0, 255, 0) px = im.load()
assert px is not None
value = px[90, 90]
assert isinstance(value, tuple)
assert value[:3] == (0, 255, 0)
def test_save_rle(tmp_path: Path) -> None: def test_save_rle(tmp_path: Path) -> None:
@ -259,13 +268,17 @@ def test_save_l_transparency(tmp_path: Path) -> None:
in_file = "Tests/images/la.tga" in_file = "Tests/images/la.tga"
with Image.open(in_file) as im: with Image.open(in_file) as im:
assert im.mode == "LA" assert im.mode == "LA"
assert im.getchannel("A").getcolors()[0][0] == num_transparent colors = im.getchannel("A").getcolors()
assert colors is not None
assert colors[0][0] == num_transparent
out = str(tmp_path / "temp.tga") out = str(tmp_path / "temp.tga")
im.save(out) im.save(out)
with Image.open(out) as test_im: with Image.open(out) as test_im:
assert test_im.mode == "LA" assert test_im.mode == "LA"
assert test_im.getchannel("A").getcolors()[0][0] == num_transparent colors = test_im.getchannel("A").getcolors()
assert colors is not None
assert colors[0][0] == num_transparent
assert_image_equal(im, test_im) assert_image_equal(im, test_im)

View File

@ -9,7 +9,13 @@ from types import ModuleType
import pytest import pytest
from PIL import Image, ImageFile, TiffImagePlugin, UnidentifiedImageError from PIL import (
Image,
ImageFile,
JpegImagePlugin,
TiffImagePlugin,
UnidentifiedImageError,
)
from PIL.TiffImagePlugin import RESOLUTION_UNIT, X_RESOLUTION, Y_RESOLUTION from PIL.TiffImagePlugin import RESOLUTION_UNIT, X_RESOLUTION, Y_RESOLUTION
from .helper import ( from .helper import (
@ -108,6 +114,8 @@ 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:
assert isinstance(im, TiffImagePlugin.TiffImageFile)
# multistrip support not yet implemented # multistrip support not yet implemented
del im.tag_v2[273] del im.tag_v2[273]
@ -127,6 +135,8 @@ class TestFileTiff:
def test_xyres_tiff(self) -> None: def test_xyres_tiff(self) -> None:
filename = "Tests/images/pil168.tif" filename = "Tests/images/pil168.tif"
with Image.open(filename) as im: with Image.open(filename) as im:
assert isinstance(im, TiffImagePlugin.TiffImageFile)
# legacy api # legacy api
assert isinstance(im.tag[X_RESOLUTION][0], tuple) assert isinstance(im.tag[X_RESOLUTION][0], tuple)
assert isinstance(im.tag[Y_RESOLUTION][0], tuple) assert isinstance(im.tag[Y_RESOLUTION][0], tuple)
@ -140,6 +150,8 @@ class TestFileTiff:
def test_xyres_fallback_tiff(self) -> None: def test_xyres_fallback_tiff(self) -> None:
filename = "Tests/images/compression.tif" filename = "Tests/images/compression.tif"
with Image.open(filename) as im: with Image.open(filename) as im:
assert isinstance(im, TiffImagePlugin.TiffImageFile)
# v2 api # v2 api
assert isinstance(im.tag_v2[X_RESOLUTION], TiffImagePlugin.IFDRational) assert isinstance(im.tag_v2[X_RESOLUTION], TiffImagePlugin.IFDRational)
assert isinstance(im.tag_v2[Y_RESOLUTION], TiffImagePlugin.IFDRational) assert isinstance(im.tag_v2[Y_RESOLUTION], TiffImagePlugin.IFDRational)
@ -154,6 +166,8 @@ class TestFileTiff:
def test_int_resolution(self) -> None: def test_int_resolution(self) -> None:
filename = "Tests/images/pil168.tif" filename = "Tests/images/pil168.tif"
with Image.open(filename) as im: with Image.open(filename) as im:
assert isinstance(im, TiffImagePlugin.TiffImageFile)
# Try to read a file where X,Y_RESOLUTION are ints # Try to read a file where X,Y_RESOLUTION are ints
im.tag_v2[X_RESOLUTION] = 71 im.tag_v2[X_RESOLUTION] = 71
im.tag_v2[Y_RESOLUTION] = 71 im.tag_v2[Y_RESOLUTION] = 71
@ -168,6 +182,7 @@ class TestFileTiff:
with Image.open( with Image.open(
"Tests/images/hopper_float_dpi_" + str(resolution_unit) + ".tif" "Tests/images/hopper_float_dpi_" + str(resolution_unit) + ".tif"
) as im: ) as im:
assert isinstance(im, TiffImagePlugin.TiffImageFile)
assert im.tag_v2.get(RESOLUTION_UNIT) == resolution_unit assert im.tag_v2.get(RESOLUTION_UNIT) == resolution_unit
assert im.info["dpi"] == (dpi, dpi) assert im.info["dpi"] == (dpi, dpi)
@ -185,6 +200,7 @@ class TestFileTiff:
with Image.open("Tests/images/10ct_32bit_128.tiff") as im: with Image.open("Tests/images/10ct_32bit_128.tiff") as im:
im.save(b, format="tiff", resolution=123.45) im.save(b, format="tiff", resolution=123.45)
with Image.open(b) as im: with Image.open(b) as im:
assert isinstance(im, TiffImagePlugin.TiffImageFile)
assert im.tag_v2[X_RESOLUTION] == 123.45 assert im.tag_v2[X_RESOLUTION] == 123.45
assert im.tag_v2[Y_RESOLUTION] == 123.45 assert im.tag_v2[Y_RESOLUTION] == 123.45
@ -200,10 +216,12 @@ class TestFileTiff:
TiffImagePlugin.PREFIXES.pop() TiffImagePlugin.PREFIXES.pop()
def test_bad_exif(self) -> None: def test_bad_exif(self) -> None:
with Image.open("Tests/images/hopper_bad_exif.jpg") as i: with Image.open("Tests/images/hopper_bad_exif.jpg") as im:
assert isinstance(im, JpegImagePlugin.JpegImageFile)
# Should not raise struct.error. # Should not raise struct.error.
with pytest.warns(UserWarning): with pytest.warns(UserWarning):
i._getexif() im._getexif()
def test_save_rgba(self, tmp_path: Path) -> None: def test_save_rgba(self, tmp_path: Path) -> None:
im = hopper("RGBA") im = hopper("RGBA")
@ -294,11 +312,13 @@ class TestFileTiff:
) )
def test_n_frames(self, path: str, n_frames: int) -> None: def test_n_frames(self, path: str, n_frames: int) -> None:
with Image.open(path) as im: with Image.open(path) as im:
assert isinstance(im, TiffImagePlugin.TiffImageFile)
assert im.n_frames == n_frames assert im.n_frames == n_frames
assert im.is_animated == (n_frames != 1) assert im.is_animated == (n_frames != 1)
def test_eoferror(self) -> None: def test_eoferror(self) -> None:
with Image.open("Tests/images/multipage-lastframe.tif") as im: with Image.open("Tests/images/multipage-lastframe.tif") as im:
assert isinstance(im, TiffImagePlugin.TiffImageFile)
n_frames = im.n_frames n_frames = im.n_frames
# Test seeking past the last frame # Test seeking past the last frame
@ -342,20 +362,24 @@ class TestFileTiff:
def test_frame_order(self) -> None: def test_frame_order(self) -> None:
# A frame can't progress to itself after reading # A frame can't progress to itself after reading
with Image.open("Tests/images/multipage_single_frame_loop.tiff") as im: with Image.open("Tests/images/multipage_single_frame_loop.tiff") as im:
assert isinstance(im, TiffImagePlugin.TiffImageFile)
assert im.n_frames == 1 assert im.n_frames == 1
# A frame can't progress to a frame that has already been read # A frame can't progress to a frame that has already been read
with Image.open("Tests/images/multipage_multiple_frame_loop.tiff") as im: with Image.open("Tests/images/multipage_multiple_frame_loop.tiff") as im:
assert isinstance(im, TiffImagePlugin.TiffImageFile)
assert im.n_frames == 2 assert im.n_frames == 2
# Frames don't have to be in sequence # Frames don't have to be in sequence
with Image.open("Tests/images/multipage_out_of_order.tiff") as im: with Image.open("Tests/images/multipage_out_of_order.tiff") as im:
assert isinstance(im, TiffImagePlugin.TiffImageFile)
assert im.n_frames == 3 assert im.n_frames == 3
def test___str__(self) -> None: def test___str__(self) -> None:
filename = "Tests/images/pil136.tiff" filename = "Tests/images/pil136.tiff"
with Image.open(filename) as im: with Image.open(filename) as im:
# Act # Act
assert isinstance(im, TiffImagePlugin.TiffImageFile)
ret = str(im.ifd) ret = str(im.ifd)
# Assert # Assert
@ -365,6 +389,8 @@ class TestFileTiff:
# Arrange # Arrange
filename = "Tests/images/pil136.tiff" filename = "Tests/images/pil136.tiff"
with Image.open(filename) as im: with Image.open(filename) as im:
assert isinstance(im, TiffImagePlugin.TiffImageFile)
# v2 interface # v2 interface
v2_tags = { v2_tags = {
256: 55, 256: 55,
@ -404,6 +430,7 @@ class TestFileTiff:
def test__delitem__(self) -> None: def test__delitem__(self) -> None:
filename = "Tests/images/pil136.tiff" filename = "Tests/images/pil136.tiff"
with Image.open(filename) as im: with Image.open(filename) as im:
assert isinstance(im, TiffImagePlugin.TiffImageFile)
len_before = len(dict(im.ifd)) len_before = len(dict(im.ifd))
del im.ifd[256] del im.ifd[256]
len_after = len(dict(im.ifd)) len_after = len(dict(im.ifd))
@ -436,6 +463,7 @@ class TestFileTiff:
def test_ifd_tag_type(self) -> None: def test_ifd_tag_type(self) -> None:
with Image.open("Tests/images/ifd_tag_type.tiff") as im: with Image.open("Tests/images/ifd_tag_type.tiff") as im:
assert isinstance(im, TiffImagePlugin.TiffImageFile)
assert 0x8825 in im.tag_v2 assert 0x8825 in im.tag_v2
def test_exif(self, tmp_path: Path) -> None: def test_exif(self, tmp_path: Path) -> None:
@ -524,6 +552,7 @@ class TestFileTiff:
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:
assert isinstance(reloaded, TiffImagePlugin.TiffImageFile)
assert reloaded.tag_v2[262] == 0 assert reloaded.tag_v2[262] == 0
assert_image_equal(im, reloaded) assert_image_equal(im, reloaded)
@ -602,6 +631,8 @@ class TestFileTiff:
filename = str(tmp_path / "temp.tif") filename = str(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:
assert isinstance(im, TiffImagePlugin.TiffImageFile)
# legacy interface # legacy interface
assert im.tag[X_RESOLUTION][0][0] == 72 assert im.tag[X_RESOLUTION][0][0] == 72
assert im.tag[Y_RESOLUTION][0][0] == 36 assert im.tag[Y_RESOLUTION][0][0] == 36
@ -676,6 +707,7 @@ class TestFileTiff:
def test_planar_configuration_save(self, tmp_path: Path) -> None: def test_planar_configuration_save(self, tmp_path: Path) -> None:
infile = "Tests/images/tiff_tiled_planar_raw.tif" infile = "Tests/images/tiff_tiled_planar_raw.tif"
with Image.open(infile) as im: with Image.open(infile) as im:
assert isinstance(im, TiffImagePlugin.TiffImageFile)
assert im._planar_configuration == 2 assert im._planar_configuration == 2
outfile = str(tmp_path / "temp.tif") outfile = str(tmp_path / "temp.tif")
@ -703,12 +735,13 @@ class TestFileTiff:
def test_tiff_save_all(self) -> None: def test_tiff_save_all(self) -> None:
mp = BytesIO() mp = BytesIO()
with Image.open("Tests/images/multipage.tiff") as im: with Image.open("Tests/images/multipage.tiff") as img:
im.save(mp, format="tiff", save_all=True) img.save(mp, format="tiff", save_all=True)
mp.seek(0, os.SEEK_SET) mp.seek(0, os.SEEK_SET)
with Image.open(mp) as im: with Image.open(mp) as img:
assert im.n_frames == 3 assert isinstance(img, TiffImagePlugin.TiffImageFile)
assert img.n_frames == 3
# Test appending images # Test appending images
mp = BytesIO() mp = BytesIO()
@ -718,6 +751,7 @@ class TestFileTiff:
mp.seek(0, os.SEEK_SET) mp.seek(0, os.SEEK_SET)
with Image.open(mp) as reread: with Image.open(mp) as reread:
assert isinstance(reread, TiffImagePlugin.TiffImageFile)
assert reread.n_frames == 3 assert reread.n_frames == 3
# Test appending using a generator # Test appending using a generator
@ -729,6 +763,7 @@ class TestFileTiff:
mp.seek(0, os.SEEK_SET) mp.seek(0, os.SEEK_SET)
with Image.open(mp) as reread: with Image.open(mp) as reread:
assert isinstance(reread, TiffImagePlugin.TiffImageFile)
assert reread.n_frames == 3 assert reread.n_frames == 3
def test_saving_icc_profile(self, tmp_path: Path) -> None: def test_saving_icc_profile(self, tmp_path: Path) -> None:
@ -792,6 +827,7 @@ class TestFileTiff:
def test_get_photoshop_blocks(self) -> None: def test_get_photoshop_blocks(self) -> None:
with Image.open("Tests/images/lab.tif") as im: with Image.open("Tests/images/lab.tif") as im:
assert isinstance(im, TiffImagePlugin.TiffImageFile)
assert list(im.get_photoshop_blocks().keys()) == [ assert list(im.get_photoshop_blocks().keys()) == [
1061, 1061,
1002, 1002,
@ -846,6 +882,7 @@ class TestFileTiff:
im = Image.open(tmpfile) im = Image.open(tmpfile)
fp = im.fp fp = im.fp
assert fp is not None
assert not fp.closed assert not fp.closed
im.load() im.load()
assert fp.closed assert fp.closed
@ -859,6 +896,7 @@ class TestFileTiff:
with open(tmpfile, "rb") as f: with open(tmpfile, "rb") as f:
im = Image.open(f) im = Image.open(f)
fp = im.fp fp = im.fp
assert fp is not None
assert not fp.closed assert not fp.closed
im.load() im.load()
assert not fp.closed assert not fp.closed
@ -910,8 +948,9 @@ class TestFileTiffW32:
im.save(tmpfile) im.save(tmpfile)
im = Image.open(tmpfile) im = Image.open(tmpfile)
assert im.fp is not None
assert not im.fp.closed
fp = im.fp fp = im.fp
assert not fp.closed
with pytest.raises(OSError): with pytest.raises(OSError):
os.remove(tmpfile) os.remove(tmpfile)
im.load() im.load()

View File

@ -61,6 +61,7 @@ def test_rt_metadata(tmp_path: Path) -> None:
img.save(f, tiffinfo=info) img.save(f, tiffinfo=info)
with Image.open(f) as loaded: with Image.open(f) as loaded:
assert isinstance(loaded, TiffImagePlugin.TiffImageFile)
assert loaded.tag[ImageJMetaDataByteCounts] == (len(bin_data),) assert loaded.tag[ImageJMetaDataByteCounts] == (len(bin_data),)
assert loaded.tag_v2[ImageJMetaDataByteCounts] == (len(bin_data),) assert loaded.tag_v2[ImageJMetaDataByteCounts] == (len(bin_data),)
@ -80,12 +81,14 @@ def test_rt_metadata(tmp_path: Path) -> None:
info[ImageJMetaDataByteCounts] = (8, len(bin_data) - 8) info[ImageJMetaDataByteCounts] = (8, len(bin_data) - 8)
img.save(f, tiffinfo=info) img.save(f, tiffinfo=info)
with Image.open(f) as loaded: with Image.open(f) as loaded:
assert isinstance(loaded, TiffImagePlugin.TiffImageFile)
assert loaded.tag[ImageJMetaDataByteCounts] == (8, len(bin_data) - 8) assert loaded.tag[ImageJMetaDataByteCounts] == (8, len(bin_data) - 8)
assert loaded.tag_v2[ImageJMetaDataByteCounts] == (8, len(bin_data) - 8) assert loaded.tag_v2[ImageJMetaDataByteCounts] == (8, len(bin_data) - 8)
def test_read_metadata() -> None: def test_read_metadata() -> None:
with Image.open("Tests/images/hopper_g4.tif") as img: with Image.open("Tests/images/hopper_g4.tif") as img:
assert isinstance(img, TiffImagePlugin.TiffImageFile)
assert { assert {
"YResolution": IFDRational(4294967295, 113653537), "YResolution": IFDRational(4294967295, 113653537),
"PlanarConfiguration": 1, "PlanarConfiguration": 1,
@ -128,6 +131,8 @@ 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:
assert isinstance(img, TiffImagePlugin.TiffImageFile)
f = str(tmp_path / "temp.tiff") f = str(tmp_path / "temp.tiff")
del img.tag[278] del img.tag[278]
img.save(f, tiffinfo=img.tag) img.save(f, tiffinfo=img.tag)
@ -135,6 +140,7 @@ def test_write_metadata(tmp_path: Path) -> None:
original = img.tag_v2.named() original = img.tag_v2.named()
with Image.open(f) as loaded: with Image.open(f) as loaded:
assert isinstance(loaded, TiffImagePlugin.TiffImageFile)
reloaded = loaded.tag_v2.named() reloaded = loaded.tag_v2.named()
ignored = ["StripByteCounts", "RowsPerStrip", "PageNumber", "StripOffsets"] ignored = ["StripByteCounts", "RowsPerStrip", "PageNumber", "StripOffsets"]
@ -165,19 +171,21 @@ 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 = str(tmp_path / "temp.tiff")
with Image.open("Tests/images/hopper.tif") as im: with Image.open("Tests/images/hopper.tif") as im:
assert isinstance(im, TiffImagePlugin.TiffImageFile)
info = im.tag_v2 info = im.tag_v2
del info[278] del info[278]
# Resize the image so that STRIPBYTECOUNTS will be larger than a SHORT # Resize the image so that STRIPBYTECOUNTS will be larger than a SHORT
im = im.resize((500, 500)) resized_im = im.resize((500, 500))
info[TiffImagePlugin.IMAGEWIDTH] = im.width info[TiffImagePlugin.IMAGEWIDTH] = resized_im.width
# STRIPBYTECOUNTS can be a SHORT or a LONG # STRIPBYTECOUNTS can be a SHORT or a LONG
info.tagtype[TiffImagePlugin.STRIPBYTECOUNTS] = TiffTags.SHORT info.tagtype[TiffImagePlugin.STRIPBYTECOUNTS] = TiffTags.SHORT
im.save(out, tiffinfo=info) resized_im.save(out, tiffinfo=info)
with Image.open(out) as reloaded: with Image.open(out) as reloaded:
assert isinstance(reloaded, TiffImagePlugin.TiffImageFile)
assert reloaded.tag_v2.tagtype[TiffImagePlugin.STRIPBYTECOUNTS] == TiffTags.LONG assert reloaded.tag_v2.tagtype[TiffImagePlugin.STRIPBYTECOUNTS] == TiffTags.LONG
@ -208,6 +216,7 @@ def test_writing_other_types_to_ascii(
im.save(out, tiffinfo=info) im.save(out, tiffinfo=info)
with Image.open(out) as reloaded: with Image.open(out) as reloaded:
assert isinstance(reloaded, TiffImagePlugin.TiffImageFile)
assert reloaded.tag_v2[271] == expected assert reloaded.tag_v2[271] == expected
@ -225,6 +234,7 @@ def test_writing_other_types_to_bytes(value: int | IFDRational, tmp_path: Path)
im.save(out, tiffinfo=info) im.save(out, tiffinfo=info)
with Image.open(out) as reloaded: with Image.open(out) as reloaded:
assert isinstance(reloaded, TiffImagePlugin.TiffImageFile)
assert reloaded.tag_v2[700] == b"\x01" assert reloaded.tag_v2[700] == b"\x01"
@ -244,6 +254,7 @@ def test_writing_other_types_to_undefined(
im.save(out, tiffinfo=info) im.save(out, tiffinfo=info)
with Image.open(out) as reloaded: with Image.open(out) as reloaded:
assert isinstance(reloaded, TiffImagePlugin.TiffImageFile)
assert reloaded.tag_v2[33723] == b"1" assert reloaded.tag_v2[33723] == b"1"
@ -288,6 +299,7 @@ def test_iccprofile_binary() -> None:
# but probably won't be able to save it. # but probably won't be able to save it.
with Image.open("Tests/images/hopper.iccprofile_binary.tif") as im: with Image.open("Tests/images/hopper.iccprofile_binary.tif") as im:
assert isinstance(im, TiffImagePlugin.TiffImageFile)
assert im.tag_v2.tagtype[34675] == 1 assert im.tag_v2.tagtype[34675] == 1
assert im.info["icc_profile"] assert im.info["icc_profile"]
@ -313,6 +325,7 @@ def test_exif_div_zero(tmp_path: Path) -> None:
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:
assert isinstance(reloaded, TiffImagePlugin.TiffImageFile)
assert 0 == reloaded.tag_v2[41988].numerator assert 0 == reloaded.tag_v2[41988].numerator
assert 0 == reloaded.tag_v2[41988].denominator assert 0 == reloaded.tag_v2[41988].denominator
@ -332,6 +345,7 @@ def test_ifd_unsigned_rational(tmp_path: Path) -> None:
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:
assert isinstance(reloaded, TiffImagePlugin.TiffImageFile)
assert max_long == reloaded.tag_v2[41493].numerator assert max_long == reloaded.tag_v2[41493].numerator
assert 1 == reloaded.tag_v2[41493].denominator assert 1 == reloaded.tag_v2[41493].denominator
@ -344,6 +358,7 @@ def test_ifd_unsigned_rational(tmp_path: Path) -> None:
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:
assert isinstance(reloaded, TiffImagePlugin.TiffImageFile)
assert max_long == reloaded.tag_v2[41493].numerator assert max_long == reloaded.tag_v2[41493].numerator
assert 1 == reloaded.tag_v2[41493].denominator assert 1 == reloaded.tag_v2[41493].denominator
@ -362,6 +377,7 @@ def test_ifd_signed_rational(tmp_path: Path) -> None:
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:
assert isinstance(reloaded, TiffImagePlugin.TiffImageFile)
assert numerator == reloaded.tag_v2[37380].numerator assert numerator == reloaded.tag_v2[37380].numerator
assert denominator == reloaded.tag_v2[37380].denominator assert denominator == reloaded.tag_v2[37380].denominator
@ -374,6 +390,7 @@ def test_ifd_signed_rational(tmp_path: Path) -> None:
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:
assert isinstance(reloaded, TiffImagePlugin.TiffImageFile)
assert numerator == reloaded.tag_v2[37380].numerator assert numerator == reloaded.tag_v2[37380].numerator
assert denominator == reloaded.tag_v2[37380].denominator assert denominator == reloaded.tag_v2[37380].denominator
@ -387,6 +404,7 @@ def test_ifd_signed_rational(tmp_path: Path) -> None:
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:
assert isinstance(reloaded, TiffImagePlugin.TiffImageFile)
assert 2**31 - 1 == reloaded.tag_v2[37380].numerator assert 2**31 - 1 == reloaded.tag_v2[37380].numerator
assert -1 == reloaded.tag_v2[37380].denominator assert -1 == reloaded.tag_v2[37380].denominator
@ -401,6 +419,7 @@ def test_ifd_signed_long(tmp_path: Path) -> None:
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:
assert isinstance(reloaded, TiffImagePlugin.TiffImageFile)
assert reloaded.tag_v2[37000] == -60000 assert reloaded.tag_v2[37000] == -60000
@ -421,11 +440,13 @@ def test_empty_values() -> None:
def test_photoshop_info(tmp_path: Path) -> None: 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 isinstance(im, TiffImagePlugin.TiffImageFile)
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 = str(tmp_path / "temp.tiff")
im.save(out) im.save(out)
with Image.open(out) as reloaded: with Image.open(out) as reloaded:
assert isinstance(reloaded, TiffImagePlugin.TiffImageFile)
assert len(reloaded.tag_v2[34377]) == 70 assert len(reloaded.tag_v2[34377]) == 70
assert isinstance(reloaded.tag_v2[34377], bytes) assert isinstance(reloaded.tag_v2[34377], bytes)

View File

@ -21,7 +21,9 @@ def test_open() -> None:
def test_load() -> None: def test_load() -> None:
with WalImageFile.open(TEST_FILE) as im: with WalImageFile.open(TEST_FILE) as im:
assert im.load()[0, 0] == 122 px = im.load()
assert px is not None
assert px[0, 0] == 122
# Test again now that it has already been loaded once # Test again now that it has already been loaded once
assert im.load()[0, 0] == 122 assert px[0, 0] == 122

View File

@ -218,6 +218,7 @@ class TestFileWebp:
# Save P mode GIF with background # Save P mode GIF with background
with Image.open("Tests/images/chi.gif") as im: with Image.open("Tests/images/chi.gif") as im:
original_value = im.convert("RGB").getpixel((1, 1)) original_value = im.convert("RGB").getpixel((1, 1))
assert isinstance(original_value, tuple)
# Save as WEBP # Save as WEBP
out_webp = str(tmp_path / "temp.webp") out_webp = str(tmp_path / "temp.webp")
@ -230,6 +231,7 @@ class TestFileWebp:
with Image.open(out_gif) as reread: with Image.open(out_gif) as reread:
reread_value = reread.convert("RGB").getpixel((1, 1)) reread_value = reread.convert("RGB").getpixel((1, 1))
assert isinstance(reread_value, tuple)
difference = sum(abs(original_value[i] - reread_value[i]) for i in range(0, 3)) difference = sum(abs(original_value[i] - reread_value[i]) for i in range(0, 3))
assert difference < 5 assert difference < 5

View File

@ -6,7 +6,7 @@ from pathlib import Path
import pytest import pytest
from packaging.version import parse as parse_version from packaging.version import parse as parse_version
from PIL import Image, features from PIL import GifImagePlugin, Image, WebPImagePlugin, features
from .helper import ( from .helper import (
assert_image_equal, assert_image_equal,
@ -22,10 +22,12 @@ def test_n_frames() -> None:
"""Ensure that WebP format sets n_frames and is_animated attributes correctly.""" """Ensure that WebP format sets n_frames and is_animated attributes correctly."""
with Image.open("Tests/images/hopper.webp") as im: with Image.open("Tests/images/hopper.webp") as im:
assert isinstance(im, WebPImagePlugin.WebPImageFile)
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/iss634.webp") as im: with Image.open("Tests/images/iss634.webp") as im:
assert isinstance(im, WebPImagePlugin.WebPImageFile)
assert im.n_frames == 42 assert im.n_frames == 42
assert im.is_animated assert im.is_animated
@ -37,11 +39,13 @@ 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 isinstance(orig, GifImagePlugin.GifImageFile)
assert orig.n_frames > 1 assert orig.n_frames > 1
temp_file = str(tmp_path / "temp.webp") temp_file = str(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 isinstance(im, WebPImagePlugin.WebPImageFile)
assert im.n_frames == orig.n_frames assert im.n_frames == orig.n_frames
# Compare first and last frames to the original animated GIF # Compare first and last frames to the original animated GIF
@ -69,6 +73,7 @@ def test_write_animation_RGB(tmp_path: Path) -> None:
def check(temp_file: str) -> None: def check(temp_file: str) -> None:
with Image.open(temp_file) as im: with Image.open(temp_file) as im:
assert isinstance(im, WebPImagePlugin.WebPImageFile)
assert im.n_frames == 2 assert im.n_frames == 2
# Compare first frame to original # Compare first frame to original
@ -127,6 +132,7 @@ def test_timestamp_and_duration(tmp_path: Path) -> None:
) )
with Image.open(temp_file) as im: with Image.open(temp_file) as im:
assert isinstance(im, WebPImagePlugin.WebPImageFile)
assert im.n_frames == 5 assert im.n_frames == 5
assert im.is_animated assert im.is_animated
@ -170,6 +176,7 @@ def test_seeking(tmp_path: Path) -> None:
) )
with Image.open(temp_file) as im: with Image.open(temp_file) as im:
assert isinstance(im, WebPImagePlugin.WebPImageFile)
assert im.n_frames == 5 assert im.n_frames == 5
assert im.is_animated assert im.is_animated

View File

@ -6,7 +6,7 @@ from types import ModuleType
import pytest import pytest
from PIL import Image from PIL import Image, WebPImagePlugin
from .helper import mark_if_feature_version, skip_unless_feature from .helper import mark_if_feature_version, skip_unless_feature
@ -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"
@ -110,6 +112,7 @@ def test_read_no_exif() -> None:
test_buffer.seek(0) test_buffer.seek(0)
with Image.open(test_buffer) as webp_image: with Image.open(test_buffer) as webp_image:
assert isinstance(webp_image, WebPImagePlugin.WebPImageFile)
assert not webp_image._getexif() assert not webp_image._getexif()

View File

@ -31,7 +31,9 @@ def test_load_raw() -> None:
def test_load() -> None: def test_load() -> None:
with Image.open("Tests/images/drawing.emf") as im: with Image.open("Tests/images/drawing.emf") as im:
if hasattr(Image.core, "drawwmf"): if hasattr(Image.core, "drawwmf"):
assert im.load()[0, 0] == (255, 255, 255) px = im.load()
assert px is not None
assert px[0, 0] == (255, 255, 255)
def test_register_handler(tmp_path: Path) -> None: def test_register_handler(tmp_path: Path) -> None:
@ -64,6 +66,7 @@ def test_load_float_dpi() -> None:
def test_load_set_dpi() -> None: def test_load_set_dpi() -> None:
with Image.open("Tests/images/drawing.wmf") as im: with Image.open("Tests/images/drawing.wmf") as im:
assert isinstance(im, WmfImagePlugin.WmfStubImageFile)
assert im.size == (82, 82) assert im.size == (82, 82)
if hasattr(Image.core, "drawwmf"): if hasattr(Image.core, "drawwmf"):

View File

@ -30,6 +30,7 @@ def test_invalid_file() -> None:
def test_load_read() -> None: def test_load_read() -> None:
# Arrange # Arrange
with Image.open(TEST_FILE) as im: with Image.open(TEST_FILE) as im:
assert isinstance(im, XpmImagePlugin.XpmImageFile)
dummy_bytes = 1 dummy_bytes = 1
# Act # Act

View File

@ -605,8 +605,8 @@ class TestImage:
assert im.mode == mode assert im.mode == mode
assert im.getpixel((0, 0)) == 0 assert im.getpixel((0, 0)) == 0
assert im.getpixel((255, 255)) == 255 assert im.getpixel((255, 255)) == 255
with Image.open(target_file) as target: with Image.open(target_file) as img:
target = target.convert(mode) target = img.convert(mode)
assert_image_equal(im, target) assert_image_equal(im, target)
def test_radial_gradient_wrong_mode(self) -> None: def test_radial_gradient_wrong_mode(self) -> None:
@ -630,8 +630,8 @@ class TestImage:
assert im.mode == mode assert im.mode == mode
assert im.getpixel((0, 0)) == 255 assert im.getpixel((0, 0)) == 255
assert im.getpixel((128, 128)) == 0 assert im.getpixel((128, 128)) == 0
with Image.open(target_file) as target: with Image.open(target_file) as img:
target = target.convert(mode) target = img.convert(mode)
assert_image_equal(im, target) assert_image_equal(im, target)
def test_register_extensions(self) -> None: def test_register_extensions(self) -> None:
@ -652,8 +652,8 @@ class TestImage:
def test_remap_palette(self) -> None: def test_remap_palette(self) -> None:
# Test identity transform # Test identity transform
with Image.open("Tests/images/hopper.gif") as im: with Image.open("Tests/images/hopper.gif") as img:
assert_image_equal(im, im.remap_palette(list(range(256)))) assert_image_equal(img, img.remap_palette(list(range(256))))
# Test identity transform with an RGBA palette # Test identity transform with an RGBA palette
im = Image.new("P", (256, 1)) im = Image.new("P", (256, 1))
@ -662,12 +662,14 @@ class TestImage:
im.putpalette(list(range(256)) * 4, "RGBA") im.putpalette(list(range(256)) * 4, "RGBA")
im_remapped = im.remap_palette(list(range(256))) im_remapped = im.remap_palette(list(range(256)))
assert_image_equal(im, im_remapped) assert_image_equal(im, im_remapped)
assert im.palette is not None
assert im_remapped.palette is not None
assert im.palette.palette == im_remapped.palette.palette assert im.palette.palette == im_remapped.palette.palette
# Test illegal image mode # Test illegal image mode
with hopper() as im: with hopper() as im:
with pytest.raises(ValueError): with pytest.raises(ValueError):
im.remap_palette(None) im.remap_palette([])
def test_remap_palette_transparency(self) -> None: def test_remap_palette_transparency(self) -> None:
im = Image.new("P", (1, 2), (0, 0, 0)) im = Image.new("P", (1, 2), (0, 0, 0))
@ -768,7 +770,7 @@ class TestImage:
assert dict(exif) assert dict(exif)
# Test that exif data is cleared after another load # Test that exif data is cleared after another load
exif.load(None) exif.load(b"")
assert not dict(exif) assert not dict(exif)
# Test loading just the EXIF header # Test loading just the EXIF header

View File

@ -76,8 +76,8 @@ def test_8bit() -> None:
def test_16bit() -> None: def test_16bit() -> None:
with Image.open("Tests/images/16bit.cropped.tif") as im: with Image.open("Tests/images/16bit.cropped.tif") as img:
_test_float_conversion(im) _test_float_conversion(img)
for color in (65535, 65536): for color in (65535, 65536):
im = Image.new("I", (1, 1), color) im = Image.new("I", (1, 1), color)
@ -236,6 +236,7 @@ def test_gif_with_rgba_palette_to_p() -> None:
with Image.open("Tests/images/hopper.gif") as im: with Image.open("Tests/images/hopper.gif") as im:
im.info["transparency"] = 255 im.info["transparency"] = 255
im.load() im.load()
assert im.palette is not None
assert im.palette.mode == "RGB" assert im.palette.mode == "RGB"
im_p = im.convert("P") im_p = im.convert("P")

View File

@ -78,13 +78,13 @@ def test_crop_crash() -> None:
extents = (1, 1, 10, 10) extents = (1, 1, 10, 10)
# works prepatch # works prepatch
with Image.open(test_img) as img: with Image.open(test_img) as img:
img2 = img.crop(extents) img1 = img.crop(extents)
img2.load() img1.load()
# fail prepatch # fail prepatch
with Image.open(test_img) as img: with Image.open(test_img) as img:
img = img.crop(extents) img2 = img.crop(extents)
img.load() img2.load()
def test_crop_zero() -> None: def test_crop_zero() -> None:

View File

@ -38,6 +38,7 @@ def test_close_after_load(caplog: pytest.LogCaptureFixture) -> None:
def test_contextmanager() -> None: def test_contextmanager() -> None:
fn = None fn = None
with Image.open("Tests/images/hopper.gif") as im: with Image.open("Tests/images/hopper.gif") as im:
assert im.fp is not None
fn = im.fp.fileno() fn = im.fp.fileno()
os.fstat(fn) os.fstat(fn)

View File

@ -62,6 +62,7 @@ def test_putpalette_with_alpha_values() -> None:
expected = im.convert("RGBA") expected = im.convert("RGBA")
palette = im.getpalette() palette = im.getpalette()
assert palette is not None
transparency = im.info.pop("transparency") transparency = im.info.pop("transparency")
palette_with_alpha_values = [] palette_with_alpha_values = []

View File

@ -56,20 +56,21 @@ def test_rgba_quantize() -> None:
def test_quantize() -> None: def test_quantize() -> None:
with Image.open("Tests/images/caption_6_33_22.png") as image: with Image.open("Tests/images/caption_6_33_22.png") as image:
image = image.convert("RGB") converted = image.convert("RGB")
converted = image.quantize() converted = converted.quantize()
assert converted.mode == "P" assert converted.mode == "P"
assert_image_similar(converted.convert("RGB"), image, 1) assert_image_similar(converted.convert("RGB"), image, 1)
def test_quantize_no_dither() -> None: def test_quantize_no_dither() -> None:
image = hopper() palette: Image.Image
with Image.open("Tests/images/caption_6_33_22.png") as palette: with Image.open("Tests/images/caption_6_33_22.png") as palette:
palette = palette.convert("P") palette = palette.convert("P")
converted = image.quantize(dither=Image.Dither.NONE, palette=palette) converted = hopper().quantize(dither=Image.Dither.NONE, palette=palette)
assert converted.mode == "P" assert converted.mode == "P"
assert converted.palette is not None assert converted.palette is not None
assert palette.palette is not None
assert converted.palette.palette == palette.palette.palette assert converted.palette.palette == palette.palette.palette
@ -93,8 +94,8 @@ def test_quantize_no_dither2() -> None:
def test_quantize_dither_diff() -> None: def test_quantize_dither_diff() -> None:
image = hopper() image = hopper()
with Image.open("Tests/images/caption_6_33_22.png") as palette: with Image.open("Tests/images/caption_6_33_22.png") as im:
palette = palette.convert("P") palette = im.convert("P")
dither = image.quantize(dither=Image.Dither.FLOYDSTEINBERG, palette=palette) dither = image.quantize(dither=Image.Dither.FLOYDSTEINBERG, palette=palette)
nodither = image.quantize(dither=Image.Dither.NONE, palette=palette) nodither = image.quantize(dither=Image.Dither.NONE, palette=palette)

View File

@ -297,14 +297,14 @@ class TestImageResize:
# Test unknown resampling filter # Test unknown resampling filter
with hopper() as im: with hopper() as im:
with pytest.raises(ValueError): with pytest.raises(ValueError):
im.resize((10, 10), "unknown") im.resize((10, 10), -1)
@skip_unless_feature("libtiff") @skip_unless_feature("libtiff")
def test_load_first(self) -> None: def test_load_first(self) -> None:
# load() may change the size of the image # load() may change the size of the image
# Test that resize() is calling it before getting the size # Test that resize() is calling it before getting the size
with Image.open("Tests/images/g4_orientation_5.tif") as im: with Image.open("Tests/images/g4_orientation_5.tif") as img:
im = im.resize((64, 64)) im = img.resize((64, 64))
assert im.size == (64, 64) assert im.size == (64, 64)
@pytest.mark.parametrize("mode", ("L", "RGB", "I", "F")) @pytest.mark.parametrize("mode", ("L", "RGB", "I", "F"))

View File

@ -40,8 +40,8 @@ def test_mode(mode: str) -> None:
@pytest.mark.parametrize("angle", (0, 90, 180, 270)) @pytest.mark.parametrize("angle", (0, 90, 180, 270))
def test_angle(angle: int) -> None: def test_angle(angle: int) -> None:
with Image.open("Tests/images/test-card.png") as im: with Image.open("Tests/images/test-card.png") as img:
rotate(im, im.mode, angle) rotate(img, img.mode, angle)
im = hopper() im = hopper()
assert_image_equal(im.rotate(angle), im.rotate(angle, expand=1)) assert_image_equal(im.rotate(angle), im.rotate(angle, expand=1))
@ -74,6 +74,7 @@ def test_center_0() -> None:
im = hopper() im = hopper()
im = im.rotate(45, center=(0, 0), resample=Image.Resampling.BICUBIC) im = im.rotate(45, center=(0, 0), resample=Image.Resampling.BICUBIC)
target: Image.Image
with Image.open("Tests/images/hopper_45.png") as target: with Image.open("Tests/images/hopper_45.png") as target:
target_origin = target.size[1] / 2 target_origin = target.size[1] / 2
target = target.crop((0, target_origin, 128, target_origin + 128)) target = target.crop((0, target_origin, 128, target_origin + 128))
@ -85,6 +86,7 @@ def test_center_14() -> None:
im = hopper() im = hopper()
im = im.rotate(45, center=(14, 14), resample=Image.Resampling.BICUBIC) im = im.rotate(45, center=(14, 14), resample=Image.Resampling.BICUBIC)
target: Image.Image
with Image.open("Tests/images/hopper_45.png") as target: with Image.open("Tests/images/hopper_45.png") as target:
target_origin = target.size[1] / 2 - 14 target_origin = target.size[1] / 2 - 14
target = target.crop((6, target_origin, 128 + 6, target_origin + 128)) target = target.crop((6, target_origin, 128 + 6, target_origin + 128))
@ -94,6 +96,8 @@ def test_center_14() -> None:
def test_translate() -> None: def test_translate() -> None:
im = hopper() im = hopper()
target: Image.Image
with Image.open("Tests/images/hopper_45.png") as target: with Image.open("Tests/images/hopper_45.png") as target:
target_origin = (target.size[1] / 2 - 64) - 5 target_origin = (target.size[1] / 2 - 64) - 5
target = target.crop( target = target.crop(

View File

@ -106,20 +106,20 @@ def test_load_first() -> None:
assert im.size == (590, 88) assert im.size == (590, 88)
def test_load_first_unless_jpeg() -> None: def test_load_first_unless_jpeg(monkeypatch: pytest.MonkeyPatch) -> None:
# Test that thumbnail() still uses draft() for JPEG # Test that thumbnail() still uses draft() for JPEG
with Image.open("Tests/images/hopper.jpg") as im: with Image.open("Tests/images/hopper.jpg") as im:
draft = im.draft orig_draft = im.draft
def im_draft( def im_draft(
mode: str, size: tuple[int, int] mode: str, size: tuple[int, int]
) -> tuple[str, tuple[int, int, float, float]] | None: ) -> tuple[str, tuple[int, int, float, float]] | None:
result = draft(mode, size) result = orig_draft(mode, size)
assert result is not None assert result is not None
return result return result
im.draft = im_draft monkeypatch.setattr(im, "draft", im_draft)
im.thumbnail((64, 64)) im.thumbnail((64, 64))
@ -158,6 +158,7 @@ def test_reducing_gap_values() -> None:
def test_reducing_gap_for_DCT_scaling() -> None: def test_reducing_gap_for_DCT_scaling() -> None:
ref: Image.Image
with Image.open("Tests/images/hopper.jpg") as ref: with Image.open("Tests/images/hopper.jpg") as ref:
# thumbnail should call draft with reducing_gap scale # thumbnail should call draft with reducing_gap scale
ref.draft(None, (18 * 3, 18 * 3)) ref.draft(None, (18 * 3, 18 * 3))

View File

@ -47,6 +47,8 @@ class TestImageTransform:
transformed = im.transform( transformed = im.transform(
im.size, Image.Transform.AFFINE, [1, 0, 0, 0, 1, 0] im.size, Image.Transform.AFFINE, [1, 0, 0, 0, 1, 0]
) )
assert im.palette is not None
assert transformed.palette is not None
assert im.palette.palette == transformed.palette.palette assert im.palette.palette == transformed.palette.palette
def test_extent(self) -> None: def test_extent(self) -> None:
@ -245,14 +247,14 @@ class TestImageTransform:
def test_missing_method_data(self) -> None: def test_missing_method_data(self) -> None:
with hopper() as im: with hopper() as im:
with pytest.raises(ValueError): with pytest.raises(ValueError):
im.transform((100, 100), None) im.transform((100, 100), None) # type: ignore[arg-type]
@pytest.mark.parametrize("resample", (Image.Resampling.BOX, "unknown")) @pytest.mark.parametrize("resample", (Image.Resampling.BOX, "unknown"))
def test_unknown_resampling_filter(self, resample: Image.Resampling | str) -> None: def test_unknown_resampling_filter(self, resample: Image.Resampling | str) -> None:
with hopper() as im: with hopper() as im:
(w, h) = im.size (w, h) = im.size
with pytest.raises(ValueError): with pytest.raises(ValueError):
im.transform((100, 100), Image.Transform.EXTENT, (0, 0, w, h), resample) im.transform((100, 100), Image.Transform.EXTENT, (0, 0, w, h), resample) # type: ignore[arg-type]
class TestImageTransformAffine: class TestImageTransformAffine:

View File

@ -190,6 +190,7 @@ def test_bitmap() -> None:
# Arrange # Arrange
im = Image.new("RGB", (W, H)) im = Image.new("RGB", (W, H))
draw = ImageDraw.Draw(im) draw = ImageDraw.Draw(im)
small: Image.Image
with Image.open("Tests/images/pil123rgba.png") as small: with Image.open("Tests/images/pil123rgba.png") as small:
small = small.resize((50, 50), Image.Resampling.NEAREST) small = small.resize((50, 50), Image.Resampling.NEAREST)

View File

@ -249,8 +249,8 @@ def test_colorize_2color() -> None:
# Test the colorizing function with 2-color functionality # Test the colorizing function with 2-color functionality
# Open test image (256px by 10px, black to white) # Open test image (256px by 10px, black to white)
with Image.open("Tests/images/bw_gradient.png") as im: with Image.open("Tests/images/bw_gradient.png") as img:
im = im.convert("L") im = img.convert("L")
# Create image with original 2-color functionality # Create image with original 2-color functionality
im_test = ImageOps.colorize(im, "red", "green") im_test = ImageOps.colorize(im, "red", "green")
@ -289,8 +289,8 @@ def test_colorize_2color_offset() -> None:
# Test the colorizing function with 2-color functionality and offset # Test the colorizing function with 2-color functionality and offset
# Open test image (256px by 10px, black to white) # Open test image (256px by 10px, black to white)
with Image.open("Tests/images/bw_gradient.png") as im: with Image.open("Tests/images/bw_gradient.png") as img:
im = im.convert("L") im = img.convert("L")
# Create image with original 2-color functionality with offsets # Create image with original 2-color functionality with offsets
im_test = ImageOps.colorize( im_test = ImageOps.colorize(
@ -331,8 +331,8 @@ def test_colorize_3color_offset() -> None:
# Test the colorizing function with 3-color functionality and offset # Test the colorizing function with 3-color functionality and offset
# Open test image (256px by 10px, black to white) # Open test image (256px by 10px, black to white)
with Image.open("Tests/images/bw_gradient.png") as im: with Image.open("Tests/images/bw_gradient.png") as img:
im = im.convert("L") im = img.convert("L")
# Create image with new three color functionality with offsets # Create image with new three color functionality with offsets
im_test = ImageOps.colorize( im_test = ImageOps.colorize(
@ -428,6 +428,7 @@ def test_exif_transpose() -> None:
check(orientation_im) check(orientation_im)
# Orientation from "XML:com.adobe.xmp" info key # Orientation from "XML:com.adobe.xmp" info key
im: Image.Image
for suffix in ("", "_exiftool"): for suffix in ("", "_exiftool"):
with Image.open("Tests/images/xmp_tags_orientation" + suffix + ".png") as im: with Image.open("Tests/images/xmp_tags_orientation" + suffix + ".png") as im:
assert im.getexif()[0x0112] == 3 assert im.getexif()[0x0112] == 3
@ -442,10 +443,10 @@ def test_exif_transpose() -> None:
# Orientation from "Raw profile type exif" info key # Orientation from "Raw profile type exif" info key
# This test image has been manually hexedited from exif_imagemagick.png # This test image has been manually hexedited from exif_imagemagick.png
# to have a different orientation # to have a different orientation
with Image.open("Tests/images/exif_imagemagick_orientation.png") as im: with Image.open("Tests/images/exif_imagemagick_orientation.png") as img:
assert im.getexif()[0x0112] == 3 assert img.getexif()[0x0112] == 3
transposed_im = ImageOps.exif_transpose(im) transposed_im = ImageOps.exif_transpose(img)
assert transposed_im is not None assert transposed_im is not None
assert 0x0112 not in transposed_im.getexif() assert 0x0112 not in transposed_im.getexif()

View File

@ -17,6 +17,7 @@ def test_sanity() -> None:
def test_reload() -> None: def test_reload() -> None:
with Image.open("Tests/images/hopper.gif") as im: with Image.open("Tests/images/hopper.gif") as im:
original = im.copy() original = im.copy()
assert im.palette is not None
im.palette.dirty = 1 im.palette.dirty = 1
assert_image_equal(im.convert("RGB"), original.convert("RGB")) assert_image_equal(im.convert("RGB"), original.convert("RGB"))

View File

@ -4,7 +4,7 @@ from pathlib import Path
import pytest import pytest
from PIL import Image, ImageSequence, TiffImagePlugin from PIL import Image, ImageSequence, PsdImagePlugin, TiffImagePlugin
from .helper import assert_image_equal, hopper, skip_unless_feature from .helper import assert_image_equal, hopper, skip_unless_feature
@ -31,6 +31,8 @@ def test_sanity(tmp_path: Path) -> None:
def test_iterator() -> None: def test_iterator() -> None:
with Image.open("Tests/images/multipage.tiff") as im: with Image.open("Tests/images/multipage.tiff") as im:
assert isinstance(im, TiffImagePlugin.TiffImageFile)
i = ImageSequence.Iterator(im) i = ImageSequence.Iterator(im)
for index in range(0, im.n_frames): for index in range(0, im.n_frames):
assert i[index] == next(i) assert i[index] == next(i)
@ -42,6 +44,8 @@ def test_iterator() -> None:
def test_iterator_min_frame() -> None: def test_iterator_min_frame() -> None:
with Image.open("Tests/images/hopper.psd") as im: with Image.open("Tests/images/hopper.psd") as im:
assert isinstance(im, PsdImagePlugin.PsdImageFile)
i = ImageSequence.Iterator(im) i = ImageSequence.Iterator(im)
for index in range(1, im.n_frames): for index in range(1, im.n_frames):
assert i[index] == next(i) assert i[index] == next(i)
@ -74,9 +78,14 @@ def test_consecutive() -> None:
def test_palette_mmap() -> None: def test_palette_mmap() -> None:
# Using mmap in ImageFile can require to reload the palette. # Using mmap in ImageFile can require to reload the palette.
with Image.open("Tests/images/multipage-mmap.tiff") as im: with Image.open("Tests/images/multipage-mmap.tiff") as im:
color1 = im.getpalette()[:3] palette = im.getpalette()
assert palette is not None
color1 = palette[:3]
im.seek(0) im.seek(0)
color2 = im.getpalette()[:3]
palette = im.getpalette()
assert palette is not None
color2 = palette[:3]
assert color1 == color2 assert color1 == color2

View File

@ -17,6 +17,7 @@ def helper_pickle_file(
tmp_path: Path, protocol: int, test_file: str, mode: str | None tmp_path: Path, protocol: int, test_file: str, mode: str | None
) -> None: ) -> None:
# Arrange # Arrange
im: Image.Image
with Image.open(test_file) as im: with Image.open(test_file) as im:
filename = str(tmp_path / "temp.pkl") filename = str(tmp_path / "temp.pkl")
if mode: if mode:
@ -33,6 +34,7 @@ def helper_pickle_file(
def helper_pickle_string(protocol: int, test_file: str, mode: str | None) -> None: def helper_pickle_string(protocol: int, test_file: str, mode: str | None) -> None:
im: Image.Image
with Image.open(test_file) as im: with Image.open(test_file) as im:
if mode: if mode:
im = im.convert(mode) im = im.convert(mode)
@ -77,8 +79,8 @@ def test_pickle_image(
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 = str(tmp_path / "temp.pkl")
with Image.open("Tests/images/hopper.jpg") as im: with Image.open("Tests/images/hopper.jpg") as img:
im = im.convert("PA") im = img.convert("PA")
# Act / Assert # Act / Assert
for protocol in range(0, pickle.HIGHEST_PROTOCOL + 1): for protocol in range(0, pickle.HIGHEST_PROTOCOL + 1):

View File

@ -39,6 +39,7 @@ class TestShellInjection:
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:
assert isinstance(im, JpegImagePlugin.JpegImageFile)
im.load_djpeg() im.load_djpeg()
@pytest.mark.skipif(not cjpeg_available(), reason="cjpeg not available") @pytest.mark.skipif(not cjpeg_available(), reason="cjpeg not available")
@ -49,11 +50,13 @@ class TestShellInjection:
@pytest.mark.skipif(not netpbm_available(), reason="Netpbm not available") @pytest.mark.skipif(not netpbm_available(), reason="Netpbm not available")
def test_save_netpbm_filename_bmp_mode(self, tmp_path: Path) -> None: def test_save_netpbm_filename_bmp_mode(self, tmp_path: Path) -> None:
with Image.open(TEST_GIF) as im: with Image.open(TEST_GIF) as im:
im = im.convert("RGB") im_rgb = im.convert("RGB")
self.assert_save_filename_check(tmp_path, im, GifImagePlugin._save_netpbm) self.assert_save_filename_check(
tmp_path, im_rgb, GifImagePlugin._save_netpbm
)
@pytest.mark.skipif(not netpbm_available(), reason="Netpbm not available") @pytest.mark.skipif(not netpbm_available(), reason="Netpbm not available")
def test_save_netpbm_filename_l_mode(self, tmp_path: Path) -> None: def test_save_netpbm_filename_l_mode(self, tmp_path: Path) -> None:
with Image.open(TEST_GIF) as im: with Image.open(TEST_GIF) as im:
im = im.convert("L") im_l = im.convert("L")
self.assert_save_filename_check(tmp_path, im, GifImagePlugin._save_netpbm) self.assert_save_filename_check(tmp_path, im_l, GifImagePlugin._save_netpbm)

View File

@ -72,4 +72,5 @@ def test_ifd_rational_save(
im.save(out, dpi=(res, res), compression="raw") im.save(out, dpi=(res, res), compression="raw")
with Image.open(out) as reloaded: with Image.open(out) as reloaded:
assert isinstance(reloaded, TiffImagePlugin.TiffImageFile)
assert float(IFDRational(301, 1)) == float(reloaded.tag_v2[282]) assert float(IFDRational(301, 1)) == float(reloaded.tag_v2[282])

View File

@ -213,6 +213,7 @@ class DdsImageFile(ImageFile.ImageFile):
format_description = "DirectDraw Surface" format_description = "DirectDraw Surface"
def _open(self) -> None: def _open(self) -> None:
assert self.fp is not None
if not _accept(self.fp.read(4)): if not _accept(self.fp.read(4)):
msg = "not a DDS file" msg = "not a DDS file"
raise SyntaxError(msg) raise SyntaxError(msg)

View File

@ -258,6 +258,7 @@ class BlpImageFile(ImageFile.ImageFile):
format_description = "Blizzard Mipmap Format" format_description = "Blizzard Mipmap Format"
def _open(self) -> None: def _open(self) -> None:
assert self.fp is not None
self.magic = self.fp.read(4) self.magic = self.fp.read(4)
self.fp.seek(5, os.SEEK_CUR) self.fp.seek(5, os.SEEK_CUR)

View File

@ -74,6 +74,7 @@ class BmpImageFile(ImageFile.ImageFile):
def _bitmap(self, header: int = 0, offset: int = 0) -> None: def _bitmap(self, header: int = 0, offset: int = 0) -> None:
"""Read relevant info about the BMP""" """Read relevant info about the BMP"""
assert self.fp is not None
read, seek = self.fp.read, self.fp.seek read, seek = self.fp.read, self.fp.seek
if header: if header:
seek(header) seek(header)
@ -307,6 +308,7 @@ class BmpImageFile(ImageFile.ImageFile):
def _open(self) -> None: def _open(self) -> None:
"""Open file, check magic number and read header""" """Open file, check magic number and read header"""
# read 14 bytes: magic number, filesize, reserved, header final offset # read 14 bytes: magic number, filesize, reserved, header final offset
assert self.fp is not None
head_data = self.fp.read(14) head_data = self.fp.read(14)
# choke if the file does not have the required magic bytes # choke if the file does not have the required magic bytes
if not _accept(head_data): if not _accept(head_data):

View File

@ -40,6 +40,7 @@ class BufrStubImageFile(ImageFile.StubImageFile):
format_description = "BUFR" format_description = "BUFR"
def _open(self) -> None: def _open(self) -> None:
assert self.fp is not None
offset = self.fp.tell() offset = self.fp.tell()
if not _accept(self.fp.read(4)): if not _accept(self.fp.read(4)):

View File

@ -38,6 +38,7 @@ class CurImageFile(BmpImagePlugin.BmpImageFile):
format_description = "Windows Cursor" format_description = "Windows Cursor"
def _open(self) -> None: def _open(self) -> None:
assert self.fp is not None
offset = self.fp.tell() offset = self.fp.tell()
# check magic # check magic

View File

@ -24,6 +24,7 @@ from __future__ import annotations
from . import Image from . import Image
from ._binary import i32le as i32 from ._binary import i32le as i32
from ._util import DeferredError
from .PcxImagePlugin import PcxImageFile from .PcxImagePlugin import PcxImageFile
MAGIC = 0x3ADE68B1 # QUIZ: what's this value, then? MAGIC = 0x3ADE68B1 # QUIZ: what's this value, then?
@ -44,6 +45,7 @@ class DcxImageFile(PcxImageFile):
def _open(self) -> None: def _open(self) -> None:
# Header # Header
assert self.fp is not None
s = self.fp.read(4) s = self.fp.read(4)
if not _accept(s): if not _accept(s):
msg = "not a DCX file" msg = "not a DCX file"
@ -66,6 +68,8 @@ class DcxImageFile(PcxImageFile):
def seek(self, frame: int) -> None: def seek(self, frame: int) -> None:
if not self._seek_check(frame): if not self._seek_check(frame):
return return
if isinstance(self._fp, DeferredError):
raise self._fp.ex
self.frame = frame self.frame = frame
self.fp = self._fp self.fp = self._fp
self.fp.seek(self._offset[frame]) self.fp.seek(self._offset[frame])

View File

@ -333,6 +333,7 @@ class DdsImageFile(ImageFile.ImageFile):
format_description = "DirectDraw Surface" format_description = "DirectDraw Surface"
def _open(self) -> None: def _open(self) -> None:
assert self.fp is not None
if not _accept(self.fp.read(4)): if not _accept(self.fp.read(4)):
msg = "not a DDS file" msg = "not a DDS file"
raise SyntaxError(msg) raise SyntaxError(msg)

View File

@ -184,6 +184,7 @@ class EpsImageFile(ImageFile.ImageFile):
mode_map = {1: "L", 2: "LAB", 3: "RGB", 4: "CMYK"} mode_map = {1: "L", 2: "LAB", 3: "RGB", 4: "CMYK"}
def _open(self) -> None: def _open(self) -> None:
assert self.fp is not None
(length, offset) = self._find_offset(self.fp) (length, offset) = self._find_offset(self.fp)
# go to offset - start of "%!PS" # go to offset - start of "%!PS"
@ -377,6 +378,7 @@ class EpsImageFile(ImageFile.ImageFile):
) -> Image.core.PixelAccess | None: ) -> Image.core.PixelAccess | None:
# Load EPS via Ghostscript # Load EPS via Ghostscript
if self.tile: if self.tile:
assert self.fp is not None
self.im = Ghostscript(self.tile, self.size, self.fp, scale, transparency) self.im = Ghostscript(self.tile, self.size, self.fp, scale, transparency)
self._mode = self.im.mode self._mode = self.im.mode
self._size = self.im.size self._size = self.im.size

View File

@ -22,6 +22,7 @@ from . import Image, ImageFile, ImagePalette
from ._binary import i16le as i16 from ._binary import i16le as i16
from ._binary import i32le as i32 from ._binary import i32le as i32
from ._binary import o8 from ._binary import o8
from ._util import DeferredError
# #
# decoder # decoder
@ -47,6 +48,7 @@ class FliImageFile(ImageFile.ImageFile):
def _open(self) -> None: def _open(self) -> None:
# HEAD # HEAD
assert self.fp is not None
s = self.fp.read(128) s = self.fp.read(128)
if not (_accept(s) and s[20:22] == b"\x00\x00"): if not (_accept(s) and s[20:22] == b"\x00\x00"):
msg = "not an FLI/FLC file" msg = "not an FLI/FLC file"
@ -110,6 +112,7 @@ class FliImageFile(ImageFile.ImageFile):
# load palette # load palette
i = 0 i = 0
assert self.fp is not None
for e in range(i16(self.fp.read(2))): for e in range(i16(self.fp.read(2))):
s = self.fp.read(2) s = self.fp.read(2)
i = i + s[0] i = i + s[0]
@ -134,6 +137,8 @@ class FliImageFile(ImageFile.ImageFile):
self._seek(f) self._seek(f)
def _seek(self, frame: int) -> None: def _seek(self, frame: int) -> None:
if isinstance(self._fp, DeferredError):
raise self._fp.ex
if frame == 0: if frame == 0:
self.__frame = -1 self.__frame = -1
self._fp.seek(self.__rewind) self._fp.seek(self.__rewind)

View File

@ -58,6 +58,7 @@ class FpxImageFile(ImageFile.ImageFile):
# read the OLE directory and see if this is a likely # read the OLE directory and see if this is a likely
# to be a FlashPix file # to be a FlashPix file
assert self.fp is not None
try: try:
self.ole = olefile.OleFileIO(self.fp) self.ole = olefile.OleFileIO(self.fp)
except OSError as e: except OSError as e:
@ -229,6 +230,7 @@ class FpxImageFile(ImageFile.ImageFile):
if y >= ysize: if y >= ysize:
break # isn't really required break # isn't really required
assert self.fp is not None
self.stream = stream self.stream = stream
self._fp = self.fp self._fp = self.fp
self.fp = None self.fp = None

View File

@ -72,6 +72,7 @@ class FtexImageFile(ImageFile.ImageFile):
format_description = "Texture File Format (IW2:EOC)" format_description = "Texture File Format (IW2:EOC)"
def _open(self) -> None: def _open(self) -> None:
assert self.fp is not None
if not _accept(self.fp.read(4)): if not _accept(self.fp.read(4)):
msg = "not an FTEX file" msg = "not an FTEX file"
raise SyntaxError(msg) raise SyntaxError(msg)

View File

@ -42,6 +42,7 @@ class GbrImageFile(ImageFile.ImageFile):
format_description = "GIMP brush file" format_description = "GIMP brush file"
def _open(self) -> None: def _open(self) -> None:
assert self.fp is not None
header_size = i32(self.fp.read(4)) header_size = i32(self.fp.read(4))
if header_size < 20: if header_size < 20:
msg = "not a GIMP brush" msg = "not a GIMP brush"
@ -91,6 +92,8 @@ class GbrImageFile(ImageFile.ImageFile):
def load(self) -> Image.core.PixelAccess | None: def load(self) -> Image.core.PixelAccess | None:
if self._im is None: if self._im is None:
self.im = Image.core.new(self.mode, self.size) self.im = Image.core.new(self.mode, self.size)
assert self.fp is not None
self.frombytes(self.fp.read(self._data_size)) self.frombytes(self.fp.read(self._data_size))
return Image.Image.load(self) return Image.Image.load(self)

View File

@ -31,7 +31,7 @@ import os
import subprocess import subprocess
from enum import IntEnum from enum import IntEnum
from functools import cached_property from functools import cached_property
from typing import IO, TYPE_CHECKING, Any, Literal, NamedTuple, Union from typing import IO, TYPE_CHECKING, Any, Literal, NamedTuple, Union, cast
from . import ( from . import (
Image, Image,
@ -45,6 +45,7 @@ from . import (
from ._binary import i16le as i16 from ._binary import i16le as i16
from ._binary import o8 from ._binary import o8
from ._binary import o16le as o16 from ._binary import o16le as o16
from ._util import DeferredError
if TYPE_CHECKING: if TYPE_CHECKING:
from . import _imaging from . import _imaging
@ -83,6 +84,7 @@ class GifImageFile(ImageFile.ImageFile):
global_palette = None global_palette = None
def data(self) -> bytes | None: def data(self) -> bytes | None:
assert self.fp is not None
s = self.fp.read(1) s = self.fp.read(1)
if s and s[0]: if s and s[0]:
return self.fp.read(s[0]) return self.fp.read(s[0])
@ -96,6 +98,7 @@ class GifImageFile(ImageFile.ImageFile):
def _open(self) -> None: def _open(self) -> None:
# Screen # Screen
assert self.fp is not None
s = self.fp.read(13) s = self.fp.read(13)
if not _accept(s): if not _accept(s):
msg = "not a GIF file" msg = "not a GIF file"
@ -113,8 +116,8 @@ class GifImageFile(ImageFile.ImageFile):
# check if palette contains colour indices # check if palette contains colour indices
p = self.fp.read(3 << bits) p = self.fp.read(3 << bits)
if self._is_palette_needed(p): if self._is_palette_needed(p):
p = ImagePalette.raw("RGB", p) palette = ImagePalette.raw("RGB", p)
self.global_palette = self.palette = p self.global_palette = self.palette = palette
self._fp = self.fp # FIXME: hack self._fp = self.fp # FIXME: hack
self.__rewind = self.fp.tell() self.__rewind = self.fp.tell()
@ -168,6 +171,8 @@ class GifImageFile(ImageFile.ImageFile):
raise EOFError(msg) from e raise EOFError(msg) from e
def _seek(self, frame: int, update_image: bool = True) -> None: def _seek(self, frame: int, update_image: bool = True) -> None:
if isinstance(self._fp, DeferredError):
raise self._fp.ex
if frame == 0: if frame == 0:
# rewind # rewind
self.__offset = 0 self.__offset = 0
@ -251,7 +256,7 @@ class GifImageFile(ImageFile.ImageFile):
info["comment"] += b"\n" + comment info["comment"] += b"\n" + comment
else: else:
info["comment"] = comment info["comment"] = comment
s = None s = b""
continue continue
elif s[0] == 255 and frame == 0 and block is not None: elif s[0] == 255 and frame == 0 and block is not None:
# #
@ -294,7 +299,7 @@ class GifImageFile(ImageFile.ImageFile):
bits = self.fp.read(1)[0] bits = self.fp.read(1)[0]
self.__offset = self.fp.tell() self.__offset = self.fp.tell()
break break
s = None s = b""
if interlace is None: if interlace is None:
msg = "image not found in GIF frame" msg = "image not found in GIF frame"
@ -347,7 +352,10 @@ class GifImageFile(ImageFile.ImageFile):
if self._frame_palette: if self._frame_palette:
if color * 3 + 3 > len(self._frame_palette.palette): if color * 3 + 3 > len(self._frame_palette.palette):
color = 0 color = 0
return tuple(self._frame_palette.palette[color * 3 : color * 3 + 3]) return cast(
tuple[int, int, int],
tuple(self._frame_palette.palette[color * 3 : color * 3 + 3]),
)
else: else:
return (color, color, color) return (color, color, color)

View File

@ -40,6 +40,7 @@ class GribStubImageFile(ImageFile.StubImageFile):
format_description = "GRIB" format_description = "GRIB"
def _open(self) -> None: def _open(self) -> None:
assert self.fp is not None
offset = self.fp.tell() offset = self.fp.tell()
if not _accept(self.fp.read(8)): if not _accept(self.fp.read(8)):

View File

@ -40,6 +40,7 @@ class HDF5StubImageFile(ImageFile.StubImageFile):
format_description = "HDF5" format_description = "HDF5"
def _open(self) -> None: def _open(self) -> None:
assert self.fp is not None
offset = self.fp.tell() offset = self.fp.tell()
if not _accept(self.fp.read(8)): if not _accept(self.fp.read(8)):

View File

@ -266,6 +266,7 @@ class IcnsImageFile(ImageFile.ImageFile):
format_description = "Mac OS icns resource" format_description = "Mac OS icns resource"
def _open(self) -> None: def _open(self) -> None:
assert self.fp is not None
self.icns = IcnsFile(self.fp) self.icns = IcnsFile(self.fp)
self._mode = "RGBA" self._mode = "RGBA"
self.info["sizes"] = self.icns.itersizes() self.info["sizes"] = self.icns.itersizes()

View File

@ -326,6 +326,7 @@ class IcoImageFile(ImageFile.ImageFile):
format_description = "Windows Icon" format_description = "Windows Icon"
def _open(self) -> None: def _open(self) -> None:
assert self.fp is not None
self.ico = IcoFile(self.fp) self.ico = IcoFile(self.fp)
self.info["sizes"] = self.ico.sizes() self.info["sizes"] = self.ico.sizes()
self.size = self.ico.entry[0].dim self.size = self.ico.entry[0].dim

View File

@ -31,6 +31,7 @@ import re
from typing import IO, Any from typing import IO, Any
from . import Image, ImageFile, ImagePalette from . import Image, ImageFile, ImagePalette
from ._util import DeferredError
# -------------------------------------------------------------------- # --------------------------------------------------------------------
# Standard tags # Standard tags
@ -124,6 +125,7 @@ class ImImageFile(ImageFile.ImageFile):
# Quick rejection: if there's not an LF among the first # Quick rejection: if there's not an LF among the first
# 100 bytes, this is (probably) not a text header. # 100 bytes, this is (probably) not a text header.
assert self.fp is not None
if b"\n" not in self.fp.read(100): if b"\n" not in self.fp.read(100):
msg = "not an IM file" msg = "not an IM file"
raise SyntaxError(msg) raise SyntaxError(msg)
@ -301,6 +303,8 @@ class ImImageFile(ImageFile.ImageFile):
size = ((self.size[0] * bits + 7) // 8) * self.size[1] size = ((self.size[0] * bits + 7) // 8) * self.size[1]
offs = self.__offset + frame * size offs = self.__offset + frame * size
if isinstance(self._fp, DeferredError):
raise self._fp.ex
self.fp = self._fp self.fp = self._fp
self.tile = [ self.tile = [

View File

@ -604,22 +604,11 @@ class Image:
return new return new
# Context manager support # Context manager support
def __enter__(self): def __enter__(self) -> Image:
return self return self
def _close_fp(self): def __exit__(self, *args: object) -> None:
if getattr(self, "_fp", False): pass
if self._fp != self.fp:
self._fp.close()
self._fp = DeferredError(ValueError("Operation on closed image"))
if self.fp:
self.fp.close()
def __exit__(self, *args):
if hasattr(self, "fp"):
if getattr(self, "_exclusive_fp", False):
self._close_fp()
self.fp = None
def close(self) -> None: def close(self) -> None:
""" """
@ -633,13 +622,6 @@ class Image:
:py:meth:`~PIL.Image.Image.load` method. See :ref:`file-handling` for :py:meth:`~PIL.Image.Image.load` method. See :ref:`file-handling` for
more information. more information.
""" """
if hasattr(self, "fp"):
try:
self._close_fp()
self.fp = None
except Exception as msg:
logger.debug("Error closing: %s", msg)
if getattr(self, "map", None): if getattr(self, "map", None):
self.map: mmap.mmap | None = None self.map: mmap.mmap | None = None
@ -1543,10 +1525,14 @@ class Image:
exif_info = bytes.fromhex( exif_info = bytes.fromhex(
"".join(self.info["Raw profile type exif"].split("\n")[3:]) "".join(self.info["Raw profile type exif"].split("\n")[3:])
) )
elif hasattr(self, "tag_v2"): else:
self._exif.bigtiff = self.tag_v2._bigtiff from . import TiffImagePlugin
self._exif.endian = self.tag_v2._endian
self._exif.load_from_fp(self.fp, self.tag_v2._offset) if isinstance(self, TiffImagePlugin.TiffImageFile):
self._exif.bigtiff = self.tag_v2._bigtiff
self._exif.endian = self.tag_v2._endian
assert self.fp is not None
self._exif.load_from_fp(self.fp, self.tag_v2._offset)
if exif_info is not None: if exif_info is not None:
self._exif.load(exif_info) self._exif.load(exif_info)
@ -1566,52 +1552,6 @@ class Image:
self._exif._loaded = False self._exif._loaded = False
self.getexif() self.getexif()
def get_child_images(self) -> list[ImageFile.ImageFile]:
child_images = []
exif = self.getexif()
ifds = []
if ExifTags.Base.SubIFDs in exif:
subifd_offsets = exif[ExifTags.Base.SubIFDs]
if subifd_offsets:
if not isinstance(subifd_offsets, tuple):
subifd_offsets = (subifd_offsets,)
for subifd_offset in subifd_offsets:
ifds.append((exif._get_ifd_dict(subifd_offset), subifd_offset))
ifd1 = exif.get_ifd(ExifTags.IFD.IFD1)
if ifd1 and ifd1.get(513):
assert exif._info is not None
ifds.append((ifd1, exif._info.next))
offset = None
for ifd, ifd_offset in ifds:
current_offset = self.fp.tell()
if offset is None:
offset = current_offset
fp = self.fp
if ifd is not None:
thumbnail_offset = ifd.get(513)
if thumbnail_offset is not None:
thumbnail_offset += getattr(self, "_exif_offset", 0)
self.fp.seek(thumbnail_offset)
data = self.fp.read(ifd.get(514))
fp = io.BytesIO(data)
with open(fp) as im:
from . import TiffImagePlugin
if thumbnail_offset is None and isinstance(
im, TiffImagePlugin.TiffImageFile
):
im._frame_pos = [ifd_offset]
im._seek(0)
im.load()
child_images.append(im)
if offset is not None:
self.fp.seek(offset)
return child_images
def getim(self) -> CapsuleType: def getim(self) -> CapsuleType:
""" """
Returns a capsule that points to the internal image memory. Returns a capsule that points to the internal image memory.
@ -2522,7 +2462,10 @@ class Image:
) )
def save( def save(
self, fp: StrOrBytesPath | IO[bytes], format: str | None = None, **params: Any self,
fp: StrOrBytesPath | IO[bytes] | io.TextIOWrapper,
format: str | None = None,
**params: Any,
) -> None: ) -> None:
""" """
Saves this image under the given filename. If no format is Saves this image under the given filename. If no format is

View File

@ -31,18 +31,21 @@ from __future__ import annotations
import abc import abc
import io import io
import itertools import itertools
import logging
import os import os
import struct import struct
import sys import sys
from typing import IO, TYPE_CHECKING, Any, NamedTuple, cast from typing import IO, TYPE_CHECKING, Any, NamedTuple, cast
from . import Image from . import ExifTags, Image
from ._deprecate import deprecate from ._deprecate import deprecate
from ._util import is_path from ._util import DeferredError, is_path
if TYPE_CHECKING: if TYPE_CHECKING:
from ._typing import StrOrBytesPath from ._typing import StrOrBytesPath
logger = logging.getLogger(__name__)
MAXBLOCK = 65536 MAXBLOCK = 65536
SAFEBLOCK = 1024 * 1024 SAFEBLOCK = 1024 * 1024
@ -127,6 +130,8 @@ class ImageFile(Image.Image):
self.decoderconfig: tuple[Any, ...] = () self.decoderconfig: tuple[Any, ...] = ()
self.decodermaxblock = MAXBLOCK self.decodermaxblock = MAXBLOCK
self.fp: IO[bytes] | None
self._fp: IO[bytes] | DeferredError
if is_path(fp): if is_path(fp):
# filename # filename
self.fp = open(fp, "rb") self.fp = open(fp, "rb")
@ -163,6 +168,93 @@ class ImageFile(Image.Image):
def _open(self) -> None: def _open(self) -> None:
pass pass
# Context manager support
def __enter__(self) -> ImageFile:
return self
def _close_fp(self) -> None:
if getattr(self, "_fp", False):
if self._fp != self.fp and not isinstance(self._fp, DeferredError):
self._fp.close()
self._fp = DeferredError(ValueError("Operation on closed image"))
if self.fp:
self.fp.close()
def __exit__(self, *args: object) -> None:
if getattr(self, "_exclusive_fp", False):
self._close_fp()
self.fp = None
def close(self) -> None:
"""
Closes the file pointer, if possible.
This operation will destroy the image core and release its memory.
The image data will be unusable afterward.
This function is required to close images that have multiple frames or
have not had their file read and closed by the
:py:meth:`~PIL.Image.Image.load` method. See :ref:`file-handling` for
more information.
"""
try:
self._close_fp()
self.fp = None
except Exception as msg:
logger.debug("Error closing: %s", msg)
super().close()
def get_child_images(self) -> list[ImageFile]:
child_images = []
exif = self.getexif()
ifds = []
if ExifTags.Base.SubIFDs in exif:
subifd_offsets = exif[ExifTags.Base.SubIFDs]
if subifd_offsets:
if not isinstance(subifd_offsets, tuple):
subifd_offsets = (subifd_offsets,)
for subifd_offset in subifd_offsets:
ifds.append((exif._get_ifd_dict(subifd_offset), subifd_offset))
ifd1 = exif.get_ifd(ExifTags.IFD.IFD1)
if ifd1 and ifd1.get(513):
assert exif._info is not None
ifds.append((ifd1, exif._info.next))
offset = None
assert self.fp is not None
for ifd, ifd_offset in ifds:
current_offset = self.fp.tell()
if offset is None:
offset = current_offset
fp = self.fp
if ifd is not None:
thumbnail_offset = ifd.get(513)
if thumbnail_offset is not None:
thumbnail_offset += getattr(self, "_exif_offset", 0)
self.fp.seek(thumbnail_offset)
length = ifd.get(514)
assert isinstance(length, int)
data = self.fp.read(length)
fp = io.BytesIO(data)
with Image.open(fp) as im:
from . import TiffImagePlugin
if thumbnail_offset is None and isinstance(
im, TiffImagePlugin.TiffImageFile
):
im._frame_pos = [ifd_offset]
im._seek(0)
im.load()
child_images.append(im)
if offset is not None:
self.fp.seek(offset)
return child_images
def get_format_mimetype(self) -> str | None: def get_format_mimetype(self) -> str | None:
if self.custom_mimetype: if self.custom_mimetype:
return self.custom_mimetype return self.custom_mimetype
@ -179,7 +271,7 @@ class ImageFile(Image.Image):
# raise exception if something's wrong. must be called # raise exception if something's wrong. must be called
# directly after open, and closes file when finished. # directly after open, and closes file when finished.
if self._exclusive_fp: if self._exclusive_fp and self.fp:
self.fp.close() self.fp.close()
self.fp = None self.fp = None
@ -199,6 +291,7 @@ class ImageFile(Image.Image):
# As of pypy 2.1.0, memory mapping was failing here. # As of pypy 2.1.0, memory mapping was failing here.
use_mmap = use_mmap and not hasattr(sys, "pypy_version_info") use_mmap = use_mmap and not hasattr(sys, "pypy_version_info")
assert self.fp is not None
readonly = 0 readonly = 0
# look for read/seek overrides # look for read/seek overrides

View File

@ -77,6 +77,7 @@ class IptcImageFile(ImageFile.ImageFile):
def field(self) -> tuple[tuple[int, int] | None, int]: def field(self) -> tuple[tuple[int, int] | None, int]:
# #
# get a IPTC field header # get a IPTC field header
assert self.fp is not None
s = self.fp.read(5) s = self.fp.read(5)
if not s.strip(b"\x00"): if not s.strip(b"\x00"):
return None, 0 return None, 0
@ -104,6 +105,7 @@ class IptcImageFile(ImageFile.ImageFile):
def _open(self) -> None: def _open(self) -> None:
# load descriptive fields # load descriptive fields
assert self.fp is not None
while True: while True:
offset = self.fp.tell() offset = self.fp.tell()
tag, size = self.field() tag, size = self.field()
@ -157,6 +159,7 @@ class IptcImageFile(ImageFile.ImageFile):
offset, compression = self.tile[0][2:] offset, compression = self.tile[0][2:]
assert self.fp is not None
self.fp.seek(offset) self.fp.seek(offset)
# Copy image data to temporary file # Copy image data to temporary file
@ -165,6 +168,7 @@ class IptcImageFile(ImageFile.ImageFile):
# To simplify access to the extracted file, # To simplify access to the extracted file,
# prepend a PPM header # prepend a PPM header
o.write(b"P5\n%d %d\n255\n" % self.size) o.write(b"P5\n%d %d\n255\n" % self.size)
assert self.fp is not None
while True: while True:
type, size = self.field() type, size = self.field()
if type != (8, 10): if type != (8, 10):
@ -188,7 +192,7 @@ Image.register_extension(IptcImageFile.format, ".iim")
def getiptcinfo( def getiptcinfo(
im: ImageFile.ImageFile, im: Image.Image,
) -> dict[tuple[int, int], bytes | list[bytes]] | None: ) -> dict[tuple[int, int], bytes | list[bytes]] | None:
""" """
Get IPTC information from TIFF, JPEG, or IPTC file. Get IPTC information from TIFF, JPEG, or IPTC file.

View File

@ -248,6 +248,7 @@ class Jpeg2KImageFile(ImageFile.ImageFile):
format_description = "JPEG 2000 (ISO 15444)" format_description = "JPEG 2000 (ISO 15444)"
def _open(self) -> None: def _open(self) -> None:
assert self.fp is not None
sig = self.fp.read(4) sig = self.fp.read(4)
if sig == b"\xff\x4f\xff\x51": if sig == b"\xff\x4f\xff\x51":
self.codec = "j2k" self.codec = "j2k"
@ -296,6 +297,7 @@ class Jpeg2KImageFile(ImageFile.ImageFile):
] ]
def _parse_comment(self) -> None: def _parse_comment(self) -> None:
assert self.fp is not None
hdr = self.fp.read(2) hdr = self.fp.read(2)
length = _binary.i16be(hdr) length = _binary.i16be(hdr)
self.fp.seek(length - 2, os.SEEK_CUR) self.fp.seek(length - 2, os.SEEK_CUR)

View File

@ -60,6 +60,7 @@ if TYPE_CHECKING:
def Skip(self: JpegImageFile, marker: int) -> None: def Skip(self: JpegImageFile, marker: int) -> None:
assert self.fp is not None
n = i16(self.fp.read(2)) - 2 n = i16(self.fp.read(2)) - 2
ImageFile._safe_read(self.fp, n) ImageFile._safe_read(self.fp, n)
@ -69,6 +70,7 @@ def APP(self: JpegImageFile, marker: int) -> None:
# Application marker. Store these in the APP dictionary. # Application marker. Store these in the APP dictionary.
# Also look for well-known application markers. # Also look for well-known application markers.
assert self.fp is not None
n = i16(self.fp.read(2)) - 2 n = i16(self.fp.read(2)) - 2
s = ImageFile._safe_read(self.fp, n) s = ImageFile._safe_read(self.fp, n)
@ -170,6 +172,7 @@ def APP(self: JpegImageFile, marker: int) -> None:
def COM(self: JpegImageFile, marker: int) -> None: def COM(self: JpegImageFile, marker: int) -> None:
# #
# Comment marker. Store these in the APP dictionary. # Comment marker. Store these in the APP dictionary.
assert self.fp is not None
n = i16(self.fp.read(2)) - 2 n = i16(self.fp.read(2)) - 2
s = ImageFile._safe_read(self.fp, n) s = ImageFile._safe_read(self.fp, n)
@ -186,6 +189,7 @@ def SOF(self: JpegImageFile, marker: int) -> None:
# mode. Note that this could be made a bit brighter, by # mode. Note that this could be made a bit brighter, by
# looking for JFIF and Adobe APP markers. # looking for JFIF and Adobe APP markers.
assert self.fp is not None
n = i16(self.fp.read(2)) - 2 n = i16(self.fp.read(2)) - 2
s = ImageFile._safe_read(self.fp, n) s = ImageFile._safe_read(self.fp, n)
self._size = i16(s, 3), i16(s, 1) self._size = i16(s, 3), i16(s, 1)
@ -234,6 +238,7 @@ def DQT(self: JpegImageFile, marker: int) -> None:
# FIXME: The quantization tables can be used to estimate the # FIXME: The quantization tables can be used to estimate the
# compression quality. # compression quality.
assert self.fp is not None
n = i16(self.fp.read(2)) - 2 n = i16(self.fp.read(2)) - 2
s = ImageFile._safe_read(self.fp, n) s = ImageFile._safe_read(self.fp, n)
while len(s): while len(s):
@ -334,6 +339,7 @@ class JpegImageFile(ImageFile.ImageFile):
format_description = "JPEG (ISO 10918)" format_description = "JPEG (ISO 10918)"
def _open(self) -> None: def _open(self) -> None:
assert self.fp is not None
s = self.fp.read(3) s = self.fp.read(3)
if not _accept(s): if not _accept(s):
@ -401,6 +407,7 @@ class JpegImageFile(ImageFile.ImageFile):
For premature EOF and LOAD_TRUNCATED_IMAGES adds EOI marker For premature EOF and LOAD_TRUNCATED_IMAGES adds EOI marker
so libjpeg can finish decoding so libjpeg can finish decoding
""" """
assert self.fp is not None
s = self.fp.read(read_bytes) s = self.fp.read(read_bytes)
if not s and ImageFile.LOAD_TRUNCATED_IMAGES and not hasattr(self, "_ended"): if not s and ImageFile.LOAD_TRUNCATED_IMAGES and not hasattr(self, "_ended"):

View File

@ -67,6 +67,7 @@ class MicImageFile(TiffImagePlugin.TiffImageFile):
self._n_frames = len(self.images) self._n_frames = len(self.images)
self.is_animated = self._n_frames > 1 self.is_animated = self._n_frames > 1
assert self.fp is not None
self.__fp = self.fp self.__fp = self.fp
self.seek(0) self.seek(0)

View File

@ -32,6 +32,7 @@ from . import (
TiffImagePlugin, TiffImagePlugin,
) )
from ._binary import o32le from ._binary import o32le
from ._util import DeferredError
def _save(im: Image.Image, fp: IO[bytes], filename: str | bytes) -> None: def _save(im: Image.Image, fp: IO[bytes], filename: str | bytes) -> None:
@ -98,6 +99,7 @@ class MpoImageFile(JpegImagePlugin.JpegImageFile):
_close_exclusive_fp_after_loading = False _close_exclusive_fp_after_loading = False
def _open(self) -> None: def _open(self) -> None:
assert self.fp is not None
self.fp.seek(0) # prep the fp in order to pass the JPEG test self.fp.seek(0) # prep the fp in order to pass the JPEG test
JpegImagePlugin.JpegImageFile._open(self) JpegImagePlugin.JpegImageFile._open(self)
self._after_jpeg_open() self._after_jpeg_open()
@ -117,6 +119,7 @@ class MpoImageFile(JpegImagePlugin.JpegImageFile):
assert self.n_frames == len(self.__mpoffsets) assert self.n_frames == len(self.__mpoffsets)
del self.info["mpoffset"] # no longer needed del self.info["mpoffset"] # no longer needed
self.is_animated = self.n_frames > 1 self.is_animated = self.n_frames > 1
assert self.fp is not None
self._fp = self.fp # FIXME: hack self._fp = self.fp # FIXME: hack
self._fp.seek(self.__mpoffsets[0]) # get ready to read first frame self._fp.seek(self.__mpoffsets[0]) # get ready to read first frame
self.__frame = 0 self.__frame = 0
@ -125,11 +128,15 @@ class MpoImageFile(JpegImagePlugin.JpegImageFile):
self.readonly = 1 self.readonly = 1
def load_seek(self, pos: int) -> None: def load_seek(self, pos: int) -> None:
if isinstance(self._fp, DeferredError):
raise self._fp.ex
self._fp.seek(pos) self._fp.seek(pos)
def seek(self, frame: int) -> None: def seek(self, frame: int) -> None:
if not self._seek_check(frame): if not self._seek_check(frame):
return return
if isinstance(self._fp, DeferredError):
raise self._fp.ex
self.fp = self._fp self.fp = self._fp
self.offset = self.__mpoffsets[frame] self.offset = self.__mpoffsets[frame]

View File

@ -48,6 +48,7 @@ from ._binary import i32be as i32
from ._binary import o8 from ._binary import o8
from ._binary import o16be as o16 from ._binary import o16be as o16
from ._binary import o32be as o32 from ._binary import o32be as o32
from ._util import DeferredError
if TYPE_CHECKING: if TYPE_CHECKING:
from . import _imaging from . import _imaging
@ -752,6 +753,7 @@ class PngImageFile(ImageFile.ImageFile):
format_description = "Portable network graphics" format_description = "Portable network graphics"
def _open(self) -> None: def _open(self) -> None:
assert self.fp is not None
if not _accept(self.fp.read(8)): if not _accept(self.fp.read(8)):
msg = "not a PNG file" msg = "not a PNG file"
raise SyntaxError(msg) raise SyntaxError(msg)
@ -869,6 +871,8 @@ class PngImageFile(ImageFile.ImageFile):
def _seek(self, frame: int, rewind: bool = False) -> None: def _seek(self, frame: int, rewind: bool = False) -> None:
assert self.png is not None assert self.png is not None
if isinstance(self._fp, DeferredError):
raise self._fp.ex
self.dispose: _imaging.ImagingCore | None self.dispose: _imaging.ImagingCore | None
dispose_extent = None dispose_extent = None
@ -981,6 +985,7 @@ class PngImageFile(ImageFile.ImageFile):
"""internal: read more image data""" """internal: read more image data"""
assert self.png is not None assert self.png is not None
assert self.fp is not None
while self.__idat == 0: while self.__idat == 0:
# end of chunk, skip forward to next one # end of chunk, skip forward to next one
@ -1014,6 +1019,7 @@ class PngImageFile(ImageFile.ImageFile):
def load_end(self) -> None: def load_end(self) -> None:
"""internal: finished reading image data""" """internal: finished reading image data"""
assert self.png is not None assert self.png is not None
assert self.fp is not None
if self.__idat != 0: if self.__idat != 0:
self.fp.read(self.__idat) self.fp.read(self.__idat)
while True: while True:

View File

@ -27,6 +27,7 @@ from ._binary import i16be as i16
from ._binary import i32be as i32 from ._binary import i32be as i32
from ._binary import si16be as si16 from ._binary import si16be as si16
from ._binary import si32be as si32 from ._binary import si32be as si32
from ._util import DeferredError
MODES = { MODES = {
# (photoshop mode, bits) -> (pil mode, required channels) # (photoshop mode, bits) -> (pil mode, required channels)
@ -60,6 +61,7 @@ class PsdImageFile(ImageFile.ImageFile):
_close_exclusive_fp_after_loading = False _close_exclusive_fp_after_loading = False
def _open(self) -> None: def _open(self) -> None:
assert self.fp is not None
read = self.fp.read read = self.fp.read
# #
@ -148,6 +150,8 @@ class PsdImageFile(ImageFile.ImageFile):
) -> list[tuple[str, str, tuple[int, int, int, int], list[ImageFile._Tile]]]: ) -> list[tuple[str, str, tuple[int, int, int, int], list[ImageFile._Tile]]]:
layers = [] layers = []
if self._layers_position is not None: if self._layers_position is not None:
if isinstance(self._fp, DeferredError):
raise self._fp.ex
self._fp.seek(self._layers_position) self._fp.seek(self._layers_position)
_layer_data = io.BytesIO(ImageFile._safe_read(self._fp, self._layers_size)) _layer_data = io.BytesIO(ImageFile._safe_read(self._fp, self._layers_size))
layers = _layerinfo(_layer_data, self._layers_size) layers = _layerinfo(_layer_data, self._layers_size)
@ -174,6 +178,8 @@ class PsdImageFile(ImageFile.ImageFile):
self._mode = mode self._mode = mode
self.tile = tile self.tile = tile
self.frame = layer self.frame = layer
if isinstance(self._fp, DeferredError):
raise self._fp.ex
self.fp = self._fp self.fp = self._fp
except IndexError as e: except IndexError as e:
msg = "no such layer" msg = "no such layer"

View File

@ -22,6 +22,7 @@ class QoiImageFile(ImageFile.ImageFile):
format_description = "Quite OK Image" format_description = "Quite OK Image"
def _open(self) -> None: def _open(self) -> None:
assert self.fp is not None
if not _accept(self.fp.read(4)): if not _accept(self.fp.read(4)):
msg = "not a QOI file" msg = "not a QOI file"
raise SyntaxError(msg) raise SyntaxError(msg)

View File

@ -40,6 +40,7 @@ import sys
from typing import IO, TYPE_CHECKING, Any, cast from typing import IO, TYPE_CHECKING, Any, cast
from . import Image, ImageFile from . import Image, ImageFile
from ._util import DeferredError
def isInt(f: Any) -> int: def isInt(f: Any) -> int:
@ -100,6 +101,7 @@ class SpiderImageFile(ImageFile.ImageFile):
def _open(self) -> None: def _open(self) -> None:
# check header # check header
assert self.fp is not None
n = 27 * 4 # read 27 float values n = 27 * 4 # read 27 float values
f = self.fp.read(n) f = self.fp.read(n)
@ -181,6 +183,9 @@ class SpiderImageFile(ImageFile.ImageFile):
if not self._seek_check(frame): if not self._seek_check(frame):
return return
self.stkoffset = self.hdrlen + frame * (self.hdrlen + self.imgbytes) self.stkoffset = self.hdrlen + frame * (self.hdrlen + self.imgbytes)
if isinstance(self._fp, DeferredError):
raise self._fp.ex
self.fp = self._fp self.fp = self._fp
self.fp.seek(self.stkoffset) self.fp.seek(self.stkoffset)
self._open() self._open()
@ -211,26 +216,27 @@ class SpiderImageFile(ImageFile.ImageFile):
# given a list of filenames, return a list of images # given a list of filenames, return a list of images
def loadImageSeries(filelist: list[str] | None = None) -> list[SpiderImageFile] | None: def loadImageSeries(filelist: list[str] | None = None) -> list[Image.Image] | None:
"""create a list of :py:class:`~PIL.Image.Image` objects for use in a montage""" """create a list of :py:class:`~PIL.Image.Image` objects for use in a montage"""
if filelist is None or len(filelist) < 1: if filelist is None or len(filelist) < 1:
return None return None
imglist = [] byte_imgs = []
for img in filelist: for img in filelist:
if not os.path.exists(img): if not os.path.exists(img):
print(f"unable to find {img}") print(f"unable to find {img}")
continue continue
try: try:
with Image.open(img) as im: with Image.open(img) as im:
im = im.convert2byte() assert isinstance(im, SpiderImageFile)
byte_im = im.convert2byte()
except Exception: except Exception:
if not isSpiderImage(img): if not isSpiderImage(img):
print(f"{img} is not a Spider image file") print(f"{img} is not a Spider image file")
continue continue
im.info["filename"] = img byte_im.info["filename"] = img
imglist.append(im) byte_imgs.append(byte_im)
return imglist return byte_imgs
# -------------------------------------------------------------------- # --------------------------------------------------------------------
@ -321,9 +327,9 @@ if __name__ == "__main__":
outfile = sys.argv[2] outfile = sys.argv[2]
# perform some image operation # perform some image operation
im = im.transpose(Image.Transpose.FLIP_LEFT_RIGHT) transposed_im = im.transpose(Image.Transpose.FLIP_LEFT_RIGHT)
print( print(
f"saving a flipped version of {os.path.basename(filename)} " f"saving a flipped version of {os.path.basename(filename)} "
f"as {outfile} " f"as {outfile} "
) )
im.save(outfile, SpiderImageFile.format) transposed_im.save(outfile, SpiderImageFile.format)

View File

@ -58,7 +58,7 @@ from ._binary import i32be as i32
from ._binary import o8 from ._binary import o8
from ._deprecate import deprecate from ._deprecate import deprecate
from ._typing import StrOrBytesPath from ._typing import StrOrBytesPath
from ._util import is_path from ._util import DeferredError, is_path
from .TiffTags import TYPES from .TiffTags import TYPES
if TYPE_CHECKING: if TYPE_CHECKING:
@ -1154,6 +1154,7 @@ class TiffImageFile(ImageFile.ImageFile):
"""Open the first image in a TIFF file""" """Open the first image in a TIFF file"""
# Header # Header
assert self.fp is not None
ifh = self.fp.read(8) ifh = self.fp.read(8)
if ifh[2] == 43: if ifh[2] == 43:
ifh += self.fp.read(8) ifh += self.fp.read(8)
@ -1198,6 +1199,8 @@ class TiffImageFile(ImageFile.ImageFile):
self.im = Image.core.new(self.mode, self.size) self.im = Image.core.new(self.mode, self.size)
def _seek(self, frame: int) -> None: def _seek(self, frame: int) -> None:
if isinstance(self._fp, DeferredError):
raise self._fp.ex
self.fp = self._fp self.fp = self._fp
# reset buffered io handle in case fp # reset buffered io handle in case fp
@ -1283,6 +1286,7 @@ class TiffImageFile(ImageFile.ImageFile):
# reset buffered io handle in case fp # reset buffered io handle in case fp
# was passed to libtiff, invalidating the buffer # was passed to libtiff, invalidating the buffer
assert self.fp is not None
self.fp.tell() self.fp.tell()
# load IFD data from fp before it is closed # load IFD data from fp before it is closed
@ -1316,6 +1320,7 @@ class TiffImageFile(ImageFile.ImageFile):
# To be nice on memory footprint, if there's a # To be nice on memory footprint, if there's a
# file descriptor, use that instead of reading # file descriptor, use that instead of reading
# into a string in python. # into a string in python.
assert self.fp is not None
try: try:
fp = hasattr(self.fp, "fileno") and self.fp.fileno() fp = hasattr(self.fp, "fileno") and self.fp.fileno()
# flush the file descriptor, prevents error on pypy 2.4+ # flush the file descriptor, prevents error on pypy 2.4+

View File

@ -39,6 +39,7 @@ class WalImageFile(ImageFile.ImageFile):
self._mode = "P" self._mode = "P"
# read header fields # read header fields
assert self.fp is not None
header = self.fp.read(32 + 24 + 32 + 12) header = self.fp.read(32 + 24 + 32 + 12)
self._size = i32(header, 32), i32(header, 36) self._size = i32(header, 32), i32(header, 36)
Image._decompression_bomb_check(self.size) Image._decompression_bomb_check(self.size)
@ -55,6 +56,7 @@ class WalImageFile(ImageFile.ImageFile):
def load(self) -> Image.core.PixelAccess | None: def load(self) -> Image.core.PixelAccess | None:
if self._im is None: if self._im is None:
assert self.fp is not None
self.im = Image.core.new(self.mode, self.size) self.im = Image.core.new(self.mode, self.size)
self.frombytes(self.fp.read(self.size[0] * self.size[1])) self.frombytes(self.fp.read(self.size[0] * self.size[1]))
self.putpalette(quake2palette) self.putpalette(quake2palette)

View File

@ -47,6 +47,7 @@ class WebPImageFile(ImageFile.ImageFile):
def _open(self) -> None: def _open(self) -> None:
# Use the newer AnimDecoder API to parse the (possibly) animated file, # Use the newer AnimDecoder API to parse the (possibly) animated file,
# and access muxed chunks like ICC/EXIF/XMP. # and access muxed chunks like ICC/EXIF/XMP.
assert self.fp is not None
self._decoder = _webp.WebPAnimDecoder(self.fp.read()) self._decoder = _webp.WebPAnimDecoder(self.fp.read())
# Get info from decoder # Get info from decoder

View File

@ -49,6 +49,7 @@ if hasattr(Image.core, "drawwmf"):
self.bbox = im.info["wmf_bbox"] self.bbox = im.info["wmf_bbox"]
def load(self, im: ImageFile.StubImageFile) -> Image.Image: def load(self, im: ImageFile.StubImageFile) -> Image.Image:
assert im.fp is not None
im.fp.seek(0) # rewind im.fp.seek(0) # rewind
return Image.frombytes( return Image.frombytes(
"RGB", "RGB",
@ -85,6 +86,7 @@ class WmfStubImageFile(ImageFile.StubImageFile):
self._inch = None self._inch = None
# check placable header # check placable header
assert self.fp is not None
s = self.fp.read(80) s = self.fp.read(80)
if s[:6] == b"\xd7\xcd\xc6\x9a\x00\x00": if s[:6] == b"\xd7\xcd\xc6\x9a\x00\x00":

View File

@ -58,15 +58,15 @@ class XVThumbImageFile(ImageFile.ImageFile):
# skip info comments # skip info comments
while True: while True:
s = self.fp.readline() line = self.fp.readline()
if not s: if not line:
msg = "Unexpected EOF reading XV thumbnail file" msg = "Unexpected EOF reading XV thumbnail file"
raise SyntaxError(msg) raise SyntaxError(msg)
if s[0] != 35: # ie. when not a comment: '#' if line[0] != 35: # ie. when not a comment: '#'
break break
# parse header line (already read) # parse header line (already read)
s = s.strip().split() s = line.strip().split()
self._mode = "P" self._mode = "P"
self._size = int(s[0]), int(s[1]) self._size = int(s[0]), int(s[1])

View File

@ -37,17 +37,18 @@ class XpmImageFile(ImageFile.ImageFile):
format_description = "X11 Pixel Map" format_description = "X11 Pixel Map"
def _open(self) -> None: def _open(self) -> None:
assert self.fp is not None
if not _accept(self.fp.read(9)): if not _accept(self.fp.read(9)):
msg = "not an XPM file" msg = "not an XPM file"
raise SyntaxError(msg) raise SyntaxError(msg)
# skip forward to next string # skip forward to next string
while True: while True:
s = self.fp.readline() line = self.fp.readline()
if not s: if not line:
msg = "broken XPM file" msg = "broken XPM file"
raise SyntaxError(msg) raise SyntaxError(msg)
m = xpm_head.match(s) m = xpm_head.match(line)
if m: if m:
break break
@ -66,14 +67,14 @@ class XpmImageFile(ImageFile.ImageFile):
palette = [b"\0\0\0"] * 256 palette = [b"\0\0\0"] * 256
for _ in range(pal): for _ in range(pal):
s = self.fp.readline() line = self.fp.readline()
if s[-2:] == b"\r\n": if line[-2:] == b"\r\n":
s = s[:-2] line = line[:-2]
elif s[-1:] in b"\r\n": elif line[-1:] in b"\r\n":
s = s[:-1] line = line[:-1]
c = s[1] c = line[1]
s = s[2:-2].split() s = line[2:-2].split()
for i in range(0, len(s), 2): for i in range(0, len(s), 2):
if s[i] == b"c": if s[i] == b"c":
@ -83,9 +84,11 @@ class XpmImageFile(ImageFile.ImageFile):
self.info["transparency"] = c self.info["transparency"] = c
elif rgb[:1] == b"#": elif rgb[:1] == b"#":
# FIXME: handle colour names (see ImagePalette.py) # FIXME: handle colour names (see ImagePalette.py)
rgb = int(rgb[1:], 16) rgb_int = int(rgb[1:], 16)
palette[c] = ( palette[c] = (
o8((rgb >> 16) & 255) + o8((rgb >> 8) & 255) + o8(rgb & 255) o8((rgb_int >> 16) & 255)
+ o8((rgb_int >> 8) & 255)
+ o8(rgb_int & 255)
) )
else: else:
# unknown colour # unknown colour
@ -111,6 +114,7 @@ class XpmImageFile(ImageFile.ImageFile):
xsize, ysize = self.size xsize, ysize = self.size
assert self.fp is not None
s = [self.fp.readline()[1 : xsize + 1].ljust(xsize) for i in range(ysize)] s = [self.fp.readline()[1 : xsize + 1].ljust(xsize) for i in range(ysize)]
return b"".join(s) return b"".join(s)