1
from __future__ import annotations
9
from pathlib import Path
10
from typing import Any, NamedTuple
14
from PIL import Image, ImageFilter, ImageOps, TiffImagePlugin, TiffTags, features
15
from PIL.TiffImagePlugin import OSUBFILETYPE, SAMPLEFORMAT, STRIPOFFSETS, SUBIFD
19
assert_image_equal_tofile,
21
assert_image_similar_tofile,
23
mark_if_feature_version,
28
@skip_unless_feature("libtiff")
30
def _assert_noerr(self, tmp_path: Path, im: TiffImagePlugin.TiffImageFile) -> None:
31
"""Helper tests that assert basic sanity about the g4 tiff reading"""
35
# Does the data actually load
40
assert im._compression == "group4"
41
except AttributeError:
42
print("No _compression")
45
# can we write it back out, in a different form.
46
out = str(tmp_path / "temp.png")
49
out_bytes = io.BytesIO()
50
im.save(out_bytes, format="tiff", compression="group4")
53
class TestFileLibTiff(LibTiffTestCase):
54
def test_version(self) -> None:
55
version = features.version_codec("libtiff")
56
assert version is not None
57
assert re.search(r"\d+\.\d+\.\d+t?$", version)
59
def test_g4_tiff(self, tmp_path: Path) -> None:
60
"""Test the ordinary file path load path"""
62
test_file = "Tests/images/hopper_g4_500.tif"
63
with Image.open(test_file) as im:
64
assert im.size == (500, 500)
65
self._assert_noerr(tmp_path, im)
67
def test_g4_large(self, tmp_path: Path) -> None:
68
test_file = "Tests/images/pport_g4.tif"
69
with Image.open(test_file) as im:
70
self._assert_noerr(tmp_path, im)
72
def test_g4_tiff_file(self, tmp_path: Path) -> None:
73
"""Testing the string load path"""
75
test_file = "Tests/images/hopper_g4_500.tif"
76
with open(test_file, "rb") as f:
77
with Image.open(f) as im:
78
assert im.size == (500, 500)
79
self._assert_noerr(tmp_path, im)
81
def test_g4_tiff_bytesio(self, tmp_path: Path) -> None:
82
"""Testing the stringio loading code path"""
83
test_file = "Tests/images/hopper_g4_500.tif"
85
with open(test_file, "rb") as f:
88
with Image.open(s) as im:
89
assert im.size == (500, 500)
90
self._assert_noerr(tmp_path, im)
92
def test_g4_non_disk_file_object(self, tmp_path: Path) -> None:
93
"""Testing loading from non-disk non-BytesIO file object"""
94
test_file = "Tests/images/hopper_g4_500.tif"
95
with open(test_file, "rb") as f:
98
class NonBytesIO(io.RawIOBase):
99
def read(self, size: int = -1) -> bytes:
107
def readable(self) -> bool:
110
r = io.BufferedReader(NonBytesIO())
111
with Image.open(r) as im:
112
assert im.size == (500, 500)
113
self._assert_noerr(tmp_path, im)
115
def test_g4_eq_png(self) -> None:
116
"""Checking that we're actually getting the data that we expect"""
117
with Image.open("Tests/images/hopper_bw_500.png") as png:
118
assert_image_equal_tofile(png, "Tests/images/hopper_g4_500.tif")
120
# see https://github.com/python-pillow/Pillow/issues/279
121
def test_g4_fillorder_eq_png(self) -> None:
122
"""Checking that we're actually getting the data that we expect"""
123
with Image.open("Tests/images/g4-fillorder-test.tif") as g4:
124
assert_image_equal_tofile(g4, "Tests/images/g4-fillorder-test.png")
126
def test_g4_write(self, tmp_path: Path) -> None:
127
"""Checking to see that the saved image is the same as what we wrote"""
128
test_file = "Tests/images/hopper_g4_500.tif"
129
with Image.open(test_file) as orig:
130
out = str(tmp_path / "temp.tif")
131
rot = orig.transpose(Image.Transpose.ROTATE_90)
132
assert rot.size == (500, 500)
135
with Image.open(out) as reread:
136
assert reread.size == (500, 500)
137
self._assert_noerr(tmp_path, reread)
138
assert_image_equal(reread, rot)
139
assert reread.info["compression"] == "group4"
141
assert reread.info["compression"] == orig.info["compression"]
143
assert orig.tobytes() != reread.tobytes()
145
def test_adobe_deflate_tiff(self) -> None:
146
test_file = "Tests/images/tiff_adobe_deflate.tif"
147
with Image.open(test_file) as im:
148
assert im.mode == "RGB"
149
assert im.size == (278, 374)
150
assert im.tile[0][:3] == ("libtiff", (0, 0, 278, 374), 0)
153
assert_image_equal_tofile(im, "Tests/images/tiff_adobe_deflate.png")
155
@pytest.mark.parametrize("legacy_api", (False, True))
156
def test_write_metadata(self, legacy_api: bool, tmp_path: Path) -> None:
157
"""Test metadata writing through libtiff"""
158
f = str(tmp_path / "temp.tiff")
159
with Image.open("Tests/images/hopper_g4.tif") as img:
160
img.save(f, tiffinfo=img.tag)
163
original = img.tag.named()
165
original = img.tag_v2.named()
167
# PhotometricInterpretation is set from SAVE_INFO,
168
# not the original image.
173
"PhotometricInterpretation",
176
with Image.open(f) as loaded:
178
reloaded = loaded.tag.named()
180
reloaded = loaded.tag_v2.named()
182
for tag, value in itertools.chain(reloaded.items(), original.items()):
183
if tag not in ignored:
185
if tag.endswith("Resolution"):
187
assert val[0][0] / val[0][1] == (
188
4294967295 / 113653537
189
), f"{tag} didn't roundtrip"
191
assert val == 37.79000115940079, f"{tag} didn't roundtrip"
193
assert val == value, f"{tag} didn't roundtrip"
195
# https://github.com/python-pillow/Pillow/issues/1561
196
requested_fields = ["StripByteCounts", "RowsPerStrip", "StripOffsets"]
197
for field in requested_fields:
198
assert field in reloaded, f"{field} not in metadata"
200
@pytest.mark.valgrind_known_error(reason="Known invalid metadata")
201
def test_additional_metadata(
202
self, monkeypatch: pytest.MonkeyPatch, tmp_path: Path
204
# these should not crash. Seriously dummy data, most of it doesn't make
205
# any sense, so we're running up against limits where we're asking
206
# libtiff to do stupid things.
208
# Get the list of the ones that we should be able to write
212
for tag, info in ((s, TiffTags.lookup(s)) for s in TiffTags.LIBTIFF_CORE)
213
if info.type is not None
216
# Exclude ones that have special meaning
217
# that we're already testing them
218
with Image.open("Tests/images/hopper_g4.tif") as im:
219
for tag in im.tag_v2:
224
del core_items[320] # colormap is special, tested below
237
5: TiffImagePlugin.IFDRational(100, 1),
241
new_ifd = TiffImagePlugin.ImageFileDirectory_v2()
242
for tag, info in core_items.items():
243
assert info.type is not None
245
new_ifd[tag] = values[info.type]
246
elif not info.length:
247
new_ifd[tag] = tuple(values[info.type] for _ in range(3))
249
new_ifd[tag] = tuple(values[info.type] for _ in range(info.length))
251
# Extra samples really doesn't make sense in this application.
254
out = str(tmp_path / "temp.tif")
255
monkeypatch.setattr(TiffImagePlugin, "WRITE_LIBTIFF", True)
257
im.save(out, tiffinfo=new_ifd)
259
@pytest.mark.parametrize(
264
marks=pytest.mark.skipif(
265
not getattr(Image.core, "libtiff_support_custom_tags", False),
266
reason="Custom tags not supported by older libtiff",
272
def test_custom_metadata(
273
self, monkeypatch: pytest.MonkeyPatch, tmp_path: Path, libtiff: bool
275
monkeypatch.setattr(TiffImagePlugin, "WRITE_LIBTIFF", libtiff)
277
class Tc(NamedTuple):
280
supported_by_default: bool
284
for k, v in enumerate(
286
Tc(4, TiffTags.SHORT, True),
287
Tc(123456789, TiffTags.LONG, True),
288
Tc(-4, TiffTags.SIGNED_BYTE, False),
289
Tc(-4, TiffTags.SIGNED_SHORT, False),
290
Tc(-123456789, TiffTags.SIGNED_LONG, False),
291
Tc(TiffImagePlugin.IFDRational(4, 7), TiffTags.RATIONAL, True),
292
Tc(4.25, TiffTags.FLOAT, True),
293
Tc(4.25, TiffTags.DOUBLE, True),
294
Tc("custom tag value", TiffTags.ASCII, True),
295
Tc(b"custom tag value", TiffTags.BYTE, True),
296
Tc((4, 5, 6), TiffTags.SHORT, True),
297
Tc((123456789, 9, 34, 234, 219387, 92432323), TiffTags.LONG, True),
298
Tc((-4, 9, 10), TiffTags.SIGNED_BYTE, False),
299
Tc((-4, 5, 6), TiffTags.SIGNED_SHORT, False),
301
(-123456789, 9, 34, 234, 219387, -92432323),
302
TiffTags.SIGNED_LONG,
305
Tc((4.25, 5.25), TiffTags.FLOAT, True),
306
Tc((4.25, 5.25), TiffTags.DOUBLE, True),
307
# array of TIFF_BYTE requires bytes instead of tuple for backwards
309
Tc(bytes([4]), TiffTags.BYTE, True),
310
Tc(bytes((4, 9, 10)), TiffTags.BYTE, True),
316
tiffinfo: TiffImagePlugin.ImageFileDirectory_v2 | dict[int, str]
320
out = str(tmp_path / "temp.tif")
321
im.save(out, tiffinfo=tiffinfo)
323
with Image.open(out) as reloaded:
324
for tag, value in tiffinfo.items():
325
reloaded_value = reloaded.tag_v2[tag]
327
isinstance(reloaded_value, TiffImagePlugin.IFDRational)
330
# libtiff does not support real RATIONALS
331
assert round(abs(float(reloaded_value) - float(value)), 7) == 0
334
assert reloaded_value == value
337
ifd = TiffImagePlugin.ImageFileDirectory_v2()
338
for tag, tagdata in custom.items():
339
ifd[tag] = tagdata.value
340
ifd.tagtype[tag] = tagdata.type
343
# Test without types. This only works for some types, int for example are
344
# always encoded as LONG and not SIGNED_LONG.
348
for tag, tagdata in custom.items()
349
if tagdata.supported_by_default
353
def test_osubfiletype(self, tmp_path: Path) -> None:
354
outfile = str(tmp_path / "temp.tif")
355
with Image.open("Tests/images/g4_orientation_6.tif") as im:
356
im.tag_v2[OSUBFILETYPE] = 1
359
def test_subifd(self, tmp_path: Path) -> None:
360
outfile = str(tmp_path / "temp.tif")
361
with Image.open("Tests/images/g4_orientation_6.tif") as im:
362
im.tag_v2[SUBIFD] = 10000
364
# Should not segfault
367
def test_xmlpacket_tag(
368
self, monkeypatch: pytest.MonkeyPatch, tmp_path: Path
370
monkeypatch.setattr(TiffImagePlugin, "WRITE_LIBTIFF", True)
372
out = str(tmp_path / "temp.tif")
373
hopper().save(out, tiffinfo={700: b"xmlpacket tag"})
375
with Image.open(out) as reloaded:
376
if 700 in reloaded.tag_v2:
377
assert reloaded.tag_v2[700] == b"xmlpacket tag"
379
def test_int_dpi(self, monkeypatch: pytest.MonkeyPatch, tmp_path: Path) -> None:
382
out = str(tmp_path / "temp.tif")
383
monkeypatch.setattr(TiffImagePlugin, "WRITE_LIBTIFF", True)
384
im.save(out, dpi=(72, 72))
385
with Image.open(out) as reloaded:
386
assert reloaded.info["dpi"] == (72.0, 72.0)
388
def test_g3_compression(self, tmp_path: Path) -> None:
389
with Image.open("Tests/images/hopper_g4_500.tif") as i:
390
out = str(tmp_path / "temp.tif")
391
i.save(out, compression="group3")
393
with Image.open(out) as reread:
394
assert reread.info["compression"] == "group3"
395
assert_image_equal(reread, i)
397
def test_little_endian(self, tmp_path: Path) -> None:
398
with Image.open("Tests/images/16bit.deflate.tif") as im:
399
assert im.getpixel((0, 0)) == 480
400
assert im.mode == "I;16"
403
# Bytes are in image native order (little endian)
404
assert b[0] == ord(b"\xe0")
405
assert b[1] == ord(b"\x01")
407
out = str(tmp_path / "temp.tif")
408
# out = "temp.le.tif"
410
with Image.open(out) as reread:
411
assert reread.info["compression"] == im.info["compression"]
412
assert reread.getpixel((0, 0)) == 480
413
# UNDONE - libtiff defaults to writing in native endian, so
414
# on big endian, we'll get back mode = 'I;16B' here.
416
def test_big_endian(self, tmp_path: Path) -> None:
417
with Image.open("Tests/images/16bit.MM.deflate.tif") as im:
418
assert im.getpixel((0, 0)) == 480
419
assert im.mode == "I;16B"
423
# Bytes are in image native order (big endian)
424
assert b[0] == ord(b"\x01")
425
assert b[1] == ord(b"\xe0")
427
out = str(tmp_path / "temp.tif")
429
with Image.open(out) as reread:
430
assert reread.info["compression"] == im.info["compression"]
431
assert reread.getpixel((0, 0)) == 480
433
def test_g4_string_info(self, tmp_path: Path) -> None:
434
"""Tests String data in info directory"""
435
test_file = "Tests/images/hopper_g4_500.tif"
436
with Image.open(test_file) as orig:
437
out = str(tmp_path / "temp.tif")
439
orig.tag[269] = "temp.tif"
442
with Image.open(out) as reread:
443
assert "temp.tif" == reread.tag_v2[269]
444
assert "temp.tif" == reread.tag[269][0]
446
def test_12bit_rawmode(self, monkeypatch: pytest.MonkeyPatch) -> None:
447
"""Are we generating the same interpretation
448
of the image as Imagemagick is?"""
449
monkeypatch.setattr(TiffImagePlugin, "READ_LIBTIFF", True)
450
with Image.open("Tests/images/12bit.cropped.tif") as im:
452
monkeypatch.setattr(TiffImagePlugin, "READ_LIBTIFF", False)
453
# to make the target --
454
# convert 12bit.cropped.tif -depth 16 tmp.tif
455
# convert tmp.tif -evaluate RightShift 4 12in16bit2.tif
456
# imagemagick will auto scale so that a 12bit FFF is 16bit FFF0,
457
# so we need to unshift so that the integer values are the same.
459
assert_image_equal_tofile(im, "Tests/images/12in16bit.tif")
461
def test_blur(self, tmp_path: Path) -> None:
462
# test case from irc, how to do blur on b/w image
463
# and save to compressed tif.
464
out = str(tmp_path / "temp.tif")
465
with Image.open("Tests/images/pport_g4.tif") as im:
468
im = im.filter(ImageFilter.GaussianBlur(4))
469
im.save(out, compression="tiff_adobe_deflate")
471
assert_image_equal_tofile(im, out)
473
def test_compressions(self, tmp_path: Path) -> None:
474
# Test various tiff compressions and assert similar image content but reduced
477
out = str(tmp_path / "temp.tif")
479
size_raw = os.path.getsize(out)
481
for compression in ("packbits", "tiff_lzw"):
482
im.save(out, compression=compression)
483
size_compressed = os.path.getsize(out)
484
assert_image_equal_tofile(im, out)
486
im.save(out, compression="jpeg")
487
size_jpeg = os.path.getsize(out)
488
with Image.open(out) as im2:
489
assert_image_similar(im, im2, 30)
491
im.save(out, compression="jpeg", quality=30)
492
size_jpeg_30 = os.path.getsize(out)
493
assert_image_similar_tofile(im2, out, 30)
495
assert size_raw > size_compressed
496
assert size_compressed > size_jpeg
497
assert size_jpeg > size_jpeg_30
499
def test_tiff_jpeg_compression(self, tmp_path: Path) -> None:
501
out = str(tmp_path / "temp.tif")
502
im.save(out, compression="tiff_jpeg")
504
with Image.open(out) as reloaded:
505
assert reloaded.info["compression"] == "jpeg"
507
def test_tiff_deflate_compression(self, tmp_path: Path) -> None:
509
out = str(tmp_path / "temp.tif")
510
im.save(out, compression="tiff_deflate")
512
with Image.open(out) as reloaded:
513
assert reloaded.info["compression"] == "tiff_adobe_deflate"
515
def test_quality(self, tmp_path: Path) -> None:
517
out = str(tmp_path / "temp.tif")
519
with pytest.raises(ValueError):
520
im.save(out, compression="tiff_lzw", quality=50)
521
with pytest.raises(ValueError):
522
im.save(out, compression="jpeg", quality=-1)
523
with pytest.raises(ValueError):
524
im.save(out, compression="jpeg", quality=101)
525
with pytest.raises(ValueError):
526
im.save(out, compression="jpeg", quality="good")
527
im.save(out, compression="jpeg", quality=0)
528
im.save(out, compression="jpeg", quality=100)
530
def test_cmyk_save(self, tmp_path: Path) -> None:
532
out = str(tmp_path / "temp.tif")
534
im.save(out, compression="tiff_adobe_deflate")
535
assert_image_equal_tofile(im, out)
537
@pytest.mark.parametrize("im", (hopper("P"), Image.new("P", (1, 1), "#000")))
538
def test_palette_save(
539
self, im: Image.Image, monkeypatch: pytest.MonkeyPatch, tmp_path: Path
541
out = str(tmp_path / "temp.tif")
543
monkeypatch.setattr(TiffImagePlugin, "WRITE_LIBTIFF", True)
546
with Image.open(out) as reloaded:
547
# colormap/palette tag
548
assert len(reloaded.tag_v2[320]) == 768
550
@pytest.mark.parametrize("compression", ("tiff_ccitt", "group3", "group4"))
551
def test_bw_compression_w_rgb(self, compression: str, tmp_path: Path) -> None:
553
out = str(tmp_path / "temp.tif")
555
with pytest.raises(OSError):
556
im.save(out, compression=compression)
558
def test_fp_leak(self) -> None:
559
im: Image.Image | None = Image.open("Tests/images/hopper_g4_500.tif")
560
assert im is not None
564
im.load() # this should close it.
565
with pytest.raises(OSError):
567
im = None # this should force even more closed.
568
with pytest.raises(OSError):
570
with pytest.raises(OSError):
573
def test_multipage(self, monkeypatch: pytest.MonkeyPatch) -> None:
575
monkeypatch.setattr(TiffImagePlugin, "READ_LIBTIFF", True)
576
with Image.open("Tests/images/multipage.tiff") as im:
577
# file is a multipage tiff, 10x10 green, 10x10 red, 20x20 blue
580
assert im.size == (10, 10)
581
assert im.convert("RGB").getpixel((0, 0)) == (0, 128, 0)
585
assert im.size == (10, 10)
586
assert im.convert("RGB").getpixel((0, 0)) == (255, 0, 0)
590
assert not im.tag.next
591
assert im.size == (20, 20)
592
assert im.convert("RGB").getpixel((0, 0)) == (0, 0, 255)
594
def test_multipage_nframes(self, monkeypatch: pytest.MonkeyPatch) -> None:
596
monkeypatch.setattr(TiffImagePlugin, "READ_LIBTIFF", True)
597
with Image.open("Tests/images/multipage.tiff") as im:
600
for _ in range(frames):
602
# Should not raise ValueError: I/O operation on closed file
605
def test_multipage_seek_backwards(self, monkeypatch: pytest.MonkeyPatch) -> None:
606
monkeypatch.setattr(TiffImagePlugin, "READ_LIBTIFF", True)
607
with Image.open("Tests/images/multipage.tiff") as im:
612
assert im.convert("RGB").getpixel((0, 0)) == (0, 128, 0)
614
def test__next(self, monkeypatch: pytest.MonkeyPatch) -> None:
615
monkeypatch.setattr(TiffImagePlugin, "READ_LIBTIFF", True)
616
with Image.open("Tests/images/hopper.tif") as im:
617
assert not im.tag.next
619
assert not im.tag.next
621
def test_4bit(self, monkeypatch: pytest.MonkeyPatch) -> None:
623
test_file = "Tests/images/hopper_gray_4bpp.tif"
624
original = hopper("L")
627
monkeypatch.setattr(TiffImagePlugin, "READ_LIBTIFF", True)
628
with Image.open(test_file) as im:
631
assert im.size == (128, 128)
632
assert im.mode == "L"
633
assert_image_similar(im, original, 7.3)
635
def test_gray_semibyte_per_pixel(self) -> None:
640
"Tests/images/tiff_gray_2_4_bpp/hopper2.tif",
641
"Tests/images/tiff_gray_2_4_bpp/hopper2I.tif",
642
"Tests/images/tiff_gray_2_4_bpp/hopper2R.tif",
643
"Tests/images/tiff_gray_2_4_bpp/hopper2IR.tif",
649
"Tests/images/tiff_gray_2_4_bpp/hopper4.tif",
650
"Tests/images/tiff_gray_2_4_bpp/hopper4I.tif",
651
"Tests/images/tiff_gray_2_4_bpp/hopper4R.tif",
652
"Tests/images/tiff_gray_2_4_bpp/hopper4IR.tif",
656
original = hopper("L")
657
for epsilon, group in test_files:
658
with Image.open(group[0]) as im:
659
assert im.size == (128, 128)
660
assert im.mode == "L"
661
assert_image_similar(im, original, epsilon)
662
for file in group[1:]:
663
with Image.open(file) as im2:
664
assert im2.size == (128, 128)
665
assert im2.mode == "L"
666
assert_image_equal(im, im2)
668
def test_save_bytesio(self, monkeypatch: pytest.MonkeyPatch) -> None:
670
# Test TIFF saving to io.BytesIO() object.
672
monkeypatch.setattr(TiffImagePlugin, "WRITE_LIBTIFF", True)
673
monkeypatch.setattr(TiffImagePlugin, "READ_LIBTIFF", True)
675
# Generate test image
678
def save_bytesio(compression: str | None = None) -> None:
679
buffer_io = io.BytesIO()
680
pilim.save(buffer_io, format="tiff", compression=compression)
683
with Image.open(buffer_io) as saved_im:
684
assert_image_similar(pilim, saved_im, 0)
688
save_bytesio("packbits")
689
save_bytesio("tiff_lzw")
691
def test_save_ycbcr(self, tmp_path: Path) -> None:
693
outfile = str(tmp_path / "temp.tif")
694
im.save(outfile, compression="jpeg")
696
with Image.open(outfile) as reloaded:
697
assert reloaded.tag_v2[530] == (1, 1)
698
assert reloaded.tag_v2[532] == (0, 255, 128, 255, 128, 255)
700
def test_exif_ifd(self) -> None:
702
with Image.open("Tests/images/tiff_adobe_deflate.tif") as im:
703
assert im.tag_v2[34665] == 125456
706
with Image.open(out) as reloaded:
707
assert 34665 not in reloaded.tag_v2
709
im.save(out, "TIFF", tiffinfo={34665: 125456})
711
with Image.open(out) as reloaded:
712
if Image.core.libtiff_support_custom_tags:
713
assert reloaded.tag_v2[34665] == 125456
715
def test_crashing_metadata(
716
self, monkeypatch: pytest.MonkeyPatch, tmp_path: Path
719
with Image.open("Tests/images/rdf.tif") as im:
720
out = str(tmp_path / "temp.tif")
722
monkeypatch.setattr(TiffImagePlugin, "WRITE_LIBTIFF", True)
723
# this shouldn't crash
724
im.save(out, format="TIFF")
726
def test_page_number_x_0(self, tmp_path: Path) -> None:
728
# Test TIFF with tag 297 (Page Number) having value of 0 0.
729
# The first number is the current page number.
730
# The second is the total number of pages, zero means not available.
731
outfile = str(tmp_path / "temp.tif")
732
# Created by printing a page in Chrome to PDF, then:
733
# /usr/bin/gs -q -sDEVICE=tiffg3 -sOutputFile=total-pages-zero.tif
734
# -dNOPAUSE /tmp/test.pdf -c quit
735
infile = "Tests/images/total-pages-zero.tif"
736
with Image.open(infile) as im:
737
# Should not divide by zero
740
def test_fd_duplication(self, tmp_path: Path) -> None:
741
# https://github.com/python-pillow/Pillow/issues/1651
743
tmpfile = str(tmp_path / "temp.tif")
744
with open(tmpfile, "wb") as f:
745
with open("Tests/images/g4-multi.tiff", "rb") as src:
748
im = Image.open(tmpfile)
749
assert isinstance(im, TiffImagePlugin.TiffImageFile)
752
# Should not raise PermissionError.
755
def test_read_icc(self, monkeypatch: pytest.MonkeyPatch) -> None:
756
with Image.open("Tests/images/hopper.iccprofile.tif") as img:
757
icc = img.info.get("icc_profile")
758
assert icc is not None
759
monkeypatch.setattr(TiffImagePlugin, "READ_LIBTIFF", True)
760
with Image.open("Tests/images/hopper.iccprofile.tif") as img:
761
icc_libtiff = img.info.get("icc_profile")
762
assert icc_libtiff is not None
763
assert icc == icc_libtiff
765
@pytest.mark.parametrize(
770
marks=pytest.mark.skipif(
771
not getattr(Image.core, "libtiff_support_custom_tags", False),
772
reason="Custom tags not supported by older libtiff",
779
self, monkeypatch: pytest.MonkeyPatch, tmp_path: Path, libtiff: bool
781
monkeypatch.setattr(TiffImagePlugin, "WRITE_LIBTIFF", libtiff)
783
with Image.open("Tests/images/hopper.iccprofile.tif") as img:
784
icc_profile = img.info["icc_profile"]
786
out = str(tmp_path / "temp.tif")
787
img.save(out, icc_profile=icc_profile)
788
with Image.open(out) as reloaded:
789
assert icc_profile == reloaded.info["icc_profile"]
791
def test_multipage_compression(self) -> None:
792
with Image.open("Tests/images/compression.tif") as im:
794
assert im._compression == "tiff_ccitt"
795
assert im.size == (10, 10)
798
assert im._compression == "packbits"
799
assert im.size == (10, 10)
803
assert im._compression == "tiff_ccitt"
804
assert im.size == (10, 10)
807
def test_save_tiff_with_jpegtables(self, tmp_path: Path) -> None:
809
outfile = str(tmp_path / "temp.tif")
811
# Created with ImageMagick: convert hopper.jpg hopper_jpg.tif
812
# Contains JPEGTables (347) tag
813
infile = "Tests/images/hopper_jpg.tif"
814
with Image.open(infile) as im:
816
# Should not raise UnicodeDecodeError or anything else
819
def test_16bit_RGB_tiff(self) -> None:
820
with Image.open("Tests/images/tiff_16bit_RGB.tiff") as im:
821
assert im.mode == "RGB"
822
assert im.size == (100, 40)
828
("RGB;16N", "tiff_adobe_deflate", False, 8),
833
assert_image_equal_tofile(im, "Tests/images/tiff_16bit_RGB_target.png")
835
def test_16bit_RGBa_tiff(self) -> None:
836
with Image.open("Tests/images/tiff_16bit_RGBa.tiff") as im:
837
assert im.mode == "RGBA"
838
assert im.size == (100, 40)
840
("libtiff", (0, 0, 100, 40), 0, ("RGBa;16N", "tiff_lzw", False, 38236))
844
assert_image_equal_tofile(im, "Tests/images/tiff_16bit_RGBa_target.png")
846
@skip_unless_feature("jpg")
847
def test_gimp_tiff(self) -> None:
848
# Read TIFF JPEG images from GIMP [@PIL168]
849
filename = "Tests/images/pil168.tif"
850
with Image.open(filename) as im:
851
assert im.mode == "RGB"
852
assert im.size == (256, 256)
854
("libtiff", (0, 0, 256, 256), 0, ("RGB", "jpeg", False, 5122))
858
assert_image_equal_tofile(im, "Tests/images/pil168.png")
860
def test_sampleformat(self) -> None:
861
# https://github.com/python-pillow/Pillow/issues/1466
862
with Image.open("Tests/images/copyleft.tiff") as im:
863
assert im.mode == "RGB"
865
assert_image_equal_tofile(im, "Tests/images/copyleft.png", mode="RGB")
867
def test_sampleformat_write(
868
self, monkeypatch: pytest.MonkeyPatch, tmp_path: Path
870
im = Image.new("F", (1, 1))
871
out = str(tmp_path / "temp.tif")
872
monkeypatch.setattr(TiffImagePlugin, "WRITE_LIBTIFF", True)
875
with Image.open(out) as reloaded:
876
assert reloaded.mode == "F"
877
assert reloaded.getexif()[SAMPLEFORMAT] == 3
879
def test_lzma(self, capfd: pytest.CaptureFixture[str]) -> None:
881
with Image.open("Tests/images/hopper_lzma.tif") as im:
882
assert im.mode == "RGB"
883
assert im.size == (128, 128)
884
assert im.format == "TIFF"
886
assert_image_similar(im, im2, 5)
888
captured = capfd.readouterr()
889
if "LZMA compression support is not configured" in captured.err:
890
pytest.skip("LZMA compression support is not configured")
891
sys.stdout.write(captured.out)
892
sys.stderr.write(captured.err)
895
def test_webp(self, capfd: pytest.CaptureFixture[str]) -> None:
897
with Image.open("Tests/images/hopper_webp.tif") as im:
898
assert im.mode == "RGB"
899
assert im.size == (128, 128)
900
assert im.format == "TIFF"
901
assert_image_similar_tofile(im, "Tests/images/hopper_webp.png", 1)
903
captured = capfd.readouterr()
904
if "WEBP compression support is not configured" in captured.err:
905
pytest.skip("WEBP compression support is not configured")
907
"Compression scheme 50001 strip decoding is not implemented"
911
"Compression scheme 50001 strip decoding is not implemented"
913
sys.stdout.write(captured.out)
914
sys.stderr.write(captured.err)
917
def test_lzw(self) -> None:
918
with Image.open("Tests/images/hopper_lzw.tif") as im:
919
assert im.mode == "RGB"
920
assert im.size == (128, 128)
921
assert im.format == "TIFF"
923
assert_image_similar(im, im2, 5)
925
def test_strip_cmyk_jpeg(self) -> None:
926
infile = "Tests/images/tiff_strip_cmyk_jpeg.tif"
927
with Image.open(infile) as im:
928
assert_image_similar_tofile(im, "Tests/images/pil_sample_cmyk.jpg", 0.5)
930
def test_strip_cmyk_16l_jpeg(self) -> None:
931
infile = "Tests/images/tiff_strip_cmyk_16l_jpeg.tif"
932
with Image.open(infile) as im:
933
assert_image_similar_tofile(im, "Tests/images/pil_sample_cmyk.jpg", 0.5)
935
@mark_if_feature_version(
936
pytest.mark.valgrind_known_error, "libjpeg_turbo", "2.0", reason="Known Failing"
938
def test_strip_ycbcr_jpeg_2x2_sampling(self) -> None:
939
infile = "Tests/images/tiff_strip_ycbcr_jpeg_2x2_sampling.tif"
940
with Image.open(infile) as im:
941
assert_image_similar_tofile(im, "Tests/images/flower.jpg", 1.2)
943
@mark_if_feature_version(
944
pytest.mark.valgrind_known_error, "libjpeg_turbo", "2.0", reason="Known Failing"
946
def test_strip_ycbcr_jpeg_1x1_sampling(self) -> None:
947
infile = "Tests/images/tiff_strip_ycbcr_jpeg_1x1_sampling.tif"
948
with Image.open(infile) as im:
949
assert_image_similar_tofile(im, "Tests/images/flower2.jpg", 0.01)
951
def test_tiled_cmyk_jpeg(self) -> None:
952
infile = "Tests/images/tiff_tiled_cmyk_jpeg.tif"
953
with Image.open(infile) as im:
954
assert_image_similar_tofile(im, "Tests/images/pil_sample_cmyk.jpg", 0.5)
956
@mark_if_feature_version(
957
pytest.mark.valgrind_known_error, "libjpeg_turbo", "2.0", reason="Known Failing"
959
def test_tiled_ycbcr_jpeg_1x1_sampling(self) -> None:
960
infile = "Tests/images/tiff_tiled_ycbcr_jpeg_1x1_sampling.tif"
961
with Image.open(infile) as im:
962
assert_image_similar_tofile(im, "Tests/images/flower2.jpg", 0.01)
964
@mark_if_feature_version(
965
pytest.mark.valgrind_known_error, "libjpeg_turbo", "2.0", reason="Known Failing"
967
def test_tiled_ycbcr_jpeg_2x2_sampling(self) -> None:
968
infile = "Tests/images/tiff_tiled_ycbcr_jpeg_2x2_sampling.tif"
969
with Image.open(infile) as im:
970
assert_image_similar_tofile(im, "Tests/images/flower.jpg", 1.5)
972
def test_strip_planar_rgb(self) -> None:
973
# gdal_translate -co TILED=no -co INTERLEAVE=BAND -co COMPRESS=LZW \
974
# tiff_strip_raw.tif tiff_strip_planar_lzw.tiff
975
infile = "Tests/images/tiff_strip_planar_lzw.tiff"
976
with Image.open(infile) as im:
977
assert_image_equal_tofile(im, "Tests/images/tiff_adobe_deflate.png")
979
def test_tiled_planar_rgb(self) -> None:
980
# gdal_translate -co TILED=yes -co INTERLEAVE=BAND -co COMPRESS=LZW \
981
# tiff_tiled_raw.tif tiff_tiled_planar_lzw.tiff
982
infile = "Tests/images/tiff_tiled_planar_lzw.tiff"
983
with Image.open(infile) as im:
984
assert_image_equal_tofile(im, "Tests/images/tiff_adobe_deflate.png")
986
def test_tiled_planar_16bit_RGB(self) -> None:
987
# gdal_translate -co TILED=yes -co INTERLEAVE=BAND -co COMPRESS=LZW \
988
# tiff_16bit_RGB.tiff tiff_tiled_planar_16bit_RGB.tiff
989
with Image.open("Tests/images/tiff_tiled_planar_16bit_RGB.tiff") as im:
990
assert_image_equal_tofile(im, "Tests/images/tiff_16bit_RGB_target.png")
992
def test_strip_planar_16bit_RGB(self) -> None:
993
# gdal_translate -co TILED=no -co INTERLEAVE=BAND -co COMPRESS=LZW \
994
# tiff_16bit_RGB.tiff tiff_strip_planar_16bit_RGB.tiff
995
with Image.open("Tests/images/tiff_strip_planar_16bit_RGB.tiff") as im:
996
assert_image_equal_tofile(im, "Tests/images/tiff_16bit_RGB_target.png")
998
def test_tiled_planar_16bit_RGBa(self) -> None:
999
# gdal_translate -co TILED=yes \
1000
# -co INTERLEAVE=BAND -co COMPRESS=LZW -co ALPHA=PREMULTIPLIED \
1001
# tiff_16bit_RGBa.tiff tiff_tiled_planar_16bit_RGBa.tiff
1002
with Image.open("Tests/images/tiff_tiled_planar_16bit_RGBa.tiff") as im:
1003
assert_image_equal_tofile(im, "Tests/images/tiff_16bit_RGBa_target.png")
1005
def test_strip_planar_16bit_RGBa(self) -> None:
1006
# gdal_translate -co TILED=no \
1007
# -co INTERLEAVE=BAND -co COMPRESS=LZW -co ALPHA=PREMULTIPLIED \
1008
# tiff_16bit_RGBa.tiff tiff_strip_planar_16bit_RGBa.tiff
1009
with Image.open("Tests/images/tiff_strip_planar_16bit_RGBa.tiff") as im:
1010
assert_image_equal_tofile(im, "Tests/images/tiff_16bit_RGBa_target.png")
1012
@pytest.mark.parametrize("compression", (None, "jpeg"))
1013
def test_block_tile_tags(self, compression: str | None, tmp_path: Path) -> None:
1015
out = str(tmp_path / "temp.tif")
1018
TiffImagePlugin.TILEWIDTH: 256,
1019
TiffImagePlugin.TILELENGTH: 256,
1020
TiffImagePlugin.TILEOFFSETS: 256,
1021
TiffImagePlugin.TILEBYTECOUNTS: 256,
1023
im.save(out, exif=tags, compression=compression)
1025
with Image.open(out) as reloaded:
1027
assert tag not in reloaded.getexif()
1029
def test_old_style_jpeg(self) -> None:
1030
with Image.open("Tests/images/old-style-jpeg-compression.tif") as im:
1031
assert_image_equal_tofile(im, "Tests/images/old-style-jpeg-compression.png")
1033
def test_open_missing_samplesperpixel(self) -> None:
1035
"Tests/images/old-style-jpeg-compression-no-samplesperpixel.tif"
1037
assert_image_equal_tofile(im, "Tests/images/old-style-jpeg-compression.png")
1039
@pytest.mark.parametrize(
1040
"file_name, mode, size, tile",
1043
"tiff_wrong_bits_per_sample.tiff",
1046
[("raw", (0, 0, 52, 53), 160, ("RGBA", 0, 1))],
1049
"tiff_wrong_bits_per_sample_2.tiff",
1052
[("raw", (0, 0, 16, 16), 8, ("RGB", 0, 1))],
1055
"tiff_wrong_bits_per_sample_3.tiff",
1058
[("libtiff", (0, 0, 512, 256), 0, ("RGBA", "tiff_lzw", False, 48782))],
1062
def test_wrong_bits_per_sample(
1066
size: tuple[int, int],
1067
tile: list[tuple[str, tuple[int, int, int, int], int, tuple[Any, ...]]],
1069
with Image.open("Tests/images/" + file_name) as im:
1070
assert im.mode == mode
1071
assert im.size == size
1072
assert im.tile == tile
1075
def test_no_rows_per_strip(self) -> None:
1076
# This image does not have a RowsPerStrip TIFF tag
1077
infile = "Tests/images/no_rows_per_strip.tif"
1078
with Image.open(infile) as im:
1080
assert im.size == (950, 975)
1082
def test_orientation(self) -> None:
1083
with Image.open("Tests/images/g4_orientation_1.tif") as base_im:
1084
for i in range(2, 9):
1085
with Image.open("Tests/images/g4_orientation_" + str(i) + ".tif") as im:
1086
assert 274 in im.tag_v2
1089
assert 274 not in im.tag_v2
1091
assert_image_similar(base_im, im, 0.7)
1093
def test_exif_transpose(self) -> None:
1094
with Image.open("Tests/images/g4_orientation_1.tif") as base_im:
1095
for i in range(2, 9):
1096
with Image.open("Tests/images/g4_orientation_" + str(i) + ".tif") as im:
1097
im = ImageOps.exif_transpose(im)
1099
assert_image_similar(base_im, im, 0.7)
1101
@pytest.mark.valgrind_known_error(reason="Backtrace in Python Core")
1102
def test_sampleformat_not_corrupted(self) -> None:
1103
# Assert that a TIFF image with SampleFormat=UINT tag is not corrupted
1104
# when saving to a new file.
1105
# Pillow 6.0 fails with "OSError: cannot identify image file".
1108
b"SUkqAAgAAAAPAP4ABAABAAAAAAAAAAABBAABAAAAAQAAAAEBBAABAAAAAQAA"
1109
b"AAIBAwADAAAAwgAAAAMBAwABAAAACAAAAAYBAwABAAAAAgAAABEBBAABAAAA"
1110
b"4AAAABUBAwABAAAAAwAAABYBBAABAAAAAQAAABcBBAABAAAACwAAABoBBQAB"
1111
b"AAAAyAAAABsBBQABAAAA0AAAABwBAwABAAAAAQAAACgBAwABAAAAAQAAAFMB"
1112
b"AwADAAAA2AAAAAAAAAAIAAgACAABAAAAAQAAAAEAAAABAAAAAQABAAEAAAB4"
1117
with Image.open(tiff) as im:
1118
im.save(out, format="tiff")
1120
with Image.open(out) as im:
1123
def test_realloc_overflow(self, monkeypatch: pytest.MonkeyPatch) -> None:
1124
monkeypatch.setattr(TiffImagePlugin, "READ_LIBTIFF", True)
1125
with Image.open("Tests/images/tiff_overflow_rows_per_strip.tif") as im:
1126
with pytest.raises(OSError) as e:
1129
# Assert that the error code is IMAGING_CODEC_MEMORY
1130
assert str(e.value) == "-9"
1132
@pytest.mark.parametrize("compression", ("tiff_adobe_deflate", "jpeg"))
1133
def test_save_multistrip(self, compression: str, tmp_path: Path) -> None:
1134
im = hopper("RGB").resize((256, 256))
1135
out = str(tmp_path / "temp.tif")
1136
im.save(out, compression=compression)
1138
with Image.open(out) as im:
1139
# Assert that there are multiple strips
1140
assert isinstance(im, TiffImagePlugin.TiffImageFile)
1141
assert len(im.tag_v2[STRIPOFFSETS]) > 1
1143
@pytest.mark.parametrize("argument", (True, False))
1144
def test_save_single_strip(self, argument: bool, tmp_path: Path) -> None:
1145
im = hopper("RGB").resize((256, 256))
1146
out = str(tmp_path / "temp.tif")
1149
TiffImagePlugin.STRIP_SIZE = 2**18
1151
arguments: dict[str, str | int] = {"compression": "tiff_adobe_deflate"}
1153
arguments["strip_size"] = 2**18
1154
im.save(out, "TIFF", **arguments)
1156
with Image.open(out) as im:
1157
assert isinstance(im, TiffImagePlugin.TiffImageFile)
1158
assert len(im.tag_v2[STRIPOFFSETS]) == 1
1160
TiffImagePlugin.STRIP_SIZE = 65536
1162
@pytest.mark.parametrize("compression", ("tiff_adobe_deflate", None))
1163
def test_save_zero(self, compression: str | None, tmp_path: Path) -> None:
1164
im = Image.new("RGB", (0, 0))
1165
out = str(tmp_path / "temp.tif")
1166
with pytest.raises(SystemError):
1167
im.save(out, compression=compression)
1169
def test_save_many_compressed(self, tmp_path: Path) -> None:
1171
out = str(tmp_path / "temp.tif")
1172
for _ in range(10000):
1173
im.save(out, compression="jpeg")
1175
@pytest.mark.parametrize(
1178
("Tests/images/hopper.tif", ()),
1179
("Tests/images/child_ifd.tiff", (16, 8)),
1180
("Tests/images/child_ifd_jpeg.tiff", (20,)),
1183
def test_get_child_images(self, path: str, sizes: tuple[int, ...]) -> None:
1184
with Image.open(path) as im:
1185
ims = im.get_child_images()
1187
assert len(ims) == len(sizes)
1188
for i, im in enumerate(ims):
1190
expected = Image.new("RGB", (w, w), "#f00")
1191
assert_image_similar(im, expected, 1)