Pillow

Форк
0
/
test_imagecms.py 
711 строк · 23.4 Кб
1
from __future__ import annotations
2

3
import datetime
4
import os
5
import re
6
import shutil
7
import sys
8
from io import BytesIO
9
from pathlib import Path
10
from typing import Any, Literal, cast
11

12
import pytest
13

14
from PIL import Image, ImageMode, ImageWin, features
15

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

25
try:
26
    from PIL import ImageCms
27
    from PIL.ImageCms import ImageCmsProfile
28

29
    ImageCms.core.profile_open
30
except ImportError:
31
    # Skipped via setup_module()
32
    pass
33

34

35
SRGB = "Tests/icc/sRGB_IEC61966-2-1_black_scaled.icc"
36
HAVE_PROFILE = os.path.exists(SRGB)
37

38

39
def setup_module() -> None:
40
    try:
41
        from PIL import ImageCms
42

43
        # need to hit getattr to trigger the delayed import error
44
        ImageCms.core.profile_open
45
    except ImportError as v:
46
        pytest.skip(str(v))
47

48

49
def skip_missing() -> None:
50
    if not HAVE_PROFILE:
51
        pytest.skip("SRGB profile not available")
52

53

54
def test_sanity() -> None:
55
    # basic smoke test.
56
    # this mostly follows the cms_test outline.
57
    with pytest.warns(DeprecationWarning):
58
        v = ImageCms.versions()  # should return four strings
59
    assert v[0] == "1.0.0 pil"
60
    assert list(map(type, v)) == [str, str, str, str]
61

62
    # internal version number
63
    version = features.version_module("littlecms2")
64
    assert version is not None
65
    assert re.search(r"\d+\.\d+(\.\d+)?$", version)
66

67
    skip_missing()
68
    i = ImageCms.profileToProfile(hopper(), SRGB, SRGB)
69
    assert i is not None
70
    assert_image(i, "RGB", (128, 128))
71

72
    i = hopper()
73
    ImageCms.profileToProfile(i, SRGB, SRGB, inPlace=True)
74
    assert_image(i, "RGB", (128, 128))
75

76
    t = ImageCms.buildTransform(SRGB, SRGB, "RGB", "RGB")
77
    i = ImageCms.applyTransform(hopper(), t)
78
    assert i is not None
79
    assert_image(i, "RGB", (128, 128))
80

81
    with hopper() as i:
82
        t = ImageCms.buildTransform(SRGB, SRGB, "RGB", "RGB")
83
        ImageCms.applyTransform(hopper(), t, inPlace=True)
84
        assert i is not None
85
        assert_image(i, "RGB", (128, 128))
86

87
    p = ImageCms.createProfile("sRGB")
88
    o = ImageCms.getOpenProfile(SRGB)
89
    t = ImageCms.buildTransformFromOpenProfiles(p, o, "RGB", "RGB")
90
    i = ImageCms.applyTransform(hopper(), t)
91
    assert i is not None
92
    assert_image(i, "RGB", (128, 128))
93

94
    t = ImageCms.buildProofTransform(SRGB, SRGB, SRGB, "RGB", "RGB")
95
    assert t.inputMode == "RGB"
96
    assert t.outputMode == "RGB"
97
    i = ImageCms.applyTransform(hopper(), t)
98
    assert i is not None
99
    assert_image(i, "RGB", (128, 128))
100

101
    # test PointTransform convenience API
102
    hopper().point(t)
103

104

105
def test_flags() -> None:
106
    assert ImageCms.Flags.NONE.value == 0
107
    assert ImageCms.Flags.GRIDPOINTS(0) == ImageCms.Flags.NONE
108
    assert ImageCms.Flags.GRIDPOINTS(256) == ImageCms.Flags.NONE
109

110
    assert ImageCms.Flags.GRIDPOINTS(255) == (255 << 16)
111
    assert ImageCms.Flags.GRIDPOINTS(-1) == ImageCms.Flags.GRIDPOINTS(255)
112
    assert ImageCms.Flags.GRIDPOINTS(511) == ImageCms.Flags.GRIDPOINTS(255)
113

114

115
def test_name() -> None:
116
    skip_missing()
117
    # get profile information for file
