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 # STRIPOFFSETS and STRIPBYTECOUNTS are added by the library
# based on the data in the strip. # based on the data in the strip.
# ICCPROFILE crashes. blocklist = [STRIPOFFSETS, STRIPBYTECOUNTS]
blocklist = [STRIPOFFSETS, STRIPBYTECOUNTS, ICCPROFILE]
atts = {} atts = {}
# 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] atts[BITSPERSAMPLE] = bits[0]
@ -1411,16 +1410,22 @@ def _save(im, fp, filename):
legacy_ifd = {} legacy_ifd = {}
if hasattr(im, 'tag'): if hasattr(im, 'tag'):
legacy_ifd = im.tag.to_v2() 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(), getattr(im, 'tag_v2', {}).items(),
legacy_ifd.items()): legacy_ifd.items()):
if k not in atts and k not in blocklist: # Libtiff can only process certain core items without adding
if isinstance(v, unicode if bytes is str else str): # them to the custom dictionary. It will segfault if it attempts
atts[k] = v.encode('ascii', 'replace') + b"\0" # to add a custom tag without the dictionary entry
elif isinstance(v, IFDRational): #
atts[k] = float(v) # 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: else:
atts[k] = v atts[tag] = value
if DEBUG: if DEBUG:
print("Converted items: %s" % sorted(atts.items())) print("Converted items: %s" % sorted(atts.items()))

View File

@ -331,3 +331,72 @@ TYPES = {}
# 11: "float", # 11: "float",
# 12: "double", # 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 itertools
import os import os
from PIL import Image, TiffImagePlugin from PIL import Image, TiffImagePlugin, TiffTags
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
@ -172,6 +172,59 @@ class TestFileLibTiff(LibTiffTestCase):
for field in requested_fields: for field in requested_fields:
self.assertTrue(field in reloaded, "%s not in metadata" % field) 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): def test_g3_compression(self):
i = Image.open('Tests/images/hopper_g4_500.tif') i = Image.open('Tests/images/hopper_g4_500.tif')
out = self.tempfile("temp.tif") out = self.tempfile("temp.tif")
@ -395,6 +448,17 @@ class TestFileLibTiff(LibTiffTestCase):
TiffImagePlugin.WRITE_LIBTIFF = False TiffImagePlugin.WRITE_LIBTIFF = False
TiffImagePlugin.READ_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__': if __name__ == '__main__':
unittest.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 The :py:attr:`~PIL.Image.Image.tag_v2` attribute contains a dictionary
of TIFF metadata. The keys are numerical indexes from 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 items, multiple values are returned in a tuple of values. Rational
numbers are returned as a :py:class:`~PIL.TiffImagePlugin.IFDRational` numbers are returned as a :py:class:`~PIL.TiffImagePlugin.IFDRational`
object. object.
@ -542,13 +542,19 @@ The :py:meth:`~PIL.Image.Image.save` method can take the following keyword argum
.. versionadded:: 3.0.0 .. 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 A string containing the desired compression method for the
file. (valid only with libtiff installed) Valid compression file. (valid only with libtiff installed) Valid compression
methods are: ``None``, ``"tiff_ccitt"``, ``"group3"``, methods are: ``None``, ``"tiff_ccitt"``, ``"group3"``,
``"group4"``, ``"tiff_jpeg"``, ``"tiff_adobe_deflate"``, ``"group4"``, ``"tiff_jpeg"``, ``"tiff_adobe_deflate"``,
``"tiff_thunderscan"``, ``"tiff_deflate"``, ``"tiff_sgilog"``, ``"tiff_thunderscan"``, ``"tiff_deflate"``, ``"tiff_sgilog"``,
``"tiff_sgilog24"``, ``"tiff_raw_16"`` ``"tiff_sgilog24"``, ``"tiff_raw_16"``
These arguments to set the tiff header fields are an alternative to These arguments to set the tiff header fields are an alternative to
using the general tags available through tiffinfo. using the general tags available through tiffinfo.