Pillow

Форк
0
/
test_file_libtiff.py 
1191 строка · 44.8 Кб
1
from __future__ import annotations
2

3
import base64
4
import io
5
import itertools
6
import os
7
import re
8
import sys
9
from pathlib import Path
10
from typing import Any, NamedTuple
11

12
import pytest
13

14
from PIL import Image, ImageFilter, ImageOps, TiffImagePlugin, TiffTags, features
15
from PIL.TiffImagePlugin import OSUBFILETYPE, SAMPLEFORMAT, STRIPOFFSETS, SUBIFD
16

17
from .helper import (
18
    assert_image_equal,
19
    assert_image_equal_tofile,
20
    assert_image_similar,
21
    assert_image_similar_tofile,
22
    hopper,
23
    mark_if_feature_version,
24
    skip_unless_feature,
25
)
26

27

28
@skip_unless_feature("libtiff")
29
class LibTiffTestCase:
30
    def _assert_noerr(self, tmp_path: Path, im: TiffImagePlugin.TiffImageFile) -> None:
31
        """Helper tests that assert basic sanity about the g4 tiff reading"""
32
        # 1 bit
33
        assert im.mode == "1"
34

35
        # Does the data actually load
36
        im.load()
37
        im.getdata()
38

39
        try:
40
            assert im._compression == "group4"
41
        except AttributeError:
42
            print("No _compression")
43
            print(dir(im))
44

45
        # can we write it back out, in a different form.
46
        out = str(tmp_path / "temp.png")
47
        im.save(out)
48

49
        out_bytes = io.BytesIO()
50
        im.save(out_bytes, format="tiff", compression="group4")
51

52

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)
58

59
    def test_g4_tiff(self, tmp_path: Path) -> None:
60
        """Test the ordinary file path load path"""
61

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)
66

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)
71

72
    def test_g4_tiff_file(self, tmp_path: Path) -> None:
73
        """Testing the string load path"""
74

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)
80

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"
84
        s = io.BytesIO()
85
        with open(test_file, "rb") as f:
86
            s.write(f.read())
87
            s.seek(0)
88
        with Image.open(s) as im:
89
            assert im.size == (500, 500)
90
            self._assert_noerr(tmp_path, im)
91

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:
96
            data = f.read()
97

98
        class NonBytesIO(io.RawIOBase):
99
            def read(self, size: int = -1) -> bytes:
100
                nonlocal data
101
                if size == -1:
102
                    size = len(data)
103
                result = data[:size]
104
                data = data[size:]
105
                return result
106

107
            def readable(self) -> bool:
108
                return True
109

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)
114

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")
119

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")
125

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)
133
            rot.save(out)
134

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"
140

141
                assert reread.info["compression"] == orig.info["compression"]
142

143
                assert orig.tobytes() != reread.tobytes()
144

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)
151
            im.load()
152

153
            assert_image_equal_tofile(im, "Tests/images/tiff_adobe_deflate.png")
154

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)
161

162
            if legacy_api:
163
                original = img.tag.named()
164
            else:
165
                original = img.tag_v2.named()
166

167
        # PhotometricInterpretation is set from SAVE_INFO,
168
        # not the original image.
169
        ignored = [
170
            "StripByteCounts",
171
            "RowsPerStrip",
172
            "PageNumber",
173
            "PhotometricInterpretation",
174
        ]
175

176
        with Image.open(f) as loaded:
177
            if legacy_api:
178
                reloaded = loaded.tag.named()
179
            else:
180
                reloaded = loaded.tag_v2.named()
181

182
        for tag, value in itertools.chain(reloaded.items(), original.items()):
183
            if tag not in ignored:
184
                val = original[tag]
185
                if tag.endswith("Resolution"):
186
                    if legacy_api:
187
                        assert val[0][0] / val[0][1] == (
188
                            4294967295 / 113653537
189
                        ), f"{tag} didn't roundtrip"
190
                    else:
191
                        assert val == 37.79000115940079, f"{tag} didn't roundtrip"
192
                else:
193
                    assert val == value, f"{tag} didn't roundtrip"
194

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"
199

200
    @pytest.mark.valgrind_known_error(reason="Known invalid metadata")
201
    def test_additional_metadata(
202
        self, monkeypatch: pytest.MonkeyPatch, tmp_path: Path
203
    ) -> None:
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.
207

