Pillow

Форк
0
/
test_file_tiff.py 
925 строк · 31.1 Кб
1
from __future__ import annotations
2

3
import os
4
import warnings
5
from collections.abc import Generator
6
from io import BytesIO
7
from pathlib import Path
8
from types import ModuleType
9

10
import pytest
11

12
from PIL import Image, ImageFile, TiffImagePlugin, UnidentifiedImageError
13
from PIL.TiffImagePlugin import RESOLUTION_UNIT, X_RESOLUTION, Y_RESOLUTION
14

15
from .helper import (
16
    assert_image_equal,
17
    assert_image_equal_tofile,
18
    assert_image_similar,
19
    assert_image_similar_tofile,
20
    hopper,
21
    is_pypy,
22
    is_win32,
23
)
24

25
ElementTree: ModuleType | None
26
try:
27
    from defusedxml import ElementTree
28
except ImportError:
29
    ElementTree = None
30

31

32
class TestFileTiff:
33
    def test_sanity(self, tmp_path: Path) -> None:
34
        filename = str(tmp_path / "temp.tif")
35

36
        hopper("RGB").save(filename)
37

38
        with Image.open(filename) as im:
39
            im.load()
40
        assert im.mode == "RGB"
41
        assert im.size == (128, 128)
42
        assert im.format == "TIFF"
43

44
        hopper("1").save(filename)
45
        with Image.open(filename):
46
            pass
47

48
        hopper("L").save(filename)
49
        with Image.open(filename):
50
            pass
51

52
        hopper("P").save(filename)
53
        with Image.open(filename):
54
            pass
55

56
        hopper("RGB").save(filename)
57
        with Image.open(filename):
58
            pass
59

60
        hopper("I").save(filename)
61
        with Image.open(filename):
62
            pass
63

64
    @pytest.mark.skipif(is_pypy(), reason="Requires CPython")
65
    def test_unclosed_file(self) -> None:
66
        def open() -> None:
67
            im = Image.open("Tests/images/multipage.tiff")
68
            im.load()
69

70
        with pytest.warns(ResourceWarning):
71
            open()
72

73
    def test_closed_file(self) -> None:
74
        with warnings.catch_warnings():
75
            im = Image.open("Tests/images/multipage.tiff")
76
            im.load()
77
            im.close()
78

79
    def test_seek_after_close(self) -> None:
80
        im = Image.open("Tests/images/multipage.tiff")
81
        assert isinstance(im, TiffImagePlugin.TiffImageFile)
82
        im.close()
83

84
        with pytest.raises(ValueError):
85
            im.n_frames
86
        with pytest.raises(ValueError):
87
            im.seek(1)
88

89
    def test_context_manager(self) -> None:
90
        with warnings.catch_warnings():
91
            with Image.open("Tests/images/multipage.tiff") as im:
92
                im.load()
93

94
    def test_mac_tiff(self) -> None:
95
        # Read RGBa images from macOS [@PIL136]
96

97
        filename = "Tests/images/pil136.tiff"
98
        with Image.open(filename) as im:
99
            assert im.mode == "RGBA"
100
            assert im.size == (55, 43)
101
            assert im.tile == [("raw", (0, 0, 55, 43), 8, ("RGBa", 0, 1))]
102
            im.load()
103

104
            assert_image_similar_tofile(im, "Tests/images/pil136.png", 1)
105

106
    def test_bigtiff(self, tmp_path: Path) -> None:
107
        with Image.open("Tests/images/hopper_bigtiff.tif") as im:
108
            assert_image_equal_tofile(im, "Tests/images/hopper.tif")
109

110
        with Image.open("Tests/images/hopper_bigtiff.tif") as im:
111
            # multistrip support not yet implemented
112
            del im.tag_v2[273]
113

114
            outfile = str(tmp_path / "temp.tif")
115
            im.save(outfile, save_all=True, append_images=[im], tiffinfo=im.tag_v2)
116

117
    def test_seek_too_large(self) -> None:
118
        with pytest.raises(ValueError, match="Unable to seek to frame"):
119
            Image.open("Tests/images/seek_too_large.tif")
120

121
    def test_set_legacy_api(self) -> None:
122
        ifd = TiffImagePlugin.ImageFileDirectory_v2()
123
        with pytest.raises(Exception) as e:
124
            ifd.legacy_api = False
125
        assert str(e.value) == "Not allowing setting of legacy api"
126

127
    def test_xyres_tiff(self) -> None:
128
        filename = "Tests/images/pil168.tif"
129
        with Image.open(filename) as im:
130
            # legacy api
131
            assert isinstance(im.tag[X_RESOLUTION][0], tuple)
