Merge pull request #380 from wiredfool/lcms2

LCMS1 replaced with LCMS2
This commit is contained in:
Alex Clark ☺ 2013-10-16 09:09:15 -07:00
commit 55a0792815
17 changed files with 432 additions and 85 deletions

View File

@ -6,7 +6,7 @@ python:
- 3.2 - 3.2
- 3.3 - 3.3
install: "sudo apt-get -qq install libfreetype6-dev liblcms1-dev libwebp-dev" install: "sudo apt-get -qq install libfreetype6-dev liblcms2-dev libwebp-dev"
script: script:
- python setup.py clean - python setup.py clean

View File

@ -200,6 +200,7 @@ _MODEINFO = {
"RGBA": ("RGB", "L", ("R", "G", "B", "A")), "RGBA": ("RGB", "L", ("R", "G", "B", "A")),
"CMYK": ("RGB", "L", ("C", "M", "Y", "K")), "CMYK": ("RGB", "L", ("C", "M", "Y", "K")),
"YCbCr": ("RGB", "L", ("Y", "Cb", "Cr")), "YCbCr": ("RGB", "L", ("Y", "Cb", "Cr")),
"LAB": ("RGB", "L", ("L", "A", "B")),
# Experimental modes include I;16, I;16L, I;16B, RGBa, BGR;15, and # Experimental modes include I;16, I;16L, I;16B, RGBa, BGR;15, and
# BGR;24. Use these modes only if you know exactly what you're # BGR;24. Use these modes only if you know exactly what you're
@ -224,6 +225,7 @@ _MODE_CONV = {
"RGBA": ('|u1', 4), "RGBA": ('|u1', 4),
"CMYK": ('|u1', 4), "CMYK": ('|u1', 4),
"YCbCr": ('|u1', 3), "YCbCr": ('|u1', 3),
"LAB": ('|u1', 3), # UNDONE - unsigned |u1i1i1
"I;16": ('=u2', None), "I;16": ('=u2', None),
"I;16B": ('>u2', None), "I;16B": ('>u2', None),
"I;16L": ('<u2', None), "I;16L": ('<u2', None),

View File

@ -42,6 +42,8 @@ pyCMS
Version History: Version History:
1.0.0 pil Oct 2013 Port to LCMS 2.
0.1.0 pil mod March 10, 2009 0.1.0 pil mod March 10, 2009
Renamed display profile to proof profile. The proof Renamed display profile to proof profile. The proof
@ -77,7 +79,7 @@ pyCMS
""" """
VERSION = "0.1.0 pil" VERSION = "1.0.0 pil"
# --------------------------------------------------------------------. # --------------------------------------------------------------------.
@ -151,8 +153,8 @@ class ImageCmsProfile:
self.profile = profile self.profile = profile
self.filename = filename self.filename = filename
if profile: if profile:
self.product_name = profile.product_name self.product_name = None #profile.product_name
self.product_info = 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
@ -564,10 +566,10 @@ def createProfile(colorSpace, colorTemp=-1):
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":
if isinstance(colorTemp, float): try:
colorTemp = int(colorTemp + 0.5) colorTemp = float(colorTemp)
if not isinstance(colorTemp, int): except:
raise PyCMSError("Color temperature must be a positive integer, \"%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)
@ -597,7 +599,19 @@ def getProfileName(profile):
# add an extra newline to preserve pyCMS compatibility # add an extra newline to preserve pyCMS compatibility
if not isinstance(profile, ImageCmsProfile): if not isinstance(profile, ImageCmsProfile):
profile = ImageCmsProfile(profile) profile = ImageCmsProfile(profile)
return profile.profile.product_name + "\n" # do it in python, not c.
# // name was "%s - %s" (model, manufacturer) || Description ,
# // but if the Model and Manufacturer were the same or the model
# // was long, Just the model, in 1.x
model = profile.profile.product_model
manufacturer = profile.profile.product_manufacturer
if not (model or manufacturer):
return profile.profile.product_description+"\n"
if not manufacturer or len(model) > 30:
return model + "\n"
return "%s - %s\n" % (model, manufacturer)
except (AttributeError, IOError, TypeError, ValueError) as v: except (AttributeError, IOError, TypeError, ValueError) as v:
raise PyCMSError(v) raise PyCMSError(v)
@ -625,10 +639,130 @@ 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
return profile.product_info + "\n" # 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
description = profile.profile.product_description
cpright = profile.profile.product_copyright
arr = []
for elt in (description, cpright):
if elt:
arr.append(elt)
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)
##
# (pyCMS) Gets the copyright for the given profile.
#
# If profile isn't a valid CmsProfile object or filename to a profile,
# a PyCMSError is raised.
#
# If an error occurs while trying to obtain the copyright tag, a PyCMSError
# is raised
#
# Use this function to obtain the information stored in the profile's
# copyright tag.
#
# @param profile EITHER a valid CmsProfile object, OR a string of the filename
# of an ICC profile.
# @return A string containing the internal profile information stored in an ICC
# tag.
# @exception PyCMSError
def getProfileCopyright(profile):
try:
# add an extra newline to preserve pyCMS compatibility
if not isinstance(profile, ImageCmsProfile):
profile = ImageCmsProfile(profile)
return profile.profile.product_copyright + "\n"
except (AttributeError, IOError, TypeError, ValueError) as v:
raise PyCMSError(v)
##
# (pyCMS) Gets the manufacturer for the given profile.
#
# If profile isn't a valid CmsProfile object or filename to a profile,
# a PyCMSError is raised.
#
# If an error occurs while trying to obtain the manufacturer tag, a PyCMSError
# is raised
#
# Use this function to obtain the information stored in the profile's
# manufacturer tag.
#
# @param profile EITHER a valid CmsProfile object, OR a string of the filename
# of an ICC profile.
# @return A string containing the internal profile information stored in an ICC
# tag.
# @exception PyCMSError
def getProfileManufacturer(profile):
try:
# add an extra newline to preserve pyCMS compatibility
if not isinstance(profile, ImageCmsProfile):
profile = ImageCmsProfile(profile)
return profile.profile.product_manufacturer + "\n"
except (AttributeError, IOError, TypeError, ValueError) as v:
raise PyCMSError(v)
##
# (pyCMS) Gets the model for the given profile.
#
# If profile isn't a valid CmsProfile object or filename to a profile,
# a PyCMSError is raised.
#
# If an error occurs while trying to obtain the model tag, a PyCMSError
# is raised
#
# Use this function to obtain the information stored in the profile's
# model tag.
#
# @param profile EITHER a valid CmsProfile object, OR a string of the filename
# of an ICC profile.
# @return A string containing the internal profile information stored in an ICC
# tag.
# @exception PyCMSError
def getProfileModel(profile):
try:
# add an extra newline to preserve pyCMS compatibility
if not isinstance(profile, ImageCmsProfile):
profile = ImageCmsProfile(profile)
return profile.profile.product_model + "\n"
except (AttributeError, IOError, TypeError, ValueError) as v:
raise PyCMSError(v)
##
# (pyCMS) Gets the description for the given profile.
#
# If profile isn't a valid CmsProfile object or filename to a profile,
# a PyCMSError is raised.
#
# If an error occurs while trying to obtain the description tag, a PyCMSError
# is raised
#
# Use this function to obtain the information stored in the profile's
# description tag.
#
# @param profile EITHER a valid CmsProfile object, OR a string of the filename
# of an ICC profile.
# @return A string containing the internal profile information stored in an ICC
# tag.
# @exception PyCMSError
def getProfileDescription(profile):
try:
# add an extra newline to preserve pyCMS compatibility
if not isinstance(profile, ImageCmsProfile):
profile = ImageCmsProfile(profile)
return profile.profile.product_description + "\n"
except (AttributeError, IOError, TypeError, ValueError) as v:
raise PyCMSError(v)
## ##
# (pyCMS) Gets the default intent name for the given profile. # (pyCMS) Gets the default intent name for the given profile.
# #

BIN
Tests/images/lab-green.tif Normal file

Binary file not shown.

BIN
Tests/images/lab-red.tif Normal file

Binary file not shown.

BIN
Tests/images/lab.tif Normal file

Binary file not shown.

BIN
Tests/images/lena.Lab.tif Normal file

Binary file not shown.

41
Tests/test_format_lab.py Normal file
View File

@ -0,0 +1,41 @@
from tester import *
from PIL import Image
def test_white():
i = Image.open('Tests/images/lab.tif')
bits = i.load()
assert_equal(i.mode, 'LAB')
assert_equal(i.getbands(), ('L','A', 'B'))
k = i.getpixel((0,0))
assert_equal(k, (255,128,128))
L = i.getdata(0)
a = i.getdata(1)
b = i.getdata(2)
assert_equal(list(L), [255]*100)
assert_equal(list(a), [128]*100)
assert_equal(list(b), [128]*100)
def test_green():
# l= 50 (/100), a = -100 (-128 .. 128) b=0 in PS
# == RGB: 0, 152, 117
i = Image.open('Tests/images/lab-green.tif')
k = i.getpixel((0,0))
assert_equal(k, (128,28,128))
def test_red():
# l= 50 (/100), a = 100 (-128 .. 128) b=0 in PS
# == RGB: 255, 0, 124
i = Image.open('Tests/images/lab-red.tif')
k = i.getpixel((0,0))
assert_equal(k, (128,228,128))

View File

@ -14,7 +14,7 @@ def test_sanity():
# 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], '0.1.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])
# internal version number # internal version number
@ -39,44 +39,121 @@ def test_sanity():
i = ImageCms.applyTransform(lena(), t) i = ImageCms.applyTransform(lena(), t)
assert_image(i, "RGB", (128, 128)) assert_image(i, "RGB", (128, 128))
# test PointTransform convenience API
im = lena().point(t)
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():
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)', '', 'WhitePoint : D65 (daylight)', '',
'Tests/icc/sRGB.icm']) 'Tests/icc/sRGB.icm'])
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():
# same, using profile object # same, using profile object
p = ImageCms.createProfile("sRGB") p = ImageCms.createProfile("sRGB")
assert_equal(ImageCms.getProfileName(p).strip(), # assert_equal(ImageCms.getProfileName(p).strip(),
'sRGB built-in - (lcms internal)') # 'sRGB built-in - (lcms internal)')
assert_equal(ImageCms.getProfileInfo(p).splitlines(), # assert_equal(ImageCms.getProfileInfo(p).splitlines(),
['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():
# extensions # extensions
i = Image.open("Tests/images/rgb.jpg") i = Image.open("Tests/images/rgb.jpg")
p = ImageCms.getOpenProfile(BytesIO(i.info["icc_profile"])) p = ImageCms.getOpenProfile(BytesIO(i.info["icc_profile"]))
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():
# 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(ImageCms.PyCMSError, lambda: ImageCms.profileToProfile(lena(), "foo", "bar"))
assert_exception(ImageCms.PyCMSError, lambda: ImageCms.buildTransform("foo", "bar", "RGB", "RGB")) 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.getProfileName(None))
assert_exception(ImageCms.PyCMSError, lambda: ImageCms.isIntentSupported(SRGB, None, None)) assert_exception(ImageCms.PyCMSError, lambda: ImageCms.isIntentSupported(SRGB, None, None))
# test PointTransform convenience API
im = lena().point(t)
def test_display_profile():
# try fetching the profile for the current display device # try fetching the profile for the current display device
assert_no_exception(lambda: ImageCms.get_display_profile()) assert_no_exception(lambda: ImageCms.get_display_profile())
def test_lab_color_profile():
pLab = ImageCms.createProfile("LAB", 5000)
pLab = ImageCms.createProfile("LAB", 6500)
def test_simple_lab():
i = Image.new('RGB', (10,10), (128,128,128))
pLab = ImageCms.createProfile("LAB")
t = ImageCms.buildTransform(SRGB, pLab, "RGB", "LAB")
i_lab = ImageCms.applyTransform(i, t)
assert_equal(i_lab.mode, 'LAB')
k = i_lab.getpixel((0,0))
assert_equal(k, (137,128,128)) # not a linear luminance map. so L != 128
L = i_lab.getdata(0)
a = i_lab.getdata(1)
b = i_lab.getdata(2)
assert_equal(list(L), [137]*100)
assert_equal(list(a), [128]*100)
assert_equal(list(b), [128]*100)
def test_lab_color():
pLab = ImageCms.createProfile("LAB")
t = ImageCms.buildTransform(SRGB, pLab, "RGB", "LAB")
# need to add a type mapping for some PIL type to TYPE_Lab_8 in findLCMSType,
# and have that mapping work back to a PIL mode. (likely RGB)
i = ImageCms.applyTransform(lena(), t)
assert_image(i, "LAB", (128, 128))
# i.save('temp.lab.tif') # visually verified vs PS.
target = Image.open('Tests/images/lena.Lab.tif')
assert_image_similar(i, target, 30)
def test_lab_srgb():
pLab = ImageCms.createProfile("LAB")
t = ImageCms.buildTransform(pLab, SRGB, "LAB", "RGB")
img = Image.open('Tests/images/lena.Lab.tif')
img_srgb = ImageCms.applyTransform(img, t)
# img_srgb.save('temp.srgb.tif') # visually verified vs ps.
assert_image_similar(lena(), img_srgb, 30)
def test_lab_roundtrip():
# check to see if we're at least internally consistent.
pLab = ImageCms.createProfile("LAB")
t = ImageCms.buildTransform(SRGB, pLab, "RGB", "LAB")
t2 = ImageCms.buildTransform(pLab, SRGB, "LAB", "RGB")
i = ImageCms.applyTransform(lena(), t)
out = ImageCms.applyTransform(i, t2)
assert_image_similar(lena(), out, 2)

View File

@ -24,24 +24,21 @@ http://www.cazabon.com\n\
" "
#include "Python.h" #include "Python.h"
#include "lcms.h" #include "lcms2.h"
#include "Imaging.h" #include "Imaging.h"
#include "py3.h" #include "py3.h"
#if LCMS_VERSION < 117
#define LCMSBOOL BOOL
#endif
#ifdef WIN32 #ifdef WIN32
#include <windef.h> #include <windef.h>
#include <wingdi.h> #include <wingdi.h>
#endif #endif
#define PYCMSVERSION "0.1.0 pil" #define PYCMSVERSION "1.0.0 pil"
/* version history */ /* version history */
/* /*
1.0.0 pil Integrating littleCMS2
0.1.0 pil integration & refactoring 0.1.0 pil integration & refactoring
0.0.2 alpha: Minor updates, added interfaces to littleCMS features, Jan 6, 2003 0.0.2 alpha: Minor updates, added interfaces to littleCMS features, Jan 6, 2003
- fixed some memory holes in how transforms/profiles were created and passed back to Python - fixed some memory holes in how transforms/profiles were created and passed back to Python
@ -107,8 +104,6 @@ cms_profile_open(PyObject* self, PyObject* args)
if (!PyArg_ParseTuple(args, "s:profile_open", &sProfile)) if (!PyArg_ParseTuple(args, "s:profile_open", &sProfile))
return NULL; return NULL;
cmsErrorAction(LCMS_ERROR_IGNORE);
hProfile = cmsOpenProfileFromFile(sProfile, "r"); hProfile = cmsOpenProfileFromFile(sProfile, "r");
if (!hProfile) { if (!hProfile) {
PyErr_SetString(PyExc_IOError, "cannot open profile file"); PyErr_SetString(PyExc_IOError, "cannot open profile file");
@ -133,8 +128,6 @@ cms_profile_fromstring(PyObject* self, PyObject* args)
return NULL; return NULL;
#endif #endif
cmsErrorAction(LCMS_ERROR_IGNORE);
hProfile = cmsOpenProfileFromMem(pProfile, nProfile); hProfile = cmsOpenProfileFromMem(pProfile, nProfile);
if (!hProfile) { if (!hProfile) {
PyErr_SetString(PyExc_IOError, "cannot open profile from string"); PyErr_SetString(PyExc_IOError, "cannot open profile from string");
@ -192,25 +185,25 @@ cms_transform_dealloc(CmsTransformObject* self)
/* internal functions */ /* internal functions */
static const char* static const char*
findICmode(icColorSpaceSignature cs) findICmode(cmsColorSpaceSignature cs)
{ {
switch (cs) { switch (cs) {
case icSigXYZData: return "XYZ"; case cmsSigXYZData: return "XYZ";
case icSigLabData: return "LAB"; case cmsSigLabData: return "LAB";
case icSigLuvData: return "LUV"; case cmsSigLuvData: return "LUV";
case icSigYCbCrData: return "YCbCr"; case cmsSigYCbCrData: return "YCbCr";
case icSigYxyData: return "YXY"; case cmsSigYxyData: return "YXY";
case icSigRgbData: return "RGB"; case cmsSigRgbData: return "RGB";
case icSigGrayData: return "L"; case cmsSigGrayData: return "L";
case icSigHsvData: return "HSV"; case cmsSigHsvData: return "HSV";
case icSigHlsData: return "HLS"; case cmsSigHlsData: return "HLS";
case icSigCmykData: return "CMYK"; case cmsSigCmykData: return "CMYK";
case icSigCmyData: return "CMY"; case cmsSigCmyData: return "CMY";
default: return ""; /* other TBA */ default: return ""; /* other TBA */
} }
} }
static DWORD static cmsUInt32Number
findLCMStype(char* PILmode) findLCMStype(char* PILmode)
{ {
if (strcmp(PILmode, "RGB") == 0) { if (strcmp(PILmode, "RGB") == 0) {
@ -243,6 +236,10 @@ findLCMStype(char* PILmode)
else if (strcmp(PILmode, "YCC") == 0) { else if (strcmp(PILmode, "YCC") == 0) {
return TYPE_YCbCr_8; return TYPE_YCbCr_8;
} }
else if (strcmp(PILmode, "LAB") == 0) {
// LabX equvalent like ALab, but not reversed -- no #define in lcms2
return (COLORSPACE_SH(PT_LabV2)|CHANNELS_SH(3)|BYTES_SH(1)|EXTRA_SH(1));
}
else { else {
/* take a wild guess... but you probably should fail instead. */ /* take a wild guess... but you probably should fail instead. */
@ -269,12 +266,10 @@ pyCMSdoTransform(Imaging im, Imaging imOut, cmsHTRANSFORM hTransform)
} }
static cmsHTRANSFORM static cmsHTRANSFORM
_buildTransform(cmsHPROFILE hInputProfile, cmsHPROFILE hOutputProfile, char *sInMode, char *sOutMode, int iRenderingIntent, DWORD cmsFLAGS) _buildTransform(cmsHPROFILE hInputProfile, cmsHPROFILE hOutputProfile, char *sInMode, char *sOutMode, int iRenderingIntent, cmsUInt32Number cmsFLAGS)
{ {
cmsHTRANSFORM hTransform; cmsHTRANSFORM hTransform;
cmsErrorAction(LCMS_ERROR_IGNORE);
Py_BEGIN_ALLOW_THREADS Py_BEGIN_ALLOW_THREADS
/* create the transform */ /* create the transform */
@ -293,12 +288,10 @@ _buildTransform(cmsHPROFILE hInputProfile, cmsHPROFILE hOutputProfile, char *sIn
} }
static cmsHTRANSFORM static cmsHTRANSFORM
_buildProofTransform(cmsHPROFILE hInputProfile, cmsHPROFILE hOutputProfile, cmsHPROFILE hProofProfile, char *sInMode, char *sOutMode, int iRenderingIntent, int iProofIntent, DWORD cmsFLAGS) _buildProofTransform(cmsHPROFILE hInputProfile, cmsHPROFILE hOutputProfile, cmsHPROFILE hProofProfile, char *sInMode, char *sOutMode, int iRenderingIntent, int iProofIntent, cmsUInt32Number cmsFLAGS)
{ {
cmsHTRANSFORM hTransform; cmsHTRANSFORM hTransform;
cmsErrorAction(LCMS_ERROR_IGNORE);
Py_BEGIN_ALLOW_THREADS Py_BEGIN_ALLOW_THREADS
/* create the transform */ /* create the transform */
@ -336,8 +329,6 @@ buildTransform(PyObject *self, PyObject *args) {
if (!PyArg_ParseTuple(args, "O!O!ss|ii:buildTransform", &CmsProfile_Type, &pInputProfile, &CmsProfile_Type, &pOutputProfile, &sInMode, &sOutMode, &iRenderingIntent, &cmsFLAGS)) if (!PyArg_ParseTuple(args, "O!O!ss|ii:buildTransform", &CmsProfile_Type, &pInputProfile, &CmsProfile_Type, &pOutputProfile, &sInMode, &sOutMode, &iRenderingIntent, &cmsFLAGS))
return NULL; return NULL;
cmsErrorAction(LCMS_ERROR_IGNORE);
transform = _buildTransform(pInputProfile->profile, pOutputProfile->profile, sInMode, sOutMode, iRenderingIntent, cmsFLAGS); transform = _buildTransform(pInputProfile->profile, pOutputProfile->profile, sInMode, sOutMode, iRenderingIntent, cmsFLAGS);
if (!transform) if (!transform)
@ -363,8 +354,6 @@ buildProofTransform(PyObject *self, PyObject *args)
if (!PyArg_ParseTuple(args, "O!O!O!ss|iii:buildProofTransform", &CmsProfile_Type, &pInputProfile, &CmsProfile_Type, &pOutputProfile, &CmsProfile_Type, &pProofProfile, &sInMode, &sOutMode, &iRenderingIntent, &iProofIntent, &cmsFLAGS)) if (!PyArg_ParseTuple(args, "O!O!O!ss|iii:buildProofTransform", &CmsProfile_Type, &pInputProfile, &CmsProfile_Type, &pOutputProfile, &CmsProfile_Type, &pProofProfile, &sInMode, &sOutMode, &iRenderingIntent, &iProofIntent, &cmsFLAGS))
return NULL; return NULL;
cmsErrorAction(LCMS_ERROR_IGNORE);
transform = _buildProofTransform(pInputProfile->profile, pOutputProfile->profile, pProofProfile->profile, sInMode, sOutMode, iRenderingIntent, iProofIntent, cmsFLAGS); transform = _buildProofTransform(pInputProfile->profile, pOutputProfile->profile, pProofProfile->profile, sInMode, sOutMode, iRenderingIntent, iProofIntent, cmsFLAGS);
if (!transform) if (!transform)
@ -390,8 +379,6 @@ cms_transform_apply(CmsTransformObject *self, PyObject *args)
im = (Imaging) idIn; im = (Imaging) idIn;
imOut = (Imaging) idOut; imOut = (Imaging) idOut;
cmsErrorAction(LCMS_ERROR_IGNORE);
result = pyCMSdoTransform(im, imOut, self->transform); result = pyCMSdoTransform(im, imOut, self->transform);
return Py_BuildValue("i", result); return Py_BuildValue("i", result);
@ -405,32 +392,34 @@ createProfile(PyObject *self, PyObject *args)
{ {
char *sColorSpace; char *sColorSpace;
cmsHPROFILE hProfile; cmsHPROFILE hProfile;
int iColorTemp = 0; cmsFloat64Number dColorTemp = 0.0;
LPcmsCIExyY whitePoint = NULL; cmsCIExyY whitePoint;
LCMSBOOL result; cmsBool result;
if (!PyArg_ParseTuple(args, "s|i:createProfile", &sColorSpace, &iColorTemp)) if (!PyArg_ParseTuple(args, "s|d:createProfile", &sColorSpace, &dColorTemp))
return NULL; return NULL;
cmsErrorAction(LCMS_ERROR_IGNORE);
if (strcmp(sColorSpace, "LAB") == 0) { if (strcmp(sColorSpace, "LAB") == 0) {
if (iColorTemp > 0) { if (dColorTemp > 0.0) {
result = cmsWhitePointFromTemp(iColorTemp, whitePoint); result = cmsWhitePointFromTemp(&whitePoint, dColorTemp);
if (!result) { if (!result) {
PyErr_SetString(PyExc_ValueError, "ERROR: Could not calculate white point from color temperature provided, must be integer in degrees Kelvin"); PyErr_SetString(PyExc_ValueError, "ERROR: Could not calculate white point from color temperature provided, must be float in degrees Kelvin");
return NULL; return NULL;
} }
hProfile = cmsCreateLabProfile(whitePoint); hProfile = cmsCreateLab2Profile(&whitePoint);
} else } else {
hProfile = cmsCreateLabProfile(NULL); hProfile = cmsCreateLab2Profile(NULL);
}
} }
else if (strcmp(sColorSpace, "XYZ") == 0) else if (strcmp(sColorSpace, "XYZ") == 0) {
hProfile = cmsCreateXYZProfile(); hProfile = cmsCreateXYZProfile();
else if (strcmp(sColorSpace, "sRGB") == 0) }
else if (strcmp(sColorSpace, "sRGB") == 0) {
hProfile = cmsCreate_sRGBProfile(); hProfile = cmsCreate_sRGBProfile();
else }
else {
hProfile = NULL; hProfile = NULL;
}
if (!hProfile) { if (!hProfile) {
PyErr_SetString(PyExc_ValueError, "failed to create requested color space"); PyErr_SetString(PyExc_ValueError, "failed to create requested color space");
@ -446,7 +435,7 @@ createProfile(PyObject *self, PyObject *args)
static PyObject * static PyObject *
cms_profile_is_intent_supported(CmsProfileObject *self, PyObject *args) cms_profile_is_intent_supported(CmsProfileObject *self, PyObject *args)
{ {
LCMSBOOL result; cmsBool result;
int intent; int intent;
int direction; int direction;
@ -465,7 +454,7 @@ static PyObject *
cms_get_display_profile_win32(PyObject* self, PyObject* args) cms_get_display_profile_win32(PyObject* self, PyObject* args)
{ {
char filename[MAX_PATH]; char filename[MAX_PATH];
DWORD filename_size; cmsUInt32Number filename_size;
BOOL ok; BOOL ok;
int handle = 0; int handle = 0;
@ -519,27 +508,63 @@ static struct PyMethodDef cms_profile_methods[] = {
}; };
static PyObject* static PyObject*
cms_profile_getattr_product_name(CmsProfileObject* self, void* closure) _profile_getattr(CmsProfileObject* self, cmsInfoType field)
{ {
return PyUnicode_DecodeFSDefault(cmsTakeProductName(self->profile)); // UNDONE -- check that I'm getting the right fields on these.
// return PyUnicode_DecodeFSDefault(cmsTakeProductName(self->profile));
//wchar_t buf[256]; -- UNDONE need wchar_t for unicode version.
char buf[256];
cmsUInt32Number written;
written = cmsGetProfileInfoASCII(self->profile,
field,
"en",
"us",
buf,
256);
if (written) {
return PyUnicode_FromString(buf);
}
// UNDONE suppressing error here by sending back blank string.
return PyUnicode_FromString("");
} }
static PyObject* static PyObject*
cms_profile_getattr_product_desc(CmsProfileObject* self, void* closure) cms_profile_getattr_product_desc(CmsProfileObject* self, void* closure)
{ {
return PyUnicode_DecodeFSDefault(cmsTakeProductDesc(self->profile)); // description was Description != 'Copyright' || or "%s - %s" (manufacturer, model) in 1.x
return _profile_getattr(self, cmsInfoDescription);
}
/* use these four for the individual fields.
*/
static PyObject*
cms_profile_getattr_product_description(CmsProfileObject* self, void* closure)
{
return _profile_getattr(self, cmsInfoDescription);
} }
static PyObject* static PyObject*
cms_profile_getattr_product_info(CmsProfileObject* self, void* closure) cms_profile_getattr_product_model(CmsProfileObject* self, void* closure)
{ {
return PyUnicode_DecodeFSDefault(cmsTakeProductInfo(self->profile)); return _profile_getattr(self, cmsInfoModel);
}
static PyObject*
cms_profile_getattr_product_manufacturer(CmsProfileObject* self, void* closure)
{
return _profile_getattr(self, cmsInfoManufacturer);
}
static PyObject*
cms_profile_getattr_product_copyright(CmsProfileObject* self, void* closure)
{
return _profile_getattr(self, cmsInfoCopyright);
} }
static PyObject* static PyObject*
cms_profile_getattr_rendering_intent(CmsProfileObject* self, void* closure) cms_profile_getattr_rendering_intent(CmsProfileObject* self, void* closure)
{ {
return PyInt_FromLong(cmsTakeRenderingIntent(self->profile)); return PyInt_FromLong(cmsGetHeaderRenderingIntent(self->profile));
} }
static PyObject* static PyObject*
@ -556,9 +581,11 @@ cms_profile_getattr_color_space(CmsProfileObject* self, void* closure)
/* FIXME: add more properties (creation_datetime etc) */ /* FIXME: add more properties (creation_datetime etc) */
static struct PyGetSetDef cms_profile_getsetters[] = { static struct PyGetSetDef cms_profile_getsetters[] = {
{ "product_name", (getter) cms_profile_getattr_product_name },
{ "product_desc", (getter) cms_profile_getattr_product_desc }, { "product_desc", (getter) cms_profile_getattr_product_desc },
{ "product_info", (getter) cms_profile_getattr_product_info }, { "product_description", (getter) cms_profile_getattr_product_description },
{ "product_manufacturer", (getter) cms_profile_getattr_product_manufacturer },
{ "product_model", (getter) cms_profile_getattr_product_model },
{ "product_copyright", (getter) cms_profile_getattr_product_copyright },
{ "rendering_intent", (getter) cms_profile_getattr_rendering_intent }, { "rendering_intent", (getter) cms_profile_getattr_rendering_intent },
{ "pcs", (getter) cms_profile_getattr_pcs }, { "pcs", (getter) cms_profile_getattr_pcs },
{ "color_space", (getter) cms_profile_getattr_color_space }, { "color_space", (getter) cms_profile_getattr_color_space },

View File

@ -237,6 +237,7 @@ ImagingAccessInit()
ADD("RGBX", line_32, get_pixel_32, put_pixel_32); ADD("RGBX", line_32, get_pixel_32, put_pixel_32);
ADD("CMYK", line_32, get_pixel_32, put_pixel_32); ADD("CMYK", line_32, get_pixel_32, put_pixel_32);
ADD("YCbCr", line_32, get_pixel_32, put_pixel_32); ADD("YCbCr", line_32, get_pixel_32, put_pixel_32);
ADD("LAB", line_32, get_pixel_32, put_pixel_32);
} }
ImagingAccess ImagingAccess

View File

@ -40,6 +40,7 @@ extern "C" {
* RGBA 4 R, G, B, A * RGBA 4 R, G, B, A
* CMYK 4 C, M, Y, K * CMYK 4 C, M, Y, K
* YCbCr 4 Y, Cb, Cr, - * YCbCr 4 Y, Cb, Cr, -
* Lab 4 L, a, b, -
* *
* experimental modes (incomplete): * experimental modes (incomplete):
* LA 4 L, -, -, A * LA 4 L, -, -, A

View File

@ -372,6 +372,19 @@ packI32S(UINT8* out, const UINT8* in, int pixels)
} }
} }
void
ImagingPackLAB(UINT8* out, const UINT8* in, int pixels)
{
int i;
/* LAB triplets */
for (i = 0; i < pixels; i++) {
out[0] = in[0];
out[1] = in[1] ^ 128; /* signed in outside world */
out[2] = in[2] ^ 128;
out += 3; in += 4;
}
}
static void static void
copy1(UINT8* out, const UINT8* in, int pixels) copy1(UINT8* out, const UINT8* in, int pixels)
{ {
@ -526,6 +539,12 @@ static struct {
{"YCbCr", "Cb", 8, band1}, {"YCbCr", "Cb", 8, band1},
{"YCbCr", "Cr", 8, band2}, {"YCbCr", "Cr", 8, band2},
/* LAB Color */
{"LAB", "LAB", 24, ImagingPackLAB},
{"LAB", "L", 8, band0},
{"LAB", "A", 8, band1},
{"LAB", "B", 8, band2},
/* integer */ /* integer */
{"I", "I", 32, copy4}, {"I", "I", 32, copy4},
{"I", "I;16B", 16, packI16B}, {"I", "I;16B", 16, packI16B},

View File

@ -178,9 +178,16 @@ ImagingNewPrologueSubtype(const char *mode, unsigned xsize, unsigned ysize,
im->pixelsize = 4; im->pixelsize = 4;
im->linesize = xsize * 4; im->linesize = xsize * 4;
} else if (strcmp(mode, "LAB") == 0) {
/* 24-bit color, luminance, + 2 color channels */
/* L is uint8, a,b are int8 */
im->bands = 3;
im->pixelsize = 4;
im->linesize = xsize * 4;
} else { } else {
free(im); free(im);
return (Imaging) ImagingError_ValueError("unrecognized mode"); return (Imaging) ImagingError_ValueError("unrecognized mode");
} }
/* Setup image descriptor */ /* Setup image descriptor */

View File

@ -750,6 +750,31 @@ unpackCMYKI(UINT8* out, const UINT8* in, int pixels)
} }
} }
/* Unpack to "LAB" image */
/* There are two representations of LAB images for whatever precision:
L: Uint (in PS, it's 0-100)
A: Int (in ps, -128 .. 128, or elsewhere 0..255, with 128 as middle.
Channels in PS display a 0 value as middle grey,
LCMS appears to use 128 as the 0 value for these channels)
B: Int (as above)
Since we don't have any signed ints, we're going with the shifted versions
internally, and we'll unshift for saving and whatnot.
*/
void
ImagingUnpackLAB(UINT8* out, const UINT8* in, int pixels)
{
int i;
/* LAB triplets */
for (i = 0; i < pixels; i++) {
out[0] = in[0];
out[1] = in[1] ^ 128; /* signed in outside world */
out[2] = in[2] ^ 128;
out[3] = 255;
out += 4; in += 3;
}
}
static void static void
copy1(UINT8* out, const UINT8* in, int pixels) copy1(UINT8* out, const UINT8* in, int pixels)
{ {
@ -764,6 +789,13 @@ copy2(UINT8* out, const UINT8* in, int pixels)
memcpy(out, in, pixels*2); memcpy(out, in, pixels*2);
} }
static void
copy3(UINT8* out, const UINT8* in, int pixels)
{
/* LAB triples, 24bit */
memcpy(out, in, 3 * pixels);
}
static void static void
copy4(UINT8* out, const UINT8* in, int pixels) copy4(UINT8* out, const UINT8* in, int pixels)
{ {
@ -1054,6 +1086,12 @@ static struct {
{"YCbCr", "YCbCrX", 32, copy4}, {"YCbCr", "YCbCrX", 32, copy4},
{"YCbCr", "YCbCrK", 32, copy4}, {"YCbCr", "YCbCrK", 32, copy4},
/* LAB Color */
{"LAB", "LAB", 24, ImagingUnpackLAB},
{"LAB", "L", 8, band0},
{"LAB", "A", 8, band1},
{"LAB", "B", 8, band2},
/* integer variations */ /* integer variations */
{"I", "I", 32, copy4}, {"I", "I", 32, copy4},
{"I", "I;8", 8, unpackI8}, {"I", "I;8", 8, unpackI8},

View File

@ -195,7 +195,7 @@ if __name__ == "__main__":
check_codec("ZLIB (PNG/ZIP)", "zip") check_codec("ZLIB (PNG/ZIP)", "zip")
check_codec("G4 TIFF", "group4") check_codec("G4 TIFF", "group4")
check_module("FREETYPE2", "PIL._imagingft") check_module("FREETYPE2", "PIL._imagingft")
check_module("LITTLECMS", "PIL._imagingcms") check_module("LITTLECMS2", "PIL._imagingcms")
check_module("WEBP", "PIL._webp") check_module("WEBP", "PIL._webp")
try: try:
from PIL import _webp from PIL import _webp

View File

@ -342,8 +342,8 @@ class pil_build_ext(build_ext):
_add_directory(self.compiler.include_dirs, dir, 0) _add_directory(self.compiler.include_dirs, dir, 0)
if feature.want('lcms'): if feature.want('lcms'):
if _find_include_file(self, "lcms.h"): if _find_include_file(self, "lcms2.h"):
if _find_library_file(self, "lcms"): if _find_library_file(self, "lcms2"):
feature.lcms = "lcms" feature.lcms = "lcms"
if _tkinter and _find_include_file(self, "tk.h"): if _tkinter and _find_include_file(self, "tk.h"):
@ -426,7 +426,7 @@ class pil_build_ext(build_ext):
if sys.platform == "win32": if sys.platform == "win32":
extra.extend(["user32", "gdi32"]) extra.extend(["user32", "gdi32"])
exts.append(Extension( exts.append(Extension(
"PIL._imagingcms", ["_imagingcms.c"], libraries=["lcms"] + extra)) "PIL._imagingcms", ["_imagingcms.c"], libraries=["lcms2"] + extra))
if os.path.isfile("_webp.c") and feature.webp: if os.path.isfile("_webp.c") and feature.webp:
libs = ["webp"] libs = ["webp"]
@ -502,7 +502,7 @@ class pil_build_ext(build_ext):
(feature.zlib, "ZLIB (PNG/ZIP)"), (feature.zlib, "ZLIB (PNG/ZIP)"),
(feature.tiff, "TIFF G3/G4 (experimental)"), (feature.tiff, "TIFF G3/G4 (experimental)"),
(feature.freetype, "FREETYPE2"), (feature.freetype, "FREETYPE2"),
(feature.lcms, "LITTLECMS"), (feature.lcms, "LITTLECMS2"),
(feature.webp, "WEBP"), (feature.webp, "WEBP"),
(feature.webpmux, "WEBPMUX"), ] (feature.webpmux, "WEBPMUX"), ]