208
        # Get the list of the ones that we should be able to write
209

210
        core_items = {
211
            tag: info
212
            for tag, info in ((s, TiffTags.lookup(s)) for s in TiffTags.LIBTIFF_CORE)
213
            if info.type is not None
214
        }
215

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:
220
                try:
221
                    del core_items[tag]
222
                except KeyError:
223
                    pass
224
            del core_items[320]  # colormap is special, tested below
225

226
            # Type codes:
227
            #     2: "ascii",
228
            #     3: "short",
229
            #     4: "long",
230
            #     5: "rational",
231
            #     12: "double",
232
            # Type: dummy value
233
            values = {
234
                2: "test",
235
                3: 1,
236
                4: 2**20,
237
                5: TiffImagePlugin.IFDRational(100, 1),
238
                12: 1.05,
239
            }
240

241
            new_ifd = TiffImagePlugin.ImageFileDirectory_v2()
242
            for tag, info in core_items.items():
243
                assert info.type is not None
244
                if info.length == 1:
245
                    new_ifd[tag] = values[info.type]
246
                elif not info.length:
247
                    new_ifd[tag] = tuple(values[info.type] for _ in range(3))
248
                else:
249
                    new_ifd[tag] = tuple(values[info.type] for _ in range(info.length))
250

251
            # Extra samples really doesn't make sense in this application.
252
            del new_ifd[338]
253

254
            out = str(tmp_path / "temp.tif")
255
            monkeypatch.setattr(TiffImagePlugin, "WRITE_LIBTIFF", True)
256

257
            im.save(out, tiffinfo=new_ifd)
258

259
    @pytest.mark.parametrize(
260
        "libtiff",
261
        (
262
            pytest.param(
263
                True,
264
                marks=pytest.mark.skipif(
265
                    not getattr(Image.core, "libtiff_support_custom_tags", False),
266
                    reason="Custom tags not supported by older libtiff",
267
                ),
268
            ),
269
            False,
270
        ),
271
    )
272
    def test_custom_metadata(
273
        self, monkeypatch: pytest.MonkeyPatch, tmp_path: Path, libtiff: bool
274
    ) -> None:
275
        monkeypatch.setattr(TiffImagePlugin, "WRITE_LIBTIFF", libtiff)
276

277
        class Tc(NamedTuple):
278
            value: Any
279
            type: int
280
            supported_by_default: bool
281

282
        custom = {
283
            37000 + k: v
284
            for k, v in enumerate(
285
                [
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),
300
                    Tc(
301
                        (-123456789, 9, 34, 234, 219387, -92432323),
302
                        TiffTags.SIGNED_LONG,
303
                        False,
304
                    ),
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
308
                    # compatibility
309
                    Tc(bytes([4]), TiffTags.BYTE, True),
310
                    Tc(bytes((4, 9, 10)), TiffTags.BYTE, True),
311
                ]
312
            )
313
        }
314

315
        def check_tags(
316
            tiffinfo: TiffImagePlugin.ImageFileDirectory_v2 | dict[int, str]
317
        ) -> None:
318
            im = hopper()
319

320
            out = str(tmp_path / "temp.tif")
321
            im.save(out, tiffinfo=tiffinfo)
322

323
            with Image.open(out) as reloaded:
324
                for tag, value in tiffinfo.items():
325
                    reloaded_value = reloaded.tag_v2[tag]
326
                    if (
327
                        isinstance(reloaded_value, TiffImagePlugin.IFDRational)
328
                        and libtiff
329
                    ):
330
                        # libtiff does not support real RATIONALS
331
                        assert round(abs(float(reloaded_value) - float(value)), 7) == 0
332
                        continue
333

334
                    assert reloaded_value == value
335

336
        # Test with types
337
        ifd = TiffImagePlugin.ImageFileDirectory_v2()
338
        for tag, tagdata in custom.items():
339
            ifd[tag] = tagdata.value
340
            ifd.tagtype[tag] = tagdata.type
341
        check_tags(ifd)
342

343
        # Test without types. This only works for some types, int for example are
344
        # always encoded as LONG and not SIGNED_LONG.
345
        check_tags(
346
            {
347
                tag: tagdata.value
348
                for tag, tagdata in custom.items()
349
                if tagdata.supported_by_default
350
            }
351
        )
352

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
357
            im.save(outfile)
358

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
363

