updates
This commit is contained in:
@@ -4,6 +4,9 @@
|
||||
# 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.
|
||||
@@ -14,22 +17,32 @@
|
||||
|
||||
# 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
|
||||
from enum import IntEnum, IntFlag
|
||||
from functools import reduce
|
||||
from typing import Any, Literal, SupportsFloat, SupportsInt, Union
|
||||
|
||||
from . import Image
|
||||
from ._deprecate import deprecate
|
||||
from ._typing import SupportsRead
|
||||
|
||||
try:
|
||||
from . import _imagingcms
|
||||
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
|
||||
|
||||
_imagingcms = DeferredError(ex)
|
||||
core = DeferredError.new(ex)
|
||||
|
||||
DESCRIPTION = """
|
||||
_DESCRIPTION = """
|
||||
pyCMS
|
||||
|
||||
a Python / PIL interface to the littleCMS ICC Color Management System
|
||||
@@ -92,11 +105,11 @@ pyCMS
|
||||
|
||||
"""
|
||||
|
||||
VERSION = "1.0.0 pil"
|
||||
_VERSION = "1.0.0 pil"
|
||||
|
||||
|
||||
# --------------------------------------------------------------------.
|
||||
|
||||
core = _imagingcms
|
||||
|
||||
#
|
||||
# intent/direction values
|
||||
@@ -118,7 +131,70 @@ class Direction(IntEnum):
|
||||
#
|
||||
# flags
|
||||
|
||||
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),
|
||||
@@ -141,11 +217,6 @@ FLAGS = {
|
||||
"GRIDPOINTS": lambda n: (n & 0xFF) << 16, # Gridpoints
|
||||
}
|
||||
|
||||
_MAX_FLAG = 0
|
||||
for flag in FLAGS.values():
|
||||
if isinstance(flag, int):
|
||||
_MAX_FLAG = _MAX_FLAG | flag
|
||||
|
||||
|
||||
# --------------------------------------------------------------------.
|
||||
# Experimental PIL-level API
|
||||
@@ -156,13 +227,14 @@ for flag in FLAGS.values():
|
||||
|
||||
|
||||
class ImageCmsProfile:
|
||||
def __init__(self, profile):
|
||||
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: str | None = None
|
||||
|
||||
if isinstance(profile, str):
|
||||
if sys.platform == "win32":
|
||||
@@ -171,24 +243,26 @@ class ImageCmsProfile:
|
||||
profile_bytes_path.decode("ascii")
|
||||
except UnicodeDecodeError:
|
||||
with open(profile, "rb") as f:
|
||||
self._set(core.profile_frombytes(f.read()))
|
||||
self.profile = core.profile_frombytes(f.read())
|
||||
return
|
||||
self._set(core.profile_open(profile), profile)
|
||||
self.filename = profile
|
||||
self.profile = core.profile_open(profile)
|
||||
elif hasattr(profile, "read"):
|
||||
self._set(core.profile_frombytes(profile.read()))
|
||||
elif isinstance(profile, _imagingcms.CmsProfile):
|
||||
self._set(profile)
|
||||
self.profile = core.profile_frombytes(profile.read())
|
||||
elif isinstance(profile, core.CmsProfile):
|
||||
self.profile = profile
|
||||
else:
|
||||
msg = "Invalid type for Profile"
|
||||
msg = "Invalid type for Profile" # type: ignore[unreachable]
|
||||
raise TypeError(msg)
|
||||
|
||||
def _set(self, profile, filename=None):
|
||||
self.profile = profile
|
||||
self.filename = filename
|
||||
self.product_name = None # profile.product_name
|
||||
self.product_info = None # profile.product_info
|
||||
def __getattr__(self, name: str) -> Any:
|
||||
if name in ("product_name", "product_info"):
|
||||
deprecate(f"ImageCms.ImageCmsProfile.{name}", 13)
|
||||
return None
|
||||
msg = f"'{self.__class__.__name__}' object has no attribute '{name}'"
|
||||
raise AttributeError(msg)
|
||||
|
||||
def tobytes(self):
|
||||
def tobytes(self) -> bytes:
|
||||
"""
|
||||
Returns the profile in a format suitable for embedding in
|
||||
saved images.
|
||||
@@ -200,7 +274,6 @@ class ImageCmsProfile:
|
||||
|
||||
|
||||
class ImageCmsTransform(Image.ImagePointHandler):
|
||||
|
||||
"""
|
||||
Transform. This can be used with the procedural API, or with the standard
|
||||
:py:func:`~PIL.Image.Image.point` method.
|
||||
@@ -210,14 +283,14 @@ class ImageCmsTransform(Image.ImagePointHandler):
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
input,
|
||||
output,
|
||||
input_mode,
|
||||
output_mode,
|
||||
intent=Intent.PERCEPTUAL,
|
||||
proof=None,
|
||||
proof_intent=Intent.ABSOLUTE_COLORIMETRIC,
|
||||
flags=0,
|
||||
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,
|
||||
):
|
||||
if proof is None:
|
||||
self.transform = core.buildTransform(
|
||||
@@ -240,28 +313,26 @@ class ImageCmsTransform(Image.ImagePointHandler):
|
||||
|
||||
self.output_profile = output
|
||||
|
||||
def point(self, im):
|
||||
def point(self, im: Image.Image) -> Image.Image:
|
||||
return self.apply(im)
|
||||
|
||||
def apply(self, im, imOut=None):
|
||||
im.load()
|
||||
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.im.id, imOut.im.id)
|
||||
self.transform.apply(im.getim(), imOut.getim())
|
||||
imOut.info["icc_profile"] = self.output_profile.tobytes()
|
||||
return imOut
|
||||
|
||||
def apply_in_place(self, im):
|
||||
im.load()
|
||||
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.im.id, im.im.id)
|
||||
self.transform.apply(im.getim(), im.getim())
|
||||
im.info["icc_profile"] = self.output_profile.tobytes()
|
||||
return im
|
||||
|
||||
|
||||
def get_display_profile(handle=None):
|
||||
def get_display_profile(handle: SupportsInt | None = None) -> ImageCmsProfile | None:
|
||||
"""
|
||||
(experimental) Fetches the profile for the current display device.
|
||||
|
||||
@@ -271,12 +342,12 @@ def get_display_profile(handle=None):
|
||||
if sys.platform != "win32":
|
||||
return None
|
||||
|
||||
from . import ImageWin
|
||||
from . import ImageWin # type: ignore[unused-ignore, unreachable]
|
||||
|
||||
if isinstance(handle, ImageWin.HDC):
|
||||
profile = core.get_display_profile_win32(handle, 1)
|
||||
profile = core.get_display_profile_win32(int(handle), 1)
|
||||
else:
|
||||
profile = core.get_display_profile_win32(handle or 0)
|
||||
profile = core.get_display_profile_win32(int(handle or 0))
|
||||
if profile is None:
|
||||
return None
|
||||
return ImageCmsProfile(profile)
|
||||
@@ -288,7 +359,6 @@ def get_display_profile(handle=None):
|
||||
|
||||
|
||||
class PyCMSError(Exception):
|
||||
|
||||
"""(pyCMS) Exception class.
|
||||
This is used for all errors in the pyCMS API."""
|
||||
|
||||
@@ -296,14 +366,14 @@ class PyCMSError(Exception):
|
||||
|
||||
|
||||
def profileToProfile(
|
||||
im,
|
||||
inputProfile,
|
||||
outputProfile,
|
||||
renderingIntent=Intent.PERCEPTUAL,
|
||||
outputMode=None,
|
||||
inPlace=False,
|
||||
flags=0,
|
||||
):
|
||||
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``.
|
||||
@@ -391,7 +461,9 @@ def profileToProfile(
|
||||
return imOut
|
||||
|
||||
|
||||
def getOpenProfile(profileFilename):
|
||||
def getOpenProfile(
|
||||
profileFilename: str | SupportsRead[bytes] | core.CmsProfile,
|
||||
) -> ImageCmsProfile:
|
||||
"""
|
||||
(pyCMS) Opens an ICC profile file.
|
||||
|
||||
@@ -414,13 +486,13 @@ def getOpenProfile(profileFilename):
|
||||
|
||||
|
||||
def buildTransform(
|
||||
inputProfile,
|
||||
outputProfile,
|
||||
inMode,
|
||||
outMode,
|
||||
renderingIntent=Intent.PERCEPTUAL,
|
||||
flags=0,
|
||||
):
|
||||
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
|
||||
@@ -481,7 +553,7 @@ def buildTransform(
|
||||
raise PyCMSError(msg)
|
||||
|
||||
if not isinstance(flags, int) or not (0 <= flags <= _MAX_FLAG):
|
||||
msg = "flags must be an integer between 0 and %s" + _MAX_FLAG
|
||||
msg = f"flags must be an integer between 0 and {_MAX_FLAG}"
|
||||
raise PyCMSError(msg)
|
||||
|
||||
try:
|
||||
@@ -497,15 +569,15 @@ def buildTransform(
|
||||
|
||||
|
||||
def buildProofTransform(
|
||||
inputProfile,
|
||||
outputProfile,
|
||||
proofProfile,
|
||||
inMode,
|
||||
outMode,
|
||||
renderingIntent=Intent.PERCEPTUAL,
|
||||
proofRenderingIntent=Intent.ABSOLUTE_COLORIMETRIC,
|
||||
flags=FLAGS["SOFTPROOFING"],
|
||||
):
|
||||
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
|
||||
@@ -585,7 +657,7 @@ def buildProofTransform(
|
||||
raise PyCMSError(msg)
|
||||
|
||||
if not isinstance(flags, int) or not (0 <= flags <= _MAX_FLAG):
|
||||
msg = "flags must be an integer between 0 and %s" + _MAX_FLAG
|
||||
msg = f"flags must be an integer between 0 and {_MAX_FLAG}"
|
||||
raise PyCMSError(msg)
|
||||
|
||||
try:
|
||||
@@ -613,16 +685,18 @@ buildTransformFromOpenProfiles = buildTransform
|
||||
buildProofTransformFromOpenProfiles = buildProofTransform
|
||||
|
||||
|
||||
def applyTransform(im, transform, inPlace=False):
|
||||
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.inMode``, a :exc:`PyCMSError` is raised.
|
||||
If ``im.mode != transform.input_mode``, a :exc:`PyCMSError` is raised.
|
||||
|
||||
If ``inPlace`` is ``True`` and ``transform.inMode != transform.outMode``, a
|
||||
If ``inPlace`` is ``True`` and ``transform.input_mode != transform.output_mode``, a
|
||||
:exc:`PyCMSError` is raised.
|
||||
|
||||
If ``im.mode``, ``transform.inMode`` or ``transform.outMode`` is not
|
||||
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.
|
||||
|
||||
@@ -636,13 +710,13 @@ def applyTransform(im, transform, inPlace=False):
|
||||
|
||||
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.inMode`` and ``transform.outMode`` are the same, because we can't
|
||||
change the mode in-place (the buffer sizes for some modes are
|
||||
``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.outMode``.
|
||||
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 ``inMode`` supported by the transform.
|
||||
: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
|
||||
@@ -666,7 +740,9 @@ def applyTransform(im, transform, inPlace=False):
|
||||
return imOut
|
||||
|
||||
|
||||
def createProfile(colorSpace, colorTemp=-1):
|
||||
def createProfile(
|
||||
colorSpace: Literal["LAB", "XYZ", "sRGB"], colorTemp: SupportsFloat = 0
|
||||
) -> core.CmsProfile:
|
||||
"""
|
||||
(pyCMS) Creates a profile.
|
||||
|
||||
@@ -688,7 +764,7 @@ def createProfile(colorSpace, colorTemp=-1):
|
||||
:param colorSpace: String, the color space of the profile you wish to
|
||||
create.
|
||||
Currently only "LAB", "XYZ", and "sRGB" are supported.
|
||||
:param colorTemp: Positive integer for the white point for the profile, in
|
||||
: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.
|
||||
@@ -715,7 +791,7 @@ def createProfile(colorSpace, colorTemp=-1):
|
||||
raise PyCMSError(v) from v
|
||||
|
||||
|
||||
def getProfileName(profile):
|
||||
def getProfileName(profile: _CmsProfileCompatible) -> str:
|
||||
"""
|
||||
|
||||
(pyCMS) Gets the internal product name for the given profile.
|
||||
@@ -749,15 +825,15 @@ def getProfileName(profile):
|
||||
|
||||
if not (model or manufacturer):
|
||||
return (profile.profile.profile_description or "") + "\n"
|
||||
if not manufacturer or len(model) > 30:
|
||||
return model + "\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):
|
||||
def getProfileInfo(profile: _CmsProfileCompatible) -> str:
|
||||
"""
|
||||
(pyCMS) Gets the internal product information for the given profile.
|
||||
|
||||
@@ -787,17 +863,14 @@ def getProfileInfo(profile):
|
||||
# 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
|
||||
arr = []
|
||||
for elt in (description, cpright):
|
||||
if elt:
|
||||
arr.append(elt)
|
||||
return "\r\n\r\n".join(arr) + "\r\n\r\n"
|
||||
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):
|
||||
def getProfileCopyright(profile: _CmsProfileCompatible) -> str:
|
||||
"""
|
||||
(pyCMS) Gets the copyright for the given profile.
|
||||
|
||||
@@ -825,7 +898,7 @@ def getProfileCopyright(profile):
|
||||
raise PyCMSError(v) from v
|
||||
|
||||
|
||||
def getProfileManufacturer(profile):
|
||||
def getProfileManufacturer(profile: _CmsProfileCompatible) -> str:
|
||||
"""
|
||||
(pyCMS) Gets the manufacturer for the given profile.
|
||||
|
||||
@@ -853,7 +926,7 @@ def getProfileManufacturer(profile):
|
||||
raise PyCMSError(v) from v
|
||||
|
||||
|
||||
def getProfileModel(profile):
|
||||
def getProfileModel(profile: _CmsProfileCompatible) -> str:
|
||||
"""
|
||||
(pyCMS) Gets the model for the given profile.
|
||||
|
||||
@@ -882,7 +955,7 @@ def getProfileModel(profile):
|
||||
raise PyCMSError(v) from v
|
||||
|
||||
|
||||
def getProfileDescription(profile):
|
||||
def getProfileDescription(profile: _CmsProfileCompatible) -> str:
|
||||
"""
|
||||
(pyCMS) Gets the description for the given profile.
|
||||
|
||||
@@ -911,7 +984,7 @@ def getProfileDescription(profile):
|
||||
raise PyCMSError(v) from v
|
||||
|
||||
|
||||
def getDefaultIntent(profile):
|
||||
def getDefaultIntent(profile: _CmsProfileCompatible) -> int:
|
||||
"""
|
||||
(pyCMS) Gets the default intent name for the given profile.
|
||||
|
||||
@@ -950,7 +1023,9 @@ def getDefaultIntent(profile):
|
||||
raise PyCMSError(v) from v
|
||||
|
||||
|
||||
def isIntentSupported(profile, intent, direction):
|
||||
def isIntentSupported(
|
||||
profile: _CmsProfileCompatible, intent: Intent, direction: Direction
|
||||
) -> Literal[-1, 1]:
|
||||
"""
|
||||
(pyCMS) Checks if a given intent is supported.
|
||||
|
||||
@@ -999,11 +1074,3 @@ def isIntentSupported(profile, intent, direction):
|
||||
return -1
|
||||
except (AttributeError, OSError, TypeError, ValueError) as v:
|
||||
raise PyCMSError(v) from v
|
||||
|
||||
|
||||
def versions():
|
||||
"""
|
||||
(pyCMS) Fetches versions.
|
||||
"""
|
||||
|
||||
return VERSION, core.littlecms_version, sys.version.split()[0], Image.__version__
|
||||
|
||||
Reference in New Issue
Block a user