118
    assert (
119
        ImageCms.getProfileName(SRGB).strip()
120
        == "IEC 61966-2-1 Default RGB Colour Space - sRGB"
121
    )
122

123

124
def test_info() -> None:
125
    skip_missing()
126
    assert ImageCms.getProfileInfo(SRGB).splitlines() == [
127
        "sRGB IEC61966-2-1 black scaled",
128
        "",
129
        "Copyright International Color Consortium, 2009",
130
        "",
131
    ]
132

133

134
def test_copyright() -> None:
135
    skip_missing()
136
    assert (
137
        ImageCms.getProfileCopyright(SRGB).strip()
138
        == "Copyright International Color Consortium, 2009"
139
    )
140

141

142
def test_manufacturer() -> None:
143
    skip_missing()
144
    assert ImageCms.getProfileManufacturer(SRGB).strip() == ""
145

146

147
def test_model() -> None:
148
    skip_missing()
149
    assert (
150
        ImageCms.getProfileModel(SRGB).strip()
151
        == "IEC 61966-2-1 Default RGB Colour Space - sRGB"
152
    )
153

154

155
def test_description() -> None:
156
    skip_missing()
157
    assert (
158
        ImageCms.getProfileDescription(SRGB).strip() == "sRGB IEC61966-2-1 black scaled"
159
    )
160

161

162
def test_intent() -> None:
163
    skip_missing()
164
    assert ImageCms.getDefaultIntent(SRGB) == 0
165
    support = ImageCms.isIntentSupported(
166
        SRGB, ImageCms.Intent.ABSOLUTE_COLORIMETRIC, ImageCms.Direction.INPUT
167
    )
168
    assert support == 1
169

170

171
def test_profile_object() -> None:
172
    # same, using profile object
173
    p = ImageCms.createProfile("sRGB")
174
    # assert ImageCms.getProfileName(p).strip() == "sRGB built-in - (lcms internal)"
175
    # assert ImageCms.getProfileInfo(p).splitlines() ==
176
    #     ["sRGB built-in", "", "WhitePoint : D65 (daylight)", "", ""]
177
    assert ImageCms.getDefaultIntent(p) == 0
178
    support = ImageCms.isIntentSupported(
179
        p, ImageCms.Intent.ABSOLUTE_COLORIMETRIC, ImageCms.Direction.INPUT
180
    )
181
    assert support == 1
182

183

184
def test_extensions() -> None:
185
    # extensions
186

187
    with Image.open("Tests/images/rgb.jpg") as i:
188
        p = ImageCms.getOpenProfile(BytesIO(i.info["icc_profile"]))
189
    assert (
190
        ImageCms.getProfileName(p).strip()
191
        == "IEC 61966-2.1 Default RGB colour space - sRGB"
192
    )
193

194

195
def test_exceptions() -> None:
196
    # Test mode mismatch
197
    psRGB = ImageCms.createProfile("sRGB")
198
    pLab = ImageCms.createProfile("LAB")
199
    t = ImageCms.buildTransform(pLab, psRGB, "LAB", "RGB")
200
    with pytest.raises(ValueError, match="mode mismatch"):
201
        t.apply_in_place(hopper("RGBA"))
202

203
    # the procedural pyCMS API uses PyCMSError for all sorts of errors
204
    with hopper() as im:
205
        with pytest.raises(ImageCms.PyCMSError, match="cannot open profile file"):
206
            ImageCms.profileToProfile(im, "foo", "bar")
207

208
    with pytest.raises(ImageCms.PyCMSError, match="cannot open profile file"):
209
        ImageCms.buildTransform("foo", "bar", "RGB", "RGB")
210

211
    with pytest.raises(ImageCms.PyCMSError, match="Invalid type for Profile"):
212
        ImageCms.getProfileName(None)  # type: ignore[arg-type]
213
    skip_missing()
214

215
    # Python <= 3.9: "an integer is required (got type NoneType)"
216
    # Python > 3.9: "'NoneType' object cannot be interpreted as an integer"
217
    with pytest.raises(ImageCms.PyCMSError, match="integer"):
218
        ImageCms.isIntentSupported(SRGB, None, None)  # type: ignore[arg-type]
219

220

