Merge pull request #7913 from nulano/types-cms2

This commit is contained in:
Hugo van Kemenade 2024-03-31 15:34:03 +03:00 committed by GitHub
commit 6c55ab22d7
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
7 changed files with 291 additions and 106 deletions

View File

@ -4,13 +4,14 @@ import datetime
import os import os
import re import re
import shutil import shutil
import sys
from io import BytesIO from io import BytesIO
from pathlib import Path from pathlib import Path
from typing import Any from typing import Any
import pytest import pytest
from PIL import Image, ImageMode, features from PIL import Image, ImageMode, ImageWin, features
from .helper import ( from .helper import (
assert_image, assert_image,
@ -18,6 +19,7 @@ from .helper import (
assert_image_similar, assert_image_similar,
assert_image_similar_tofile, assert_image_similar_tofile,
hopper, hopper,
is_pypy,
) )
try: try:
@ -213,6 +215,10 @@ def test_display_profile() -> None:
# try fetching the profile for the current display device # try fetching the profile for the current display device
ImageCms.get_display_profile() ImageCms.get_display_profile()
if sys.platform == "win32":
ImageCms.get_display_profile(ImageWin.HDC(0))
ImageCms.get_display_profile(ImageWin.HWND(0))
def test_lab_color_profile() -> None: def test_lab_color_profile() -> None:
ImageCms.createProfile("LAB", 5000) ImageCms.createProfile("LAB", 5000)
@ -496,16 +502,34 @@ def test_non_ascii_path(tmp_path: Path) -> None:
def test_profile_typesafety() -> None: def test_profile_typesafety() -> None:
"""Profile init type safety # does not segfault
prepatch, these would segfault, postpatch they should emit a typeerror
"""
with pytest.raises(TypeError, match="Invalid type for Profile"): with pytest.raises(TypeError, match="Invalid type for Profile"):
ImageCms.ImageCmsProfile(0).tobytes() ImageCms.ImageCmsProfile(0).tobytes()
with pytest.raises(TypeError, match="Invalid type for Profile"): with pytest.raises(TypeError, match="Invalid type for Profile"):
ImageCms.ImageCmsProfile(1).tobytes() ImageCms.ImageCmsProfile(1).tobytes()
# also check core function
with pytest.raises(TypeError):
ImageCms.core.profile_tobytes(0)
with pytest.raises(TypeError):
ImageCms.core.profile_tobytes(1)
if not is_pypy():
# core profile should not be directly instantiable
with pytest.raises(TypeError):
ImageCms.core.CmsProfile()
with pytest.raises(TypeError):
ImageCms.core.CmsProfile(0)
@pytest.mark.skipif(is_pypy(), reason="fails on PyPy")
def test_transform_typesafety() -> None:
# core transform should not be directly instantiable
with pytest.raises(TypeError):
ImageCms.core.CmsProfile()
with pytest.raises(TypeError):
ImageCms.core.CmsProfile(0)
def assert_aux_channel_preserved( def assert_aux_channel_preserved(
mode: str, transform_in_place: bool, preserved_channel: str mode: str, transform_in_place: bool, preserved_channel: str

View File

@ -121,7 +121,12 @@ nitpicky = True
# generating warnings in “nitpicky mode”. Note that type should include the domain name # generating warnings in “nitpicky mode”. Note that type should include the domain name
# if present. Example entries would be ('py:func', 'int') or # if present. Example entries would be ('py:func', 'int') or
# ('envvar', 'LD_LIBRARY_PATH'). # ('envvar', 'LD_LIBRARY_PATH').
# nitpick_ignore = [] nitpick_ignore = [
# Sphinx does not understand typing.Literal[-1]
# Will be fixed in a future version.
# https://github.com/sphinx-doc/sphinx/pull/11904
("py:obj", "typing.Literal[-1, 1]"),
]
# -- Options for HTML output ---------------------------------------------- # -- Options for HTML output ----------------------------------------------

View File

@ -73,7 +73,7 @@ can be easily displayed in a chromaticity diagram, for example).
:canonical: PIL._imagingcms.CmsProfile :canonical: PIL._imagingcms.CmsProfile
.. py:attribute:: creation_date .. py:attribute:: creation_date
:type: Optional[datetime.datetime] :type: datetime.datetime | None
Date and time this profile was first created (see 7.2.1 of ICC.1:2010). Date and time this profile was first created (see 7.2.1 of ICC.1:2010).
@ -156,58 +156,58 @@ can be easily displayed in a chromaticity diagram, for example).
not been calculated (see 7.2.18 of ICC.1:2010). not been calculated (see 7.2.18 of ICC.1:2010).
.. py:attribute:: copyright .. py:attribute:: copyright
:type: Optional[str] :type: str | None
The text copyright information for the profile (see 9.2.21 of ICC.1:2010). The text copyright information for the profile (see 9.2.21 of ICC.1:2010).
.. py:attribute:: manufacturer .. py:attribute:: manufacturer
:type: Optional[str] :type: str | None
The (English) display string for the device manufacturer (see The (English) display string for the device manufacturer (see
9.2.22 of ICC.1:2010). 9.2.22 of ICC.1:2010).
.. py:attribute:: model .. py:attribute:: model
:type: Optional[str] :type: str | None
The (English) display string for the device model of the device The (English) display string for the device model of the device
for which this profile is created (see 9.2.23 of ICC.1:2010). for which this profile is created (see 9.2.23 of ICC.1:2010).
.. py:attribute:: profile_description .. py:attribute:: profile_description
:type: Optional[str] :type: str | None
The (English) display string for the profile description (see The (English) display string for the profile description (see
9.2.41 of ICC.1:2010). 9.2.41 of ICC.1:2010).
.. py:attribute:: target .. py:attribute:: target
:type: Optional[str] :type: str | None
The name of the registered characterization data set, or the The name of the registered characterization data set, or the
measurement data for a characterization target (see 9.2.14 of measurement data for a characterization target (see 9.2.14 of
ICC.1:2010). ICC.1:2010).
.. py:attribute:: red_colorant .. py:attribute:: red_colorant
:type: Optional[tuple[tuple[float]]] :type: tuple[tuple[float, float, float], tuple[float, float, float]] | None
The first column in the matrix used in matrix/TRC transforms (see 9.2.44 of ICC.1:2010). The first column in the matrix used in matrix/TRC transforms (see 9.2.44 of ICC.1:2010).
The value is in the format ``((X, Y, Z), (x, y, Y))``, if available. The value is in the format ``((X, Y, Z), (x, y, Y))``, if available.
.. py:attribute:: green_colorant .. py:attribute:: green_colorant
:type: Optional[tuple[tuple[float]]] :type: tuple[tuple[float, float, float], tuple[float, float, float]] | None
The second column in the matrix used in matrix/TRC transforms (see 9.2.30 of ICC.1:2010). The second column in the matrix used in matrix/TRC transforms (see 9.2.30 of ICC.1:2010).
The value is in the format ``((X, Y, Z), (x, y, Y))``, if available. The value is in the format ``((X, Y, Z), (x, y, Y))``, if available.
.. py:attribute:: blue_colorant .. py:attribute:: blue_colorant
:type: Optional[tuple[tuple[float]]] :type: tuple[tuple[float, float, float], tuple[float, float, float]] | None
The third column in the matrix used in matrix/TRC transforms (see 9.2.4 of ICC.1:2010). The third column in the matrix used in matrix/TRC transforms (see 9.2.4 of ICC.1:2010).
The value is in the format ``((X, Y, Z), (x, y, Y))``, if available. The value is in the format ``((X, Y, Z), (x, y, Y))``, if available.
.. py:attribute:: luminance .. py:attribute:: luminance
:type: Optional[tuple[tuple[float]]] :type: tuple[tuple[float, float, float], tuple[float, float, float]] | None
The absolute luminance of emissive devices in candelas per square The absolute luminance of emissive devices in candelas per square
metre as described by the Y channel (see 9.2.32 of ICC.1:2010). metre as described by the Y channel (see 9.2.32 of ICC.1:2010).
@ -215,7 +215,7 @@ can be easily displayed in a chromaticity diagram, for example).
The value is in the format ``((X, Y, Z), (x, y, Y))``, if available. The value is in the format ``((X, Y, Z), (x, y, Y))``, if available.
.. py:attribute:: chromaticity .. py:attribute:: chromaticity
:type: Optional[tuple[tuple[float]]] :type: tuple[tuple[float, float, float], tuple[float, float, float], tuple[float, float, float]] | None
The data of the phosphor/colorant chromaticity set used (red, The data of the phosphor/colorant chromaticity set used (red,
green and blue channels, see 9.2.16 of ICC.1:2010). green and blue channels, see 9.2.16 of ICC.1:2010).
@ -223,7 +223,7 @@ can be easily displayed in a chromaticity diagram, for example).
The value is in the format ``((x, y, Y), (x, y, Y), (x, y, Y))``, if available. The value is in the format ``((x, y, Y), (x, y, Y), (x, y, Y))``, if available.
.. py:attribute:: chromatic_adaption .. py:attribute:: chromatic_adaption
:type: tuple[tuple[float]] :type: tuple[tuple[tuple[float, float, float], tuple[float, float, float], tuple[float, float, float]], tuple[tuple[float, float, float], tuple[float, float, float], tuple[float, float, float]]] | None
The chromatic adaption matrix converts a color measured using the The chromatic adaption matrix converts a color measured using the
actual illumination conditions and relative to the actual adopted actual illumination conditions and relative to the actual adopted
@ -249,34 +249,34 @@ can be easily displayed in a chromaticity diagram, for example).
9.2.19 of ICC.1:2010). 9.2.19 of ICC.1:2010).
.. py:attribute:: colorimetric_intent .. py:attribute:: colorimetric_intent
:type: Optional[str] :type: str | None
4-character string (padded with whitespace) identifying the image 4-character string (padded with whitespace) identifying the image
state of PCS colorimetry produced using the colorimetric intent state of PCS colorimetry produced using the colorimetric intent
transforms (see 9.2.20 of ICC.1:2010 for details). transforms (see 9.2.20 of ICC.1:2010 for details).
.. py:attribute:: perceptual_rendering_intent_gamut .. py:attribute:: perceptual_rendering_intent_gamut
:type: Optional[str] :type: str | None
4-character string (padded with whitespace) identifying the (one) 4-character string (padded with whitespace) identifying the (one)
standard reference medium gamut (see 9.2.37 of ICC.1:2010 for standard reference medium gamut (see 9.2.37 of ICC.1:2010 for
details). details).
.. py:attribute:: saturation_rendering_intent_gamut .. py:attribute:: saturation_rendering_intent_gamut
:type: Optional[str] :type: str | None
4-character string (padded with whitespace) identifying the (one) 4-character string (padded with whitespace) identifying the (one)
standard reference medium gamut (see 9.2.37 of ICC.1:2010 for standard reference medium gamut (see 9.2.37 of ICC.1:2010 for
details). details).
.. py:attribute:: technology .. py:attribute:: technology
:type: Optional[str] :type: str | None
4-character string (padded with whitespace) identifying the device 4-character string (padded with whitespace) identifying the device
technology (see 9.2.47 of ICC.1:2010 for details). technology (see 9.2.47 of ICC.1:2010 for details).
.. py:attribute:: media_black_point .. py:attribute:: media_black_point
:type: Optional[tuple[tuple[float]]] :type: tuple[tuple[float, float, float], tuple[float, float, float]] | None
This tag specifies the media black point and is used for This tag specifies the media black point and is used for
generating absolute colorimetry. generating absolute colorimetry.
@ -287,19 +287,19 @@ can be easily displayed in a chromaticity diagram, for example).
The value is in the format ``((X, Y, Z), (x, y, Y))``, if available. The value is in the format ``((X, Y, Z), (x, y, Y))``, if available.
.. py:attribute:: media_white_point_temperature .. py:attribute:: media_white_point_temperature
:type: Optional[float] :type: float | None
Calculates the white point temperature (see the LCMS documentation Calculates the white point temperature (see the LCMS documentation
for more information). for more information).
.. py:attribute:: viewing_condition .. py:attribute:: viewing_condition
:type: Optional[str] :type: str | None
The (English) display string for the viewing conditions (see The (English) display string for the viewing conditions (see
9.2.48 of ICC.1:2010). 9.2.48 of ICC.1:2010).
.. py:attribute:: screening_description .. py:attribute:: screening_description
:type: Optional[str] :type: str | None
The (English) display string for the screening conditions. The (English) display string for the screening conditions.
@ -307,21 +307,21 @@ can be easily displayed in a chromaticity diagram, for example).
version 4. version 4.
.. py:attribute:: red_primary .. py:attribute:: red_primary
:type: Optional[tuple[tuple[float]]] :type: tuple[tuple[float, float, float], tuple[float, float, float]] | None
The XYZ-transformed of the RGB primary color red (1, 0, 0). The XYZ-transformed of the RGB primary color red (1, 0, 0).
The value is in the format ``((X, Y, Z), (x, y, Y))``, if available. The value is in the format ``((X, Y, Z), (x, y, Y))``, if available.
.. py:attribute:: green_primary .. py:attribute:: green_primary
:type: Optional[tuple[tuple[float]]] :type: tuple[tuple[float, float, float], tuple[float, float, float]] | None
The XYZ-transformed of the RGB primary color green (0, 1, 0). The XYZ-transformed of the RGB primary color green (0, 1, 0).
The value is in the format ``((X, Y, Z), (x, y, Y))``, if available. The value is in the format ``((X, Y, Z), (x, y, Y))``, if available.
.. py:attribute:: blue_primary .. py:attribute:: blue_primary
:type: Optional[tuple[tuple[float]]] :type: tuple[tuple[float, float, float], tuple[float, float, float]] | None
The XYZ-transformed of the RGB primary color blue (0, 0, 1). The XYZ-transformed of the RGB primary color blue (0, 0, 1).
@ -334,7 +334,7 @@ can be easily displayed in a chromaticity diagram, for example).
documentation on LCMS). documentation on LCMS).
.. py:attribute:: clut .. py:attribute:: clut
:type: dict[tuple[bool]] :type: dict[int, tuple[bool, bool, bool]] | None
Returns a dictionary of all supported intents and directions for Returns a dictionary of all supported intents and directions for
the CLUT model. the CLUT model.
@ -353,7 +353,7 @@ can be easily displayed in a chromaticity diagram, for example).
that intent is supported for that direction. that intent is supported for that direction.
.. py:attribute:: intent_supported .. py:attribute:: intent_supported
:type: dict[tuple[bool]] :type: dict[int, tuple[bool, bool, bool]] | None
Returns a dictionary of all supported intents and directions. Returns a dictionary of all supported intents and directions.
@ -372,7 +372,7 @@ can be easily displayed in a chromaticity diagram, for example).
There is one function defined on the class: There is one function defined on the class:
.. py:method:: is_intent_supported(intent, direction) .. py:method:: is_intent_supported(intent: int, direction: int, /)
Returns if the intent is supported for the given direction. Returns if the intent is supported for the given direction.

