mirror of
				https://github.com/python-pillow/Pillow.git
				synced 2025-10-31 16:07:30 +03:00 
			
		
		
		
	
		
			
				
	
	
		
			1178 lines
		
	
	
		
			40 KiB
		
	
	
	
		
			Python
		
	
	
	
	
	
			
		
		
	
	
			1178 lines
		
	
	
		
			40 KiB
		
	
	
	
		
			Python
		
	
	
	
	
	
| #
 | |
| # The Python Imaging Library.
 | |
| # $Id$
 | |
| #
 | |
| # TIFF file handling
 | |
| #
 | |
| # TIFF is a flexible, if somewhat aged, image file format originally
 | |
| # defined by Aldus.  Although TIFF supports a wide variety of pixel
 | |
| # layouts and compression methods, the name doesn't really stand for
 | |
| # "thousands of incompatible file formats," it just feels that way.
 | |
| #
 | |
| # To read TIFF data from a stream, the stream must be seekable.  For
 | |
| # progressive decoding, make sure to use TIFF files where the tag
 | |
| # directory is placed first in the file.
 | |
| #
 | |
| # History:
 | |
| # 1995-09-01 fl   Created
 | |
| # 1996-05-04 fl   Handle JPEGTABLES tag
 | |
| # 1996-05-18 fl   Fixed COLORMAP support
 | |
| # 1997-01-05 fl   Fixed PREDICTOR support
 | |
| # 1997-08-27 fl   Added support for rational tags (from Perry Stoll)
 | |
| # 1998-01-10 fl   Fixed seek/tell (from Jan Blom)
 | |
| # 1998-07-15 fl   Use private names for internal variables
 | |
| # 1999-06-13 fl   Rewritten for PIL 1.0 (1.0)
 | |
| # 2000-10-11 fl   Additional fixes for Python 2.0 (1.1)
 | |
| # 2001-04-17 fl   Fixed rewind support (seek to frame 0) (1.2)
 | |
| # 2001-05-12 fl   Added write support for more tags (from Greg Couch) (1.3)
 | |
| # 2001-12-18 fl   Added workaround for broken Matrox library
 | |
| # 2002-01-18 fl   Don't mess up if photometric tag is missing (D. Alan Stewart)
 | |
| # 2003-05-19 fl   Check FILLORDER tag
 | |
| # 2003-09-26 fl   Added RGBa support
 | |
| # 2004-02-24 fl   Added DPI support; fixed rational write support
 | |
| # 2005-02-07 fl   Added workaround for broken Corel Draw 10 files
 | |
| # 2006-01-09 fl   Added support for float/double tags (from Russell Nelson)
 | |
| #
 | |
| # Copyright (c) 1997-2006 by Secret Labs AB.  All rights reserved.
 | |
| # Copyright (c) 1995-1997 by Fredrik Lundh
 | |
| #
 | |
| # See the README file for information on usage and redistribution.
 | |
| #
 | |
| 
 | |
| from __future__ import print_function
 | |
| 
 | |
| __version__ = "1.3.5"
 | |
| 
 | |
| from PIL import Image, ImageFile
 | |
| from PIL import ImagePalette
 | |
| from PIL import _binary
 | |
| from PIL._util import isStringType
 | |
| 
 | |
| import warnings
 | |
| import array, sys
 | |
| import collections
 | |
| import itertools
 | |
| import os
 | |
| 
 | |
| # Set these to true to force use of libtiff for reading or writing.
 | |
| READ_LIBTIFF = False
 | |
| WRITE_LIBTIFF= False
 | |
| 
 | |
| II = b"II" # little-endian (intel-style)
 | |
| MM = b"MM" # big-endian (motorola-style)
 | |
| 
 | |
| i8 = _binary.i8
 | |
| o8 = _binary.o8
 | |
| 
 | |
| if sys.byteorder == "little":
 | |
|     native_prefix = II
 | |
| else:
 | |
|     native_prefix = MM
 | |
| 
 | |
| #
 | |
| # --------------------------------------------------------------------
 | |
| # Read TIFF files
 | |
| 
 | |
| il16 = _binary.i16le
 | |
| il32 = _binary.i32le
 | |
| ol16 = _binary.o16le
 | |
| ol32 = _binary.o32le
 | |
| 
 | |
| ib16 = _binary.i16be
 | |
| ib32 = _binary.i32be
 | |
| ob16 = _binary.o16be
 | |
| ob32 = _binary.o32be
 | |
| 
 | |
| # a few tag names, just to make the code below a bit more readable
 | |
| IMAGEWIDTH = 256
 | |
| IMAGELENGTH = 257
 | |
| BITSPERSAMPLE = 258
 | |
| COMPRESSION = 259
 | |
| PHOTOMETRIC_INTERPRETATION = 262
 | |
| FILLORDER = 266
 | |
| IMAGEDESCRIPTION = 270
 | |
| STRIPOFFSETS = 273
 | |
| SAMPLESPERPIXEL = 277
 | |
| ROWSPERSTRIP = 278
 | |
| STRIPBYTECOUNTS = 279
 | |
| X_RESOLUTION = 282
 | |
| Y_RESOLUTION = 283
 | |
| PLANAR_CONFIGURATION = 284
 | |
| RESOLUTION_UNIT = 296
 | |
| SOFTWARE = 305
 | |
| DATE_TIME = 306
 | |
| ARTIST = 315
 | |
| PREDICTOR = 317
 | |
| COLORMAP = 320
 | |
| TILEOFFSETS = 324
 | |
| EXTRASAMPLES = 338
 | |
| SAMPLEFORMAT = 339
 | |
| JPEGTABLES = 347
 | |
| COPYRIGHT = 33432
 | |
| IPTC_NAA_CHUNK = 33723 # newsphoto properties
 | |
| PHOTOSHOP_CHUNK = 34377 # photoshop properties
 | |
| ICCPROFILE = 34675
 | |
| EXIFIFD = 34665
 | |
| XMP = 700
 | |
| 
 | |
| # https://github.com/fiji/ImageJA/blob/master/src/main/java/ij/io/TiffDecoder.java
 | |
| IMAGEJ_META_DATA_BYTE_COUNTS = 50838
 | |
| IMAGEJ_META_DATA = 50839
 | |
| 
 | |
| COMPRESSION_INFO = {
 | |
|     # Compression => pil compression name
 | |
|     1: "raw",
 | |
|     2: "tiff_ccitt",
 | |
|     3: "group3",
 | |
|     4: "group4",
 | |
|     5: "tiff_lzw",
 | |
|     6: "tiff_jpeg", # obsolete
 | |
|     7: "jpeg",
 | |
|     8: "tiff_adobe_deflate",
 | |
|     32771: "tiff_raw_16", # 16-bit padding
 | |
|     32773: "packbits",
 | |
|     32809: "tiff_thunderscan",
 | |
|     32946: "tiff_deflate",
 | |
|     34676: "tiff_sgilog",
 | |
|     34677: "tiff_sgilog24",
 | |
| }
 | |
| 
 | |
| COMPRESSION_INFO_REV = dict([(v,k) for (k,v) in COMPRESSION_INFO.items()])
 | |
| 
 | |
