Pillow

Форк
0
/
test_image.py 
1154 строки · 36.7 Кб
1
from __future__ import annotations
2

3
import io
4
import logging
5
import os
6
import shutil
7
import sys
8
import tempfile
9
import warnings
10
from pathlib import Path
11
from types import ModuleType
12
from typing import IO, Any
13

14
import pytest
15

16
from PIL import (
17
    ExifTags,
18
    Image,
19
    ImageDraw,
20
    ImageFile,
21
    ImagePalette,
22
    UnidentifiedImageError,
23
    features,
24
)
25

26
from .helper import (
27
    assert_image_equal,
28
    assert_image_equal_tofile,
29
    assert_image_similar,
30
    assert_image_similar_tofile,
31
    assert_not_all_same,
32
    hopper,
33
    is_big_endian,
34
    is_win32,
35
    mark_if_feature_version,
36
    skip_unless_feature,
37
)
38

39
ElementTree: ModuleType | None
40
try:
41
    from defusedxml import ElementTree
42
except ImportError:
43
    ElementTree = None
44

45
PrettyPrinter: type | None
46
try:
47
    from IPython.lib.pretty import PrettyPrinter
48
except ImportError:
49
    PrettyPrinter = None
50

51

52
# Deprecation helper
53
def helper_image_new(mode: str, size: tuple[int, int]) -> Image.Image:
54
    if mode.startswith("BGR;"):
55
        with pytest.warns(DeprecationWarning):
56
            return Image.new(mode, size)
57
    else:
58
        return Image.new(mode, size)
59

60

61
class TestImage:
62
    @pytest.mark.parametrize("mode", Image.MODES + ["BGR;15", "BGR;16", "BGR;24"])
63
    def test_image_modes_success(self, mode: str) -> None:
64
        helper_image_new(mode, (1, 1))
65

66
    @pytest.mark.parametrize("mode", ("", "bad", "very very long"))
67
    def test_image_modes_fail(self, mode: str) -> None:
68
        with pytest.raises(ValueError) as e:
69
            Image.new(mode, (1, 1))
70
        assert str(e.value) == "unrecognized image mode"
71

72
    def test_exception_inheritance(self) -> None:
73
        assert issubclass(UnidentifiedImageError, OSError)
74

75
    def test_sanity(self) -> None:
76
        im = Image.new("L", (100, 100))
77
        assert repr(im)[:45] == "<PIL.Image.Image image mode=L size=100x100 at"
78
        assert im.mode == "L"
79
        assert im.size == (100, 100)
80

81
        im = Image.new("RGB", (100, 100))
82
        assert repr(im)[:45] == "<PIL.Image.Image image mode=RGB size=100x100 "
83
        assert im.mode == "RGB"
84
        assert im.size == (100, 100)
85

86
        Image.new("L", (100, 100), None)
87
        im2 = Image.new("L", (100, 100), 0)
88
        im3 = Image.new("L", (100, 100), "black")
89

90
        assert im2.getcolors() == [(10000, 0)]
91
        assert im3.getcolors() == [(10000, 0)]
92

93
        with pytest.raises(ValueError):
94
            Image.new("X", (100, 100))
95
        with pytest.raises(ValueError):
96
            Image.new("", (100, 100))
97
        # with pytest.raises(MemoryError):
98
        #   Image.new("L", (1000000, 1000000))
99

100
    @pytest.mark.skipif(PrettyPrinter is None, reason="IPython is not installed")
101
    def test_repr_pretty(self) -> None:
102
        im = Image.new("L", (100, 100))
103

104
        output = io.StringIO()
105
        assert PrettyPrinter is not None
106
        p = PrettyPrinter(output)
107
        im._repr_pretty_(p, False)
108
        assert output.getvalue() == "<PIL.Image.Image image mode=L size=100x100>"
109

110
    def test_open_formats(self) -> None:
111
        PNGFILE = "Tests/images/hopper.png"
112
        JPGFILE = "Tests/images/hopper.jpg"
113

114
        with pytest.raises(TypeError):
115
            with Image.open(PNGFILE, formats=123):  # type: ignore[arg-type]
116
                pass
117

118
        format_list: list[list[str] | tuple[str, ...]] = [
119
            ["JPEG"],
120
            ("JPEG",),
121
            ["jpeg"],
122
            ["Jpeg"],
123
            ["jPeG"],
124
            ["JpEg"],
125
        ]
126
        for formats in format_list:
127
            with pytest.raises(UnidentifiedImageError):
128
                with Image.open(PNGFILE, formats=formats):
129
                    pass
130

131
            with Image.open(JPGFILE, formats=formats) as im:
132
                assert im.mode == "RGB"
133
                assert im.size == (128, 128)
134

135
        for file in [PNGFILE, JPGFILE]:
136
            with Image.open(file, formats=None) as im:
137
                assert im.mode == "RGB"
138
                assert im.size == (128, 128)
139

140
    def test_open_verbose_failure(self, monkeypatch: pytest.MonkeyPatch) -> None:
141
        monkeypatch.setattr(Image, "WARN_POSSIBLE_FORMATS", True)
142

143
        im = io.BytesIO(b"")
144
        with pytest.warns(UserWarning):
145
            with pytest.raises(UnidentifiedImageError):
146
                with Image.open(im):
147
                    pass
148

149
    def test_width_height(self) -> None:
150
        im = Image.new("RGB", (1, 2))
151
        assert im.width == 1
152
        assert im.height == 2
153

154
        with pytest.raises(AttributeError):
155
            im.size = (3, 4)  # type: ignore[misc]