364
            # Should not segfault
365
            im.save(outfile)
366

367
    def test_xmlpacket_tag(
368
        self, monkeypatch: pytest.MonkeyPatch, tmp_path: Path
369
    ) -> None:
370
        monkeypatch.setattr(TiffImagePlugin, "WRITE_LIBTIFF", True)
371

372
        out = str(tmp_path / "temp.tif")
373
        hopper().save(out, tiffinfo={700: b"xmlpacket tag"})
374

375
        with Image.open(out) as reloaded:
376
            if 700 in reloaded.tag_v2:
377
                assert reloaded.tag_v2[700] == b"xmlpacket tag"
378

379
    def test_int_dpi(self, monkeypatch: pytest.MonkeyPatch, tmp_path: Path) -> None:
380
        # issue #1765
381
        im = hopper("RGB")
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)
387

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")
392

393
            with Image.open(out) as reread:
394
                assert reread.info["compression"] == "group3"
395
                assert_image_equal(reread, i)
396

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"
401

402
            b = im.tobytes()
403
            # Bytes are in image native order (little endian)
404
            assert b[0] == ord(b"\xe0")
405
            assert b[1] == ord(b"\x01")
406

407
            out = str(tmp_path / "temp.tif")
408
            # out = "temp.le.tif"
409
            im.save(out)
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.
415

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"
420

421
            b = im.tobytes()
422

423
            # Bytes are in image native order (big endian)
424
            assert b[0] == ord(b"\x01")
425
            assert b[1] == ord(b"\xe0")
426

427
            out = str(tmp_path / "temp.tif")
428
            im.save(out)
429
            with Image.open(out) as reread:
430
                assert reread.info["compression"] == im.info["compression"]
431
                assert reread.getpixel((0, 0)) == 480
432

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")
438

439
            orig.tag[269] = "temp.tif"
440
            orig.save(out)
441

442
        with Image.open(out) as reread:
443
            assert "temp.tif" == reread.tag_v2[269]
444
            assert "temp.tif" == reread.tag[269][0]
445

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:
451
            im.load()
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.
458

459
            assert_image_equal_tofile(im, "Tests/images/12in16bit.tif")
460

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:
466
            im = im.convert("L")
467

468
        im = im.filter(ImageFilter.GaussianBlur(4))
469
        im.save(out, compression="tiff_adobe_deflate")
470

471
        assert_image_equal_tofile(im, out)
472

473
    def test_compressions(self, tmp_path: Path) -> None:
474
        # Test various tiff compressions and assert similar image content but reduced
475
        # file sizes.
476
        im = hopper("RGB")
477
        out = str(tmp_path / "temp.tif")
478
        im.save(out)
479
        size_raw = os.path.getsize(out)
480

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)
485

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)
490

491
        im.save(out, compression="jpeg", quality=30)
492
        size_jpeg_30 = os.path.getsize(out)
493
        assert_image_similar_tofile(im2, out, 30)
494

495
        assert size_raw > size_compressed
496
        assert size_compressed > size_jpeg
497
        assert size_jpeg > size_jpeg_30
498

499
    def test_tiff_jpeg_compression(self, tmp_path: Path) -> None:
500
        im = hopper("RGB")
501
        out = str(tmp_path / "temp.tif")
502
        im.save(out, compression="tiff_jpeg")
503

504
        with Image.open(out) as reloaded:
505
            assert reloaded.info["compression"] == "jpeg"
506

507
    def test_tiff_deflate_compression(self, tmp_path: Path) -> None:
508
        im = hopper("RGB")
509
        out = str(tmp_path / "temp.tif")
510
        im.save(out, compression="tiff_deflate")
511

512
        with Image.open(out) as reloaded:
513
            assert reloaded.info["compression"] == "tiff_adobe_deflate"
514

515
    def test_quality(self, tmp_path: Path) -> None:
516
        im = hopper("RGB")
517
        out = str(tmp_path / "temp.tif")
518

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)
529

530
    def test_cmyk_save(self, tmp_path: Path) -> None:
531
        im = hopper("CMYK")
532
        out = str(tmp_path / "temp.tif")
533

534
        im.save(out, compression="tiff_adobe_deflate")
535
        assert_image_equal_tofile(im, out)
536

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
540
    ) -> None:
541
        out = str(tmp_path / "temp.tif")
542