221
def test_display_profile() -> None:
222
    # try fetching the profile for the current display device
223
    ImageCms.get_display_profile()
224

225
    if sys.platform == "win32":
226
        ImageCms.get_display_profile(ImageWin.HDC(0))
227
        ImageCms.get_display_profile(ImageWin.HWND(0))
228

229

230
def test_lab_color_profile() -> None:
231
    ImageCms.createProfile("LAB", 5000)
232
    ImageCms.createProfile("LAB", 6500)
233

234

235
def test_unsupported_color_space() -> None:
236
    with pytest.raises(
237
        ImageCms.PyCMSError,
238
        match=re.escape(
239
            "Color space not supported for on-the-fly profile creation (unsupported)"
240
        ),
241
    ):
242
        ImageCms.createProfile("unsupported")  # type: ignore[arg-type]
243

244

245
def test_invalid_color_temperature() -> None:
246
    with pytest.raises(
247
        ImageCms.PyCMSError,
248
        match='Color temperature must be numeric, "invalid" not valid',
249
    ):
250
        ImageCms.createProfile("LAB", "invalid")  # type: ignore[arg-type]
251

252

253
@pytest.mark.parametrize("flag", ("my string", -1))
254
def test_invalid_flag(flag: str | int) -> None:
255
    with hopper() as im:
256
        with pytest.raises(
257
            ImageCms.PyCMSError, match="flags must be an integer between 0 and "
258
        ):
259
            ImageCms.profileToProfile(im, "foo", "bar", flags=flag)  # type: ignore[arg-type]
260

261

262
def test_simple_lab() -> None:
263
    i = Image.new("RGB", (10, 10), (128, 128, 128))
264

265
    psRGB = ImageCms.createProfile("sRGB")
266
    pLab = ImageCms.createProfile("LAB")
267
    t = ImageCms.buildTransform(psRGB, pLab, "RGB", "LAB")
268

269
    i_lab = ImageCms.applyTransform(i, t)
270
    assert i_lab is not None
271
    assert i_lab.mode == "LAB"
272

273
    k = i_lab.getpixel((0, 0))
274
    # not a linear luminance map. so L != 128:
275
    assert k == (137, 128, 128)
276

277
    l_data = i_lab.getdata(0)
278
    a_data = i_lab.getdata(1)
279
    b_data = i_lab.getdata(2)
280

281
    assert list(l_data) == [137] * 100
282
    assert list(a_data) == [128] * 100
283
    assert list(b_data) == [128] * 100
284

285

286
def test_lab_color() -> None:
287
    psRGB = ImageCms.createProfile("sRGB")
288
    pLab = ImageCms.createProfile("LAB")
289
    t = ImageCms.buildTransform(psRGB, pLab, "RGB", "LAB")
290

291
    # Need to add a type mapping for some PIL type to TYPE_Lab_8 in findLCMSType, and
292
    # have that mapping work back to a PIL mode (likely RGB).
293
    i = ImageCms.applyTransform(hopper(), t)
294
    assert i is not None
295
    assert_image(i, "LAB", (128, 128))
296

297
    # i.save('temp.lab.tif')  # visually verified vs PS.
298

299
    assert_image_similar_tofile(i, "Tests/images/hopper.Lab.tif", 3.5)
300

301

302
def test_lab_srgb() -> None:
303
    psRGB = ImageCms.createProfile("sRGB")
304
    pLab = ImageCms.createProfile("LAB")
305
    t = ImageCms.buildTransform(pLab, psRGB, "LAB", "RGB")
306

307
    with Image.open("Tests/images/hopper.Lab.tif") as img:
308
        img_srgb = ImageCms.applyTransform(img, t)
309
    assert img_srgb is not None
310

311
    # img_srgb.save('temp.srgb.tif') # visually verified vs ps.
312

313
    assert_image_similar(hopper(), img_srgb, 30)
314
    assert img_srgb.info["icc_profile"]
315

316
    profile = ImageCmsProfile(BytesIO(img_srgb.info["icc_profile"]))
317
    assert "sRGB" in ImageCms.getProfileDescription(profile)
318

319

320
def test_lab_roundtrip() -> None:
321
    # check to see if we're at least internally consistent.
322
    psRGB = ImageCms.createProfile("sRGB")
