2014-12-29 18:48:01 +03:00
|
|
|
from __future__ import division
|
|
|
|
|
2015-09-15 04:06:51 +03:00
|
|
|
import io
|
|
|
|
import struct
|
|
|
|
|
2014-09-05 13:14:45 +04:00
|
|
|
from helper import unittest, PillowTestCase, hopper
|
2014-06-10 13:10:47 +04:00
|
|
|
|
2013-10-03 09:06:17 +04:00
|
|
|
from PIL import Image, TiffImagePlugin, TiffTags
|
2015-12-27 23:54:14 +03:00
|
|
|
from PIL.TiffImagePlugin import _limit_rational, IFDRational
|
2013-10-03 09:06:17 +04:00
|
|
|
|
2016-11-07 15:33:46 +03:00
|
|
|
tag_ids = {info.name: info.value for info in TiffTags.TAGS_V2.values()}
|
2013-10-03 09:06:17 +04:00
|
|
|
|
2014-06-03 14:02:44 +04:00
|
|
|
|
2014-06-10 13:10:47 +04:00
|
|
|
class TestFileTiffMetadata(PillowTestCase):
|
|
|
|
|
|
|
|
def test_rt_metadata(self):
|
2014-08-26 16:51:37 +04:00
|
|
|
""" Test writing arbitrary metadata into the tiff image directory
|
2014-06-10 13:10:47 +04:00
|
|
|
Use case is ImageJ private tags, one numeric, one arbitrary
|
|
|
|
data. https://github.com/python-pillow/Pillow/issues/291
|
|
|
|
"""
|
|
|
|
|
2014-09-05 13:14:45 +04:00
|
|
|
img = hopper()
|
2014-06-10 13:10:47 +04:00
|
|
|
|
2015-09-13 16:08:49 +03:00
|
|
|
# Behaviour change: re #1416
|
2015-09-12 12:11:10 +03:00
|
|
|
# Pre ifd rewrite, ImageJMetaData was being written as a string(2),
|
|
|
|
# Post ifd rewrite, it's defined as arbitrary bytes(7). It should
|
|
|
|
# roundtrip with the actual bytes, rather than stripped text
|
|
|
|
# of the premerge tests.
|
|
|
|
#
|
|
|
|
# For text items, we still have to decode('ascii','replace') because
|
|
|
|
# the tiff file format can't take 8 bit bytes in that field.
|
2015-12-10 01:35:35 +03:00
|
|
|
|
2015-06-27 19:35:36 +03:00
|
|
|
basetextdata = "This is some arbitrary metadata for a text field"
|
2015-09-12 12:11:10 +03:00
|
|
|
bindata = basetextdata.encode('ascii') + b" \xff"
|
|
|
|
textdata = basetextdata + " " + chr(255)
|
|
|
|
reloaded_textdata = basetextdata + " ?"
|
2015-02-23 12:51:42 +03:00
|
|
|
floatdata = 12.345
|
|
|
|
doubledata = 67.89
|
2014-06-10 13:10:47 +04:00
|
|
|
info = TiffImagePlugin.ImageFileDirectory()
|
|
|
|
|
2015-09-12 12:11:10 +03:00
|
|
|
ImageJMetaData = tag_ids['ImageJMetaData']
|
|
|
|
ImageJMetaDataByteCounts = tag_ids['ImageJMetaDataByteCounts']
|
|
|
|
ImageDescription = tag_ids['ImageDescription']
|
2015-12-10 01:35:35 +03:00
|
|
|
|
2015-09-12 12:11:10 +03:00
|
|
|
info[ImageJMetaDataByteCounts] = len(bindata)
|
|
|
|
info[ImageJMetaData] = bindata
|
2015-02-23 12:51:42 +03:00
|
|
|
info[tag_ids['RollAngle']] = floatdata
|
|
|
|
info.tagtype[tag_ids['RollAngle']] = 11
|
2015-02-23 13:09:01 +03:00
|
|
|
info[tag_ids['YawAngle']] = doubledata
|
2015-02-23 12:51:42 +03:00
|
|
|
info.tagtype[tag_ids['YawAngle']] = 12
|
2013-10-03 09:06:17 +04:00
|
|
|
|
2015-09-12 12:11:10 +03:00
|
|
|
info[ImageDescription] = textdata
|
2015-12-10 01:35:35 +03:00
|
|
|
|
2014-06-10 13:10:47 +04:00
|
|
|
f = self.tempfile("temp.tif")
|
2013-10-03 09:06:17 +04:00
|
|
|
|
2014-06-10 13:10:47 +04:00
|
|
|
img.save(f, tiffinfo=info)
|
2013-10-03 09:06:17 +04:00
|
|
|
|
2015-09-12 00:24:35 +03:00
|
|
|
loaded = Image.open(f)
|
|
|
|
|
2015-09-12 12:11:10 +03:00
|
|
|
self.assertEqual(loaded.tag[ImageJMetaDataByteCounts], (len(bindata),))
|
|
|
|
self.assertEqual(loaded.tag_v2[ImageJMetaDataByteCounts], len(bindata))
|
2015-09-12 00:24:35 +03:00
|
|
|
|
2015-09-12 12:11:10 +03:00
|
|
|
self.assertEqual(loaded.tag[ImageJMetaData], bindata)
|
|
|
|
self.assertEqual(loaded.tag_v2[ImageJMetaData], bindata)
|
2015-09-12 00:24:35 +03:00
|
|
|
|
2015-09-12 12:11:10 +03:00
|
|
|
self.assertEqual(loaded.tag[ImageDescription], (reloaded_textdata,))
|
|
|
|
self.assertEqual(loaded.tag_v2[ImageDescription], reloaded_textdata)
|
2015-09-12 00:24:35 +03:00
|
|
|
|
|
|
|
loaded_float = loaded.tag[tag_ids['RollAngle']][0]
|
|
|
|
self.assertAlmostEqual(loaded_float, floatdata, places=5)
|
|
|
|
loaded_double = loaded.tag[tag_ids['YawAngle']][0]
|
|
|
|
self.assertAlmostEqual(loaded_double, doubledata)
|
2013-10-03 09:06:17 +04:00
|
|
|
|
2014-06-10 13:10:47 +04:00
|
|
|
def test_read_metadata(self):
|
2015-09-12 00:24:35 +03:00
|
|
|
img = Image.open('Tests/images/hopper_g4.tif')
|
|
|
|
|
2015-12-27 23:47:13 +03:00
|
|
|
self.assertEqual({'YResolution': IFDRational(4294967295, 113653537),
|
2015-09-12 00:24:35 +03:00
|
|
|
'PlanarConfiguration': 1,
|
|
|
|
'BitsPerSample': (1,),
|
|
|
|
'ImageLength': 128,
|
|
|
|
'Compression': 4,
|
|
|
|
'FillOrder': 1,
|
|
|
|
'RowsPerStrip': 128,
|
|
|
|
'ResolutionUnit': 3,
|
|
|
|
'PhotometricInterpretation': 0,
|
|
|
|
'PageNumber': (0, 1),
|
2015-12-27 23:47:13 +03:00
|
|
|
'XResolution': IFDRational(4294967295, 113653537),
|
2015-09-12 00:24:35 +03:00
|
|
|
'ImageWidth': 128,
|
|
|
|
'Orientation': 1,
|
|
|
|
'StripByteCounts': (1968,),
|
|
|
|
'SamplesPerPixel': 1,
|
|
|
|
'StripOffsets': (8,)
|
|
|
|
}, img.tag_v2.named())
|
|
|
|
|
|
|
|
self.assertEqual({'YResolution': ((4294967295, 113653537),),
|
|
|
|
'PlanarConfiguration': (1,),
|
|
|
|
'BitsPerSample': (1,),
|
|
|
|
'ImageLength': (128,),
|
|
|
|
'Compression': (4,),
|
|
|
|
'FillOrder': (1,),
|
|
|
|
'RowsPerStrip': (128,),
|
|
|
|
'ResolutionUnit': (3,),
|
|
|
|
'PhotometricInterpretation': (0,),
|
|
|
|
'PageNumber': (0, 1),
|
|
|
|
'XResolution': ((4294967295, 113653537),),
|
|
|
|
'ImageWidth': (128,),
|
|
|
|
'Orientation': (1,),
|
|
|
|
'StripByteCounts': (1968,),
|
|
|
|
'SamplesPerPixel': (1,),
|
|
|
|
'StripOffsets': (8,)
|
|
|
|
}, img.tag.named())
|
2013-10-08 10:01:15 +04:00
|
|
|
|
2014-06-10 13:10:47 +04:00
|
|
|
def test_write_metadata(self):
|
|
|
|
""" Test metadata writing through the python code """
|
2014-09-05 13:14:45 +04:00
|
|
|
img = Image.open('Tests/images/hopper.tif')
|
2013-10-08 10:01:15 +04:00
|
|
|
|
2014-06-10 13:10:47 +04:00
|
|
|
f = self.tempfile('temp.tiff')
|
|
|
|
img.save(f, tiffinfo=img.tag)
|
2013-10-08 10:01:15 +04:00
|
|
|
|
2014-06-10 13:10:47 +04:00
|
|
|
loaded = Image.open(f)
|
2013-10-08 10:01:15 +04:00
|
|
|
|
2015-09-13 16:08:49 +03:00
|
|
|
original = img.tag_v2.named()
|
|
|
|
reloaded = loaded.tag_v2.named()
|
2013-10-08 10:01:15 +04:00
|
|
|
|
2016-02-05 01:57:13 +03:00
|
|
|
for k, v in original.items():
|
2016-10-31 03:43:32 +03:00
|
|
|
if isinstance(v, IFDRational):
|
2016-02-05 01:57:13 +03:00
|
|
|
original[k] = IFDRational(*_limit_rational(v, 2**31))
|
2016-10-31 03:43:32 +03:00
|
|
|
if isinstance(v, tuple) and isinstance(v[0], IFDRational):
|
2015-12-27 23:47:13 +03:00
|
|
|
original[k] = tuple([IFDRational(
|
2016-02-05 01:57:13 +03:00
|
|
|
*_limit_rational(elt, 2**31)) for elt in v])
|
2015-12-27 23:47:13 +03:00
|
|
|
|
|
|
|
ignored = ['StripByteCounts', 'RowsPerStrip',
|
|
|
|
'PageNumber', 'StripOffsets']
|
2013-10-08 10:01:15 +04:00
|
|
|
|
2014-06-10 13:10:47 +04:00
|
|
|
for tag, value in reloaded.items():
|
2016-02-05 01:57:13 +03:00
|
|
|
if tag in ignored:
|
|
|
|
continue
|
2016-10-31 03:43:32 +03:00
|
|
|
if (isinstance(original[tag], tuple)
|
|
|
|
and isinstance(original[tag][0], IFDRational)):
|
2015-12-27 14:27:18 +03:00
|
|
|
# Need to compare element by element in the tuple,
|
|
|
|
# not comparing tuples of object references
|
|
|
|
self.assert_deep_equal(original[tag],
|
|
|
|
value,
|
|
|
|
"%s didn't roundtrip, %s, %s" %
|
|
|
|
(tag, original[tag], value))
|
|
|
|
else:
|
2015-10-25 15:49:45 +03:00
|
|
|
self.assertEqual(original[tag],
|
|
|
|
value,
|
|
|
|
"%s didn't roundtrip, %s, %s" %
|
|
|
|
(tag, original[tag], value))
|
2013-10-08 10:01:15 +04:00
|
|
|
|
2014-06-10 13:10:47 +04:00
|
|
|
for tag, value in original.items():
|
|
|
|
if tag not in ignored:
|
|
|
|
self.assertEqual(
|
|
|
|
value, reloaded[tag], "%s didn't roundtrip" % tag)
|
2013-10-08 10:01:15 +04:00
|
|
|
|
2014-08-26 16:51:37 +04:00
|
|
|
def test_no_duplicate_50741_tag(self):
|
|
|
|
self.assertEqual(tag_ids['MakerNoteSafety'], 50741)
|
|
|
|
self.assertEqual(tag_ids['BestQualityScale'], 50780)
|
|
|
|
|
2015-09-15 04:06:51 +03:00
|
|
|
def test_empty_metadata(self):
|
|
|
|
f = io.BytesIO(b'II*\x00\x08\x00\x00\x00')
|
|
|
|
head = f.read(8)
|
|
|
|
info = TiffImagePlugin.ImageFileDirectory(head)
|
|
|
|
try:
|
|
|
|
self.assert_warning(UserWarning, lambda: info.load(f))
|
|
|
|
except struct.error:
|
|
|
|
self.fail("Should not be struct errors there.")
|
|
|
|
|
2015-10-03 18:35:53 +03:00
|
|
|
def test_iccprofile(self):
|
|
|
|
# https://github.com/python-pillow/Pillow/issues/1462
|
2015-10-04 03:04:40 +03:00
|
|
|
im = Image.open('Tests/images/hopper.iccprofile.tif')
|
2015-10-03 18:35:53 +03:00
|
|
|
out = self.tempfile('temp.tiff')
|
|
|
|
|
|
|
|
im.save(out)
|
|
|
|
reloaded = Image.open(out)
|
2016-10-31 03:43:32 +03:00
|
|
|
self.assertNotIsInstance(im.info['icc_profile'], tuple)
|
2015-10-03 18:35:53 +03:00
|
|
|
self.assertEqual(im.info['icc_profile'], reloaded.info['icc_profile'])
|
|
|
|
|
2015-11-15 19:41:49 +03:00
|
|
|
def test_iccprofile_binary(self):
|
|
|
|
# https://github.com/python-pillow/Pillow/issues/1526
|
|
|
|
# We should be able to load this, but probably won't be able to save it.
|
|
|
|
|
|
|
|
im = Image.open('Tests/images/hopper.iccprofile_binary.tif')
|
|
|
|
self.assertEqual(im.tag_v2.tagtype[34675], 1)
|
2015-12-16 06:30:17 +03:00
|
|
|
self.assertTrue(im.info['icc_profile'])
|
2015-11-15 19:41:49 +03:00
|
|
|
|
2016-06-26 14:05:05 +03:00
|
|
|
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)
|
|
|
|
|
2015-12-30 01:00:36 +03:00
|
|
|
def test_exif_div_zero(self):
|
|
|
|
im = hopper()
|
|
|
|
info = TiffImagePlugin.ImageFileDirectory_v2()
|
2016-02-05 01:57:13 +03:00
|
|
|
info[41988] = TiffImagePlugin.IFDRational(0, 0)
|
2015-12-30 01:00:36 +03:00
|
|
|
|
|
|
|
out = self.tempfile('temp.tiff')
|
|
|
|
im.save(out, tiffinfo=info, compression='raw')
|
|
|
|
|
|
|
|
reloaded = Image.open(out)
|
|
|
|
self.assertEqual(0, reloaded.tag_v2[41988][0].numerator)
|
|
|
|
self.assertEqual(0, reloaded.tag_v2[41988][0].denominator)
|
|
|
|
|
2016-07-12 18:09:02 +03:00
|
|
|
def test_expty_values(self):
|
|
|
|
data = io.BytesIO(
|
|
|
|
b'II*\x00\x08\x00\x00\x00\x03\x00\x1a\x01\x05\x00\x00\x00\x00\x00'
|
2016-07-12 21:53:30 +03:00
|
|
|
b'\x00\x00\x00\x00\x1b\x01\x05\x00\x00\x00\x00\x00\x00\x00\x00\x00'
|
|
|
|
b'\x98\x82\x02\x00\x07\x00\x00\x002\x00\x00\x00\x00\x00\x00\x00a '
|
|
|
|
b'text\x00\x00')
|
2016-07-12 18:09:02 +03:00
|
|
|
head = data.read(8)
|
|
|
|
info = TiffImagePlugin.ImageFileDirectory_v2(head)
|
|
|
|
info.load(data)
|
|
|
|
try:
|
2016-07-12 19:23:44 +03:00
|
|
|
info = dict(info)
|
2016-07-12 18:09:02 +03:00
|
|
|
except ValueError:
|
|
|
|
self.fail("Should not be struct value error there.")
|
|
|
|
self.assertIn(33432, info)
|
|
|
|
|
2015-12-30 01:00:36 +03:00
|
|
|
|
2014-06-10 13:10:47 +04:00
|
|
|
if __name__ == '__main__':
|
|
|
|
unittest.main()
|