From 990fb03218440e9aa2f4dd18c4ea7806a96cc8ed Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Sun, 26 Aug 2018 13:52:02 +1000 Subject: [PATCH 1/6] Changed if to elif --- Tests/test_file_tiff_metadata.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Tests/test_file_tiff_metadata.py b/Tests/test_file_tiff_metadata.py index 9c615354c..7407cc456 100644 --- a/Tests/test_file_tiff_metadata.py +++ b/Tests/test_file_tiff_metadata.py @@ -135,7 +135,7 @@ class TestFileTiffMetadata(PillowTestCase): for k, v in original.items(): if isinstance(v, IFDRational): original[k] = IFDRational(*_limit_rational(v, 2**31)) - if isinstance(v, tuple) and isinstance(v[0], IFDRational): + elif isinstance(v, tuple) and isinstance(v[0], IFDRational): original[k] = tuple(IFDRational(*_limit_rational(elt, 2**31)) for elt in v) From a3d45e9cefd05105a360588cd3087ccd5ae63b35 Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Thu, 25 Oct 2018 19:45:13 +1100 Subject: [PATCH 2/6] Added custom int and float TIFF tags --- Tests/test_file_libtiff.py | 18 +++++++++++++++ src/PIL/TiffImagePlugin.py | 9 ++++---- src/PIL/TiffTags.py | 1 + src/encode.c | 46 ++++++++++++++++++++++++++----------- src/libImaging/TiffDecode.c | 13 +++++++++++ src/libImaging/TiffDecode.h | 1 + 6 files changed, 69 insertions(+), 19 deletions(-) diff --git a/Tests/test_file_libtiff.py b/Tests/test_file_libtiff.py index 58a3d38c5..19c952ae4 100644 --- a/Tests/test_file_libtiff.py +++ b/Tests/test_file_libtiff.py @@ -231,6 +231,24 @@ class TestFileLibTiff(LibTiffTestCase): TiffImagePlugin.WRITE_LIBTIFF = False + def test_custom_metadata(self): + custom = { + 37000: 4, + 37001: 4.2 + } + for libtiff in [False, True]: + TiffImagePlugin.WRITE_LIBTIFF = libtiff + + im = hopper() + + out = self.tempfile("temp.tif") + im.save(out, tiffinfo=custom) + TiffImagePlugin.WRITE_LIBTIFF = False + + reloaded = Image.open(out) + for tag, value in custom.items(): + self.assertEqual(reloaded.tag_v2[tag], value) + def test_int_dpi(self): # issue #1765 im = hopper('RGB') diff --git a/src/PIL/TiffImagePlugin.py b/src/PIL/TiffImagePlugin.py index 5059a1324..c2b09e294 100644 --- a/src/PIL/TiffImagePlugin.py +++ b/src/PIL/TiffImagePlugin.py @@ -1499,11 +1499,10 @@ 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. It will segfault if it attempts - # to add a custom tag without the dictionary entry - # - # UNDONE -- add code for the custom dictionary - if tag not in TiffTags.LIBTIFF_CORE: + # them to the custom dictionary. Support has only been been added + # for int and float values + if tag not in TiffTags.LIBTIFF_CORE and not \ + (isinstance(value, int) or isinstance(value, float)): continue if tag not in atts and tag not in blocklist: if isinstance(value, str if py3 else unicode): diff --git a/src/PIL/TiffTags.py b/src/PIL/TiffTags.py index c1e14af4a..d810f2f35 100644 --- a/src/PIL/TiffTags.py +++ b/src/PIL/TiffTags.py @@ -425,6 +425,7 @@ TYPES = {} # some of these are not in our TAGS_V2 dict and were included from tiff.h +# This list also exists in encode.c LIBTIFF_CORE = {255, 256, 257, 258, 259, 262, 263, 266, 274, 277, 278, 280, 281, 340, 341, 282, 283, 284, 286, 287, 296, 297, 321, 320, 338, 32995, 322, 323, 32998, diff --git a/src/encode.c b/src/encode.c index c60048c41..385d576dd 100644 --- a/src/encode.c +++ b/src/encode.c @@ -804,7 +804,13 @@ PyImaging_LibTiffEncoderNew(PyObject* self, PyObject* args) PyObject *dir; PyObject *key, *value; Py_ssize_t pos = 0; - int status; + int key_int, status, is_core_tag, i; + // This list also exists in TiffTags.py + const int tags[32] = { + 256, 257, 258, 259, 262, 263, 266, 269, 274, 277, 278, 280, 281, 340, + 341, 282, 283, 284, 286, 287, 296, 297, 321, 338, 32995, 32998, 32996, + 339, 32997, 330, 531, 530 + }; Py_ssize_t d_size; PyObject *keys, *values; @@ -845,21 +851,33 @@ PyImaging_LibTiffEncoderNew(PyObject* self, PyObject* args) for (pos = 0; pos < d_size; pos++) { key = PyList_GetItem(keys, pos); + key_int = (int)PyInt_AsLong(key); value = PyList_GetItem(values, pos); status = 0; - TRACE(("Attempting to set key: %d\n", (int)PyInt_AsLong(key))); + is_core_tag = 0; + for (i=0; i<32; i++) { + if (tags[i] == key_int) { + is_core_tag = 1; + break; + } + } + TRACE(("Attempting to set key: %d\n", key_int)); if (PyInt_Check(value)) { - TRACE(("Setting from Int: %d %ld \n", (int)PyInt_AsLong(key),PyInt_AsLong(value))); - status = ImagingLibTiffSetField(&encoder->state, - (ttag_t) PyInt_AsLong(key), - PyInt_AsLong(value)); + TRACE(("Setting from Int: %d %ld \n", key_int, PyInt_AsLong(value))); + if (is_core_tag || !ImagingLibTiffMergeFieldInfo(&encoder->state, TIFF_LONG, key_int)) { + status = ImagingLibTiffSetField(&encoder->state, + (ttag_t) PyInt_AsLong(key), + PyInt_AsLong(value)); + } } else if (PyFloat_Check(value)) { - TRACE(("Setting from Float: %d, %f \n", (int)PyInt_AsLong(key),PyFloat_AsDouble(value))); - status = ImagingLibTiffSetField(&encoder->state, - (ttag_t) PyInt_AsLong(key), - (float)PyFloat_AsDouble(value)); + TRACE(("Setting from Float: %d, %f \n", key_int, PyFloat_AsDouble(value))); + if (is_core_tag || !ImagingLibTiffMergeFieldInfo(&encoder->state, TIFF_DOUBLE, key_int)) { + status = ImagingLibTiffSetField(&encoder->state, + (ttag_t) PyInt_AsLong(key), + (double)PyFloat_AsDouble(value)); + } } else if (PyBytes_Check(value)) { - TRACE(("Setting from Bytes: %d, %s \n", (int)PyInt_AsLong(key),PyBytes_AsString(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)); @@ -867,7 +885,7 @@ PyImaging_LibTiffEncoderNew(PyObject* self, PyObject* args) Py_ssize_t len,i; float *floatav; int *intav; - TRACE(("Setting from Tuple: %d \n", (int)PyInt_AsLong(key))); + TRACE(("Setting from Tuple: %d \n", key_int)); len = PyTuple_Size(value); if (len) { if (PyInt_Check(PyTuple_GetItem(value,0))) { @@ -898,13 +916,13 @@ PyImaging_LibTiffEncoderNew(PyObject* self, PyObject* args) } } else { TRACE(("Unhandled type in tuple for key %d : %s \n", - (int)PyInt_AsLong(key), + key_int, PyBytes_AsString(PyObject_Str(value)))); } } } else { TRACE(("Unhandled type for key %d : %s \n", - (int)PyInt_AsLong(key), + key_int, PyBytes_AsString(PyObject_Str(value)))); } if (!status) { diff --git a/src/libImaging/TiffDecode.c b/src/libImaging/TiffDecode.c index e77afdf61..bfab4b79e 100644 --- a/src/libImaging/TiffDecode.c +++ b/src/libImaging/TiffDecode.c @@ -402,6 +402,19 @@ int ImagingLibTiffEncodeInit(ImagingCodecState state, char *filename, int fp) { } +int ImagingLibTiffMergeFieldInfo(ImagingCodecState state, TIFFDataType field_type, int key){ + TIFFSTATE *clientstate = (TIFFSTATE *)state->context; + char field_name[10]; + uint32 n; + + const TIFFFieldInfo info[] = { + { key, 0, 1, field_type, FIELD_CUSTOM, 1, 0, field_name } + }; + n = sizeof(info) / sizeof(info[0]); + + return TIFFMergeFieldInfo(clientstate->tiff, info, n); +} + int ImagingLibTiffSetField(ImagingCodecState state, ttag_t tag, ...){ // after tif_dir.c->TIFFSetField. TIFFSTATE *clientstate = (TIFFSTATE *)state->context; diff --git a/src/libImaging/TiffDecode.h b/src/libImaging/TiffDecode.h index e14a09329..e29a6c88f 100644 --- a/src/libImaging/TiffDecode.h +++ b/src/libImaging/TiffDecode.h @@ -45,6 +45,7 @@ typedef struct { extern int ImagingLibTiffInit(ImagingCodecState state, int fp, int offset); extern int ImagingLibTiffEncodeInit(ImagingCodecState state, char *filename, int fp); +extern int ImagingLibTiffMergeFieldInfo(ImagingCodecState state, TIFFDataType field_type, int key); extern int ImagingLibTiffSetField(ImagingCodecState state, ttag_t tag, ...); From ddf8593e7bb4f5edc64c96ea730076920ea8cf33 Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Thu, 25 Oct 2018 19:36:49 +1100 Subject: [PATCH 3/6] Do not write custom tags with libtiff < 4 --- Tests/test_file_libtiff.py | 11 ++++++++++- Tests/versions.py | 1 + src/PIL/TiffImagePlugin.py | 13 ++++++++++--- src/_imaging.c | 7 +++++++ src/libImaging/TiffDecode.c | 16 +++++++++++++++- 5 files changed, 43 insertions(+), 5 deletions(-) diff --git a/Tests/test_file_libtiff.py b/Tests/test_file_libtiff.py index 19c952ae4..2ddb2263a 100644 --- a/Tests/test_file_libtiff.py +++ b/Tests/test_file_libtiff.py @@ -8,6 +8,7 @@ import io import logging import itertools import os +import distutils.version from PIL import Image, TiffImagePlugin, TiffTags @@ -236,7 +237,15 @@ class TestFileLibTiff(LibTiffTestCase): 37000: 4, 37001: 4.2 } - for libtiff in [False, True]: + + libtiff_version = TiffImagePlugin._libtiff_version() + + libtiffs = [False] + if distutils.version.StrictVersion(libtiff_version) >= \ + distutils.version.StrictVersion("4.0"): + libtiffs.append(True) + + for libtiff in libtiffs: TiffImagePlugin.WRITE_LIBTIFF = libtiff im = hopper() diff --git a/Tests/versions.py b/Tests/versions.py index abc1a3be9..835865b37 100644 --- a/Tests/versions.py +++ b/Tests/versions.py @@ -10,6 +10,7 @@ def version(module, version): version(Image, "jpeglib") version(Image, "zlib") +version(Image, "libtiff") try: from PIL import ImageFont diff --git a/src/PIL/TiffImagePlugin.py b/src/PIL/TiffImagePlugin.py index c2b09e294..2b4ddb038 100644 --- a/src/PIL/TiffImagePlugin.py +++ b/src/PIL/TiffImagePlugin.py @@ -54,6 +54,7 @@ import os import struct import sys import warnings +import distutils.version from .TiffTags import TYPES @@ -284,6 +285,10 @@ def _limit_rational(val, max_val): return n_d[::-1] if inv else n_d +def _libtiff_version(): + return Image.core.libtiff_version.split("\n")[0].split("Version ")[1] + + ## # Wrapper for TIFF IFDs. @@ -1501,9 +1506,11 @@ def _save(im, fp, filename): # 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 - if tag not in TiffTags.LIBTIFF_CORE and not \ - (isinstance(value, int) or isinstance(value, float)): - continue + if tag not in TiffTags.LIBTIFF_CORE: + if (distutils.version.StrictVersion(_libtiff_version()) < + distutils.version.StrictVersion("4.0")) \ + or not (isinstance(value, int) or isinstance(value, float)): + continue if tag not in atts and tag not in blocklist: if isinstance(value, str if py3 else unicode): atts[tag] = value.encode('ascii', 'replace') + b"\0" diff --git a/src/_imaging.c b/src/_imaging.c index 474444348..5b8ccb8ad 100644 --- a/src/_imaging.c +++ b/src/_imaging.c @@ -3849,6 +3849,13 @@ setup_module(PyObject* m) { } #endif +#ifdef HAVE_LIBTIFF + { + extern const char * ImagingTiffVersion(void); + PyDict_SetItemString(d, "libtiff_version", PyUnicode_FromString(ImagingTiffVersion())); + } +#endif + PyDict_SetItemString(d, "PILLOW_VERSION", PyUnicode_FromString(version)); return 0; diff --git a/src/libImaging/TiffDecode.c b/src/libImaging/TiffDecode.c index bfab4b79e..64ac86e6a 100644 --- a/src/libImaging/TiffDecode.c +++ b/src/libImaging/TiffDecode.c @@ -406,13 +406,20 @@ int ImagingLibTiffMergeFieldInfo(ImagingCodecState state, TIFFDataType field_typ TIFFSTATE *clientstate = (TIFFSTATE *)state->context; char field_name[10]; uint32 n; + int status = 0; const TIFFFieldInfo info[] = { { key, 0, 1, field_type, FIELD_CUSTOM, 1, 0, field_name } }; n = sizeof(info) / sizeof(info[0]); - return TIFFMergeFieldInfo(clientstate->tiff, info, n); + // Test for libtiff 4.0 or later, excluding libtiff 3.9.6 and 3.9.7 +#if TIFFLIB_VERSION >= 20111221 && TIFFLIB_VERSION != 20120218 && TIFFLIB_VERSION != 20120922 + status = TIFFMergeFieldInfo(clientstate->tiff, info, n); +#else + TIFFMergeFieldInfo(clientstate->tiff, info, n); +#endif + return status; } int ImagingLibTiffSetField(ImagingCodecState state, ttag_t tag, ...){ @@ -514,4 +521,11 @@ int ImagingLibTiffEncode(Imaging im, ImagingCodecState state, UINT8* buffer, int state->errcode = IMAGING_CODEC_END; return 0; } + +const char* +ImagingTiffVersion(void) +{ + return TIFFGetVersion(); +} + #endif From 207ed04d87cd29f0117566d1191bdcc6e5716acb Mon Sep 17 00:00:00 2001 From: Hugo Date: Thu, 25 Oct 2018 20:17:20 +1100 Subject: [PATCH 4/6] Removed hardcoded array size Co-Authored-By: radarhere --- src/encode.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/encode.c b/src/encode.c index 385d576dd..238eabd1e 100644 --- a/src/encode.c +++ b/src/encode.c @@ -855,7 +855,8 @@ PyImaging_LibTiffEncoderNew(PyObject* self, PyObject* args) value = PyList_GetItem(values, pos); status = 0; is_core_tag = 0; - for (i=0; i<32; i++) { + int number_of_tags = sizeof(tags) / sizeof(int); + for (i=0; i Date: Thu, 25 Oct 2018 20:18:49 +1100 Subject: [PATCH 5/6] Changed array size to be arbitrary Co-Authored-By: radarhere --- src/encode.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/encode.c b/src/encode.c index 238eabd1e..2626e0eb8 100644 --- a/src/encode.c +++ b/src/encode.c @@ -806,7 +806,7 @@ PyImaging_LibTiffEncoderNew(PyObject* self, PyObject* args) Py_ssize_t pos = 0; int key_int, status, is_core_tag, i; // This list also exists in TiffTags.py - const int tags[32] = { + const int tags[] = { 256, 257, 258, 259, 262, 263, 266, 269, 274, 277, 278, 280, 281, 340, 341, 282, 283, 284, 286, 287, 296, 297, 321, 338, 32995, 32998, 32996, 339, 32997, 330, 531, 530 From d8e66c1882a11a0f81c1f3e3308a64134779ab55 Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Thu, 25 Oct 2018 21:00:38 +1100 Subject: [PATCH 6/6] Moved variable declaration and execution --- src/encode.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/encode.c b/src/encode.c index 2626e0eb8..bce1c2988 100644 --- a/src/encode.c +++ b/src/encode.c @@ -804,7 +804,7 @@ PyImaging_LibTiffEncoderNew(PyObject* self, PyObject* args) PyObject *dir; PyObject *key, *value; Py_ssize_t pos = 0; - int key_int, status, is_core_tag, i; + int key_int, status, is_core_tag, number_of_tags, i; // This list also exists in TiffTags.py const int tags[] = { 256, 257, 258, 259, 262, 263, 266, 269, 274, 277, 278, 280, 281, 340, @@ -849,13 +849,13 @@ PyImaging_LibTiffEncoderNew(PyObject* self, PyObject* args) return NULL; } + number_of_tags = sizeof(tags) / sizeof(int); for (pos = 0; pos < d_size; pos++) { key = PyList_GetItem(keys, pos); key_int = (int)PyInt_AsLong(key); value = PyList_GetItem(values, pos); status = 0; is_core_tag = 0; - int number_of_tags = sizeof(tags) / sizeof(int); for (i=0; i