156

157
    def test_set_mode(self) -> None:
158
        im = Image.new("RGB", (1, 1))
159

160
        with pytest.raises(AttributeError):
161
            im.mode = "P"  # type: ignore[misc]
162

163
    def test_invalid_image(self) -> None:
164
        im = io.BytesIO(b"")
165
        with pytest.raises(UnidentifiedImageError):
166
            with Image.open(im):
167
                pass
168

169
    def test_bad_mode(self) -> None:
170
        with pytest.raises(ValueError):
171
            with Image.open("filename", "bad mode"):  # type: ignore[arg-type]
172
                pass
173

174
    def test_stringio(self) -> None:
175
        with pytest.raises(ValueError):
176
            with Image.open(io.StringIO()):  # type: ignore[arg-type]
177
                pass
178

179
    def test_pathlib(self, tmp_path: Path) -> None:
180
        with Image.open(Path("Tests/images/multipage-mmap.tiff")) as im:
181
            assert im.mode == "P"
182
            assert im.size == (10, 10)
183

184
        with Image.open(Path("Tests/images/hopper.jpg")) as im:
185
            assert im.mode == "RGB"
186
            assert im.size == (128, 128)
187

188
            for ext in (".jpg", ".jp2"):
189
                if ext == ".jp2" and not features.check_codec("jpg_2000"):
190
                    pytest.skip("jpg_2000 not available")
191
                temp_file = str(tmp_path / ("temp." + ext))
192
                if os.path.exists(temp_file):
193
                    os.remove(temp_file)
194
                im.save(Path(temp_file))
195

196
    def test_fp_name(self, tmp_path: Path) -> None:
197
        temp_file = str(tmp_path / "temp.jpg")
198

199
        class FP(io.BytesIO):
200
            name: str
201

202
            if sys.version_info >= (3, 12):
203
                from collections.abc import Buffer
204

205
                def write(self, data: Buffer) -> int:
206
                    return len(data)
207

208
            else:
209

210
                def write(self, data: Any) -> int:
211
                    return len(data)
212

213
        fp = FP()
214
        fp.name = temp_file
215

216
        im = hopper()
217
        im.save(fp)
218

219
    def test_tempfile(self) -> None:
220
        # see #1460, pathlib support breaks tempfile.TemporaryFile on py27
221
        # Will error out on save on 3.0.0
222
        im = hopper()
223
        with tempfile.TemporaryFile() as fp:
224
            im.save(fp, "JPEG")
225
            fp.seek(0)
226
            with Image.open(fp) as reloaded:
227
                assert_image_similar(im, reloaded, 20)
228

229
    def test_unknown_extension(self, tmp_path: Path) -> None:
230
        im = hopper()
231
        temp_file = str(tmp_path / "temp.unknown")
232
        with pytest.raises(ValueError):
233
            im.save(temp_file)
234

235
    def test_internals(self) -> None:
236
        im = Image.new("L", (100, 100))
237
        im.readonly = 1
238
        im._copy()
239
        assert not im.readonly
240

241
        im.readonly = 1
242
        im.paste(0, (0, 0, 100, 100))
243
        assert not im.readonly
244

245
    @pytest.mark.skipif(is_win32(), reason="Test requires opening tempfile twice")
246
    @pytest.mark.skipif(
247
        sys.platform == "cygwin",
248
        reason="Test requires opening an mmaped file for writing",
249
    )
250
    def test_readonly_save(self, tmp_path: Path) -> None:
251
        temp_file = str(tmp_path / "temp.bmp")
252
        shutil.copy("Tests/images/rgb32bf-rgba.bmp", temp_file)
253

254
        with Image.open(temp_file) as im:
255
            assert im.readonly
256
            im.save(temp_file)
257

258
    def test_dump(self, tmp_path: Path) -> None:
259
        im = Image.new("L", (10, 10))
260
        im._dump(str(tmp_path / "temp_L.ppm"))
261

262
        im = Image.new("RGB", (10, 10))
263
        im._dump(str(tmp_path / "temp_RGB.ppm"))
264

265
        im = Image.new("HSV", (10, 10))
266
        with pytest.raises(ValueError):
267
            im._dump(str(tmp_path / "temp_HSV.ppm"))
268

269
    def test_comparison_with_other_type(self) -> None:
270
        # Arrange
271
        item = Image.new("RGB", (25, 25), "#000")
272
        num = 12
273

274
        # Act/Assert
275
        # Shouldn't cause AttributeError (#774)
276
        assert item is not None
277
        assert item != num
278

279
    def test_expand_x(self) -> None:
280
        # Arrange
281
        im = hopper()
282
        orig_size = im.size
283
        xmargin = 5
284

285
        # Act
286
        im = im._expand(xmargin)
287

288
        # Assert
289
        assert im.size[0] == orig_size[0] + 2 * xmargin
290
        assert im.size[1] == orig_size[1] + 2 * xmargin
291

292
    def test_expand_xy(self) -> None:
293
        # Arrange
294
        im = hopper()
295
        orig_size = im.size
296
        xmargin = 5
297
        ymargin = 3
298

299
        # Act
300
        im = im._expand(xmargin, ymargin)
301

302
        # Assert
303
        assert im.size[0] == orig_size[0] + 2 * xmargin
304
        assert im.size[1] == orig_size[1] + 2 * ymargin
305

306
    def test_getbands(self) -> None:
307
        # Assert
308
        assert hopper("RGB").getbands() == ("R", "G", "B")
309
        assert hopper("YCbCr").getbands() == ("Y", "Cb", "Cr")
