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

View File

@ -49,7 +49,8 @@ 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
@ -58,8 +59,8 @@ import os
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
@ -164,7 +165,7 @@ OPEN_INFO = {
(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"),
@ -196,7 +197,7 @@ OPEN_INFO = {
(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"),
@ -214,9 +215,11 @@ OPEN_INFO = {
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.
@ -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):
@ -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])
@ -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:
@ -586,6 +597,7 @@ class ImageFileDirectory(collections.MutableMapping):
return offset return offset
## ##
# Image plugin for TIFF files. # Image plugin for TIFF files.
@ -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,12 +720,12 @@ 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())
@ -720,7 +734,8 @@ class TiffImageFile(ImageFile.ImageFile):
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:
@ -728,7 +743,6 @@ class TiffImageFile(ImageFile.ImageFile):
# 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
@ -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_ccitt", "group3", "group4",
"tiff_jpeg", "tiff_adobe_deflate", "tiff_jpeg", "tiff_adobe_deflate",
"tiff_thunderscan", "tiff_deflate", "tiff_thunderscan", "tiff_deflate",
"tiff_sgilog", "tiff_sgilog24", "tiff_sgilog", "tiff_sgilog24",
"tiff_raw_16"]: "tiff_raw_16"]:
## if Image.DEBUG: # if Image.DEBUG:
## print "Activating g4 compression for whole file" # 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.
@ -937,10 +953,12 @@ 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),
@ -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'
@ -1010,7 +1031,6 @@ def _save(im, fp, filename):
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
if hasattr(im, 'tag'): if hasattr(im, 'tag'):
@ -1078,7 +1098,8 @@ def _save(im, fp, filename):
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.
blocklist = [STRIPOFFSETS, STRIPBYTECOUNTS, ROWSPERSTRIP, 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]
# 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:
@ -1143,7 +1169,8 @@ def _save(im, fp, filename):
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:
@ -1158,7 +1185,6 @@ def _save(im, fp, filename):
("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)