Pillow/Tests/test_file_tiff_metadata.py

259 lines
9.4 KiB
Python
Raw Normal View History

import io
import struct
from PIL import Image, TiffImagePlugin, TiffTags
from PIL.TiffImagePlugin import IFDRational, _limit_rational
from .helper import PillowTestCase, hopper
tag_ids = {info.name: info.value for info in TiffTags.TAGS_V2.values()}
2014-06-10 13:10:47 +04:00
class TestFileTiffMetadata(PillowTestCase):
def test_rt_metadata(self):
""" 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
"""
img = hopper()
2014-06-10 13:10:47 +04: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
basetextdata = "This is some arbitrary metadata for a text field"
2019-06-13 18:54:11 +03:00
bindata = basetextdata.encode("ascii") + b" \xff"
2015-09-12 12:11:10 +03:00
textdata = basetextdata + " " + chr(255)
reloaded_textdata = basetextdata + " ?"
floatdata = 12.345
doubledata = 67.89
2014-06-10 13:10:47 +04:00
info = TiffImagePlugin.ImageFileDirectory()
2019-06-13 18:54:11 +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
2019-06-13 18:54:11 +03:00
info[tag_ids["RollAngle"]] = floatdata
info.tagtype[tag_ids["RollAngle"]] = 11
info[tag_ids["YawAngle"]] = doubledata
info.tagtype[tag_ids["YawAngle"]] = 12
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")
2014-06-10 13:10:47 +04:00
img.save(f, tiffinfo=info)
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),))
2019-06-13 18:54:11 +03:00
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
2019-06-13 18:54:11 +03:00
loaded_float = loaded.tag[tag_ids["RollAngle"]][0]
2015-09-12 00:24:35 +03:00
self.assertAlmostEqual(loaded_float, floatdata, places=5)
2019-06-13 18:54:11 +03:00
loaded_double = loaded.tag[tag_ids["YawAngle"]][0]
2015-09-12 00:24:35 +03:00
self.assertAlmostEqual(loaded_double, doubledata)
# check with 2 element ImageJMetaDataByteCounts, issue #2006
2018-01-27 09:07:24 +03:00
info[ImageJMetaDataByteCounts] = (8, len(bindata) - 8)
img.save(f, tiffinfo=info)
loaded = Image.open(f)
2019-06-13 18:54:11 +03:00
self.assertEqual(loaded.tag[ImageJMetaDataByteCounts], (8, len(bindata) - 8))
self.assertEqual(loaded.tag_v2[ImageJMetaDataByteCounts], (8, len(bindata) - 8))
2014-06-10 13:10:47 +04:00
def test_read_metadata(self):
2019-06-13 18:54:11 +03:00
img = Image.open("Tests/images/hopper_g4.tif")
self.assertEqual(
{
"YResolution": IFDRational(4294967295, 113653537),
"PlanarConfiguration": 1,
"BitsPerSample": (1,),
"ImageLength": 128,
"Compression": 4,
"FillOrder": 1,
"RowsPerStrip": 128,
"ResolutionUnit": 3,
"PhotometricInterpretation": 0,
"PageNumber": (0, 1),
"XResolution": IFDRational(4294967295, 113653537),
"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(),
)
2014-06-10 13:10:47 +04:00
def test_write_metadata(self):
""" Test metadata writing through the python code """
2019-06-13 18:54:11 +03:00
img = Image.open("Tests/images/hopper.tif")
2019-06-13 18:54:11 +03:00
f = self.tempfile("temp.tiff")
2014-06-10 13:10:47 +04:00
img.save(f, tiffinfo=img.tag)
2014-06-10 13:10:47 +04:00
loaded = Image.open(f)
original = img.tag_v2.named()
reloaded = loaded.tag_v2.named()
2016-02-05 01:57:13 +03:00
for k, v in original.items():
if isinstance(v, IFDRational):
2019-06-13 18:54:11 +03:00
original[k] = IFDRational(*_limit_rational(v, 2 ** 31))
2018-08-26 06:52:02 +03:00
elif isinstance(v, tuple) and isinstance(v[0], IFDRational):
2019-06-13 18:54:11 +03:00
original[k] = tuple(
IFDRational(*_limit_rational(elt, 2 ** 31)) for elt in v
)
2019-06-13 18:54:11 +03:00
ignored = ["StripByteCounts", "RowsPerStrip", "PageNumber", "StripOffsets"]
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
2019-06-13 18:54:11 +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
2019-06-13 18:54:11 +03:00
self.assert_deep_equal(
original[tag],
value,
"%s didn't roundtrip, %s, %s" % (tag, original[tag], value),
)
2015-12-27 14:27:18 +03:00
else:
2019-06-13 18:54:11 +03:00
self.assertEqual(
original[tag],
value,
"%s didn't roundtrip, %s, %s" % (tag, original[tag], value),
)
2014-06-10 13:10:47 +04:00
for tag, value in original.items():
if tag not in ignored:
2019-06-13 18:54:11 +03:00
self.assertEqual(value, reloaded[tag], "%s didn't roundtrip" % tag)
def test_no_duplicate_50741_tag(self):
2019-06-13 18:54:11 +03:00
self.assertEqual(tag_ids["MakerNoteSafety"], 50741)
self.assertEqual(tag_ids["BestQualityScale"], 50780)
def test_empty_metadata(self):
2019-06-13 18:54:11 +03:00
f = io.BytesIO(b"II*\x00\x08\x00\x00\x00")
head = f.read(8)
info = TiffImagePlugin.ImageFileDirectory(head)
# Should not raise struct.error.
self.assert_warning(UserWarning, info.load, f)
def test_iccprofile(self):
# https://github.com/python-pillow/Pillow/issues/1462
2019-06-13 18:54:11 +03:00
im = Image.open("Tests/images/hopper.iccprofile.tif")
out = self.tempfile("temp.tiff")
im.save(out)
reloaded = Image.open(out)
2019-06-13 18:54:11 +03:00
self.assertNotIsInstance(im.info["icc_profile"], tuple)
self.assertEqual(im.info["icc_profile"], reloaded.info["icc_profile"])
def test_iccprofile_binary(self):
# https://github.com/python-pillow/Pillow/issues/1526
2018-09-27 13:35:00 +03:00
# We should be able to load this,
# but probably won't be able to save it.
2019-06-13 18:54:11 +03:00
im = Image.open("Tests/images/hopper.iccprofile_binary.tif")
self.assertEqual(im.tag_v2.tagtype[34675], 1)
2019-06-13 18:54:11 +03:00
self.assertTrue(im.info["icc_profile"])
def test_iccprofile_save_png(self):
2019-06-13 18:54:11 +03:00
im = Image.open("Tests/images/hopper.iccprofile.tif")
outfile = self.tempfile("temp.png")
im.save(outfile)
def test_iccprofile_binary_save_png(self):
2019-06-13 18:54:11 +03:00
im = Image.open("Tests/images/hopper.iccprofile_binary.tif")
outfile = self.tempfile("temp.png")
im.save(outfile)
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)
2019-06-13 18:54:11 +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].numerator)
self.assertEqual(0, reloaded.tag_v2[41988].denominator)
2019-08-29 12:02:19 +03:00
def test_empty_values(self):
data = io.BytesIO(
2019-06-13 18:54:11 +03:00
b"II*\x00\x08\x00\x00\x00\x03\x00\x1a\x01\x05\x00\x00\x00\x00\x00"
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"
)
head = data.read(8)
info = TiffImagePlugin.ImageFileDirectory_v2(head)
info.load(data)
# Should not raise ValueError.
info = dict(info)
self.assertIn(33432, info)
2017-09-14 19:25:11 +03:00
def test_PhotoshopInfo(self):
2019-06-13 18:54:11 +03:00
im = Image.open("Tests/images/issue_2278.tif")
2017-09-14 19:25:11 +03:00
2019-08-19 14:12:16 +03:00
self.assertEqual(len(im.tag_v2[34377]), 1)
self.assertIsInstance(im.tag_v2[34377][0], bytes)
2019-06-13 18:54:11 +03:00
out = self.tempfile("temp.tiff")
2017-09-14 19:25:11 +03:00
im.save(out)
reloaded = Image.open(out)
2019-08-19 14:12:16 +03:00
self.assertEqual(len(reloaded.tag_v2[34377]), 1)
self.assertIsInstance(reloaded.tag_v2[34377][0], bytes)
2017-09-19 13:35:14 +03:00
def test_too_many_entries(self):
ifd = TiffImagePlugin.ImageFileDirectory_v2()
# 277: ("SamplesPerPixel", SHORT, 1),
2019-06-13 18:54:11 +03:00
ifd._tagdata[277] = struct.pack("hh", 4, 4)
2017-09-19 13:35:14 +03:00
ifd.tagtype[277] = TiffTags.SHORT
# Should not raise ValueError.
self.assert_warning(UserWarning, lambda: ifd[277])