| OPEN_INFO = {
 | |
|     # (ByteOrder, PhotoInterpretation, SampleFormat, FillOrder, BitsPerSample,
 | |
|     #  ExtraSamples) => mode, rawmode
 | |
|     (II, 0, 1, 1, (1,), ()): ("1", "1;I"),
 | |
|     (II, 0, 1, 2, (1,), ()): ("1", "1;IR"),
 | |
|     (II, 0, 1, 1, (8,), ()): ("L", "L;I"),
 | |
|     (II, 0, 1, 2, (8,), ()): ("L", "L;IR"),
 | |
|     (II, 0, 3, 1, (32,), ()): ("F", "F;32F"),
 | |
|     (II, 1, 1, 1, (1,), ()): ("1", "1"),
 | |
|     (II, 1, 1, 2, (1,), ()): ("1", "1;R"),
 | |
|     (II, 1, 1, 1, (8,), ()): ("L", "L"),
 | |
|     (II, 1, 1, 1, (8,8), (2,)): ("LA", "LA"),
 | |
|     (II, 1, 1, 2, (8,), ()): ("L", "L;R"),
 | |
|     (II, 1, 1, 1, (12,), ()): ("I;16", "I;12"),
 | |
|     (II, 1, 1, 1, (16,), ()): ("I;16", "I;16"),
 | |
|     (II, 1, 2, 1, (16,), ()): ("I;16S", "I;16S"),
 | |
|     (II, 1, 1, 1, (32,), ()): ("I", "I;32N"),
 | |
|     (II, 1, 2, 1, (32,), ()): ("I", "I;32S"),
 | |
|     (II, 1, 3, 1, (32,), ()): ("F", "F;32F"),
 | |
|     (II, 2, 1, 1, (8,8,8), ()): ("RGB", "RGB"),
 | |
|     (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), (0,)): ("RGBX", "RGBX"),
 | |
|     (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), (999,)): ("RGBA", "RGBA"), # corel draw 10
 | |
|     (II, 3, 1, 1, (1,), ()): ("P", "P;1"),
 | |
|     (II, 3, 1, 2, (1,), ()): ("P", "P;1R"),
 | |
|     (II, 3, 1, 1, (2,), ()): ("P", "P;2"),
 | |
|     (II, 3, 1, 2, (2,), ()): ("P", "P;2R"),
 | |
|     (II, 3, 1, 1, (4,), ()): ("P", "P;4"),
 | |
|     (II, 3, 1, 2, (4,), ()): ("P", "P;4R"),
 | |
|     (II, 3, 1, 1, (8,), ()): ("P", "P"),
 | |
|     (II, 3, 1, 1, (8,8), (2,)): ("PA", "PA"),
 | |
|     (II, 3, 1, 2, (8,), ()): ("P", "P;R"),
 | |
|     (II, 5, 1, 1, (8,8,8,8), ()): ("CMYK", "CMYK"),
 | |
|     (II, 6, 1, 1, (8,8,8), ()): ("YCbCr", "YCbCr"),
 | |
|     (II, 8, 1, 1, (8,8,8), ()): ("LAB", "LAB"),
 | |
| 
 | |
|     (MM, 0, 1, 1, (1,), ()): ("1", "1;I"),
 | |
|     (MM, 0, 1, 2, (1,), ()): ("1", "1;IR"),
 | |
|     (MM, 0, 1, 1, (8,), ()): ("L", "L;I"),
 | |
|     (MM, 0, 1, 2, (8,), ()): ("L", "L;IR"),
 | |
|     (MM, 1, 1, 1, (1,), ()): ("1", "1"),
 | |
|     (MM, 1, 1, 2, (1,), ()): ("1", "1;R"),
 | |
|     (MM, 1, 1, 1, (8,), ()): ("L", "L"),
 | |
|     (MM, 1, 1, 1, (8,8), (2,)): ("LA", "LA"),
 | |
|     (MM, 1, 1, 2, (8,), ()): ("L", "L;R"),
 | |
|     (MM, 1, 1, 1, (16,), ()): ("I;16B", "I;16B"),
 | |
|     (MM, 1, 2, 1, (16,), ()): ("I;16BS", "I;16BS"),
 | |
|     (MM, 1, 2, 1, (32,), ()): ("I;32BS", "I;32BS"),
 | |
|     (MM, 1, 3, 1, (32,), ()): ("F", "F;32BF"),
 | |
|     (MM, 2, 1, 1, (8,8,8), ()): ("RGB", "RGB"),
 | |
|     (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), (1,)): ("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, 3, 1, 1, (1,), ()): ("P", "P;1"),
 | |
|     (MM, 3, 1, 2, (1,), ()): ("P", "P;1R"),
 | |
|     (MM, 3, 1, 1, (2,), ()): ("P", "P;2"),
 | |
|     (MM, 3, 1, 2, (2,), ()): ("P", "P;2R"),
 | |
|     (MM, 3, 1, 1, (4,), ()): ("P", "P;4"),
 | |
|     (MM, 3, 1, 2, (4,), ()): ("P", "P;4R"),
 | |
|     (MM, 3, 1, 1, (8,), ()): ("P", "P"),
 | |
|     (MM, 3, 1, 1, (8,8), (2,)): ("PA", "PA"),
 | |
|     (MM, 3, 1, 2, (8,), ()): ("P", "P;R"),
 | |
|     (MM, 5, 1, 1, (8,8,8,8), ()): ("CMYK", "CMYK"),
 | |
|     (MM, 6, 1, 1, (8,8,8), ()): ("YCbCr", "YCbCr"),
 | |
|     (MM, 8, 1, 1, (8,8,8), ()): ("LAB", "LAB"),
 | |
| 
 | |
| }
 | |
| 
 | |
| PREFIXES = [b"MM\000\052", b"II\052\000", b"II\xBC\000"]
 | |
| 
 | |
| def _accept(prefix):
 | |
|     return prefix[:4] in PREFIXES
 | |
| 
 | |
| ##
 | |
| # Wrapper for TIFF IFDs.
 | |
| 
 | |
| class ImageFileDirectory(collections.MutableMapping):
 | |
|     """ This class represents a TIFF tag directory.  To speed things
 | |
|         up, we don't decode tags unless they're asked for.
 | |
| 
 | |
|         Exposes a dictionary interface of the tags in the directory
 | |
|         ImageFileDirectory[key] = value
 | |
|         value = ImageFileDirectory[key]
 | |
| 
 | |
|         Also contains a dictionary of tag types as read from the tiff
 | |
|         image file, 'ImageFileDirectory.tagtype'
 | |
| 
 | |
| 
 | |
|         Data Structures:
 | |
|         'public'
 | |
|         * self.tagtype = {} Key: numerical tiff tag number
 | |
|                             Value: integer corresponding to the data type from
 | |
|                             `TiffTags.TYPES`
 | |
| 
 | |
|         'internal'
 | |
|         * self.tags = {}  Key: numerical tiff tag number
 | |
|                           Value: Decoded data, Generally a tuple.
 | |
|                             * If set from __setval__ -- always a tuple
 | |
|                             * Numeric types -- always a tuple
 | |
|                             * String type -- not a tuple, returned as string
 | |
|                             * Undefined data -- not a tuple, returned as bytes
 | |
|                             * Byte -- not a tuple, returned as byte.
 | |
|         * self.tagdata = {} Key: numerical tiff tag number
 | |
|                             Value: undecoded byte string from file
 | |
| 
 | |
| 
 | |
|         Tags will be found in either self.tags or self.tagdata, but
 | |
|         not both. The union of the two should contain all the tags
 | |
|         from the Tiff image file.  External classes shouldn't
 | |
|         reference these unless they're really sure what they're doing.
 | |
|         """
 | |
