initial take of proper tiff metadata support

This commit is contained in:
wiredfool 2016-12-06 21:48:37 +00:00
parent 3f372ef54a
commit 18b0a376e3
3 changed files with 167 additions and 62 deletions

View File

@ -1430,8 +1430,18 @@ def _save(im, fp, filename):
# based on the data in the strip. # based on the data in the strip.
blocklist = [STRIPOFFSETS, STRIPBYTECOUNTS] blocklist = [STRIPOFFSETS, STRIPBYTECOUNTS]
atts = {} 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. # 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 # Merge the ones that we have with (optional) more bits from
# the original file, e.g x,y resolution so that we can # the original file, e.g x,y resolution so that we can
# save(load('')) == original file. # save(load('')) == original file.
@ -1448,16 +1458,38 @@ def _save(im, fp, filename):
# UNDONE -- add code for the custom dictionary # UNDONE -- add code for the custom dictionary
if tag not in TiffTags.LIBTIFF_CORE: if tag not in TiffTags.LIBTIFF_CORE:
continue continue
if tag not in atts and tag not in blocklist: if tag in atts:
if isinstance(value, unicode if bytes is str else str): continue
atts[tag] = value.encode('ascii', 'replace') + b"\0" if tag in blocklist:
elif isinstance(value, IFDRational): continue
atts[tag] = float(value) tag_info = TiffTags.lookup(tag)
else: # numeric types
atts[tag] = value 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: if DEBUG:
print("Converted items: %s" % sorted(atts.items())) print("Converted items: %s" % sorted(atts.items()))
print("Length: %s" % len(atts))
# libtiff always expects the bytes in native order. # libtiff always expects the bytes in native order.
# we're storing image byte order. So, if the rawmode # we're storing image byte order. So, if the rawmode

View File

