|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
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 |
|
[email protected] |
|
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 |
|
|
|
""" |
|
|
|
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" |
|
raise TypeError(msg) |
|
|
|
def _set(self, profile: core.CmsProfile, filename: str | None = None) -> None: |
|
self.profile = profile |
|
self.filename = filename |
|
self.product_name = None |
|
self.product_info = None |
|
|
|
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__ |
|
|