310

311
    def test_getchannel_wrong_params(self) -> None:
312
        im = hopper()
313

314
        with pytest.raises(ValueError):
315
            im.getchannel(-1)
316
        with pytest.raises(ValueError):
317
            im.getchannel(3)
318
        with pytest.raises(ValueError):
319
            im.getchannel("Z")
320
        with pytest.raises(ValueError):
321
            im.getchannel("1")
322

323
    def test_getchannel(self) -> None:
324
        im = hopper("YCbCr")
325
        Y, Cb, Cr = im.split()
326

327
        assert_image_equal(Y, im.getchannel(0))
328
        assert_image_equal(Y, im.getchannel("Y"))
329
        assert_image_equal(Cb, im.getchannel(1))
330
        assert_image_equal(Cb, im.getchannel("Cb"))
331
        assert_image_equal(Cr, im.getchannel(2))
332
        assert_image_equal(Cr, im.getchannel("Cr"))
333

334
    def test_getbbox(self) -> None:
335
        # Arrange
336
        im = hopper()
337

338
        # Act
339
        bbox = im.getbbox()
340

341
        # Assert
342
        assert bbox == (0, 0, 128, 128)
343

344
    def test_ne(self) -> None:
345
        # Arrange
346
        im1 = Image.new("RGB", (25, 25), "black")
347
        im2 = Image.new("RGB", (25, 25), "white")
348

349
        # Act / Assert
350
        assert im1 != im2
351

352
    def test_alpha_composite(self) -> None:
353
        # https://stackoverflow.com/questions/3374878
354
        # Arrange
355
        expected_colors = sorted(
356
            [
357
                (1122, (128, 127, 0, 255)),
358
                (1089, (0, 255, 0, 255)),
359
                (3300, (255, 0, 0, 255)),
360
                (1156, (170, 85, 0, 192)),
361
                (1122, (0, 255, 0, 128)),
362
                (1122, (255, 0, 0, 128)),
363
                (1089, (0, 255, 0, 0)),
364
            ]
365
        )
366

367
        dst = Image.new("RGBA", size=(100, 100), color=(0, 255, 0, 255))
368
        draw = ImageDraw.Draw(dst)
369
        draw.rectangle((0, 33, 100, 66), fill=(0, 255, 0, 128))
370
        draw.rectangle((0, 67, 100, 100), fill=(0, 255, 0, 0))
371
        src = Image.new("RGBA", size=(100, 100), color=(255, 0, 0, 255))
372
        draw = ImageDraw.Draw(src)
373
        draw.rectangle((33, 0, 66, 100), fill=(255, 0, 0, 128))
374
        draw.rectangle((67, 0, 100, 100), fill=(255, 0, 0, 0))
375

376
        # Act
377
        img = Image.alpha_composite(dst, src)
378

379
        # Assert
380
        img_colors = img.getcolors()
381
        assert img_colors is not None
382
        assert sorted(img_colors) == expected_colors
383

384
    def test_alpha_inplace(self) -> None:
385
        src = Image.new("RGBA", (128, 128), "blue")
386

387
        over = Image.new("RGBA", (128, 128), "red")
388
        mask = hopper("L")
389
        over.putalpha(mask)
390

391
        target = Image.alpha_composite(src, over)
392

393
        # basic
394
        full = src.copy()
395
        full.alpha_composite(over)
396
        assert_image_equal(full, target)
397

398
        # with offset down to right
399
        offset = src.copy()
400
        offset.alpha_composite(over, (64, 64))
401
        assert_image_equal(offset.crop((64, 64, 127, 127)), target.crop((0, 0, 63, 63)))
402
        assert offset.size == (128, 128)
403

404
        # with negative offset
405
        offset = src.copy()
406
        offset.alpha_composite(over, (-64, -64))
407
        assert_image_equal(offset.crop((0, 0, 63, 63)), target.crop((64, 64, 127, 127)))
408
        assert offset.size == (128, 128)
409

410
        # offset and crop
411
        box = src.copy()
412
        box.alpha_composite(over, (64, 64), (0, 0, 32, 32))
413
        assert_image_equal(box.crop((64, 64, 96, 96)), target.crop((0, 0, 32, 32)))
414
        assert_image_equal(box.crop((96, 96, 128, 128)), src.crop((0, 0, 32, 32)))
415
        assert box.size == (128, 128)
416

417
        # source point
418
        source = src.copy()
419
        source.alpha_composite(over, (32, 32), (32, 32, 96, 96))
420

421
        assert_image_equal(source.crop((32, 32, 96, 96)), target.crop((32, 32, 96, 96)))
422
        assert source.size == (128, 128)
423

424
        # errors
425
        with pytest.raises(ValueError):
426
            source.alpha_composite(over, "invalid destination")  # type: ignore[arg-type]
427
        with pytest.raises(ValueError):
428
            source.alpha_composite(over, (0, 0), "invalid source")  # type: ignore[arg-type]
429
        with pytest.raises(ValueError):
430
            source.alpha_composite(over, 0)  # type: ignore[arg-type]
431
        with pytest.raises(ValueError):
432
            source.alpha_composite(over, (0, 0), 0)  # type: ignore[arg-type]
433
        with pytest.raises(ValueError):
434
            source.alpha_composite(over, (0, 0), (0, -1))
435

436
    def test_register_open_duplicates(self) -> None:
437
        # Arrange
438
        factory, accept = Image.OPEN["JPEG"]
439
        id_length = len(Image.ID)
440

441
        # Act
442
        Image.register_open("JPEG", factory, accept)
443

