1
from __future__ import annotations
8
from pathlib import Path
9
from types import ModuleType
10
from typing import Any, cast
14
from PIL import Image, ImageFile, PngImagePlugin, features
20
assert_image_equal_tofile,
23
mark_if_feature_version,
27
ElementTree: ModuleType | None
29
from defusedxml import ElementTree
35
TEST_PNG_FILE = "Tests/images/hopper.png"
37
# stuff to create inline PNG images
39
MAGIC = PngImagePlugin._MAGIC
42
def chunk(cid: bytes, *data: bytes) -> bytes:
44
PngImagePlugin.putchunk(test_file, cid, *data)
45
return test_file.getvalue()
48
o32 = PngImagePlugin.o32
50
IHDR = chunk(b"IHDR", o32(1), o32(1), b"\x08\x02", b"\0\0\0")
58
def load(data: bytes) -> Image.Image:
59
return Image.open(BytesIO(data))
62
def roundtrip(im: Image.Image, **options: Any) -> PngImagePlugin.PngImageFile:
64
im.save(out, "PNG", **options)
66
return cast(PngImagePlugin.PngImageFile, Image.open(out))
69
@skip_unless_feature("zlib")
71
def get_chunks(self, filename: str) -> list[bytes]:
73
with open(filename, "rb") as fp:
75
with PngImagePlugin.PngStream(fp) as png:
77
cid, pos, length = png.read()
80
s = png.call(cid, pos, length)
86
def test_sanity(self, tmp_path: Path) -> None:
87
# internal version number
88
version = features.version_codec("zlib")
89
assert version is not None
90
assert re.search(r"\d+(\.\d+){1,3}(\.zlib\-ng)?$", version)
92
test_file = str(tmp_path / "temp.png")
94
hopper("RGB").save(test_file)
96
with Image.open(test_file) as im:
98
assert im.mode == "RGB"
99
assert im.size == (128, 128)
100
assert im.format == "PNG"
101
assert im.get_format_mimetype() == "image/png"
103
for mode in ["1", "L", "P", "RGB", "I", "I;16", "I;16B"]:
106
with Image.open(test_file) as reloaded:
107
if mode in ("I", "I;16B"):
108
reloaded = reloaded.convert(mode)
109
assert_image_equal(reloaded, im)
111
def test_invalid_file(self) -> None:
112
invalid_file = "Tests/images/flower.jpg"
114
with pytest.raises(SyntaxError):
115
PngImagePlugin.PngImageFile(invalid_file)
117
def test_broken(self) -> None:
118
# Check reading of totally broken files. In this case, the test
119
# file was checked into Subversion as a text file.
121
test_file = "Tests/images/broken.png"
122
with pytest.raises(OSError):
123
with Image.open(test_file):
126
def test_bad_text(self) -> None:
127
# Make sure PIL can read malformed tEXt chunks (@PIL152)
129
im = load(HEAD + chunk(b"tEXt") + TAIL)
132
im = load(HEAD + chunk(b"tEXt", b"spam") + TAIL)
133
assert im.info == {"spam": ""}
135
im = load(HEAD + chunk(b"tEXt", b"spam\0") + TAIL)
136
assert im.info == {"spam": ""}
138
im = load(HEAD + chunk(b"tEXt", b"spam\0egg") + TAIL)
139
assert im.info == {"spam": "egg"}
141
im = load(HEAD + chunk(b"tEXt", b"spam\0egg\0") + TAIL)
142
assert im.info == {"spam": "egg\x00"}
144
def test_bad_ztxt(self) -> None:
145
# Test reading malformed zTXt chunks (python-pillow/Pillow#318)
147
im = load(HEAD + chunk(b"zTXt") + TAIL)
150
im = load(HEAD + chunk(b"zTXt", b"spam") + TAIL)
151
assert im.info == {"spam": ""}
153
im = load(HEAD + chunk(b"zTXt", b"spam\0") + TAIL)
154
assert im.info == {"spam": ""}
156
im = load(HEAD + chunk(b"zTXt", b"spam\0\0") + TAIL)
157
assert im.info == {"spam": ""}
159
im = load(HEAD + chunk(b"zTXt", b"spam\0\0" + zlib.compress(b"egg")[:1]) + TAIL)
160
assert im.info == {"spam": ""}
162
im = load(HEAD + chunk(b"zTXt", b"spam\0\0" + zlib.compress(b"egg")) + TAIL)
163
assert im.info == {"spam": "egg"}
165
def test_bad_itxt(self) -> None:
166
im = load(HEAD + chunk(b"iTXt") + TAIL)
169
im = load(HEAD + chunk(b"iTXt", b"spam") + TAIL)
172
im = load(HEAD + chunk(b"iTXt", b"spam\0") + TAIL)
175
im = load(HEAD + chunk(b"iTXt", b"spam\0\x02") + TAIL)
178
im = load(HEAD + chunk(b"iTXt", b"spam\0\0\0foo\0") + TAIL)
181
im = load(HEAD + chunk(b"iTXt", b"spam\0\0\0en\0Spam\0egg") + TAIL)
182
assert im.info == {"spam": "egg"}
183
assert im.info["spam"].lang == "en"
184
assert im.info["spam"].tkey == "Spam"
188
+ chunk(b"iTXt", b"spam\0\1\0en\0Spam\0" + zlib.compress(b"egg")[:1])
191
assert im.info == {"spam": ""}
195
+ chunk(b"iTXt", b"spam\0\1\1en\0Spam\0" + zlib.compress(b"egg"))
202
+ chunk(b"iTXt", b"spam\0\1\0en\0Spam\0" + zlib.compress(b"egg"))
205
assert im.info == {"spam": "egg"}
206
assert im.info["spam"].lang == "en"
207
assert im.info["spam"].tkey == "Spam"
209
def test_interlace(self) -> None:
210
test_file = "Tests/images/pil123p.png"
211
with Image.open(test_file) as im:
212
assert_image(im, "P", (162, 150))
213
assert im.info.get("interlace")
217
test_file = "Tests/images/pil123rgba.png"
218
with Image.open(test_file) as im:
219
assert_image(im, "RGBA", (162, 150))
220
assert im.info.get("interlace")
224
def test_load_transparent_p(self) -> None:
225
test_file = "Tests/images/pil123p.png"
226
with Image.open(test_file) as im:
227
assert_image(im, "P", (162, 150))
228
im = im.convert("RGBA")
229
assert_image(im, "RGBA", (162, 150))
231
# image has 124 unique alpha values
232
assert len(im.getchannel("A").getcolors()) == 124
234
def test_load_transparent_rgb(self) -> None:
235
test_file = "Tests/images/rgb_trns.png"
236
with Image.open(test_file) as im:
237
assert im.info["transparency"] == (0, 255, 52)
239
assert_image(im, "RGB", (64, 64))
240
im = im.convert("RGBA")
241
assert_image(im, "RGBA", (64, 64))
243
# image has 876 transparent pixels
244
assert im.getchannel("A").getcolors()[0][0] == 876
246
def test_save_p_transparent_palette(self, tmp_path: Path) -> None:
247
in_file = "Tests/images/pil123p.png"
248
with Image.open(in_file) as im:
249
# 'transparency' contains a byte string with the opacity for
251
assert len(im.info["transparency"]) == 256
253
test_file = str(tmp_path / "temp.png")
256
# check if saved image contains same transparency
257
with Image.open(test_file) as im:
258
assert len(im.info["transparency"]) == 256
260
assert_image(im, "P", (162, 150))
261
im = im.convert("RGBA")
262
assert_image(im, "RGBA", (162, 150))
264
# image has 124 unique alpha values
265
assert len(im.getchannel("A").getcolors()) == 124
267
def test_save_p_single_transparency(self, tmp_path: Path) -> None:
268
in_file = "Tests/images/p_trns_single.png"
269
with Image.open(in_file) as im:
270
# pixel value 164 is full transparent
271
assert im.info["transparency"] == 164
272
assert im.getpixel((31, 31)) == 164
274
test_file = str(tmp_path / "temp.png")
277
# check if saved image contains same transparency
278
with Image.open(test_file) as im:
279
assert im.info["transparency"] == 164
280
assert im.getpixel((31, 31)) == 164
281
assert_image(im, "P", (64, 64))
282
im = im.convert("RGBA")
283
assert_image(im, "RGBA", (64, 64))
285
assert im.getpixel((31, 31)) == (0, 255, 52, 0)
287
# image has 876 transparent pixels
288
assert im.getchannel("A").getcolors()[0][0] == 876
290
def test_save_p_transparent_black(self, tmp_path: Path) -> None:
291
# check if solid black image with full transparency
292
# is supported (check for #1838)
293
im = Image.new("RGBA", (10, 10), (0, 0, 0, 0))
294
assert im.getcolors() == [(100, (0, 0, 0, 0))]
297
test_file = str(tmp_path / "temp.png")
300
# check if saved image contains same transparency
301
with Image.open(test_file) as im:
302
assert len(im.info["transparency"]) == 256
303
assert_image(im, "P", (10, 10))
304
im = im.convert("RGBA")
305
assert_image(im, "RGBA", (10, 10))
306
assert im.getcolors() == [(100, (0, 0, 0, 0))]
308
def test_save_grayscale_transparency(self, tmp_path: Path) -> None:
309
for mode, num_transparent in {"1": 1994, "L": 559, "I;16": 559}.items():
310
in_file = "Tests/images/" + mode.split(";")[0].lower() + "_trns.png"
311
with Image.open(in_file) as im:
312
assert im.mode == mode
313
assert im.info["transparency"] == 255
315
im_rgba = im.convert("RGBA")
316
assert im_rgba.getchannel("A").getcolors()[0][0] == num_transparent
318
test_file = str(tmp_path / "temp.png")
321
with Image.open(test_file) as test_im:
322
assert test_im.mode == mode
323
assert test_im.info["transparency"] == 255
324
assert_image_equal(im, test_im)
326
test_im_rgba = test_im.convert("RGBA")
327
assert test_im_rgba.getchannel("A").getcolors()[0][0] == num_transparent
329
def test_save_rgb_single_transparency(self, tmp_path: Path) -> None:
330
in_file = "Tests/images/caption_6_33_22.png"
331
with Image.open(in_file) as im:
332
test_file = str(tmp_path / "temp.png")
335
def test_load_verify(self) -> None:
336
# Check open/load/verify exception (@PIL150)
338
with Image.open(TEST_PNG_FILE) as im:
339
# Assert that there is no unclosed file warning
340
with warnings.catch_warnings():
343
with Image.open(TEST_PNG_FILE) as im:
345
with pytest.raises(RuntimeError):
348
def test_verify_struct_error(self) -> None:
349
# Check open/load/verify exception (#1755)
351
# offsets to test, -10: breaks in i32() in read. (OSError)
352
# -13: breaks in crc, txt chunk.
353
# -14: malformed chunk
355
for offset in (-10, -13, -14):
356
with open(TEST_PNG_FILE, "rb") as f:
357
test_file = f.read()[:offset]
359
with Image.open(BytesIO(test_file)) as im:
360
assert im.fp is not None
361
with pytest.raises((OSError, SyntaxError)):
364
def test_verify_ignores_crc_error(self) -> None:
365
# check ignores crc errors in ancillary chunks
367
chunk_data = chunk(b"tEXt", b"spam")
368
broken_crc_chunk_data = chunk_data[:-1] + b"q" # break CRC
370
image_data = HEAD + broken_crc_chunk_data + TAIL
371
with pytest.raises(SyntaxError):
372
PngImagePlugin.PngImageFile(BytesIO(image_data))
374
ImageFile.LOAD_TRUNCATED_IMAGES = True
376
im = load(image_data)
377
assert im is not None
379
ImageFile.LOAD_TRUNCATED_IMAGES = False
381
def test_verify_not_ignores_crc_error_in_required_chunk(self) -> None:
382
# check does not ignore crc errors in required chunks
384
image_data = MAGIC + IHDR[:-1] + b"q" + TAIL
386
ImageFile.LOAD_TRUNCATED_IMAGES = True
388
with pytest.raises(SyntaxError):
389
PngImagePlugin.PngImageFile(BytesIO(image_data))
391
ImageFile.LOAD_TRUNCATED_IMAGES = False
393
def test_roundtrip_dpi(self) -> None:
394
# Check dpi roundtripping
396
with Image.open(TEST_PNG_FILE) as im:
397
im = roundtrip(im, dpi=(100.33, 100.33))
398
assert im.info["dpi"] == (100.33, 100.33)
400
def test_load_float_dpi(self) -> None:
401
with Image.open(TEST_PNG_FILE) as im:
402
assert im.info["dpi"] == (95.9866, 95.9866)
404
def test_roundtrip_text(self) -> None:
405
# Check text roundtripping
407
with Image.open(TEST_PNG_FILE) as im:
408
info = PngImagePlugin.PngInfo()
409
info.add_text("TXT", "VALUE")
410
info.add_text("ZIP", "VALUE", zip=True)
412
im = roundtrip(im, pnginfo=info)
413
assert im.info == {"TXT": "VALUE", "ZIP": "VALUE"}
414
assert im.text == {"TXT": "VALUE", "ZIP": "VALUE"}
416
def test_roundtrip_itxt(self) -> None:
417
# Check iTXt roundtripping
419
im = Image.new("RGB", (32, 32))
420
info = PngImagePlugin.PngInfo()
421
info.add_itxt("spam", "Eggs", "en", "Spam")
422
info.add_text("eggs", PngImagePlugin.iTXt("Spam", "en", "Eggs"), zip=True)
424
im = roundtrip(im, pnginfo=info)
425
assert im.info == {"spam": "Eggs", "eggs": "Spam"}
426
assert im.text == {"spam": "Eggs", "eggs": "Spam"}
427
assert isinstance(im.text["spam"], PngImagePlugin.iTXt)
428
assert im.text["spam"].lang == "en"
429
assert im.text["spam"].tkey == "Spam"
430
assert isinstance(im.text["eggs"], PngImagePlugin.iTXt)
431
assert im.text["eggs"].lang == "en"
432
assert im.text["eggs"].tkey == "Eggs"
434
def test_nonunicode_text(self) -> None:
435
# Check so that non-Unicode text is saved as a tEXt rather than iTXt
437
im = Image.new("RGB", (32, 32))
438
info = PngImagePlugin.PngInfo()
439
info.add_text("Text", "Ascii")
440
im = roundtrip(im, pnginfo=info)
441
assert isinstance(im.info["Text"], str)
443
def test_unicode_text(self) -> None:
444
# Check preservation of non-ASCII characters
446
def rt_text(value: str) -> None:
447
im = Image.new("RGB", (32, 32))
448
info = PngImagePlugin.PngInfo()
449
info.add_text("Text", value)
450
im = roundtrip(im, pnginfo=info)
451
assert im.info == {"Text": value}
453
rt_text(" Aa" + chr(0xA0) + chr(0xC4) + chr(0xFF)) # Latin1
454
rt_text(chr(0x400) + chr(0x472) + chr(0x4FF)) # Cyrillic
456
rt_text(chr(0x4E00) + chr(0x66F0) + chr(0x9FBA) + chr(0x3042) + chr(0xAC00))
457
rt_text("A" + chr(0xC4) + chr(0x472) + chr(0x3042)) # Combined
459
def test_scary(self) -> None:
460
# Check reading of evil PNG file. For information, see:
461
# http://scary.beasts.org/security/CESA-2004-001.txt
462
# The first byte is removed from pngtest_bad.png
463
# to avoid classification as malware.
465
with open("Tests/images/pngtest_bad.png.bin", "rb") as fd:
466
data = b"\x89" + fd.read()
468
pngfile = BytesIO(data)
469
with pytest.raises(OSError):
470
with Image.open(pngfile):
473
def test_trns_rgb(self) -> None:
474
# Check writing and reading of tRNS chunks for RGB images.
475
# Independent file sample provided by Sebastian Spaeth.
477
test_file = "Tests/images/caption_6_33_22.png"
478
with Image.open(test_file) as im:
479
assert im.info["transparency"] == (248, 248, 248)
481
# check saving transparency by default
483
assert im.info["transparency"] == (248, 248, 248)
485
im = roundtrip(im, transparency=(0, 1, 2))
486
assert im.info["transparency"] == (0, 1, 2)
488
def test_trns_p(self, tmp_path: Path) -> None:
489
# Check writing a transparency of 0, issue #528
491
im.info["transparency"] = 0
493
f = str(tmp_path / "temp.png")
496
with Image.open(f) as im2:
497
assert "transparency" in im2.info
499
assert_image_equal(im2.convert("RGBA"), im.convert("RGBA"))
501
def test_trns_null(self) -> None:
502
# Check reading images with null tRNS value, issue #1239
503
test_file = "Tests/images/tRNS_null_1x1.png"
504
with Image.open(test_file) as im:
505
assert im.info["transparency"] == 0
507
def test_save_icc_profile(self) -> None:
508
with Image.open("Tests/images/icc_profile_none.png") as im:
509
assert im.info["icc_profile"] is None
511
with Image.open("Tests/images/icc_profile.png") as with_icc:
512
expected_icc = with_icc.info["icc_profile"]
514
im = roundtrip(im, icc_profile=expected_icc)
515
assert im.info["icc_profile"] == expected_icc
517
def test_discard_icc_profile(self) -> None:
518
with Image.open("Tests/images/icc_profile.png") as im:
519
assert "icc_profile" in im.info
521
im = roundtrip(im, icc_profile=None)
522
assert "icc_profile" not in im.info
524
def test_roundtrip_icc_profile(self) -> None:
525
with Image.open("Tests/images/icc_profile.png") as im:
526
expected_icc = im.info["icc_profile"]
529
assert im.info["icc_profile"] == expected_icc
531
def test_roundtrip_no_icc_profile(self) -> None:
532
with Image.open("Tests/images/icc_profile_none.png") as im:
533
assert im.info["icc_profile"] is None
536
assert "icc_profile" not in im.info
538
def test_repr_png(self) -> None:
543
with Image.open(BytesIO(b)) as repr_png:
544
assert repr_png.format == "PNG"
545
assert_image_equal(im, repr_png)
547
def test_repr_png_error_returns_none(self) -> None:
550
assert im._repr_png_() is None
552
def test_chunk_order(self, tmp_path: Path) -> None:
553
with Image.open("Tests/images/icc_profile.png") as im:
554
test_file = str(tmp_path / "temp.png")
555
im.convert("P").save(test_file, dpi=(100, 100))
557
chunks = self.get_chunks(test_file)
559
# https://www.w3.org/TR/PNG/#5ChunkOrdering
560
# IHDR - shall be first
561
assert chunks.index(b"IHDR") == 0
562
# PLTE - before first IDAT
563
assert chunks.index(b"PLTE") < chunks.index(b"IDAT")
564
# iCCP - before PLTE and IDAT
565
assert chunks.index(b"iCCP") < chunks.index(b"PLTE")
566
assert chunks.index(b"iCCP") < chunks.index(b"IDAT")
567
# tRNS - after PLTE, before IDAT
568
assert chunks.index(b"tRNS") > chunks.index(b"PLTE")
569
assert chunks.index(b"tRNS") < chunks.index(b"IDAT")
571
assert chunks.index(b"pHYs") < chunks.index(b"IDAT")
573
def test_getchunks(self) -> None:
576
chunks = PngImagePlugin.getchunks(im)
577
assert len(chunks) == 3
579
def test_read_private_chunks(self) -> None:
580
with Image.open("Tests/images/exif.png") as im:
581
assert im.private_chunks == [(b"orNT", b"\x01")]
583
def test_roundtrip_private_chunk(self) -> None:
584
# Check private chunk roundtripping
586
with Image.open(TEST_PNG_FILE) as im:
587
info = PngImagePlugin.PngInfo()
588
info.add(b"prIV", b"VALUE")
589
info.add(b"atEC", b"VALUE2")
590
info.add(b"prIV", b"VALUE3", True)
592
im = roundtrip(im, pnginfo=info)
593
assert im.private_chunks == [(b"prIV", b"VALUE"), (b"atEC", b"VALUE2")]
595
assert im.private_chunks == [
597
(b"atEC", b"VALUE2"),
598
(b"prIV", b"VALUE3", True),
601
def test_textual_chunks_after_idat(self) -> None:
602
with Image.open("Tests/images/hopper.png") as im:
603
assert "comment" in im.text
605
"date:create": "2014-09-04T09:37:08+03:00",
606
"date:modify": "2014-09-04T09:37:08+03:00",
608
assert im.text[k] == v
610
# Raises a SyntaxError in load_end
611
with Image.open("Tests/images/broken_data_stream.png") as im:
612
with pytest.raises(OSError):
613
assert isinstance(im.text, dict)
615
# Raises a UnicodeDecodeError in load_end
616
with Image.open("Tests/images/truncated_image.png") as im:
617
# The file is truncated
618
with pytest.raises(OSError):
620
ImageFile.LOAD_TRUNCATED_IMAGES = True
621
assert isinstance(im.text, dict)
622
ImageFile.LOAD_TRUNCATED_IMAGES = False
624
# Raises an EOFError in load_end
625
with Image.open("Tests/images/hopper_idat_after_image_end.png") as im:
626
assert im.text == {"TXT": "VALUE", "ZIP": "VALUE"}
628
def test_unknown_compression_method(self) -> None:
629
with pytest.raises(SyntaxError, match="Unknown compression method"):
630
PngImagePlugin.PngImageFile("Tests/images/unknown_compression_method.png")
632
def test_padded_idat(self) -> None:
633
# This image has been manually hexedited
634
# so that the IDAT chunk has padding at the end
635
# Set MAXBLOCK to the length of the actual data
636
# so that the decoder finishes reading before the chunk ends
637
MAXBLOCK = ImageFile.MAXBLOCK
638
ImageFile.MAXBLOCK = 45
639
ImageFile.LOAD_TRUNCATED_IMAGES = True
641
with Image.open("Tests/images/padded_idat.png") as im:
644
ImageFile.MAXBLOCK = MAXBLOCK
645
ImageFile.LOAD_TRUNCATED_IMAGES = False
647
assert_image_equal_tofile(im, "Tests/images/bw_gradient.png")
649
@pytest.mark.parametrize(
650
"cid", (b"IHDR", b"sRGB", b"pHYs", b"acTL", b"fcTL", b"fdAT")
652
def test_truncated_chunks(self, cid: bytes) -> None:
654
with PngImagePlugin.PngStream(fp) as png:
655
with pytest.raises(ValueError):
658
ImageFile.LOAD_TRUNCATED_IMAGES = True
660
ImageFile.LOAD_TRUNCATED_IMAGES = False
662
@pytest.mark.parametrize("save_all", (True, False))
663
def test_specify_bits(self, save_all: bool, tmp_path: Path) -> None:
666
out = str(tmp_path / "temp.png")
667
im.save(out, bits=4, save_all=save_all)
669
with Image.open(out) as reloaded:
670
assert len(reloaded.png.im_palette[1]) == 48
672
def test_plte_length(self, tmp_path: Path) -> None:
673
im = Image.new("P", (1, 1))
674
im.putpalette((1, 1, 1))
676
out = str(tmp_path / "temp.png")
677
im.save(str(tmp_path / "temp.png"))
679
with Image.open(out) as reloaded:
680
assert len(reloaded.png.im_palette[1]) == 3
682
def test_getxmp(self) -> None:
683
with Image.open("Tests/images/color_snakes.png") as im:
684
if ElementTree is None:
687
match="XMP data cannot be read without defusedxml dependency",
689
assert im.getxmp() == {}
691
assert "xmp" in im.info
694
description = xmp["xmpmeta"]["RDF"]["Description"]
695
assert description["PixelXDimension"] == "10"
696
assert description["subject"]["Seq"] is None
698
def test_exif(self) -> None:
700
with Image.open("Tests/images/exif.png") as im:
702
assert exif[274] == 1
704
# With an ImageMagick zTXt chunk
705
with Image.open("Tests/images/exif_imagemagick.png") as im:
707
assert exif[274] == 1
709
# Assert that info still can be extracted
710
# when the image is no longer a PngImageFile instance
711
exif = im.copy().getexif()
712
assert exif[274] == 1
715
with Image.open("Tests/images/exif_text.png") as im:
717
assert exif[274] == 1
720
with Image.open("Tests/images/xmp_tags_orientation.png") as im:
722
assert exif[274] == 3
724
def test_exif_save(self, tmp_path: Path) -> None:
725
# Test exif is not saved from info
726
test_file = str(tmp_path / "temp.png")
727
with Image.open("Tests/images/exif.png") as im:
730
with Image.open(test_file) as reloaded:
731
assert reloaded._getexif() is None
733
# Test passing in exif
734
with Image.open("Tests/images/exif.png") as im:
735
im.save(test_file, exif=im.getexif())
737
with Image.open(test_file) as reloaded:
738
exif = reloaded._getexif()
739
assert exif[274] == 1
741
@mark_if_feature_version(
742
pytest.mark.valgrind_known_error, "libjpeg_turbo", "2.0", reason="Known Failing"
744
def test_exif_from_jpg(self, tmp_path: Path) -> None:
745
with Image.open("Tests/images/pil_sample_rgb.jpg") as im:
746
test_file = str(tmp_path / "temp.png")
747
im.save(test_file, exif=im.getexif())
749
with Image.open(test_file) as reloaded:
750
exif = reloaded._getexif()
751
assert exif[305] == "Adobe Photoshop CS Macintosh"
753
def test_exif_argument(self, tmp_path: Path) -> None:
754
with Image.open(TEST_PNG_FILE) as im:
755
test_file = str(tmp_path / "temp.png")
756
im.save(test_file, exif=b"exifstring")
758
with Image.open(test_file) as reloaded:
759
assert reloaded.info["exif"] == b"Exif\x00\x00exifstring"
761
def test_tell(self) -> None:
762
with Image.open(TEST_PNG_FILE) as im:
763
assert im.tell() == 0
765
def test_seek(self) -> None:
766
with Image.open(TEST_PNG_FILE) as im:
769
with pytest.raises(EOFError):
772
@pytest.mark.parametrize("buffer", (True, False))
773
def test_save_stdout(self, buffer: bool) -> None:
774
old_stdout = sys.stdout
779
mystdout: MyStdOut | BytesIO = MyStdOut() if buffer else BytesIO()
781
sys.stdout = mystdout
783
with Image.open(TEST_PNG_FILE) as im:
784
im.save(sys.stdout, "PNG")
787
sys.stdout = old_stdout
789
if isinstance(mystdout, MyStdOut):
790
mystdout = mystdout.buffer
791
with Image.open(mystdout) as reloaded:
792
assert_image_equal_tofile(reloaded, TEST_PNG_FILE)
794
def test_truncated_end_chunk(self) -> None:
795
with Image.open("Tests/images/truncated_end_chunk.png") as im:
796
with pytest.raises(OSError):
799
ImageFile.LOAD_TRUNCATED_IMAGES = True
801
with Image.open("Tests/images/truncated_end_chunk.png") as im:
802
assert_image_equal_tofile(im, "Tests/images/hopper.png")
804
ImageFile.LOAD_TRUNCATED_IMAGES = False
807
@pytest.mark.skipif(is_win32(), reason="Requires Unix or macOS")
808
@skip_unless_feature("zlib")
809
class TestTruncatedPngPLeaks(PillowLeakTestCase):
810
mem_limit = 2 * 1024 # max increase in K
811
iterations = 100 # Leak is 56k/iteration, this will leak 5.6megs
813
def test_leak_load(self) -> None:
814
with open("Tests/images/hopper.png", "rb") as f:
815
DATA = BytesIO(f.read(16 * 1024))
817
ImageFile.LOAD_TRUNCATED_IMAGES = True
818
with Image.open(DATA) as im:
822
with Image.open(DATA) as im:
826
self._test_leak(core)
828
ImageFile.LOAD_TRUNCATED_IMAGES = False