| |
|
| |
|
| |
|
| |
|
| |
|
| |
|
| |
|
| |
|
| |
|
| |
|
| |
|
| |
|
| |
|
| |
|
| |
|
| |
|
| |
|
| |
|
| |
|
| | 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:
|
| |
|
| |
|
| | 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)
|
| |
|
| |
|
| |
|
| |
|
| |
|
| |
|
| |
|
| |
|
| |
|
| | class Intent(IntEnum):
|
| | PERCEPTUAL = 0
|
| | RELATIVE_COLORIMETRIC = 1
|
| | SATURATION = 2
|
| | ABSOLUTE_COLORIMETRIC = 3
|
| |
|
| |
|
| | class Direction(IntEnum):
|
| | INPUT = 0
|
| | OUTPUT = 1
|
| | PROOF = 2
|
| |
|
| |
|
| |
|
| |
|
| |
|
| |
|
| | 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"""
|
| |
|
| | 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,
|
| |
|
| |
|
| | "NOPRELINEARIZATION": 16,
|
| | "GUESSDEVICECLASS": 32,
|
| | "NOTCACHE": 64,
|
| | "NOTPRECALC": 256,
|
| | "NULLTRANSFORM": 512,
|
| | "HIGHRESPRECALC": 1024,
|
| | "LOWRESPRECALC": 2048,
|
| | "WHITEBLACKCOMPENSATION": 8192,
|
| | "BLACKPOINTCOMPENSATION": 8192,
|
| | "GAMUTCHECK": 4096,
|
| | "SOFTPROOFING": 16384,
|
| | "PRESERVEBLACK": 32768,
|
| | "NODEFAULTRESOURCEDEF": 16777216,
|
| | "GRIDPOINTS": lambda n: (n & 0xFF) << 16,
|
| | }
|
| |
|
| |
|
| |
|
| |
|
| |
|
| |
|
| |
|
| |
|
| |
|
| |
|
| | 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
|
| |
|
| | """
|
| | self.filename = None
|
| | self.product_name = None
|
| | self.product_info = None
|
| |
|
| | 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.profile = core.profile_frombytes(f.read())
|
| | return
|
| | self.filename = profile
|
| | self.profile = core.profile_open(profile)
|
| | elif hasattr(profile, "read"):
|
| | self.profile = core.profile_frombytes(profile.read())
|
| | elif isinstance(profile, core.CmsProfile):
|
| | self.profile = profile
|
| | else:
|
| | msg = "Invalid type for Profile"
|
| | raise TypeError(msg)
|
| |
|
| | 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,
|
| | )
|
| |
|
| | 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)
|
| | 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
|
| |
|
| | 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)
|
| |
|
| |
|
| |
|
| |
|
| |
|
| |
|
| |
|
| | 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:
|
| |
|
| | if not isinstance(profile, ImageCmsProfile):
|
| | profile = ImageCmsProfile(profile)
|
| |
|
| |
|
| |
|
| |
|
| | 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)
|
| |
|
| |
|
| |
|
| |
|
| | 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:
|
| |
|
| | 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:
|
| |
|
| | 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:
|
| |
|
| | 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:
|
| |
|
| | 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)
|
| |
|
| |
|
| | 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__
|
| |
|