This commit is contained in:
hugovk 2014-07-28 19:00:06 +03:00
parent 659b8c2f6f
commit 70528dd539

View File

@ -49,17 +49,18 @@ from PIL import _binary
from PIL._util import isStringType from PIL._util import isStringType
import warnings import warnings
import array, sys import array
import sys
import collections import collections
import itertools import itertools
import os import os
# Set these to true to force use of libtiff for reading or writing. # Set these to true to force use of libtiff for reading or writing.
READ_LIBTIFF = False READ_LIBTIFF = False
WRITE_LIBTIFF= False WRITE_LIBTIFF = False
II = b"II" # little-endian (intel-style) II = b"II" # little-endian (Intel style)
MM = b"MM" # big-endian (motorola-style) MM = b"MM" # big-endian (Motorola style)
i8 = _binary.i8 i8 = _binary.i8
o8 = _binary.o8 o8 = _binary.o8
@ -109,8 +110,8 @@ EXTRASAMPLES = 338
SAMPLEFORMAT = 339 SAMPLEFORMAT = 339
JPEGTABLES = 347 JPEGTABLES = 347
COPYRIGHT = 33432 COPYRIGHT = 33432
IPTC_NAA_CHUNK = 33723 # newsphoto properties IPTC_NAA_CHUNK = 33723 # newsphoto properties
PHOTOSHOP_CHUNK = 34377 # photoshop properties PHOTOSHOP_CHUNK = 34377 # photoshop properties
ICCPROFILE = 34675 ICCPROFILE = 34675
EXIFIFD = 34665 EXIFIFD = 34665
XMP = 700 XMP = 700
@ -126,10 +127,10 @@ COMPRESSION_INFO = {
3: "group3", 3: "group3",
4: "group4", 4: "group4",
5: "tiff_lzw", 5: "tiff_lzw",
6: "tiff_jpeg", # obsolete 6: "tiff_jpeg", # obsolete
7: "jpeg", 7: "jpeg",
8: "tiff_adobe_deflate", 8: "tiff_adobe_deflate",
32771: "tiff_raw_16", # 16-bit padding 32771: "tiff_raw_16", # 16-bit padding
32773: "packbits", 32773: "packbits",
32809: "tiff_thunderscan", 32809: "tiff_thunderscan",
32946: "tiff_deflate", 32946: "tiff_deflate",
@ -137,7 +138,7 @@ COMPRESSION_INFO = {
34677: "tiff_sgilog24", 34677: "tiff_sgilog24",
} }
COMPRESSION_INFO_REV = dict([(v,k) for (k,v) in COMPRESSION_INFO.items()]) COMPRESSION_INFO_REV = dict([(v, k) for (k, v) in COMPRESSION_INFO.items()])
OPEN_INFO = { OPEN_INFO = {
# (ByteOrder, PhotoInterpretation, SampleFormat, FillOrder, BitsPerSample, # (ByteOrder, PhotoInterpretation, SampleFormat, FillOrder, BitsPerSample,
@ -150,7 +151,7 @@ OPEN_INFO = {
(II, 1, 1, 1, (1,), ()): ("1", "1"), (II, 1, 1, 1, (1,), ()): ("1", "1"),
(II, 1, 1, 2, (1,), ()): ("1", "1;R"), (II, 1, 1, 2, (1,), ()): ("1", "1;R"),
(II, 1, 1, 1, (8,), ()): ("L", "L"), (II, 1, 1, 1, (8,), ()): ("L", "L"),
(II, 1, 1, 1, (8,8), (2,)): ("LA", "LA"), (II, 1, 1, 1, (8, 8), (2,)): ("LA", "LA"),
(II, 1, 1, 2, (8,), ()): ("L", "L;R"), (II, 1, 1, 2, (8,), ()): ("L", "L;R"),
(II, 1, 1, 1, (12,), ()): ("I;16", "I;12"), (II, 1, 1, 1, (12,), ()): ("I;16", "I;12"),
(II, 1, 1, 1, (16,), ()): ("I;16", "I;16"), (II, 1, 1, 1, (16,), ()): ("I;16", "I;16"),
@ -158,13 +159,13 @@ OPEN_INFO = {
(II, 1, 1, 1, (32,), ()): ("I", "I;32N"), (II, 1, 1, 1, (32,), ()): ("I", "I;32N"),
(II, 1, 2, 1, (32,), ()): ("I", "I;32S"), (II, 1, 2, 1, (32,), ()): ("I", "I;32S"),
(II, 1, 3, 1, (32,), ()): ("F", "F;32F"), (II, 1, 3, 1, (32,), ()): ("F", "F;32F"),
(II, 2, 1, 1, (8,8,8), ()): ("RGB", "RGB"), (II, 2, 1, 1, (8, 8, 8), ()): ("RGB", "RGB"),
(II, 2, 1, 2, (8,8,8), ()): ("RGB", "RGB;R"), (II, 2, 1, 2, (8, 8, 8), ()): ("RGB", "RGB;R"),
(II, 2, 1, 1, (8,8,8,8), ()): ("RGBA", "RGBA"), # missing ExtraSamples (II, 2, 1, 1, (8, 8, 8, 8), ()): ("RGBA", "RGBA"), # missing ExtraSamples
(II, 2, 1, 1, (8,8,8,8), (0,)): ("RGBX", "RGBX"), (II, 2, 1, 1, (8, 8, 8, 8), (0,)): ("RGBX", "RGBX"),
(II, 2, 1, 1, (8,8,8,8), (1,)): ("RGBA", "RGBa"), (II, 2, 1, 1, (8, 8, 8, 8), (1,)): ("RGBA", "RGBa"),
(II, 2, 1, 1, (8,8,8,8), (2,)): ("RGBA", "RGBA"), (II, 2, 1, 1, (8, 8, 8, 8), (2,)): ("RGBA", "RGBA"),
(II, 2, 1, 1, (8,8,8,8), (999,)): ("RGBA", "RGBA"), # corel draw 10 (II, 2, 1, 1, (8, 8, 8, 8), (999,)): ("RGBA", "RGBA"), # Corel Draw 10
(II, 3, 1, 1, (1,), ()): ("P", "P;1"), (II, 3, 1, 1, (1,), ()): ("P", "P;1"),
(II, 3, 1, 2, (1,), ()): ("P", "P;1R"), (II, 3, 1, 2, (1,), ()): ("P", "P;1R"),
(II, 3, 1, 1, (2,), ()): ("P", "P;2"), (II, 3, 1, 1, (2,), ()): ("P", "P;2"),
@ -172,11 +173,11 @@ OPEN_INFO = {
(II, 3, 1, 1, (4,), ()): ("P", "P;4"), (II, 3, 1, 1, (4,), ()): ("P", "P;4"),
(II, 3, 1, 2, (4,), ()): ("P", "P;4R"), (II, 3, 1, 2, (4,), ()): ("P", "P;4R"),
(II, 3, 1, 1, (8,), ()): ("P", "P"), (II, 3, 1, 1, (8,), ()): ("P", "P"),
(II, 3, 1, 1, (8,8), (2,)): ("PA", "PA"), (II, 3, 1, 1, (8, 8), (2,)): ("PA", "PA"),
(II, 3, 1, 2, (8,), ()): ("P", "P;R"), (II, 3, 1, 2, (8,), ()): ("P", "P;R"),
(II, 5, 1, 1, (8,8,8,8), ()): ("CMYK", "CMYK"), (II, 5, 1, 1, (8, 8, 8, 8), ()): ("CMYK", "CMYK"),
(II, 6, 1, 1, (8,8,8), ()): ("YCbCr", "YCbCr"), (II, 6, 1, 1, (8, 8, 8), ()): ("YCbCr", "YCbCr"),
(II, 8, 1, 1, (8,8,8), ()): ("LAB", "LAB"), (II, 8, 1, 1, (8, 8, 8), ()): ("LAB", "LAB"),
(MM, 0, 1, 1, (1,), ()): ("1", "1;I"), (MM, 0, 1, 1, (1,), ()): ("1", "1;I"),
(MM, 0, 1, 2, (1,), ()): ("1", "1;IR"), (MM, 0, 1, 2, (1,), ()): ("1", "1;IR"),
@ -185,18 +186,18 @@ OPEN_INFO = {
(MM, 1, 1, 1, (1,), ()): ("1", "1"), (MM, 1, 1, 1, (1,), ()): ("1", "1"),
(MM, 1, 1, 2, (1,), ()): ("1", "1;R"), (MM, 1, 1, 2, (1,), ()): ("1", "1;R"),
(MM, 1, 1, 1, (8,), ()): ("L", "L"), (MM, 1, 1, 1, (8,), ()): ("L", "L"),
(MM, 1, 1, 1, (8,8), (2,)): ("LA", "LA"), (MM, 1, 1, 1, (8, 8), (2,)): ("LA", "LA"),
(MM, 1, 1, 2, (8,), ()): ("L", "L;R"), (MM, 1, 1, 2, (8,), ()): ("L", "L;R"),
(MM, 1, 1, 1, (16,), ()): ("I;16B", "I;16B"), (MM, 1, 1, 1, (16,), ()): ("I;16B", "I;16B"),
(MM, 1, 2, 1, (16,), ()): ("I;16BS", "I;16BS"), (MM, 1, 2, 1, (16,), ()): ("I;16BS", "I;16BS"),
(MM, 1, 2, 1, (32,), ()): ("I;32BS", "I;32BS"), (MM, 1, 2, 1, (32,), ()): ("I;32BS", "I;32BS"),
(MM, 1, 3, 1, (32,), ()): ("F", "F;32BF"), (MM, 1, 3, 1, (32,), ()): ("F", "F;32BF"),
(MM, 2, 1, 1, (8,8,8), ()): ("RGB", "RGB"), (MM, 2, 1, 1, (8, 8, 8), ()): ("RGB", "RGB"),
(MM, 2, 1, 2, (8,8,8), ()): ("RGB", "RGB;R"), (MM, 2, 1, 2, (8, 8, 8), ()): ("RGB", "RGB;R"),
(MM, 2, 1, 1, (8,8,8,8), (0,)): ("RGBX", "RGBX"), (MM, 2, 1, 1, (8, 8, 8, 8), (0,)): ("RGBX", "RGBX"),
(MM, 2, 1, 1, (8,8,8,8), (1,)): ("RGBA", "RGBa"), (MM, 2, 1, 1, (8, 8, 8, 8), (1,)): ("RGBA", "RGBa"),
(MM, 2, 1, 1, (8,8,8,8), (2,)): ("RGBA", "RGBA"), (MM, 2, 1, 1, (8, 8, 8, 8), (2,)): ("RGBA", "RGBA"),
(MM, 2, 1, 1, (8,8,8,8), (999,)): ("RGBA", "RGBA"), # corel draw 10 (MM, 2, 1, 1, (8, 8, 8, 8), (999,)): ("RGBA", "RGBA"), # Corel Draw 10
(MM, 3, 1, 1, (1,), ()): ("P", "P;1"), (MM, 3, 1, 1, (1,), ()): ("P", "P;1"),
(MM, 3, 1, 2, (1,), ()): ("P", "P;1R"), (MM, 3, 1, 2, (1,), ()): ("P", "P;1R"),
(MM, 3, 1, 1, (2,), ()): ("P", "P;2"), (MM, 3, 1, 1, (2,), ()): ("P", "P;2"),
@ -204,19 +205,21 @@ OPEN_INFO = {
(MM, 3, 1, 1, (4,), ()): ("P", "P;4"), (MM, 3, 1, 1, (4,), ()): ("P", "P;4"),
(MM, 3, 1, 2, (4,), ()): ("P", "P;4R"), (MM, 3, 1, 2, (4,), ()): ("P", "P;4R"),
(MM, 3, 1, 1, (8,), ()): ("P", "P"), (MM, 3, 1, 1, (8,), ()): ("P", "P"),
(MM, 3, 1, 1, (8,8), (2,)): ("PA", "PA"), (MM, 3, 1, 1, (8, 8), (2,)): ("PA", "PA"),
(MM, 3, 1, 2, (8,), ()): ("P", "P;R"), (MM, 3, 1, 2, (8,), ()): ("P", "P;R"),
(MM, 5, 1, 1, (8,8,8,8), ()): ("CMYK", "CMYK"), (MM, 5, 1, 1, (8, 8, 8, 8), ()): ("CMYK", "CMYK"),
(MM, 6, 1, 1, (8,8,8), ()): ("YCbCr", "YCbCr"), (MM, 6, 1, 1, (8, 8, 8), ()): ("YCbCr", "YCbCr"),
(MM, 8, 1, 1, (8,8,8), ()): ("LAB", "LAB"), (MM, 8, 1, 1, (8, 8, 8), ()): ("LAB", "LAB"),
} }
PREFIXES = [b"MM\000\052", b"II\052\000", b"II\xBC\000"] PREFIXES = [b"MM\000\052", b"II\052\000", b"II\xBC\000"]
def _accept(prefix): def _accept(prefix):
return prefix[:4] in PREFIXES return prefix[:4] in PREFIXES
## ##
# Wrapper for TIFF IFDs. # Wrapper for TIFF IFDs.
@ -276,7 +279,7 @@ class ImageFileDirectory(collections.MutableMapping):
#: For a complete dictionary, use the as_dict method. #: For a complete dictionary, use the as_dict method.
self.tags = {} self.tags = {}
self.tagdata = {} self.tagdata = {}
self.tagtype = {} # added 2008-06-05 by Florian Hoech self.tagtype = {} # added 2008-06-05 by Florian Hoech
self.next = None self.next = None
def __str__(self): def __str__(self):
@ -287,7 +290,9 @@ class ImageFileDirectory(collections.MutableMapping):
return dict(self.items()) return dict(self.items())
def named(self): def named(self):
"""Returns the complete tag dictionary, with named tags where posible.""" """
Returns the complete tag dictionary, with named tags where posible.
"""
from PIL import TiffTags from PIL import TiffTags
result = {} result = {}
for tag_code, value in self.items(): for tag_code, value in self.items():
@ -295,7 +300,6 @@ class ImageFileDirectory(collections.MutableMapping):
result[tag_name] = value result[tag_name] = value
return result return result
# dictionary API # dictionary API
def __len__(self): def __len__(self):
@ -305,7 +309,7 @@ class ImageFileDirectory(collections.MutableMapping):
try: try:
return self.tags[tag] return self.tags[tag]
except KeyError: except KeyError:
data = self.tagdata[tag] # unpack on the fly data = self.tagdata[tag] # unpack on the fly
type = self.tagtype[tag] type = self.tagtype[tag]
size, handler = self.load_dispatch[type] size, handler = self.load_dispatch[type]
self.tags[tag] = data = handler(self, data) self.tags[tag] = data = handler(self, data)
@ -319,7 +323,7 @@ class ImageFileDirectory(collections.MutableMapping):
if tag == SAMPLEFORMAT: if tag == SAMPLEFORMAT:
# work around broken (?) matrox library # work around broken (?) matrox library
# (from Ted Wright, via Bob Klimek) # (from Ted Wright, via Bob Klimek)
raise KeyError # use default raise KeyError # use default
raise ValueError("not a scalar") raise ValueError("not a scalar")
return value[0] return value[0]
except KeyError: except KeyError:
@ -433,7 +437,7 @@ class ImageFileDirectory(collections.MutableMapping):
except KeyError: except KeyError:
if Image.DEBUG: if Image.DEBUG:
print("- unsupported type", typ) print("- unsupported type", typ)
continue # ignore unsupported type continue # ignore unsupported type
size, handler = dispatch size, handler = dispatch
@ -449,14 +453,19 @@ class ImageFileDirectory(collections.MutableMapping):
data = ifd[8:8+size] data = ifd[8:8+size]
if len(data) != size: if len(data) != size:
warnings.warn("Possibly corrupt EXIF data. Expecting to read %d bytes but only got %d. Skipping tag %s" % (size, len(data), tag)) warnings.warn(
"Possibly corrupt EXIF data. "
"Expecting to read %d bytes but only got %d. "
"Skipping tag %s" % (size, len(data), tag))
continue continue
self.tagdata[tag] = data self.tagdata[tag] = data
self.tagtype[tag] = typ self.tagtype[tag] = typ
if Image.DEBUG: if Image.DEBUG:
if tag in (COLORMAP, IPTC_NAA_CHUNK, PHOTOSHOP_CHUNK, ICCPROFILE, XMP): if tag in (
COLORMAP, IPTC_NAA_CHUNK,
PHOTOSHOP_CHUNK, ICCPROFILE, XMP):
print("- value: <table: %d bytes>" % size) print("- value: <table: %d bytes>" % size)
else: else:
print("- value:", self[tag]) print("- value:", self[tag])
@ -518,8 +527,8 @@ class ImageFileDirectory(collections.MutableMapping):
# integer data # integer data
if tag == STRIPOFFSETS: if tag == STRIPOFFSETS:
stripoffsets = len(directory) stripoffsets = len(directory)
typ = 4 # to avoid catch-22 typ = 4 # to avoid catch-22
elif tag in (X_RESOLUTION, Y_RESOLUTION) or typ==5: elif tag in (X_RESOLUTION, Y_RESOLUTION) or typ == 5:
# identify rational data fields # identify rational data fields
typ = 5 typ = 5
if isinstance(value[0], tuple): if isinstance(value[0], tuple):
@ -541,7 +550,9 @@ class ImageFileDirectory(collections.MutableMapping):
typname = TiffTags.TYPES.get(typ, "unknown") typname = TiffTags.TYPES.get(typ, "unknown")
print("save: %s (%d)" % (tagname, tag), end=' ') print("save: %s (%d)" % (tagname, tag), end=' ')
print("- type: %s (%d)" % (typname, typ), end=' ') print("- type: %s (%d)" % (typname, typ), end=' ')
if tag in (COLORMAP, IPTC_NAA_CHUNK, PHOTOSHOP_CHUNK, ICCPROFILE, XMP): if tag in (
COLORMAP, IPTC_NAA_CHUNK,
PHOTOSHOP_CHUNK, ICCPROFILE, XMP):
size = len(data) size = len(data)
print("- value: <table: %d bytes>" % size) print("- value: <table: %d bytes>" % size)
else: else:
@ -576,7 +587,7 @@ class ImageFileDirectory(collections.MutableMapping):
fp.write(o16(tag) + o16(typ) + o32(count) + value) fp.write(o16(tag) + o16(typ) + o32(count) + value)
# -- overwrite here for multi-page -- # -- overwrite here for multi-page --
fp.write(b"\0\0\0\0") # end of directory fp.write(b"\0\0\0\0") # end of directory
# pass 3: write auxiliary data to file # pass 3: write auxiliary data to file
for tag, typ, count, value, data in directory: for tag, typ, count, value, data in directory:
@ -586,6 +597,7 @@ class ImageFileDirectory(collections.MutableMapping):
return offset return offset
## ##
# Image plugin for TIFF files. # Image plugin for TIFF files.
@ -616,7 +628,7 @@ class TiffImageFile(ImageFile.ImageFile):
print ("- __first:", self.__first) print ("- __first:", self.__first)
print ("- ifh: ", ifh) print ("- ifh: ", ifh)
# and load the first frame # and load the first frame
self._seek(0) self._seek(0)
def seek(self, frame): def seek(self, frame):
@ -694,9 +706,11 @@ class TiffImageFile(ImageFile.ImageFile):
if not len(self.tile) == 1: if not len(self.tile) == 1:
raise IOError("Not exactly one tile") raise IOError("Not exactly one tile")
# (self._compression, (extents tuple), 0, (rawmode, self._compression, fp)) # (self._compression, (extents tuple),
# 0, (rawmode, self._compression, fp))
ignored, extents, ignored_2, args = self.tile[0] ignored, extents, ignored_2, args = self.tile[0]
decoder = Image._getdecoder(self.mode, 'libtiff', args, self.decoderconfig) decoder = Image._getdecoder(
self.mode, 'libtiff', args, self.decoderconfig)
try: try:
decoder.setimage(self.im, extents) decoder.setimage(self.im, extents)
except ValueError: except ValueError:
@ -706,35 +720,35 @@ class TiffImageFile(ImageFile.ImageFile):
# We've got a stringio like thing passed in. Yay for all in memory. # We've got a stringio like thing passed in. Yay for all in memory.
# The decoder needs the entire file in one shot, so there's not # The decoder needs the entire file in one shot, so there's not
# a lot we can do here other than give it the entire file. # a lot we can do here other than give it the entire file.
# unless we could do something like get the address of the underlying # unless we could do something like get the address of the
# string for stringio. # underlying string for stringio.
# #
# Rearranging for supporting byteio items, since they have a fileno # Rearranging for supporting byteio items, since they have a fileno
# that returns an IOError if there's no underlying fp. Easier to deal # that returns an IOError if there's no underlying fp. Easier to
# with here by reordering. # dea. with here by reordering.
if Image.DEBUG: if Image.DEBUG:
print ("have getvalue. just sending in a string from getvalue") print ("have getvalue. just sending in a string from getvalue")
n,err = decoder.decode(self.fp.getvalue()) n, err = decoder.decode(self.fp.getvalue())
elif hasattr(self.fp, "fileno"): elif hasattr(self.fp, "fileno"):
# we've got a actual file on disk, pass in the fp. # we've got a actual file on disk, pass in the fp.
if Image.DEBUG: if Image.DEBUG:
print ("have fileno, calling fileno version of the decoder.") print ("have fileno, calling fileno version of the decoder.")
self.fp.seek(0) self.fp.seek(0)
n,err = decoder.decode(b"fpfp") # 4 bytes, otherwise the trace might error out # 4 bytes, otherwise the trace might error out
n, err = decoder.decode(b"fpfp")
else: else:
# we have something else. # we have something else.
if Image.DEBUG: if Image.DEBUG:
print ("don't have fileno or getvalue. just reading") print ("don't have fileno or getvalue. just reading")
# UNDONE -- so much for that buffer size thing. # UNDONE -- so much for that buffer size thing.
n,err = decoder.decode(self.fp.read()) n, err = decoder.decode(self.fp.read())
self.tile = [] self.tile = []
self.readonly = 0 self.readonly = 0
# libtiff closed the fp in a, we need to close self.fp, if possible # libtiff closed the fp in a, we need to close self.fp, if possible
if hasattr(self.fp, 'close'): if hasattr(self.fp, 'close'):
self.fp.close() self.fp.close()
self.fp = None # might be shared self.fp = None # might be shared
if err < 0: if err < 0:
raise IOError(err) raise IOError(err)
@ -810,11 +824,11 @@ class TiffImageFile(ImageFile.ImageFile):
xres = xres[0] / (xres[1] or 1) xres = xres[0] / (xres[1] or 1)
yres = yres[0] / (yres[1] or 1) yres = yres[0] / (yres[1] or 1)
resunit = getscalar(RESOLUTION_UNIT, 1) resunit = getscalar(RESOLUTION_UNIT, 1)
if resunit == 2: # dots per inch if resunit == 2: # dots per inch
self.info["dpi"] = xres, yres self.info["dpi"] = xres, yres
elif resunit == 3: # dots per centimeter. convert to dpi elif resunit == 3: # dots per centimeter. convert to dpi
self.info["dpi"] = xres * 2.54, yres * 2.54 self.info["dpi"] = xres * 2.54, yres * 2.54
else: # No absolute unit of measurement else: # No absolute unit of measurement
self.info["resolution"] = xres, yres self.info["resolution"] = xres, yres
# build tile descriptors # build tile descriptors
@ -825,13 +839,14 @@ class TiffImageFile(ImageFile.ImageFile):
offsets = self.tag[STRIPOFFSETS] offsets = self.tag[STRIPOFFSETS]
h = getscalar(ROWSPERSTRIP, ysize) h = getscalar(ROWSPERSTRIP, ysize)
w = self.size[0] w = self.size[0]
if READ_LIBTIFF or self._compression in ["tiff_ccitt", "group3", "group4", if READ_LIBTIFF or self._compression in [
"tiff_jpeg", "tiff_adobe_deflate", "tiff_ccitt", "group3", "group4",
"tiff_thunderscan", "tiff_deflate", "tiff_jpeg", "tiff_adobe_deflate",
"tiff_sgilog", "tiff_sgilog24", "tiff_thunderscan", "tiff_deflate",
"tiff_raw_16"]: "tiff_sgilog", "tiff_sgilog24",
## if Image.DEBUG: "tiff_raw_16"]:
## print "Activating g4 compression for whole file" # if Image.DEBUG:
# print "Activating g4 compression for whole file"
# Decoder expects entire file as one tile. # Decoder expects entire file as one tile.
# There's a buffer size limit in load (64k) # There's a buffer size limit in load (64k)
@ -850,7 +865,8 @@ class TiffImageFile(ImageFile.ImageFile):
# libtiff closes the file descriptor, so pass in a dup. # libtiff closes the file descriptor, so pass in a dup.
try: try:
fp = hasattr(self.fp, "fileno") and os.dup(self.fp.fileno()) fp = hasattr(self.fp, "fileno") and \
os.dup(self.fp.fileno())
except IOError: except IOError:
# io.BytesIO have a fileno, but returns an IOError if # io.BytesIO have a fileno, but returns an IOError if
# it doesn't use a file descriptor. # it doesn't use a file descriptor.
@ -881,7 +897,7 @@ class TiffImageFile(ImageFile.ImageFile):
# Offset in the tile tuple is 0, we go from 0,0 to # Offset in the tile tuple is 0, we go from 0,0 to
# w,h, and we only do this once -- eds # w,h, and we only do this once -- eds
a = (rawmode, self._compression, fp ) a = (rawmode, self._compression, fp)
self.tile.append( self.tile.append(
(self._compression, (self._compression,
(0, 0, w, ysize), (0, 0, w, ysize),
@ -893,8 +909,8 @@ class TiffImageFile(ImageFile.ImageFile):
a = self._decoder(rawmode, l, i) a = self._decoder(rawmode, l, i)
self.tile.append( self.tile.append(
(self._compression, (self._compression,
(0, min(y, ysize), w, min(y+h, ysize)), (0, min(y, ysize), w, min(y+h, ysize)),
offsets[i], a)) offsets[i], a))
if Image.DEBUG: if Image.DEBUG:
print ("tiles: ", self.tile) print ("tiles: ", self.tile)
y = y + h y = y + h
@ -914,8 +930,8 @@ class TiffImageFile(ImageFile.ImageFile):
# is not a multiple of the tile size... # is not a multiple of the tile size...
self.tile.append( self.tile.append(
(self._compression, (self._compression,
(x, y, x+w, y+h), (x, y, x+w, y+h),
o, a)) o, a))
x = x + w x = x + w
if x >= self.size[0]: if x >= self.size[0]:
x, y = 0, y + h x, y = 0, y + h
@ -937,25 +953,27 @@ class TiffImageFile(ImageFile.ImageFile):
# -------------------------------------------------------------------- # --------------------------------------------------------------------
# Write TIFF files # Write TIFF files
# little endian is default except for image modes with explict big endian byte-order # little endian is default except for image modes with
# explict big endian byte-order
SAVE_INFO = { SAVE_INFO = {
# mode => rawmode, byteorder, photometrics, sampleformat, bitspersample, extra # mode => rawmode, byteorder, photometrics,
# sampleformat, bitspersample, extra
"1": ("1", II, 1, 1, (1,), None), "1": ("1", II, 1, 1, (1,), None),
"L": ("L", II, 1, 1, (8,), None), "L": ("L", II, 1, 1, (8,), None),
"LA": ("LA", II, 1, 1, (8,8), 2), "LA": ("LA", II, 1, 1, (8, 8), 2),
"P": ("P", II, 3, 1, (8,), None), "P": ("P", II, 3, 1, (8,), None),
"PA": ("PA", II, 3, 1, (8,8), 2), "PA": ("PA", II, 3, 1, (8, 8), 2),
"I": ("I;32S", II, 1, 2, (32,), None), "I": ("I;32S", II, 1, 2, (32,), None),
"I;16": ("I;16", II, 1, 1, (16,), None), "I;16": ("I;16", II, 1, 1, (16,), None),
"I;16S": ("I;16S", II, 1, 2, (16,), None), "I;16S": ("I;16S", II, 1, 2, (16,), None),
"F": ("F;32F", II, 1, 3, (32,), None), "F": ("F;32F", II, 1, 3, (32,), None),
"RGB": ("RGB", II, 2, 1, (8,8,8), None), "RGB": ("RGB", II, 2, 1, (8, 8, 8), None),
"RGBX": ("RGBX", II, 2, 1, (8,8,8,8), 0), "RGBX": ("RGBX", II, 2, 1, (8, 8, 8, 8), 0),
"RGBA": ("RGBA", II, 2, 1, (8,8,8,8), 2), "RGBA": ("RGBA", II, 2, 1, (8, 8, 8, 8), 2),
"CMYK": ("CMYK", II, 5, 1, (8,8,8,8), None), "CMYK": ("CMYK", II, 5, 1, (8, 8, 8, 8), None),
"YCbCr": ("YCbCr", II, 6, 1, (8,8,8), None), "YCbCr": ("YCbCr", II, 6, 1, (8, 8, 8), None),
"LAB": ("LAB", II, 8, 1, (8,8,8), None), "LAB": ("LAB", II, 8, 1, (8, 8, 8), None),
"I;32BS": ("I;32BS", MM, 1, 2, (32,), None), "I;32BS": ("I;32BS", MM, 1, 2, (32,), None),
"I;16B": ("I;16B", MM, 1, 1, (16,), None), "I;16B": ("I;16B", MM, 1, 1, (16,), None),
@ -963,6 +981,7 @@ SAVE_INFO = {
"F;32BF": ("F;32BF", MM, 1, 3, (32,), None), "F;32BF": ("F;32BF", MM, 1, 3, (32,), None),
} }
def _cvt_res(value): def _cvt_res(value):
# convert value to TIFF rational number -- (numerator, denominator) # convert value to TIFF rational number -- (numerator, denominator)
if isinstance(value, collections.Sequence): if isinstance(value, collections.Sequence):
@ -973,6 +992,7 @@ def _cvt_res(value):
value = float(value) value = float(value)
return (int(value * 65536), 65536) return (int(value * 65536), 65536)
def _save(im, fp, filename): def _save(im, fp, filename):
try: try:
@ -982,7 +1002,8 @@ def _save(im, fp, filename):
ifd = ImageFileDirectory(prefix) ifd = ImageFileDirectory(prefix)
compression = im.encoderinfo.get('compression',im.info.get('compression','raw')) compression = im.encoderinfo.get(
'compression', im.info.get('compression', 'raw'))
libtiff = WRITE_LIBTIFF or compression != 'raw' libtiff = WRITE_LIBTIFF or compression != 'raw'
@ -999,17 +1020,16 @@ def _save(im, fp, filename):
ifd[IMAGELENGTH] = im.size[1] ifd[IMAGELENGTH] = im.size[1]
# write any arbitrary tags passed in as an ImageFileDirectory # write any arbitrary tags passed in as an ImageFileDirectory
info = im.encoderinfo.get("tiffinfo",{}) info = im.encoderinfo.get("tiffinfo", {})
if Image.DEBUG: if Image.DEBUG:
print ("Tiffinfo Keys: %s"% info.keys) print("Tiffinfo Keys: %s" % info.keys)
keys = list(info.keys()) keys = list(info.keys())
for key in keys: for key in keys:
ifd[key] = info.get(key) ifd[key] = info.get(key)
try: try:
ifd.tagtype[key] = info.tagtype[key] ifd.tagtype[key] = info.tagtype[key]
except: except:
pass # might not be an IFD, Might not have populated type pass # might not be an IFD, Might not have populated type
# additions written by Greg Couch, gregc@cgl.ucsf.edu # additions written by Greg Couch, gregc@cgl.ucsf.edu
# inspired by image-sig posting from Kevin Cazabon, kcazabon@home.com # inspired by image-sig posting from Kevin Cazabon, kcazabon@home.com
@ -1030,7 +1050,7 @@ def _save(im, fp, filename):
ifd[IMAGEDESCRIPTION] = im.encoderinfo["description"] ifd[IMAGEDESCRIPTION] = im.encoderinfo["description"]
if "resolution" in im.encoderinfo: if "resolution" in im.encoderinfo:
ifd[X_RESOLUTION] = ifd[Y_RESOLUTION] \ ifd[X_RESOLUTION] = ifd[Y_RESOLUTION] \
= _cvt_res(im.encoderinfo["resolution"]) = _cvt_res(im.encoderinfo["resolution"])
if "x resolution" in im.encoderinfo: if "x resolution" in im.encoderinfo:
ifd[X_RESOLUTION] = _cvt_res(im.encoderinfo["x resolution"]) ifd[X_RESOLUTION] = _cvt_res(im.encoderinfo["x resolution"])
if "y resolution" in im.encoderinfo: if "y resolution" in im.encoderinfo:
@ -1077,8 +1097,9 @@ def _save(im, fp, filename):
stride = len(bits) * ((im.size[0]*bits[0]+7)//8) stride = len(bits) * ((im.size[0]*bits[0]+7)//8)
ifd[ROWSPERSTRIP] = im.size[1] ifd[ROWSPERSTRIP] = im.size[1]
ifd[STRIPBYTECOUNTS] = stride * im.size[1] ifd[STRIPBYTECOUNTS] = stride * im.size[1]
ifd[STRIPOFFSETS] = 0 # this is adjusted by IFD writer ifd[STRIPOFFSETS] = 0 # this is adjusted by IFD writer
ifd[COMPRESSION] = COMPRESSION_INFO_REV.get(compression,1) # no compression by default # no compression by default:
ifd[COMPRESSION] = COMPRESSION_INFO_REV.get(compression, 1)
if libtiff: if libtiff:
if Image.DEBUG: if Image.DEBUG:
@ -1089,23 +1110,27 @@ def _save(im, fp, filename):
fp.seek(0) fp.seek(0)
_fp = os.dup(fp.fileno()) _fp = os.dup(fp.fileno())
blocklist = [STRIPOFFSETS, STRIPBYTECOUNTS, ROWSPERSTRIP, ICCPROFILE] # ICC Profile crashes. # ICC Profile crashes.
atts={} blocklist = [STRIPOFFSETS, STRIPBYTECOUNTS, ROWSPERSTRIP, ICCPROFILE]
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]
# 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.
for k,v in itertools.chain(ifd.items(), getattr(im, 'ifd', {}).items()): for k, v in itertools.chain(
ifd.items(), getattr(im, 'ifd', {}).items()):
if k not in atts and k not in blocklist: if k not in atts and k not in blocklist:
if type(v[0]) == tuple and len(v) > 1: if type(v[0]) == tuple and len(v) > 1:
# A tuple of more than one rational tuples # A tuple of more than one rational tuples
# flatten to floats, following tiffcp.c->cpTag->TIFF_RATIONAL # flatten to floats,
# following tiffcp.c->cpTag->TIFF_RATIONAL
atts[k] = [float(elt[0])/float(elt[1]) for elt in v] atts[k] = [float(elt[0])/float(elt[1]) for elt in v]
continue continue
if type(v[0]) == tuple and len(v) == 1: if type(v[0]) == tuple and len(v) == 1:
# A tuple of one rational tuples # A tuple of one rational tuples
# flatten to floats, following tiffcp.c->cpTag->TIFF_RATIONAL # flatten to floats,
# following tiffcp.c->cpTag->TIFF_RATIONAL
atts[k] = float(v[0][0])/float(v[0][1]) atts[k] = float(v[0][0])/float(v[0][1])
continue continue
if type(v) == tuple and len(v) > 2: if type(v) == tuple and len(v) > 2:
@ -1115,7 +1140,8 @@ def _save(im, fp, filename):
continue continue
if type(v) == tuple and len(v) == 2: if type(v) == tuple and len(v) == 2:
# one rational tuple # one rational tuple
# flatten to float, following tiffcp.c->cpTag->TIFF_RATIONAL # flatten to float,
# following tiffcp.c->cpTag->TIFF_RATIONAL
atts[k] = float(v[0])/float(v[1]) atts[k] = float(v[0])/float(v[1])
continue continue
if type(v) == tuple and len(v) == 1: if type(v) == tuple and len(v) == 1:
@ -1141,9 +1167,10 @@ def _save(im, fp, filename):
a = (rawmode, compression, _fp, filename, atts) a = (rawmode, compression, _fp, filename, atts)
# print (im.mode, compression, a, im.encoderconfig) # print (im.mode, compression, a, im.encoderconfig)
e = Image._getencoder(im.mode, 'libtiff', a, im.encoderconfig) e = Image._getencoder(im.mode, 'libtiff', a, im.encoderconfig)
e.setimage(im.im, (0,0)+im.size) e.setimage(im.im, (0, 0)+im.size)
while True: while True:
l, s, d = e.encode(16*1024) # undone, change to self.decodermaxblock # undone, change to self.decodermaxblock:
l, s, d = e.encode(16*1024)
if not _fp: if not _fp:
fp.write(d) fp.write(d)
if s: if s:
@ -1155,13 +1182,12 @@ def _save(im, fp, filename):
offset = ifd.save(fp) offset = ifd.save(fp)
ImageFile._save(im, fp, [ ImageFile._save(im, fp, [
("raw", (0,0)+im.size, offset, (rawmode, stride, 1)) ("raw", (0, 0)+im.size, offset, (rawmode, stride, 1))
]) ])
# -- helper for multi-page save -- # -- helper for multi-page save --
if "_debug_multipage" in im.encoderinfo: if "_debug_multipage" in im.encoderinfo:
#just to access o32 and o16 (using correct byte order) # just to access o32 and o16 (using correct byte order)
im._debug_multipage = ifd im._debug_multipage = ifd
# #