543
        monkeypatch.setattr(TiffImagePlugin, "WRITE_LIBTIFF", True)
544
        im.save(out)
545

546
        with Image.open(out) as reloaded:
547
            # colormap/palette tag
548
            assert len(reloaded.tag_v2[320]) == 768
549

550
    @pytest.mark.parametrize("compression", ("tiff_ccitt", "group3", "group4"))
551
    def test_bw_compression_w_rgb(self, compression: str, tmp_path: Path) -> None:
552
        im = hopper("RGB")
553
        out = str(tmp_path / "temp.tif")
554

555
        with pytest.raises(OSError):
556
            im.save(out, compression=compression)
557

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
561
        fn = im.fp.fileno()
562

563
        os.fstat(fn)
564
        im.load()  # this should close it.
565
        with pytest.raises(OSError):
566
            os.fstat(fn)
567
        im = None  # this should force even more closed.
568
        with pytest.raises(OSError):
569
            os.fstat(fn)
570
        with pytest.raises(OSError):
571
            os.close(fn)
572

573
    def test_multipage(self, monkeypatch: pytest.MonkeyPatch) -> None:
574
        # issue #862
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
578

579
            im.seek(0)
580
            assert im.size == (10, 10)
581
            assert im.convert("RGB").getpixel((0, 0)) == (0, 128, 0)
582
            assert im.tag.next
583

584
            im.seek(1)
585
            assert im.size == (10, 10)
586
            assert im.convert("RGB").getpixel((0, 0)) == (255, 0, 0)
587
            assert im.tag.next
588

589
            im.seek(2)
590
            assert not im.tag.next
591
            assert im.size == (20, 20)
592
            assert im.convert("RGB").getpixel((0, 0)) == (0, 0, 255)
593

594
    def test_multipage_nframes(self, monkeypatch: pytest.MonkeyPatch) -> None:
595
        # issue #862
596
        monkeypatch.setattr(TiffImagePlugin, "READ_LIBTIFF", True)
597
        with Image.open("Tests/images/multipage.tiff") as im:
598
            frames = im.n_frames
599
            assert frames == 3
600
            for _ in range(frames):
601
                im.seek(0)
602
                # Should not raise ValueError: I/O operation on closed file
603
                im.load()
604

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:
608
            im.seek(1)
609
            im.load()
610

611
            im.seek(0)
612
            assert im.convert("RGB").getpixel((0, 0)) == (0, 128, 0)
613

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
618
            im.load()
619
            assert not im.tag.next
620

621
    def test_4bit(self, monkeypatch: pytest.MonkeyPatch) -> None:
622
        # Arrange
623
        test_file = "Tests/images/hopper_gray_4bpp.tif"
624
        original = hopper("L")
625

626
        # Act
627
        monkeypatch.setattr(TiffImagePlugin, "READ_LIBTIFF", True)
628
        with Image.open(test_file) as im:
629

630
            # Assert
631
            assert im.size == (128, 128)
632
            assert im.mode == "L"
633
            assert_image_similar(im, original, 7.3)
634

635
    def test_gray_semibyte_per_pixel(self) -> None:
636
        test_files = (
637
            (
638
                24.8,  # epsilon
639
                (  # group
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",
644
                ),
645
            ),
646
            (
647
                7.3,  # epsilon
648
                (  # group
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",
653
                ),
654
            ),
655
        )
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)
667

668
    def test_save_bytesio(self, monkeypatch: pytest.MonkeyPatch) -> None:
669
        # PR 1011
670
        # Test TIFF saving to io.BytesIO() object.
671

672
        monkeypatch.setattr(TiffImagePlugin, "WRITE_LIBTIFF", True)
673
        monkeypatch.setattr(TiffImagePlugin, "READ_LIBTIFF", True)
674

675
        # Generate test image
676
        pilim = hopper()
677

678
        def save_bytesio(compression: str | None = None) -> None:
679
            buffer_io = io.BytesIO()
680
            pilim.save(buffer_io, format="tiff", compression=compression)
681
            buffer_io.seek(0)
682

683
            with Image.open(buffer_io) as saved_im:
684
                assert_image_similar(pilim, saved_im, 0)
685

686
        save_bytesio()
687
        save_bytesio("raw")
688
        save_bytesio("packbits")
689
        save_bytesio("tiff_lzw")
690

