diff --git a/.travis.yml b/.travis.yml index 58f493b1b..88118b1b2 100644 --- a/.travis.yml +++ b/.travis.yml @@ -6,7 +6,7 @@ python: - 3.2 - 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: - python setup.py clean diff --git a/PIL/Image.py b/PIL/Image.py index 6f5e37591..6f53d5148 100644 --- a/PIL/Image.py +++ b/PIL/Image.py @@ -200,6 +200,7 @@ _MODEINFO = { "RGBA": ("RGB", "L", ("R", "G", "B", "A")), "CMYK": ("RGB", "L", ("C", "M", "Y", "K")), "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 # BGR;24. Use these modes only if you know exactly what you're @@ -224,6 +225,7 @@ _MODE_CONV = { "RGBA": ('|u1', 4), "CMYK": ('|u1', 4), "YCbCr": ('|u1', 3), + "LAB": ('|u1', 3), # UNDONE - unsigned |u1i1i1 "I;16": ('=u2', None), "I;16B": ('>u2', None), "I;16L": (' 30: + return model + "\n" + return "%s - %s\n" % (model, manufacturer) + except (AttributeError, IOError, TypeError, ValueError) as v: raise PyCMSError(v) @@ -625,10 +639,130 @@ def getProfileInfo(profile): if not isinstance(profile, ImageCmsProfile): profile = ImageCmsProfile(profile) # 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: 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. # diff --git a/Tests/images/lab-green.tif b/Tests/images/lab-green.tif new file mode 100644 index 000000000..76c129ee9 Binary files /dev/null and b/Tests/images/lab-green.tif differ diff --git a/Tests/images/lab-red.tif b/Tests/images/lab-red.tif new file mode 100644 index 000000000..fc1953006 Binary files /dev/null and b/Tests/images/lab-red.tif differ diff --git a/Tests/images/lab.tif b/Tests/images/lab.tif new file mode 100644 index 000000000..7dab9b2ba Binary files /dev/null and b/Tests/images/lab.tif differ diff --git a/Tests/images/lena.Lab.tif b/Tests/images/lena.Lab.tif new file mode 100644 index 000000000..335598210 Binary files /dev/null and b/Tests/images/lena.Lab.tif differ diff --git a/Tests/test_format_lab.py b/Tests/test_format_lab.py new file mode 100644 index 000000000..371b06a0b --- /dev/null +++ b/Tests/test_format_lab.py @@ -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)) diff --git a/Tests/test_imagecms.py b/Tests/test_imagecms.py index 29e578192..d18132598 100644 --- a/Tests/test_imagecms.py +++ b/Tests/test_imagecms.py @@ -14,7 +14,7 @@ def test_sanity(): # this mostly follows the cms_test outline. 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]) # internal version number @@ -39,44 +39,121 @@ def test_sanity(): i = ImageCms.applyTransform(lena(), t) assert_image(i, "RGB", (128, 128)) + # test PointTransform convenience API + im = lena().point(t) + +def test_name(): # get profile information for file assert_equal(ImageCms.getProfileName(SRGB).strip(), 'IEC 61966-2.1 Default RGB colour space - sRGB') +def x_test_info(): assert_equal(ImageCms.getProfileInfo(SRGB).splitlines(), ['sRGB IEC61966-2.1', '', 'Copyright (c) 1998 Hewlett-Packard Company', '', 'WhitePoint : D65 (daylight)', '', 'Tests/icc/sRGB.icm']) + +def test_intent(): assert_equal(ImageCms.getDefaultIntent(SRGB), 0) assert_equal(ImageCms.isIntentSupported( SRGB, ImageCms.INTENT_ABSOLUTE_COLORIMETRIC, ImageCms.DIRECTION_INPUT), 1) +def test_profile_object(): # same, using profile object p = ImageCms.createProfile("sRGB") - assert_equal(ImageCms.getProfileName(p).strip(), - 'sRGB built-in - (lcms internal)') - assert_equal(ImageCms.getProfileInfo(p).splitlines(), - ['sRGB built-in', '', 'WhitePoint : D65 (daylight)', '', '']) +# assert_equal(ImageCms.getProfileName(p).strip(), +# 'sRGB built-in - (lcms internal)') +# assert_equal(ImageCms.getProfileInfo(p).splitlines(), +# ['sRGB built-in', '', 'WhitePoint : D65 (daylight)', '', '']) assert_equal(ImageCms.getDefaultIntent(p), 0) assert_equal(ImageCms.isIntentSupported( p, ImageCms.INTENT_ABSOLUTE_COLORIMETRIC, ImageCms.DIRECTION_INPUT), 1) +def test_extensions(): # extensions i = Image.open("Tests/images/rgb.jpg") p = ImageCms.getOpenProfile(BytesIO(i.info["icc_profile"])) assert_equal(ImageCms.getProfileName(p).strip(), 'IEC 61966-2.1 Default RGB colour space - sRGB') +def test_exceptions(): # 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.buildTransform("foo", "bar", "RGB", "RGB")) assert_exception(ImageCms.PyCMSError, lambda: ImageCms.getProfileName(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 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) + + diff --git a/_imagingcms.c b/_imagingcms.c index 968042795..b2903adbf 100644 --- a/_imagingcms.c +++ b/_imagingcms.c @@ -24,24 +24,21 @@ http://www.cazabon.com\n\ " #include "Python.h" -#include "lcms.h" +#include "lcms2.h" #include "Imaging.h" #include "py3.h" -#if LCMS_VERSION < 117 -#define LCMSBOOL BOOL -#endif - #ifdef WIN32 #include #include #endif -#define PYCMSVERSION "0.1.0 pil" +#define PYCMSVERSION "1.0.0 pil" /* version history */ /* + 1.0.0 pil Integrating littleCMS2 0.1.0 pil integration & refactoring 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 @@ -107,8 +104,6 @@ cms_profile_open(PyObject* self, PyObject* args) if (!PyArg_ParseTuple(args, "s:profile_open", &sProfile)) return NULL; - cmsErrorAction(LCMS_ERROR_IGNORE); - hProfile = cmsOpenProfileFromFile(sProfile, "r"); if (!hProfile) { PyErr_SetString(PyExc_IOError, "cannot open profile file"); @@ -133,8 +128,6 @@ cms_profile_fromstring(PyObject* self, PyObject* args) return NULL; #endif - cmsErrorAction(LCMS_ERROR_IGNORE); - hProfile = cmsOpenProfileFromMem(pProfile, nProfile); if (!hProfile) { PyErr_SetString(PyExc_IOError, "cannot open profile from string"); @@ -192,25 +185,25 @@ cms_transform_dealloc(CmsTransformObject* self) /* internal functions */ static const char* -findICmode(icColorSpaceSignature cs) +findICmode(cmsColorSpaceSignature cs) { switch (cs) { - case icSigXYZData: return "XYZ"; - case icSigLabData: return "LAB"; - case icSigLuvData: return "LUV"; - case icSigYCbCrData: return "YCbCr"; - case icSigYxyData: return "YXY"; - case icSigRgbData: return "RGB"; - case icSigGrayData: return "L"; - case icSigHsvData: return "HSV"; - case icSigHlsData: return "HLS"; - case icSigCmykData: return "CMYK"; - case icSigCmyData: return "CMY"; + case cmsSigXYZData: return "XYZ"; + case cmsSigLabData: return "LAB"; + case cmsSigLuvData: return "LUV"; + case cmsSigYCbCrData: return "YCbCr"; + case cmsSigYxyData: return "YXY"; + case cmsSigRgbData: return "RGB"; + case cmsSigGrayData: return "L"; + case cmsSigHsvData: return "HSV"; + case cmsSigHlsData: return "HLS"; + case cmsSigCmykData: return "CMYK"; + case cmsSigCmyData: return "CMY"; default: return ""; /* other TBA */ } } -static DWORD +static cmsUInt32Number findLCMStype(char* PILmode) { if (strcmp(PILmode, "RGB") == 0) { @@ -243,6 +236,10 @@ findLCMStype(char* PILmode) else if (strcmp(PILmode, "YCC") == 0) { 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 { /* take a wild guess... but you probably should fail instead. */ @@ -269,12 +266,10 @@ pyCMSdoTransform(Imaging im, Imaging imOut, cmsHTRANSFORM hTransform) } 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; - cmsErrorAction(LCMS_ERROR_IGNORE); - Py_BEGIN_ALLOW_THREADS /* create the transform */ @@ -293,12 +288,10 @@ _buildTransform(cmsHPROFILE hInputProfile, cmsHPROFILE hOutputProfile, char *sIn } 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; - cmsErrorAction(LCMS_ERROR_IGNORE); - Py_BEGIN_ALLOW_THREADS /* 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)) return NULL; - cmsErrorAction(LCMS_ERROR_IGNORE); - transform = _buildTransform(pInputProfile->profile, pOutputProfile->profile, sInMode, sOutMode, iRenderingIntent, cmsFLAGS); 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)) return NULL; - cmsErrorAction(LCMS_ERROR_IGNORE); - transform = _buildProofTransform(pInputProfile->profile, pOutputProfile->profile, pProofProfile->profile, sInMode, sOutMode, iRenderingIntent, iProofIntent, cmsFLAGS); if (!transform) @@ -390,8 +379,6 @@ cms_transform_apply(CmsTransformObject *self, PyObject *args) im = (Imaging) idIn; imOut = (Imaging) idOut; - cmsErrorAction(LCMS_ERROR_IGNORE); - result = pyCMSdoTransform(im, imOut, self->transform); return Py_BuildValue("i", result); @@ -405,32 +392,34 @@ createProfile(PyObject *self, PyObject *args) { char *sColorSpace; cmsHPROFILE hProfile; - int iColorTemp = 0; - LPcmsCIExyY whitePoint = NULL; - LCMSBOOL result; + cmsFloat64Number dColorTemp = 0.0; + cmsCIExyY whitePoint; + cmsBool result; - if (!PyArg_ParseTuple(args, "s|i:createProfile", &sColorSpace, &iColorTemp)) + if (!PyArg_ParseTuple(args, "s|d:createProfile", &sColorSpace, &dColorTemp)) return NULL; - cmsErrorAction(LCMS_ERROR_IGNORE); - if (strcmp(sColorSpace, "LAB") == 0) { - if (iColorTemp > 0) { - result = cmsWhitePointFromTemp(iColorTemp, whitePoint); + if (dColorTemp > 0.0) { + result = cmsWhitePointFromTemp(&whitePoint, dColorTemp); 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; } - hProfile = cmsCreateLabProfile(whitePoint); - } else - hProfile = cmsCreateLabProfile(NULL); + hProfile = cmsCreateLab2Profile(&whitePoint); + } else { + hProfile = cmsCreateLab2Profile(NULL); + } } - else if (strcmp(sColorSpace, "XYZ") == 0) + else if (strcmp(sColorSpace, "XYZ") == 0) { hProfile = cmsCreateXYZProfile(); - else if (strcmp(sColorSpace, "sRGB") == 0) + } + else if (strcmp(sColorSpace, "sRGB") == 0) { hProfile = cmsCreate_sRGBProfile(); - else + } + else { hProfile = NULL; + } if (!hProfile) { PyErr_SetString(PyExc_ValueError, "failed to create requested color space"); @@ -446,7 +435,7 @@ createProfile(PyObject *self, PyObject *args) static PyObject * cms_profile_is_intent_supported(CmsProfileObject *self, PyObject *args) { - LCMSBOOL result; + cmsBool result; int intent; int direction; @@ -465,7 +454,7 @@ static PyObject * cms_get_display_profile_win32(PyObject* self, PyObject* args) { char filename[MAX_PATH]; - DWORD filename_size; + cmsUInt32Number filename_size; BOOL ok; int handle = 0; @@ -519,27 +508,63 @@ static struct PyMethodDef cms_profile_methods[] = { }; 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* 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* -cms_profile_getattr_product_info(CmsProfileObject* self, void* closure) -{ - return PyUnicode_DecodeFSDefault(cmsTakeProductInfo(self->profile)); +cms_profile_getattr_product_model(CmsProfileObject* self, void* closure) +{ + 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* cms_profile_getattr_rendering_intent(CmsProfileObject* self, void* closure) { - return PyInt_FromLong(cmsTakeRenderingIntent(self->profile)); + return PyInt_FromLong(cmsGetHeaderRenderingIntent(self->profile)); } static PyObject* @@ -556,9 +581,11 @@ cms_profile_getattr_color_space(CmsProfileObject* self, void* closure) /* FIXME: add more properties (creation_datetime etc) */ static struct PyGetSetDef cms_profile_getsetters[] = { - { "product_name", (getter) cms_profile_getattr_product_name }, { "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 }, { "pcs", (getter) cms_profile_getattr_pcs }, { "color_space", (getter) cms_profile_getattr_color_space }, diff --git a/libImaging/Access.c b/libImaging/Access.c index fdc1a8886..70eb1af4c 100644 --- a/libImaging/Access.c +++ b/libImaging/Access.c @@ -237,6 +237,7 @@ ImagingAccessInit() ADD("RGBX", 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("LAB", line_32, get_pixel_32, put_pixel_32); } ImagingAccess diff --git a/libImaging/Imaging.h b/libImaging/Imaging.h index 0da13baab..c139aed88 100644 --- a/libImaging/Imaging.h +++ b/libImaging/Imaging.h @@ -40,6 +40,7 @@ extern "C" { * RGBA 4 R, G, B, A * CMYK 4 C, M, Y, K * YCbCr 4 Y, Cb, Cr, - + * Lab 4 L, a, b, - * * experimental modes (incomplete): * LA 4 L, -, -, A diff --git a/libImaging/Pack.c b/libImaging/Pack.c index 478de7499..320e94d67 100644 --- a/libImaging/Pack.c +++ b/libImaging/Pack.c @@ -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 copy1(UINT8* out, const UINT8* in, int pixels) { @@ -526,6 +539,12 @@ static struct { {"YCbCr", "Cb", 8, band1}, {"YCbCr", "Cr", 8, band2}, + /* LAB Color */ + {"LAB", "LAB", 24, ImagingPackLAB}, + {"LAB", "L", 8, band0}, + {"LAB", "A", 8, band1}, + {"LAB", "B", 8, band2}, + /* integer */ {"I", "I", 32, copy4}, {"I", "I;16B", 16, packI16B}, diff --git a/libImaging/Storage.c b/libImaging/Storage.c index eb3be4322..50259be47 100644 --- a/libImaging/Storage.c +++ b/libImaging/Storage.c @@ -178,9 +178,16 @@ ImagingNewPrologueSubtype(const char *mode, unsigned xsize, unsigned ysize, im->pixelsize = 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 { free(im); - return (Imaging) ImagingError_ValueError("unrecognized mode"); + return (Imaging) ImagingError_ValueError("unrecognized mode"); } /* Setup image descriptor */ diff --git a/libImaging/Unpack.c b/libImaging/Unpack.c index 5be744ac5..2fba92e98 100644 --- a/libImaging/Unpack.c +++ b/libImaging/Unpack.c @@ -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 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); } +static void +copy3(UINT8* out, const UINT8* in, int pixels) +{ + /* LAB triples, 24bit */ + memcpy(out, in, 3 * pixels); +} + static void copy4(UINT8* out, const UINT8* in, int pixels) { @@ -1054,6 +1086,12 @@ static struct { {"YCbCr", "YCbCrX", 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 */ {"I", "I", 32, copy4}, {"I", "I;8", 8, unpackI8}, diff --git a/selftest.py b/selftest.py index 463902648..1eb7738e3 100644 --- a/selftest.py +++ b/selftest.py @@ -195,7 +195,7 @@ if __name__ == "__main__": check_codec("ZLIB (PNG/ZIP)", "zip") check_codec("G4 TIFF", "group4") check_module("FREETYPE2", "PIL._imagingft") - check_module("LITTLECMS", "PIL._imagingcms") + check_module("LITTLECMS2", "PIL._imagingcms") check_module("WEBP", "PIL._webp") try: from PIL import _webp diff --git a/setup.py b/setup.py index 4698aef0b..6c677bbd3 100644 --- a/setup.py +++ b/setup.py @@ -342,8 +342,8 @@ class pil_build_ext(build_ext): _add_directory(self.compiler.include_dirs, dir, 0) if feature.want('lcms'): - if _find_include_file(self, "lcms.h"): - if _find_library_file(self, "lcms"): + if _find_include_file(self, "lcms2.h"): + if _find_library_file(self, "lcms2"): feature.lcms = "lcms" if _tkinter and _find_include_file(self, "tk.h"): @@ -426,7 +426,7 @@ class pil_build_ext(build_ext): if sys.platform == "win32": extra.extend(["user32", "gdi32"]) 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: libs = ["webp"] @@ -502,7 +502,7 @@ class pil_build_ext(build_ext): (feature.zlib, "ZLIB (PNG/ZIP)"), (feature.tiff, "TIFF G3/G4 (experimental)"), (feature.freetype, "FREETYPE2"), - (feature.lcms, "LITTLECMS"), + (feature.lcms, "LITTLECMS2"), (feature.webp, "WEBP"), (feature.webpmux, "WEBPMUX"), ]