@ -130,6 +130,7 @@ TAGS_V2 = {
324: ("TileOffsets", LONG, 0), 324: ("TileOffsets", LONG, 0),
325: ("TileByteCounts", LONG, 0), 325: ("TileByteCounts", LONG, 0),
330: ("SubIFD", SHORT, 1),
332: ("InkSet", SHORT, 1), 332: ("InkSet", SHORT, 1),
333: ("InkNames", ASCII, 1), 333: ("InkNames", ASCII, 1),
334: ("NumberOfInks", SHORT, 1), 334: ("NumberOfInks", SHORT, 1),
@ -158,6 +159,12 @@ TAGS_V2 = {
531: ("YCbCrPositioning", SHORT, 1), 531: ("YCbCrPositioning", SHORT, 1),
532: ("ReferenceBlackWhite", LONG, 0), 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), 33432: ("Copyright", ASCII, 1),
# FIXME add more tags here # FIXME add more tags here
@ -417,6 +424,7 @@ TYPES = {}
# 393: case TIFFTAG_INKNAMES: # 393: case TIFFTAG_INKNAMES:
# some of these are not in our TAGS_V2 dict and were included from tiff.h # 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, LIBTIFF_CORE = {255, 256, 257, 258, 259, 262, 263, 266, 274, 277,
278, 280, 281, 340, 341, 282, 283, 284, 286, 287, 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(301) # Array of short, crashes
LIBTIFF_CORE.remove(532) # Array of long, 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(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(322) # We don't have support for tiled images in libtiff
LIBTIFF_CORE.remove(323) # Tiled images LIBTIFF_CORE.remove(323) # Tiled images

171
encode.c
View File

@ -749,6 +749,19 @@ PyImaging_JpegEncoderNew(PyObject* self, PyObject* args)
#include <string.h> #include <string.h>
#define PUSHMETAARRAY(_type, _PyFunction)\
arrav = calloc(len, sizeof(_type));\
if (arrav) {\
for (i=0;i<len;i++) {\
((_type *)arrav)[i] = (_type)_PyFunction(PyTuple_GetItem(value,i)); \
}\
status = ImagingLibTiffSetField(&encoder->state,\
(ttag_t) PyInt_AsLong(key),\
len, arrav);\
free(arrav);\
}
PyObject* PyObject*
PyImaging_LibTiffEncoderNew(PyObject* self, PyObject* args) PyImaging_LibTiffEncoderNew(PyObject* self, PyObject* args)
{ {
@ -761,7 +774,8 @@ PyImaging_LibTiffEncoderNew(PyObject* self, PyObject* args)
int fp; int fp;
PyObject *dir; PyObject *dir;
PyObject *key, *value; PyObject *key, *value, *valuetuple;
int type, length, flarray;
Py_ssize_t pos = 0; Py_ssize_t pos = 0;
int status; int status;
@ -804,67 +818,116 @@ PyImaging_LibTiffEncoderNew(PyObject* self, PyObject* args)
for (pos = 0; pos < d_size; pos++) { for (pos = 0; pos < d_size; pos++) {
key = PyList_GetItem(keys, pos); key = PyList_GetItem(keys, pos);
value = PyList_GetItem(values, pos); valuetuple = PyList_GetItem(values, pos);
status = 0; status = 0;
TRACE(("Attempting to set key: %d\n", (int)PyInt_AsLong(key))); 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))); if (! PyTuple_Check(valuetuple)) {
status = ImagingLibTiffSetField(&encoder->state, PyErr_SetString(PyExc_ValueError, "Expected a tuple for tiff metadata");
(ttag_t) PyInt_AsLong(key), return NULL;
PyInt_AsLong(value)); }
} else if (PyFloat_Check(value)) { /* undone checks */
TRACE(("Setting from Float: %d, %f \n", (int)PyInt_AsLong(key),PyFloat_AsDouble(value))); type = PyInt_AsLong(PyTuple_GetItem(valuetuple,0));
status = ImagingLibTiffSetField(&encoder->state, flarray = PyInt_AsLong(PyTuple_GetItem(valuetuple,1));
(ttag_t) PyInt_AsLong(key), length = PyInt_AsLong(PyTuple_GetItem(valuetuple,2));
(float)PyFloat_AsDouble(value)); value = PyTuple_GetItem(valuetuple,3);
} else if (PyBytes_Check(value)) {
TRACE(("Setting from Bytes: %d, %s \n", (int)PyInt_AsLong(key),PyBytes_AsString(value))); if (flarray == 0) {
status = ImagingLibTiffSetField(&encoder->state, if (length != 1) {
(ttag_t) PyInt_AsLong(key), PyErr_SetString(PyExc_ValueError, "Expected length == 1 for non-array item");
PyBytes_AsString(value)); return NULL;
} else if (PyTuple_Check(value)) { }
Py_ssize_t len,i; switch (type) {
float *floatav; case 3:
int *intav; case 4:
TRACE(("Setting from Tuple: %d \n", (int)PyInt_AsLong(key))); case 6:
len = PyTuple_Size(value); case 8:
if (len) { case 9:
if (PyInt_Check(PyTuple_GetItem(value,0))) { if (PyInt_Check(value)) {
TRACE((" %d elements, setting as ints \n", (int)len)); TRACE(("Setting %d from Int: %d %ld \n",
/* malloc check ok, calloc checks for overflow */
intav = calloc(len, sizeof(int));
if (intav) {
for (i=0;i<len;i++) {
intav[i] = (int)PyInt_AsLong(PyTuple_GetItem(value,i));
}
status = ImagingLibTiffSetField(&encoder->state,
(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;i<len;i++) {
floatav[i] = (float)PyFloat_AsDouble(PyTuple_GetItem(value,i));
}
status = ImagingLibTiffSetField(&encoder->state,
(ttag_t) PyInt_AsLong(key),
len, floatav);
free(floatav);
}
} else {
TRACE(("Unhandled type in tuple for key %d : %s \n",
(int)PyInt_AsLong(key), (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 { } 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;i<len;i++) {
(int *arrav)[i] = (int)PyInt_AsLong(PyTuple_GetItem(value,i));
}
status = ImagingLibTiffSetField(&encoder->state,
(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", TRACE(("Unhandled type for key %d : %s \n",
(int)PyInt_AsLong(key), (int)PyInt_AsLong(key),
PyBytes_AsString(PyObject_Str(value)))); PyBytes_AsString(PyObject_Str(value))));*/
} }
if (!status) { if (!status) {
TRACE(("Error setting Field\n")); TRACE(("Error setting Field\n"));