444
        # Assert
445
        assert len(Image.ID) == id_length
446

447
    def test_registered_extensions_uninitialized(self) -> None:
448
        # Arrange
449
        Image._initialized = 0
450

451
        # Act
452
        Image.registered_extensions()
453

454
        # Assert
455
        assert Image._initialized == 2
456

457
    def test_registered_extensions(self) -> None:
458
        # Arrange
459
        # Open an image to trigger plugin registration
460
        with Image.open("Tests/images/rgb.jpg"):
461
            pass
462

463
        # Act
464
        extensions = Image.registered_extensions()
465

466
        # Assert
467
        assert extensions
468
        for ext in [".cur", ".icns", ".tif", ".tiff"]:
469
            assert ext in extensions
470

471
    def test_effect_mandelbrot(self) -> None:
472
        # Arrange
473
        size = (512, 512)
474
        extent = (-3, -2.5, 2, 2.5)
475
        quality = 100
476

477
        # Act
478
        im = Image.effect_mandelbrot(size, extent, quality)
479

480
        # Assert
481
        assert im.size == (512, 512)
482
        assert_image_equal_tofile(im, "Tests/images/effect_mandelbrot.png")
483

484
    def test_effect_mandelbrot_bad_arguments(self) -> None:
485
        # Arrange
486
        size = (512, 512)
487
        # Get coordinates the wrong way round:
488
        extent = (+3, +2.5, -2, -2.5)
489
        # Quality < 2:
490
        quality = 1
491

492
        # Act/Assert
493
        with pytest.raises(ValueError):
494
            Image.effect_mandelbrot(size, extent, quality)
495

496
    def test_effect_noise(self) -> None:
497
        # Arrange
498
        size = (100, 100)
499
        sigma = 128
500

501
        # Act
502
        im = Image.effect_noise(size, sigma)
503

504
        # Assert
505
        assert im.size == (100, 100)
506
        assert im.mode == "L"
507
        p0 = im.getpixel((0, 0))
508
        p1 = im.getpixel((0, 1))
509
        p2 = im.getpixel((0, 2))
510
        p3 = im.getpixel((0, 3))
511
        p4 = im.getpixel((0, 4))
512
        assert_not_all_same([p0, p1, p2, p3, p4])
513

514
    def test_effect_spread(self) -> None:
515
        # Arrange
516
        im = hopper()
517
        distance = 10
518

519
        # Act
520
        im2 = im.effect_spread(distance)
521

522
        # Assert
523
        assert im.size == (128, 128)
524
        assert_image_similar_tofile(im2, "Tests/images/effect_spread.png", 110)
525

526
    def test_effect_spread_zero(self) -> None:
527
        # Arrange
528
        im = hopper()
529
        distance = 0
530

531
        # Act
532
        im2 = im.effect_spread(distance)
533

534
        # Assert
535
        assert_image_equal(im, im2)
536

537
    def test_check_size(self) -> None:
538
        # Checking that the _check_size function throws value errors when we want it to
539
        with pytest.raises(ValueError):
540
            # not a tuple
541
            Image.new("RGB", 0)  # type: ignore[arg-type]
542
        with pytest.raises(ValueError):
543
            # tuple too short
544
            Image.new("RGB", (0,))  # type: ignore[arg-type]
545
        with pytest.raises(ValueError):
546
            Image.new("RGB", (-1, -1))  # w,h < 0
547

548
        # this should pass with 0 sized images, #2259
549
        im = Image.new("L", (0, 0))
550
        assert im.size == (0, 0)
551

552
        im = Image.new("L", (0, 100))
553
        assert im.size == (0, 100)
554

555
        im = Image.new("L", (100, 0))
556
        assert im.size == (100, 0)
557

558
        assert Image.new("RGB", (1, 1))
559
        # Should pass lists too
560
        i = Image.new("RGB", [1, 1])
561
        assert isinstance(i.size, tuple)
562

563
    @pytest.mark.timeout(0.75)
564
    @pytest.mark.skipif(
565
        "PILLOW_VALGRIND_TEST" in os.environ, reason="Valgrind is slower"
566
    )
567
    @pytest.mark.parametrize("size", ((0, 100000000), (100000000, 0)))
568
    def test_empty_image(self, size: tuple[int, int]) -> None:
569
        Image.new("RGB", size)
570

571
    def test_storage_neg(self) -> None:
572
        # Storage.c accepted negative values for xsize, ysize.  Was
573
        # test_neg_ppm, but the core function for that has been
574
        # removed Calling directly into core to test the error in
575
        # Storage.c, rather than the size check above
576

577
        with pytest.raises(ValueError):
578
            Image.core.fill("RGB", (2, -2), (0, 0, 0))
579

580
    def test_one_item_tuple(self) -> None:
581
        for mode in ("I", "F", "L"):
582
            im = Image.new(mode, (100, 100), (5,))
583
            px = im.load()
584
            assert px is not None
585
            assert px[0, 0] == 5
586

587
    def test_linear_gradient_wrong_mode(self) -> None:
588
        # Arrange
589
        wrong_mode = "RGB"
590

591
        # Act / Assert
592
        with pytest.raises(ValueError):
593
            Image.linear_gradient(wrong_mode)
594

595
    @pytest.mark.parametrize("mode", ("L", "P", "I", "F"))
596
    def test_linear_gradient(self, mode: str) -> None:
597
        # Arrange
598
        target_file = "Tests/images/linear_gradient.png"
599

600
        # Act
601
        im = Image.linear_gradient(mode)
602

603
        # Assert
604
        assert im.size == (256, 256)
