From 952de2ec4c3479874648754082095603b2a47e77 Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Sat, 29 Dec 2018 16:14:29 +1100 Subject: [PATCH 1/2] Use constants for tag types --- src/PIL/TiffImagePlugin.py | 42 ++++++++++++++++++++++---------------- src/PIL/TiffTags.py | 4 ++++ 2 files changed, 28 insertions(+), 18 deletions(-) diff --git a/src/PIL/TiffImagePlugin.py b/src/PIL/TiffImagePlugin.py index e13a039a2..a3db8e15c 100644 --- a/src/PIL/TiffImagePlugin.py +++ b/src/PIL/TiffImagePlugin.py @@ -557,26 +557,26 @@ class ImageFileDirectory_v2(MutableMapping): else: self.tagtype[tag] = 7 if all(isinstance(v, IFDRational) for v in values): - self.tagtype[tag] = 5 + self.tagtype[tag] = TiffTags.RATIONAL elif all(isinstance(v, int) for v in values): if all(v < 2 ** 16 for v in values): - self.tagtype[tag] = 3 + self.tagtype[tag] = TiffTags.SHORT else: - self.tagtype[tag] = 4 + self.tagtype[tag] = TiffTags.LONG elif all(isinstance(v, float) for v in values): - self.tagtype[tag] = 12 + self.tagtype[tag] = TiffTags.DOUBLE else: if py3: if all(isinstance(v, str) for v in values): - self.tagtype[tag] = 2 + self.tagtype[tag] = TiffTags.ASCII else: # Never treat data as binary by default on Python 2. - self.tagtype[tag] = 2 + self.tagtype[tag] = TiffTags.ASCII - if self.tagtype[tag] == 7 and py3: + if self.tagtype[tag] == TiffTags.UNDEFINED and py3: values = [value.encode("ascii", 'replace') if isinstance( value, str) else value] - elif self.tagtype[tag] == 5: + elif self.tagtype[tag] == TiffTags.RATIONAL: values = [float(v) if isinstance(v, int) else v for v in values] @@ -592,7 +592,10 @@ class ImageFileDirectory_v2(MutableMapping): if (info.length == 1) or \ (info.length is None and len(values) == 1 and not legacy_api): # Don't mess with the legacy api, since it's frozen. - if legacy_api and self.tagtype[tag] in [5, 10]: # rationals + if legacy_api and self.tagtype[tag] in [ + TiffTags.RATIONAL, + TiffTags.SIGNED_RATIONAL + ]: # rationals values = values, try: dest[tag], = values @@ -649,13 +652,13 @@ class ImageFileDirectory_v2(MutableMapping): b"".join(self._pack(fmt, value) for value in values)) list(map(_register_basic, - [(3, "H", "short"), - (4, "L", "long"), - (6, "b", "signed byte"), - (8, "h", "signed short"), - (9, "l", "signed long"), - (11, "f", "float"), - (12, "d", "double")])) + [(TiffTags.SHORT, "H", "short"), + (TiffTags.LONG, "L", "long"), + (TiffTags.SIGNED_BYTE, "b", "signed byte"), + (TiffTags.SIGNED_SHORT, "h", "signed short"), + (TiffTags.SIGNED_LONG, "l", "signed long"), + (TiffTags.FLOAT, "f", "float"), + (TiffTags.DOUBLE, "d", "double")])) @_register_loader(1, 1) # Basic type, except for the legacy API. def load_byte(self, data, legacy_api=True): @@ -811,7 +814,10 @@ class ImageFileDirectory_v2(MutableMapping): print("- value:", values) # count is sum of lengths for string and arbitrary data - count = len(data) if typ in [2, 7] else len(values) + if typ in [TiffTags.ASCII, TiffTags.UNDEFINED]: + count = len(data) + else: + count = len(values) # figure out if data fits into the entry if len(data) <= 4: entries.append((tag, typ, count, data.ljust(4, b"\0"), b"")) @@ -1799,7 +1805,7 @@ class AppendingTiffWriter: # local (not referenced with another offset) self.rewriteLastShortToLong(offset) self.f.seek(-10, os.SEEK_CUR) - self.writeShort(4) # rewrite the type to LONG + self.writeShort(TiffTags.LONG) # rewrite the type to LONG self.f.seek(8, os.SEEK_CUR) elif isShort: self.rewriteLastShort(offset) diff --git a/src/PIL/TiffTags.py b/src/PIL/TiffTags.py index f122a39e9..3e0291512 100644 --- a/src/PIL/TiffTags.py +++ b/src/PIL/TiffTags.py @@ -64,8 +64,12 @@ ASCII = 2 SHORT = 3 LONG = 4 RATIONAL = 5 +SIGNED_BYTE = 6 UNDEFINED = 7 +SIGNED_SHORT = 8 +SIGNED_LONG = 9 SIGNED_RATIONAL = 10 +FLOAT = 11 DOUBLE = 12 TAGS_V2 = { From 6ead422e91bec1facc644f02bc25a561d0a02383 Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Sat, 29 Dec 2018 15:57:49 +1100 Subject: [PATCH 2/2] Added custom string TIFF tags --- Tests/test_file_libtiff.py | 7 ++++++- src/PIL/TiffImagePlugin.py | 10 +++++++--- src/encode.c | 8 +++++--- 3 files changed, 18 insertions(+), 7 deletions(-) diff --git a/Tests/test_file_libtiff.py b/Tests/test_file_libtiff.py index 2ddb2263a..6834b7256 100644 --- a/Tests/test_file_libtiff.py +++ b/Tests/test_file_libtiff.py @@ -235,7 +235,10 @@ class TestFileLibTiff(LibTiffTestCase): def test_custom_metadata(self): custom = { 37000: 4, - 37001: 4.2 + 37001: 4.2, + 37002: 'custom tag value', + 37003: u'custom tag value', + 37004: b'custom tag value' } libtiff_version = TiffImagePlugin._libtiff_version() @@ -256,6 +259,8 @@ class TestFileLibTiff(LibTiffTestCase): reloaded = Image.open(out) for tag, value in custom.items(): + if libtiff and isinstance(value, bytes): + value = value.decode() self.assertEqual(reloaded.tag_v2[tag], value) def test_int_dpi(self): diff --git a/src/PIL/TiffImagePlugin.py b/src/PIL/TiffImagePlugin.py index a3db8e15c..be24624d6 100644 --- a/src/PIL/TiffImagePlugin.py +++ b/src/PIL/TiffImagePlugin.py @@ -1519,12 +1519,16 @@ def _save(im, fp, filename): getattr(im, 'tag_v2', {}).items(), legacy_ifd.items()): # Libtiff can only process certain core items without adding - # them to the custom dictionary. Support has only been been added - # for int and float values + # them to the custom dictionary. + # Support for custom items has only been been added + # for int, float, unicode, string and byte values if tag not in TiffTags.LIBTIFF_CORE: + if TiffTags.lookup(tag).type == TiffTags.UNDEFINED: + continue if (distutils.version.StrictVersion(_libtiff_version()) < distutils.version.StrictVersion("4.0")) \ - or not (isinstance(value, int) or isinstance(value, float)): + or not (isinstance(value, (int, float, str, bytes)) or + (not py3 and isinstance(value, unicode))): # noqa: F821 continue if tag not in atts and tag not in blocklist: if isinstance(value, str if py3 else unicode): # noqa: F821 diff --git a/src/encode.c b/src/encode.c index 3c2f90f18..13da1b999 100644 --- a/src/encode.c +++ b/src/encode.c @@ -885,9 +885,11 @@ PyImaging_LibTiffEncoderNew(PyObject* self, PyObject* args) } } else if (PyBytes_Check(value)) { TRACE(("Setting from Bytes: %d, %s \n", key_int, PyBytes_AsString(value))); - status = ImagingLibTiffSetField(&encoder->state, - (ttag_t) PyInt_AsLong(key), - PyBytes_AsString(value)); + if (is_core_tag || !ImagingLibTiffMergeFieldInfo(&encoder->state, TIFF_ASCII, key_int)) { + status = ImagingLibTiffSetField(&encoder->state, + (ttag_t) PyInt_AsLong(key), + PyBytes_AsString(value)); + } } else if (PyTuple_Check(value)) { Py_ssize_t len,i; float *floatav;