| 
 | |
|     def __init__(self, prefix=II):
 | |
|         """
 | |
|         :prefix: 'II'|'MM'  tiff endianness
 | |
|         """
 | |
|         self.prefix = prefix[:2]
 | |
|         if self.prefix == MM:
 | |
|             self.i16, self.i32 = ib16, ib32
 | |
|             self.o16, self.o32 = ob16, ob32
 | |
|         elif self.prefix == II:
 | |
|             self.i16, self.i32 = il16, il32
 | |
|             self.o16, self.o32 = ol16, ol32
 | |
|         else:
 | |
|             raise SyntaxError("not a TIFF IFD")
 | |
|         self.reset()
 | |
| 
 | |
|     def reset(self):
 | |
|         #: Tags is an incomplete dictionary of the tags of the image.
 | |
|         #: For a complete dictionary, use the as_dict method.
 | |
|         self.tags = {}
 | |
|         self.tagdata = {}
 | |
|         self.tagtype = {} # added 2008-06-05 by Florian Hoech
 | |
|         self.next = None
 | |
| 
 | |
|     def __str__(self):
 | |
|         return str(self.as_dict())
 | |
| 
 | |
|     def as_dict(self):
 | |
|         """Return a dictionary of the image's tags."""
 | |
|         return dict(self.items())
 | |
| 
 | |
|     def named(self):
 | |
|         """Returns the complete tag dictionary, with named tags where posible."""
 | |
|         from PIL import TiffTags
 | |
|         result = {}
 | |
|         for tag_code, value in self.items():
 | |
|             tag_name = TiffTags.TAGS.get(tag_code, tag_code)
 | |
|             result[tag_name] = value
 | |
|         return result
 | |
| 
 | |
| 
 | |
|     # dictionary API
 | |
| 
 | |
|     def __len__(self):
 | |
|         return len(self.tagdata) + len(self.tags)
 | |
| 
 | |
|     def __getitem__(self, tag):
 | |
|         try:
 | |
|             return self.tags[tag]
 | |
|         except KeyError:
 | |
|             data = self.tagdata[tag] # unpack on the fly
 | |
|             type = self.tagtype[tag]
 | |
|             size, handler = self.load_dispatch[type]
 | |
|             self.tags[tag] = data = handler(self, data)
 | |
|             del self.tagdata[tag]
 | |
|             return data
 | |
| 
 | |
|     def getscalar(self, tag, default=None):
 | |
|         try:
 | |
|             value = self[tag]
 | |
|             if len(value) != 1:
 | |
|                 if tag == SAMPLEFORMAT:
 | |
|                     # work around broken (?) matrox library
 | |
|                     # (from Ted Wright, via Bob Klimek)
 | |
|                     raise KeyError # use default
 | |
|                 raise ValueError("not a scalar")
 | |
|             return value[0]
 | |
|         except KeyError:
 | |
|             if default is None:
 | |
|                 raise
 | |
|             return default
 | |
| 
 | |
|     def __contains__(self, tag):
 | |
|         return tag in self.tags or tag in self.tagdata
 | |
| 
 | |
|     if bytes is str:
 | |
|         def has_key(self, tag):
 | |
|             return tag in self
 | |
| 
 | |
|     def __setitem__(self, tag, value):
 | |
|         # tags are tuples for integers
 | |
|         # tags are not tuples for byte, string, and undefined data.
 | |
|         # see load_*
 | |
|         if not isinstance(value, tuple):
 | |
|             value = (value,)
 | |
|         self.tags[tag] = value
 | |
| 
 | |
|     def __delitem__(self, tag):
 | |
|         self.tags.pop(tag, self.tagdata.pop(tag, None))
 | |
| 
 | |
|     def __iter__(self):
 | |
|         return itertools.chain(self.tags.__iter__(), self.tagdata.__iter__())
 | |
| 
 | |
|     def items(self):
 | |
|         keys = list(self.__iter__())
 | |
|         values = [self[key] for key in keys]
 | |
|         return zip(keys, values)
 | |
| 
 | |
|     # load primitives
 | |
| 
 | |
|     load_dispatch = {}
 | |
| 
 | |
|     def load_byte(self, data):
 | |
|         return data
 | |
|     load_dispatch[1] = (1, load_byte)
 | |
| 
 | |
|     def load_string(self, data):
 | |
|         if data[-1:] == b'\0':
 | |
|             data = data[:-1]
 | |
|         return data.decode('latin-1', 'replace')
 | |
|     load_dispatch[2] = (1, load_string)
 | |
| 
 | |
|     def load_short(self, data):
 | |
|         l = []
 | |
|         for i in range(0, len(data), 2):
 | |
|             l.append(self.i16(data, i))
 | |
|         return tuple(l)
 | |
|     load_dispatch[3] = (2, load_short)
 | |
| 
 | |
|     def load_long(self, data):
 | |
|         l = []
 | |
|         for i in range(0, len(data), 4):
 | |
|             l.append(self.i32(data, i))
 | |
|         return tuple(l)
 | |
|     load_dispatch[4] = (4, load_long)
 | |
| 
 | |
|     def load_rational(self, data):
 | |
|         l = []
 | |
|         for i in range(0, len(data), 8):
 | |
|             l.append((self.i32(data, i), self.i32(data, i+4)))
 | |
|         return tuple(l)
 | |
|     load_dispatch[5] = (8, load_rational)
 | |
| 
 | |
|     def load_float(self, data):
 | |
|         a = array.array("f", data)
 | |
|         if self.prefix != native_prefix:
 | |
|             a.byteswap()
 | |
|         return tuple(a)
 | |
|     load_dispatch[11] = (4, load_float)
 | |
| 
 | |
|     def load_double(self, data):
 | |
|         a = array.array("d", data)
 | |
|         if self.prefix != native_prefix:
 | |
|             a.byteswap()
 | |
|         return tuple(a)
 | |
|     load_dispatch[12] = (8, load_double)
 | |
| 
 | |
|     def load_undefined(self, data):
 | |
|         # Untyped data
 | |
|         return data
 | |
|     load_dispatch[7] = (1, load_undefined)
 | |
| 
 | |
|     def load(self, fp):
 | |
|         # load tag dictionary
 | |
| 
 | |
|         self.reset()
 | |
| 
 | |
|         i16 = self.i16
 | |
|         i32 = self.i32
 | |
| 
 | |
|         for i in range(i16(fp.read(2))):
 | |
| 
 | |
|             ifd = fp.read(12)
 | |
| 
 | |
|             tag, typ = i16(ifd), i16(ifd, 2)
 | |
| 
 | |
|             if Image.DEBUG:
 | |
|                 from PIL import TiffTags
 | |
|                 tagname = TiffTags.TAGS.get(tag, "unknown")
 | |