605
        assert im.mode == mode
606
        assert im.getpixel((0, 0)) == 0
607
        assert im.getpixel((255, 255)) == 255
608
        with Image.open(target_file) as target:
609
            target = target.convert(mode)
610
        assert_image_equal(im, target)
611

612
    def test_radial_gradient_wrong_mode(self) -> None:
613
        # Arrange
614
        wrong_mode = "RGB"
615

616
        # Act / Assert
617
        with pytest.raises(ValueError):
618
            Image.radial_gradient(wrong_mode)
619

620
    @pytest.mark.parametrize("mode", ("L", "P", "I", "F"))
621
    def test_radial_gradient(self, mode: str) -> None:
622
        # Arrange
623
        target_file = "Tests/images/radial_gradient.png"
624

625
        # Act
626
        im = Image.radial_gradient(mode)
627

628
        # Assert
629
        assert im.size == (256, 256)
630
        assert im.mode == mode
631
        assert im.getpixel((0, 0)) == 255
632
        assert im.getpixel((128, 128)) == 0
633
        with Image.open(target_file) as target:
634
            target = target.convert(mode)
635
        assert_image_equal(im, target)
636

637
    def test_register_extensions(self) -> None:
638
        test_format = "a"
639
        exts = ["b", "c"]
640
        for ext in exts:
641
            Image.register_extension(test_format, ext)
642
        ext_individual = Image.EXTENSION.copy()
643
        for ext in exts:
644
            del Image.EXTENSION[ext]
645

646
        Image.register_extensions(test_format, exts)
647
        ext_multiple = Image.EXTENSION.copy()
648
        for ext in exts:
649
            del Image.EXTENSION[ext]
650

651
        assert ext_individual == ext_multiple
652

653
    def test_remap_palette(self) -> None:
654
        # Test identity transform
655
        with Image.open("Tests/images/hopper.gif") as im:
656
            assert_image_equal(im, im.remap_palette(list(range(256))))
657

658
        # Test identity transform with an RGBA palette
659
        im = Image.new("P", (256, 1))
660
        for x in range(256):
661
            im.putpixel((x, 0), x)
662
        im.putpalette(list(range(256)) * 4, "RGBA")
663
        im_remapped = im.remap_palette(list(range(256)))
664
        assert_image_equal(im, im_remapped)
665
        assert im.palette.palette == im_remapped.palette.palette
666

667
        # Test illegal image mode
668
        with hopper() as im:
669
            with pytest.raises(ValueError):
670
                im.remap_palette(None)
671

672
    def test_remap_palette_transparency(self) -> None:
673
        im = Image.new("P", (1, 2), (0, 0, 0))
674
        im.putpixel((0, 1), (255, 0, 0))
675
        im.info["transparency"] = 0
676

677
        im_remapped = im.remap_palette([1, 0])
678
        assert im_remapped.info["transparency"] == 1
679
        palette = im_remapped.getpalette()
680
        assert palette is not None
681
        assert len(palette) == 6
682

683
        # Test unused transparency
684
        im.info["transparency"] = 2
685

686
        im_remapped = im.remap_palette([1, 0])
687
        assert "transparency" not in im_remapped.info
688

689
    def test__new(self) -> None:
690
        im = hopper("RGB")
691
        im_p = hopper("P")
692

693
        blank_p = Image.new("P", (10, 10))
694
        blank_pa = Image.new("PA", (10, 10))
695
        blank_p.palette = None
696
        blank_pa.palette = None
697

698
        def _make_new(
699
            base_image: Image.Image,
700
            image: Image.Image,
701
            palette_result: ImagePalette.ImagePalette | None = None,
702
        ) -> None:
703
            new_image = base_image._new(image.im)
704
            assert new_image.mode == image.mode
705
            assert new_image.size == image.size
706
            assert new_image.info == base_image.info
707
            if palette_result is not None:
708
                assert new_image.palette is not None
709
                assert new_image.palette.tobytes() == palette_result.tobytes()
710
            else:
711
                assert new_image.palette is None
712

713
        _make_new(im, im_p, ImagePalette.ImagePalette("RGB"))
714
        _make_new(im_p, im, None)
715
        _make_new(im, blank_p, ImagePalette.ImagePalette())
716
        _make_new(im, blank_pa, ImagePalette.ImagePalette())
717

718
    @pytest.mark.parametrize(
719
        "mode, color",
720
        (
721
            ("RGB", "#DDEEFF"),
722
            ("RGB", (221, 238, 255)),
723
            ("RGBA", (221, 238, 255, 255)),
724
        ),
725
    )
726
    def test_p_from_rgb_rgba(self, mode: str, color: str | tuple[int, ...]) -> None:
727
        im = Image.new("P", (100, 100), color)
728
        expected = Image.new(mode, (100, 100), color)
729
        assert_image_equal(im.convert(mode), expected)
730

731
    def test_no_resource_warning_on_save(self, tmp_path: Path) -> None:
732
        # https://github.com/python-pillow/Pillow/issues/835
733
        # Arrange
734
        test_file = "Tests/images/hopper.png"
735
        temp_file = str(tmp_path / "temp.jpg")
736

737
        # Act/Assert
738
        with Image.open(test_file) as im:
739
            with warnings.catch_warnings():
740
                im.save(temp_file)
741

742
    def test_no_new_file_on_error(self, tmp_path: Path) -> None:
743
        temp_file = str(tmp_path / "temp.jpg")
744

745
        im = Image.new("RGB", (0, 0))
746
        with pytest.raises(ValueError):
