Merge pull request #1988 from wiredfool/iccprofile

Binary Tiff Metadata/ICC profile.
This commit is contained in:
wiredfool 2016-06-29 22:35:03 +01:00 committed by GitHub
commit ffcc067038
5 changed files with 49 additions and 22 deletions

View File

@ -544,8 +544,7 @@ class ImageFileDirectory_v2(collections.MutableMapping):
self.tagtype[tag] = 2 self.tagtype[tag] = 2
if self.tagtype[tag] == 7 and bytes is not str: if self.tagtype[tag] == 7 and bytes is not str:
values = [value.encode("ascii", 'replace') if isinstance(value, str) else value values = [value.encode("ascii", 'replace') if isinstance(value, str) else value]
for value in values]
values = tuple(info.cvt_enum(value) for value in values) values = tuple(info.cvt_enum(value) for value in values)
@ -604,8 +603,7 @@ class ImageFileDirectory_v2(collections.MutableMapping):
@_register_loader(1, 1) # Basic type, except for the legacy API. @_register_loader(1, 1) # Basic type, except for the legacy API.
def load_byte(self, data, legacy_api=True): def load_byte(self, data, legacy_api=True):
return (data if legacy_api else return data
tuple(map(ord, data) if bytes is str else data))
@_register_writer(1) # Basic type, except for the legacy API. @_register_writer(1) # Basic type, except for the legacy API.
def write_byte(self, data): def write_byte(self, data):

View File

