CoolFace
Apppublic

Aluode/PerceptionLabPortable

sourceHugging Faceupdated 9mo agoView on Hugging Face
0likes
ImageCms.py1077 linesDownload Raw Back to PIL
1# The Python Imaging Library.
2# $Id$
3
4# Optional color management support, based on Kevin Cazabon's PyCMS
5# library.
6
7# Originally released under LGPL.  Graciously donated to PIL in
8# March 2009, for distribution under the standard PIL license
9
10# History:
11
12# 2009-03-08 fl   Added to PIL.
13
14# Copyright (C) 2002-2003 Kevin Cazabon
15# Copyright (c) 2009 by Fredrik Lundh
16# Copyright (c) 2013 by Eric Soroos
17
18# See the README file for information on usage and redistribution.  See
19# below for the original description.
20from __future__ import annotations
21
22import operator
23import sys
24from enum import IntEnum, IntFlag
25from functools import reduce
26from typing import Any, Literal, SupportsFloat, SupportsInt, Union
27
28from . import Image
29from ._deprecate import deprecate
30from ._typing import SupportsRead
31
32try:
33    from . import _imagingcms as core
34
35    _CmsProfileCompatible = Union[
36        str, SupportsRead[bytes], core.CmsProfile, "ImageCmsProfile"
37    ]
38except ImportError as ex:
39    # Allow error import for doc purposes, but error out when accessing
40    # anything in core.
41    from ._util import DeferredError
42
43    core = DeferredError.new(ex)
44
45_DESCRIPTION = """
46pyCMS
47
48    a Python / PIL interface to the littleCMS ICC Color Management System
49    Copyright (C) 2002-2003 Kevin Cazabon
50    kevin@cazabon.com
51    https://www.cazabon.com
52
53    pyCMS home page:  https://www.cazabon.com/pyCMS
54    littleCMS home page:  https://www.littlecms.com
55    (littleCMS is Copyright (C) 1998-2001 Marti Maria)
56
57    Originally released under LGPL.  Graciously donated to PIL in
58    March 2009, for distribution under the standard PIL license
59
60    The pyCMS.py module provides a "clean" interface between Python/PIL and
61    pyCMSdll, taking care of some of the more complex handling of the direct
62    pyCMSdll functions, as well as error-checking and making sure that all
63    relevant data is kept together.
64
65    While it is possible to call pyCMSdll functions directly, it's not highly
66    recommended.
67
68    Version History:
69
70        1.0.0 pil       Oct 2013 Port to LCMS 2.
71
72        0.1.0 pil mod   March 10, 2009
73
74                        Renamed display profile to proof profile. The proof
75                        profile is the profile of the device that is being
76                        simulated, not the profile of the device which is
77                        actually used to display/print the final simulation
78                        (that'd be the output profile) - also see LCMSAPI.txt
79                        input colorspace -> using 'renderingIntent' -> proof
80                        colorspace -> using 'proofRenderingIntent' -> output
81                        colorspace
82
83                        Added LCMS FLAGS support.
84                        Added FLAGS["SOFTPROOFING"] as default flag for
85                        buildProofTransform (otherwise the proof profile/intent
86                        would be ignored).
87
88        0.1.0 pil       March 2009 - added to PIL, as PIL.ImageCms
89
90        0.0.2 alpha     Jan 6, 2002
91
92                        Added try/except statements around type() checks of
93                        potential CObjects... Python won't let you use type()
94                        on them, and raises a TypeError (stupid, if you ask
95                        me!)
96
97                        Added buildProofTransformFromOpenProfiles() function.
98                        Additional fixes in DLL, see DLL code for details.
99
100        0.0.1 alpha     first public release, Dec. 26, 2002
101
102    Known to-do list with current version (of Python interface, not pyCMSdll):
103
104        none
105
106"""
107
108_VERSION = "1.0.0 pil"
109
110
111# --------------------------------------------------------------------.
112
113
114#
115# intent/direction values
116
117
118class Intent(IntEnum):
119    PERCEPTUAL = 0
120    RELATIVE_COLORIMETRIC = 1
121    SATURATION = 2
122    ABSOLUTE_COLORIMETRIC = 3
123
124
125class Direction(IntEnum):
126    INPUT = 0
127    OUTPUT = 1
128    PROOF = 2
129
130
131#
132# flags
133
134
135class Flags(IntFlag):
136    """Flags and documentation are taken from ``lcms2.h``."""
137
138    NONE = 0
139    NOCACHE = 0x0040
140    """Inhibit 1-pixel cache"""
141    NOOPTIMIZE = 0x0100
142    """Inhibit optimizations"""
143    NULLTRANSFORM = 0x0200
144    """Don't transform anyway"""
145    GAMUTCHECK = 0x1000
146    """Out of Gamut alarm"""
147    SOFTPROOFING = 0x4000
148    """Do softproofing"""
149    BLACKPOINTCOMPENSATION = 0x2000
150    NOWHITEONWHITEFIXUP = 0x0004
151    """Don't fix scum dot"""
152    HIGHRESPRECALC = 0x0400
153    """Use more memory to give better accuracy"""
154    LOWRESPRECALC = 0x0800
155    """Use less memory to minimize resources"""
156    # this should be 8BITS_DEVICELINK, but that is not a valid name in Python:
157    USE_8BITS_DEVICELINK = 0x0008
158    """Create 8 bits devicelinks"""
159    GUESSDEVICECLASS = 0x0020
160    """Guess device class (for ``transform2devicelink``)"""
161    KEEP_SEQUENCE = 0x0080
162    """Keep profile sequence for devicelink creation"""
163    FORCE_CLUT = 0x0002
164    """Force CLUT optimization"""
165    CLUT_POST_LINEARIZATION = 0x0001
166    """create postlinearization tables if possible"""
167    CLUT_PRE_LINEARIZATION = 0x0010
168    """create prelinearization tables if possible"""
169    NONEGATIVES = 0x8000
170    """Prevent negative numbers in floating point transforms"""
171    COPY_ALPHA = 0x04000000
172    """Alpha channels are copied on ``cmsDoTransform()``"""
173    NODEFAULTRESOURCEDEF = 0x01000000
174
175    _GRIDPOINTS_1 = 1 << 16
176    _GRIDPOINTS_2 = 2 << 16
177    _GRIDPOINTS_4 = 4 << 16
178    _GRIDPOINTS_8 = 8 << 16
179    _GRIDPOINTS_16 = 16 << 16
180    _GRIDPOINTS_32 = 32 << 16
181    _GRIDPOINTS_64 = 64 << 16
182    _GRIDPOINTS_128 = 128 << 16
183
184    @staticmethod
185    def GRIDPOINTS(n: int) -> Flags:
186        """
187        Fine-tune control over number of gridpoints
188
189        :param n: :py:class:`int` in range ``0 <= n <= 255``
190        """
191        return Flags.NONE | ((n & 0xFF) << 16)
192
193
194_MAX_FLAG = reduce(operator.or_, Flags)
195
196
197_FLAGS = {
198    "MATRIXINPUT": 1,
199    "MATRIXOUTPUT": 2,
200    "MATRIXONLY": (1 | 2),
201    "NOWHITEONWHITEFIXUP": 4,  # Don't hot fix scum dot
202    # Don't create prelinearization tables on precalculated transforms
203    # (internal use):
204    "NOPRELINEARIZATION": 16,
205    "GUESSDEVICECLASS": 32,  # Guess device class (for transform2devicelink)
206    "NOTCACHE": 64,  # Inhibit 1-pixel cache
207    "NOTPRECALC": 256,
208    "NULLTRANSFORM": 512,  # Don't transform anyway
209    "HIGHRESPRECALC": 1024,  # Use more memory to give better accuracy
210    "LOWRESPRECALC": 2048,  # Use less memory to minimize resources
211    "WHITEBLACKCOMPENSATION": 8192,
212    "BLACKPOINTCOMPENSATION": 8192,
213    "GAMUTCHECK": 4096,  # Out of Gamut alarm
214    "SOFTPROOFING": 16384,  # Do softproofing
215    "PRESERVEBLACK": 32768,  # Black preservation
216    "NODEFAULTRESOURCEDEF": 16777216,  # CRD special
217    "GRIDPOINTS": lambda n: (n & 0xFF) << 16,  # Gridpoints
218}
219
220
221# --------------------------------------------------------------------.
222# Experimental PIL-level API
223# --------------------------------------------------------------------.
224
225##
226# Profile.
227
228
229class ImageCmsProfile:
230    def __init__(self, profile: str | SupportsRead[bytes] | core.CmsProfile) -> None:
231        """
232        :param profile: Either a string representing a filename,
233            a file like object containing a profile or a
234            low-level profile object
235
236        """
237        self.filename: str | None = None
238
239        if isinstance(profile, str):
240            if sys.platform == "win32":
241                profile_bytes_path = profile.encode()
242                try:
243                    profile_bytes_path.decode("ascii")
244                except UnicodeDecodeError:
245                    with open(profile, "rb") as f:
246                        self.profile = core.profile_frombytes(f.read())
247                    return
248            self.filename = profile
249            self.profile = core.profile_open(profile)
250        elif hasattr(profile, "read"):
251            self.profile = core.profile_frombytes(profile.read())
252        elif isinstance(profile, core.CmsProfile):
253            self.profile = profile
254        else:
255            msg = "Invalid type for Profile"  # type: ignore[unreachable]
256            raise TypeError(msg)
257
258    def __getattr__(self, name: str) -> Any:
259        if name in ("product_name", "product_info"):
260            deprecate(f"ImageCms.ImageCmsProfile.{name}", 13)
261            return None
262        msg = f"'{self.__class__.__name__}' object has no attribute '{name}'"
263        raise AttributeError(msg)
264
265    def tobytes(self) -> bytes:
266        """
267        Returns the profile in a format suitable for embedding in
268        saved images.
269
270        :returns: a bytes object containing the ICC profile.
271        """
272
273        return core.profile_tobytes(self.profile)
274
275
276class ImageCmsTransform(Image.ImagePointHandler):
277    """
278    Transform.  This can be used with the procedural API, or with the standard
279    :py:func:`~PIL.Image.Image.point` method.
280
281    Will return the output profile in the ``output.info['icc_profile']``.
282    """
283
284    def __init__(
285        self,
286        input: ImageCmsProfile,
287        output: ImageCmsProfile,
288        input_mode: str,
289        output_mode: str,
290        intent: Intent = Intent.PERCEPTUAL,
291        proof: ImageCmsProfile | None = None,
292        proof_intent: Intent = Intent.ABSOLUTE_COLORIMETRIC,
293        flags: Flags = Flags.NONE,
294    ):
295        if proof is None:
296            self.transform = core.buildTransform(
297                input.profile, output.profile, input_mode, output_mode, intent, flags
298            )
299        else:
300            self.transform = core.buildProofTransform(
301                input.profile,
302                output.profile,
303                proof.profile,
304                input_mode,
305                output_mode,
306                intent,
307                proof_intent,
308                flags,
309            )
310        # Note: inputMode and outputMode are for pyCMS compatibility only
311        self.input_mode = self.inputMode = input_mode
312        self.output_mode = self.outputMode = output_mode
313
314        self.output_profile = output
315
316    def point(self, im: Image.Image) -> Image.Image:
317        return self.apply(im)
318
319    def apply(self, im: Image.Image, imOut: Image.Image | None = None) -> Image.Image:
320        if imOut is None:
321            imOut = Image.new(self.output_mode, im.size, None)
322        self.transform.apply(im.getim(), imOut.getim())
323        imOut.info["icc_profile"] = self.output_profile.tobytes()
324        return imOut
325
326    def apply_in_place(self, im: Image.Image) -> Image.Image:
327        if im.mode != self.output_mode:
328            msg = "mode mismatch"
329            raise ValueError(msg)  # wrong output mode
330        self.transform.apply(im.getim(), im.getim())
331        im.info["icc_profile"] = self.output_profile.tobytes()
332        return im
333
334
335def get_display_profile(handle: SupportsInt | None = None) -> ImageCmsProfile | None:
336    """
337    (experimental) Fetches the profile for the current display device.
338
339    :returns: ``None`` if the profile is not known.
340    """
341
342    if sys.platform != "win32":
343        return None
344
345    from . import ImageWin  # type: ignore[unused-ignore, unreachable]
346
347    if isinstance(handle, ImageWin.HDC):
348        profile = core.get_display_profile_win32(int(handle), 1)
349    else:
350        profile = core.get_display_profile_win32(int(handle or 0))
351    if profile is None:
352        return None
353    return ImageCmsProfile(profile)
354
355
356# --------------------------------------------------------------------.
357# pyCMS compatible layer
358# --------------------------------------------------------------------.
359
360
361class PyCMSError(Exception):
362    """(pyCMS) Exception class.
363    This is used for all errors in the pyCMS API."""
364
365    pass
366
367
368def profileToProfile(
369    im: Image.Image,
370    inputProfile: _CmsProfileCompatible,
371    outputProfile: _CmsProfileCompatible,
372    renderingIntent: Intent = Intent.PERCEPTUAL,
373    outputMode: str | None = None,
374    inPlace: bool = False,
375    flags: Flags = Flags.NONE,
376) -> Image.Image | None:
377    """
378    (pyCMS) Applies an ICC transformation to a given image, mapping from
379    ``inputProfile`` to ``outputProfile``.
380
381    If the input or output profiles specified are not valid filenames, a
382    :exc:`PyCMSError` will be raised.  If ``inPlace`` is ``True`` and
383    ``outputMode != im.mode``, a :exc:`PyCMSError` will be raised.
384    If an error occurs during application of the profiles,
385    a :exc:`PyCMSError` will be raised.
386    If ``outputMode`` is not a mode supported by the ``outputProfile`` (or by pyCMS),
387    a :exc:`PyCMSError` will be raised.
388
389    This function applies an ICC transformation to im from ``inputProfile``'s
390    color space to ``outputProfile``'s color space using the specified rendering
391    intent to decide how to handle out-of-gamut colors.
392
393    ``outputMode`` can be used to specify that a color mode conversion is to
394    be done using these profiles, but the specified profiles must be able
395    to handle that mode.  I.e., if converting im from RGB to CMYK using
396    profiles, the input profile must handle RGB data, and the output
397    profile must handle CMYK data.
398
399    :param im: An open :py:class:`~PIL.Image.Image` object (i.e. Image.new(...)
400        or Image.open(...), etc.)
401    :param inputProfile: String, as a valid filename path to the ICC input
402        profile you wish to use for this image, or a profile object
403    :param outputProfile: String, as a valid filename path to the ICC output
404        profile you wish to use for this image, or a profile object
405    :param renderingIntent: Integer (0-3) specifying the rendering intent you
406        wish to use for the transform
407
408            ImageCms.Intent.PERCEPTUAL            = 0 (DEFAULT)
409            ImageCms.Intent.RELATIVE_COLORIMETRIC = 1
410            ImageCms.Intent.SATURATION            = 2
411            ImageCms.Intent.ABSOLUTE_COLORIMETRIC = 3
412
413        see the pyCMS documentation for details on rendering intents and what
414        they do.
415    :param outputMode: A valid PIL mode for the output image (i.e. "RGB",
416        "CMYK", etc.).  Note: if rendering the image "inPlace", outputMode
417        MUST be the same mode as the input, or omitted completely.  If
418        omitted, the outputMode will be the same as the mode of the input
419        image (im.mode)
420    :param inPlace: Boolean.  If ``True``, the original image is modified in-place,
421        and ``None`` is returned.  If ``False`` (default), a new
422        :py:class:`~PIL.Image.Image` object is returned with the transform applied.
423    :param flags: Integer (0-...) specifying additional flags
424    :returns: Either None or a new :py:class:`~PIL.Image.Image` object, depending on
425        the value of ``inPlace``
426    :exception PyCMSError:
427    """
428
429    if outputMode is None:
430        outputMode = im.mode
431
432    if not isinstance(renderingIntent, int) or not (0 <= renderingIntent <= 3):
433        msg = "renderingIntent must be an integer between 0 and 3"
434        raise PyCMSError(msg)
435
436    if not isinstance(flags, int) or not (0 <= flags <= _MAX_FLAG):
437        msg = f"flags must be an integer between 0 and {_MAX_FLAG}"
438        raise PyCMSError(msg)
439
440    try:
441        if not isinstance(inputProfile, ImageCmsProfile):
442            inputProfile = ImageCmsProfile(inputProfile)
443        if not isinstance(outputProfile, ImageCmsProfile):
444            outputProfile = ImageCmsProfile(outputProfile)
445        transform = ImageCmsTransform(
446            inputProfile,
447            outputProfile,
448            im.mode,
449            outputMode,
450            renderingIntent,
451            flags=flags,
452        )
453        if inPlace:
454            transform.apply_in_place(im)
455            imOut = None
456        else:
457            imOut = transform.apply(im)
458    except (OSError, TypeError, ValueError) as v:
459        raise PyCMSError(v) from v
460
461    return imOut
462
463
464def getOpenProfile(
465    profileFilename: str | SupportsRead[bytes] | core.CmsProfile,
466) -> ImageCmsProfile:
467    """
468    (pyCMS) Opens an ICC profile file.
469
470    The PyCMSProfile object can be passed back into pyCMS for use in creating
471    transforms and such (as in ImageCms.buildTransformFromOpenProfiles()).
472
473    If ``profileFilename`` is not a valid filename for an ICC profile,
474    a :exc:`PyCMSError` will be raised.
475
476    :param profileFilename: String, as a valid filename path to the ICC profile
477        you wish to open, or a file-like object.
478    :returns: A CmsProfile class object.
479    :exception PyCMSError:
480    """
481
482    try:
483        return ImageCmsProfile(profileFilename)
484    except (OSError, TypeError, ValueError) as v:
485        raise PyCMSError(v) from v
486
487
488def buildTransform(
489    inputProfile: _CmsProfileCompatible,
490    outputProfile: _CmsProfileCompatible,
491    inMode: str,
492    outMode: str,
493    renderingIntent: Intent = Intent.PERCEPTUAL,
494    flags: Flags = Flags.NONE,
495) -> ImageCmsTransform:
496    """
497    (pyCMS) Builds an ICC transform mapping from the ``inputProfile`` to the
498    ``outputProfile``. Use applyTransform to apply the transform to a given
499    image.
500
501    If the input or output profiles specified are not valid filenames, a
502    :exc:`PyCMSError` will be raised. If an error occurs during creation
503    of the transform, a :exc:`PyCMSError` will be raised.
504
505    If ``inMode`` or ``outMode`` are not a mode supported by the ``outputProfile``
506    (or by pyCMS), a :exc:`PyCMSError` will be raised.
507
508    This function builds and returns an ICC transform from the ``inputProfile``
509    to the ``outputProfile`` using the ``renderingIntent`` to determine what to do
510    with out-of-gamut colors.  It will ONLY work for converting images that
511    are in ``inMode`` to images that are in ``outMode`` color format (PIL mode,
512    i.e. "RGB", "RGBA", "CMYK", etc.).
513
514    Building the transform is a fair part of the overhead in
515    ImageCms.profileToProfile(), so if you're planning on converting multiple
516    images using the same input/output settings, this can save you time.
517    Once you have a transform object, it can be used with
518    ImageCms.applyProfile() to convert images without the need to re-compute
519    the lookup table for the transform.
520
521    The reason pyCMS returns a class object rather than a handle directly
522    to the transform is that it needs to keep track of the PIL input/output
523    modes that the transform is meant for.  These attributes are stored in
524    the ``inMode`` and ``outMode`` attributes of the object (which can be
525    manually overridden if you really want to, but I don't know of any
526    time that would be of use, or would even work).
527
528    :param inputProfile: String, as a valid filename path to the ICC input
529        profile you wish to use for this transform, or a profile object
530    :param outputProfile: String, as a valid filename path to the ICC output
531        profile you wish to use for this transform, or a profile object
532    :param inMode: String, as a valid PIL mode that the appropriate profile
533        also supports (i.e. "RGB", "RGBA", "CMYK", etc.)
534    :param outMode: String, as a valid PIL mode that the appropriate profile
535        also supports (i.e. "RGB", "RGBA", "CMYK", etc.)
536    :param renderingIntent: Integer (0-3) specifying the rendering intent you
537        wish to use for the transform
538
539            ImageCms.Intent.PERCEPTUAL            = 0 (DEFAULT)
540            ImageCms.Intent.RELATIVE_COLORIMETRIC = 1
541            ImageCms.Intent.SATURATION            = 2
542            ImageCms.Intent.ABSOLUTE_COLORIMETRIC = 3
543
544        see the pyCMS documentation for details on rendering intents and what
545        they do.
546    :param flags: Integer (0-...) specifying additional flags
547    :returns: A CmsTransform class object.
548    :exception PyCMSError:
549    """
550
551    if not isinstance(renderingIntent, int) or not (0 <= renderingIntent <= 3):
552        msg = "renderingIntent must be an integer between 0 and 3"
553        raise PyCMSError(msg)
554
555    if not isinstance(flags, int) or not (0 <= flags <= _MAX_FLAG):
556        msg = f"flags must be an integer between 0 and {_MAX_FLAG}"
557        raise PyCMSError(msg)
558
559    try:
560        if not isinstance(inputProfile, ImageCmsProfile):
561            inputProfile = ImageCmsProfile(inputProfile)
562        if not isinstance(outputProfile, ImageCmsProfile):
563            outputProfile = ImageCmsProfile(outputProfile)
564        return ImageCmsTransform(
565            inputProfile, outputProfile, inMode, outMode, renderingIntent, flags=flags
566        )
567    except (OSError, TypeError, ValueError) as v:
568        raise PyCMSError(v) from v
569
570
571def buildProofTransform(
572    inputProfile: _CmsProfileCompatible,
573    outputProfile: _CmsProfileCompatible,
574    proofProfile: _CmsProfileCompatible,
575    inMode: str,
576    outMode: str,
577    renderingIntent: Intent = Intent.PERCEPTUAL,
578    proofRenderingIntent: Intent = Intent.ABSOLUTE_COLORIMETRIC,
579    flags: Flags = Flags.SOFTPROOFING,
580) -> ImageCmsTransform:
581    """
582    (pyCMS) Builds an ICC transform mapping from the ``inputProfile`` to the
583    ``outputProfile``, but tries to simulate the result that would be
584    obtained on the ``proofProfile`` device.
585
586    If the input, output, or proof profiles specified are not valid
587    filenames, a :exc:`PyCMSError` will be raised.
588
589    If an error occurs during creation of the transform,
590    a :exc:`PyCMSError` will be raised.
591
592    If ``inMode`` or ``outMode`` are not a mode supported by the ``outputProfile``
593    (or by pyCMS), a :exc:`PyCMSError` will be raised.
594
595    This function builds and returns an ICC transform from the ``inputProfile``
596    to the ``outputProfile``, but tries to simulate the result that would be
597    obtained on the ``proofProfile`` device using ``renderingIntent`` and
598    ``proofRenderingIntent`` to determine what to do with out-of-gamut
599    colors.  This is known as "soft-proofing".  It will ONLY work for
600    converting images that are in ``inMode`` to images that are in outMode
601    color format (PIL mode, i.e. "RGB", "RGBA", "CMYK", etc.).
602
603    Usage of the resulting transform object is exactly the same as with
604    ImageCms.buildTransform().
605
606    Proof profiling is generally used when using an output device to get a
607    good idea of what the final printed/displayed image would look like on
608    the ``proofProfile`` device when it's quicker and easier to use the
609    output device for judging color.  Generally, this means that the
610    output device is a monitor, or a dye-sub printer (etc.), and the simulated
611    device is something more expensive, complicated, or time consuming
612    (making it difficult to make a real print for color judgement purposes).
613
614    Soft-proofing basically functions by adjusting the colors on the
615    output device to match the colors of the device being simulated. However,
616    when the simulated device has a much wider gamut than the output
617    device, you may obtain marginal results.
618
619    :param inputProfile: String, as a valid filename path to the ICC input
620        profile you wish to use for this transform, or a profile object
621    :param outputProfile: String, as a valid filename path to the ICC output
622        (monitor, usually) profile you wish to use for this transform, or a
623        profile object
624    :param proofProfile: String, as a valid filename path to the ICC proof
625        profile you wish to use for this transform, or a profile object
626    :param inMode: String, as a valid PIL mode that the appropriate profile
627        also supports (i.e. "RGB", "RGBA", "CMYK", etc.)
628    :param outMode: String, as a valid PIL mode that the appropriate profile
629        also supports (i.e. "RGB", "RGBA", "CMYK", etc.)
630    :param renderingIntent: Integer (0-3) specifying the rendering intent you
631        wish to use for the input->proof (simulated) transform
632
633            ImageCms.Intent.PERCEPTUAL            = 0 (DEFAULT)
634            ImageCms.Intent.RELATIVE_COLORIMETRIC = 1
635            ImageCms.Intent.SATURATION            = 2
636            ImageCms.Intent.ABSOLUTE_COLORIMETRIC = 3
637
638        see the pyCMS documentation for details on rendering intents and what
639        they do.
640    :param proofRenderingIntent: Integer (0-3) specifying the rendering intent
641        you wish to use for proof->output transform
642
643            ImageCms.Intent.PERCEPTUAL            = 0 (DEFAULT)
644            ImageCms.Intent.RELATIVE_COLORIMETRIC = 1
645            ImageCms.Intent.SATURATION            = 2
646            ImageCms.Intent.ABSOLUTE_COLORIMETRIC = 3
647
648        see the pyCMS documentation for details on rendering intents and what
649        they do.
650    :param flags: Integer (0-...) specifying additional flags
651    :returns: A CmsTransform class object.
652    :exception PyCMSError:
653    """
654
655    if not isinstance(renderingIntent, int) or not (0 <= renderingIntent <= 3):
656        msg = "renderingIntent must be an integer between 0 and 3"
657        raise PyCMSError(msg)
658
659    if not isinstance(flags, int) or not (0 <= flags <= _MAX_FLAG):
660        msg = f"flags must be an integer between 0 and {_MAX_FLAG}"
661        raise PyCMSError(msg)
662
663    try:
664        if not isinstance(inputProfile, ImageCmsProfile):
665            inputProfile = ImageCmsProfile(inputProfile)
666        if not isinstance(outputProfile, ImageCmsProfile):
667            outputProfile = ImageCmsProfile(outputProfile)
668        if not isinstance(proofProfile, ImageCmsProfile):
669            proofProfile = ImageCmsProfile(proofProfile)
670        return ImageCmsTransform(
671            inputProfile,
672            outputProfile,
673            inMode,
674            outMode,
675            renderingIntent,
676            proofProfile,
677            proofRenderingIntent,
678            flags,
679        )
680    except (OSError, TypeError, ValueError) as v:
681        raise PyCMSError(v) from v
682
683
684buildTransformFromOpenProfiles = buildTransform
685buildProofTransformFromOpenProfiles = buildProofTransform
686
687
688def applyTransform(
689    im: Image.Image, transform: ImageCmsTransform, inPlace: bool = False
690) -> Image.Image | None:
691    """
692    (pyCMS) Applies a transform to a given image.
693
694    If ``im.mode != transform.input_mode``, a :exc:`PyCMSError` is raised.
695
696    If ``inPlace`` is ``True`` and ``transform.input_mode != transform.output_mode``, a
697    :exc:`PyCMSError` is raised.
698
699    If ``im.mode``, ``transform.input_mode`` or ``transform.output_mode`` is not
700    supported by pyCMSdll or the profiles you used for the transform, a
701    :exc:`PyCMSError` is raised.
702
703    If an error occurs while the transform is being applied,
704    a :exc:`PyCMSError` is raised.
705
706    This function applies a pre-calculated transform (from
707    ImageCms.buildTransform() or ImageCms.buildTransformFromOpenProfiles())
708    to an image. The transform can be used for multiple images, saving
709    considerable calculation time if doing the same conversion multiple times.
710
711    If you want to modify im in-place instead of receiving a new image as
712    the return value, set ``inPlace`` to ``True``.  This can only be done if
713    ``transform.input_mode`` and ``transform.output_mode`` are the same, because we
714    can't change the mode in-place (the buffer sizes for some modes are
715    different).  The default behavior is to return a new :py:class:`~PIL.Image.Image`
716    object of the same dimensions in mode ``transform.output_mode``.
717
718    :param im: An :py:class:`~PIL.Image.Image` object, and ``im.mode`` must be the same
719        as the ``input_mode`` supported by the transform.
720    :param transform: A valid CmsTransform class object
721    :param inPlace: Bool.  If ``True``, ``im`` is modified in place and ``None`` is
722        returned, if ``False``, a new :py:class:`~PIL.Image.Image` object with the
723        transform applied is returned (and ``im`` is not changed). The default is
724        ``False``.
725    :returns: Either ``None``, or a new :py:class:`~PIL.Image.Image` object,
726        depending on the value of ``inPlace``. The profile will be returned in
727        the image's ``info['icc_profile']``.
728    :exception PyCMSError:
729    """
730
731    try:
732        if inPlace:
733            transform.apply_in_place(im)
734            imOut = None
735        else:
736            imOut = transform.apply(im)
737    except (TypeError, ValueError) as v:
738        raise PyCMSError(v) from v
739
740    return imOut
741
742
743def createProfile(
744    colorSpace: Literal["LAB", "XYZ", "sRGB"], colorTemp: SupportsFloat = 0
745) -> core.CmsProfile:
746    """
747    (pyCMS) Creates a profile.
748
749    If colorSpace not in ``["LAB", "XYZ", "sRGB"]``,
750    a :exc:`PyCMSError` is raised.
751
752    If using LAB and ``colorTemp`` is not a positive integer,
753    a :exc:`PyCMSError` is raised.
754
755    If an error occurs while creating the profile,
756    a :exc:`PyCMSError` is raised.
757
758    Use this function to create common profiles on-the-fly instead of
759    having to supply a profile on disk and knowing the path to it.  It
760    returns a normal CmsProfile object that can be passed to
761    ImageCms.buildTransformFromOpenProfiles() to create a transform to apply
762    to images.
763
764    :param colorSpace: String, the color space of the profile you wish to
765        create.
766        Currently only "LAB", "XYZ", and "sRGB" are supported.
767    :param colorTemp: Positive number for the white point for the profile, in
768        degrees Kelvin (i.e. 5000, 6500, 9600, etc.).  The default is for D50
769        illuminant if omitted (5000k).  colorTemp is ONLY applied to LAB
770        profiles, and is ignored for XYZ and sRGB.
771    :returns: A CmsProfile class object
772    :exception PyCMSError:
773    """
774
775    if colorSpace not in ["LAB", "XYZ", "sRGB"]:
776        msg = (
777            f"Color space not supported for on-the-fly profile creation ({colorSpace})"
778        )
779        raise PyCMSError(msg)
780
781    if colorSpace == "LAB":
782        try:
783            colorTemp = float(colorTemp)
784        except (TypeError, ValueError) as e:
785            msg = f'Color temperature must be numeric, "{colorTemp}" not valid'
786            raise PyCMSError(msg) from e
787
788    try:
789        return core.createProfile(colorSpace, colorTemp)
790    except (TypeError, ValueError) as v:
791        raise PyCMSError(v) from v
792
793
794def getProfileName(profile: _CmsProfileCompatible) -> str:
795    """
796
797    (pyCMS) Gets the internal product name for the given profile.
798
799    If ``profile`` isn't a valid CmsProfile object or filename to a profile,
800    a :exc:`PyCMSError` is raised If an error occurs while trying
801    to obtain the name tag, a :exc:`PyCMSError` is raised.
802
803    Use this function to obtain the INTERNAL name of the profile (stored
804    in an ICC tag in the profile itself), usually the one used when the
805    profile was originally created.  Sometimes this tag also contains
806    additional information supplied by the creator.
807
808    :param profile: EITHER a valid CmsProfile object, OR a string of the
809        filename of an ICC profile.
810    :returns: A string containing the internal name of the profile as stored
811        in an ICC tag.
812    :exception PyCMSError:
813    """
814
815    try:
816        # add an extra newline to preserve pyCMS compatibility
817        if not isinstance(profile, ImageCmsProfile):
818            profile = ImageCmsProfile(profile)
819        # do it in python, not c.
820        #    // name was "%s - %s" (model, manufacturer) || Description ,
821        #    // but if the Model and Manufacturer were the same or the model
822        #    // was long, Just the model,  in 1.x
823        model = profile.profile.model
824        manufacturer = profile.profile.manufacturer
825
826        if not (model or manufacturer):
827            return (profile.profile.profile_description or "") + "\n"
828        if not manufacturer or (model and len(model) > 30):
829            return f"{model}\n"
830        return f"{model} - {manufacturer}\n"
831
832    except (AttributeError, OSError, TypeError, ValueError) as v:
833        raise PyCMSError(v) from v
834
835
836def getProfileInfo(profile: _CmsProfileCompatible) -> str:
837    """
838    (pyCMS) Gets the internal product information for the given profile.
839
840    If ``profile`` isn't a valid CmsProfile object or filename to a profile,
841    a :exc:`PyCMSError` is raised.
842
843    If an error occurs while trying to obtain the info tag,
844    a :exc:`PyCMSError` is raised.
845
846    Use this function to obtain the information stored in the profile's
847    info tag.  This often contains details about the profile, and how it
848    was created, as supplied by the creator.
849
850    :param profile: EITHER a valid CmsProfile object, OR a string of the
851        filename of an ICC profile.
852    :returns: A string containing the internal profile information stored in
853        an ICC tag.
854    :exception PyCMSError:
855    """
856
857    try:
858        if not isinstance(profile, ImageCmsProfile):
859            profile = ImageCmsProfile(profile)
860        # add an extra newline to preserve pyCMS compatibility
861        # Python, not C. the white point bits weren't working well,
862        # so skipping.
863        # info was description \r\n\r\n copyright \r\n\r\n K007 tag \r\n\r\n whitepoint
864        description = profile.profile.profile_description
865        cpright = profile.profile.copyright
866        elements = [element for element in (description, cpright) if element]
867        return "\r\n\r\n".join(elements) + "\r\n\r\n"
868
869    except (AttributeError, OSError, TypeError, ValueError) as v:
870        raise PyCMSError(v) from v
871
872
873def getProfileCopyright(profile: _CmsProfileCompatible) -> str:
874    """
875    (pyCMS) Gets the copyright for the given profile.
876
877    If ``profile`` isn't a valid CmsProfile object or filename to a profile, a
878    :exc:`PyCMSError` is raised.
879
880    If an error occurs while trying to obtain the copyright tag,
881    a :exc:`PyCMSError` is raised.
882
883    Use this function to obtain the information stored in the profile's
884    copyright tag.
885
886    :param profile: EITHER a valid CmsProfile object, OR a string of the
887        filename of an ICC profile.
888    :returns: A string containing the internal profile information stored in
889        an ICC tag.
890    :exception PyCMSError:
891    """
892    try:
893        # add an extra newline to preserve pyCMS compatibility
894        if not isinstance(profile, ImageCmsProfile):
895            profile = ImageCmsProfile(profile)
896        return (profile.profile.copyright or "") + "\n"
897    except (AttributeError, OSError, TypeError, ValueError) as v:
898        raise PyCMSError(v) from v
899
900
901def getProfileManufacturer(profile: _CmsProfileCompatible) -> str:
902    """
903    (pyCMS) Gets the manufacturer for the given profile.
904
905    If ``profile`` isn't a valid CmsProfile object or filename to a profile, a
906    :exc:`PyCMSError` is raised.
907
908    If an error occurs while trying to obtain the manufacturer tag, a
909    :exc:`PyCMSError` is raised.
910
911    Use this function to obtain the information stored in the profile's
912    manufacturer tag.
913
914    :param profile: EITHER a valid CmsProfile object, OR a string of the
915        filename of an ICC profile.
916    :returns: A string containing the internal profile information stored in
917        an ICC tag.
918    :exception PyCMSError:
919    """
920    try:
921        # add an extra newline to preserve pyCMS compatibility
922        if not isinstance(profile, ImageCmsProfile):
923            profile = ImageCmsProfile(profile)
924        return (profile.profile.manufacturer or "") + "\n"
925    except (AttributeError, OSError, TypeError, ValueError) as v:
926        raise PyCMSError(v) from v
927
928
929def getProfileModel(profile: _CmsProfileCompatible) -> str:
930    """
931    (pyCMS) Gets the model for the given profile.
932
933    If ``profile`` isn't a valid CmsProfile object or filename to a profile, a
934    :exc:`PyCMSError` is raised.
935
936    If an error occurs while trying to obtain the model tag,
937    a :exc:`PyCMSError` is raised.
938
939    Use this function to obtain the information stored in the profile's
940    model tag.
941
942    :param profile: EITHER a valid CmsProfile object, OR a string of the
943        filename of an ICC profile.
944    :returns: A string containing the internal profile information stored in
945        an ICC tag.
946    :exception PyCMSError:
947    """
948
949    try:
950        # add an extra newline to preserve pyCMS compatibility
951        if not isinstance(profile, ImageCmsProfile):
952            profile = ImageCmsProfile(profile)
953        return (profile.profile.model or "") + "\n"
954    except (AttributeError, OSError, TypeError, ValueError) as v:
955        raise PyCMSError(v) from v
956
957
958def getProfileDescription(profile: _CmsProfileCompatible) -> str:
959    """
960    (pyCMS) Gets the description for the given profile.
961
962    If ``profile`` isn't a valid CmsProfile object or filename to a profile, a
963    :exc:`PyCMSError` is raised.
964
965    If an error occurs while trying to obtain the description tag,
966    a :exc:`PyCMSError` is raised.
967
968    Use this function to obtain the information stored in the profile's
969    description tag.
970
971    :param profile: EITHER a valid CmsProfile object, OR a string of the
972        filename of an ICC profile.
973    :returns: A string containing the internal profile information stored in an
974        ICC tag.
975    :exception PyCMSError:
976    """
977
978    try:
979        # add an extra newline to preserve pyCMS compatibility
980        if not isinstance(profile, ImageCmsProfile):
981            profile = ImageCmsProfile(profile)
982        return (profile.profile.profile_description or "") + "\n"
983    except (AttributeError, OSError, TypeError, ValueError) as v:
984        raise PyCMSError(v) from v
985
986
987def getDefaultIntent(profile: _CmsProfileCompatible) -> int:
988    """
989    (pyCMS) Gets the default intent name for the given profile.
990
991    If ``profile`` isn't a valid CmsProfile object or filename to a profile, a
992    :exc:`PyCMSError` is raised.
993
994    If an error occurs while trying to obtain the default intent, a
995    :exc:`PyCMSError` is raised.
996
997    Use this function to determine the default (and usually best optimized)
998    rendering intent for this profile.  Most profiles support multiple
999    rendering intents, but are intended mostly for one type of conversion.
1000    If you wish to use a different intent than returned, use
1001    ImageCms.isIntentSupported() to verify it will work first.
1002
1003    :param profile: EITHER a valid CmsProfile object, OR a string of the
1004        filename of an ICC profile.
1005    :returns: Integer 0-3 specifying the default rendering intent for this
1006        profile.
1007
1008            ImageCms.Intent.PERCEPTUAL            = 0 (DEFAULT)
1009            ImageCms.Intent.RELATIVE_COLORIMETRIC = 1
1010            ImageCms.Intent.SATURATION            = 2
1011            ImageCms.Intent.ABSOLUTE_COLORIMETRIC = 3
1012
1013        see the pyCMS documentation for details on rendering intents and what
1014            they do.
1015    :exception PyCMSError:
1016    """
1017
1018    try:
1019        if not isinstance(profile, ImageCmsProfile):
1020            profile = ImageCmsProfile(profile)
1021        return profile.profile.rendering_intent
1022    except (AttributeError, OSError, TypeError, ValueError) as v:
1023        raise PyCMSError(v) from v
1024
1025
1026def isIntentSupported(
1027    profile: _CmsProfileCompatible, intent: Intent, direction: Direction
1028) -> Literal[-1, 1]:
1029    """
1030    (pyCMS) Checks if a given intent is supported.
1031
1032    Use this function to verify that you can use your desired
1033    ``intent`` with ``profile``, and that ``profile`` can be used for the
1034    input/output/proof profile as you desire.
1035
1036    Some profiles are created specifically for one "direction", can cannot
1037    be used for others. Some profiles can only be used for certain
1038    rendering intents, so it's best to either verify this before trying
1039    to create a transform with them (using this function), or catch the
1040    potential :exc:`PyCMSError` that will occur if they don't
1041    support the modes you select.
1042
1043    :param profile: EITHER a valid CmsProfile object, OR a string of the
1044        filename of an ICC profile.
1045    :param intent: Integer (0-3) specifying the rendering intent you wish to
1046        use with this profile
1047
1048            ImageCms.Intent.PERCEPTUAL            = 0 (DEFAULT)
1049            ImageCms.Intent.RELATIVE_COLORIMETRIC = 1
1050            ImageCms.Intent.SATURATION            = 2
1051            ImageCms.Intent.ABSOLUTE_COLORIMETRIC = 3
1052
1053        see the pyCMS documentation for details on rendering intents and what
1054            they do.
1055    :param direction: Integer specifying if the profile is to be used for
1056        input, output, or proof
1057
1058            INPUT  = 0 (or use ImageCms.Direction.INPUT)
1059            OUTPUT = 1 (or use ImageCms.Direction.OUTPUT)
1060            PROOF  = 2 (or use ImageCms.Direction.PROOF)
1061
1062    :returns: 1 if the intent/direction are supported, -1 if they are not.
1063    :exception PyCMSError:
1064    """
1065
1066    try:
1067        if not isinstance(profile, ImageCmsProfile):
1068            profile = ImageCmsProfile(profile)
1069        # FIXME: I get different results for the same data w. different
1070        # compilers.  Bug in LittleCMS or in the binding?
1071        if profile.profile.is_intent_supported(intent, direction):
1072            return 1
1073        else:
1074            return -1
1075    except (AttributeError, OSError, TypeError, ValueError) as v:
1076        raise PyCMSError(v) from v
1077 
Aluode/PerceptionLabPortable · CoolFace