747
            im.save(temp_file)
748

749
        assert not os.path.exists(temp_file)
750

751
    def test_load_on_nonexclusive_multiframe(self) -> None:
752
        with open("Tests/images/frozenpond.mpo", "rb") as fp:
753

754
            def act(fp: IO[bytes]) -> None:
755
                im = Image.open(fp)
756
                im.load()
757

758
            act(fp)
759

760
            with Image.open(fp) as im:
761
                im.load()
762

763
            assert not fp.closed
764

765
    def test_empty_exif(self) -> None:
766
        with Image.open("Tests/images/exif.png") as im:
767
            exif = im.getexif()
768
        assert dict(exif)
769

770
        # Test that exif data is cleared after another load
771
        exif.load(None)
772
        assert not dict(exif)
773

774
        # Test loading just the EXIF header
775
        exif.load(b"Exif\x00\x00")
776
        assert not dict(exif)
777

778
    @mark_if_feature_version(
779
        pytest.mark.valgrind_known_error, "libjpeg_turbo", "2.0", reason="Known Failing"
780
    )
781
    def test_exif_jpeg(self, tmp_path: Path) -> None:
782
        with Image.open("Tests/images/exif-72dpi-int.jpg") as im:  # Little endian
783
            exif = im.getexif()
784
            assert 258 not in exif
785
            assert 274 in exif
786
            assert 282 in exif
787
            assert exif[296] == 2
788
            assert exif[11] == "gThumb 3.0.1"
789

790
            out = str(tmp_path / "temp.jpg")
791
            exif[258] = 8
792
            del exif[274]
793
            del exif[282]
794
            exif[296] = 455
795
            exif[11] = "Pillow test"
796
            im.save(out, exif=exif)
797
        with Image.open(out) as reloaded:
798
            reloaded_exif = reloaded.getexif()
799
            assert reloaded_exif[258] == 8
800
            assert 274 not in reloaded_exif
801
            assert 282 not in reloaded_exif
802
            assert reloaded_exif[296] == 455
803
            assert reloaded_exif[11] == "Pillow test"
804

805
        with Image.open("Tests/images/no-dpi-in-exif.jpg") as im:  # Big endian
806
            exif = im.getexif()
807
            assert 258 not in exif
808
            assert 306 in exif
809
            assert exif[274] == 1
810
            assert exif[305] == "Adobe Photoshop CC 2017 (Macintosh)"
811

812
            out = str(tmp_path / "temp.jpg")
813
            exif[258] = 8
814
            del exif[306]
815
            exif[274] = 455
816
            exif[305] = "Pillow test"
817
            im.save(out, exif=exif)
818
        with Image.open(out) as reloaded:
819
            reloaded_exif = reloaded.getexif()
820
            assert reloaded_exif[258] == 8
821
            assert 306 not in reloaded_exif
822
            assert reloaded_exif[274] == 455
823
            assert reloaded_exif[305] == "Pillow test"
824

825
    @skip_unless_feature("webp")
826
    def test_exif_webp(self, tmp_path: Path) -> None:
827
        with Image.open("Tests/images/hopper.webp") as im:
828
            exif = im.getexif()
829
            assert exif == {}
830

831
            out = str(tmp_path / "temp.webp")
832
            exif[258] = 8
833
            exif[40963] = 455
834
            exif[305] = "Pillow test"
835

836
            def check_exif() -> None:
837
                with Image.open(out) as reloaded:
838
                    reloaded_exif = reloaded.getexif()
839
                    assert reloaded_exif[258] == 8
840
                    assert reloaded_exif[40963] == 455
841
                    assert reloaded_exif[305] == "Pillow test"
842

843
            im.save(out, exif=exif)
844
            check_exif()
845
            im.save(out, exif=exif, save_all=True)
846
            check_exif()
847

848
    def test_exif_png(self, tmp_path: Path) -> None:
849
        with Image.open("Tests/images/exif.png") as im:
850
            exif = im.getexif()
851
            assert exif == {274: 1}
852

853
            out = str(tmp_path / "temp.png")
854
            exif[258] = 8
855
            del exif[274]
856
            exif[40963] = 455
857
            exif[305] = "Pillow test"
858
            im.save(out, exif=exif)
859

860
        with Image.open(out) as reloaded:
861
            reloaded_exif = reloaded.getexif()
862
            assert reloaded_exif == {258: 8, 40963: 455, 305: "Pillow test"}
863

864
    def test_exif_interop(self) -> None:
865
        with Image.open("Tests/images/flower.jpg") as im:
866
            exif = im.getexif()
867
            assert exif.get_ifd(0xA005) == {
868
                1: "R98",
869
                2: b"0100",
870
                4097: 2272,
871
                4098: 1704,
872
            }
873

874
            reloaded_exif = Image.Exif()
875
            reloaded_exif.load(exif.tobytes())
876
            assert reloaded_exif.get_ifd(0xA005) == exif.get_ifd(0xA005)
877

878
    def test_exif_ifd1(self) -> None:
879
        with Image.open("Tests/images/flower.jpg") as im:
880
            exif = im.getexif()
881
            assert exif.get_ifd(ExifTags.IFD.IFD1) == {
882
                513: 2036,
883
                514: 5448,
884
                259: 6,
885
                296: 2,
886
                282: 180.0,
887
                283: 180.0,
888
            }
889

890
    def test_exif_ifd(self) -> None:
891
        with Image.open("Tests/images/flower.jpg") as im:
892
            exif = im.getexif()
893
        del exif.get_ifd(0x8769)[0xA005]
894