691
    def test_save_ycbcr(self, tmp_path: Path) -> None:
692
        im = hopper("YCbCr")
693
        outfile = str(tmp_path / "temp.tif")
694
        im.save(outfile, compression="jpeg")
695

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)
699

700
    def test_exif_ifd(self) -> None:
701
        out = io.BytesIO()
702
        with Image.open("Tests/images/tiff_adobe_deflate.tif") as im:
703
            assert im.tag_v2[34665] == 125456
704
            im.save(out, "TIFF")
705

706
            with Image.open(out) as reloaded:
707
                assert 34665 not in reloaded.tag_v2
708

709
            im.save(out, "TIFF", tiffinfo={34665: 125456})
710

711
        with Image.open(out) as reloaded:
712
            if Image.core.libtiff_support_custom_tags:
713
                assert reloaded.tag_v2[34665] == 125456
714

715
    def test_crashing_metadata(
716
        self, monkeypatch: pytest.MonkeyPatch, tmp_path: Path
717
    ) -> None:
718
        # issue 1597
719
        with Image.open("Tests/images/rdf.tif") as im:
720
            out = str(tmp_path / "temp.tif")
721

722
            monkeypatch.setattr(TiffImagePlugin, "WRITE_LIBTIFF", True)
723
            # this shouldn't crash
724
            im.save(out, format="TIFF")
725

726
    def test_page_number_x_0(self, tmp_path: Path) -> None:
727
        # Issue 973
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
738
            im.save(outfile)
739

740
    def test_fd_duplication(self, tmp_path: Path) -> None:
741
        # https://github.com/python-pillow/Pillow/issues/1651
742

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:
746
                f.write(src.read())
747

748
        im = Image.open(tmpfile)
749
        assert isinstance(im, TiffImagePlugin.TiffImageFile)
750
        im.n_frames
751
        im.close()
752
        # Should not raise PermissionError.
753
        os.remove(tmpfile)
754

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
764

765
    @pytest.mark.parametrize(
766
        "libtiff",
767
        (
768
            pytest.param(
769
                True,
770
                marks=pytest.mark.skipif(
771
                    not getattr(Image.core, "libtiff_support_custom_tags", False),
772
                    reason="Custom tags not supported by older libtiff",
773
                ),
774
            ),
775
            False,
776
        ),
777
    )
778
    def test_write_icc(
779
        self, monkeypatch: pytest.MonkeyPatch, tmp_path: Path, libtiff: bool
780
    ) -> None:
781
        monkeypatch.setattr(TiffImagePlugin, "WRITE_LIBTIFF", libtiff)
782

783
        with Image.open("Tests/images/hopper.iccprofile.tif") as img:
784
            icc_profile = img.info["icc_profile"]
785

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"]
790

791
    def test_multipage_compression(self) -> None:
792
        with Image.open("Tests/images/compression.tif") as im:
793
            im.seek(0)
794
            assert im._compression == "tiff_ccitt"
795
            assert im.size == (10, 10)
796

797
            im.seek(1)
798
            assert im._compression == "packbits"
799
            assert im.size == (10, 10)
800
            im.load()
801

802
            im.seek(0)
803
            assert im._compression == "tiff_ccitt"
804
            assert im.size == (10, 10)
805
            im.load()
806

807
    def test_save_tiff_with_jpegtables(self, tmp_path: Path) -> None:
808
        # Arrange
809
        outfile = str(tmp_path / "temp.tif")
810

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:
815
            # Act / Assert
816
            # Should not raise UnicodeDecodeError or anything else
817
            im.save(outfile)
818

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)
823
            assert im.tile, [
824
                (
825
                    "libtiff",
826
                    (0, 0, 100, 40),
827
                    0,
828
                    ("RGB;16N", "tiff_adobe_deflate", False, 8),
829
                )
830
            ]
831
            im.load()
832

833
            assert_image_equal_tofile(im, "Tests/images/tiff_16bit_RGB_target.png")
834

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)
839
            assert im.tile, [
840
                ("libtiff", (0, 0, 100, 40), 0, ("RGBa;16N", "tiff_lzw", False, 38236))
841
            ]
842
            im.load()
843

844
            assert_image_equal_tofile(im, "Tests/images/tiff_16bit_RGBa_target.png")
845

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)
853
            assert im.tile == [
854
                ("libtiff", (0, 0, 256, 256), 0, ("RGB", "jpeg", False, 5122))
855
            ]
