Merge pull request #837 from wiredfool/cms-profile

Return Profile with Transformed Images
This commit is contained in:
Hugo 2014-08-03 09:22:03 +03:00
commit 3fa247fd7c
3 changed files with 102 additions and 8 deletions

View File

@ -150,8 +150,13 @@ for flag in FLAGS.values():
class ImageCmsProfile: class ImageCmsProfile:
def __init__(self, profile): 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): if isStringType(profile):
self._set(core.profile_open(profile), profile) self._set(core.profile_open(profile), profile)
elif hasattr(profile, "read"): elif hasattr(profile, "read"):
@ -169,12 +174,23 @@ class ImageCmsProfile:
self.product_name = None self.product_name = None
self.product_info = None 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): 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.
""" #
# Will return the output profile in the output.info['icc_profile'].
def __init__(self, input, output, input_mode, output_mode, def __init__(self, input, output, input_mode, output_mode,
intent=INTENT_PERCEPTUAL, proof=None, intent=INTENT_PERCEPTUAL, proof=None,
@ -197,6 +213,8 @@ class ImageCmsTransform(Image.ImagePointHandler):
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
self.output_profile = output
def point(self, im): def point(self, im):
return self.apply(im) return self.apply(im)
@ -205,6 +223,7 @@ class ImageCmsTransform(Image.ImagePointHandler):
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)
self.transform.apply(im.im.id, imOut.im.id) self.transform.apply(im.im.id, imOut.im.id)
imOut.info['icc_profile'] = self.output_profile.tobytes()
return imOut return imOut
def apply_in_place(self, im): def apply_in_place(self, im):
@ -212,6 +231,7 @@ class ImageCmsTransform(Image.ImagePointHandler):
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
self.transform.apply(im.im.id, im.im.id) self.transform.apply(im.im.id, im.im.id)
im.info['icc_profile'] = self.output_profile.tobytes()
return im return im
@ -570,7 +590,7 @@ def applyTransform(im, transform, inPlace=0):
with the transform applied is returned (and im is not changed). The with the transform applied is returned (and im is not changed). The
default is False. default is False.
:returns: Either None, or a new PIL Image object, depending on the value of :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: :exception PyCMSError:
""" """

View File

@ -2,8 +2,11 @@ from helper import unittest, PillowTestCase, lena
from PIL import Image from PIL import Image
from io import BytesIO
try: try:
from PIL import ImageCms from PIL import ImageCms
from PIL.ImageCms import ImageCmsProfile
ImageCms.core.profile_open ImageCms.core.profile_open
except ImportError as v: except ImportError as v:
# Skipped via setUp() # Skipped via setUp()
@ -118,7 +121,7 @@ class TestImageCms(PillowTestCase):
def test_extensions(self): def test_extensions(self):
# extensions # extensions
from io import BytesIO
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"]))
self.assertEqual( self.assertEqual(
@ -196,6 +199,11 @@ class TestImageCms(PillowTestCase):
# img_srgb.save('temp.srgb.tif') # visually verified vs ps. # img_srgb.save('temp.srgb.tif') # visually verified vs ps.
self.assert_image_similar(lena(), img_srgb, 30) 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): def test_lab_roundtrip(self):
# check to see if we're at least internally consistent. # check to see if we're at least internally consistent.
@ -205,11 +213,32 @@ class TestImageCms(PillowTestCase):
t2 = ImageCms.buildTransform(pLab, SRGB, "LAB", "RGB") t2 = ImageCms.buildTransform(pLab, SRGB, "LAB", "RGB")
i = ImageCms.applyTransform(lena(), t) i = ImageCms.applyTransform(lena(), t)
self.assertEqual(i.info['icc_profile'],
ImageCmsProfile(pLab).tobytes())
out = ImageCms.applyTransform(i, t2) out = ImageCms.applyTransform(i, t2)
self.assert_image_similar(lena(), out, 2) 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__': if __name__ == '__main__':
unittest.main() unittest.main()

View File

@ -49,7 +49,8 @@ http://www.cazabon.com\n\
/* known to-do list with current version: /* 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 creating custom RGB profiles on the fly
Add support for checking presence of a specific tag in a profile 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); 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 static void
cms_profile_dealloc(CmsProfileObject* self) cms_profile_dealloc(CmsProfileObject* self)
{ {
@ -485,6 +529,7 @@ static PyMethodDef pyCMSdll_methods[] = {
{"profile_open", cms_profile_open, 1}, {"profile_open", cms_profile_open, 1},
{"profile_frombytes", cms_profile_fromstring, 1}, {"profile_frombytes", cms_profile_fromstring, 1},
{"profile_fromstring", cms_profile_fromstring, 1}, {"profile_fromstring", cms_profile_fromstring, 1},
{"profile_tobytes", cms_profile_tobytes, 1},
/* profile and transform functions */ /* profile and transform functions */
{"buildTransform", buildTransform, 1}, {"buildTransform", buildTransform, 1},