mirror of
https://github.com/python-pillow/Pillow.git
synced 2024-12-25 17:36:18 +03:00
commit
841582fa26
|
@ -39,7 +39,7 @@ after_success:
|
||||||
- coverage report
|
- coverage report
|
||||||
- coveralls
|
- coveralls
|
||||||
- pip install pep8 pyflakes
|
- pip install pep8 pyflakes
|
||||||
- pep8 PIL/*.py
|
- pep8 --statistics --count PIL/*.py
|
||||||
- pyflakes PIL/*.py
|
- pep8 --statistics --count Tests/*.py
|
||||||
- pep8 Tests/*.py
|
- pyflakes PIL/*.py | tee >(wc -l)
|
||||||
- pyflakes Tests/*.py
|
- pyflakes Tests/*.py | tee >(wc -l)
|
||||||
|
|
351
PIL/ImageCms.py
351
PIL/ImageCms.py
|
@ -1,19 +1,19 @@
|
||||||
#
|
"""
|
||||||
# The Python Imaging Library.
|
The Python Imaging Library.
|
||||||
# $Id$
|
$Id$
|
||||||
#
|
|
||||||
# optional color managment support, based on Kevin Cazabon's PyCMS
|
Optional color managment support, based on Kevin Cazabon's PyCMS
|
||||||
# library.
|
library.
|
||||||
#
|
|
||||||
# History:
|
History:
|
||||||
# 2009-03-08 fl Added to PIL.
|
2009-03-08 fl Added to PIL.
|
||||||
#
|
|
||||||
# Copyright (C) 2002-2003 Kevin Cazabon
|
Copyright (C) 2002-2003 Kevin Cazabon
|
||||||
# Copyright (c) 2009 by Fredrik Lundh
|
Copyright (c) 2009 by Fredrik Lundh
|
||||||
#
|
|
||||||
# See the README file for information on usage and redistribution. See
|
See the README file for information on usage and redistribution. See
|
||||||
# below for the original description.
|
below for the original description.
|
||||||
#
|
"""
|
||||||
|
|
||||||
from __future__ import print_function
|
from __future__ import print_function
|
||||||
|
|
||||||
|
@ -66,7 +66,8 @@ pyCMS
|
||||||
|
|
||||||
Added try/except statements arount type() checks of
|
Added try/except statements arount type() checks of
|
||||||
potential CObjects... Python won't let you use type()
|
potential CObjects... Python won't let you use type()
|
||||||
on them, and raises a TypeError (stupid, if you ask me!)
|
on them, and raises a TypeError (stupid, if you ask
|
||||||
|
me!)
|
||||||
|
|
||||||
Added buildProofTransformFromOpenProfiles() function.
|
Added buildProofTransformFromOpenProfiles() function.
|
||||||
Additional fixes in DLL, see DLL code for details.
|
Additional fixes in DLL, see DLL code for details.
|
||||||
|
@ -89,8 +90,8 @@ try:
|
||||||
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 deferred_error
|
from _util import import_err
|
||||||
_imagingcms = deferred_error(ex)
|
_imagingcms = import_err(ex)
|
||||||
from PIL._util import isStringType
|
from PIL._util import isStringType
|
||||||
|
|
||||||
core = _imagingcms
|
core = _imagingcms
|
||||||
|
@ -113,22 +114,24 @@ DIRECTION_PROOF = 2
|
||||||
FLAGS = {
|
FLAGS = {
|
||||||
"MATRIXINPUT": 1,
|
"MATRIXINPUT": 1,
|
||||||
"MATRIXOUTPUT": 2,
|
"MATRIXOUTPUT": 2,
|
||||||
"MATRIXONLY": (1|2),
|
"MATRIXONLY": (1 | 2),
|
||||||
"NOWHITEONWHITEFIXUP": 4, # Don't hot fix scum dot
|
"NOWHITEONWHITEFIXUP": 4, # Don't hot fix scum dot
|
||||||
"NOPRELINEARIZATION": 16, # Don't create prelinearization tables on precalculated transforms (internal use)
|
# Don't create prelinearization tables on precalculated transforms
|
||||||
"GUESSDEVICECLASS": 32, # Guess device class (for transform2devicelink)
|
# (internal use):
|
||||||
"NOTCACHE": 64, # Inhibit 1-pixel cache
|
"NOPRELINEARIZATION": 16,
|
||||||
|
"GUESSDEVICECLASS": 32, # Guess device class (for transform2devicelink)
|
||||||
|
"NOTCACHE": 64, # Inhibit 1-pixel cache
|
||||||
"NOTPRECALC": 256,
|
"NOTPRECALC": 256,
|
||||||
"NULLTRANSFORM": 512, # Don't transform anyway
|
"NULLTRANSFORM": 512, # Don't transform anyway
|
||||||
"HIGHRESPRECALC": 1024, # Use more memory to give better accurancy
|
"HIGHRESPRECALC": 1024, # Use more memory to give better accurancy
|
||||||
"LOWRESPRECALC": 2048, # Use less memory to minimize resouces
|
"LOWRESPRECALC": 2048, # Use less memory to minimize resouces
|
||||||
"WHITEBLACKCOMPENSATION": 8192,
|
"WHITEBLACKCOMPENSATION": 8192,
|
||||||
"BLACKPOINTCOMPENSATION": 8192,
|
"BLACKPOINTCOMPENSATION": 8192,
|
||||||
"GAMUTCHECK": 4096, # Out of Gamut alarm
|
"GAMUTCHECK": 4096, # Out of Gamut alarm
|
||||||
"SOFTPROOFING": 16384, # Do softproofing
|
"SOFTPROOFING": 16384, # Do softproofing
|
||||||
"PRESERVEBLACK": 32768, # Black preservation
|
"PRESERVEBLACK": 32768, # Black preservation
|
||||||
"NODEFAULTRESOURCEDEF": 16777216, # CRD special
|
"NODEFAULTRESOURCEDEF": 16777216, # CRD special
|
||||||
"GRIDPOINTS": lambda n: ((n) & 0xFF) << 16 # Gridpoints
|
"GRIDPOINTS": lambda n: ((n) & 0xFF) << 16 # Gridpoints
|
||||||
}
|
}
|
||||||
|
|
||||||
_MAX_FLAG = 0
|
_MAX_FLAG = 0
|
||||||
|
@ -136,6 +139,7 @@ for flag in FLAGS.values():
|
||||||
if isinstance(flag, int):
|
if isinstance(flag, int):
|
||||||
_MAX_FLAG = _MAX_FLAG | flag
|
_MAX_FLAG = _MAX_FLAG | flag
|
||||||
|
|
||||||
|
|
||||||
# --------------------------------------------------------------------.
|
# --------------------------------------------------------------------.
|
||||||
# Experimental PIL-level API
|
# Experimental PIL-level API
|
||||||
# --------------------------------------------------------------------.
|
# --------------------------------------------------------------------.
|
||||||
|
@ -153,40 +157,42 @@ class ImageCmsProfile:
|
||||||
elif hasattr(profile, "read"):
|
elif hasattr(profile, "read"):
|
||||||
self._set(core.profile_frombytes(profile.read()))
|
self._set(core.profile_frombytes(profile.read()))
|
||||||
else:
|
else:
|
||||||
self._set(profile) # assume it's already a profile
|
self._set(profile) # assume it's already a profile
|
||||||
|
|
||||||
def _set(self, profile, filename=None):
|
def _set(self, profile, filename=None):
|
||||||
self.profile = profile
|
self.profile = profile
|
||||||
self.filename = filename
|
self.filename = filename
|
||||||
if profile:
|
if profile:
|
||||||
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
|
||||||
else:
|
else:
|
||||||
self.product_name = None
|
self.product_name = None
|
||||||
self.product_info = None
|
self.product_info = None
|
||||||
|
|
||||||
|
|
||||||
class ImageCmsTransform(Image.ImagePointHandler):
|
class ImageCmsTransform(Image.ImagePointHandler):
|
||||||
|
|
||||||
"""Transform. This can be used with the procedural API, or with the
|
"""Transform. This can be used with the procedural API, or with the
|
||||||
standard Image.point() method.
|
standard Image.point() method.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def __init__(self, input, output, input_mode, output_mode,
|
def __init__(self, input, output, input_mode, output_mode,
|
||||||
intent=INTENT_PERCEPTUAL,
|
intent=INTENT_PERCEPTUAL, proof=None,
|
||||||
proof=None, proof_intent=INTENT_ABSOLUTE_COLORIMETRIC, flags=0):
|
proof_intent=INTENT_ABSOLUTE_COLORIMETRIC, flags=0):
|
||||||
if proof is None:
|
if proof is None:
|
||||||
self.transform = core.buildTransform(
|
self.transform = core.buildTransform(
|
||||||
input.profile, output.profile,
|
input.profile, output.profile,
|
||||||
input_mode, output_mode,
|
input_mode, output_mode,
|
||||||
intent,
|
intent,
|
||||||
flags
|
flags
|
||||||
)
|
)
|
||||||
else:
|
else:
|
||||||
self.transform = core.buildProofTransform(
|
self.transform = core.buildProofTransform(
|
||||||
input.profile, output.profile, proof.profile,
|
input.profile, output.profile, proof.profile,
|
||||||
input_mode, output_mode,
|
input_mode, output_mode,
|
||||||
intent, proof_intent,
|
intent, proof_intent,
|
||||||
flags
|
flags
|
||||||
)
|
)
|
||||||
# Note: inputMode and outputMode are for pyCMS compatibility only
|
# Note: inputMode and outputMode are for pyCMS compatibility only
|
||||||
self.input_mode = self.inputMode = input_mode
|
self.input_mode = self.inputMode = input_mode
|
||||||
self.output_mode = self.outputMode = output_mode
|
self.output_mode = self.outputMode = output_mode
|
||||||
|
@ -198,16 +204,17 @@ class ImageCmsTransform(Image.ImagePointHandler):
|
||||||
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)
|
||||||
result = self.transform.apply(im.im.id, imOut.im.id)
|
self.transform.apply(im.im.id, imOut.im.id)
|
||||||
return imOut
|
return imOut
|
||||||
|
|
||||||
def apply_in_place(self, im):
|
def apply_in_place(self, im):
|
||||||
im.load()
|
im.load()
|
||||||
if im.mode != self.output_mode:
|
if im.mode != self.output_mode:
|
||||||
raise ValueError("mode mismatch") # wrong output mode
|
raise ValueError("mode mismatch") # wrong output mode
|
||||||
result = self.transform.apply(im.im.id, im.im.id)
|
self.transform.apply(im.im.id, im.im.id)
|
||||||
return im
|
return im
|
||||||
|
|
||||||
|
|
||||||
def get_display_profile(handle=None):
|
def get_display_profile(handle=None):
|
||||||
""" (experimental) Fetches the profile for the current display device.
|
""" (experimental) Fetches the profile for the current display device.
|
||||||
:returns: None if the profile is not known.
|
:returns: None if the profile is not known.
|
||||||
|
@ -229,15 +236,21 @@ def get_display_profile(handle=None):
|
||||||
profile = get()
|
profile = get()
|
||||||
return ImageCmsProfile(profile)
|
return ImageCmsProfile(profile)
|
||||||
|
|
||||||
|
|
||||||
# --------------------------------------------------------------------.
|
# --------------------------------------------------------------------.
|
||||||
# pyCMS compatible layer
|
# pyCMS compatible layer
|
||||||
# --------------------------------------------------------------------.
|
# --------------------------------------------------------------------.
|
||||||
|
|
||||||
class PyCMSError(Exception):
|
class PyCMSError(Exception):
|
||||||
""" (pyCMS) Exception class. This is used for all errors in the pyCMS API. """
|
|
||||||
|
""" (pyCMS) Exception class.
|
||||||
|
This is used for all errors in the pyCMS API. """
|
||||||
pass
|
pass
|
||||||
|
|
||||||
def profileToProfile(im, inputProfile, outputProfile, renderingIntent=INTENT_PERCEPTUAL, outputMode=None, inPlace=0, flags=0):
|
|
||||||
|
def profileToProfile(
|
||||||
|
im, inputProfile, outputProfile, renderingIntent=INTENT_PERCEPTUAL,
|
||||||
|
outputMode=None, inPlace=0, flags=0):
|
||||||
"""
|
"""
|
||||||
(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.
|
||||||
|
@ -259,40 +272,45 @@ def profileToProfile(im, inputProfile, outputProfile, renderingIntent=INTENT_PER
|
||||||
profiles, the input profile must handle RGB data, and the output
|
profiles, the input profile must handle RGB data, and the output
|
||||||
profile must handle CMYK data.
|
profile must handle CMYK data.
|
||||||
|
|
||||||
:param im: An open PIL image object (i.e. Image.new(...) or Image.open(...), etc.)
|
:param im: An open PIL image object (i.e. Image.new(...) or
|
||||||
:param inputProfile: String, as a valid filename path to the ICC input profile
|
Image.open(...), etc.)
|
||||||
you wish to use for this image, or a profile object
|
: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
|
:param outputProfile: String, as a valid filename path to the ICC output
|
||||||
profile you wish to use for this image, or a profile object
|
profile you wish to use for this image, or a profile object
|
||||||
:param renderingIntent: Integer (0-3) specifying the rendering intent you wish
|
:param renderingIntent: Integer (0-3) specifying the rendering intent you
|
||||||
to use for the transform
|
wish to use for the transform
|
||||||
|
|
||||||
INTENT_PERCEPTUAL = 0 (DEFAULT) (ImageCms.INTENT_PERCEPTUAL)
|
INTENT_PERCEPTUAL = 0 (DEFAULT) (ImageCms.INTENT_PERCEPTUAL)
|
||||||
INTENT_RELATIVE_COLORIMETRIC = 1 (ImageCms.INTENT_RELATIVE_COLORIMETRIC)
|
INTENT_RELATIVE_COLORIMETRIC = 1 (ImageCms.INTENT_RELATIVE_COLORIMETRIC)
|
||||||
INTENT_SATURATION = 2 (ImageCms.INTENT_SATURATION)
|
INTENT_SATURATION = 2 (ImageCms.INTENT_SATURATION)
|
||||||
INTENT_ABSOLUTE_COLORIMETRIC = 3 (ImageCms.INTENT_ABSOLUTE_COLORIMETRIC)
|
INTENT_ABSOLUTE_COLORIMETRIC = 3 (ImageCms.INTENT_ABSOLUTE_COLORIMETRIC)
|
||||||
|
|
||||||
see the pyCMS documentation for details on rendering intents and what they do.
|
see the pyCMS documentation for details on rendering intents and what
|
||||||
:param outputMode: A valid PIL mode for the output image (i.e. "RGB", "CMYK",
|
they do.
|
||||||
etc.). Note: if rendering the image "inPlace", outputMode MUST be the
|
:param outputMode: A valid PIL mode for the output image (i.e. "RGB",
|
||||||
same mode as the input, or omitted completely. If omitted, the outputMode
|
"CMYK", etc.). Note: if rendering the image "inPlace", outputMode
|
||||||
will be the same as the mode of the input image (im.mode)
|
MUST be the same mode as the input, or omitted completely. If
|
||||||
:param inPlace: Boolean (1 = True, None or 0 = False). If True, the original
|
omitted, the outputMode will be the same as the mode of the input
|
||||||
image is modified in-place, and None is returned. If False (default), a
|
image (im.mode)
|
||||||
new Image object is returned with the transform applied.
|
:param inPlace: Boolean (1 = True, None or 0 = False). If True, the
|
||||||
|
original image is modified in-place, and None is returned. If False
|
||||||
|
(default), a new Image object is returned with the transform applied.
|
||||||
:param flags: Integer (0-...) specifying additional flags
|
:param flags: Integer (0-...) specifying additional flags
|
||||||
:returns: Either None or a new PIL image object, depending on value of inPlace
|
:returns: Either None or a new PIL image object, depending on value of
|
||||||
|
inPlace
|
||||||
:exception PyCMSError:
|
:exception PyCMSError:
|
||||||
"""
|
"""
|
||||||
|
|
||||||
if outputMode is None:
|
if outputMode is None:
|
||||||
outputMode = im.mode
|
outputMode = im.mode
|
||||||
|
|
||||||
if not isinstance(renderingIntent, int) or not (0 <= renderingIntent <=3):
|
if not isinstance(renderingIntent, int) or not (0 <= renderingIntent <= 3):
|
||||||
raise PyCMSError("renderingIntent must be an integer between 0 and 3")
|
raise PyCMSError("renderingIntent must be an integer between 0 and 3")
|
||||||
|
|
||||||
if not isinstance(flags, int) or not (0 <= flags <= _MAX_FLAG):
|
if not isinstance(flags, int) or not (0 <= flags <= _MAX_FLAG):
|
||||||
raise PyCMSError("flags must be an integer between 0 and %s" + _MAX_FLAG)
|
raise PyCMSError(
|
||||||
|
"flags must be an integer between 0 and %s" + _MAX_FLAG)
|
||||||
|
|
||||||
try:
|
try:
|
||||||
if not isinstance(inputProfile, ImageCmsProfile):
|
if not isinstance(inputProfile, ImageCmsProfile):
|
||||||
|
@ -300,8 +318,9 @@ def profileToProfile(im, inputProfile, outputProfile, renderingIntent=INTENT_PER
|
||||||
if not isinstance(outputProfile, ImageCmsProfile):
|
if not isinstance(outputProfile, ImageCmsProfile):
|
||||||
outputProfile = ImageCmsProfile(outputProfile)
|
outputProfile = ImageCmsProfile(outputProfile)
|
||||||
transform = ImageCmsTransform(
|
transform = ImageCmsTransform(
|
||||||
inputProfile, outputProfile, im.mode, outputMode, renderingIntent, flags=flags
|
inputProfile, outputProfile, im.mode, outputMode,
|
||||||
)
|
renderingIntent, flags=flags
|
||||||
|
)
|
||||||
if inPlace:
|
if inPlace:
|
||||||
transform.apply_in_place(im)
|
transform.apply_in_place(im)
|
||||||
imOut = None
|
imOut = None
|
||||||
|
@ -323,8 +342,8 @@ def getOpenProfile(profileFilename):
|
||||||
If profileFilename is not a vaild filename for an ICC profile, a PyCMSError
|
If profileFilename is not a vaild filename for an ICC profile, a PyCMSError
|
||||||
will be raised.
|
will be raised.
|
||||||
|
|
||||||
:param profileFilename: String, as a valid filename path to the ICC profile you
|
:param profileFilename: String, as a valid filename path to the ICC profile
|
||||||
wish to open, or a file-like object.
|
you wish to open, or a file-like object.
|
||||||
:returns: A CmsProfile class object.
|
:returns: A CmsProfile class object.
|
||||||
:exception PyCMSError:
|
:exception PyCMSError:
|
||||||
"""
|
"""
|
||||||
|
@ -334,7 +353,10 @@ def getOpenProfile(profileFilename):
|
||||||
except (IOError, TypeError, ValueError) as v:
|
except (IOError, TypeError, ValueError) as v:
|
||||||
raise PyCMSError(v)
|
raise PyCMSError(v)
|
||||||
|
|
||||||
def buildTransform(inputProfile, outputProfile, inMode, outMode, renderingIntent=INTENT_PERCEPTUAL, flags=0):
|
|
||||||
|
def buildTransform(
|
||||||
|
inputProfile, outputProfile, inMode, outMode,
|
||||||
|
renderingIntent=INTENT_PERCEPTUAL, flags=0):
|
||||||
"""
|
"""
|
||||||
(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
|
||||||
|
@ -367,14 +389,14 @@ def buildTransform(inputProfile, outputProfile, inMode, outMode, renderingIntent
|
||||||
manually overridden if you really want to, but I don't know of any
|
manually overridden if you really want to, but I don't know of any
|
||||||
time that would be of use, or would even work).
|
time that would be of use, or would even work).
|
||||||
|
|
||||||
:param inputProfile: String, as a valid filename path to the ICC input profile
|
:param inputProfile: String, as a valid filename path to the ICC input
|
||||||
you wish to use for this transform, or a profile object
|
profile you wish to use for this transform, or a profile object
|
||||||
:param outputProfile: String, as a valid filename path to the ICC output
|
:param outputProfile: String, as a valid filename path to the ICC output
|
||||||
profile you wish to use for this transform, or a profile object
|
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
|
:param inMode: String, as a valid PIL mode that the appropriate profile
|
||||||
supports (i.e. "RGB", "RGBA", "CMYK", etc.)
|
also supports (i.e. "RGB", "RGBA", "CMYK", etc.)
|
||||||
:param outMode: String, as a valid PIL mode that the appropriate profile also
|
:param outMode: String, as a valid PIL mode that the appropriate profile
|
||||||
supports (i.e. "RGB", "RGBA", "CMYK", etc.)
|
also supports (i.e. "RGB", "RGBA", "CMYK", etc.)
|
||||||
:param renderingIntent: Integer (0-3) specifying the rendering intent you
|
:param renderingIntent: Integer (0-3) specifying the rendering intent you
|
||||||
wish to use for the transform
|
wish to use for the transform
|
||||||
|
|
||||||
|
@ -383,28 +405,37 @@ def buildTransform(inputProfile, outputProfile, inMode, outMode, renderingIntent
|
||||||
INTENT_SATURATION = 2 (ImageCms.INTENT_SATURATION)
|
INTENT_SATURATION = 2 (ImageCms.INTENT_SATURATION)
|
||||||
INTENT_ABSOLUTE_COLORIMETRIC = 3 (ImageCms.INTENT_ABSOLUTE_COLORIMETRIC)
|
INTENT_ABSOLUTE_COLORIMETRIC = 3 (ImageCms.INTENT_ABSOLUTE_COLORIMETRIC)
|
||||||
|
|
||||||
see the pyCMS documentation for details on rendering intents and what they do.
|
see the pyCMS documentation for details on rendering intents and what
|
||||||
|
they do.
|
||||||
:param flags: Integer (0-...) specifying additional flags
|
:param flags: Integer (0-...) specifying additional flags
|
||||||
:returns: A CmsTransform class object.
|
:returns: A CmsTransform class object.
|
||||||
:exception PyCMSError:
|
:exception PyCMSError:
|
||||||
"""
|
"""
|
||||||
|
|
||||||
if not isinstance(renderingIntent, int) or not (0 <= renderingIntent <=3):
|
if not isinstance(renderingIntent, int) or not (0 <= renderingIntent <= 3):
|
||||||
raise PyCMSError("renderingIntent must be an integer between 0 and 3")
|
raise PyCMSError("renderingIntent must be an integer between 0 and 3")
|
||||||
|
|
||||||
if not isinstance(flags, int) or not (0 <= flags <= _MAX_FLAG):
|
if not isinstance(flags, int) or not (0 <= flags <= _MAX_FLAG):
|
||||||
raise PyCMSError("flags must be an integer between 0 and %s" + _MAX_FLAG)
|
raise PyCMSError(
|
||||||
|
"flags must be an integer between 0 and %s" + _MAX_FLAG)
|
||||||
|
|
||||||
try:
|
try:
|
||||||
if not isinstance(inputProfile, ImageCmsProfile):
|
if not isinstance(inputProfile, ImageCmsProfile):
|
||||||
inputProfile = ImageCmsProfile(inputProfile)
|
inputProfile = ImageCmsProfile(inputProfile)
|
||||||
if not isinstance(outputProfile, ImageCmsProfile):
|
if not isinstance(outputProfile, ImageCmsProfile):
|
||||||
outputProfile = ImageCmsProfile(outputProfile)
|
outputProfile = ImageCmsProfile(outputProfile)
|
||||||
return ImageCmsTransform(inputProfile, outputProfile, inMode, outMode, renderingIntent, flags=flags)
|
return ImageCmsTransform(
|
||||||
|
inputProfile, outputProfile, inMode, outMode,
|
||||||
|
renderingIntent, flags=flags)
|
||||||
except (IOError, TypeError, ValueError) as v:
|
except (IOError, TypeError, ValueError) as v:
|
||||||
raise PyCMSError(v)
|
raise PyCMSError(v)
|
||||||
|
|
||||||
def buildProofTransform(inputProfile, outputProfile, proofProfile, inMode, outMode, renderingIntent=INTENT_PERCEPTUAL, proofRenderingIntent=INTENT_ABSOLUTE_COLORIMETRIC, flags=FLAGS["SOFTPROOFING"]):
|
|
||||||
|
def buildProofTransform(
|
||||||
|
inputProfile, outputProfile, proofProfile, inMode, outMode,
|
||||||
|
renderingIntent=INTENT_PERCEPTUAL,
|
||||||
|
proofRenderingIntent=INTENT_ABSOLUTE_COLORIMETRIC,
|
||||||
|
flags=FLAGS["SOFTPROOFING"]):
|
||||||
"""
|
"""
|
||||||
(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
|
||||||
|
@ -443,17 +474,17 @@ def buildProofTransform(inputProfile, outputProfile, proofProfile, inMode, outMo
|
||||||
when the simulated device has a much wider gamut than the output
|
when the simulated device has a much wider gamut than the output
|
||||||
device, you may obtain marginal results.
|
device, you may obtain marginal results.
|
||||||
|
|
||||||
:param inputProfile: String, as a valid filename path to the ICC input profile
|
:param inputProfile: String, as a valid filename path to the ICC input
|
||||||
you wish to use for this transform, or a profile object
|
profile you wish to use for this transform, or a profile object
|
||||||
:param outputProfile: String, as a valid filename path to the ICC output
|
:param outputProfile: String, as a valid filename path to the ICC output
|
||||||
(monitor, usually) profile you wish to use for this transform, or a
|
(monitor, usually) profile you wish to use for this transform, or a
|
||||||
profile object
|
profile object
|
||||||
:param proofProfile: String, as a valid filename path to the ICC proof profile
|
:param proofProfile: String, as a valid filename path to the ICC proof
|
||||||
you wish to use for this transform, or a profile object
|
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
|
:param inMode: String, as a valid PIL mode that the appropriate profile
|
||||||
supports (i.e. "RGB", "RGBA", "CMYK", etc.)
|
also supports (i.e. "RGB", "RGBA", "CMYK", etc.)
|
||||||
:param outMode: String, as a valid PIL mode that the appropriate profile also
|
:param outMode: String, as a valid PIL mode that the appropriate profile
|
||||||
supports (i.e. "RGB", "RGBA", "CMYK", etc.)
|
also supports (i.e. "RGB", "RGBA", "CMYK", etc.)
|
||||||
:param renderingIntent: Integer (0-3) specifying the rendering intent you
|
:param renderingIntent: Integer (0-3) specifying the rendering intent you
|
||||||
wish to use for the input->proof (simulated) transform
|
wish to use for the input->proof (simulated) transform
|
||||||
|
|
||||||
|
@ -462,7 +493,8 @@ def buildProofTransform(inputProfile, outputProfile, proofProfile, inMode, outMo
|
||||||
INTENT_SATURATION = 2 (ImageCms.INTENT_SATURATION)
|
INTENT_SATURATION = 2 (ImageCms.INTENT_SATURATION)
|
||||||
INTENT_ABSOLUTE_COLORIMETRIC = 3 (ImageCms.INTENT_ABSOLUTE_COLORIMETRIC)
|
INTENT_ABSOLUTE_COLORIMETRIC = 3 (ImageCms.INTENT_ABSOLUTE_COLORIMETRIC)
|
||||||
|
|
||||||
see the pyCMS documentation for details on rendering intents and what they do.
|
see the pyCMS documentation for details on rendering intents and what
|
||||||
|
they do.
|
||||||
:param proofRenderingIntent: Integer (0-3) specifying the rendering intent you
|
:param proofRenderingIntent: Integer (0-3) specifying the rendering intent you
|
||||||
wish to use for proof->output transform
|
wish to use for proof->output transform
|
||||||
|
|
||||||
|
@ -471,17 +503,19 @@ def buildProofTransform(inputProfile, outputProfile, proofProfile, inMode, outMo
|
||||||
INTENT_SATURATION = 2 (ImageCms.INTENT_SATURATION)
|
INTENT_SATURATION = 2 (ImageCms.INTENT_SATURATION)
|
||||||
INTENT_ABSOLUTE_COLORIMETRIC = 3 (ImageCms.INTENT_ABSOLUTE_COLORIMETRIC)
|
INTENT_ABSOLUTE_COLORIMETRIC = 3 (ImageCms.INTENT_ABSOLUTE_COLORIMETRIC)
|
||||||
|
|
||||||
see the pyCMS documentation for details on rendering intents and what they do.
|
see the pyCMS documentation for details on rendering intents and what
|
||||||
|
they do.
|
||||||
:param flags: Integer (0-...) specifying additional flags
|
:param flags: Integer (0-...) specifying additional flags
|
||||||
:returns: A CmsTransform class object.
|
:returns: A CmsTransform class object.
|
||||||
:exception PyCMSError:
|
:exception PyCMSError:
|
||||||
"""
|
"""
|
||||||
|
|
||||||
if not isinstance(renderingIntent, int) or not (0 <= renderingIntent <=3):
|
if not isinstance(renderingIntent, int) or not (0 <= renderingIntent <= 3):
|
||||||
raise PyCMSError("renderingIntent must be an integer between 0 and 3")
|
raise PyCMSError("renderingIntent must be an integer between 0 and 3")
|
||||||
|
|
||||||
if not isinstance(flags, int) or not (0 <= flags <= _MAX_FLAG):
|
if not isinstance(flags, int) or not (0 <= flags <= _MAX_FLAG):
|
||||||
raise PyCMSError("flags must be an integer between 0 and %s" + _MAX_FLAG)
|
raise PyCMSError(
|
||||||
|
"flags must be an integer between 0 and %s" + _MAX_FLAG)
|
||||||
|
|
||||||
try:
|
try:
|
||||||
if not isinstance(inputProfile, ImageCmsProfile):
|
if not isinstance(inputProfile, ImageCmsProfile):
|
||||||
|
@ -490,13 +524,16 @@ def buildProofTransform(inputProfile, outputProfile, proofProfile, inMode, outMo
|
||||||
outputProfile = ImageCmsProfile(outputProfile)
|
outputProfile = ImageCmsProfile(outputProfile)
|
||||||
if not isinstance(proofProfile, ImageCmsProfile):
|
if not isinstance(proofProfile, ImageCmsProfile):
|
||||||
proofProfile = ImageCmsProfile(proofProfile)
|
proofProfile = ImageCmsProfile(proofProfile)
|
||||||
return ImageCmsTransform(inputProfile, outputProfile, inMode, outMode, renderingIntent, proofProfile, proofRenderingIntent, flags)
|
return ImageCmsTransform(
|
||||||
|
inputProfile, outputProfile, inMode, outMode, renderingIntent,
|
||||||
|
proofProfile, proofRenderingIntent, flags)
|
||||||
except (IOError, TypeError, ValueError) as v:
|
except (IOError, TypeError, ValueError) as v:
|
||||||
raise PyCMSError(v)
|
raise PyCMSError(v)
|
||||||
|
|
||||||
buildTransformFromOpenProfiles = buildTransform
|
buildTransformFromOpenProfiles = buildTransform
|
||||||
buildProofTransformFromOpenProfiles = buildProofTransform
|
buildProofTransformFromOpenProfiles = buildProofTransform
|
||||||
|
|
||||||
|
|
||||||
def applyTransform(im, transform, inPlace=0):
|
def applyTransform(im, transform, inPlace=0):
|
||||||
"""
|
"""
|
||||||
(pyCMS) Applies a transform to a given image.
|
(pyCMS) Applies a transform to a given image.
|
||||||
|
@ -514,8 +551,8 @@ def applyTransform(im, transform, inPlace=0):
|
||||||
is raised.
|
is raised.
|
||||||
|
|
||||||
This function applies a pre-calculated transform (from
|
This function applies a pre-calculated transform (from
|
||||||
ImageCms.buildTransform() or ImageCms.buildTransformFromOpenProfiles()) to an
|
ImageCms.buildTransform() or ImageCms.buildTransformFromOpenProfiles())
|
||||||
image. The transform can be used for multiple images, saving
|
to an image. The transform can be used for multiple images, saving
|
||||||
considerable calcuation time if doing the same conversion multiple times.
|
considerable calcuation time if doing the same conversion multiple times.
|
||||||
|
|
||||||
If you want to modify im in-place instead of receiving a new image as
|
If you want to modify im in-place instead of receiving a new image as
|
||||||
|
@ -528,10 +565,12 @@ def applyTransform(im, transform, inPlace=0):
|
||||||
:param im: A PIL Image object, and im.mode must be the same as the inMode
|
:param im: A PIL Image object, and im.mode must be the same as the inMode
|
||||||
supported by the transform.
|
supported by the transform.
|
||||||
:param transform: A valid CmsTransform class object
|
:param transform: A valid CmsTransform class object
|
||||||
:param inPlace: Bool (1 == True, 0 or None == False). If True, im is modified
|
:param inPlace: Bool (1 == True, 0 or None == False). If True, im is
|
||||||
in place and None is returned, if False, a new Image object with the
|
modified in place and None is returned, if False, a new Image object
|
||||||
transform applied is returned (and im is not changed). The default is False.
|
with the transform applied is returned (and im is not changed). The
|
||||||
:returns: Either None, or a new PIL Image object, depending on the value of inPlace
|
default is False.
|
||||||
|
:returns: Either None, or a new PIL Image object, depending on the value of
|
||||||
|
inPlace
|
||||||
:exception PyCMSError:
|
:exception PyCMSError:
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
@ -546,6 +585,7 @@ def applyTransform(im, transform, inPlace=0):
|
||||||
|
|
||||||
return imOut
|
return imOut
|
||||||
|
|
||||||
|
|
||||||
def createProfile(colorSpace, colorTemp=-1):
|
def createProfile(colorSpace, colorTemp=-1):
|
||||||
"""
|
"""
|
||||||
(pyCMS) Creates a profile.
|
(pyCMS) Creates a profile.
|
||||||
|
@ -562,30 +602,36 @@ def createProfile(colorSpace, colorTemp=-1):
|
||||||
ImageCms.buildTransformFromOpenProfiles() to create a transform to apply
|
ImageCms.buildTransformFromOpenProfiles() to create a transform to apply
|
||||||
to images.
|
to images.
|
||||||
|
|
||||||
:param colorSpace: String, the color space of the profile you wish to create.
|
:param colorSpace: String, the color space of the profile you wish to
|
||||||
|
create.
|
||||||
Currently only "LAB", "XYZ", and "sRGB" are supported.
|
Currently only "LAB", "XYZ", and "sRGB" are supported.
|
||||||
:param colorTemp: Positive integer for the white point for the profile, in
|
:param colorTemp: Positive integer for the white point for the profile, in
|
||||||
degrees Kelvin (i.e. 5000, 6500, 9600, etc.). The default is for D50
|
degrees Kelvin (i.e. 5000, 6500, 9600, etc.). The default is for D50
|
||||||
illuminant if omitted (5000k). colorTemp is ONLY applied to LAB profiles,
|
illuminant if omitted (5000k). colorTemp is ONLY applied to LAB
|
||||||
and is ignored for XYZ and sRGB.
|
profiles, and is ignored for XYZ and sRGB.
|
||||||
:returns: A CmsProfile class object
|
:returns: A CmsProfile class object
|
||||||
:exception PyCMSError:
|
:exception PyCMSError:
|
||||||
"""
|
"""
|
||||||
|
|
||||||
if colorSpace not in ["LAB", "XYZ", "sRGB"]:
|
if colorSpace not in ["LAB", "XYZ", "sRGB"]:
|
||||||
raise PyCMSError("Color space not supported for on-the-fly profile creation (%s)" % colorSpace)
|
raise PyCMSError(
|
||||||
|
"Color space not supported for on-the-fly profile creation (%s)"
|
||||||
|
% colorSpace)
|
||||||
|
|
||||||
if colorSpace == "LAB":
|
if colorSpace == "LAB":
|
||||||
try:
|
try:
|
||||||
colorTemp = float(colorTemp)
|
colorTemp = float(colorTemp)
|
||||||
except:
|
except:
|
||||||
raise PyCMSError("Color temperature must be numeric, \"%s\" not valid" % colorTemp)
|
raise PyCMSError(
|
||||||
|
"Color temperature must be numeric, \"%s\" not valid"
|
||||||
|
% colorTemp)
|
||||||
|
|
||||||
try:
|
try:
|
||||||
return core.createProfile(colorSpace, colorTemp)
|
return core.createProfile(colorSpace, colorTemp)
|
||||||
except (TypeError, ValueError) as v:
|
except (TypeError, ValueError) as v:
|
||||||
raise PyCMSError(v)
|
raise PyCMSError(v)
|
||||||
|
|
||||||
|
|
||||||
def getProfileName(profile):
|
def getProfileName(profile):
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
@ -600,10 +646,10 @@ def getProfileName(profile):
|
||||||
profile was originally created. Sometimes this tag also contains
|
profile was originally created. Sometimes this tag also contains
|
||||||
additional information supplied by the creator.
|
additional information supplied by the creator.
|
||||||
|
|
||||||
:param profile: EITHER a valid CmsProfile object, OR a string of the filename
|
:param profile: EITHER a valid CmsProfile object, OR a string of the
|
||||||
of an ICC profile.
|
filename of an ICC profile.
|
||||||
:returns: A string containing the internal name of the profile as stored in an
|
:returns: A string containing the internal name of the profile as stored
|
||||||
ICC tag.
|
in an ICC tag.
|
||||||
:exception PyCMSError:
|
:exception PyCMSError:
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
@ -619,7 +665,7 @@ def getProfileName(profile):
|
||||||
manufacturer = profile.profile.product_manufacturer
|
manufacturer = profile.profile.product_manufacturer
|
||||||
|
|
||||||
if not (model or manufacturer):
|
if not (model or manufacturer):
|
||||||
return profile.profile.product_description+"\n"
|
return profile.profile.product_description + "\n"
|
||||||
if not manufacturer or len(model) > 30:
|
if not manufacturer or len(model) > 30:
|
||||||
return model + "\n"
|
return model + "\n"
|
||||||
return "%s - %s\n" % (model, manufacturer)
|
return "%s - %s\n" % (model, manufacturer)
|
||||||
|
@ -627,6 +673,7 @@ def getProfileName(profile):
|
||||||
except (AttributeError, IOError, TypeError, ValueError) as v:
|
except (AttributeError, IOError, TypeError, ValueError) as v:
|
||||||
raise PyCMSError(v)
|
raise PyCMSError(v)
|
||||||
|
|
||||||
|
|
||||||
def getProfileInfo(profile):
|
def getProfileInfo(profile):
|
||||||
"""
|
"""
|
||||||
(pyCMS) Gets the internal product information for the given profile.
|
(pyCMS) Gets the internal product information for the given profile.
|
||||||
|
@ -641,10 +688,10 @@ def getProfileInfo(profile):
|
||||||
info tag. This often contains details about the profile, and how it
|
info tag. This often contains details about the profile, and how it
|
||||||
was created, as supplied by the creator.
|
was created, as supplied by the creator.
|
||||||
|
|
||||||
:param profile: EITHER a valid CmsProfile object, OR a string of the filename
|
:param profile: EITHER a valid CmsProfile object, OR a string of the
|
||||||
of an ICC profile.
|
filename of an ICC profile.
|
||||||
:returns: A string containing the internal profile information stored in an ICC
|
:returns: A string containing the internal profile information stored in
|
||||||
tag.
|
an ICC tag.
|
||||||
:exception PyCMSError:
|
:exception PyCMSError:
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
@ -652,7 +699,8 @@ def getProfileInfo(profile):
|
||||||
if not isinstance(profile, ImageCmsProfile):
|
if not isinstance(profile, ImageCmsProfile):
|
||||||
profile = ImageCmsProfile(profile)
|
profile = ImageCmsProfile(profile)
|
||||||
# add an extra newline to preserve pyCMS compatibility
|
# add an extra newline to preserve pyCMS compatibility
|
||||||
# Python, not C. the white point bits weren't working well, so skipping.
|
# Python, not C. the white point bits weren't working well,
|
||||||
|
# so skipping.
|
||||||
# // info was description \r\n\r\n copyright \r\n\r\n K007 tag \r\n\r\n whitepoint
|
# // info was description \r\n\r\n copyright \r\n\r\n K007 tag \r\n\r\n whitepoint
|
||||||
description = profile.profile.product_description
|
description = profile.profile.product_description
|
||||||
cpright = profile.profile.product_copyright
|
cpright = profile.profile.product_copyright
|
||||||
|
@ -660,7 +708,7 @@ def getProfileInfo(profile):
|
||||||
for elt in (description, cpright):
|
for elt in (description, cpright):
|
||||||
if elt:
|
if elt:
|
||||||
arr.append(elt)
|
arr.append(elt)
|
||||||
return "\r\n\r\n".join(arr)+"\r\n\r\n"
|
return "\r\n\r\n".join(arr) + "\r\n\r\n"
|
||||||
|
|
||||||
except (AttributeError, IOError, TypeError, ValueError) as v:
|
except (AttributeError, IOError, TypeError, ValueError) as v:
|
||||||
raise PyCMSError(v)
|
raise PyCMSError(v)
|
||||||
|
@ -679,10 +727,10 @@ def getProfileCopyright(profile):
|
||||||
Use this function to obtain the information stored in the profile's
|
Use this function to obtain the information stored in the profile's
|
||||||
copyright tag.
|
copyright tag.
|
||||||
|
|
||||||
:param profile: EITHER a valid CmsProfile object, OR a string of the filename
|
:param profile: EITHER a valid CmsProfile object, OR a string of the
|
||||||
of an ICC profile.
|
filename of an ICC profile.
|
||||||
:returns: A string containing the internal profile information stored in an ICC
|
:returns: A string containing the internal profile information stored in
|
||||||
tag.
|
an ICC tag.
|
||||||
:exception PyCMSError:
|
:exception PyCMSError:
|
||||||
"""
|
"""
|
||||||
try:
|
try:
|
||||||
|
@ -693,6 +741,7 @@ def getProfileCopyright(profile):
|
||||||
except (AttributeError, IOError, TypeError, ValueError) as v:
|
except (AttributeError, IOError, TypeError, ValueError) as v:
|
||||||
raise PyCMSError(v)
|
raise PyCMSError(v)
|
||||||
|
|
||||||
|
|
||||||
def getProfileManufacturer(profile):
|
def getProfileManufacturer(profile):
|
||||||
"""
|
"""
|
||||||
(pyCMS) Gets the manufacturer for the given profile.
|
(pyCMS) Gets the manufacturer for the given profile.
|
||||||
|
@ -700,16 +749,16 @@ def getProfileManufacturer(profile):
|
||||||
If profile isn't a valid CmsProfile object or filename to a profile,
|
If profile isn't a valid CmsProfile object or filename to a profile,
|
||||||
a PyCMSError is raised.
|
a PyCMSError is raised.
|
||||||
|
|
||||||
If an error occurs while trying to obtain the manufacturer tag, a PyCMSError
|
If an error occurs while trying to obtain the manufacturer tag, a
|
||||||
is raised
|
PyCMSError is raised
|
||||||
|
|
||||||
Use this function to obtain the information stored in the profile's
|
Use this function to obtain the information stored in the profile's
|
||||||
manufacturer tag.
|
manufacturer tag.
|
||||||
|
|
||||||
:param profile: EITHER a valid CmsProfile object, OR a string of the filename
|
:param profile: EITHER a valid CmsProfile object, OR a string of the
|
||||||
of an ICC profile.
|
filename of an ICC profile.
|
||||||
:returns: A string containing the internal profile information stored in an ICC
|
:returns: A string containing the internal profile information stored in
|
||||||
tag.
|
an ICC tag.
|
||||||
:exception PyCMSError:
|
:exception PyCMSError:
|
||||||
"""
|
"""
|
||||||
try:
|
try:
|
||||||
|
@ -720,6 +769,7 @@ def getProfileManufacturer(profile):
|
||||||
except (AttributeError, IOError, TypeError, ValueError) as v:
|
except (AttributeError, IOError, TypeError, ValueError) as v:
|
||||||
raise PyCMSError(v)
|
raise PyCMSError(v)
|
||||||
|
|
||||||
|
|
||||||
def getProfileModel(profile):
|
def getProfileModel(profile):
|
||||||
"""
|
"""
|
||||||
(pyCMS) Gets the model for the given profile.
|
(pyCMS) Gets the model for the given profile.
|
||||||
|
@ -733,10 +783,10 @@ def getProfileModel(profile):
|
||||||
Use this function to obtain the information stored in the profile's
|
Use this function to obtain the information stored in the profile's
|
||||||
model tag.
|
model tag.
|
||||||
|
|
||||||
:param profile: EITHER a valid CmsProfile object, OR a string of the filename
|
:param profile: EITHER a valid CmsProfile object, OR a string of the
|
||||||
of an ICC profile.
|
filename of an ICC profile.
|
||||||
:returns: A string containing the internal profile information stored in an ICC
|
:returns: A string containing the internal profile information stored in
|
||||||
tag.
|
an ICC tag.
|
||||||
:exception PyCMSError:
|
:exception PyCMSError:
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
@ -748,6 +798,7 @@ def getProfileModel(profile):
|
||||||
except (AttributeError, IOError, TypeError, ValueError) as v:
|
except (AttributeError, IOError, TypeError, ValueError) as v:
|
||||||
raise PyCMSError(v)
|
raise PyCMSError(v)
|
||||||
|
|
||||||
|
|
||||||
def getProfileDescription(profile):
|
def getProfileDescription(profile):
|
||||||
"""
|
"""
|
||||||
(pyCMS) Gets the description for the given profile.
|
(pyCMS) Gets the description for the given profile.
|
||||||
|
@ -761,10 +812,10 @@ def getProfileDescription(profile):
|
||||||
Use this function to obtain the information stored in the profile's
|
Use this function to obtain the information stored in the profile's
|
||||||
description tag.
|
description tag.
|
||||||
|
|
||||||
:param profile: EITHER a valid CmsProfile object, OR a string of the filename
|
:param profile: EITHER a valid CmsProfile object, OR a string of the
|
||||||
of an ICC profile.
|
filename of an ICC profile.
|
||||||
:returns: A string containing the internal profile information stored in an ICC
|
:returns: A string containing the internal profile information stored in an
|
||||||
tag.
|
ICC tag.
|
||||||
:exception PyCMSError:
|
:exception PyCMSError:
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
@ -793,16 +844,18 @@ def getDefaultIntent(profile):
|
||||||
If you wish to use a different intent than returned, use
|
If you wish to use a different intent than returned, use
|
||||||
ImageCms.isIntentSupported() to verify it will work first.
|
ImageCms.isIntentSupported() to verify it will work first.
|
||||||
|
|
||||||
:param profile: EITHER a valid CmsProfile object, OR a string of the filename
|
:param profile: EITHER a valid CmsProfile object, OR a string of the
|
||||||
of an ICC profile.
|
filename of an ICC profile.
|
||||||
:returns: Integer 0-3 specifying the default rendering intent for this profile.
|
:returns: Integer 0-3 specifying the default rendering intent for this
|
||||||
|
profile.
|
||||||
|
|
||||||
INTENT_PERCEPTUAL = 0 (DEFAULT) (ImageCms.INTENT_PERCEPTUAL)
|
INTENT_PERCEPTUAL = 0 (DEFAULT) (ImageCms.INTENT_PERCEPTUAL)
|
||||||
INTENT_RELATIVE_COLORIMETRIC = 1 (ImageCms.INTENT_RELATIVE_COLORIMETRIC)
|
INTENT_RELATIVE_COLORIMETRIC = 1 (ImageCms.INTENT_RELATIVE_COLORIMETRIC)
|
||||||
INTENT_SATURATION = 2 (ImageCms.INTENT_SATURATION)
|
INTENT_SATURATION = 2 (ImageCms.INTENT_SATURATION)
|
||||||
INTENT_ABSOLUTE_COLORIMETRIC = 3 (ImageCms.INTENT_ABSOLUTE_COLORIMETRIC)
|
INTENT_ABSOLUTE_COLORIMETRIC = 3 (ImageCms.INTENT_ABSOLUTE_COLORIMETRIC)
|
||||||
|
|
||||||
see the pyCMS documentation for details on rendering intents and what they do.
|
see the pyCMS documentation for details on rendering intents and what
|
||||||
|
they do.
|
||||||
:exception PyCMSError:
|
:exception PyCMSError:
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
@ -813,6 +866,7 @@ def getDefaultIntent(profile):
|
||||||
except (AttributeError, IOError, TypeError, ValueError) as v:
|
except (AttributeError, IOError, TypeError, ValueError) as v:
|
||||||
raise PyCMSError(v)
|
raise PyCMSError(v)
|
||||||
|
|
||||||
|
|
||||||
def isIntentSupported(profile, intent, direction):
|
def isIntentSupported(profile, intent, direction):
|
||||||
"""
|
"""
|
||||||
(pyCMS) Checks if a given intent is supported.
|
(pyCMS) Checks if a given intent is supported.
|
||||||
|
@ -828,17 +882,18 @@ def isIntentSupported(profile, intent, direction):
|
||||||
potential PyCMSError that will occur if they don't support the modes
|
potential PyCMSError that will occur if they don't support the modes
|
||||||
you select.
|
you select.
|
||||||
|
|
||||||
:param profile: EITHER a valid CmsProfile object, OR a string of the filename
|
:param profile: EITHER a valid CmsProfile object, OR a string of the
|
||||||
of an ICC profile.
|
filename of an ICC profile.
|
||||||
:param intent: Integer (0-3) specifying the rendering intent you wish to use
|
:param intent: Integer (0-3) specifying the rendering intent you wish to
|
||||||
with this profile
|
use with this profile
|
||||||
|
|
||||||
INTENT_PERCEPTUAL = 0 (DEFAULT) (ImageCms.INTENT_PERCEPTUAL)
|
INTENT_PERCEPTUAL = 0 (DEFAULT) (ImageCms.INTENT_PERCEPTUAL)
|
||||||
INTENT_RELATIVE_COLORIMETRIC = 1 (ImageCms.INTENT_RELATIVE_COLORIMETRIC)
|
INTENT_RELATIVE_COLORIMETRIC = 1 (ImageCms.INTENT_RELATIVE_COLORIMETRIC)
|
||||||
INTENT_SATURATION = 2 (ImageCms.INTENT_SATURATION)
|
INTENT_SATURATION = 2 (ImageCms.INTENT_SATURATION)
|
||||||
INTENT_ABSOLUTE_COLORIMETRIC = 3 (ImageCms.INTENT_ABSOLUTE_COLORIMETRIC)
|
INTENT_ABSOLUTE_COLORIMETRIC = 3 (ImageCms.INTENT_ABSOLUTE_COLORIMETRIC)
|
||||||
|
|
||||||
see the pyCMS documentation for details on rendering intents and what they do.
|
see the pyCMS documentation for details on rendering intents and what
|
||||||
|
they do.
|
||||||
:param direction: Integer specifing if the profile is to be used for input,
|
:param direction: Integer specifing if the profile is to be used for input,
|
||||||
output, or proof
|
output, or proof
|
||||||
|
|
||||||
|
@ -862,6 +917,7 @@ def isIntentSupported(profile, intent, direction):
|
||||||
except (AttributeError, IOError, TypeError, ValueError) as v:
|
except (AttributeError, IOError, TypeError, ValueError) as v:
|
||||||
raise PyCMSError(v)
|
raise PyCMSError(v)
|
||||||
|
|
||||||
|
|
||||||
def versions():
|
def versions():
|
||||||
"""
|
"""
|
||||||
(pyCMS) Fetches versions.
|
(pyCMS) Fetches versions.
|
||||||
|
@ -869,8 +925,9 @@ def versions():
|
||||||
|
|
||||||
import sys
|
import sys
|
||||||
return (
|
return (
|
||||||
VERSION, core.littlecms_version, sys.version.split()[0], Image.VERSION
|
VERSION, core.littlecms_version,
|
||||||
)
|
sys.version.split()[0], Image.VERSION
|
||||||
|
)
|
||||||
|
|
||||||
# --------------------------------------------------------------------
|
# --------------------------------------------------------------------
|
||||||
|
|
||||||
|
@ -880,14 +937,16 @@ if __name__ == "__main__":
|
||||||
from PIL import ImageCms
|
from PIL import ImageCms
|
||||||
print(__doc__)
|
print(__doc__)
|
||||||
|
|
||||||
for f in dir(pyCMS):
|
for f in dir(ImageCms):
|
||||||
print("="*80)
|
doc = None
|
||||||
print("%s" %f)
|
|
||||||
|
|
||||||
try:
|
try:
|
||||||
exec ("doc = ImageCms.%s.__doc__" %(f))
|
exec("doc = %s.__doc__" % (f))
|
||||||
if "pyCMS" in doc:
|
if "pyCMS" in doc:
|
||||||
# so we don't get the __doc__ string for imported modules
|
# so we don't get the __doc__ string for imported modules
|
||||||
|
print("=" * 80)
|
||||||
|
print("%s" % f)
|
||||||
print(doc)
|
print(doc)
|
||||||
except AttributeError:
|
except (AttributeError, TypeError):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
# End of file
|
||||||
|
|
|
@ -15,11 +15,12 @@
|
||||||
|
|
||||||
__version__ = "0.1"
|
__version__ = "0.1"
|
||||||
|
|
||||||
from PIL import Image, ImageFile, _binary
|
from PIL import Image, ImageFile
|
||||||
import struct
|
import struct
|
||||||
import os
|
import os
|
||||||
import io
|
import io
|
||||||
|
|
||||||
|
|
||||||
def _parse_codestream(fp):
|
def _parse_codestream(fp):
|
||||||
"""Parse the JPEG 2000 codestream to extract the size and component
|
"""Parse the JPEG 2000 codestream to extract the size and component
|
||||||
count from the SIZ marker segment, returning a PIL (size, mode) tuple."""
|
count from the SIZ marker segment, returning a PIL (size, mode) tuple."""
|
||||||
|
@ -28,7 +29,7 @@ def _parse_codestream(fp):
|
||||||
lsiz = struct.unpack('>H', hdr)[0]
|
lsiz = struct.unpack('>H', hdr)[0]
|
||||||
siz = hdr + fp.read(lsiz - 2)
|
siz = hdr + fp.read(lsiz - 2)
|
||||||
lsiz, rsiz, xsiz, ysiz, xosiz, yosiz, xtsiz, ytsiz, \
|
lsiz, rsiz, xsiz, ysiz, xosiz, yosiz, xtsiz, ytsiz, \
|
||||||
xtosiz, ytosiz, csiz \
|
xtosiz, ytosiz, csiz \
|
||||||
= struct.unpack('>HHIIIIIIIIH', siz[:38])
|
= struct.unpack('>HHIIIIIIIIH', siz[:38])
|
||||||
ssiz = [None]*csiz
|
ssiz = [None]*csiz
|
||||||
xrsiz = [None]*csiz
|
xrsiz = [None]*csiz
|
||||||
|
@ -51,6 +52,7 @@ def _parse_codestream(fp):
|
||||||
|
|
||||||
return (size, mode)
|
return (size, mode)
|
||||||
|
|
||||||
|
|
||||||
def _parse_jp2_header(fp):
|
def _parse_jp2_header(fp):
|
||||||
"""Parse the JP2 header box to extract size, component count and
|
"""Parse the JP2 header box to extract size, component count and
|
||||||
color space information, returning a PIL (size, mode) tuple."""
|
color space information, returning a PIL (size, mode) tuple."""
|
||||||
|
@ -90,7 +92,7 @@ def _parse_jp2_header(fp):
|
||||||
|
|
||||||
if tbox == b'ihdr':
|
if tbox == b'ihdr':
|
||||||
height, width, nc, bpc, c, unkc, ipr \
|
height, width, nc, bpc, c, unkc, ipr \
|
||||||
= struct.unpack('>IIHBBBB', content)
|
= struct.unpack('>IIHBBBB', content)
|
||||||
size = (width, height)
|
size = (width, height)
|
||||||
if unkc:
|
if unkc:
|
||||||
if nc == 1:
|
if nc == 1:
|
||||||
|
@ -112,13 +114,13 @@ def _parse_jp2_header(fp):
|
||||||
elif nc == 4:
|
elif nc == 4:
|
||||||
mode = 'RGBA'
|
mode = 'RGBA'
|
||||||
break
|
break
|
||||||
elif cs == 17: # grayscale
|
elif cs == 17: # grayscale
|
||||||
if nc == 1:
|
if nc == 1:
|
||||||
mode = 'L'
|
mode = 'L'
|
||||||
elif nc == 2:
|
elif nc == 2:
|
||||||
mode = 'LA'
|
mode = 'LA'
|
||||||
break
|
break
|
||||||
elif cs == 18: # sYCC
|
elif cs == 18: # sYCC
|
||||||
if nc == 3:
|
if nc == 3:
|
||||||
mode = 'RGB'
|
mode = 'RGB'
|
||||||
elif nc == 4:
|
elif nc == 4:
|
||||||
|
@ -127,6 +129,7 @@ def _parse_jp2_header(fp):
|
||||||
|
|
||||||
return (size, mode)
|
return (size, mode)
|
||||||
|
|
||||||
|
|
||||||
##
|
##
|
||||||
# Image plugin for JPEG2000 images.
|
# Image plugin for JPEG2000 images.
|
||||||
|
|
||||||
|
@ -180,10 +183,12 @@ class Jpeg2KImageFile(ImageFile.ImageFile):
|
||||||
|
|
||||||
ImageFile.ImageFile.load(self)
|
ImageFile.ImageFile.load(self)
|
||||||
|
|
||||||
|
|
||||||
def _accept(prefix):
|
def _accept(prefix):
|
||||||
return (prefix[:4] == b'\xff\x4f\xff\x51'
|
return (prefix[:4] == b'\xff\x4f\xff\x51'
|
||||||
or prefix[:12] == b'\x00\x00\x00\x0cjP \x0d\x0a\x87\x0a')
|
or prefix[:12] == b'\x00\x00\x00\x0cjP \x0d\x0a\x87\x0a')
|
||||||
|
|
||||||
|
|
||||||
# ------------------------------------------------------------
|
# ------------------------------------------------------------
|
||||||
# Save support
|
# Save support
|
||||||
|
|
||||||
|
@ -228,7 +233,7 @@ def _save(im, fp, filename):
|
||||||
progression,
|
progression,
|
||||||
cinema_mode,
|
cinema_mode,
|
||||||
fd
|
fd
|
||||||
)
|
)
|
||||||
|
|
||||||
ImageFile._save(im, fp, [('jpeg2k', (0, 0)+im.size, 0, kind)])
|
ImageFile._save(im, fp, [('jpeg2k', (0, 0)+im.size, 0, kind)])
|
||||||
|
|
||||||
|
|
|
@ -9,12 +9,13 @@ except ImportError:
|
||||||
|
|
||||||
SRGB = "Tests/icc/sRGB.icm"
|
SRGB = "Tests/icc/sRGB.icm"
|
||||||
|
|
||||||
|
|
||||||
def test_sanity():
|
def test_sanity():
|
||||||
|
|
||||||
# basic smoke test.
|
# basic smoke test.
|
||||||
# this mostly follows the cms_test outline.
|
# this mostly follows the cms_test outline.
|
||||||
|
|
||||||
v = ImageCms.versions() # should return four strings
|
v = ImageCms.versions() # should return four strings
|
||||||
assert_equal(v[0], '1.0.0 pil')
|
assert_equal(v[0], '1.0.0 pil')
|
||||||
assert_equal(list(map(type, v)), [str, str, str, str])
|
assert_equal(list(map(type, v)), [str, str, str, str])
|
||||||
|
|
||||||
|
@ -24,10 +25,19 @@ def test_sanity():
|
||||||
i = ImageCms.profileToProfile(lena(), SRGB, SRGB)
|
i = ImageCms.profileToProfile(lena(), SRGB, SRGB)
|
||||||
assert_image(i, "RGB", (128, 128))
|
assert_image(i, "RGB", (128, 128))
|
||||||
|
|
||||||
|
i = lena()
|
||||||
|
ImageCms.profileToProfile(i, SRGB, SRGB, inPlace=True)
|
||||||
|
assert_image(i, "RGB", (128, 128))
|
||||||
|
|
||||||
t = ImageCms.buildTransform(SRGB, SRGB, "RGB", "RGB")
|
t = ImageCms.buildTransform(SRGB, SRGB, "RGB", "RGB")
|
||||||
i = ImageCms.applyTransform(lena(), t)
|
i = ImageCms.applyTransform(lena(), t)
|
||||||
assert_image(i, "RGB", (128, 128))
|
assert_image(i, "RGB", (128, 128))
|
||||||
|
|
||||||
|
i = lena()
|
||||||
|
t = ImageCms.buildTransform(SRGB, SRGB, "RGB", "RGB")
|
||||||
|
ImageCms.applyTransform(lena(), t, inPlace=True)
|
||||||
|
assert_image(i, "RGB", (128, 128))
|
||||||
|
|
||||||
p = ImageCms.createProfile("sRGB")
|
p = ImageCms.createProfile("sRGB")
|
||||||
o = ImageCms.getOpenProfile(SRGB)
|
o = ImageCms.getOpenProfile(SRGB)
|
||||||
t = ImageCms.buildTransformFromOpenProfiles(p, o, "RGB", "RGB")
|
t = ImageCms.buildTransformFromOpenProfiles(p, o, "RGB", "RGB")
|
||||||
|
@ -41,24 +51,47 @@ def test_sanity():
|
||||||
assert_image(i, "RGB", (128, 128))
|
assert_image(i, "RGB", (128, 128))
|
||||||
|
|
||||||
# test PointTransform convenience API
|
# test PointTransform convenience API
|
||||||
im = lena().point(t)
|
lena().point(t)
|
||||||
|
|
||||||
|
|
||||||
def test_name():
|
def test_name():
|
||||||
# get profile information for file
|
# get profile information for file
|
||||||
assert_equal(ImageCms.getProfileName(SRGB).strip(),
|
assert_equal(ImageCms.getProfileName(SRGB).strip(),
|
||||||
'IEC 61966-2.1 Default RGB colour space - sRGB')
|
'IEC 61966-2.1 Default RGB colour space - sRGB')
|
||||||
def x_test_info():
|
|
||||||
|
|
||||||
|
def test_info():
|
||||||
assert_equal(ImageCms.getProfileInfo(SRGB).splitlines(),
|
assert_equal(ImageCms.getProfileInfo(SRGB).splitlines(),
|
||||||
['sRGB IEC61966-2.1', '',
|
['sRGB IEC61966-2.1', '',
|
||||||
'Copyright (c) 1998 Hewlett-Packard Company', '',
|
'Copyright (c) 1998 Hewlett-Packard Company', ''])
|
||||||
'WhitePoint : D65 (daylight)', '',
|
|
||||||
'Tests/icc/sRGB.icm'])
|
|
||||||
|
def test_copyright():
|
||||||
|
assert_equal(ImageCms.getProfileCopyright(SRGB).strip(),
|
||||||
|
'Copyright (c) 1998 Hewlett-Packard Company')
|
||||||
|
|
||||||
|
|
||||||
|
def test_manufacturer():
|
||||||
|
assert_equal(ImageCms.getProfileManufacturer(SRGB).strip(),
|
||||||
|
'IEC http://www.iec.ch')
|
||||||
|
|
||||||
|
|
||||||
|
def test_model():
|
||||||
|
assert_equal(ImageCms.getProfileModel(SRGB).strip(),
|
||||||
|
'IEC 61966-2.1 Default RGB colour space - sRGB')
|
||||||
|
|
||||||
|
|
||||||
|
def test_description():
|
||||||
|
assert_equal(ImageCms.getProfileDescription(SRGB).strip(),
|
||||||
|
'sRGB IEC61966-2.1')
|
||||||
|
|
||||||
|
|
||||||
def test_intent():
|
def test_intent():
|
||||||
assert_equal(ImageCms.getDefaultIntent(SRGB), 0)
|
assert_equal(ImageCms.getDefaultIntent(SRGB), 0)
|
||||||
assert_equal(ImageCms.isIntentSupported(
|
assert_equal(ImageCms.isIntentSupported(
|
||||||
SRGB, ImageCms.INTENT_ABSOLUTE_COLORIMETRIC,
|
SRGB, ImageCms.INTENT_ABSOLUTE_COLORIMETRIC,
|
||||||
ImageCms.DIRECTION_INPUT), 1)
|
ImageCms.DIRECTION_INPUT), 1)
|
||||||
|
|
||||||
|
|
||||||
def test_profile_object():
|
def test_profile_object():
|
||||||
# same, using profile object
|
# same, using profile object
|
||||||
|
@ -69,8 +102,9 @@ def test_profile_object():
|
||||||
# ['sRGB built-in', '', 'WhitePoint : D65 (daylight)', '', ''])
|
# ['sRGB built-in', '', 'WhitePoint : D65 (daylight)', '', ''])
|
||||||
assert_equal(ImageCms.getDefaultIntent(p), 0)
|
assert_equal(ImageCms.getDefaultIntent(p), 0)
|
||||||
assert_equal(ImageCms.isIntentSupported(
|
assert_equal(ImageCms.isIntentSupported(
|
||||||
p, ImageCms.INTENT_ABSOLUTE_COLORIMETRIC,
|
p, ImageCms.INTENT_ABSOLUTE_COLORIMETRIC,
|
||||||
ImageCms.DIRECTION_INPUT), 1)
|
ImageCms.DIRECTION_INPUT), 1)
|
||||||
|
|
||||||
|
|
||||||
def test_extensions():
|
def test_extensions():
|
||||||
# extensions
|
# extensions
|
||||||
|
@ -79,12 +113,21 @@ def test_extensions():
|
||||||
assert_equal(ImageCms.getProfileName(p).strip(),
|
assert_equal(ImageCms.getProfileName(p).strip(),
|
||||||
'IEC 61966-2.1 Default RGB colour space - sRGB')
|
'IEC 61966-2.1 Default RGB colour space - sRGB')
|
||||||
|
|
||||||
|
|
||||||
def test_exceptions():
|
def test_exceptions():
|
||||||
# the procedural pyCMS API uses PyCMSError for all sorts of errors
|
# the procedural pyCMS API uses PyCMSError for all sorts of errors
|
||||||
assert_exception(ImageCms.PyCMSError, lambda: ImageCms.profileToProfile(lena(), "foo", "bar"))
|
assert_exception(
|
||||||
assert_exception(ImageCms.PyCMSError, lambda: ImageCms.buildTransform("foo", "bar", "RGB", "RGB"))
|
ImageCms.PyCMSError,
|
||||||
assert_exception(ImageCms.PyCMSError, lambda: ImageCms.getProfileName(None))
|
lambda: ImageCms.profileToProfile(lena(), "foo", "bar"))
|
||||||
assert_exception(ImageCms.PyCMSError, lambda: ImageCms.isIntentSupported(SRGB, None, None))
|
assert_exception(
|
||||||
|
ImageCms.PyCMSError,
|
||||||
|
lambda: ImageCms.buildTransform("foo", "bar", "RGB", "RGB"))
|
||||||
|
assert_exception(
|
||||||
|
ImageCms.PyCMSError,
|
||||||
|
lambda: ImageCms.getProfileName(None))
|
||||||
|
assert_exception(
|
||||||
|
ImageCms.PyCMSError,
|
||||||
|
lambda: ImageCms.isIntentSupported(SRGB, None, None))
|
||||||
|
|
||||||
|
|
||||||
def test_display_profile():
|
def test_display_profile():
|
||||||
|
@ -93,37 +136,37 @@ def test_display_profile():
|
||||||
|
|
||||||
|
|
||||||
def test_lab_color_profile():
|
def test_lab_color_profile():
|
||||||
pLab = ImageCms.createProfile("LAB", 5000)
|
ImageCms.createProfile("LAB", 5000)
|
||||||
pLab = ImageCms.createProfile("LAB", 6500)
|
ImageCms.createProfile("LAB", 6500)
|
||||||
|
|
||||||
|
|
||||||
def test_simple_lab():
|
def test_simple_lab():
|
||||||
i = Image.new('RGB', (10,10), (128,128,128))
|
i = Image.new('RGB', (10, 10), (128, 128, 128))
|
||||||
|
|
||||||
pLab = ImageCms.createProfile("LAB")
|
pLab = ImageCms.createProfile("LAB")
|
||||||
t = ImageCms.buildTransform(SRGB, pLab, "RGB", "LAB")
|
t = ImageCms.buildTransform(SRGB, pLab, "RGB", "LAB")
|
||||||
|
|
||||||
i_lab = ImageCms.applyTransform(i, t)
|
i_lab = ImageCms.applyTransform(i, t)
|
||||||
|
|
||||||
|
|
||||||
assert_equal(i_lab.mode, 'LAB')
|
assert_equal(i_lab.mode, 'LAB')
|
||||||
|
|
||||||
k = i_lab.getpixel((0,0))
|
k = i_lab.getpixel((0, 0))
|
||||||
assert_equal(k, (137,128,128)) # not a linear luminance map. so L != 128
|
assert_equal(k, (137, 128, 128)) # not a linear luminance map. so L != 128
|
||||||
|
|
||||||
L = i_lab.getdata(0)
|
L = i_lab.getdata(0)
|
||||||
a = i_lab.getdata(1)
|
a = i_lab.getdata(1)
|
||||||
b = i_lab.getdata(2)
|
b = i_lab.getdata(2)
|
||||||
|
|
||||||
assert_equal(list(L), [137]*100)
|
assert_equal(list(L), [137] * 100)
|
||||||
assert_equal(list(a), [128]*100)
|
assert_equal(list(a), [128] * 100)
|
||||||
assert_equal(list(b), [128]*100)
|
assert_equal(list(b), [128] * 100)
|
||||||
|
|
||||||
|
|
||||||
def test_lab_color():
|
def test_lab_color():
|
||||||
pLab = ImageCms.createProfile("LAB")
|
pLab = ImageCms.createProfile("LAB")
|
||||||
t = ImageCms.buildTransform(SRGB, pLab, "RGB", "LAB")
|
t = ImageCms.buildTransform(SRGB, pLab, "RGB", "LAB")
|
||||||
# need to add a type mapping for some PIL type to TYPE_Lab_8 in findLCMSType,
|
# Need to add a type mapping for some PIL type to TYPE_Lab_8 in
|
||||||
# and have that mapping work back to a PIL mode. (likely RGB)
|
# findLCMSType, and have that mapping work back to a PIL mode (likely RGB).
|
||||||
i = ImageCms.applyTransform(lena(), t)
|
i = ImageCms.applyTransform(lena(), t)
|
||||||
assert_image(i, "LAB", (128, 128))
|
assert_image(i, "LAB", (128, 128))
|
||||||
|
|
||||||
|
@ -133,6 +176,7 @@ def test_lab_color():
|
||||||
|
|
||||||
assert_image_similar(i, target, 30)
|
assert_image_similar(i, target, 30)
|
||||||
|
|
||||||
|
|
||||||
def test_lab_srgb():
|
def test_lab_srgb():
|
||||||
pLab = ImageCms.createProfile("LAB")
|
pLab = ImageCms.createProfile("LAB")
|
||||||
t = ImageCms.buildTransform(pLab, SRGB, "LAB", "RGB")
|
t = ImageCms.buildTransform(pLab, SRGB, "LAB", "RGB")
|
||||||
|
@ -145,6 +189,7 @@ def test_lab_srgb():
|
||||||
|
|
||||||
assert_image_similar(lena(), img_srgb, 30)
|
assert_image_similar(lena(), img_srgb, 30)
|
||||||
|
|
||||||
|
|
||||||
def test_lab_roundtrip():
|
def test_lab_roundtrip():
|
||||||
# check to see if we're at least internally consistent.
|
# check to see if we're at least internally consistent.
|
||||||
pLab = ImageCms.createProfile("LAB")
|
pLab = ImageCms.createProfile("LAB")
|
||||||
|
@ -156,5 +201,3 @@ def test_lab_roundtrip():
|
||||||
out = ImageCms.applyTransform(i, t2)
|
out = ImageCms.applyTransform(i, t2)
|
||||||
|
|
||||||
assert_image_similar(lena(), out, 2)
|
assert_image_similar(lena(), out, 2)
|
||||||
|
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue
Block a user