@ -48,11 +48,21 @@ def lookup(tag):
# #
# id: (Name, Type, Length, enum_values) # id: (Name, Type, Length, enum_values)
# #
# The length here differs from the length in the tiff spec. For
# numbers, the tiff spec is for the number of fields returned. We
# agree here. For string-like types, the tiff spec uses the length of
# field in bytes. In Pillow, we are using the number of expected
# fields, in general 1 for string-like types.
BYTE = 1
ASCII = 2 ASCII = 2
SHORT = 3 SHORT = 3
LONG = 4 LONG = 4
RATIONAL = 5 RATIONAL = 5
UNDEFINED = 7
SIGNED_RATIONAL = 10
DOUBLE = 12
TAGS_V2 = { TAGS_V2 = {
@ -128,8 +138,8 @@ TAGS_V2 = {
338: ("ExtraSamples", SHORT, 0), 338: ("ExtraSamples", SHORT, 0),
339: ("SampleFormat", SHORT, 0), 339: ("SampleFormat", SHORT, 0),
340: ("SMinSampleValue", 12, 0), 340: ("SMinSampleValue", DOUBLE, 0),
341: ("SMaxSampleValue", 12, 0), 341: ("SMaxSampleValue", DOUBLE, 0),
342: ("TransferRange", SHORT, 6), 342: ("TransferRange", SHORT, 6),
# obsolete JPEG tags # obsolete JPEG tags
@ -152,34 +162,34 @@ TAGS_V2 = {
# FIXME add more tags here # FIXME add more tags here
34665: ("ExifIFD", SHORT, 1), 34665: ("ExifIFD", SHORT, 1),
34675: ('ICCProfile', 7, 0), 34675: ('ICCProfile', UNDEFINED, 1),
34853: ('GPSInfoIFD', 1, 1), 34853: ('GPSInfoIFD', BYTE, 1),
# MPInfo # MPInfo
45056: ("MPFVersion", 7, 1), 45056: ("MPFVersion", UNDEFINED, 1),
45057: ("NumberOfImages", LONG, 1), 45057: ("NumberOfImages", LONG, 1),
45058: ("MPEntry", 7, 1), 45058: ("MPEntry", UNDEFINED, 1),
45059: ("ImageUIDList", 7, 0), 45059: ("ImageUIDList", UNDEFINED, 0), # UNDONE, check
45060: ("TotalFrames", LONG, 1), 45060: ("TotalFrames", LONG, 1),
45313: ("MPIndividualNum", LONG, 1), 45313: ("MPIndividualNum", LONG, 1),
45569: ("PanOrientation", LONG, 1), 45569: ("PanOrientation", LONG, 1),
45570: ("PanOverlap_H", RATIONAL, 1), 45570: ("PanOverlap_H", RATIONAL, 1),
45571: ("PanOverlap_V", RATIONAL, 1), 45571: ("PanOverlap_V", RATIONAL, 1),
45572: ("BaseViewpointNum", LONG, 1), 45572: ("BaseViewpointNum", LONG, 1),
45573: ("ConvergenceAngle", 10, 1), 45573: ("ConvergenceAngle", SIGNED_RATIONAL, 1),
45574: ("BaselineLength", RATIONAL, 1), 45574: ("BaselineLength", RATIONAL, 1),
45575: ("VerticalDivergence", 10, 1), 45575: ("VerticalDivergence", SIGNED_RATIONAL, 1),
45576: ("AxisDistance_X", 10, 1), 45576: ("AxisDistance_X", SIGNED_RATIONAL, 1),
45577: ("AxisDistance_Y", 10, 1), 45577: ("AxisDistance_Y", SIGNED_RATIONAL, 1),
45578: ("AxisDistance_Z", 10, 1), 45578: ("AxisDistance_Z", SIGNED_RATIONAL, 1),
45579: ("YawAngle", 10, 1), 45579: ("YawAngle", SIGNED_RATIONAL, 1),
45580: ("PitchAngle", 10, 1), 45580: ("PitchAngle", SIGNED_RATIONAL, 1),
45581: ("RollAngle", 10, 1), 45581: ("RollAngle", SIGNED_RATIONAL, 1),
50741: ("MakerNoteSafety", SHORT, 1, {"Unsafe": 0, "Safe": 1}), 50741: ("MakerNoteSafety", SHORT, 1, {"Unsafe": 0, "Safe": 1}),
50780: ("BestQualityScale", RATIONAL, 1), 50780: ("BestQualityScale", RATIONAL, 1),
50838: ("ImageJMetaDataByteCounts", LONG, 1), 50838: ("ImageJMetaDataByteCounts", LONG, 1),
50839: ("ImageJMetaData", 7, 1) 50839: ("ImageJMetaData", UNDEFINED, 1)
} }
# Legacy Tags structure # Legacy Tags structure

View File

@ -287,7 +287,7 @@ class TestFileTiff(PillowTestCase):
ifd = TiffImagePlugin.ImageFileDirectory_v2() ifd = TiffImagePlugin.ImageFileDirectory_v2()
data = b"abc" data = b"abc"
ret = ifd.load_byte(data, legacy_api) ret = ifd.load_byte(data, legacy_api)
self.assertEqual(ret, b"abc" if legacy_api else (97, 98, 99)) self.assertEqual(ret, b"abc")
def test_load_string(self): def test_load_string(self):
ifd = TiffImagePlugin.ImageFileDirectory_v2() ifd = TiffImagePlugin.ImageFileDirectory_v2()

View File

@ -175,7 +175,7 @@ class TestFileTiffMetadata(PillowTestCase):
im.save(out) im.save(out)
reloaded = Image.open(out) reloaded = Image.open(out)
self.assert_(type(im.info['icc_profile']) is not type(tuple)) self.assert_(type(im.info['icc_profile']) is not tuple)
self.assertEqual(im.info['icc_profile'], reloaded.info['icc_profile']) self.assertEqual(im.info['icc_profile'], reloaded.info['icc_profile'])
def test_iccprofile_binary(self): def test_iccprofile_binary(self):
@ -186,6 +186,16 @@ class TestFileTiffMetadata(PillowTestCase):
self.assertEqual(im.tag_v2.tagtype[34675], 1) self.assertEqual(im.tag_v2.tagtype[34675], 1)
self.assertTrue(im.info['icc_profile']) self.assertTrue(im.info['icc_profile'])
def test_iccprofile_save_png(self):
im = Image.open('Tests/images/hopper.iccprofile.tif')
outfile = self.tempfile('temp.png')
im.save(outfile)
def test_iccprofile_binary_save_png(self):
im = Image.open('Tests/images/hopper.iccprofile_binary.tif')
outfile = self.tempfile('temp.png')
im.save(outfile)
def test_exif_div_zero(self): def test_exif_div_zero(self):
im = hopper() im = hopper()
info = TiffImagePlugin.ImageFileDirectory_v2() info = TiffImagePlugin.ImageFileDirectory_v2()

View File

@ -22,3 +22,12 @@ There are two new options to control the ``build_ext`` task in ``setup.py``:
that are checked for libraries and headers for build systems or that are checked for libraries and headers for build systems or
cross compilers that specify that information in via environment cross compilers that specify that information in via environment
variables. variables.
Image Metadata
==============
The return type for binary data in version 2 Exif and Tiff metadata
has been changed from a tuple of integers to bytes. This is a change
from the behavior since ``3.0.0``.