From 13eb3d667afb8df8b4ac9a68a4fb936b1f8f3e7e Mon Sep 17 00:00:00 2001 From: wiredfool Date: Tue, 29 Jul 2014 20:44:17 -0700 Subject: [PATCH 1/3] Added profile.tobytes() for ImageCms Profiles --- PIL/ImageCms.py | 2 ++ Tests/test_imagecms.py | 17 +++++++++++++++ _imagingcms.c | 47 +++++++++++++++++++++++++++++++++++++++++- 3 files changed, 65 insertions(+), 1 deletion(-) diff --git a/PIL/ImageCms.py b/PIL/ImageCms.py index 4ea6409d6..9848e5ba2 100644 --- a/PIL/ImageCms.py +++ b/PIL/ImageCms.py @@ -169,6 +169,8 @@ class ImageCmsProfile: self.product_name = None self.product_info = None + def tobytes(self): + return core.profile_tobytes(self.profile) class ImageCmsTransform(Image.ImagePointHandler): diff --git a/Tests/test_imagecms.py b/Tests/test_imagecms.py index 152241f90..74eef3037 100644 --- a/Tests/test_imagecms.py +++ b/Tests/test_imagecms.py @@ -210,6 +210,23 @@ class TestImageCms(PillowTestCase): self.assert_image_similar(lena(), out, 2) + def test_profile_tobytes(self): + from io import BytesIO + i = Image.open("Tests/images/rgb.jpg") + p = ImageCms.getOpenProfile(BytesIO(i.info["icc_profile"])) + + p2 = ImageCms.getOpenProfile(BytesIO(p.tobytes())) + + # not the same bytes as the original icc_profile, + # but it does roundtrip + self.assertEqual(p.tobytes(),p2.tobytes()) + self.assertEqual(ImageCms.getProfileName(p), + ImageCms.getProfileName(p2)) + self.assertEqual(ImageCms.getProfileDescription(p), + ImageCms.getProfileDescription(p2)) + + + if __name__ == '__main__': unittest.main() diff --git a/_imagingcms.c b/_imagingcms.c index 1b7ef49e1..3b822006a 100644 --- a/_imagingcms.c +++ b/_imagingcms.c @@ -49,7 +49,8 @@ http://www.cazabon.com\n\ /* known to-do list with current version: - Verify that PILmode->littleCMStype conversion in findLCMStype is correct for all PIL modes (it probably isn't for the more obscure ones) + Verify that PILmode->littleCMStype conversion in findLCMStype is correct for all + PIL modes (it probably isn't for the more obscure ones) Add support for creating custom RGB profiles on the fly Add support for checking presence of a specific tag in a profile @@ -134,6 +135,49 @@ cms_profile_fromstring(PyObject* self, PyObject* args) return cms_profile_new(hProfile); } +static PyObject* +cms_profile_tobytes(PyObject* self, PyObject* args) +{ + char *pProfile =NULL; + cmsUInt32Number nProfile; + PyObject* CmsProfile; + + cmsHPROFILE *profile; + + PyObject* ret; + if (!PyArg_ParseTuple(args, "O", &CmsProfile)){ + return NULL; + } + + profile = ((CmsProfileObject*)CmsProfile)->profile; + + if (!cmsSaveProfileToMem(profile, pProfile, &nProfile)) { + PyErr_SetString(PyExc_IOError, "Could not determine profile size"); + return NULL; + } + + pProfile = (char*)malloc(nProfile); + if (!pProfile) { + PyErr_SetString(PyExc_IOError, "Out of Memory"); + return NULL; + } + + if (!cmsSaveProfileToMem(profile, pProfile, &nProfile)) { + PyErr_SetString(PyExc_IOError, "Could not get profile"); + free(pProfile); + return NULL; + } + +#if PY_VERSION_HEX >= 0x03000000 + ret = PyBytes_FromStringAndSize(pProfile, (Py_ssize_t)nProfile); +#else + ret = PyString_FromStringAndSize(pProfile, (Py_ssize_t)nProfile); +#endif + + free(pProfile); + return ret; +} + static void cms_profile_dealloc(CmsProfileObject* self) { @@ -485,6 +529,7 @@ static PyMethodDef pyCMSdll_methods[] = { {"profile_open", cms_profile_open, 1}, {"profile_frombytes", cms_profile_fromstring, 1}, {"profile_fromstring", cms_profile_fromstring, 1}, + {"profile_tobytes", cms_profile_tobytes, 1}, /* profile and transform functions */ {"buildTransform", buildTransform, 1}, From 5966278643e356957986c52679184add492bf553 Mon Sep 17 00:00:00 2001 From: wiredfool Date: Tue, 29 Jul 2014 21:20:11 -0700 Subject: [PATCH 2/3] Added im.info['icc_profile'] to results for ImageCms.applyTransform --- PIL/ImageCms.py | 4 ++++ Tests/test_imagecms.py | 14 +++++++++++++- 2 files changed, 17 insertions(+), 1 deletion(-) diff --git a/PIL/ImageCms.py b/PIL/ImageCms.py index 9848e5ba2..e708d1dad 100644 --- a/PIL/ImageCms.py +++ b/PIL/ImageCms.py @@ -199,6 +199,8 @@ class ImageCmsTransform(Image.ImagePointHandler): self.input_mode = self.inputMode = input_mode self.output_mode = self.outputMode = output_mode + self.output_profile = output + def point(self, im): return self.apply(im) @@ -207,6 +209,7 @@ class ImageCmsTransform(Image.ImagePointHandler): if imOut is None: imOut = Image.new(self.output_mode, im.size, None) self.transform.apply(im.im.id, imOut.im.id) + imOut.info['icc_profile'] = self.output_profile.tobytes() return imOut def apply_in_place(self, im): @@ -214,6 +217,7 @@ class ImageCmsTransform(Image.ImagePointHandler): if im.mode != self.output_mode: raise ValueError("mode mismatch") # wrong output mode self.transform.apply(im.im.id, im.im.id) + im.info['icc_profile'] = self.output_profile.tobytes() return im diff --git a/Tests/test_imagecms.py b/Tests/test_imagecms.py index 74eef3037..e731c8945 100644 --- a/Tests/test_imagecms.py +++ b/Tests/test_imagecms.py @@ -2,8 +2,11 @@ from helper import unittest, PillowTestCase, lena from PIL import Image +from io import BytesIO + try: from PIL import ImageCms + from PIL.ImageCms import ImageCmsProfile ImageCms.core.profile_open except ImportError as v: # Skipped via setUp() @@ -118,7 +121,7 @@ class TestImageCms(PillowTestCase): def test_extensions(self): # extensions - from io import BytesIO + i = Image.open("Tests/images/rgb.jpg") p = ImageCms.getOpenProfile(BytesIO(i.info["icc_profile"])) self.assertEqual( @@ -196,6 +199,11 @@ class TestImageCms(PillowTestCase): # img_srgb.save('temp.srgb.tif') # visually verified vs ps. self.assert_image_similar(lena(), img_srgb, 30) + self.assertTrue(img_srgb.info['icc_profile']) + + profile = ImageCmsProfile(BytesIO(img_srgb.info['icc_profile'])) + self.assertTrue('sRGB' in ImageCms.getProfileDescription(profile)) + def test_lab_roundtrip(self): # check to see if we're at least internally consistent. @@ -205,6 +213,10 @@ class TestImageCms(PillowTestCase): t2 = ImageCms.buildTransform(pLab, SRGB, "LAB", "RGB") i = ImageCms.applyTransform(lena(), t) + + self.assertEqual(i.info['icc_profile'], + ImageCmsProfile(pLab).tobytes()) + out = ImageCms.applyTransform(i, t2) self.assert_image_similar(lena(), out, 2) From 6538d971e2da1032241ddeece122d1b14e0c9edc Mon Sep 17 00:00:00 2001 From: wiredfool Date: Sat, 2 Aug 2014 21:22:51 -0700 Subject: [PATCH 3/3] Docs for profile additions --- PIL/ImageCms.py | 26 ++++++++++++++++++++------ 1 file changed, 20 insertions(+), 6 deletions(-) diff --git a/PIL/ImageCms.py b/PIL/ImageCms.py index e708d1dad..04a3f7f61 100644 --- a/PIL/ImageCms.py +++ b/PIL/ImageCms.py @@ -150,8 +150,13 @@ for flag in FLAGS.values(): class ImageCmsProfile: def __init__(self, profile): - # accepts a string (filename), a file-like object, or a low-level - # profile object + """ + :param profile: Either a string representing a filename, + a file like object containing a profile or a + low-level profile object + + """ + if isStringType(profile): self._set(core.profile_open(profile), profile) elif hasattr(profile, "read"): @@ -170,13 +175,22 @@ class ImageCmsProfile: self.product_info = None def tobytes(self): + """ + Returns the profile in a format suitable for embedding in + saved images. + + :returns: a bytes object containing the ICC profile. + """ + return core.profile_tobytes(self.profile) class ImageCmsTransform(Image.ImagePointHandler): - """Transform. This can be used with the procedural API, or with the - standard Image.point() method. - """ + # Transform. This can be used with the procedural API, or with the + # standard Image.point() method. + # + # Will return the output profile in the output.info['icc_profile']. + def __init__(self, input, output, input_mode, output_mode, intent=INTENT_PERCEPTUAL, proof=None, @@ -576,7 +590,7 @@ def applyTransform(im, transform, inPlace=0): with the transform applied is returned (and im is not changed). The default is False. :returns: Either None, or a new PIL Image object, depending on the value of - inPlace + inPlace. The profile will be returned in the image's info['icc_profile']. :exception PyCMSError: """