895
        reloaded_exif = Image.Exif()
896
        reloaded_exif.load(exif.tobytes())
897
        assert reloaded_exif.get_ifd(0x8769) == exif.get_ifd(0x8769)
898

899
    def test_exif_load_from_fp(self) -> None:
900
        with Image.open("Tests/images/flower.jpg") as im:
901
            data = im.info["exif"]
902
            if data.startswith(b"Exif\x00\x00"):
903
                data = data[6:]
904
            fp = io.BytesIO(data)
905

906
            exif = Image.Exif()
907
            exif.load_from_fp(fp)
908
            assert exif == {
909
                271: "Canon",
910
                272: "Canon PowerShot S40",
911
                274: 1,
912
                282: 180.0,
913
                283: 180.0,
914
                296: 2,
915
                306: "2003:12:14 12:01:44",
916
                531: 1,
917
                34665: 196,
918
            }
919

920
    def test_exif_hide_offsets(self) -> None:
921
        with Image.open("Tests/images/flower.jpg") as im:
922
            exif = im.getexif()
923

924
        # Check offsets are present initially
925
        assert 0x8769 in exif
926
        for tag in (0xA005, 0x927C):
927
            assert tag in exif.get_ifd(0x8769)
928
        assert exif.get_ifd(0xA005)
929
        loaded_exif = exif
930

931
        with Image.open("Tests/images/flower.jpg") as im:
932
            new_exif = im.getexif()
933

934
            for exif in (loaded_exif, new_exif):
935
                exif.hide_offsets()
936

937
                # Assert they are hidden afterwards,
938
                # but that the IFDs are still available
939
                assert 0x8769 not in exif
940
                assert exif.get_ifd(0x8769)
941
                for tag in (0xA005, 0x927C):
942
                    assert tag not in exif.get_ifd(0x8769)
943
                assert exif.get_ifd(0xA005)
944

945
    def test_empty_xmp(self) -> None:
946
        with Image.open("Tests/images/hopper.gif") as im:
947
            if ElementTree is None:
948
                with pytest.warns(
949
                    UserWarning,
950
                    match="XMP data cannot be read without defusedxml dependency",
951
                ):
952
                    xmp = im.getxmp()
953
            else:
954
                xmp = im.getxmp()
955
            assert xmp == {}
956

957
    def test_getxmp_padded(self) -> None:
958
        im = Image.new("RGB", (1, 1))
959
        im.info["xmp"] = (
960
            b'<?xpacket begin="\xef\xbb\xbf" id="W5M0MpCehiHzreSzNTczkc9d"?>\n'
961
            b'<x:xmpmeta xmlns:x="adobe:ns:meta/" />\n<?xpacket end="w"?>\x00\x00'
962
        )
963
        if ElementTree is None:
964
            with pytest.warns(
965
                UserWarning,
966
                match="XMP data cannot be read without defusedxml dependency",
967
            ):
968
                assert im.getxmp() == {}
969
        else:
970
            assert im.getxmp() == {"xmpmeta": None}
971

972
    @pytest.mark.parametrize("size", ((1, 0), (0, 1), (0, 0)))
973
    def test_zero_tobytes(self, size: tuple[int, int]) -> None:
974
        im = Image.new("RGB", size)
975
        assert im.tobytes() == b""
976

977
    @pytest.mark.parametrize("size", ((1, 0), (0, 1), (0, 0)))
978
    def test_zero_frombytes(self, size: tuple[int, int]) -> None:
979
        Image.frombytes("RGB", size, b"")
980

981
        im = Image.new("RGB", size)
982
        im.frombytes(b"")
983

984
    def test_has_transparency_data(self) -> None:
985
        for mode in ("1", "L", "P", "RGB"):
986
            im = Image.new(mode, (1, 1))
987
            assert not im.has_transparency_data
988

989
        for mode in ("LA", "La", "PA", "RGBA", "RGBa"):
990
            im = Image.new(mode, (1, 1))
991
            assert im.has_transparency_data
992

993
        # P mode with "transparency" info
994
        with Image.open("Tests/images/first_frame_transparency.gif") as im:
995
            assert "transparency" in im.info
996
            assert im.has_transparency_data
997

998
        # RGB mode with "transparency" info
999
        with Image.open("Tests/images/rgb_trns.png") as im:
1000
            assert "transparency" in im.info
1001
            assert im.has_transparency_data
1002

1003
        # P mode with RGBA palette
1004
        im = Image.new("RGBA", (1, 1)).convert("P")
1005
        assert im.mode == "P"
1006
        assert im.palette is not None
1007
        assert im.palette.mode == "RGBA"
1008
        assert im.has_transparency_data
1009

1010
    def test_apply_transparency(self) -> None:
1011
        im = Image.new("P", (1, 1))
1012
        im.putpalette((0, 0, 0, 1, 1, 1))
1013
        assert im.palette is not None
1014
        assert im.palette.colors == {(0, 0, 0): 0, (1, 1, 1): 1}
1015

1016
        # Test that no transformation is applied without transparency
1017
        im.apply_transparency()
1018
        assert im.palette.colors == {(0, 0, 0): 0, (1, 1, 1): 1}
1019

1020
        # Test that a transparency index is applied
1021
        im.info["transparency"] = 0
1022
        im.apply_transparency()
1023
        assert "transparency" not in im.info
1024
        assert im.palette.colors == {(0, 0, 0, 0): 0, (1, 1, 1, 255): 1}
1025

1026
        # Test that existing transparency is kept
1027
        im = Image.new("P", (1, 1))
