mirror of
				https://github.com/python-pillow/Pillow.git
				synced 2025-10-31 07:57:27 +03:00 
			
		
		
		
	
		
			
				
	
	
		
			338 lines
		
	
	
		
			12 KiB
		
	
	
	
		
			Python
		
	
	
	
	
	
			
		
		
	
	
			338 lines
		
	
	
		
			12 KiB
		
	
	
	
		
			Python
		
	
	
	
	
	
| import io
 | |
| import struct
 | |
| 
 | |
| import pytest
 | |
| from PIL import Image, TiffImagePlugin, TiffTags
 | |
| from PIL.TiffImagePlugin import IFDRational
 | |
| 
 | |
| from .helper import PillowTestCase, assert_deep_equal, hopper
 | |
| 
 | |
| tag_ids = {info.name: info.value for info in TiffTags.TAGS_V2.values()}
 | |
| 
 | |
| 
 | |
| class TestFileTiffMetadata(PillowTestCase):
 | |
|     def test_rt_metadata(self):
 | |
|         """ Test writing arbitrary metadata into the tiff image directory
 | |
|             Use case is ImageJ private tags, one numeric, one arbitrary
 | |
|             data.  https://github.com/python-pillow/Pillow/issues/291
 | |
|             """
 | |
| 
 | |
|         img = hopper()
 | |
| 
 | |
|         # Behaviour change: re #1416
 | |
|         # 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.
 | |
| 
 | |
|         basetextdata = "This is some arbitrary metadata for a text field"
 | |
|         bindata = basetextdata.encode("ascii") + b" \xff"
 | |
|         textdata = basetextdata + " " + chr(255)
 | |
|         reloaded_textdata = basetextdata + " ?"
 | |
|         floatdata = 12.345
 | |
|         doubledata = 67.89
 | |
|         info = TiffImagePlugin.ImageFileDirectory()
 | |
| 
 | |
|         ImageJMetaData = tag_ids["ImageJMetaData"]
 | |
|         ImageJMetaDataByteCounts = tag_ids["ImageJMetaDataByteCounts"]
 | |
|         ImageDescription = tag_ids["ImageDescription"]
 | |
| 
 | |
|         info[ImageJMetaDataByteCounts] = len(bindata)
 | |
|         info[ImageJMetaData] = bindata
 | |
|         info[tag_ids["RollAngle"]] = floatdata
 | |
|         info.tagtype[tag_ids["RollAngle"]] = 11
 | |
|         info[tag_ids["YawAngle"]] = doubledata
 | |
|         info.tagtype[tag_ids["YawAngle"]] = 12
 | |
| 
 | |
|         info[ImageDescription] = textdata
 | |
| 
 | |
|         f = self.tempfile("temp.tif")
 | |
| 
 | |
|         img.save(f, tiffinfo=info)
 | |
| 
 | |
|         with Image.open(f) as loaded:
 | |
| 
 | |
|             self.assertEqual(loaded.tag[ImageJMetaDataByteCounts], (len(bindata),))
 | |
|             self.assertEqual(loaded.tag_v2[ImageJMetaDataByteCounts], (len(bindata),))
 | |
| 
 | |
|             self.assertEqual(loaded.tag[ImageJMetaData], bindata)
 | |
|             self.assertEqual(loaded.tag_v2[ImageJMetaData], bindata)
 | |
| 
 | |
|             self.assertEqual(loaded.tag[ImageDescription], (reloaded_textdata,))
 | |
|             self.assertEqual(loaded.tag_v2[ImageDescription], reloaded_textdata)
 | |
| 
 | |
|             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)
 | |
| 
 | |
|         # check with 2 element ImageJMetaDataByteCounts, issue #2006
 | |
| 
 | |
|         info[ImageJMetaDataByteCounts] = (8, len(bindata) - 8)
 | |
|         img.save(f, tiffinfo=info)
 | |
|         with Image.open(f) as loaded:
 | |
| 
 | |
|             self.assertEqual(
 | |
|                 loaded.tag[ImageJMetaDataByteCounts], (8, len(bindata) - 8)
 | |
|             )
 | |
|             self.assertEqual(
 | |
|                 loaded.tag_v2[ImageJMetaDataByteCounts], (8, len(bindata) - 8)
 | |
|             )
 | |
| 
 | |
|     def test_read_metadata(self):
 | |
|         with Image.open("Tests/images/hopper_g4.tif") as img:
 | |
| 
 | |
|             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(),
 | |
|             )
 | |
| 
 | |
|     def test_write_metadata(self):
 | |
|         """ Test metadata writing through the python code """
 | |
|         with Image.open("Tests/images/hopper.tif") as img:
 | |
|             f = self.tempfile("temp.tiff")
 | |
|             img.save(f, tiffinfo=img.tag)
 | |