View File

@ -117,6 +117,7 @@ ignore = [
"E221", # Multiple spaces before operator "E221", # Multiple spaces before operator
"E226", # Missing whitespace around arithmetic operator "E226", # Missing whitespace around arithmetic operator
"E241", # Multiple spaces after ',' "E241", # Multiple spaces after ','
"PYI026", # flake8-pyi: typing.TypeAlias added in Python 3.10
"PYI034", # flake8-pyi: typing.Self added in Python 3.11 "PYI034", # flake8-pyi: typing.Self added in Python 3.11
] ]

View File

@ -23,19 +23,20 @@ import operator
import sys import sys
from enum import IntEnum, IntFlag from enum import IntEnum, IntFlag
from functools import reduce from functools import reduce
from typing import Any from typing import Any, Literal, SupportsFloat, SupportsInt, Union
from . import Image, __version__ from . import Image, __version__
from ._deprecate import deprecate from ._deprecate import deprecate
from ._typing import SupportsRead
try: try:
from . import _imagingcms from . import _imagingcms as core
except ImportError as ex: except ImportError as ex:
# Allow error import for doc purposes, but error out when accessing # Allow error import for doc purposes, but error out when accessing
# anything in core. # anything in core.
from ._util import DeferredError from ._util import DeferredError
_imagingcms = DeferredError.new(ex) core = DeferredError.new(ex)
_DESCRIPTION = """ _DESCRIPTION = """
pyCMS pyCMS
@ -119,7 +120,6 @@ def __getattr__(name: str) -> Any:
# --------------------------------------------------------------------. # --------------------------------------------------------------------.
core = _imagingcms
# #
# intent/direction values # intent/direction values
@ -237,7 +237,7 @@ _FLAGS = {
class ImageCmsProfile: class ImageCmsProfile:
def __init__(self, profile): def __init__(self, profile: str | SupportsRead[bytes] | core.CmsProfile) -> None:
""" """
:param profile: Either a string representing a filename, :param profile: Either a string representing a filename,
a file like object containing a profile or a a file like object containing a profile or a
@ -257,19 +257,19 @@ class ImageCmsProfile:
self._set(core.profile_open(profile), profile) self._set(core.profile_open(profile), profile)
elif hasattr(profile, "read"): elif hasattr(profile, "read"):
self._set(core.profile_frombytes(profile.read())) self._set(core.profile_frombytes(profile.read()))
elif isinstance(profile, _imagingcms.CmsProfile): elif isinstance(profile, core.CmsProfile):
self._set(profile) self._set(profile)
else: else:
msg = "Invalid type for Profile" msg = "Invalid type for Profile" # type: ignore[unreachable]
raise TypeError(msg) raise TypeError(msg)
def _set(self, profile, filename=None): def _set(self, profile: core.CmsProfile, filename: str | None = None) -> None:
self.profile = profile self.profile = profile
self.filename = filename self.filename = filename
self.product_name = None # profile.product_name self.product_name = None # profile.product_name
self.product_info = None # profile.product_info self.product_info = None # profile.product_info
def tobytes(self): def tobytes(self) -> bytes:
""" """
Returns the profile in a format suitable for embedding in Returns the profile in a format suitable for embedding in
saved images. saved images.
@ -290,14 +290,14 @@ class ImageCmsTransform(Image.ImagePointHandler):
def __init__( def __init__(
self, self,
input, input: ImageCmsProfile,
output, output: ImageCmsProfile,
input_mode, input_mode: str,
output_mode, output_mode: str,
intent=Intent.PERCEPTUAL, intent: Intent = Intent.PERCEPTUAL,
proof=None, proof: ImageCmsProfile | None = None,
proof_intent=Intent.ABSOLUTE_COLORIMETRIC, proof_intent: Intent = Intent.ABSOLUTE_COLORIMETRIC,
flags=Flags.NONE, flags: Flags = Flags.NONE,
): ):
if proof is None: if proof is None:
self.transform = core.buildTransform( self.transform = core.buildTransform(
@ -320,10 +320,10 @@ class ImageCmsTransform(Image.ImagePointHandler):
self.output_profile = output self.output_profile = output
def point(self, im): def point(self, im: Image.Image) -> Image.Image:
return self.apply(im) return self.apply(im)
def apply(self, im, imOut=None): def apply(self, im: Image.Image, imOut: Image.Image | None = None) -> Image.Image:
im.load() im.load()
if imOut is None: if imOut is None:
imOut = Image.new(self.output_mode, im.size, None) imOut = Image.new(self.output_mode, im.size, None)
@ -331,7 +331,7 @@ class ImageCmsTransform(Image.ImagePointHandler):
imOut.info["icc_profile"] = self.output_profile.tobytes() imOut.info["icc_profile"] = self.output_profile.tobytes()
return imOut return imOut
def apply_in_place(self, im): def apply_in_place(self, im: Image.Image) -> Image.Image:
im.load() im.load()
if im.mode != self.output_mode: if im.mode != self.output_mode:
msg = "mode mismatch" msg = "mode mismatch"
@ -341,7 +341,7 @@ class ImageCmsTransform(Image.ImagePointHandler):
return im 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. (experimental) Fetches the profile for the current display device.
@ -351,12 +351,12 @@ def get_display_profile(handle=None):
if sys.platform != "win32": if sys.platform != "win32":
return None return None
from . import ImageWin from . import ImageWin # type: ignore[unused-ignore, unreachable]
if isinstance(handle, ImageWin.HDC): if isinstance(handle, ImageWin.HDC):
profile = core.get_display_profile_win32(handle, 1) profile = core.get_display_profile_win32(int(handle), 1)
else: else:
profile = core.get_display_profile_win32(handle or 0) profile = core.get_display_profile_win32(int(handle or 0))
if profile is None: if profile is None:
return None return None
return ImageCmsProfile(profile) return ImageCmsProfile(profile)
@ -366,6 +366,10 @@ def get_display_profile(handle=None):
# pyCMS compatible layer # pyCMS compatible layer
# --------------------------------------------------------------------. # --------------------------------------------------------------------.
_CmsProfileCompatible = Union[
str, SupportsRead[bytes], core.CmsProfile, ImageCmsProfile
]
class PyCMSError(Exception): class PyCMSError(Exception):
"""(pyCMS) Exception class. """(pyCMS) Exception class.
@ -375,14 +379,14 @@ class PyCMSError(Exception):
def profileToProfile( def profileToProfile(
im, im: Image.Image,
inputProfile, inputProfile: _CmsProfileCompatible,
outputProfile, outputProfile: _CmsProfileCompatible,
renderingIntent=Intent.PERCEPTUAL, renderingIntent: Intent = Intent.PERCEPTUAL,
outputMode=None, outputMode: str | None = None,
inPlace=False, inPlace: bool = False,
flags=Flags.NONE, flags: Flags = Flags.NONE,
): ) -> Image.Image | None:
""" """
(pyCMS) Applies an ICC transformation to a given image, mapping from (pyCMS) Applies an ICC transformation to a given image, mapping from
``inputProfile`` to ``outputProfile``. ``inputProfile`` to ``outputProfile``.
@ -470,7 +474,9 @@ def profileToProfile(
return imOut return imOut
def getOpenProfile(profileFilename): def getOpenProfile(
profileFilename: str | SupportsRead[bytes] | core.CmsProfile,
) -> ImageCmsProfile:
""" """
(pyCMS) Opens an ICC profile file. (pyCMS) Opens an ICC profile file.
@ -493,13 +499,13 @@ def getOpenProfile(profileFilename):
def buildTransform( def buildTransform(
inputProfile, inputProfile: _CmsProfileCompatible,
outputProfile, outputProfile: _CmsProfileCompatible,
inMode, inMode: str,
outMode, outMode: str,
renderingIntent=Intent.PERCEPTUAL, renderingIntent: Intent = Intent.PERCEPTUAL,
flags=Flags.NONE, flags: Flags = Flags.NONE,
): ) -> ImageCmsTransform:
""" """
(pyCMS) Builds an ICC transform mapping from the ``inputProfile`` to the (pyCMS) Builds an ICC transform mapping from the ``inputProfile`` to the
``outputProfile``. Use applyTransform to apply the transform to a given ``outputProfile``. Use applyTransform to apply the transform to a given
@ -576,15 +582,15 @@ def buildTransform(
def buildProofTransform( def buildProofTransform(
inputProfile, inputProfile: _CmsProfileCompatible,
outputProfile, outputProfile: _CmsProfileCompatible,
proofProfile, proofProfile: _CmsProfileCompatible,
inMode, inMode: str,
outMode, outMode: str,
renderingIntent=Intent.PERCEPTUAL, renderingIntent: Intent = Intent.PERCEPTUAL,
proofRenderingIntent=Intent.ABSOLUTE_COLORIMETRIC, proofRenderingIntent: Intent = Intent.ABSOLUTE_COLORIMETRIC,
flags=Flags.SOFTPROOFING, flags: Flags = Flags.SOFTPROOFING,
): ) -> ImageCmsTransform:
""" """
(pyCMS) Builds an ICC transform mapping from the ``inputProfile`` to the (pyCMS) Builds an ICC transform mapping from the ``inputProfile`` to the
``outputProfile``, but tries to simulate the result that would be ``outputProfile``, but tries to simulate the result that would be
@ -692,7 +698,9 @@ buildTransformFromOpenProfiles = buildTransform
buildProofTransformFromOpenProfiles = buildProofTransform 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. (pyCMS) Applies a transform to a given image.
@ -745,7 +753,9 @@ def applyTransform(im, transform, inPlace=False):
return imOut return imOut
def createProfile(colorSpace, colorTemp=-1): def createProfile(
colorSpace: Literal["LAB", "XYZ", "sRGB"], colorTemp: SupportsFloat = -1
) -> core.CmsProfile:
""" """
(pyCMS) Creates a profile. (pyCMS) Creates a profile.
@ -794,7 +804,7 @@ def createProfile(colorSpace, colorTemp=-1):
raise PyCMSError(v) from v raise PyCMSError(v) from v
def getProfileName(profile): def getProfileName(profile: _CmsProfileCompatible) -> str:
""" """
(pyCMS) Gets the internal product name for the given profile. (pyCMS) Gets the internal product name for the given profile.
@ -828,15 +838,15 @@ def getProfileName(profile):
if not (model or manufacturer): if not (model or manufacturer):
return (profile.profile.profile_description or "") + "\n" return (profile.profile.profile_description or "") + "\n"
if not manufacturer or len(model) > 30: if not manufacturer or len(model) > 30: # type: ignore[arg-type]
return model + "\n" return model + "\n" # type: ignore[operator]
return f"{model} - {manufacturer}\n" return f"{model} - {manufacturer}\n"
except (AttributeError, OSError, TypeError, ValueError) as v: except (AttributeError, OSError, TypeError, ValueError) as v:
raise PyCMSError(v) from v raise PyCMSError(v) from v
def getProfileInfo(profile): def getProfileInfo(profile: _CmsProfileCompatible) -> str:
""" """
(pyCMS) Gets the internal product information for the given profile. (pyCMS) Gets the internal product information for the given profile.
@ -873,7 +883,7 @@ def getProfileInfo(profile):
raise PyCMSError(v) from v raise PyCMSError(v) from v
def getProfileCopyright(profile): def getProfileCopyright(profile: _CmsProfileCompatible) -> str:
""" """
(pyCMS) Gets the copyright for the given profile. (pyCMS) Gets the copyright for the given profile.
@ -901,7 +911,7 @@ def getProfileCopyright(profile):
raise PyCMSError(v) from v raise PyCMSError(v) from v
def getProfileManufacturer(profile): def getProfileManufacturer(profile: _CmsProfileCompatible) -> str:
""" """
(pyCMS) Gets the manufacturer for the given profile. (pyCMS) Gets the manufacturer for the given profile.
@ -929,7 +939,7 @@ def getProfileManufacturer(profile):
raise PyCMSError(v) from v raise PyCMSError(v) from v
def getProfileModel(profile): def getProfileModel(profile: _CmsProfileCompatible) -> str:
""" """
(pyCMS) Gets the model for the given profile. (pyCMS) Gets the model for the given profile.
@ -958,7 +968,7 @@ def getProfileModel(profile):
raise PyCMSError(v) from v raise PyCMSError(v) from v
def getProfileDescription(profile): def getProfileDescription(profile: _CmsProfileCompatible) -> str:
""" """
(pyCMS) Gets the description for the given profile. (pyCMS) Gets the description for the given profile.
@ -987,7 +997,7 @@ def getProfileDescription(profile):
raise PyCMSError(v) from v raise PyCMSError(v) from v
def getDefaultIntent(profile): def getDefaultIntent(profile: _CmsProfileCompatible) -> int:
""" """
(pyCMS) Gets the default intent name for the given profile. (pyCMS) Gets the default intent name for the given profile.
@ -1026,7 +1036,9 @@ def getDefaultIntent(profile):
raise PyCMSError(v) from v 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. (pyCMS) Checks if a given intent is supported.
@ -1077,7 +1089,7 @@ def isIntentSupported(profile, intent, direction):
raise PyCMSError(v) from v raise PyCMSError(v) from v
def versions(): def versions() -> tuple[str, str, str, str]:
""" """
(pyCMS) Fetches versions. (pyCMS) Fetches versions.
""" """

View File

@ -1,3 +1,145 @@
from typing import Any import datetime
import sys
from typing import Literal, SupportsFloat, TypedDict
def __getattr__(name: str) -> Any: ... littlecms_version: str
_Tuple3f = tuple[float, float, float]
_Tuple2x3f = tuple[_Tuple3f, _Tuple3f]
_Tuple3x3f = tuple[_Tuple3f, _Tuple3f, _Tuple3f]
class _IccMeasurementCondition(TypedDict):
observer: int
backing: _Tuple3f
geo: str
flare: float
illuminant_type: str
class _IccViewingCondition(TypedDict):
illuminant: _Tuple3f
surround: _Tuple3f
illuminant_type: str
class CmsProfile:
@property
def rendering_intent(self) -> int: ...
@property
def creation_date(self) -> datetime.datetime | None: ...
@property
def copyright(self) -> str | None: ...
@property
def target(self) -> str | None: ...
@property
def manufacturer(self) -> str | None: ...
@property
def model(self) -> str | None: ...
@property
def profile_description(self) -> str | None: ...
@property
def screening_description(self) -> str | None: ...
@property
def viewing_condition(self) -> str | None: ...
@property
def version(self) -> float: ...
@property
def icc_version(self) -> int: ...
@property
def attributes(self) -> int: ...
@property
def header_flags(self) -> int: ...
@property
def header_manufacturer(self) -> str: ...
@property
def header_model(self) -> str: ...
@property
def device_class(self) -> str: ...
@property
def connection_space(self) -> str: ...
@property
def xcolor_space(self) -> str: ...
@property
def profile_id(self) -> bytes: ...
@property
def is_matrix_shaper(self) -> bool: ...
@property
def technology(self) -> str | None: ...
@property
def colorimetric_intent(self) -> str | None: ...
@property
def perceptual_rendering_intent_gamut(self) -> str | None: ...
@property
def saturation_rendering_intent_gamut(self) -> str | None: ...
@property
def red_colorant(self) -> _Tuple2x3f | None: ...
@property
def green_colorant(self) -> _Tuple2x3f | None: ...
@property
def blue_colorant(self) -> _Tuple2x3f | None: ...
@property
def red_primary(self) -> _Tuple2x3f | None: ...
@property
def green_primary(self) -> _Tuple2x3f | None: ...
@property
def blue_primary(self) -> _Tuple2x3f | None: ...
@property
def media_white_point_temperature(self) -> float | None: ...
@property
def media_white_point(self) -> _Tuple2x3f | None: ...
@property
def media_black_point(self) -> _Tuple2x3f | None: ...
@property
def luminance(self) -> _Tuple2x3f | None: ...
@property
def chromatic_adaptation(self) -> tuple[_Tuple3x3f, _Tuple3x3f] | None: ...
@property
def chromaticity(self) -> _Tuple3x3f | None: ...
@property
def colorant_table(self) -> list[str] | None: ...
@property
def colorant_table_out(self) -> list[str] | None: ...
@property
def intent_supported(self) -> dict[int, tuple[bool, bool, bool]] | None: ...
@property
def clut(self) -> dict[int, tuple[bool, bool, bool]] | None: ...
@property
def icc_measurement_condition(self) -> _IccMeasurementCondition | None: ...
@property
def icc_viewing_condition(self) -> _IccViewingCondition | None: ...
def is_intent_supported(self, intent: int, direction: int, /) -> int: ...
class CmsTransform:
@property
def inputMode(self) -> str: ...
@property
def outputMode(self) -> str: ...
def apply(self, id_in: int, id_out: int) -> int: ...
def profile_open(profile: str, /) -> CmsProfile: ...
def profile_frombytes(profile: bytes, /) -> CmsProfile: ...
def profile_tobytes(profile: CmsProfile, /) -> bytes: ...
def buildTransform(
input_profile: CmsProfile,
output_profile: CmsProfile,
in_mode: str,
out_mode: str,
rendering_intent: int = 0,
cms_flags: int = 0,
/,
) -> CmsTransform: ...
def buildProofTransform(
input_profile: CmsProfile,
output_profile: CmsProfile,
proof_profile: CmsProfile,
in_mode: str,
out_mode: str,
rendering_intent: int = 0,
proof_intent: int = 0,
cms_flags: int = 0,
/,
) -> CmsTransform: ...
def createProfile(
color_space: Literal["LAB", "XYZ", "sRGB"], color_temp: SupportsFloat = 0.0, /
) -> CmsProfile: ...
if sys.platform == "win32":
def get_display_profile_win32(handle: int = 0, is_dc: int = 0, /) -> str | None: ...

View File

@ -143,7 +143,7 @@ cms_profile_tobytes(PyObject *self, PyObject *args) {
cmsHPROFILE *profile; cmsHPROFILE *profile;
PyObject *ret; PyObject *ret;
if (!PyArg_ParseTuple(args, "O", &CmsProfile)) { if (!PyArg_ParseTuple(args, "O!", &CmsProfile_Type, &CmsProfile)) {
return NULL; return NULL;
} }
@ -1421,7 +1421,7 @@ static struct PyGetSetDef cms_profile_getsetters[] = {
{NULL}}; {NULL}};
static PyTypeObject CmsProfile_Type = { static PyTypeObject CmsProfile_Type = {
PyVarObject_HEAD_INIT(NULL, 0) "PIL._imagingcms.CmsProfile", /*tp_name*/ PyVarObject_HEAD_INIT(NULL, 0) "PIL.ImageCms.core.CmsProfile", /*tp_name*/
sizeof(CmsProfileObject), /*tp_basicsize*/ sizeof(CmsProfileObject), /*tp_basicsize*/
0, /*tp_itemsize*/ 0, /*tp_itemsize*/
/* methods */ /* methods */
@ -1473,7 +1473,7 @@ static struct PyGetSetDef cms_transform_getsetters[] = {
{NULL}}; {NULL}};
static PyTypeObject CmsTransform_Type = { static PyTypeObject CmsTransform_Type = {
PyVarObject_HEAD_INIT(NULL, 0) "CmsTransform", /*tp_name*/ PyVarObject_HEAD_INIT(NULL, 0) "PIL.ImageCms.core.CmsTransform", /*tp_name*/
sizeof(CmsTransformObject), /*tp_basicsize*/ sizeof(CmsTransformObject), /*tp_basicsize*/
0, /*tp_itemsize*/ 0, /*tp_itemsize*/
/* methods */ /* methods */
@ -1511,8 +1511,6 @@ setup_module(PyObject *m) {
PyObject *v; PyObject *v;
int vn; int vn;
CmsProfile_Type.tp_new = PyType_GenericNew;
/* Ready object types */ /* Ready object types */
PyType_Ready(&CmsProfile_Type); PyType_Ready(&CmsProfile_Type);
PyType_Ready(&CmsTransform_Type); PyType_Ready(&CmsTransform_Type);
@ -1520,6 +1518,9 @@ setup_module(PyObject *m) {
Py_INCREF(&CmsProfile_Type); Py_INCREF(&CmsProfile_Type);
PyModule_AddObject(m, "CmsProfile", (PyObject *)&CmsProfile_Type); PyModule_AddObject(m, "CmsProfile", (PyObject *)&CmsProfile_Type);
Py_INCREF(&CmsTransform_Type);
PyModule_AddObject(m, "CmsTransform", (PyObject *)&CmsTransform_Type);
d = PyModule_GetDict(m); d = PyModule_GetDict(m);
/* this check is also in PIL.features.pilinfo() */ /* this check is also in PIL.features.pilinfo() */