323
    pLab = ImageCms.createProfile("LAB")
324
    t = ImageCms.buildTransform(psRGB, pLab, "RGB", "LAB")
325

326
    t2 = ImageCms.buildTransform(pLab, psRGB, "LAB", "RGB")
327

328
    i = ImageCms.applyTransform(hopper(), t)
329
    assert i is not None
330
    assert i.info["icc_profile"] == ImageCmsProfile(pLab).tobytes()
331

332
    out = ImageCms.applyTransform(i, t2)
333
    assert out is not None
334
    assert_image_similar(hopper(), out, 2)
335

336

337
def test_profile_tobytes() -> None:
338
    with Image.open("Tests/images/rgb.jpg") as i:
339
        p = ImageCms.getOpenProfile(BytesIO(i.info["icc_profile"]))
340

341
    p2 = ImageCms.getOpenProfile(BytesIO(p.tobytes()))
342

343
    # not the same bytes as the original icc_profile, but it does roundtrip
344
    assert p.tobytes() == p2.tobytes()
345
    assert ImageCms.getProfileName(p) == ImageCms.getProfileName(p2)
346
    assert ImageCms.getProfileDescription(p) == ImageCms.getProfileDescription(p2)
347

348

349
def test_extended_information() -> None:
350
    skip_missing()
351
    o = ImageCms.getOpenProfile(SRGB)
352
    p = o.profile
353

354
    def assert_truncated_tuple_equal(
355
        tup1: tuple[Any, ...] | None, tup2: tuple[Any, ...], digits: int = 10
356
    ) -> None:
357
        # Helper function to reduce precision of tuples of floats
358
        # recursively and then check equality.
359
        power = 10**digits
360

361
        def truncate_tuple(tuple_value: tuple[Any, ...]) -> tuple[Any, ...]:
362
            return tuple(
363
                (
364
                    truncate_tuple(val)
365
                    if isinstance(val, tuple)
366
                    else int(val * power) / power
367
                )
368
                for val in tuple_value
369
            )
370

371
        assert tup1 is not None
372
        assert truncate_tuple(tup1) == truncate_tuple(tup2)
373

374
    assert p.attributes == 4294967296
375
    assert_truncated_tuple_equal(
376
        p.blue_colorant,
377
        (
378
            (0.14306640625, 0.06060791015625, 0.7140960693359375),
379
            (0.1558847490315394, 0.06603820639433387, 0.06060791015625),
380
        ),
381
    )
382
    assert_truncated_tuple_equal(
383
        p.blue_primary,
384
        (
385
            (0.14306641366715667, 0.06060790921083026, 0.7140960805782015),
386
            (0.15588475410450106, 0.06603820408959558, 0.06060790921083026),
387
        ),
388
    )
389
    assert_truncated_tuple_equal(
390
        p.chromatic_adaptation,
391
        (
392
            (
393
                (1.04791259765625, 0.0229339599609375, -0.050201416015625),
394
                (0.02960205078125, 0.9904632568359375, -0.0170745849609375),
395
                (-0.009246826171875, 0.0150604248046875, 0.7517852783203125),
396
            ),
397
            (
398
                (1.0267159024652783, 0.022470062342089134, 0.0229339599609375),
399
                (0.02951378324103937, 0.9875098886387147, 0.9904632568359375),
400
                (-0.012205438066465256, 0.01987915407854985, 0.0150604248046875),
401
            ),
402
        ),
403
    )
404
    assert p.chromaticity is None
405
    assert p.clut == {
406
        0: (False, False, True),
407
        1: (False, False, True),
408
        2: (False, False, True),
409
        3: (False, False, True),
410
    }
411

412
    assert p.colorant_table is None
413
    assert p.colorant_table_out is None
414
    assert p.colorimetric_intent is None
415
    assert p.connection_space == "XYZ "
416
    assert p.copyright == "Copyright International Color Consortium, 2009"
417
    assert p.creation_date == datetime.datetime(2009, 2, 27, 21, 36, 31)
418
    assert p.device_class == "mntr"
419
    assert_truncated_tuple_equal(
420
        p.green_colorant,
421
        (
422
            (0.3851470947265625, 0.7168731689453125, 0.097076416015625),
423
            (0.32119769927720654, 0.5978443449048152, 0.7168731689453125),
424
        ),
425
    )