132
            assert isinstance(im.tag[Y_RESOLUTION][0], tuple)
133

134
            # v2 api
135
            assert isinstance(im.tag_v2[X_RESOLUTION], TiffImagePlugin.IFDRational)
136
            assert isinstance(im.tag_v2[Y_RESOLUTION], TiffImagePlugin.IFDRational)
137

138
            assert im.info["dpi"] == (72.0, 72.0)
139

140
    def test_xyres_fallback_tiff(self) -> None:
141
        filename = "Tests/images/compression.tif"
142
        with Image.open(filename) as im:
143
            # v2 api
144
            assert isinstance(im.tag_v2[X_RESOLUTION], TiffImagePlugin.IFDRational)
145
            assert isinstance(im.tag_v2[Y_RESOLUTION], TiffImagePlugin.IFDRational)
146
            with pytest.raises(KeyError):
147
                im.tag_v2[RESOLUTION_UNIT]
148

149
            # Legacy.
150
            assert im.info["resolution"] == (100.0, 100.0)
151
            # Fallback "inch".
152
            assert im.info["dpi"] == (100.0, 100.0)
153

154
    def test_int_resolution(self) -> None:
155
        filename = "Tests/images/pil168.tif"
156
        with Image.open(filename) as im:
157
            # Try to read a file where X,Y_RESOLUTION are ints
158
            im.tag_v2[X_RESOLUTION] = 71
159
            im.tag_v2[Y_RESOLUTION] = 71
160
            im._setup()
161
            assert im.info["dpi"] == (71.0, 71.0)
162

163
    @pytest.mark.parametrize(
164
        "resolution_unit, dpi",
165
        [(None, 72.8), (2, 72.8), (3, 184.912)],
166
    )
167
    def test_load_float_dpi(self, resolution_unit: int | None, dpi: float) -> None:
168
        with Image.open(
169
            "Tests/images/hopper_float_dpi_" + str(resolution_unit) + ".tif"
170
        ) as im:
171
            assert im.tag_v2.get(RESOLUTION_UNIT) == resolution_unit
172
            assert im.info["dpi"] == (dpi, dpi)
173

174
    def test_save_float_dpi(self, tmp_path: Path) -> None:
175
        outfile = str(tmp_path / "temp.tif")
176
        with Image.open("Tests/images/hopper.tif") as im:
177
            dpi = (72.2, 72.2)
178
            im.save(outfile, dpi=dpi)
179

180
            with Image.open(outfile) as reloaded:
181
                assert reloaded.info["dpi"] == dpi
182

183
    def test_save_setting_missing_resolution(self) -> None:
184
        b = BytesIO()
185
        with Image.open("Tests/images/10ct_32bit_128.tiff") as im:
186
            im.save(b, format="tiff", resolution=123.45)
187
        with Image.open(b) as im:
188
            assert im.tag_v2[X_RESOLUTION] == 123.45
189
            assert im.tag_v2[Y_RESOLUTION] == 123.45
190

191
    def test_invalid_file(self) -> None:
192
        invalid_file = "Tests/images/flower.jpg"
193

194
        with pytest.raises(SyntaxError):
195
            TiffImagePlugin.TiffImageFile(invalid_file)
196

197
        TiffImagePlugin.PREFIXES.append(b"\xff\xd8\xff\xe0")
198
        with pytest.raises(SyntaxError):
199
            TiffImagePlugin.TiffImageFile(invalid_file)
200
        TiffImagePlugin.PREFIXES.pop()
201

202
    def test_bad_exif(self) -> None:
203
        with Image.open("Tests/images/hopper_bad_exif.jpg") as i:
204
            # Should not raise struct.error.
205
            with pytest.warns(UserWarning):
206
                i._getexif()
207

208
    def test_save_rgba(self, tmp_path: Path) -> None:
209
        im = hopper("RGBA")
210
        outfile = str(tmp_path / "temp.tif")
211
        im.save(outfile)
212

213
    def test_save_unsupported_mode(self, tmp_path: Path) -> None:
214
        im = hopper("HSV")
215
        outfile = str(tmp_path / "temp.tif")
216
        with pytest.raises(OSError):
217
            im.save(outfile)
218

219
    def test_8bit_s(self) -> None:
220
        with Image.open("Tests/images/8bit.s.tif") as im:
221
            im.load()
222
            assert im.mode == "L"
223
            assert im.getpixel((50, 50)) == 184
224

225
    def test_little_endian(self) -> None:
226
        with Image.open("Tests/images/16bit.cropped.tif") as im:
227
            assert im.getpixel((0, 0)) == 480
228
            assert im.mode == "I;16"
229

230
            b = im.tobytes()
