diff --git a/PIL/TiffImagePlugin.py b/PIL/TiffImagePlugin.py index ae98831d2..ff354ae31 100644 --- a/PIL/TiffImagePlugin.py +++ b/PIL/TiffImagePlugin.py @@ -1430,8 +1430,18 @@ def _save(im, fp, filename): # based on the data in the strip. blocklist = [STRIPOFFSETS, STRIPBYTECOUNTS] atts = {} + # atts is a dict of key: tuple(type, array, count, value) + # where type is the tifftype int + # array is 0/1 for single or array value + # count is the number of items + # value is the value, or a tuple of the items. + # Note that we've got some items where there's an unspecified length + # in the spec (or 0), and they may have 1 item, so they need to be + # passed in in the array interface as an array of one item. + # bits per sample is a single short in the tiff directory, not a list. - atts[BITSPERSAMPLE] = bits[0] + tag_info = TiffTags.lookup(BITSPERSAMPLE) + atts[BITSPERSAMPLE] = (tag_info.type, 0, 1, bits[0]) # Merge the ones that we have with (optional) more bits from # the original file, e.g x,y resolution so that we can # save(load('')) == original file. @@ -1448,16 +1458,38 @@ def _save(im, fp, filename): # UNDONE -- add code for the custom dictionary if tag not in TiffTags.LIBTIFF_CORE: continue - if tag not in atts and tag not in blocklist: - if isinstance(value, unicode if bytes is str else str): - atts[tag] = value.encode('ascii', 'replace') + b"\0" - elif isinstance(value, IFDRational): - atts[tag] = float(value) - else: - atts[tag] = value + if tag in atts: + continue + if tag in blocklist: + continue + tag_info = TiffTags.lookup(tag) + # numeric types + if tag_info.length == 1: + if tag_info.type in (3,4,6,8,9): + atts[tag] = (tag_info.type, 0, 1, int(value)) + elif tag_info.type in (5,10,11,12): + atts[tag] = (tag_info.type, 0, 1, float(value)) + elif tag_info.type == 2: + if isinstance(value, unicode if bytes is str else str): + atts[tag] = (tag_info.type, 0, 1, + value.encode('ascii', 'replace') + b"\0") + else: + atts[tag] = (tag_info.type, 0, 1, value) + # we're not sending bytes to libtiff, as they require custom fields. + #elif tag_info.type == 7: + # atts[tag] = (tag_info.type, 0, 1, value) + + else: # undefined or set length. + if tag_info.type in (3,4,6,8,9): + atts[tag] = (tag_info.type, 1, len(value), tuple(map(int,value))) + elif tag_info.type in (5,10,11,12): + atts[tag] = (tag_info.type, 1, len(value), tuple(map(float,value))) + # stringish types + if DEBUG: print("Converted items: %s" % sorted(atts.items())) + print("Length: %s" % len(atts)) # libtiff always expects the bytes in native order. # we're storing image byte order. So, if the rawmode diff --git a/PIL/TiffTags.py b/PIL/TiffTags.py index 6644b237e..2e3c9bce4 100644 --- a/PIL/TiffTags.py +++ b/PIL/TiffTags.py @@ -130,6 +130,7 @@ TAGS_V2 = { 324: ("TileOffsets", LONG, 0), 325: ("TileByteCounts", LONG, 0), + 330: ("SubIFD", SHORT, 1), 332: ("InkSet", SHORT, 1), 333: ("InkNames", ASCII, 1), 334: ("NumberOfInks", SHORT, 1), @@ -158,6 +159,12 @@ TAGS_V2 = { 531: ("YCbCrPositioning", SHORT, 1), 532: ("ReferenceBlackWhite", LONG, 0), + # sgi, in core + 32995:("Matteing", SHORT, 1), + 32996:("DataType", SHORT, 0), + 32997:("ImageDepth", LONG, 1), + 32998:("TileDepth", LONG, 1), + 33432: ("Copyright", ASCII, 1), # FIXME add more tags here @@ -417,6 +424,7 @@ TYPES = {} # 393: case TIFFTAG_INKNAMES: # some of these are not in our TAGS_V2 dict and were included from tiff.h +# Anything included here needs to have the correct type in TAGS_V2 above LIBTIFF_CORE = {255, 256, 257, 258, 259, 262, 263, 266, 274, 277, 278, 280, 281, 340, 341, 282, 283, 284, 286, 287, @@ -430,6 +438,8 @@ LIBTIFF_CORE.remove(320) # Array of short, crashes LIBTIFF_CORE.remove(301) # Array of short, crashes LIBTIFF_CORE.remove(532) # Array of long, crashes +LIBTIFF_CORE.remove(330) # subifd, requires extra support for uint64 payload + LIBTIFF_CORE.remove(255) # We don't have support for subfiletypes LIBTIFF_CORE.remove(322) # We don't have support for tiled images in libtiff LIBTIFF_CORE.remove(323) # Tiled images diff --git a/encode.c b/encode.c index 7e8f6b125..b9dfb778b 100644 --- a/encode.c +++ b/encode.c @@ -749,6 +749,19 @@ PyImaging_JpegEncoderNew(PyObject* self, PyObject* args) #include +#define PUSHMETAARRAY(_type, _PyFunction)\ + arrav = calloc(len, sizeof(_type));\ + if (arrav) {\ + for (i=0;istate,\ + (ttag_t) PyInt_AsLong(key),\ + len, arrav);\ + free(arrav);\ + } + + PyObject* PyImaging_LibTiffEncoderNew(PyObject* self, PyObject* args) { @@ -761,7 +774,8 @@ PyImaging_LibTiffEncoderNew(PyObject* self, PyObject* args) int fp; PyObject *dir; - PyObject *key, *value; + PyObject *key, *value, *valuetuple; + int type, length, flarray; Py_ssize_t pos = 0; int status; @@ -804,67 +818,116 @@ PyImaging_LibTiffEncoderNew(PyObject* self, PyObject* args) for (pos = 0; pos < d_size; pos++) { key = PyList_GetItem(keys, pos); - value = PyList_GetItem(values, pos); + valuetuple = PyList_GetItem(values, pos); status = 0; TRACE(("Attempting to set key: %d\n", (int)PyInt_AsLong(key))); - 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)); - } 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)); - } else if (PyBytes_Check(value)) { - TRACE(("Setting from Bytes: %d, %s \n", (int)PyInt_AsLong(key),PyBytes_AsString(value))); - status = ImagingLibTiffSetField(&encoder->state, - (ttag_t) PyInt_AsLong(key), - PyBytes_AsString(value)); - } else if (PyTuple_Check(value)) { - Py_ssize_t len,i; - float *floatav; - int *intav; - TRACE(("Setting from Tuple: %d \n", (int)PyInt_AsLong(key))); - len = PyTuple_Size(value); - if (len) { - if (PyInt_Check(PyTuple_GetItem(value,0))) { - TRACE((" %d elements, setting as ints \n", (int)len)); - /* malloc check ok, calloc checks for overflow */ - intav = calloc(len, sizeof(int)); - if (intav) { - for (i=0;istate, - (ttag_t) PyInt_AsLong(key), - len, intav); - free(intav); - } - } else if (PyFloat_Check(PyTuple_GetItem(value,0))) { - TRACE((" %d elements, setting as floats \n", (int)len)); - /* malloc check ok, calloc checks for overflow */ - floatav = calloc(len, sizeof(float)); - if (floatav) { - for (i=0;istate, - (ttag_t) PyInt_AsLong(key), - len, floatav); - free(floatav); - } - } else { - TRACE(("Unhandled type in tuple for key %d : %s \n", + + if (! PyTuple_Check(valuetuple)) { + PyErr_SetString(PyExc_ValueError, "Expected a tuple for tiff metadata"); + return NULL; + } + /* undone checks */ + type = PyInt_AsLong(PyTuple_GetItem(valuetuple,0)); + flarray = PyInt_AsLong(PyTuple_GetItem(valuetuple,1)); + length = PyInt_AsLong(PyTuple_GetItem(valuetuple,2)); + value = PyTuple_GetItem(valuetuple,3); + + if (flarray == 0) { + if (length != 1) { + PyErr_SetString(PyExc_ValueError, "Expected length == 1 for non-array item"); + return NULL; + } + switch (type) { + case 3: + case 4: + case 6: + case 8: + case 9: + if (PyInt_Check(value)) { + TRACE(("Setting %d from Int: %d %ld \n", (int)PyInt_AsLong(key), - PyBytes_AsString(PyObject_Str(value)))); + type, + PyInt_AsLong(value))); + status = ImagingLibTiffSetField(&encoder->state, + (ttag_t) PyInt_AsLong(key), + PyInt_AsLong(value)); + } else { + PyErr_SetString(PyExc_ValueError, "Expected int for metadata value"); + return NULL; + } + break; + case 5: + case 10: + case 11: + case 12: + 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)); + } else { + PyErr_SetString(PyExc_ValueError, "Expected floatlike for metadata value"); + return NULL; + } + break; + default: + if (PyBytes_Check(value)) { + TRACE(("Setting from Bytes: %d, %s \n", (int)PyInt_AsLong(key),PyBytes_AsString(value))); + status = ImagingLibTiffSetField(&encoder->state, + (ttag_t) PyInt_AsLong(key), + PyBytes_AsString(value)); + } else { + PyErr_SetString(PyExc_ValueError, "Expected stringlike for metadata value"); + return NULL; } } } else { + Py_ssize_t len,i; + void *arrav; + + TRACE(("Setting from Tuple: %d \n", (int)PyInt_AsLong(key))); + len = PyTuple_Size(value); + if ((int)len == length) { + switch (type) { + case 3: + TRACE((" %d elements, setting as short \n", (int)len)); + PUSHMETAARRAY(short, PyInt_AsLong); + break; + case 4: + TRACE((" %d elements, setting as ints \n", (int)len)); + PUSHMETAARRAY(int, PyInt_AsLong); + /*arrav = calloc(len, sizeof(int)); + if (arrav) { + for (i=0;istate, + (ttag_t) PyInt_AsLong(key), + len, arrav); + free(arrav); + }*/ + break; + case 5: + case 10: + case 11: + TRACE((" %d elements, setting as floats \n", (int)len)); + /* malloc check ok, calloc checks for overflow */ + PUSHMETAARRAY(float, PyFloat_AsDouble); + break; + case 12: + TRACE((" %d elements, setting as double \n", (int)len)); + PUSHMETAARRAY(double, PyFloat_AsDouble); + break; + default: + TRACE(("Unhandled type in tuple for key %d : %d, len: %d \n", + (int)PyInt_AsLong(key), + type, length)); + } + } + /* } else { TRACE(("Unhandled type for key %d : %s \n", (int)PyInt_AsLong(key), - PyBytes_AsString(PyObject_Str(value)))); + PyBytes_AsString(PyObject_Str(value))));*/ } if (!status) { TRACE(("Error setting Field\n"));