Merge pull request #1620 from wiredfool/issue_1597

Partial fix for #1597
This commit is contained in:
wiredfool 2016-01-01 07:47:58 -08:00
commit e5076a3278
5 changed files with 161 additions and 17 deletions

View File

@ -1400,8 +1400,7 @@ def _save(im, fp, filename):
# STRIPOFFSETS and STRIPBYTECOUNTS are added by the library
# based on the data in the strip.
# ICCPROFILE crashes.
blocklist = [STRIPOFFSETS, STRIPBYTECOUNTS, ICCPROFILE]
blocklist = [STRIPOFFSETS, STRIPBYTECOUNTS]
atts = {}
# bits per sample is a single short in the tiff directory, not a list.
atts[BITSPERSAMPLE] = bits[0]
@ -1411,16 +1410,22 @@ def _save(im, fp, filename):
legacy_ifd = {}
if hasattr(im, 'tag'):
legacy_ifd = im.tag.to_v2()
for k, v in itertools.chain(ifd.items(),
for tag, value in itertools.chain(ifd.items(),
getattr(im, 'tag_v2', {}).items(),
legacy_ifd.items()):
if k not in atts and k not in blocklist:
if isinstance(v, unicode if bytes is str else str):
atts[k] = v.encode('ascii', 'replace') + b"\0"
elif isinstance(v, IFDRational):
atts[k] = float(v)
# 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: 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[k] = v
atts[tag] = value
if DEBUG:
print("Converted items: %s" % sorted(atts.items()))

View File

@ -331,3 +331,72 @@ TYPES = {}
# 11: "float",
# 12: "double",
# }
#
# These tags are handled by default in libtiff, without
# adding to the custom dictionary. From tif_dir.c, searching for
# case TIFFTAG in the _TIFFVSetField function:
# Line: item.
# 148: case TIFFTAG_SUBFILETYPE:
# 151: case TIFFTAG_IMAGEWIDTH:
# 154: case TIFFTAG_IMAGELENGTH:
# 157: case TIFFTAG_BITSPERSAMPLE:
# 181: case TIFFTAG_COMPRESSION:
# 202: case TIFFTAG_PHOTOMETRIC:
# 205: case TIFFTAG_THRESHHOLDING:
# 208: case TIFFTAG_FILLORDER:
# 214: case TIFFTAG_ORIENTATION:
# 221: case TIFFTAG_SAMPLESPERPIXEL:
# 228: case TIFFTAG_ROWSPERSTRIP:
# 238: case TIFFTAG_MINSAMPLEVALUE:
# 241: case TIFFTAG_MAXSAMPLEVALUE:
# 244: case TIFFTAG_SMINSAMPLEVALUE:
# 247: case TIFFTAG_SMAXSAMPLEVALUE:
# 250: case TIFFTAG_XRESOLUTION:
# 256: case TIFFTAG_YRESOLUTION:
# 262: case TIFFTAG_PLANARCONFIG:
# 268: case TIFFTAG_XPOSITION:
# 271: case TIFFTAG_YPOSITION:
# 274: case TIFFTAG_RESOLUTIONUNIT:
# 280: case TIFFTAG_PAGENUMBER:
# 284: case TIFFTAG_HALFTONEHINTS:
# 288: case TIFFTAG_COLORMAP:
# 294: case TIFFTAG_EXTRASAMPLES:
# 298: case TIFFTAG_MATTEING:
# 305: case TIFFTAG_TILEWIDTH:
# 316: case TIFFTAG_TILELENGTH:
# 327: case TIFFTAG_TILEDEPTH:
# 333: case TIFFTAG_DATATYPE:
# 344: case TIFFTAG_SAMPLEFORMAT:
# 361: case TIFFTAG_IMAGEDEPTH:
# 364: case TIFFTAG_SUBIFD:
# 376: case TIFFTAG_YCBCRPOSITIONING:
# 379: case TIFFTAG_YCBCRSUBSAMPLING:
# 383: case TIFFTAG_TRANSFERFUNCTION:
# 389: case TIFFTAG_REFERENCEBLACKWHITE:
# 393: case TIFFTAG_INKNAMES:
# some of these are not in our TAGS_V2 dict and were included from tiff.h
LIBTIFF_CORE = set ([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,
32996, 339, 32997, 330, 531, 530, 301, 532, 333,
# as above
269 # this has been in our tests forever, and works
])
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(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
LIBTIFF_CORE.remove(333) # Ink Names either
# Note to advanced users: There may be combinations of these
# parameters and values that when added properly, will work and
# produce valid tiff images that may work in your application.
# It is safe to add and remove tags from this set from Pillow's point
# of view so long as you test against libtiff.

BIN
Tests/images/rdf.tif Normal file

Binary file not shown.

View File

@ -7,7 +7,7 @@ import logging
import itertools
import os
from PIL import Image, TiffImagePlugin
from PIL import Image, TiffImagePlugin, TiffTags
logger = logging.getLogger(__name__)
@ -172,6 +172,59 @@ class TestFileLibTiff(LibTiffTestCase):
for field in requested_fields:
self.assertTrue(field in reloaded, "%s not in metadata" % field)
def test_additional_metadata(self):
# these should not crash. Seriously dummy data, most of it doesn't make
# any sense, so we're running up against limits where we're asking
# libtiff to do stupid things.
# Get the list of the ones that we should be able to write
core_items = dict((tag, info) for tag, info in [(s,TiffTags.lookup(s)) for s
in TiffTags.LIBTIFF_CORE]
if info.type is not None)
# Exclude ones that have special meaning that we're already testing them
im = Image.open('Tests/images/hopper_g4.tif')
for tag in im.tag_v2.keys():
try:
del(core_items[tag])
except: pass
# Type codes:
# 2: "ascii",
# 3: "short",
# 4: "long",
# 5: "rational",
# 12: "double",
# type: dummy value
values = { 2: 'test',
3: 1,
4: 2**20,
5: TiffImagePlugin.IFDRational(100,1),
12: 1.05 }
new_ifd = TiffImagePlugin.ImageFileDirectory_v2()
for tag, info in core_items.items():
if info.length == 1:
new_ifd[tag] = values[info.type]
if info.length == 0:
new_ifd[tag] = tuple(values[info.type] for _ in range(3))
else:
new_ifd[tag] = tuple(values[info.type] for _ in range(info.length))
# Extra samples really doesn't make sense in this application.
del(new_ifd[338])
out = self.tempfile("temp.tif")
TiffImagePlugin.WRITE_LIBTIFF = True
im.save(out, tiffinfo=new_ifd)
TiffImagePlugin.WRITE_LIBTIFF = False
def test_g3_compression(self):
i = Image.open('Tests/images/hopper_g4_500.tif')
out = self.tempfile("temp.tif")
@ -395,6 +448,17 @@ class TestFileLibTiff(LibTiffTestCase):
TiffImagePlugin.WRITE_LIBTIFF = False
TiffImagePlugin.READ_LIBTIFF = False
def test_crashing_metadata(self):
# issue 1597
im = Image.open('Tests/images/rdf.tif')
out = self.tempfile('temp.tif')
TiffImagePlugin.WRITE_LIBTIFF = True
# this shouldn't crash
im.save(out, format='TIFF')
TiffImagePlugin.WRITE_LIBTIFF = False
if __name__ == '__main__':
unittest.main()

View File

@ -498,7 +498,7 @@ The :py:meth:`~PIL.Image.Image.open` method sets the following
The :py:attr:`~PIL.Image.Image.tag_v2` attribute contains a dictionary
of TIFF metadata. The keys are numerical indexes from
`~PIL.TiffTags.TAGS_V2`. Values are strings or numbers for single
:py:attr:`~PIL.TiffTags.TAGS_V2`. Values are strings or numbers for single
items, multiple values are returned in a tuple of values. Rational
numbers are returned as a :py:class:`~PIL.TiffImagePlugin.IFDRational`
object.
@ -542,13 +542,19 @@ The :py:meth:`~PIL.Image.Image.save` method can take the following keyword argum
.. versionadded:: 3.0.0
**compression**
.. note::
Only some tags are currently supported when writing using
libtiff. The supported list is found in
:py:attr:`~PIL:TiffTags.LIBTIFF_CORE`.
**compression**
A string containing the desired compression method for the
file. (valid only with libtiff installed) Valid compression
methods are: ``None``, ``"tiff_ccitt"``, ``"group3"``,
``"group4"``, ``"tiff_jpeg"``, ``"tiff_adobe_deflate"``,
``"tiff_thunderscan"``, ``"tiff_deflate"``, ``"tiff_sgilog"``,
``"tiff_sgilog24"``, ``"tiff_raw_16"``
file. (valid only with libtiff installed) Valid compression
methods are: ``None``, ``"tiff_ccitt"``, ``"group3"``,
``"group4"``, ``"tiff_jpeg"``, ``"tiff_adobe_deflate"``,
``"tiff_thunderscan"``, ``"tiff_deflate"``, ``"tiff_sgilog"``,
``"tiff_sgilog24"``, ``"tiff_raw_16"``
These arguments to set the tiff header fields are an alternative to
using the general tags available through tiffinfo.