| 
 | |
|             original = img.tag_v2.named()
 | |
| 
 | |
|         with Image.open(f) as loaded:
 | |
|             reloaded = loaded.tag_v2.named()
 | |
| 
 | |
|         ignored = ["StripByteCounts", "RowsPerStrip", "PageNumber", "StripOffsets"]
 | |
| 
 | |
|         for tag, value in reloaded.items():
 | |
|             if tag in ignored:
 | |
|                 continue
 | |
|             if isinstance(original[tag], tuple) and isinstance(
 | |
|                 original[tag][0], IFDRational
 | |
|             ):
 | |
|                 # Need to compare element by element in the tuple,
 | |
|                 # not comparing tuples of object references
 | |
|                 assert_deep_equal(
 | |
|                     original[tag],
 | |
|                     value,
 | |
|                     "{} didn't roundtrip, {}, {}".format(tag, original[tag], value),
 | |
|                 )
 | |
|             else:
 | |
|                 self.assertEqual(
 | |
|                     original[tag],
 | |
|                     value,
 | |
|                     "{} didn't roundtrip, {}, {}".format(tag, original[tag], value),
 | |
|                 )
 | |
| 
 | |
|         for tag, value in original.items():
 | |
|             if tag not in ignored:
 | |
|                 self.assertEqual(value, reloaded[tag], "%s didn't roundtrip" % tag)
 | |
| 
 | |
|     def test_no_duplicate_50741_tag(self):
 | |
|         self.assertEqual(tag_ids["MakerNoteSafety"], 50741)
 | |
|         self.assertEqual(tag_ids["BestQualityScale"], 50780)
 | |
| 
 | |
|     def test_empty_metadata(self):
 | |
|         f = io.BytesIO(b"II*\x00\x08\x00\x00\x00")
 | |
|         head = f.read(8)
 | |
|         info = TiffImagePlugin.ImageFileDirectory(head)
 | |
|         # Should not raise struct.error.
 | |
|         pytest.warns(UserWarning, info.load, f)
 | |
| 
 | |
|     def test_iccprofile(self):
 | |
|         # https://github.com/python-pillow/Pillow/issues/1462
 | |
|         out = self.tempfile("temp.tiff")
 | |
|         with Image.open("Tests/images/hopper.iccprofile.tif") as im:
 | |
|             im.save(out)
 | |
| 
 | |
|         with Image.open(out) as reloaded:
 | |
|             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
 | |
|         # We should be able to load this,
 | |
|         # but probably won't be able to save it.
 | |
| 
 | |
|         with Image.open("Tests/images/hopper.iccprofile_binary.tif") as im:
 | |
|             self.assertEqual(im.tag_v2.tagtype[34675], 1)
 | |
|             self.assertTrue(im.info["icc_profile"])
 | |
| 
 | |
|     def test_iccprofile_save_png(self):
 | |
|         with Image.open("Tests/images/hopper.iccprofile.tif") as im:
 | |
|             outfile = self.tempfile("temp.png")
 | |
|             im.save(outfile)
 | |
| 
 | |
|     def test_iccprofile_binary_save_png(self):
 | |
|         with Image.open("Tests/images/hopper.iccprofile_binary.tif") as im:
 | |
|             outfile = self.tempfile("temp.png")
 | |
|             im.save(outfile)
 | |
| 
 | |
|     def test_exif_div_zero(self):
 | |
|         im = hopper()
 | |
|         info = TiffImagePlugin.ImageFileDirectory_v2()
 | |
|         info[41988] = TiffImagePlugin.IFDRational(0, 0)
 | |
| 
 | |
|         out = self.tempfile("temp.tiff")
 | |
|         im.save(out, tiffinfo=info, compression="raw")
 | |
| 
 | |
|         with Image.open(out) as reloaded:
 | |
|             self.assertEqual(0, reloaded.tag_v2[41988].numerator)
 | |
|             self.assertEqual(0, reloaded.tag_v2[41988].denominator)
 | |
| 
 | |
|     def test_ifd_unsigned_rational(self):
 | |
|         im = hopper()
 | |
|         info = TiffImagePlugin.ImageFileDirectory_v2()
 | |
| 
 | |
|         max_long = 2 ** 32 - 1
 | |
| 
 | |
|         # 4 bytes unsigned long
 | |
|         numerator = max_long
 | |
| 
 | |
|         info[41493] = TiffImagePlugin.IFDRational(numerator, 1)
 | |
| 
 | |
|         out = self.tempfile("temp.tiff")
 | |
|         im.save(out, tiffinfo=info, compression="raw")
 | |
| 
 | |
|         with Image.open(out) as reloaded:
 | |
|             self.assertEqual(max_long, reloaded.tag_v2[41493].numerator)
 | |