231
        # Bytes are in image native order (little endian)
232
        assert b[0] == ord(b"\xe0")
233
        assert b[1] == ord(b"\x01")
234

235
    def test_big_endian(self) -> None:
236
        with Image.open("Tests/images/16bit.MM.cropped.tif") as im:
237
            assert im.getpixel((0, 0)) == 480
238
            assert im.mode == "I;16B"
239

240
            b = im.tobytes()
241
        # Bytes are in image native order (big endian)
242
        assert b[0] == ord(b"\x01")
243
        assert b[1] == ord(b"\xe0")
244

245
    def test_16bit_r(self) -> None:
246
        with Image.open("Tests/images/16bit.r.tif") as im:
247
            assert im.getpixel((0, 0)) == 480
248
            assert im.mode == "I;16"
249

250
            b = im.tobytes()
251
        assert b[0] == ord(b"\xe0")
252
        assert b[1] == ord(b"\x01")
253

254
    def test_16bit_s(self) -> None:
255
        with Image.open("Tests/images/16bit.s.tif") as im:
256
            im.load()
257
            assert im.mode == "I"
258
            assert im.getpixel((0, 0)) == 32767
259
            assert im.getpixel((0, 1)) == 0
260

261
    def test_12bit_rawmode(self) -> None:
262
        """Are we generating the same interpretation
263
        of the image as Imagemagick is?"""
264

265
        with Image.open("Tests/images/12bit.cropped.tif") as im:
266
            # to make the target --
267
            # convert 12bit.cropped.tif -depth 16 tmp.tif
268
            # convert tmp.tif -evaluate RightShift 4 12in16bit2.tif
269
            # imagemagick will auto scale so that a 12bit FFF is 16bit FFF0,
270
            # so we need to unshift so that the integer values are the same.
271

272
            assert_image_equal_tofile(im, "Tests/images/12in16bit.tif")
273

274
    def test_32bit_float(self) -> None:
275
        # Issue 614, specific 32-bit float format
276
        path = "Tests/images/10ct_32bit_128.tiff"
277
        with Image.open(path) as im:
278
            im.load()
279

280
            assert im.getpixel((0, 0)) == -0.4526388943195343
281
            assert im.getextrema() == (-3.140936851501465, 3.140684127807617)
282

283
    def test_unknown_pixel_mode(self) -> None:
284
        with pytest.raises(OSError):
285
            with Image.open("Tests/images/hopper_unknown_pixel_mode.tif"):
286
                pass
287

288
    @pytest.mark.parametrize(
289
        "path, n_frames",
290
        (
291
            ("Tests/images/multipage-lastframe.tif", 1),
292
            ("Tests/images/multipage.tiff", 3),
293
        ),
294
    )
295
    def test_n_frames(self, path: str, n_frames: int) -> None:
296
        with Image.open(path) as im:
297
            assert im.n_frames == n_frames
298
            assert im.is_animated == (n_frames != 1)
299

300
    def test_eoferror(self) -> None:
301
        with Image.open("Tests/images/multipage-lastframe.tif") as im:
302
            n_frames = im.n_frames
303

304
            # Test seeking past the last frame
305
            with pytest.raises(EOFError):
306
                im.seek(n_frames)
307
            assert im.tell() < n_frames
308

309
            # Test that seeking to the last frame does not raise an error
310
            im.seek(n_frames - 1)
311

312
    def test_multipage(self) -> None:
313
        # issue #862
314
        with Image.open("Tests/images/multipage.tiff") as im:
315
            # file is a multipage tiff: 10x10 green, 10x10 red, 20x20 blue
316

317
            im.seek(0)
318
            assert im.size == (10, 10)
319
            assert im.convert("RGB").getpixel((0, 0)) == (0, 128, 0)
320

321
            im.seek(1)
322
            im.load()
323
            assert im.size == (10, 10)
324
            assert im.convert("RGB").getpixel((0, 0)) == (255, 0, 0)
325

326
            im.seek(0)
327
            im.load()
328
            assert im.size == (10, 10)
329
            assert im.convert("RGB").getpixel((0, 0)) == (0, 128, 0)
330

331
            im.seek(2)
332
            im.load()
333
            assert im.size == (20, 20)
334
            assert im.convert("RGB").getpixel((0, 0)) == (0, 0, 255)
335

336
    def test_multipage_last_frame(self) -> None:
337
        with Image.open("Tests/images/multipage-lastframe.tif") as im:
338
            im.load()
339
            assert im.size == (20, 20)
340
            assert im.convert("RGB").getpixel((0, 0)) == (0, 0, 255)
341

342
    def test_frame_order(self) -> None:
343
        # A frame can't progress to itself after reading
344
        with Image.open("Tests/images/multipage_single_frame_loop.tiff") as im:
345
            assert im.n_frames == 1
346

347
        # A frame can't progress to a frame that has already been read
348
        with Image.open("Tests/images/multipage_multiple_frame_loop.tiff") as im:
349
            assert im.n_frames == 2
350

351
        # Frames don't have to be in sequence
352
        with Image.open("Tests/images/multipage_out_of_order.tiff") as im:
353
            assert im.n_frames == 3
354

355
    def test___str__(self) -> None:
356
        filename = "Tests/images/pil136.tiff"
357
        with Image.open(filename) as im:
358
            # Act
359
            ret = str(im.ifd)
360

361
            # Assert
362
            assert isinstance(ret, str)
363

364
    def test_dict(self) -> None:
365
        # Arrange
366
        filename = "Tests/images/pil136.tiff"
367
        with Image.open(filename) as im:
368
            # v2 interface
369
            v2_tags = {
370
                256: 55,
371
                257: 43,
372
                258: (8, 8, 8, 8),
373
                259: 1,
374
                262: 2,
375
                296: 2,
376
                273: (8,),
377
                338: (1,),
378
                277: 4,
379
                279: (9460,),
380
                282: 72.0,
381
                283: 72.0,
382
                284: 1,
383
            }
384
            assert dict(im.tag_v2) == v2_tags
385

386
            # legacy interface
387
            legacy_tags = {
388
                256: (55,),
389
                257: (43,),
390
                258: (8, 8, 8, 8),
391
                259: (1,),
392
                262: (2,),
393
                296: (2,),
394
                273: (8,),
395
                338: (1,),
396
                277: (4,),
397
                279: (9460,),
398
                282: ((720000, 10000),),
399
                283: ((720000, 10000),),
400
                284: (1,),
401
            }
402
            assert dict(im.tag) == legacy_tags
403

404
    def test__delitem__(self) -> None:
405
        filename = "Tests/images/pil136.tiff"
406
        with Image.open(filename) as im:
407
            len_before = len(dict(im.ifd))
408
            del im.ifd[256]
409
            len_after = len(dict(im.ifd))
410
            assert len_before == len_after + 1
411

412
    @pytest.mark.parametrize("legacy_api", (False, True))
413
    def test_load_byte(self, legacy_api: bool) -> None:
414
        ifd = TiffImagePlugin.ImageFileDirectory_v2()
415
        data = b"abc"
416
        ret = ifd.load_byte(data, legacy_api)
417
        assert ret == b"abc"
418

419
    def test_load_string(self) -> None:
420
        ifd = TiffImagePlugin.ImageFileDirectory_v2()
421
        data = b"abc\0"
422
        ret = ifd.load_string(data, False)
423
        assert ret == "abc"
424

425
    def test_load_float(self) -> None:
426
        ifd = TiffImagePlugin.ImageFileDirectory_v2()
427
        data = b"abcdabcd"
428
        ret = getattr(ifd, "load_float")(data, False)
429
        assert ret == (1.6777999408082104e22, 1.6777999408082104e22)
430

431
    def test_load_double(self) -> None:
432
        ifd = TiffImagePlugin.ImageFileDirectory_v2()
433
        data = b"abcdefghabcdefgh"
434
        ret = getattr(ifd, "load_double")(data, False)
435
        assert ret == (8.540883223036124e194, 8.540883223036124e194)
436

437
    def test_ifd_tag_type(self) -> None:
438
        with Image.open("Tests/images/ifd_tag_type.tiff") as im:
439
            assert 0x8825 in im.tag_v2
440

441
    def test_exif(self, tmp_path: Path) -> None:
442
        def check_exif(exif: Image.Exif) -> None:
443
            assert sorted(exif.keys()) == [
444
                256,
445
                257,
446
                258,
447
                259,
448
                262,
449
                271,
450
                272,
451
                273,
452
                277,
453
                278,
454
                279,
455
                282,
456
                283,
457
                284,
458
                296,
459
                297,
460
                305,
461
                339,
462
                700,
463
                34665,
464
                34853,
465
                50735,
466
            ]
467
            assert exif[256] == 640
468
            assert exif[271] == "FLIR"
469

470
            gps = exif.get_ifd(0x8825)
471
            assert list(gps.keys()) == [0, 1, 2, 3, 4, 5, 6, 18]
472
            assert gps[0] == b"\x03\x02\x00\x00"
473
            assert gps[18] == "WGS-84"
474

475
        outfile = str(tmp_path / "temp.tif")
476
        with Image.open("Tests/images/ifd_tag_type.tiff") as im:
477
            exif = im.getexif()
478
            check_exif(exif)
479

480
            im.save(outfile, exif=exif)
481

482
        outfile2 = str(tmp_path / "temp2.tif")
483
        with Image.open(outfile) as im:
484
            exif = im.getexif()
485
            check_exif(exif)
486

487
            im.save(outfile2, exif=exif.tobytes())
488

489
        with Image.open(outfile2) as im:
490
            exif = im.getexif()
491
            check_exif(exif)
492

493
    def test_modify_exif(self, tmp_path: Path) -> None:
494
        outfile = str(tmp_path / "temp.tif")
495
        with Image.open("Tests/images/ifd_tag_type.tiff") as im:
496
            exif = im.getexif()
497
            exif[264] = 100
498

499
            im.save(outfile, exif=exif)
500

501
        with Image.open(outfile) as im:
502
            exif = im.getexif()
503
            assert exif[264] == 100
504

505
    def test_reload_exif_after_seek(self) -> None:
506
        with Image.open("Tests/images/multipage.tiff") as im:
507
            exif = im.getexif()
508
            del exif[256]
509
            im.seek(1)
510

511
            assert 256 in exif
512

513
    def test_exif_frames(self) -> None:
514
        # Test that EXIF data can change across frames
515
        with Image.open("Tests/images/g4-multi.tiff") as im:
516
            assert im.getexif()[273] == (328, 815)
517

518
            im.seek(1)
519
            assert im.getexif()[273] == (1408, 1907)
520

521
    @pytest.mark.parametrize("mode", ("1", "L"))
522
    def test_photometric(self, mode: str, tmp_path: Path) -> None:
523
        filename = str(tmp_path / "temp.tif")
524
        im = hopper(mode)
525
        im.save(filename, tiffinfo={262: 0})
526
        with Image.open(filename) as reloaded:
527
            assert reloaded.tag_v2[262] == 0
528
            assert_image_equal(im, reloaded)
529

530
    def test_seek(self) -> None:
531
        filename = "Tests/images/pil136.tiff"
532
        with Image.open(filename) as im:
533
            im.seek(0)
534
            assert im.tell() == 0
535

536
    def test_seek_eof(self) -> None:
537
        filename = "Tests/images/pil136.tiff"
538
        with Image.open(filename) as im:
539
            assert im.tell() == 0
540
            with pytest.raises(EOFError):
541
                im.seek(-1)
542
            with pytest.raises(EOFError):
543
                im.seek(1)
544

545
    def test__limit_rational_int(self) -> None:
546
        from PIL.TiffImagePlugin import _limit_rational
547

548
        value = 34
549
        ret = _limit_rational(value, 65536)
550
        assert ret == (34, 1)
551

552
    def test__limit_rational_float(self) -> None:
553
        from PIL.TiffImagePlugin import _limit_rational
554

555
        value = 22.3
556
        ret = _limit_rational(value, 65536)
557
        assert ret == (223, 10)
558

559
    def test_4bit(self) -> None:
560
        test_file = "Tests/images/hopper_gray_4bpp.tif"
561
        original = hopper("L")
562
        with Image.open(test_file) as im:
563
            assert im.size == (128, 128)
564
            assert im.mode == "L"
565
            assert_image_similar(im, original, 7.3)
566

567
    def test_gray_semibyte_per_pixel(self) -> None:
568
        test_files = (
569
            (
570
                24.8,  # epsilon
571
                (  # group
572
                    "Tests/images/tiff_gray_2_4_bpp/hopper2.tif",
573
                    "Tests/images/tiff_gray_2_4_bpp/hopper2I.tif",
574
                    "Tests/images/tiff_gray_2_4_bpp/hopper2R.tif",
575
                    "Tests/images/tiff_gray_2_4_bpp/hopper2IR.tif",
576
                ),
577
            ),
578
            (
579
                7.3,  # epsilon
580
                (  # group
581
                    "Tests/images/tiff_gray_2_4_bpp/hopper4.tif",
582
                    "Tests/images/tiff_gray_2_4_bpp/hopper4I.tif",
583
                    "Tests/images/tiff_gray_2_4_bpp/hopper4R.tif",
584
                    "Tests/images/tiff_gray_2_4_bpp/hopper4IR.tif",
585
                ),
586
            ),
587
        )
588
        original = hopper("L")
589
        for epsilon, group in test_files:
590
            with Image.open(group[0]) as im:
591
                assert im.size == (128, 128)
592
                assert im.mode == "L"
593
                assert_image_similar(im, original, epsilon)
594
                for file in group[1:]:
595
                    with Image.open(file) as im2:
596
                        assert im2.size == (128, 128)