426
    assert_truncated_tuple_equal(
427
        p.green_primary,
428
        (
429
            (0.3851470888162112, 0.7168731974161346, 0.09707641738998518),
430
            (0.32119768793686687, 0.5978443567149709, 0.7168731974161346),
431
        ),
432
    )
433
    assert p.header_flags == 0
434
    assert p.header_manufacturer == "\x00\x00\x00\x00"
435
    assert p.header_model == "\x00\x00\x00\x00"
436
    assert p.icc_measurement_condition == {
437
        "backing": (0.0, 0.0, 0.0),
438
        "flare": 0.0,
439
        "geo": "unknown",
440
        "observer": 1,
441
        "illuminant_type": "D65",
442
    }
443
    assert p.icc_version == 33554432
444
    assert p.icc_viewing_condition is None
445
    assert p.intent_supported == {
446
        0: (True, True, True),
447
        1: (True, True, True),
448
        2: (True, True, True),
449
        3: (True, True, True),
450
    }
451
    assert p.is_matrix_shaper
452
    assert p.luminance == ((0.0, 80.0, 0.0), (0.0, 1.0, 80.0))
453
    assert p.manufacturer is None
454
    assert_truncated_tuple_equal(
455
        p.media_black_point,
456
        (
457
            (0.012054443359375, 0.0124969482421875, 0.01031494140625),
458
            (0.34573304157549234, 0.35842450765864337, 0.0124969482421875),
459
        ),
460
    )
461
    assert_truncated_tuple_equal(
462
        p.media_white_point,
463
        (
464
            (0.964202880859375, 1.0, 0.8249053955078125),
465
            (0.3457029219802284, 0.3585375327567059, 1.0),
466
        ),
467
    )
468
    assert_truncated_tuple_equal(
469
        (p.media_white_point_temperature,), (5000.722328847392,)
470
    )
471
    assert p.model == "IEC 61966-2-1 Default RGB Colour Space - sRGB"
472

473
    assert p.perceptual_rendering_intent_gamut is None
474

475
    assert p.profile_description == "sRGB IEC61966-2-1 black scaled"
476
    assert p.profile_id == b")\xf8=\xde\xaf\xf2U\xaexB\xfa\xe4\xca\x839\r"
477
    assert_truncated_tuple_equal(
478
        p.red_colorant,
479
        (
480
            (0.436065673828125, 0.2224884033203125, 0.013916015625),
481
            (0.6484536316398539, 0.3308524880306778, 0.2224884033203125),
482
        ),
483
    )
484
    assert_truncated_tuple_equal(
485
        p.red_primary,
486
        (
487
            (0.43606566581047446, 0.22248840582960838, 0.013916015621759925),
488
            (0.6484536250319214, 0.3308524944738204, 0.22248840582960838),
489
        ),
490
    )
491
    assert p.rendering_intent == 0
492
    assert p.saturation_rendering_intent_gamut is None
493
    assert p.screening_description is None
494
    assert p.target is None
495
    assert p.technology == "CRT "
496
    assert p.version == 2.0
497
    assert p.viewing_condition == "Reference Viewing Condition in IEC 61966-2-1"
498
    assert p.xcolor_space == "RGB "
499

500

501
def test_non_ascii_path(tmp_path: Path) -> None:
502
    skip_missing()
503
    tempfile = str(tmp_path / ("temp_" + chr(128) + ".icc"))
504
    try:
505
        shutil.copy(SRGB, tempfile)
506
    except UnicodeEncodeError:
507
        pytest.skip("Non-ASCII path could not be created")
508

509
    o = ImageCms.getOpenProfile(tempfile)
510
    p = o.profile
511
    assert p.model == "IEC 61966-2-1 Default RGB Colour Space - sRGB"
512

513

514
def test_profile_typesafety() -> None:
515
    # does not segfault
516
    with pytest.raises(TypeError, match="Invalid type for Profile"):
517
        ImageCms.ImageCmsProfile(0)  # type: ignore[arg-type]
518
    with pytest.raises(TypeError, match="Invalid type for Profile"):
