mirror of
https://github.com/python-pillow/Pillow.git
synced 2024-12-27 18:36:17 +03:00
Merge pull request #837 from wiredfool/cms-profile
Return Profile with Transformed Images
This commit is contained in:
commit
3fa247fd7c
|
@ -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:
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
|
|
@ -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()
|
||||||
|
|
||||||
|
|
|
@ -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},
|
||||||
|
|
Loading…
Reference in New Issue
Block a user