From e96109b93c58822c93227fcc1044c7f75545f01c Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Thu, 13 Aug 2020 21:36:39 +1000 Subject: [PATCH 1/2] Added writing of subIFDs --- Tests/test_file_jpeg.py | 35 +++++++++++++++++++++-------------- src/PIL/TiffImagePlugin.py | 27 +++++++++++++++++++++------ 2 files changed, 42 insertions(+), 20 deletions(-) diff --git a/Tests/test_file_jpeg.py b/Tests/test_file_jpeg.py index 03a2dba51..6b88ccfdf 100644 --- a/Tests/test_file_jpeg.py +++ b/Tests/test_file_jpeg.py @@ -224,23 +224,30 @@ class TestFileJpeg: # Should not raise a TypeError im._getexif() - def test_exif_gps(self): - # Arrange + def test_exif_gps(self, tmp_path): + expected_exif_gps = { + 0: b"\x00\x00\x00\x01", + 2: 4294967295, + 5: b"\x01", + 30: 65535, + 29: "1999:99:99 99:99:99", + } + gps_index = 34853 + + # Reading with Image.open("Tests/images/exif_gps.jpg") as im: - gps_index = 34853 - expected_exif_gps = { - 0: b"\x00\x00\x00\x01", - 2: 4294967295, - 5: b"\x01", - 30: 65535, - 29: "1999:99:99 99:99:99", - } - - # Act exif = im._getexif() + assert exif[gps_index] == expected_exif_gps - # Assert - assert exif[gps_index] == expected_exif_gps + # Writing + f = str(tmp_path / "temp.jpg") + exif = Image.Exif() + exif[gps_index] = expected_exif_gps + hopper().save(f, exif=exif) + + with Image.open(f) as reloaded: + exif = reloaded._getexif() + assert exif[gps_index] == expected_exif_gps def test_exif_rollback(self): # rolling back exif support in 3.1 to pre-3.0 formatting. diff --git a/src/PIL/TiffImagePlugin.py b/src/PIL/TiffImagePlugin.py index 6a772e48b..7a09107d5 100644 --- a/src/PIL/TiffImagePlugin.py +++ b/src/PIL/TiffImagePlugin.py @@ -567,7 +567,9 @@ class ImageFileDirectory_v2(MutableMapping): elif self.tagtype[tag] == TiffTags.RATIONAL: values = [float(v) if isinstance(v, int) else v for v in values] - values = tuple(info.cvt_enum(value) for value in values) + is_ifd = self.tagtype[tag] == TiffTags.LONG and isinstance(values, dict) + if not is_ifd: + values = tuple(info.cvt_enum(value) for value in values) dest = self._tags_v1 if legacy_api else self._tags_v2 @@ -576,7 +578,7 @@ class ImageFileDirectory_v2(MutableMapping): # Spec'd length == 1, Actual > 1, Warn and truncate. Formerly barfed. # No Spec, Actual length 1, Formerly (<4.2) returned a 1 element tuple. # Don't mess with the legacy api, since it's frozen. - if ( + if not is_ifd and ( (info.length == 1) or self.tagtype[tag] == TiffTags.BYTE or (info.length is None and len(values) == 1 and not legacy_api) @@ -802,11 +804,22 @@ class ImageFileDirectory_v2(MutableMapping): stripoffsets = len(entries) typ = self.tagtype.get(tag) logger.debug("Tag {}, Type: {}, Value: {}".format(tag, typ, value)) - values = value if isinstance(value, tuple) else (value,) - data = self._write_dispatch[typ](self, *values) + is_ifd = typ == TiffTags.LONG and isinstance(value, dict) + if is_ifd: + if self._endian == "<": + ifh = b"II\x2A\x00\x08\x00\x00\x00" + else: + ifh = b"MM\x00\x2A\x00\x00\x00\x08" + ifd = ImageFileDirectory_v2(ifh) + for ifd_tag, ifd_value in self._tags_v2[tag].items(): + ifd[ifd_tag] = ifd_value + data = ifd.tobytes(offset) + else: + values = value if isinstance(value, tuple) else (value,) + data = self._write_dispatch[typ](self, *values) tagname = TiffTags.lookup(tag).name - typname = TYPES.get(typ, "unknown") + typname = "ifd" if is_ifd else TYPES.get(typ, "unknown") msg = "save: %s (%d) - type: %s (%d)" % (tagname, tag, typname, typ) msg += " - value: " + ( "" % len(data) if len(data) >= 16 else str(values) @@ -814,7 +827,9 @@ class ImageFileDirectory_v2(MutableMapping): logger.debug(msg) # count is sum of lengths for string and arbitrary data - if typ in [TiffTags.BYTE, TiffTags.ASCII, TiffTags.UNDEFINED]: + if is_ifd: + count = 1 + elif typ in [TiffTags.BYTE, TiffTags.ASCII, TiffTags.UNDEFINED]: count = len(data) else: count = len(values) From 186a4723c8a67644a8c98a4e83bff2ff869b8e5a Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Wed, 2 Sep 2020 21:14:36 +1000 Subject: [PATCH 2/2] Added test for empty GPS IFD --- Tests/images/empty_gps_ifd.jpg | Bin 0 -> 1087 bytes Tests/test_file_jpeg.py | 19 +++++++++++++++++++ 2 files changed, 19 insertions(+) create mode 100644 Tests/images/empty_gps_ifd.jpg diff --git a/Tests/images/empty_gps_ifd.jpg b/Tests/images/empty_gps_ifd.jpg new file mode 100644 index 0000000000000000000000000000000000000000..28f180b8743a97318161e5caa47e5bcdc3fd5fbc GIT binary patch literal 1087 zcmb7DL2DC16n^t&wux!6*|aLugKI$)Pn&H_(i{{NiwZ3mFCu!dq{1Fb5kx!}rNN8o z+*(kUDvEm23I_3DwI^@hs$TRUcoF;o>iXX9>?BApzGQa3{pNdb-n@AmZ^d7rcxLMK z6bK>U6#XFnihoYt2(JK4Ps3pV$it8Tkc7l=L=TbT!7dYk#OG+-6U7AYB>Yg6QVk4{ zW(*wSC@!0bcSTNWDfkmZ}_!FQ1i-_My1-Q)B*M_u4Z{)bMdal7uw^!oZ1Id+@f%u z^o+_60>2}(TyeIeEWW-hk~%1X610Fqng!n0Xya=wEx)Rj#cSHJnkPEf%R%Q2-UXm{ z7+fS=u=?k^ljJ~mfUoqj#c#E?_ KD?hxY_~Tzl{d_zC literal 0 HcmV?d00001 diff --git a/Tests/test_file_jpeg.py b/Tests/test_file_jpeg.py index 6b88ccfdf..c3b7e2960 100644 --- a/Tests/test_file_jpeg.py +++ b/Tests/test_file_jpeg.py @@ -8,6 +8,7 @@ from PIL import ( ExifTags, Image, ImageFile, + ImageOps, JpegImagePlugin, UnidentifiedImageError, features, @@ -249,6 +250,24 @@ class TestFileJpeg: exif = reloaded._getexif() assert exif[gps_index] == expected_exif_gps + def test_empty_exif_gps(self): + with Image.open("Tests/images/empty_gps_ifd.jpg") as im: + exif = im.getexif() + del exif[0x8769] + + # Assert that it needs to be transposed + assert exif[0x0112] == Image.TRANSVERSE + + # Assert that the GPS IFD is present and empty + assert exif[0x8825] == {} + + transposed = ImageOps.exif_transpose(im) + exif = transposed.getexif() + assert exif[0x8825] == {} + + # Assert that it was transposed + assert 0x0112 not in exif + def test_exif_rollback(self): # rolling back exif support in 3.1 to pre-3.0 formatting. # expected from 2.9, with b/u qualifiers switched for 3.2 compatibility