|                 typname = TiffTags.TYPES.get(typ, "unknown")
 | |
|                 print("tag: %s (%d)" % (tagname, tag), end=' ')
 | |
|                 print("- type: %s (%d)" % (typname, typ), end=' ')
 | |
| 
 | |
|             try:
 | |
|                 dispatch = self.load_dispatch[typ]
 | |
|             except KeyError:
 | |
|                 if Image.DEBUG:
 | |
|                     print("- unsupported type", typ)
 | |
|                 continue # ignore unsupported type
 | |
| 
 | |
|             size, handler = dispatch
 | |
| 
 | |
|             size = size * i32(ifd, 4)
 | |
| 
 | |
|             # Get and expand tag value
 | |
|             if size > 4:
 | |
|                 here = fp.tell()
 | |
|                 fp.seek(i32(ifd, 8))
 | |
|                 data = ImageFile._safe_read(fp, size)
 | |
|                 fp.seek(here)
 | |
|             else:
 | |
|                 data = ifd[8:8+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))
 | |
|                 continue
 | |
| 
 | |
|             self.tagdata[tag] = data
 | |
|             self.tagtype[tag] = typ
 | |
| 
 | |
|             if Image.DEBUG:
 | |
|                 if tag in (COLORMAP, IPTC_NAA_CHUNK, PHOTOSHOP_CHUNK, ICCPROFILE, XMP):
 | |
|                     print("- value: <table: %d bytes>" % size)
 | |
|                 else:
 | |
|                     print("- value:", self[tag])
 | |
| 
 | |
|         self.next = i32(fp.read(4))
 | |
| 
 | |
|     # save primitives
 | |
| 
 | |
|     def save(self, fp):
 | |
| 
 | |
|         o16 = self.o16
 | |
|         o32 = self.o32
 | |
| 
 | |
|         fp.write(o16(len(self.tags)))
 | |
| 
 | |
|         # always write in ascending tag order
 | |
|         tags = sorted(self.tags.items())
 | |
| 
 | |
|         directory = []
 | |
|         append = directory.append
 | |
| 
 | |
|         offset = fp.tell() + len(self.tags) * 12 + 4
 | |
| 
 | |
|         stripoffsets = None
 | |
| 
 | |
|         # pass 1: convert tags to binary format
 | |
|         for tag, value in tags:
 | |
| 
 | |
|             typ = None
 | |
| 
 | |
|             if tag in self.tagtype:
 | |
|                 typ = self.tagtype[tag]
 | |
| 
 | |
|             if Image.DEBUG:
 | |
|                 print ("Tag %s, Type: %s, Value: %s" % (tag, typ, value))
 | |
| 
 | |
|             if typ == 1:
 | |
|                 # byte data
 | |
|                 if isinstance(value, tuple):
 | |
|                     data = value = value[-1]
 | |
|                 else:
 | |
|                     data = value
 | |
|             elif typ == 7:
 | |
|                 # untyped data
 | |
|                 data = value = b"".join(value)
 | |
|             elif isStringType(value[0]):
 | |
|                 # string data
 | |
|                 if isinstance(value, tuple):
 | |
|                     value = value[-1]
 | |
|                 typ = 2
 | |
|                 # was b'\0'.join(str), which led to \x00a\x00b sorts
 | |
|                 # of strings which I don't see in in the wild tiffs
 | |
|                 # and doesn't match the tiff spec: 8-bit byte that
 | |
|                 # contains a 7-bit ASCII code; the last byte must be
 | |
|                 # NUL (binary zero). Also, I don't think this was well
 | |
|                 # excersized before.
 | |
|                 data = value = b"" + value.encode('ascii', 'replace') + b"\0"
 | |
|             else:
 | |
|                 # integer data
 | |
|                 if tag == STRIPOFFSETS:
 | |
|                     stripoffsets = len(directory)
 | |
|                     typ = 4 # to avoid catch-22
 | |
|                 elif tag in (X_RESOLUTION, Y_RESOLUTION) or typ==5:
 | |
|                     # identify rational data fields
 | |
|                     typ = 5
 | |
|                     if isinstance(value[0], tuple):
 | |
|                         # long name for flatten
 | |
|                         value = tuple(itertools.chain.from_iterable(value))
 | |
|                 elif not typ:
 | |
|                     typ = 3
 | |
|                     for v in value:
 | |
|                         if v >= 65536:
 | |
|                             typ = 4
 | |
|                 if typ == 3:
 | |
|                     data = b"".join(map(o16, value))
 | |
|                 else:
 | |
|                     data = b"".join(map(o32, value))
 | |
| 
 | |
|             if Image.DEBUG:
 | |
|                 from PIL import TiffTags
 | |
|                 tagname = TiffTags.TAGS.get(tag, "unknown")
 | |
|                 typname = TiffTags.TYPES.get(typ, "unknown")
 | |
|                 print("save: %s (%d)" % (tagname, tag), end=' ')
 | |
|                 print("- type: %s (%d)" % (typname, typ), end=' ')
 | |
|                 if tag in (COLORMAP, IPTC_NAA_CHUNK, PHOTOSHOP_CHUNK, ICCPROFILE, XMP):
 | |
|                     size = len(data)
 | |
|                     print("- value: <table: %d bytes>" % size)
 | |
|                 else:
 | |
|                     print("- value:", value)
 | |
| 
 | |
|             # figure out if data fits into the directory
 | |
|             if len(data) == 4:
 | |
|                 append((tag, typ, len(value), data, b""))
 | |
|             elif len(data) < 4:
 | |
|                 append((tag, typ, len(value), data + (4-len(data))*b"\0", b""))
 | |
|             else:
 | |
|                 count = len(value)
 | |
|                 if typ == 5:
 | |
|                     count = count // 2        # adjust for rational data field
 | |
| 
 | |
|                 append((tag, typ, count, o32(offset), data))
 | |
|                 offset += len(data)
 | |
|                 if offset & 1:
 | |
|                     offset += 1  # word padding
 | |
| 
 | |
|         # update strip offset data to point beyond auxiliary data
 | |
|         if stripoffsets is not None:
 | |
|             tag, typ, count, value, data = directory[stripoffsets]
 | |
|             assert not data, "multistrip support not yet implemented"
 | |
|             value = o32(self.i32(value) + offset)
 | |
|             directory[stripoffsets] = tag, typ, count, value, data
 | |
| 
 | |
|         # pass 2: write directory to file
 | |
|         for tag, typ, count, value, data in directory:
 | |
|             if Image.DEBUG > 1:
 | |
|                 print(tag, typ, count, repr(value), repr(data))
 | |
|             fp.write(o16(tag) + o16(typ) + o32(count) + value)
 | |
| 
 | |
|         # -- overwrite here for multi-page --
 | |
|         fp.write(b"\0\0\0\0") # end of directory
 | |
| 
 | |
|         # pass 3: write auxiliary data to file
 | |
|         for tag, typ, count, value, data in directory:
 | |
|             fp.write(data)
 | |
|             if len(data) & 1:
 | |
|                 fp.write(b"\0")
 | |
| 
 | |
|         return offset
 | |
| 
 | |
| ##
 | |
| # Image plugin for TIFF files.
 | |
| 
 | |