|             self.assertEqual(1, reloaded.tag_v2[41493].denominator)
 | |
| 
 | |
|         # out of bounds of 4 byte unsigned long
 | |
|         numerator = max_long + 1
 | |
| 
 | |
|         info[41493] = TiffImagePlugin.IFDRational(numerator, 1)
 | |
| 
 | |
|         out = self.tempfile("temp.tiff")
 | |
|         im.save(out, tiffinfo=info, compression="raw")
 | |
| 
 | |
|         with Image.open(out) as reloaded:
 | |
|             self.assertEqual(max_long, reloaded.tag_v2[41493].numerator)
 | |
|             self.assertEqual(1, reloaded.tag_v2[41493].denominator)
 | |
| 
 | |
|     def test_ifd_signed_rational(self):
 | |
|         im = hopper()
 | |
|         info = TiffImagePlugin.ImageFileDirectory_v2()
 | |
| 
 | |
|         # pair of 4 byte signed longs
 | |
|         numerator = 2 ** 31 - 1
 | |
|         denominator = -(2 ** 31)
 | |
| 
 | |
|         info[37380] = TiffImagePlugin.IFDRational(numerator, denominator)
 | |
| 
 | |
|         out = self.tempfile("temp.tiff")
 | |
|         im.save(out, tiffinfo=info, compression="raw")
 | |
| 
 | |
|         with Image.open(out) as reloaded:
 | |
|             self.assertEqual(numerator, reloaded.tag_v2[37380].numerator)
 | |
|             self.assertEqual(denominator, reloaded.tag_v2[37380].denominator)
 | |
| 
 | |
|         numerator = -(2 ** 31)
 | |
|         denominator = 2 ** 31 - 1
 | |
| 
 | |
|         info[37380] = TiffImagePlugin.IFDRational(numerator, denominator)
 | |
| 
 | |
|         out = self.tempfile("temp.tiff")
 | |
|         im.save(out, tiffinfo=info, compression="raw")
 | |
| 
 | |
|         with Image.open(out) as reloaded:
 | |
|             self.assertEqual(numerator, reloaded.tag_v2[37380].numerator)
 | |
|             self.assertEqual(denominator, reloaded.tag_v2[37380].denominator)
 | |
| 
 | |
|         # out of bounds of 4 byte signed long
 | |
|         numerator = -(2 ** 31) - 1
 | |
|         denominator = 1
 | |
| 
 | |
|         info[37380] = TiffImagePlugin.IFDRational(numerator, denominator)
 | |
| 
 | |
|         out = self.tempfile("temp.tiff")
 | |
|         im.save(out, tiffinfo=info, compression="raw")
 | |
| 
 | |
|         with Image.open(out) as reloaded:
 | |
|             self.assertEqual(2 ** 31 - 1, reloaded.tag_v2[37380].numerator)
 | |
|             self.assertEqual(-1, reloaded.tag_v2[37380].denominator)
 | |
| 
 | |
|     def test_ifd_signed_long(self):
 | |
|         im = hopper()
 | |
|         info = TiffImagePlugin.ImageFileDirectory_v2()
 | |
| 
 | |
|         info[37000] = -60000
 | |
| 
 | |
|         out = self.tempfile("temp.tiff")
 | |
|         im.save(out, tiffinfo=info, compression="raw")
 | |
| 
 | |
|         with Image.open(out) as reloaded:
 | |
|             self.assertEqual(reloaded.tag_v2[37000], -60000)
 | |
| 
 | |
|     def test_empty_values(self):
 | |
|         data = io.BytesIO(
 | |
|             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)
 | |
| 
 | |
|     def test_PhotoshopInfo(self):
 | |
|         with Image.open("Tests/images/issue_2278.tif") as im:
 | |
|             self.assertEqual(len(im.tag_v2[34377]), 1)
 | |
|             self.assertIsInstance(im.tag_v2[34377][0], bytes)
 | |
|             out = self.tempfile("temp.tiff")
 | |
|             im.save(out)
 | |
|         with Image.open(out) as reloaded:
 | |
|             self.assertEqual(len(reloaded.tag_v2[34377]), 1)
 | |
|             self.assertIsInstance(reloaded.tag_v2[34377][0], bytes)
 | |
| 
 | |
|     def test_too_many_entries(self):
 | |
|         ifd = TiffImagePlugin.ImageFileDirectory_v2()
 | |
| 
 | |
|         #    277: ("SamplesPerPixel", SHORT, 1),
 | |
|         ifd._tagdata[277] = struct.pack("hh", 4, 4)
 | |
|         ifd.tagtype[277] = TiffTags.SHORT
 | |
| 
 | |
|         # Should not raise ValueError.
 | |
|         pytest.warns(UserWarning, lambda: ifd[277])
 |