519
        ImageCms.ImageCmsProfile(1)  # type: ignore[arg-type]
520

521
    # also check core function
522
    with pytest.raises(TypeError):
523
        ImageCms.core.profile_tobytes(0)  # type: ignore[arg-type]
524
    with pytest.raises(TypeError):
525
        ImageCms.core.profile_tobytes(1)  # type: ignore[arg-type]
526

527
    if not is_pypy():
528
        # core profile should not be directly instantiable
529
        with pytest.raises(TypeError):
530
            ImageCms.core.CmsProfile()
531
        with pytest.raises(TypeError):
532
            ImageCms.core.CmsProfile(0)  # type: ignore[call-arg]
533

534

535
@pytest.mark.skipif(is_pypy(), reason="fails on PyPy")
536
def test_transform_typesafety() -> None:
537
    # core transform should not be directly instantiable
538
    with pytest.raises(TypeError):
539
        ImageCms.core.CmsTransform()
540
    with pytest.raises(TypeError):
541
        ImageCms.core.CmsTransform(0)  # type: ignore[call-arg]
542

543

544
def assert_aux_channel_preserved(
545
    mode: str, transform_in_place: bool, preserved_channel: str
546
) -> None:
547
    def create_test_image() -> Image.Image:
548
        # set up test image with something interesting in the tested aux channel.
549
        # fmt: off
550
        nine_grid_deltas = [
551
            (-1, -1), (-1, 0), (-1, 1),
552
            (0,  -1),  (0, 0),  (0, 1),
553
            (1,  -1),  (1, 0),  (1, 1),
554
        ]
555
        # fmt: on
556
        chans = []
557
        bands = ImageMode.getmode(mode).bands
558
        for band_ndx in range(len(bands)):
559
            channel_type = "L"  # 8-bit unorm
560
            channel_pattern = hopper(channel_type)
561

562
            # paste pattern with varying offsets to avoid correlation
563
            # potentially hiding some bugs (like channels getting mixed).
564
            paste_offset = (
565
                int(band_ndx / len(bands) * channel_pattern.size[0]),
566
                int(band_ndx / (len(bands) * 2) * channel_pattern.size[1]),
567
            )
568
            channel_data = Image.new(channel_type, channel_pattern.size)
569
            for delta in nine_grid_deltas:
570
                channel_data.paste(
571
                    channel_pattern,
572
                    (
573
                        paste_offset[0] + delta[0] * channel_pattern.size[0],
574
                        paste_offset[1] + delta[1] * channel_pattern.size[1],
575
                    ),
576
                )
577
            chans.append(channel_data)
578
        return Image.merge(mode, chans)
579

580
    source_image = create_test_image()
581
    source_image_aux = source_image.getchannel(preserved_channel)
582

583
    # create some transform, it doesn't matter which one
584
    source_profile = ImageCms.createProfile("sRGB")
585
    destination_profile = ImageCms.createProfile("sRGB")
586
    t = ImageCms.buildTransform(
587
        source_profile, destination_profile, inMode=mode, outMode=mode
588
    )
589

590
    # apply transform
591
    result_image: Image.Image | None
592
    if transform_in_place:
593
        ImageCms.applyTransform(source_image, t, inPlace=True)
594
        result_image = source_image
595
    else:
596
        result_image = ImageCms.applyTransform(source_image, t, inPlace=False)
597
    assert result_image is not None
598
    result_image_aux = result_image.getchannel(preserved_channel)
599

600
    assert_image_equal(source_image_aux, result_image_aux)
601

602

603
def test_preserve_auxiliary_channels_rgba() -> None:
604
    assert_aux_channel_preserved(
605
        mode="RGBA", transform_in_place=False, preserved_channel="A"
606
    )
607

608

609
def test_preserve_auxiliary_channels_rgba_in_place() -> None:
610
    assert_aux_channel_preserved(
611
        mode="RGBA", transform_in_place=True, preserved_channel="A"
612
    )
613

614

615
def test_preserve_auxiliary_channels_rgbx() -> None:
616
    assert_aux_channel_preserved(
617
        mode="RGBX", transform_in_place=False, preserved_channel="X"
618
    )
619

620