| class TiffImageFile(ImageFile.ImageFile):
 | |
| 
 | |
|     format = "TIFF"
 | |
|     format_description = "Adobe TIFF"
 | |
| 
 | |
|     def _open(self):
 | |
|         "Open the first image in a TIFF file"
 | |
| 
 | |
|         # Header
 | |
|         ifh = self.fp.read(8)
 | |
| 
 | |
|         if ifh[:4] not in PREFIXES:
 | |
|             raise SyntaxError("not a TIFF file")
 | |
| 
 | |
|         # image file directory (tag dictionary)
 | |
|         self.tag = self.ifd = ImageFileDirectory(ifh[:2])
 | |
| 
 | |
|         # setup frame pointers
 | |
|         self.__first = self.__next = self.ifd.i32(ifh, 4)
 | |
|         self.__frame = -1
 | |
|         self.__fp = self.fp
 | |
| 
 | |
|         if Image.DEBUG:
 | |
|             print ("*** TiffImageFile._open ***")
 | |
|             print ("- __first:", self.__first)
 | |
|             print ("- ifh: ", ifh)
 | |
| 
 | |
|        # and load the first frame
 | |
|         self._seek(0)
 | |
| 
 | |
|     def seek(self, frame):
 | |
|         "Select a given frame as current image"
 | |
| 
 | |
|         if frame < 0:
 | |
|             frame = 0
 | |
|         self._seek(frame)
 | |
| 
 | |
|     def tell(self):
 | |
|         "Return the current frame number"
 | |
| 
 | |
|         return self._tell()
 | |
| 
 | |
|     def _seek(self, frame):
 | |
| 
 | |
|         self.fp = self.__fp
 | |
|         if frame < self.__frame:
 | |
|             # rewind file
 | |
|             self.__frame = -1
 | |
|             self.__next = self.__first
 | |
|         while self.__frame < frame:
 | |
|             if not self.__next:
 | |
|                 raise EOFError("no more images in TIFF file")
 | |
|             self.fp.seek(self.__next)
 | |
|             self.tag.load(self.fp)
 | |
|             self.__next = self.tag.next
 | |
|             self.__frame += 1
 | |
|         self._setup()
 | |
| 
 | |
|     def _tell(self):
 | |
| 
 | |
|         return self.__frame
 | |
| 
 | |
|     def _decoder(self, rawmode, layer, tile=None):
 | |
|         "Setup decoder contexts"
 | |
| 
 | |
|         args = None
 | |
|         if rawmode == "RGB" and self._planar_configuration == 2:
 | |
|             rawmode = rawmode[layer]
 | |
|         compression = self._compression
 | |
|         if compression == "raw":
 | |
|             args = (rawmode, 0, 1)
 | |
|         elif compression == "jpeg":
 | |
|             args = rawmode, ""
 | |
|             if JPEGTABLES in self.tag:
 | |
|                 # Hack to handle abbreviated JPEG headers
 | |
|                 self.tile_prefix = self.tag[JPEGTABLES]
 | |
|         elif compression == "packbits":
 | |
|             args = rawmode
 | |
|         elif compression == "tiff_lzw":
 | |
|             args = rawmode
 | |
|             if 317 in self.tag:
 | |
|                 # Section 14: Differencing Predictor
 | |
|                 self.decoderconfig = (self.tag[PREDICTOR][0],)
 | |
| 
 | |
|         if ICCPROFILE in self.tag:
 | |
|             self.info['icc_profile'] = self.tag[ICCPROFILE]
 | |
| 
 | |
|         return args
 | |
| 
 | |
|     def _load_libtiff(self):
 | |
|         """ Overload method triggered when we detect a compressed tiff
 | |
|             Calls out to libtiff """
 | |
| 
 | |
|         pixel = Image.Image.load(self)
 | |
| 
 | |
|         if self.tile is None:
 | |
|             raise IOError("cannot load this image")
 | |
|         if not self.tile:
 | |
|             return pixel
 | |
| 
 | |
|         self.load_prepare()
 | |
| 
 | |
|         if not len(self.tile) == 1:
 | |
|             raise IOError("Not exactly one tile")
 | |
| 
 | |
|         # (self._compression, (extents tuple), 0, (rawmode, self._compression, fp))
 | |
|         ignored, extents, ignored_2, args = self.tile[0]
 | |
|         decoder = Image._getdecoder(self.mode, 'libtiff', args, self.decoderconfig)
 | |
|         try:
 | |
|             decoder.setimage(self.im, extents)
 | |
|         except ValueError:
 | |
|             raise IOError("Couldn't set the image")
 | |
| 
 | |
|         if hasattr(self.fp, "getvalue"):
 | |
|             # 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
 | |
|             # 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
 | |
|             # string for stringio.
 | |
|             #
 | |
|             # Rearranging for supporting byteio items, since they have a fileno
 | |
|             # that returns an IOError if there's no underlying fp. Easier to deal
 | |
|             # with here by reordering.
 | |
|             if Image.DEBUG:
 | |
|                 print ("have getvalue. just sending in a string from getvalue")
 | |
|             n,err = decoder.decode(self.fp.getvalue())
 | |
|         elif hasattr(self.fp, "fileno"):
 | |
|             # we've got a actual file on disk, pass in the fp.
 | |
|             if Image.DEBUG:
 | |
|                 print ("have fileno, calling fileno version of the decoder.")
 | |
|             self.fp.seek(0)
 | |
|             n,err = decoder.decode(b"fpfp") # 4 bytes, otherwise the trace might error out
 | |
|         else:
 | |
|             # we have something else.
 | |
|             if Image.DEBUG:
 | |
|                 print ("don't have fileno or getvalue. just reading")
 | |
|             # UNDONE -- so much for that buffer size thing.
 | |
|             n,err = decoder.decode(self.fp.read())
 | |
| 
 | |
| 
 | |
|         self.tile = []
 | |
|         self.readonly = 0
 | |
|         # libtiff closed the fp in a, we need to close self.fp, if possible
 | |
|         if hasattr(self.fp, 'close'):
 | |
|             self.fp.close()
 | |
|         self.fp = None # might be shared
 | |
| 
 | |
|         if err < 0:
 | |
|             raise IOError(err)
 | |
| 
 | |
|         self.load_end()
 | |
| 
 | |
|         return Image.Image.load(self)
 | |
| 
 | |
|     def _setup(self):
 | |
|         "Setup this image object based on current tags"
 | |
| 
 | |
|         if 0xBC01 in self.tag:
 | |
|             raise IOError("Windows Media Photo files not yet supported")
 | |
| 
 | |
|         getscalar = self.tag.getscalar
 | |
| 
 | |
|         # extract relevant tags
 | |
|         self._compression = COMPRESSION_INFO[getscalar(COMPRESSION, 1)]
 | |
|         self._planar_configuration = getscalar(PLANAR_CONFIGURATION, 1)
 | |
| 
 | |
|         # photometric is a required tag, but not everyone is reading
 | |
|         # the specification
 | |
|         photo = getscalar(PHOTOMETRIC_INTERPRETATION, 0)
 | |
| 
 | |
|         fillorder = getscalar(FILLORDER, 1)
 | |
| 
 | |
|         if Image.DEBUG:
 | |