597
                        assert im2.mode == "L"
598
                        assert_image_equal(im, im2)
599

600
    def test_with_underscores(self, tmp_path: Path) -> None:
601
        kwargs = {"resolution_unit": "inch", "x_resolution": 72, "y_resolution": 36}
602
        filename = str(tmp_path / "temp.tif")
603
        hopper("RGB").save(filename, "TIFF", **kwargs)
604
        with Image.open(filename) as im:
605
            # legacy interface
606
            assert im.tag[X_RESOLUTION][0][0] == 72
607
            assert im.tag[Y_RESOLUTION][0][0] == 36
608

609
            # v2 interface
610
            assert im.tag_v2[X_RESOLUTION] == 72
611
            assert im.tag_v2[Y_RESOLUTION] == 36
612

613
    def test_roundtrip_tiff_uint16(self, tmp_path: Path) -> None:
614
        # Test an image of all '0' values
615
        pixel_value = 0x1234
616
        infile = "Tests/images/uint16_1_4660.tif"
617
        with Image.open(infile) as im:
618
            assert im.getpixel((0, 0)) == pixel_value
619

620
            tmpfile = str(tmp_path / "temp.tif")
621
            im.save(tmpfile)
622

623
            assert_image_equal_tofile(im, tmpfile)
624

625
    def test_iptc(self, tmp_path: Path) -> None:
626
        # Do not preserve IPTC_NAA_CHUNK by default if type is LONG
627
        outfile = str(tmp_path / "temp.tif")
628
        with Image.open("Tests/images/hopper.tif") as im:
629
            im.load()
630
            assert isinstance(im, TiffImagePlugin.TiffImageFile)
631
            ifd = TiffImagePlugin.ImageFileDirectory_v2()
632
            ifd[33723] = 1
633
            ifd.tagtype[33723] = 4
634
            im.tag_v2 = ifd
635
            im.save(outfile)
636

637
        with Image.open(outfile) as im:
638
            assert isinstance(im, TiffImagePlugin.TiffImageFile)
639
            assert 33723 not in im.tag_v2
640

641
    def test_rowsperstrip(self, tmp_path: Path) -> None:
642
        outfile = str(tmp_path / "temp.tif")
643
        im = hopper()
644
        im.save(outfile, tiffinfo={278: 256})
645

646
        with Image.open(outfile) as im:
647
            assert isinstance(im, TiffImagePlugin.TiffImageFile)
648
            assert im.tag_v2[278] == 256
649

650
    def test_strip_raw(self) -> None:
651
        infile = "Tests/images/tiff_strip_raw.tif"
652
        with Image.open(infile) as im:
653
            assert_image_equal_tofile(im, "Tests/images/tiff_adobe_deflate.png")
654

655
    def test_strip_planar_raw(self) -> None:
656
        # gdal_translate -of GTiff -co INTERLEAVE=BAND \
657
        # tiff_strip_raw.tif tiff_strip_planar_raw.tiff
658
        infile = "Tests/images/tiff_strip_planar_raw.tif"
659
        with Image.open(infile) as im:
660
            assert_image_equal_tofile(im, "Tests/images/tiff_adobe_deflate.png")
661

662
    def test_strip_planar_raw_with_overviews(self) -> None:
663
        # gdaladdo tiff_strip_planar_raw2.tif 2 4 8 16
664
        infile = "Tests/images/tiff_strip_planar_raw_with_overviews.tif"
665
        with Image.open(infile) as im:
666
            assert_image_equal_tofile(im, "Tests/images/tiff_adobe_deflate.png")
667

668
    def test_tiled_planar_raw(self) -> None:
669
        # gdal_translate -of GTiff -co TILED=YES -co BLOCKXSIZE=32 \
670
        # -co BLOCKYSIZE=32 -co INTERLEAVE=BAND \
671
        # tiff_tiled_raw.tif tiff_tiled_planar_raw.tiff
672
        infile = "Tests/images/tiff_tiled_planar_raw.tif"
673
        with Image.open(infile) as im:
674
            assert_image_equal_tofile(im, "Tests/images/tiff_adobe_deflate.png")
675

676
    def test_planar_configuration_save(self, tmp_path: Path) -> None:
677
        infile = "Tests/images/tiff_tiled_planar_raw.tif"
678
        with Image.open(infile) as im:
679
            assert im._planar_configuration == 2
680

681
            outfile = str(tmp_path / "temp.tif")
682
            im.save(outfile)
683

684
            with Image.open(outfile) as reloaded:
685
                assert_image_equal_tofile(reloaded, infile)
686

687
    def test_invalid_tiled_dimensions(self) -> None:
688
        with open("Tests/images/tiff_tiled_planar_raw.tif", "rb") as fp:
689
            data = fp.read()
690
        b = BytesIO(data[:144] + b"\x02" + data[145:])
691
        with pytest.raises(ValueError):
692
            Image.open(b)
693

694
    @pytest.mark.parametrize("mode", ("P", "PA"))
695
    def test_palette(self, mode: str, tmp_path: Path) -> None:
696
        outfile = str(tmp_path / "temp.tif")
697

698
        im = hopper(mode)
699
        im.save(outfile)
700

701
        with Image.open(outfile) as reloaded:
702
            assert_image_equal(im.convert("RGB"), reloaded.convert("RGB"))
703

704
    def test_tiff_save_all(self) -> None:
705
        mp = BytesIO()
706
        with Image.open("Tests/images/multipage.tiff") as im:
707
            im.save(mp, format="tiff", save_all=True)
708

709
        mp.seek(0, os.SEEK_SET)
710
        with Image.open(mp) as im:
711
            assert im.n_frames == 3
712

713
        # Test appending images
714
        mp = BytesIO()
715
        im = Image.new("RGB", (100, 100), "#f00")
716
        ims = [Image.new("RGB", (100, 100), color) for color in ["#0f0", "#00f"]]
717
        im.copy().save(mp, format="TIFF", save_all=True, append_images=ims)
718

719
        mp.seek(0, os.SEEK_SET)
720
        with Image.open(mp) as reread:
721
            assert reread.n_frames == 3
722

723
        # Test appending using a generator
724
        def im_generator(ims: list[Image.Image]) -> Generator[Image.Image, None, None]:
725
            yield from ims
726

727
        mp = BytesIO()
728
        im.save(mp, format="TIFF", save_all=True, append_images=im_generator(ims))
729

730
        mp.seek(0, os.SEEK_SET)
731
        with Image.open(mp) as reread:
732
            assert reread.n_frames == 3
733

734
    def test_saving_icc_profile(self, tmp_path: Path) -> None:
735
        # Tests saving TIFF with icc_profile set.
736
        # At the time of writing this will only work for non-compressed tiffs
737
        # as libtiff does not support embedded ICC profiles,
738
        # ImageFile._save(..) however does.
739
        im = Image.new("RGB", (1, 1))
740
        im.info["icc_profile"] = "Dummy value"
741

742
        # Try save-load round trip to make sure both handle icc_profile.
743
        tmpfile = str(tmp_path / "temp.tif")
744
        im.save(tmpfile, "TIFF", compression="raw")
745
        with Image.open(tmpfile) as reloaded:
746
            assert b"Dummy value" == reloaded.info["icc_profile"]
747

748
    def test_save_icc_profile(self, tmp_path: Path) -> None:
749
        im = hopper()
750
        assert "icc_profile" not in im.info
751

752
        outfile = str(tmp_path / "temp.tif")
753
        icc_profile = b"Dummy value"
754
        im.save(outfile, icc_profile=icc_profile)
755

756
        with Image.open(outfile) as reloaded:
757
            assert reloaded.info["icc_profile"] == icc_profile
758

759
    def test_save_bmp_compression(self, tmp_path: Path) -> None:
760
        with Image.open("Tests/images/hopper.bmp") as im:
761
            assert im.info["compression"] == 0
762

763
            outfile = str(tmp_path / "temp.tif")
764
            im.save(outfile)
765

766
    def test_discard_icc_profile(self, tmp_path: Path) -> None:
767
        outfile = str(tmp_path / "temp.tif")
768

769
        with Image.open("Tests/images/icc_profile.png") as im:
770
            assert "icc_profile" in im.info
771

772
            im.save(outfile, icc_profile=None)
773

774
        with Image.open(outfile) as reloaded:
775
            assert "icc_profile" not in reloaded.info
776

777
    def test_getxmp(self) -> None:
778
        with Image.open("Tests/images/lab.tif") as im:
779
            if ElementTree is None:
780
                with pytest.warns(
781
                    UserWarning,
782
                    match="XMP data cannot be read without defusedxml dependency",
783
                ):
784
                    assert im.getxmp() == {}
785
            else:
786
                assert "xmp" in im.info
787
                xmp = im.getxmp()
788

789
                description = xmp["xmpmeta"]["RDF"]["Description"]
790
                assert description[0]["format"] == "image/tiff"
791
                assert description[3]["BitsPerSample"]["Seq"]["li"] == ["8", "8", "8"]
792

793
    def test_get_photoshop_blocks(self) -> None:
794
        with Image.open("Tests/images/lab.tif") as im:
795
            assert list(im.get_photoshop_blocks().keys()) == [
796
                1061,
797
                1002,
798
                1005,
799
                1062,
800
                1037,
801
                1049,
802
                1011,
803
                1034,
804
                10000,
805
                1013,
806
                1016,
807
                1032,
808
                1054,
809
                1050,
810
                1064,
811
                1041,
812
                1044,
813
                1036,
814
                1057,
815
                4000,
816
                4001,
817
            ]
818

819
    def test_tiff_chunks(self, tmp_path: Path) -> None:
820
        tmpfile = str(tmp_path / "temp.tif")
821

822
        im = hopper()
823
        with open(tmpfile, "wb") as fp:
824
            for y in range(0, 128, 32):
825
                chunk = im.crop((0, y, 128, y + 32))
826
                if y == 0:
827
                    chunk.save(
828
                        fp,
829
                        "TIFF",
830
                        tiffinfo={
831
                            TiffImagePlugin.IMAGEWIDTH: 128,
832
                            TiffImagePlugin.IMAGELENGTH: 128,
833
                        },
834
                    )
835
                else:
836
                    fp.write(chunk.tobytes())
837

838
        assert_image_equal_tofile(im, tmpfile)
839

840
    def test_close_on_load_exclusive(self, tmp_path: Path) -> None:
841
        # similar to test_fd_leak, but runs on unixlike os
842
        tmpfile = str(tmp_path / "temp.tif")
843

844
        with Image.open("Tests/images/uint16_1_4660.tif") as im:
845
            im.save(tmpfile)
846

847
        im = Image.open(tmpfile)
848
        fp = im.fp
849
        assert not fp.closed
850
        im.load()
851
        assert fp.closed
852

853
    def test_close_on_load_nonexclusive(self, tmp_path: Path) -> None:
854
        tmpfile = str(tmp_path / "temp.tif")
855

856
        with Image.open("Tests/images/uint16_1_4660.tif") as im:
857
            im.save(tmpfile)
858

859
        with open(tmpfile, "rb") as f:
860
            im = Image.open(f)
861
            fp = im.fp
862
            assert not fp.closed
863
            im.load()
864
            assert not fp.closed
865

866
    # Ignore this UserWarning which triggers for four tags:
867
    # "Possibly corrupt EXIF data.  Expecting to read 50404352 bytes but..."
868
    @pytest.mark.filterwarnings("ignore:Possibly corrupt EXIF data")
869
    # Ignore this UserWarning:
870
    @pytest.mark.filterwarnings("ignore:Truncated File Read")
871
    @pytest.mark.skipif(
872
        not os.path.exists("Tests/images/string_dimension.tiff"),
873
        reason="Extra image files not installed",
874
    )
875
    def test_string_dimension(self) -> None:
876
        # Assert that an error is raised if one of the dimensions is a string
877
        with Image.open("Tests/images/string_dimension.tiff") as im:
878
            with pytest.raises(OSError):
879
                im.load()
880

881
    @pytest.mark.timeout(6)
882
    @pytest.mark.filterwarnings("ignore:Truncated File Read")
883
    def test_timeout(self) -> None:
884
        with Image.open("Tests/images/timeout-6646305047838720") as im:
885
            ImageFile.LOAD_TRUNCATED_IMAGES = True
886
            im.load()
887
            ImageFile.LOAD_TRUNCATED_IMAGES = False
888

889
    @pytest.mark.parametrize(
890
        "test_file",
891
        [
892
            "Tests/images/oom-225817ca0f8c663be7ab4b9e717b02c661e66834.tif",
893
        ],
894
    )
895
    @pytest.mark.timeout(2)
896
    def test_oom(self, test_file: str) -> None:
897
        with pytest.raises(UnidentifiedImageError):
898
            with pytest.warns(UserWarning):
899
                with Image.open(test_file):
900
                    pass
901

902

903
@pytest.mark.skipif(not is_win32(), reason="Windows only")
904
class TestFileTiffW32:
905
    def test_fd_leak(self, tmp_path: Path) -> None:
906
        tmpfile = str(tmp_path / "temp.tif")
907

908
        # this is an mmaped file.
909
        with Image.open("Tests/images/uint16_1_4660.tif") as im:
910
            im.save(tmpfile)
911

912
        im = Image.open(tmpfile)
913
        fp = im.fp
914
        assert not fp.closed
915
        with pytest.raises(OSError):
916
            os.remove(tmpfile)
917
        im.load()
918
        assert fp.closed
919

920
        # this closes the mmap
921
        im.close()
922

923
        # this should not fail, as load should have closed the file pointer,
924
        # and close should have closed the mmap
925
        os.remove(tmpfile)
926

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

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

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

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