621
def test_preserve_auxiliary_channels_rgbx_in_place() -> None:
622
    assert_aux_channel_preserved(
623
        mode="RGBX", transform_in_place=True, preserved_channel="X"
624
    )
625

626

627
def test_auxiliary_channels_isolated() -> None:
628
    # test data in aux channels does not affect non-aux channels
629
    aux_channel_formats = [
630
        # format, profile, color-only format, source test image
631
        ("RGBA", "sRGB", "RGB", hopper("RGBA")),
632
        ("RGBX", "sRGB", "RGB", hopper("RGBX")),
633
        ("LAB", "LAB", "LAB", Image.open("Tests/images/hopper.Lab.tif")),
634
    ]
635
    for src_format in aux_channel_formats:
636
        for dst_format in aux_channel_formats:
637
            for transform_in_place in [True, False]:
638
                # inplace only if format doesn't change
639
                if transform_in_place and src_format[0] != dst_format[0]:
640
                    continue
641

642
                # convert with and without AUX data, test colors are equal
643
                src_colorSpace = cast(Literal["LAB", "XYZ", "sRGB"], src_format[1])
644
                source_profile = ImageCms.createProfile(src_colorSpace)
645
                dst_colorSpace = cast(Literal["LAB", "XYZ", "sRGB"], dst_format[1])
646
                destination_profile = ImageCms.createProfile(dst_colorSpace)
647
                source_image = src_format[3]
648
                test_transform = ImageCms.buildTransform(
649
                    source_profile,
650
                    destination_profile,
651
                    inMode=src_format[0],
652
                    outMode=dst_format[0],
653
                )
654

655
                # test conversion from aux-ful source
656
                test_image: Image.Image | None
657
                if transform_in_place:
658
                    test_image = source_image.copy()
659
                    ImageCms.applyTransform(test_image, test_transform, inPlace=True)
660
                else:
661
                    test_image = ImageCms.applyTransform(
662
                        source_image, test_transform, inPlace=False
663
                    )
664
                assert test_image is not None
665

666
                # reference conversion from aux-less source
667
                reference_transform = ImageCms.buildTransform(
668
                    source_profile,
669
                    destination_profile,
670
                    inMode=src_format[2],
671
                    outMode=dst_format[2],
672
                )
673
                reference_image = ImageCms.applyTransform(
674
                    source_image.convert(src_format[2]), reference_transform
675
                )
676
                assert reference_image is not None
677
                assert_image_equal(test_image.convert(dst_format[2]), reference_image)
678

679

680
def test_long_modes() -> None:
681
    p = ImageCms.getOpenProfile("Tests/icc/sGrey-v2-nano.icc")
682
    with pytest.warns(DeprecationWarning):
683
        ImageCms.buildTransform(p, p, "ABCDEFGHI", "ABCDEFGHI")
684

685

686
@pytest.mark.parametrize("mode", ("RGB", "RGBA", "RGBX"))
687
def test_rgb_lab(mode: str) -> None:
688
    im = Image.new(mode, (1, 1))
689
    converted_im = im.convert("LAB")
690
    assert converted_im.getpixel((0, 0)) == (0, 128, 128)
691

692
    im = Image.new("LAB", (1, 1), (255, 0, 0))
693
    converted_im = im.convert(mode)
694
    value = converted_im.getpixel((0, 0))
695
    assert isinstance(value, tuple)
696
    assert value[:3] == (0, 255, 255)
697

698

699
def test_deprecation() -> None:
700
    with pytest.warns(DeprecationWarning):
701
        assert ImageCms.DESCRIPTION.strip().startswith("pyCMS")
702
    with pytest.warns(DeprecationWarning):
703
        assert ImageCms.VERSION == "1.0.0 pil"
704
    with pytest.warns(DeprecationWarning):
705
        assert isinstance(ImageCms.FLAGS, dict)
706

707
    profile = ImageCmsProfile(ImageCms.createProfile("sRGB"))
708
    with pytest.warns(DeprecationWarning):
709
        ImageCms.ImageCmsTransform(profile, profile, "RGBA;16B", "RGB")
710
    with pytest.warns(DeprecationWarning):
711
        ImageCms.ImageCmsTransform(profile, profile, "RGB", "RGBA;16B")
712

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

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

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

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