|             print("*** Summary ***")
 | |
|             print("- compression:", self._compression)
 | |
|             print("- photometric_interpretation:", photo)
 | |
|             print("- planar_configuration:", self._planar_configuration)
 | |
|             print("- fill_order:", fillorder)
 | |
| 
 | |
|         # size
 | |
|         xsize = getscalar(IMAGEWIDTH)
 | |
|         ysize = getscalar(IMAGELENGTH)
 | |
|         self.size = xsize, ysize
 | |
| 
 | |
|         if Image.DEBUG:
 | |
|             print("- size:", self.size)
 | |
| 
 | |
|         format = getscalar(SAMPLEFORMAT, 1)
 | |
| 
 | |
|         # mode: check photometric interpretation and bits per pixel
 | |
|         key = (
 | |
|             self.tag.prefix, photo, format, fillorder,
 | |
|             self.tag.get(BITSPERSAMPLE, (1,)),
 | |
|             self.tag.get(EXTRASAMPLES, ())
 | |
|             )
 | |
|         if Image.DEBUG:
 | |
|             print("format key:", key)
 | |
|         try:
 | |
|             self.mode, rawmode = OPEN_INFO[key]
 | |
|         except KeyError:
 | |
|             if Image.DEBUG:
 | |
|                 print("- unsupported format")
 | |
|             raise SyntaxError("unknown pixel mode")
 | |
| 
 | |
|         if Image.DEBUG:
 | |
|             print("- raw mode:", rawmode)
 | |
|             print("- pil mode:", self.mode)
 | |
| 
 | |
|         self.info["compression"] = self._compression
 | |
| 
 | |
|         xres = getscalar(X_RESOLUTION, (1, 1))
 | |
|         yres = getscalar(Y_RESOLUTION, (1, 1))
 | |
| 
 | |
|         if xres and not isinstance(xres, tuple):
 | |
|             xres = (xres, 1.)
 | |
|         if yres and not isinstance(yres, tuple):
 | |
|             yres = (yres, 1.)
 | |
|         if xres and yres:
 | |
|             xres = xres[0] / (xres[1] or 1)
 | |
|             yres = yres[0] / (yres[1] or 1)
 | |
|             resunit = getscalar(RESOLUTION_UNIT, 1)
 | |
|             if resunit == 2: # dots per inch
 | |
|                 self.info["dpi"] = xres, yres
 | |
|             elif resunit == 3: # dots per centimeter. convert to dpi
 | |
|                 self.info["dpi"] = xres * 2.54, yres * 2.54
 | |
|             else: # No absolute unit of measurement
 | |
|                 self.info["resolution"] = xres, yres
 | |
| 
 | |
|         # build tile descriptors
 | |
|         x = y = l = 0
 | |
|         self.tile = []
 | |
|         if STRIPOFFSETS in self.tag:
 | |
|             # striped image
 | |
|             offsets = self.tag[STRIPOFFSETS]
 | |
|             h = getscalar(ROWSPERSTRIP, ysize)
 | |
|             w = self.size[0]
 | |
|             if READ_LIBTIFF or self._compression in ["tiff_ccitt", "group3", "group4",
 | |
|                                                      "tiff_jpeg", "tiff_adobe_deflate",
 | |
|                                                      "tiff_thunderscan", "tiff_deflate",
 | |
|                                                      "tiff_sgilog", "tiff_sgilog24",
 | |
|                                                      "tiff_raw_16"]:
 | |
|                 ## if Image.DEBUG:
 | |
|                 ##     print "Activating g4 compression for whole file"
 | |
| 
 | |
|                 # Decoder expects entire file as one tile.
 | |
|                 # There's a buffer size limit in load (64k)
 | |
|                 # so large g4 images will fail if we use that
 | |
|                 # function.
 | |
|                 #
 | |
|                 # Setup the one tile for the whole image, then
 | |
|                 # replace the existing load function with our
 | |
|                 # _load_libtiff function.
 | |
| 
 | |
|                 self.load = self._load_libtiff
 | |
| 
 | |
|                 # To be nice on memory footprint, if there's a
 | |
|                 # file descriptor, use that instead of reading
 | |
|                 # into a string in python.
 | |
| 
 | |
|                 # libtiff closes the file descriptor, so pass in a dup.
 | |
|                 try:
 | |
|                     fp = hasattr(self.fp, "fileno") and os.dup(self.fp.fileno())
 | |
|                 except IOError:
 | |
|                     # io.BytesIO have a fileno, but returns an IOError if
 | |
|                     # it doesn't use a file descriptor.
 | |
|                     fp = False
 | |
| 
 | |
|                 # libtiff handles the fillmode for us, so 1;IR should
 | |
|                 # actually be 1;I. Including the R double reverses the
 | |
|                 # bits, so stripes of the image are reversed.  See
 | |
|                 # https://github.com/python-pillow/Pillow/issues/279
 | |
|                 if fillorder == 2:
 | |
|                     key = (
 | |
|                         self.tag.prefix, photo, format, 1,
 | |
|                         self.tag.get(BITSPERSAMPLE, (1,)),
 | |
|                         self.tag.get(EXTRASAMPLES, ())
 | |
|                         )
 | |
|                     if Image.DEBUG:
 | |
|                         print("format key:", key)
 | |
|                     # this should always work, since all the
 | |
|                     # fillorder==2 modes have a corresponding
 | |
|                     # fillorder=1 mode
 | |
|                     self.mode, rawmode = OPEN_INFO[key]
 | |
|                 # libtiff always returns the bytes in native order.
 | |
|                 # we're expecting image byte order. So, if the rawmode
 | |
|                 # contains I;16, we need to convert from native to image
 | |
|                 # byte order.
 | |
|                 if self.mode in ('I;16B', 'I;16') and 'I;16' in rawmode:
 | |
|                     rawmode = 'I;16N'
 | |
| 
 | |
|                 # Offset in the tile tuple is 0, we go from 0,0 to
 | |
|                 # w,h, and we only do this once -- eds
 | |
|                 a = (rawmode, self._compression, fp )
 | |
|                 self.tile.append(
 | |
|                     (self._compression,
 | |
|                      (0, 0, w, ysize),
 | |
|                      0, a))
 | |
|                 a = None
 | |
| 
 | |
|             else:
 | |
|                 for i in range(len(offsets)):
 | |
|                     a = self._decoder(rawmode, l, i)
 | |
|                     self.tile.append(
 | |
|                         (self._compression,
 | |
|                         (0, min(y, ysize), w, min(y+h, ysize)),
 | |
|                         offsets[i], a))
 | |
|                     if Image.DEBUG:
 | |
|                         print ("tiles: ", self.tile)
 | |
|                     y = y + h
 | |
|                     if y >= self.size[1]:
 | |
|                         x = y = 0
 | |
|                         l += 1
 | |
|                     a = None
 | |
|         elif TILEOFFSETS in self.tag:
 | |
|             # tiled image
 | |
|             w = getscalar(322)
 | |
|             h = getscalar(323)
 | |
|             a = None
 | |
|             for o in self.tag[TILEOFFSETS]:
 | |
|                 if not a:
 | |
|                     a = self._decoder(rawmode, l)
 | |
