about summary refs log tree commit diff
path: root/.venv/lib/python3.12/site-packages/PIL/ImageCms.py
diff options
context:
space:
mode:
Diffstat (limited to '.venv/lib/python3.12/site-packages/PIL/ImageCms.py')
-rw-r--r--.venv/lib/python3.12/site-packages/PIL/ImageCms.py1125
1 files changed, 1125 insertions, 0 deletions
diff --git a/.venv/lib/python3.12/site-packages/PIL/ImageCms.py b/.venv/lib/python3.12/site-packages/PIL/ImageCms.py
new file mode 100644
index 00000000..fdfbee78
--- /dev/null
+++ b/.venv/lib/python3.12/site-packages/PIL/ImageCms.py
@@ -0,0 +1,1125 @@
+# The Python Imaging Library.
+# $Id$
+
+# Optional color management support, based on Kevin Cazabon's PyCMS
+# library.
+
+# Originally released under LGPL.  Graciously donated to PIL in
+# March 2009, for distribution under the standard PIL license
+
+# History:
+
+# 2009-03-08 fl   Added to PIL.
+
+# Copyright (C) 2002-2003 Kevin Cazabon
+# Copyright (c) 2009 by Fredrik Lundh
+# Copyright (c) 2013 by Eric Soroos
+
+# See the README file for information on usage and redistribution.  See
+# below for the original description.
+from __future__ import annotations
+
+import operator
+import sys
+from enum import IntEnum, IntFlag
+from functools import reduce
+from typing import Any, Literal, SupportsFloat, SupportsInt, Union
+
+from . import Image, __version__
+from ._deprecate import deprecate
+from ._typing import SupportsRead
+
+try:
+    from . import _imagingcms as core
+
+    _CmsProfileCompatible = Union[
+        str, SupportsRead[bytes], core.CmsProfile, "ImageCmsProfile"
+    ]
+except ImportError as ex:
+    # Allow error import for doc purposes, but error out when accessing
+    # anything in core.
+    from ._util import DeferredError
+
+    core = DeferredError.new(ex)
+
+_DESCRIPTION = """
+pyCMS
+
+    a Python / PIL interface to the littleCMS ICC Color Management System
+    Copyright (C) 2002-2003 Kevin Cazabon
+    kevin@cazabon.com
+    https://www.cazabon.com
+
+    pyCMS home page:  https://www.cazabon.com/pyCMS
+    littleCMS home page:  https://www.littlecms.com
+    (littleCMS is Copyright (C) 1998-2001 Marti Maria)
+
+    Originally released under LGPL.  Graciously donated to PIL in
+    March 2009, for distribution under the standard PIL license
+
+    The pyCMS.py module provides a "clean" interface between Python/PIL and
+    pyCMSdll, taking care of some of the more complex handling of the direct
+    pyCMSdll functions, as well as error-checking and making sure that all
+    relevant data is kept together.
+
+    While it is possible to call pyCMSdll functions directly, it's not highly
+    recommended.
+
+    Version History:
+
+        1.0.0 pil       Oct 2013 Port to LCMS 2.
+
+        0.1.0 pil mod   March 10, 2009
+
+                        Renamed display profile to proof profile. The proof
+                        profile is the profile of the device that is being
+                        simulated, not the profile of the device which is
+                        actually used to display/print the final simulation
+                        (that'd be the output profile) - also see LCMSAPI.txt
+                        input colorspace -> using 'renderingIntent' -> proof
+                        colorspace -> using 'proofRenderingIntent' -> output
+                        colorspace
+
+                        Added LCMS FLAGS support.
+                        Added FLAGS["SOFTPROOFING"] as default flag for
+                        buildProofTransform (otherwise the proof profile/intent
+                        would be ignored).
+
+        0.1.0 pil       March 2009 - added to PIL, as PIL.ImageCms
+
+        0.0.2 alpha     Jan 6, 2002
+
+                        Added try/except statements around type() checks of
+                        potential CObjects... Python won't let you use type()
+                        on them, and raises a TypeError (stupid, if you ask
+                        me!)
+
+                        Added buildProofTransformFromOpenProfiles() function.
+                        Additional fixes in DLL, see DLL code for details.
+
+        0.0.1 alpha     first public release, Dec. 26, 2002
+
+    Known to-do list with current version (of Python interface, not pyCMSdll):
+
+        none
+
+"""
+
+_VERSION = "1.0.0 pil"
+
+
+def __getattr__(name: str) -> Any:
+    if name == "DESCRIPTION":
+        deprecate("PIL.ImageCms.DESCRIPTION", 12)
+        return _DESCRIPTION
+    elif name == "VERSION":
+        deprecate("PIL.ImageCms.VERSION", 12)
+        return _VERSION
+    elif name == "FLAGS":
+        deprecate("PIL.ImageCms.FLAGS", 12, "PIL.ImageCms.Flags")
+        return _FLAGS
+    msg = f"module '{__name__}' has no attribute '{name}'"
+    raise AttributeError(msg)
+
+
+# --------------------------------------------------------------------.
+
+
+#
+# intent/direction values
+
+
+class Intent(IntEnum):
+    PERCEPTUAL = 0
+    RELATIVE_COLORIMETRIC = 1
+    SATURATION = 2
+    ABSOLUTE_COLORIMETRIC = 3
+
+
+class Direction(IntEnum):
+    INPUT = 0
+    OUTPUT = 1
+    PROOF = 2
+
+
+#
+# flags
+
+
+class Flags(IntFlag):
+    """Flags and documentation are taken from ``lcms2.h``."""
+
+    NONE = 0
+    NOCACHE = 0x0040
+    """Inhibit 1-pixel cache"""
+    NOOPTIMIZE = 0x0100
+    """Inhibit optimizations"""
+    NULLTRANSFORM = 0x0200
+    """Don't transform anyway"""
+    GAMUTCHECK = 0x1000
+    """Out of Gamut alarm"""
+    SOFTPROOFING = 0x4000
+    """Do softproofing"""
+    BLACKPOINTCOMPENSATION = 0x2000
+    NOWHITEONWHITEFIXUP = 0x0004
+    """Don't fix scum dot"""
+    HIGHRESPRECALC = 0x0400
+    """Use more memory to give better accuracy"""
+    LOWRESPRECALC = 0x0800
+    """Use less memory to minimize resources"""
+    # this should be 8BITS_DEVICELINK, but that is not a valid name in Python:
+    USE_8BITS_DEVICELINK = 0x0008
+    """Create 8 bits devicelinks"""
+    GUESSDEVICECLASS = 0x0020
+    """Guess device class (for ``transform2devicelink``)"""
+    KEEP_SEQUENCE = 0x0080
+    """Keep profile sequence for devicelink creation"""
+    FORCE_CLUT = 0x0002
+    """Force CLUT optimization"""
+    CLUT_POST_LINEARIZATION = 0x0001
+    """create postlinearization tables if possible"""
+    CLUT_PRE_LINEARIZATION = 0x0010
+    """create prelinearization tables if possible"""
+    NONEGATIVES = 0x8000
+    """Prevent negative numbers in floating point transforms"""
+    COPY_ALPHA = 0x04000000
+    """Alpha channels are copied on ``cmsDoTransform()``"""
+    NODEFAULTRESOURCEDEF = 0x01000000
+
+    _GRIDPOINTS_1 = 1 << 16
+    _GRIDPOINTS_2 = 2 << 16
+    _GRIDPOINTS_4 = 4 << 16
+    _GRIDPOINTS_8 = 8 << 16
+    _GRIDPOINTS_16 = 16 << 16
+    _GRIDPOINTS_32 = 32 << 16
+    _GRIDPOINTS_64 = 64 << 16
+    _GRIDPOINTS_128 = 128 << 16
+
+    @staticmethod
+    def GRIDPOINTS(n: int) -> Flags:
+        """
+        Fine-tune control over number of gridpoints
+
+        :param n: :py:class:`int` in range ``0 <= n <= 255``
+        """
+        return Flags.NONE | ((n & 0xFF) << 16)
+
+
+_MAX_FLAG = reduce(operator.or_, Flags)
+
+
+_FLAGS = {
+    "MATRIXINPUT": 1,
+    "MATRIXOUTPUT": 2,
+    "MATRIXONLY": (1 | 2),
+    "NOWHITEONWHITEFIXUP": 4,  # Don't hot fix scum dot
+    # Don't create prelinearization tables on precalculated transforms
+    # (internal use):
+    "NOPRELINEARIZATION": 16,
+    "GUESSDEVICECLASS": 32,  # Guess device class (for transform2devicelink)
+    "NOTCACHE": 64,  # Inhibit 1-pixel cache
+    "NOTPRECALC": 256,
+    "NULLTRANSFORM": 512,  # Don't transform anyway
+    "HIGHRESPRECALC": 1024,  # Use more memory to give better accuracy
+    "LOWRESPRECALC": 2048,  # Use less memory to minimize resources
+    "WHITEBLACKCOMPENSATION": 8192,
+    "BLACKPOINTCOMPENSATION": 8192,
+    "GAMUTCHECK": 4096,  # Out of Gamut alarm
+    "SOFTPROOFING": 16384,  # Do softproofing
+    "PRESERVEBLACK": 32768,  # Black preservation
+    "NODEFAULTRESOURCEDEF": 16777216,  # CRD special
+    "GRIDPOINTS": lambda n: (n & 0xFF) << 16,  # Gridpoints
+}
+
+
+# --------------------------------------------------------------------.
+# Experimental PIL-level API
+# --------------------------------------------------------------------.
+
+##
+# Profile.
+
+
+class ImageCmsProfile:
+    def __init__(self, profile: str | SupportsRead[bytes] | core.CmsProfile) -> None:
+        """
+        :param profile: Either a string representing a filename,
+            a file like object containing a profile or a
+            low-level profile object
+
+        """
+
+        if isinstance(profile, str):
+            if sys.platform == "win32":
+                profile_bytes_path = profile.encode()
+                try:
+                    profile_bytes_path.decode("ascii")
+                except UnicodeDecodeError:
+                    with open(profile, "rb") as f:
+                        self._set(core.profile_frombytes(f.read()))
+                    return
+            self._set(core.profile_open(profile), profile)
+        elif hasattr(profile, "read"):
+            self._set(core.profile_frombytes(profile.read()))
+        elif isinstance(profile, core.CmsProfile):
+            self._set(profile)
+        else:
+            msg = "Invalid type for Profile"  # type: ignore[unreachable]
+            raise TypeError(msg)
+
+    def _set(self, profile: core.CmsProfile, filename: str | None = None) -> None:
+        self.profile = profile
+        self.filename = filename
+        self.product_name = None  # profile.product_name
+        self.product_info = None  # profile.product_info
+
+    def tobytes(self) -> bytes:
+        """
+        Returns the profile in a format suitable for embedding in
+        saved images.
+
+        :returns: a bytes object containing the ICC profile.
+        """
+
+        return core.profile_tobytes(self.profile)
+
+
+class ImageCmsTransform(Image.ImagePointHandler):
+    """
+    Transform.  This can be used with the procedural API, or with the standard
+    :py:func:`~PIL.Image.Image.point` method.
+
+    Will return the output profile in the ``output.info['icc_profile']``.
+    """
+
+    def __init__(
+        self,
+        input: ImageCmsProfile,
+        output: ImageCmsProfile,
+        input_mode: str,
+        output_mode: str,
+        intent: Intent = Intent.PERCEPTUAL,
+        proof: ImageCmsProfile | None = None,
+        proof_intent: Intent = Intent.ABSOLUTE_COLORIMETRIC,
+        flags: Flags = Flags.NONE,
+    ):
+        supported_modes = (
+            "RGB",
+            "RGBA",
+            "RGBX",
+            "CMYK",
+            "I;16",
+            "I;16L",
+            "I;16B",
+            "YCbCr",
+            "LAB",
+            "L",
+            "1",
+        )
+        for mode in (input_mode, output_mode):
+            if mode not in supported_modes:
+                deprecate(
+                    mode,
+                    12,
+                    {
+                        "L;16": "I;16 or I;16L",
+                        "L:16B": "I;16B",
+                        "YCCA": "YCbCr",
+                        "YCC": "YCbCr",
+                    }.get(mode),
+                )
+        if proof is None:
+            self.transform = core.buildTransform(
+                input.profile, output.profile, input_mode, output_mode, intent, flags
+            )
+        else:
+            self.transform = core.buildProofTransform(
+                input.profile,
+                output.profile,
+                proof.profile,
+                input_mode,
+                output_mode,
+                intent,
+                proof_intent,
+                flags,
+            )
+        # Note: inputMode and outputMode are for pyCMS compatibility only
+        self.input_mode = self.inputMode = input_mode
+        self.output_mode = self.outputMode = output_mode
+
+        self.output_profile = output
+
+    def point(self, im: Image.Image) -> Image.Image:
+        return self.apply(im)
+
+    def apply(self, im: Image.Image, imOut: Image.Image | None = None) -> Image.Image:
+        if imOut is None:
+            imOut = Image.new(self.output_mode, im.size, None)
+        self.transform.apply(im.getim(), imOut.getim())
+        imOut.info["icc_profile"] = self.output_profile.tobytes()
+        return imOut
+
+    def apply_in_place(self, im: Image.Image) -> Image.Image:
+        if im.mode != self.output_mode:
+            msg = "mode mismatch"
+            raise ValueError(msg)  # wrong output mode
+        self.transform.apply(im.getim(), im.getim())
+        im.info["icc_profile"] = self.output_profile.tobytes()
+        return im
+
+
+def get_display_profile(handle: SupportsInt | None = None) -> ImageCmsProfile | None:
+    """
+    (experimental) Fetches the profile for the current display device.
+
+    :returns: ``None`` if the profile is not known.
+    """
+
+    if sys.platform != "win32":
+        return None
+
+    from . import ImageWin  # type: ignore[unused-ignore, unreachable]
+
+    if isinstance(handle, ImageWin.HDC):
+        profile = core.get_display_profile_win32(int(handle), 1)
+    else:
+        profile = core.get_display_profile_win32(int(handle or 0))
+    if profile is None:
+        return None
+    return ImageCmsProfile(profile)
+
+
+# --------------------------------------------------------------------.
+# pyCMS compatible layer
+# --------------------------------------------------------------------.
+
+
+class PyCMSError(Exception):
+    """(pyCMS) Exception class.
+    This is used for all errors in the pyCMS API."""
+
+    pass
+
+
+def profileToProfile(
+    im: Image.Image,
+    inputProfile: _CmsProfileCompatible,
+    outputProfile: _CmsProfileCompatible,
+    renderingIntent: Intent = Intent.PERCEPTUAL,
+    outputMode: str | None = None,
+    inPlace: bool = False,
+    flags: Flags = Flags.NONE,
+) -> Image.Image | None:
+    """
+    (pyCMS) Applies an ICC transformation to a given image, mapping from
+    ``inputProfile`` to ``outputProfile``.
+
+    If the input or output profiles specified are not valid filenames, a
+    :exc:`PyCMSError` will be raised.  If ``inPlace`` is ``True`` and
+    ``outputMode != im.mode``, a :exc:`PyCMSError` will be raised.
+    If an error occurs during application of the profiles,
+    a :exc:`PyCMSError` will be raised.
+    If ``outputMode`` is not a mode supported by the ``outputProfile`` (or by pyCMS),
+    a :exc:`PyCMSError` will be raised.
+
+    This function applies an ICC transformation to im from ``inputProfile``'s
+    color space to ``outputProfile``'s color space using the specified rendering
+    intent to decide how to handle out-of-gamut colors.
+
+    ``outputMode`` can be used to specify that a color mode conversion is to
+    be done using these profiles, but the specified profiles must be able
+    to handle that mode.  I.e., if converting im from RGB to CMYK using
+    profiles, the input profile must handle RGB data, and the output
+    profile must handle CMYK data.
+
+    :param im: An open :py:class:`~PIL.Image.Image` object (i.e. Image.new(...)
+        or Image.open(...), etc.)
+    :param inputProfile: String, as a valid filename path to the ICC input
+        profile you wish to use for this image, or a profile object
+    :param outputProfile: String, as a valid filename path to the ICC output
+        profile you wish to use for this image, or a profile object
+    :param renderingIntent: Integer (0-3) specifying the rendering intent you
+        wish to use for the transform
+
+            ImageCms.Intent.PERCEPTUAL            = 0 (DEFAULT)
+            ImageCms.Intent.RELATIVE_COLORIMETRIC = 1
+            ImageCms.Intent.SATURATION            = 2
+            ImageCms.Intent.ABSOLUTE_COLORIMETRIC = 3
+
+        see the pyCMS documentation for details on rendering intents and what
+        they do.
+    :param outputMode: A valid PIL mode for the output image (i.e. "RGB",
+        "CMYK", etc.).  Note: if rendering the image "inPlace", outputMode
+        MUST be the same mode as the input, or omitted completely.  If
+        omitted, the outputMode will be the same as the mode of the input
+        image (im.mode)
+    :param inPlace: Boolean.  If ``True``, the original image is modified in-place,
+        and ``None`` is returned.  If ``False`` (default), a new
+        :py:class:`~PIL.Image.Image` object is returned with the transform applied.
+    :param flags: Integer (0-...) specifying additional flags
+    :returns: Either None or a new :py:class:`~PIL.Image.Image` object, depending on
+        the value of ``inPlace``
+    :exception PyCMSError:
+    """
+
+    if outputMode is None:
+        outputMode = im.mode
+
+    if not isinstance(renderingIntent, int) or not (0 <= renderingIntent <= 3):
+        msg = "renderingIntent must be an integer between 0 and 3"
+        raise PyCMSError(msg)
+
+    if not isinstance(flags, int) or not (0 <= flags <= _MAX_FLAG):
+        msg = f"flags must be an integer between 0 and {_MAX_FLAG}"
+        raise PyCMSError(msg)
+
+    try:
+        if not isinstance(inputProfile, ImageCmsProfile):
+            inputProfile = ImageCmsProfile(inputProfile)
+        if not isinstance(outputProfile, ImageCmsProfile):
+            outputProfile = ImageCmsProfile(outputProfile)
+        transform = ImageCmsTransform(
+            inputProfile,
+            outputProfile,
+            im.mode,
+            outputMode,
+            renderingIntent,
+            flags=flags,
+        )
+        if inPlace:
+            transform.apply_in_place(im)
+            imOut = None
+        else:
+            imOut = transform.apply(im)
+    except (OSError, TypeError, ValueError) as v:
+        raise PyCMSError(v) from v
+
+    return imOut
+
+
+def getOpenProfile(
+    profileFilename: str | SupportsRead[bytes] | core.CmsProfile,
+) -> ImageCmsProfile:
+    """
+    (pyCMS) Opens an ICC profile file.
+
+    The PyCMSProfile object can be passed back into pyCMS for use in creating
+    transforms and such (as in ImageCms.buildTransformFromOpenProfiles()).
+
+    If ``profileFilename`` is not a valid filename for an ICC profile,
+    a :exc:`PyCMSError` will be raised.
+
+    :param profileFilename: String, as a valid filename path to the ICC profile
+        you wish to open, or a file-like object.
+    :returns: A CmsProfile class object.
+    :exception PyCMSError:
+    """
+
+    try:
+        return ImageCmsProfile(profileFilename)
+    except (OSError, TypeError, ValueError) as v:
+        raise PyCMSError(v) from v
+
+
+def buildTransform(
+    inputProfile: _CmsProfileCompatible,
+    outputProfile: _CmsProfileCompatible,
+    inMode: str,
+    outMode: str,
+    renderingIntent: Intent = Intent.PERCEPTUAL,
+    flags: Flags = Flags.NONE,
+) -> ImageCmsTransform:
+    """
+    (pyCMS) Builds an ICC transform mapping from the ``inputProfile`` to the
+    ``outputProfile``. Use applyTransform to apply the transform to a given
+    image.
+
+    If the input or output profiles specified are not valid filenames, a
+    :exc:`PyCMSError` will be raised. If an error occurs during creation
+    of the transform, a :exc:`PyCMSError` will be raised.
+
+    If ``inMode`` or ``outMode`` are not a mode supported by the ``outputProfile``
+    (or by pyCMS), a :exc:`PyCMSError` will be raised.
+
+    This function builds and returns an ICC transform from the ``inputProfile``
+    to the ``outputProfile`` using the ``renderingIntent`` to determine what to do
+    with out-of-gamut colors.  It will ONLY work for converting images that
+    are in ``inMode`` to images that are in ``outMode`` color format (PIL mode,
+    i.e. "RGB", "RGBA", "CMYK", etc.).
+
+    Building the transform is a fair part of the overhead in
+    ImageCms.profileToProfile(), so if you're planning on converting multiple
+    images using the same input/output settings, this can save you time.
+    Once you have a transform object, it can be used with
+    ImageCms.applyProfile() to convert images without the need to re-compute
+    the lookup table for the transform.
+
+    The reason pyCMS returns a class object rather than a handle directly
+    to the transform is that it needs to keep track of the PIL input/output
+    modes that the transform is meant for.  These attributes are stored in
+    the ``inMode`` and ``outMode`` attributes of the object (which can be
+    manually overridden if you really want to, but I don't know of any
+    time that would be of use, or would even work).
+
+    :param inputProfile: String, as a valid filename path to the ICC input
+        profile you wish to use for this transform, or a profile object
+    :param outputProfile: String, as a valid filename path to the ICC output
+        profile you wish to use for this transform, or a profile object
+    :param inMode: String, as a valid PIL mode that the appropriate profile
+        also supports (i.e. "RGB", "RGBA", "CMYK", etc.)
+    :param outMode: String, as a valid PIL mode that the appropriate profile
+        also supports (i.e. "RGB", "RGBA", "CMYK", etc.)
+    :param renderingIntent: Integer (0-3) specifying the rendering intent you
+        wish to use for the transform
+
+            ImageCms.Intent.PERCEPTUAL            = 0 (DEFAULT)
+            ImageCms.Intent.RELATIVE_COLORIMETRIC = 1
+            ImageCms.Intent.SATURATION            = 2
+            ImageCms.Intent.ABSOLUTE_COLORIMETRIC = 3
+
+        see the pyCMS documentation for details on rendering intents and what
+        they do.
+    :param flags: Integer (0-...) specifying additional flags
+    :returns: A CmsTransform class object.
+    :exception PyCMSError:
+    """
+
+    if not isinstance(renderingIntent, int) or not (0 <= renderingIntent <= 3):
+        msg = "renderingIntent must be an integer between 0 and 3"
+        raise PyCMSError(msg)
+
+    if not isinstance(flags, int) or not (0 <= flags <= _MAX_FLAG):
+        msg = f"flags must be an integer between 0 and {_MAX_FLAG}"
+        raise PyCMSError(msg)
+
+    try:
+        if not isinstance(inputProfile, ImageCmsProfile):
+            inputProfile = ImageCmsProfile(inputProfile)
+        if not isinstance(outputProfile, ImageCmsProfile):
+            outputProfile = ImageCmsProfile(outputProfile)
+        return ImageCmsTransform(
+            inputProfile, outputProfile, inMode, outMode, renderingIntent, flags=flags
+        )
+    except (OSError, TypeError, ValueError) as v:
+        raise PyCMSError(v) from v
+
+
+def buildProofTransform(
+    inputProfile: _CmsProfileCompatible,
+    outputProfile: _CmsProfileCompatible,
+    proofProfile: _CmsProfileCompatible,
+    inMode: str,
+    outMode: str,
+    renderingIntent: Intent = Intent.PERCEPTUAL,
+    proofRenderingIntent: Intent = Intent.ABSOLUTE_COLORIMETRIC,
+    flags: Flags = Flags.SOFTPROOFING,
+) -> ImageCmsTransform:
+    """
+    (pyCMS) Builds an ICC transform mapping from the ``inputProfile`` to the
+    ``outputProfile``, but tries to simulate the result that would be
+    obtained on the ``proofProfile`` device.
+
+    If the input, output, or proof profiles specified are not valid
+    filenames, a :exc:`PyCMSError` will be raised.
+
+    If an error occurs during creation of the transform,
+    a :exc:`PyCMSError` will be raised.
+
+    If ``inMode`` or ``outMode`` are not a mode supported by the ``outputProfile``
+    (or by pyCMS), a :exc:`PyCMSError` will be raised.
+
+    This function builds and returns an ICC transform from the ``inputProfile``
+    to the ``outputProfile``, but tries to simulate the result that would be
+    obtained on the ``proofProfile`` device using ``renderingIntent`` and
+    ``proofRenderingIntent`` to determine what to do with out-of-gamut
+    colors.  This is known as "soft-proofing".  It will ONLY work for
+    converting images that are in ``inMode`` to images that are in outMode
+    color format (PIL mode, i.e. "RGB", "RGBA", "CMYK", etc.).
+
+    Usage of the resulting transform object is exactly the same as with
+    ImageCms.buildTransform().
+
+    Proof profiling is generally used when using an output device to get a
+    good idea of what the final printed/displayed image would look like on
+    the ``proofProfile`` device when it's quicker and easier to use the
+    output device for judging color.  Generally, this means that the
+    output device is a monitor, or a dye-sub printer (etc.), and the simulated
+    device is something more expensive, complicated, or time consuming
+    (making it difficult to make a real print for color judgement purposes).
+
+    Soft-proofing basically functions by adjusting the colors on the
+    output device to match the colors of the device being simulated. However,
+    when the simulated device has a much wider gamut than the output
+    device, you may obtain marginal results.
+
+    :param inputProfile: String, as a valid filename path to the ICC input
+        profile you wish to use for this transform, or a profile object
+    :param outputProfile: String, as a valid filename path to the ICC output
+        (monitor, usually) profile you wish to use for this transform, or a
+        profile object
+    :param proofProfile: String, as a valid filename path to the ICC proof
+        profile you wish to use for this transform, or a profile object
+    :param inMode: String, as a valid PIL mode that the appropriate profile
+        also supports (i.e. "RGB", "RGBA", "CMYK", etc.)
+    :param outMode: String, as a valid PIL mode that the appropriate profile
+        also supports (i.e. "RGB", "RGBA", "CMYK", etc.)
+    :param renderingIntent: Integer (0-3) specifying the rendering intent you
+        wish to use for the input->proof (simulated) transform
+
+            ImageCms.Intent.PERCEPTUAL            = 0 (DEFAULT)
+            ImageCms.Intent.RELATIVE_COLORIMETRIC = 1
+            ImageCms.Intent.SATURATION            = 2
+            ImageCms.Intent.ABSOLUTE_COLORIMETRIC = 3
+
+        see the pyCMS documentation for details on rendering intents and what
+        they do.
+    :param proofRenderingIntent: Integer (0-3) specifying the rendering intent
+        you wish to use for proof->output transform
+
+            ImageCms.Intent.PERCEPTUAL            = 0 (DEFAULT)
+            ImageCms.Intent.RELATIVE_COLORIMETRIC = 1
+            ImageCms.Intent.SATURATION            = 2
+            ImageCms.Intent.ABSOLUTE_COLORIMETRIC = 3
+
+        see the pyCMS documentation for details on rendering intents and what
+        they do.
+    :param flags: Integer (0-...) specifying additional flags
+    :returns: A CmsTransform class object.
+    :exception PyCMSError:
+    """
+
+    if not isinstance(renderingIntent, int) or not (0 <= renderingIntent <= 3):
+        msg = "renderingIntent must be an integer between 0 and 3"
+        raise PyCMSError(msg)
+
+    if not isinstance(flags, int) or not (0 <= flags <= _MAX_FLAG):
+        msg = f"flags must be an integer between 0 and {_MAX_FLAG}"
+        raise PyCMSError(msg)
+
+    try:
+        if not isinstance(inputProfile, ImageCmsProfile):
+            inputProfile = ImageCmsProfile(inputProfile)
+        if not isinstance(outputProfile, ImageCmsProfile):
+            outputProfile = ImageCmsProfile(outputProfile)
+        if not isinstance(proofProfile, ImageCmsProfile):
+            proofProfile = ImageCmsProfile(proofProfile)
+        return ImageCmsTransform(
+            inputProfile,
+            outputProfile,
+            inMode,
+            outMode,
+            renderingIntent,
+            proofProfile,
+            proofRenderingIntent,
+            flags,
+        )
+    except (OSError, TypeError, ValueError) as v:
+        raise PyCMSError(v) from v
+
+
+buildTransformFromOpenProfiles = buildTransform
+buildProofTransformFromOpenProfiles = buildProofTransform
+
+
+def applyTransform(
+    im: Image.Image, transform: ImageCmsTransform, inPlace: bool = False
+) -> Image.Image | None:
+    """
+    (pyCMS) Applies a transform to a given image.
+
+    If ``im.mode != transform.input_mode``, a :exc:`PyCMSError` is raised.
+
+    If ``inPlace`` is ``True`` and ``transform.input_mode != transform.output_mode``, a
+    :exc:`PyCMSError` is raised.
+
+    If ``im.mode``, ``transform.input_mode`` or ``transform.output_mode`` is not
+    supported by pyCMSdll or the profiles you used for the transform, a
+    :exc:`PyCMSError` is raised.
+
+    If an error occurs while the transform is being applied,
+    a :exc:`PyCMSError` is raised.
+
+    This function applies a pre-calculated transform (from
+    ImageCms.buildTransform() or ImageCms.buildTransformFromOpenProfiles())
+    to an image. The transform can be used for multiple images, saving
+    considerable calculation time if doing the same conversion multiple times.
+
+    If you want to modify im in-place instead of receiving a new image as
+    the return value, set ``inPlace`` to ``True``.  This can only be done if
+    ``transform.input_mode`` and ``transform.output_mode`` are the same, because we
+    can't change the mode in-place (the buffer sizes for some modes are
+    different).  The default behavior is to return a new :py:class:`~PIL.Image.Image`
+    object of the same dimensions in mode ``transform.output_mode``.
+
+    :param im: An :py:class:`~PIL.Image.Image` object, and ``im.mode`` must be the same
+        as the ``input_mode`` supported by the transform.
+    :param transform: A valid CmsTransform class object
+    :param inPlace: Bool.  If ``True``, ``im`` is modified in place and ``None`` is
+        returned, if ``False``, a new :py:class:`~PIL.Image.Image` object with the
+        transform applied is returned (and ``im`` is not changed). The default is
+        ``False``.
+    :returns: Either ``None``, or a new :py:class:`~PIL.Image.Image` object,
+        depending on the value of ``inPlace``. The profile will be returned in
+        the image's ``info['icc_profile']``.
+    :exception PyCMSError:
+    """
+
+    try:
+        if inPlace:
+            transform.apply_in_place(im)
+            imOut = None
+        else:
+            imOut = transform.apply(im)
+    except (TypeError, ValueError) as v:
+        raise PyCMSError(v) from v
+
+    return imOut
+
+
+def createProfile(
+    colorSpace: Literal["LAB", "XYZ", "sRGB"], colorTemp: SupportsFloat = 0
+) -> core.CmsProfile:
+    """
+    (pyCMS) Creates a profile.
+
+    If colorSpace not in ``["LAB", "XYZ", "sRGB"]``,
+    a :exc:`PyCMSError` is raised.
+
+    If using LAB and ``colorTemp`` is not a positive integer,
+    a :exc:`PyCMSError` is raised.
+
+    If an error occurs while creating the profile,
+    a :exc:`PyCMSError` is raised.
+
+    Use this function to create common profiles on-the-fly instead of
+    having to supply a profile on disk and knowing the path to it.  It
+    returns a normal CmsProfile object that can be passed to
+    ImageCms.buildTransformFromOpenProfiles() to create a transform to apply
+    to images.
+
+    :param colorSpace: String, the color space of the profile you wish to
+        create.
+        Currently only "LAB", "XYZ", and "sRGB" are supported.
+    :param colorTemp: Positive number for the white point for the profile, in
+        degrees Kelvin (i.e. 5000, 6500, 9600, etc.).  The default is for D50
+        illuminant if omitted (5000k).  colorTemp is ONLY applied to LAB
+        profiles, and is ignored for XYZ and sRGB.
+    :returns: A CmsProfile class object
+    :exception PyCMSError:
+    """
+
+    if colorSpace not in ["LAB", "XYZ", "sRGB"]:
+        msg = (
+            f"Color space not supported for on-the-fly profile creation ({colorSpace})"
+        )
+        raise PyCMSError(msg)
+
+    if colorSpace == "LAB":
+        try:
+            colorTemp = float(colorTemp)
+        except (TypeError, ValueError) as e:
+            msg = f'Color temperature must be numeric, "{colorTemp}" not valid'
+            raise PyCMSError(msg) from e
+
+    try:
+        return core.createProfile(colorSpace, colorTemp)
+    except (TypeError, ValueError) as v:
+        raise PyCMSError(v) from v
+
+
+def getProfileName(profile: _CmsProfileCompatible) -> str:
+    """
+
+    (pyCMS) Gets the internal product name for the given profile.
+
+    If ``profile`` isn't a valid CmsProfile object or filename to a profile,
+    a :exc:`PyCMSError` is raised If an error occurs while trying
+    to obtain the name tag, a :exc:`PyCMSError` is raised.
+
+    Use this function to obtain the INTERNAL name of the profile (stored
+    in an ICC tag in the profile itself), usually the one used when the
+    profile was originally created.  Sometimes this tag also contains
+    additional information supplied by the creator.
+
+    :param profile: EITHER a valid CmsProfile object, OR a string of the
+        filename of an ICC profile.
+    :returns: A string containing the internal name of the profile as stored
+        in an ICC tag.
+    :exception PyCMSError:
+    """
+
+    try:
+        # add an extra newline to preserve pyCMS compatibility
+        if not isinstance(profile, ImageCmsProfile):
+            profile = ImageCmsProfile(profile)
+        # do it in python, not c.
+        #    // name was "%s - %s" (model, manufacturer) || Description ,
+        #    // but if the Model and Manufacturer were the same or the model
+        #    // was long, Just the model,  in 1.x
+        model = profile.profile.model
+        manufacturer = profile.profile.manufacturer
+
+        if not (model or manufacturer):
+            return (profile.profile.profile_description or "") + "\n"
+        if not manufacturer or (model and len(model) > 30):
+            return f"{model}\n"
+        return f"{model} - {manufacturer}\n"
+
+    except (AttributeError, OSError, TypeError, ValueError) as v:
+        raise PyCMSError(v) from v
+
+
+def getProfileInfo(profile: _CmsProfileCompatible) -> str:
+    """
+    (pyCMS) Gets the internal product information for the given profile.
+
+    If ``profile`` isn't a valid CmsProfile object or filename to a profile,
+    a :exc:`PyCMSError` is raised.
+
+    If an error occurs while trying to obtain the info tag,
+    a :exc:`PyCMSError` is raised.
+
+    Use this function to obtain the information stored in the profile's
+    info tag.  This often contains details about the profile, and how it
+    was created, as supplied by the creator.
+
+    :param profile: EITHER a valid CmsProfile object, OR a string of the
+        filename of an ICC profile.
+    :returns: A string containing the internal profile information stored in
+        an ICC tag.
+    :exception PyCMSError:
+    """
+
+    try:
+        if not isinstance(profile, ImageCmsProfile):
+            profile = ImageCmsProfile(profile)
+        # add an extra newline to preserve pyCMS compatibility
+        # Python, not C. the white point bits weren't working well,
+        # so skipping.
+        # info was description \r\n\r\n copyright \r\n\r\n K007 tag \r\n\r\n whitepoint
+        description = profile.profile.profile_description
+        cpright = profile.profile.copyright
+        elements = [element for element in (description, cpright) if element]
+        return "\r\n\r\n".join(elements) + "\r\n\r\n"
+
+    except (AttributeError, OSError, TypeError, ValueError) as v:
+        raise PyCMSError(v) from v
+
+
+def getProfileCopyright(profile: _CmsProfileCompatible) -> str:
+    """
+    (pyCMS) Gets the copyright for the given profile.
+
+    If ``profile`` isn't a valid CmsProfile object or filename to a profile, a
+    :exc:`PyCMSError` is raised.
+
+    If an error occurs while trying to obtain the copyright tag,
+    a :exc:`PyCMSError` is raised.
+
+    Use this function to obtain the information stored in the profile's
+    copyright tag.
+
+    :param profile: EITHER a valid CmsProfile object, OR a string of the
+        filename of an ICC profile.
+    :returns: A string containing the internal profile information stored in
+        an ICC tag.
+    :exception PyCMSError:
+    """
+    try:
+        # add an extra newline to preserve pyCMS compatibility
+        if not isinstance(profile, ImageCmsProfile):
+            profile = ImageCmsProfile(profile)
+        return (profile.profile.copyright or "") + "\n"
+    except (AttributeError, OSError, TypeError, ValueError) as v:
+        raise PyCMSError(v) from v
+
+
+def getProfileManufacturer(profile: _CmsProfileCompatible) -> str:
+    """
+    (pyCMS) Gets the manufacturer for the given profile.
+
+    If ``profile`` isn't a valid CmsProfile object or filename to a profile, a
+    :exc:`PyCMSError` is raised.
+
+    If an error occurs while trying to obtain the manufacturer tag, a
+    :exc:`PyCMSError` is raised.
+
+    Use this function to obtain the information stored in the profile's
+    manufacturer tag.
+
+    :param profile: EITHER a valid CmsProfile object, OR a string of the
+        filename of an ICC profile.
+    :returns: A string containing the internal profile information stored in
+        an ICC tag.
+    :exception PyCMSError:
+    """
+    try:
+        # add an extra newline to preserve pyCMS compatibility
+        if not isinstance(profile, ImageCmsProfile):
+            profile = ImageCmsProfile(profile)
+        return (profile.profile.manufacturer or "") + "\n"
+    except (AttributeError, OSError, TypeError, ValueError) as v:
+        raise PyCMSError(v) from v
+
+
+def getProfileModel(profile: _CmsProfileCompatible) -> str:
+    """
+    (pyCMS) Gets the model for the given profile.
+
+    If ``profile`` isn't a valid CmsProfile object or filename to a profile, a
+    :exc:`PyCMSError` is raised.
+
+    If an error occurs while trying to obtain the model tag,
+    a :exc:`PyCMSError` is raised.
+
+    Use this function to obtain the information stored in the profile's
+    model tag.
+
+    :param profile: EITHER a valid CmsProfile object, OR a string of the
+        filename of an ICC profile.
+    :returns: A string containing the internal profile information stored in
+        an ICC tag.
+    :exception PyCMSError:
+    """
+
+    try:
+        # add an extra newline to preserve pyCMS compatibility
+        if not isinstance(profile, ImageCmsProfile):
+            profile = ImageCmsProfile(profile)
+        return (profile.profile.model or "") + "\n"
+    except (AttributeError, OSError, TypeError, ValueError) as v:
+        raise PyCMSError(v) from v
+
+
+def getProfileDescription(profile: _CmsProfileCompatible) -> str:
+    """
+    (pyCMS) Gets the description for the given profile.
+
+    If ``profile`` isn't a valid CmsProfile object or filename to a profile, a
+    :exc:`PyCMSError` is raised.
+
+    If an error occurs while trying to obtain the description tag,
+    a :exc:`PyCMSError` is raised.
+
+    Use this function to obtain the information stored in the profile's
+    description tag.
+
+    :param profile: EITHER a valid CmsProfile object, OR a string of the
+        filename of an ICC profile.
+    :returns: A string containing the internal profile information stored in an
+        ICC tag.
+    :exception PyCMSError:
+    """
+
+    try:
+        # add an extra newline to preserve pyCMS compatibility
+        if not isinstance(profile, ImageCmsProfile):
+            profile = ImageCmsProfile(profile)
+        return (profile.profile.profile_description or "") + "\n"
+    except (AttributeError, OSError, TypeError, ValueError) as v:
+        raise PyCMSError(v) from v
+
+
+def getDefaultIntent(profile: _CmsProfileCompatible) -> int:
+    """
+    (pyCMS) Gets the default intent name for the given profile.
+
+    If ``profile`` isn't a valid CmsProfile object or filename to a profile, a
+    :exc:`PyCMSError` is raised.
+
+    If an error occurs while trying to obtain the default intent, a
+    :exc:`PyCMSError` is raised.
+
+    Use this function to determine the default (and usually best optimized)
+    rendering intent for this profile.  Most profiles support multiple
+    rendering intents, but are intended mostly for one type of conversion.
+    If you wish to use a different intent than returned, use
+    ImageCms.isIntentSupported() to verify it will work first.
+
+    :param profile: EITHER a valid CmsProfile object, OR a string of the
+        filename of an ICC profile.
+    :returns: Integer 0-3 specifying the default rendering intent for this
+        profile.
+
+            ImageCms.Intent.PERCEPTUAL            = 0 (DEFAULT)
+            ImageCms.Intent.RELATIVE_COLORIMETRIC = 1
+            ImageCms.Intent.SATURATION            = 2
+            ImageCms.Intent.ABSOLUTE_COLORIMETRIC = 3
+
+        see the pyCMS documentation for details on rendering intents and what
+            they do.
+    :exception PyCMSError:
+    """
+
+    try:
+        if not isinstance(profile, ImageCmsProfile):
+            profile = ImageCmsProfile(profile)
+        return profile.profile.rendering_intent
+    except (AttributeError, OSError, TypeError, ValueError) as v:
+        raise PyCMSError(v) from v
+
+
+def isIntentSupported(
+    profile: _CmsProfileCompatible, intent: Intent, direction: Direction
+) -> Literal[-1, 1]:
+    """
+    (pyCMS) Checks if a given intent is supported.
+
+    Use this function to verify that you can use your desired
+    ``intent`` with ``profile``, and that ``profile`` can be used for the
+    input/output/proof profile as you desire.
+
+    Some profiles are created specifically for one "direction", can cannot
+    be used for others. Some profiles can only be used for certain
+    rendering intents, so it's best to either verify this before trying
+    to create a transform with them (using this function), or catch the
+    potential :exc:`PyCMSError` that will occur if they don't
+    support the modes you select.
+
+    :param profile: EITHER a valid CmsProfile object, OR a string of the
+        filename of an ICC profile.
+    :param intent: Integer (0-3) specifying the rendering intent you wish to
+        use with this profile
+
+            ImageCms.Intent.PERCEPTUAL            = 0 (DEFAULT)
+            ImageCms.Intent.RELATIVE_COLORIMETRIC = 1
+            ImageCms.Intent.SATURATION            = 2
+            ImageCms.Intent.ABSOLUTE_COLORIMETRIC = 3
+
+        see the pyCMS documentation for details on rendering intents and what
+            they do.
+    :param direction: Integer specifying if the profile is to be used for
+        input, output, or proof
+
+            INPUT  = 0 (or use ImageCms.Direction.INPUT)
+            OUTPUT = 1 (or use ImageCms.Direction.OUTPUT)
+            PROOF  = 2 (or use ImageCms.Direction.PROOF)
+
+    :returns: 1 if the intent/direction are supported, -1 if they are not.
+    :exception PyCMSError:
+    """
+
+    try:
+        if not isinstance(profile, ImageCmsProfile):
+            profile = ImageCmsProfile(profile)
+        # FIXME: I get different results for the same data w. different
+        # compilers.  Bug in LittleCMS or in the binding?
+        if profile.profile.is_intent_supported(intent, direction):
+            return 1
+        else:
+            return -1
+    except (AttributeError, OSError, TypeError, ValueError) as v:
+        raise PyCMSError(v) from v
+
+
+def versions() -> tuple[str, str | None, str, str]:
+    """
+    (pyCMS) Fetches versions.
+    """
+
+    deprecate(
+        "PIL.ImageCms.versions()",
+        12,
+        '(PIL.features.version("littlecms2"), sys.version, PIL.__version__)',
+    )
+    return _VERSION, core.littlecms_version, sys.version.split()[0], __version__