856
            im.load()
857

858
            assert_image_equal_tofile(im, "Tests/images/pil168.png")
859

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"
864

865
            assert_image_equal_tofile(im, "Tests/images/copyleft.png", mode="RGB")
866

867
    def test_sampleformat_write(
868
        self, monkeypatch: pytest.MonkeyPatch, tmp_path: Path
869
    ) -> None:
870
        im = Image.new("F", (1, 1))
871
        out = str(tmp_path / "temp.tif")
872
        monkeypatch.setattr(TiffImagePlugin, "WRITE_LIBTIFF", True)
873
        im.save(out)
874

875
        with Image.open(out) as reloaded:
876
            assert reloaded.mode == "F"
877
            assert reloaded.getexif()[SAMPLEFORMAT] == 3
878

879
    def test_lzma(self, capfd: pytest.CaptureFixture[str]) -> None:
880
        try:
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"
885
                im2 = hopper()
886
                assert_image_similar(im, im2, 5)
887
        except OSError:
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)
893
            raise
894

895
    def test_webp(self, capfd: pytest.CaptureFixture[str]) -> None:
896
        try:
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)
902
        except OSError:
903
            captured = capfd.readouterr()
904
            if "WEBP compression support is not configured" in captured.err:
905
                pytest.skip("WEBP compression support is not configured")
906
            if (
907
                "Compression scheme 50001 strip decoding is not implemented"
908
                in captured.err
909
            ):
910
                pytest.skip(
911
                    "Compression scheme 50001 strip decoding is not implemented"
912
                )
913
            sys.stdout.write(captured.out)
914
            sys.stderr.write(captured.err)
915
            raise
916

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"
922
            im2 = hopper()
923
            assert_image_similar(im, im2, 5)
924

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)
929

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)
934

935
    @mark_if_feature_version(
936
        pytest.mark.valgrind_known_error, "libjpeg_turbo", "2.0", reason="Known Failing"
937
    )
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)
942

943
    @mark_if_feature_version(
944
        pytest.mark.valgrind_known_error, "libjpeg_turbo", "2.0", reason="Known Failing"
945
    )
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)
950

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)
955

956
    @mark_if_feature_version(
957
        pytest.mark.valgrind_known_error, "libjpeg_turbo", "2.0", reason="Known Failing"
958
    )
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)
963

964
    @mark_if_feature_version(
965
        pytest.mark.valgrind_known_error, "libjpeg_turbo", "2.0", reason="Known Failing"
966
    )
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)
971

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")
978

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")
985

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")
991

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")
997

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")
1004

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")
1011

1012
    @pytest.mark.parametrize("compression", (None, "jpeg"))
1013
    def test_block_tile_tags(self, compression: str | None, tmp_path: Path) -> None:
1014
        im = hopper()
1015
        out = str(tmp_path / "temp.tif")
1016

1017
        tags = {
1018
            TiffImagePlugin.TILEWIDTH: 256,
1019
            TiffImagePlugin.TILELENGTH: 256,
1020
            TiffImagePlugin.TILEOFFSETS: 256,
1021
            TiffImagePlugin.TILEBYTECOUNTS: 256,
1022
        }
1023
        im.save(out, exif=tags, compression=compression)
1024

1025
        with Image.open(out) as reloaded:
1026
            for tag in tags:
1027
                assert tag not in reloaded.getexif()
1028

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")
1032

1033
    def test_open_missing_samplesperpixel(self) -> None:
1034
        with Image.open(
1035
            "Tests/images/old-style-jpeg-compression-no-samplesperpixel.tif"
1036
        ) as im:
1037
            assert_image_equal_tofile(im, "Tests/images/old-style-jpeg-compression.png")
1038

1039
    @pytest.mark.parametrize(
1040
        "file_name, mode, size, tile",
1041
        [
1042
            (
1043
                "tiff_wrong_bits_per_sample.tiff",
1044
                "RGBA",
1045
                (52, 53),
1046
                [("raw", (0, 0, 52, 53), 160, ("RGBA", 0, 1))],
1047
            ),
1048
            (
1049
                "tiff_wrong_bits_per_sample_2.tiff",
1050
                "RGB",
1051
                (16, 16),
1052
                [("raw", (0, 0, 16, 16), 8, ("RGB", 0, 1))],
1053
            ),
1054
            (
1055
                "tiff_wrong_bits_per_sample_3.tiff",
1056
                "RGBA",
1057
                (512, 256),
1058
                [("libtiff", (0, 0, 512, 256), 0, ("RGBA", "tiff_lzw", False, 48782))],
1059
            ),
1060
        ],
1061
    )