|                 # FIXME: this doesn't work if the image size
 | |
|                 # is not a multiple of the tile size...
 | |
|                 self.tile.append(
 | |
|                     (self._compression,
 | |
|                     (x, y, x+w, y+h),
 | |
|                     o, a))
 | |
|                 x = x + w
 | |
|                 if x >= self.size[0]:
 | |
|                     x, y = 0, y + h
 | |
|                     if y >= self.size[1]:
 | |
|                         x = y = 0
 | |
|                         l += 1
 | |
|                         a = None
 | |
|         else:
 | |
|             if Image.DEBUG:
 | |
|                 print("- unsupported data organization")
 | |
|             raise SyntaxError("unknown data organization")
 | |
| 
 | |
|         # fixup palette descriptor
 | |
| 
 | |
|         if self.mode == "P":
 | |
|             palette = [o8(a // 256) for a in self.tag[COLORMAP]]
 | |
|             self.palette = ImagePalette.raw("RGB;L", b"".join(palette))
 | |
| #
 | |
| # --------------------------------------------------------------------
 | |
| # Write TIFF files
 | |
| 
 | |
| # little endian is default except for image modes with explict big endian byte-order
 | |
| 
 | |
| SAVE_INFO = {
 | |
|     # mode => rawmode, byteorder, photometrics, sampleformat, bitspersample, extra
 | |
|     "1": ("1", II, 1, 1, (1,), None),
 | |
|     "L": ("L", II, 1, 1, (8,), None),
 | |
|     "LA": ("LA", II, 1, 1, (8,8), 2),
 | |
|     "P": ("P", II, 3, 1, (8,), None),
 | |
|     "PA": ("PA", II, 3, 1, (8,8), 2),
 | |
|     "I": ("I;32S", II, 1, 2, (32,), None),
 | |
|     "I;16": ("I;16", II, 1, 1, (16,), None),
 | |
|     "I;16S": ("I;16S", II, 1, 2, (16,), None),
 | |
|     "F": ("F;32F", II, 1, 3, (32,), None),
 | |
|     "RGB": ("RGB", II, 2, 1, (8,8,8), None),
 | |
|     "RGBX": ("RGBX", II, 2, 1, (8,8,8,8), 0),
 | |
|     "RGBA": ("RGBA", II, 2, 1, (8,8,8,8), 2),
 | |
|     "CMYK": ("CMYK", II, 5, 1, (8,8,8,8), None),
 | |
|     "YCbCr": ("YCbCr", II, 6, 1, (8,8,8), None),
 | |
|     "LAB": ("LAB", II, 8, 1, (8,8,8), None),
 | |
| 
 | |
|     "I;32BS": ("I;32BS", MM, 1, 2, (32,), None),
 | |
|     "I;16B": ("I;16B", MM, 1, 1, (16,), None),
 | |
|     "I;16BS": ("I;16BS", MM, 1, 2, (16,), None),
 | |
|     "F;32BF": ("F;32BF", MM, 1, 3, (32,), None),
 | |
| }
 | |
| 
 | |
| def _cvt_res(value):
 | |
|     # convert value to TIFF rational number -- (numerator, denominator)
 | |
|     if isinstance(value, collections.Sequence):
 | |
|         assert(len(value) % 2 == 0)
 | |
|         return value
 | |
|     if isinstance(value, int):
 | |
|         return (value, 1)
 | |
|     value = float(value)
 | |
|     return (int(value * 65536), 65536)
 | |
| 
 | |
| def _save(im, fp, filename):
 | |
| 
 | |
|     try:
 | |
|         rawmode, prefix, photo, format, bits, extra = SAVE_INFO[im.mode]
 | |
|     except KeyError:
 | |
|         raise IOError("cannot write mode %s as TIFF" % im.mode)
 | |
| 
 | |
|     ifd = ImageFileDirectory(prefix)
 | |
| 
 | |
|     compression = im.encoderinfo.get('compression',im.info.get('compression','raw'))
 | |
| 
 | |
|     libtiff = WRITE_LIBTIFF or compression != 'raw'
 | |
| 
 | |
|     # required for color libtiff images
 | |
|     ifd[PLANAR_CONFIGURATION] = getattr(im, '_planar_configuration', 1)
 | |
| 
 | |
|     # -- multi-page -- skip TIFF header on subsequent pages
 | |
|     if not libtiff and fp.tell() == 0:
 | |
|         # tiff header (write via IFD to get everything right)
 | |
|         # PIL always starts the first IFD at offset 8
 | |
|         fp.write(ifd.prefix + ifd.o16(42) + ifd.o32(8))
 | |
| 
 | |
|     ifd[IMAGEWIDTH] = im.size[0]
 | |
|     ifd[IMAGELENGTH] = im.size[1]
 | |
| 
 | |
|     # write any arbitrary tags passed in as an ImageFileDirectory
 | |
|     info = im.encoderinfo.get("tiffinfo",{})
 | |
|     if Image.DEBUG:
 | |
|         print ("Tiffinfo Keys: %s"% info.keys)
 | |
|     keys = list(info.keys())
 | |
|     for key in keys:
 | |
|         ifd[key] = info.get(key)
 | |
|         try:
 | |
|             ifd.tagtype[key] = info.tagtype[key]
 | |
|         except:
 | |
|             pass # might not be an IFD, Might not have populated type
 | |
| 
 | |
| 
 | |
|     # additions written by Greg Couch, gregc@cgl.ucsf.edu
 | |
|     # inspired by image-sig posting from Kevin Cazabon, kcazabon@home.com
 | |
|     if hasattr(im, 'tag'):
 | |
|         # preserve tags from original TIFF image file
 | |
|         for key in (RESOLUTION_UNIT, X_RESOLUTION, Y_RESOLUTION,
 | |
|                     IPTC_NAA_CHUNK, PHOTOSHOP_CHUNK, XMP):
 | |
|             if key in im.tag:
 | |
|                 ifd[key] = im.tag[key]
 | |
|             ifd.tagtype[key] = im.tag.tagtype.get(key, None)
 | |
| 
 | |
|         # preserve ICC profile (should also work when saving other formats
 | |
|         # which support profiles as TIFF) -- 2008-06-06 Florian Hoech
 | |
|         if "icc_profile" in im.info:
 | |
|             ifd[ICCPROFILE] = im.info["icc_profile"]
 | |
| 
 | |
|     if "description" in im.encoderinfo:
 | |
|         ifd[IMAGEDESCRIPTION] = im.encoderinfo["description"]
 | |
|     if "resolution" in im.encoderinfo:
 | |
|         ifd[X_RESOLUTION] = ifd[Y_RESOLUTION] \
 | |
|                                 = _cvt_res(im.encoderinfo["resolution"])
 | |
|     if "x resolution" in im.encoderinfo:
 | |
|         ifd[X_RESOLUTION] = _cvt_res(im.encoderinfo["x resolution"])
 | |
|     if "y resolution" in im.encoderinfo:
 | |
|         ifd[Y_RESOLUTION] = _cvt_res(im.encoderinfo["y resolution"])
 | |
|     if "resolution unit" in im.encoderinfo:
 | |
|         unit = im.encoderinfo["resolution unit"]
 | |
|         if unit == "inch":
 | |
|             ifd[RESOLUTION_UNIT] = 2
 | |
|         elif unit == "cm" or unit == "centimeter":
 | |
|             ifd[RESOLUTION_UNIT] = 3
 | |
|         else:
 | |
|             ifd[RESOLUTION_UNIT] = 1
 | |
|     if "software" in im.encoderinfo:
 | |
|         ifd[SOFTWARE] = im.encoderinfo["software"]
 | |
|     if "date time" in im.encoderinfo:
 | |
|         ifd[DATE_TIME] = im.encoderinfo["date time"]
 | |
|     if "artist" in im.encoderinfo:
 | |
|         ifd[ARTIST] = im.encoderinfo["artist"]
 | |
|     if "copyright" in im.encoderinfo:
 | |
|         ifd[COPYRIGHT] = im.encoderinfo["copyright"]
 | |
| 
 | |
|     dpi = im.encoderinfo.get("dpi")
 | |
|     if dpi:
 | |
|         ifd[RESOLUTION_UNIT] = 2
 | |
|         ifd[X_RESOLUTION] = _cvt_res(dpi[0])
 | |
|         ifd[Y_RESOLUTION] = _cvt_res(dpi[1])
 | |
| 
 | |
|     if bits != (1,):
 | |
|         ifd[BITSPERSAMPLE] = bits
 | |
|         if len(bits) != 1:
 | |
|             ifd[SAMPLESPERPIXEL] = len(bits)
 | |
|     if extra is not None:
 | |
|         ifd[EXTRASAMPLES] = extra
 | |
|     if format != 1:
 | |
|         ifd[SAMPLEFORMAT] = format
 | |
| 
 | |
|     ifd[PHOTOMETRIC_INTERPRETATION] = photo
 | |
| 
 | |
|     if im.mode == "P":
 | |
|         lut = im.im.getpalette("RGB", "RGB;L")
 | |
|         ifd[COLORMAP] = tuple(i8(v) * 256 for v in lut)
 | |
| 
 | |
|     # data orientation
 | |
|     stride = len(bits) * ((im.size[0]*bits[0]+7)//8)
 | |
|     ifd[ROWSPERSTRIP] = im.size[1]
 | |
|     ifd[STRIPBYTECOUNTS] = stride * im.size[1]
 | |
|     ifd[STRIPOFFSETS] = 0 # this is adjusted by IFD writer
 | |
|     ifd[COMPRESSION] = COMPRESSION_INFO_REV.get(compression,1) # no compression by default
 | |
| 
 | |
|     if libtiff:
 | |
|         if Image.DEBUG:
 | |
|             print ("Saving using libtiff encoder")
 | |
|             print (ifd.items())
 | |
|         _fp = 0
 | |
|         if hasattr(fp, "fileno"):
 | |
|             fp.seek(0)
 | |
|             _fp = os.dup(fp.fileno())
 | |
| 
 | |
|         blocklist = [STRIPOFFSETS, STRIPBYTECOUNTS, ROWSPERSTRIP, ICCPROFILE] # ICC Profile crashes.
 | |
|         atts={}
 | |
|         # bits per sample is a single short in the tiff directory, not a list.
 | |
|         atts[BITSPERSAMPLE] = bits[0]
 | |
|         # Merge the ones that we have with (optional) more bits from
 | |
|         # the original file, e.g x,y resolution so that we can
 | |
|         # save(load('')) == original file.
 | |
|         for k,v in itertools.chain(ifd.items(), getattr(im, 'ifd', {}).items()):
 | |
|             if k not in atts and k not in blocklist:
 | |
|                 if type(v[0]) == tuple and len(v) > 1:
 | |
|                     # A tuple of more than one rational tuples
 | |
|                     # flatten to floats, following tiffcp.c->cpTag->TIFF_RATIONAL
 | |
|                     atts[k] = [float(elt[0])/float(elt[1]) for elt in v]
 | |
|                     continue
 | |
|                 if type(v[0]) == tuple and len(v) == 1:
 | |
|                     # A tuple of one rational tuples
 | |
|                     # flatten to floats, following tiffcp.c->cpTag->TIFF_RATIONAL
 | |
|                     atts[k] = float(v[0][0])/float(v[0][1])
 | |
|                     continue
 | |
|                 if type(v) == tuple and len(v) > 2:
 | |
|                     # List of ints?
 | |
|                     if type(v[0]) in (int, float):
 | |
|                         atts[k] = list(v)
 | |
|                     continue
 | |
|                 if type(v) == tuple and len(v) == 2:
 | |
|                     # one rational tuple
 | |
|                     # flatten to float, following tiffcp.c->cpTag->TIFF_RATIONAL
 | |
|                     atts[k] = float(v[0])/float(v[1])
 | |
|                     continue
 | |
|                 if type(v) == tuple and len(v) == 1:
 | |
|                     v = v[0]
 | |
|                     # drop through
 | |
|                 if isStringType(v):
 | |
|                     atts[k] = bytes(v.encode('ascii', 'replace')) + b"\0"
 | |
|                     continue
 | |
|                 else:
 | |
|                     # int or similar
 | |
|                     atts[k] = v
 | |
| 
 | |
|         if Image.DEBUG:
 | |
|             print (atts)
 | |
| 
 | |
|         # libtiff always expects the bytes in native order.
 | |
|         # we're storing image byte order. So, if the rawmode
 | |
|         # contains I;16, we need to convert from native to image
 | |
|         # byte order.
 | |
|         if im.mode in ('I;16B', 'I;16'):
 | |
|             rawmode = 'I;16N'
 | |
| 
 | |
|         a = (rawmode, compression, _fp, filename, atts)
 | |
|         # print (im.mode, compression, a, im.encoderconfig)
 | |
|         e = Image._getencoder(im.mode, 'libtiff', a, im.encoderconfig)
 | |
|         e.setimage(im.im, (0,0)+im.size)
 | |
|         while True:
 | |
|             l, s, d = e.encode(16*1024) # undone, change to self.decodermaxblock
 | |
|             if not _fp:
 | |
|                 fp.write(d)
 | |
|             if s:
 | |
|                 break
 | |
|         if s < 0:
 | |
|             raise IOError("encoder error %d when writing image file" % s)
 | |
| 
 | |
|     else:
 | |
|         offset = ifd.save(fp)
 | |
| 
 | |
|         ImageFile._save(im, fp, [
 | |
|             ("raw", (0,0)+im.size, offset, (rawmode, stride, 1))
 | |
|             ])
 | |
| 
 | |
| 
 | |
|     # -- helper for multi-page save --
 | |
|     if "_debug_multipage" in im.encoderinfo:
 | |
|         #just to access o32 and o16 (using correct byte order)
 | |
|         im._debug_multipage = ifd
 | |
| 
 | |
| #
 | |
| # --------------------------------------------------------------------
 | |
| # Register
 | |
| 
 | |
| Image.register_open("TIFF", TiffImageFile, _accept)
 | |
| Image.register_save("TIFF", _save)
 | |
| 
 | |
| Image.register_extension("TIFF", ".tif")
 | |
| Image.register_extension("TIFF", ".tiff")
 | |
| 
 | |
| Image.register_mime("TIFF", "image/tiff")
 |