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.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

View File

@ -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": ('<u2', None),

View File

@ -42,6 +42,8 @@ pyCMS
Version History:
1.0.0 pil Oct 2013 Port to LCMS 2.
0.1.0 pil mod March 10, 2009
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.filename = filename
if profile:
self.product_name = profile.product_name
self.product_info = profile.product_info
self.product_name = None #profile.product_name
self.product_info = None #profile.product_info
else:
self.product_name = 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)
if colorSpace == "LAB":
if isinstance(colorTemp, float):
colorTemp = int(colorTemp + 0.5)
if not isinstance(colorTemp, int):
raise PyCMSError("Color temperature must be a positive integer, \"%s\" not valid" % colorTemp)
try:
colorTemp = float(colorTemp)
except:
raise PyCMSError("Color temperature must be numeric, \"%s\" not valid" % colorTemp)
try:
return core.createProfile(colorSpace, colorTemp)
@ -597,7 +599,19 @@ def getProfileName(profile):
# add an extra newline to preserve pyCMS compatibility
if not isinstance(profile, ImageCmsProfile):
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:
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.
#

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.
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)

View File

@ -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 <windef.h>
#include <wingdi.h>
#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 },

View File

@ -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

View File

@ -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

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
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},

View File

@ -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 */

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
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},

View File

@ -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

View File

@ -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"), ]