1062
    def test_wrong_bits_per_sample(
1063
        self,
1064
        file_name: str,
1065
        mode: str,
1066
        size: tuple[int, int],
1067
        tile: list[tuple[str, tuple[int, int, int, int], int, tuple[Any, ...]]],
1068
    ) -> None:
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
1073
            im.load()
1074

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:
1079
            im.load()
1080
        assert im.size == (950, 975)
1081

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
1087

1088
                    im.load()
1089
                    assert 274 not in im.tag_v2
1090

1091
                    assert_image_similar(base_im, im, 0.7)
1092

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)
1098

1099
                    assert_image_similar(base_im, im, 0.7)
1100

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".
1106
        tiff = io.BytesIO(
1107
            base64.b64decode(
1108
                b"SUkqAAgAAAAPAP4ABAABAAAAAAAAAAABBAABAAAAAQAAAAEBBAABAAAAAQAA"
1109
                b"AAIBAwADAAAAwgAAAAMBAwABAAAACAAAAAYBAwABAAAAAgAAABEBBAABAAAA"
1110
                b"4AAAABUBAwABAAAAAwAAABYBBAABAAAAAQAAABcBBAABAAAACwAAABoBBQAB"
1111
                b"AAAAyAAAABsBBQABAAAA0AAAABwBAwABAAAAAQAAACgBAwABAAAAAQAAAFMB"
1112
                b"AwADAAAA2AAAAAAAAAAIAAgACAABAAAAAQAAAAEAAAABAAAAAQABAAEAAAB4"
1113
                b"nGNgYAAAAAMAAQ=="
1114
            )
1115
        )
1116
        out = io.BytesIO()
1117
        with Image.open(tiff) as im:
1118
            im.save(out, format="tiff")
1119
        out.seek(0)
1120
        with Image.open(out) as im:
1121
            im.load()
1122

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:
1127
                im.load()
1128

1129
            # Assert that the error code is IMAGING_CODEC_MEMORY
1130
            assert str(e.value) == "-9"
1131

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)
1137

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
1142

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")
1147

1148
        if not argument:
1149
            TiffImagePlugin.STRIP_SIZE = 2**18
1150
        try:
1151
            arguments: dict[str, str | int] = {"compression": "tiff_adobe_deflate"}
1152
            if argument:
1153
                arguments["strip_size"] = 2**18
1154
            im.save(out, "TIFF", **arguments)
1155

1156
            with Image.open(out) as im:
1157
                assert isinstance(im, TiffImagePlugin.TiffImageFile)
1158
                assert len(im.tag_v2[STRIPOFFSETS]) == 1
1159
        finally:
1160
            TiffImagePlugin.STRIP_SIZE = 65536
1161

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)
1168

1169
    def test_save_many_compressed(self, tmp_path: Path) -> None:
1170
        im = hopper()
1171
        out = str(tmp_path / "temp.tif")
1172
        for _ in range(10000):
1173
            im.save(out, compression="jpeg")
1174

1175
    @pytest.mark.parametrize(
1176
        "path, sizes",
1177
        (
1178
            ("Tests/images/hopper.tif", ()),
1179
            ("Tests/images/child_ifd.tiff", (16, 8)),
1180
            ("Tests/images/child_ifd_jpeg.tiff", (20,)),
1181
        ),
1182
    )
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()
1186

1187
        assert len(ims) == len(sizes)
1188
        for i, im in enumerate(ims):
1189
            w = sizes[i]
1190
            expected = Image.new("RGB", (w, w), "#f00")
1191
            assert_image_similar(im, expected, 1)
1192

Использование cookies

Мы используем файлы cookie в соответствии с Политикой конфиденциальности и Политикой использования cookies.

Нажимая кнопку «Принимаю», Вы даете АО «СберТех» согласие на обработку Ваших персональных данных в целях совершенствования нашего веб-сайта и Сервиса GitVerse, а также повышения удобства их использования.

Запретить использование cookies Вы можете самостоятельно в настройках Вашего браузера.