1028
        im.putpalette((0, 0, 0, 255, 1, 1, 1, 128), "RGBA")
1029
        im.info["transparency"] = 0
1030
        im.apply_transparency()
1031
        assert im.palette is not None
1032
        assert im.palette.colors == {(0, 0, 0, 0): 0, (1, 1, 1, 128): 1}
1033

1034
        # Test that transparency bytes are applied
1035
        with Image.open("Tests/images/pil123p.png") as im:
1036
            assert isinstance(im.info["transparency"], bytes)
1037
            assert im.palette is not None
1038
            assert im.palette.colors[(27, 35, 6)] == 24
1039
            im.apply_transparency()
1040
            assert im.palette is not None
1041
            assert im.palette.colors[(27, 35, 6, 214)] == 24
1042

1043
    def test_constants(self) -> None:
1044
        for enum in (
1045
            Image.Transpose,
1046
            Image.Transform,
1047
            Image.Resampling,
1048
            Image.Dither,
1049
            Image.Palette,
1050
            Image.Quantize,
1051
        ):
1052
            for name in enum.__members__:
1053
                assert getattr(Image, name) == enum[name]
1054

1055
    @pytest.mark.parametrize(
1056
        "path",
1057
        [
1058
            "fli_overrun.bin",
1059
            "sgi_overrun.bin",
1060
            "sgi_overrun_expandrow.bin",
1061
            "sgi_overrun_expandrow2.bin",
1062
            "pcx_overrun.bin",
1063
            "pcx_overrun2.bin",
1064
            "ossfuzz-4836216264589312.pcx",
1065
            "01r_00.pcx",
1066
        ],
1067
    )
1068
    def test_overrun(self, path: str) -> None:
1069
        """For overrun completeness, test as:
1070
        valgrind pytest -qq Tests/test_image.py::TestImage::test_overrun | grep decode.c
1071
        """
1072
        with Image.open(os.path.join("Tests/images", path)) as im:
1073
            try:
1074
                im.load()
1075
                pytest.fail()
1076
            except OSError as e:
1077
                buffer_overrun = str(e) == "buffer overrun when reading image file"
1078
                truncated = "image file is truncated" in str(e)
1079

1080
                assert buffer_overrun or truncated
1081

1082
    def test_fli_overrun2(self) -> None:
1083
        with Image.open("Tests/images/fli_overrun2.bin") as im:
1084
            try:
1085
                im.seek(1)
1086
                pytest.fail()
1087
            except OSError as e:
1088
                assert str(e) == "buffer overrun when reading image file"
1089

1090
    def test_exit_fp(self) -> None:
1091
        with Image.new("L", (1, 1)) as im:
1092
            pass
1093
        assert not hasattr(im, "fp")
1094

1095
    def test_close_graceful(self, caplog: pytest.LogCaptureFixture) -> None:
1096
        with Image.open("Tests/images/hopper.jpg") as im:
1097
            copy = im.copy()
1098
            with caplog.at_level(logging.DEBUG):
1099
                im.close()
1100
                copy.close()
1101
            assert len(caplog.records) == 0
1102
            assert im.fp is None
1103

1104

1105
class TestImageBytes:
1106
    @pytest.mark.parametrize("mode", Image.MODES + ["BGR;15", "BGR;16", "BGR;24"])
1107
    def test_roundtrip_bytes_constructor(self, mode: str) -> None:
1108
        im = hopper(mode)
1109
        source_bytes = im.tobytes()
1110

1111
        if mode.startswith("BGR;"):
1112
            with pytest.warns(DeprecationWarning):
1113
                reloaded = Image.frombytes(mode, im.size, source_bytes)
1114
        else:
1115
            reloaded = Image.frombytes(mode, im.size, source_bytes)
1116
        assert reloaded.tobytes() == source_bytes
1117

1118
    @pytest.mark.parametrize("mode", Image.MODES + ["BGR;15", "BGR;16", "BGR;24"])
1119
    def test_roundtrip_bytes_method(self, mode: str) -> None:
1120
        im = hopper(mode)
1121
        source_bytes = im.tobytes()
1122

1123
        reloaded = helper_image_new(mode, im.size)
1124
        reloaded.frombytes(source_bytes)
1125
        assert reloaded.tobytes() == source_bytes
1126

1127
    @pytest.mark.parametrize("mode", Image.MODES + ["BGR;15", "BGR;16", "BGR;24"])
1128
    def test_getdata_putdata(self, mode: str) -> None:
1129
        if is_big_endian() and mode == "BGR;15":
1130
            pytest.xfail("Known failure of BGR;15 on big-endian")
1131
        im = hopper(mode)
1132
        reloaded = helper_image_new(mode, im.size)
1133
        reloaded.putdata(im.getdata())
1134
        assert_image_equal(im, reloaded)
1135

1136

1137
class MockEncoder(ImageFile.PyEncoder):
1138
    pass
1139

1140

1141
class TestRegistry:
1142
    def test_encode_registry(self) -> None:
1143
        Image.register_encoder("MOCK", MockEncoder)
1144
        assert "MOCK" in Image.ENCODERS
1145

1146
        enc = Image._getencoder("RGB", "MOCK", ("args",), extra=("extra",))
1147

1148
        assert isinstance(enc, MockEncoder)
1149
        assert enc.mode == "RGB"
1150
        assert enc.args == ("args", "extra")
1151

1152
    def test_encode_registry_fail(self) -> None:
1153
        with pytest.raises(OSError):
1154
            Image._getencoder("RGB", "DoesNotExist", ("args",), extra=("extra",))
1155

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

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

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

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