# 
 | 
# 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. 
 | 
# 
 | 
  
 | 
__version__ = "1.3.5" 
 | 
  
 | 
import Image, ImageFile 
 | 
import ImagePalette 
 | 
  
 | 
import array, string, sys 
 | 
  
 | 
II = "II" # little-endian (intel-style) 
 | 
MM = "MM" # big-endian (motorola-style) 
 | 
  
 | 
try: 
 | 
    if sys.byteorder == "little": 
 | 
        native_prefix = II 
 | 
    else: 
 | 
        native_prefix = MM 
 | 
except AttributeError: 
 | 
    if ord(array.array("i",[1]).tostring()[0]): 
 | 
        native_prefix = II 
 | 
    else: 
 | 
        native_prefix = MM 
 | 
  
 | 
# 
 | 
# -------------------------------------------------------------------- 
 | 
# Read TIFF files 
 | 
  
 | 
def il16(c,o=0): 
 | 
    return ord(c[o]) + (ord(c[o+1])<<8) 
 | 
def il32(c,o=0): 
 | 
    return ord(c[o]) + (ord(c[o+1])<<8) + (ord(c[o+2])<<16) + (ord(c[o+3])<<24) 
 | 
def ol16(i): 
 | 
    return chr(i&255) + chr(i>>8&255) 
 | 
def ol32(i): 
 | 
    return chr(i&255) + chr(i>>8&255) + chr(i>>16&255) + chr(i>>24&255) 
 | 
  
 | 
def ib16(c,o=0): 
 | 
    return ord(c[o+1]) + (ord(c[o])<<8) 
 | 
def ib32(c,o=0): 
 | 
    return ord(c[o+3]) + (ord(c[o+2])<<8) + (ord(c[o+1])<<16) + (ord(c[o])<<24) 
 | 
def ob16(i): 
 | 
    return chr(i>>8&255) + chr(i&255) 
 | 
def ob32(i): 
 | 
    return chr(i>>24&255) + chr(i>>16&255) + chr(i>>8&255) + chr(i&255) 
 | 
  
 | 
# 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 
 | 
  
 | 
COMPRESSION_INFO = { 
 | 
    # Compression => pil compression name 
 | 
    1: "raw", 
 | 
    2: "tiff_ccitt", 
 | 
    3: "group3", 
 | 
    4: "group4", 
 | 
    5: "tiff_lzw", 
 | 
    6: "tiff_jpeg", # obsolete 
 | 
    7: "jpeg", 
 | 
    32771: "tiff_raw_16", # 16-bit padding 
 | 
    32773: "packbits" 
 | 
} 
 | 
  
 | 
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, 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, (16,), ()): ("I;16", "I;16"), 
 | 
    (II, 1, 2, 1, (16,), ()): ("I;16S", "I;16S"), 
 | 
    (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), (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;32BF", "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 = ["MM\000\052", "II\052\000", "II\xBC\000"] 
 | 
  
 | 
def _accept(prefix): 
 | 
    return prefix[:4] in PREFIXES 
 | 
  
 | 
## 
 | 
# Wrapper for TIFF IFDs. 
 | 
  
 | 
class ImageFileDirectory: 
 | 
  
 | 
    # represents a TIFF tag directory.  to speed things up, 
 | 
    # we don't decode tags unless they're asked for. 
 | 
  
 | 
    def __init__(self, prefix): 
 | 
        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): 
 | 
        self.tags = {} 
 | 
        self.tagdata = {} 
 | 
        self.tagtype = {} # added 2008-06-05 by Florian Hoech 
 | 
        self.next = None 
 | 
  
 | 
    # dictionary API (sort of) 
 | 
  
 | 
    def keys(self): 
 | 
        return self.tagdata.keys() + self.tags.keys() 
 | 
  
 | 
    def items(self): 
 | 
        items = self.tags.items() 
 | 
        for tag in self.tagdata.keys(): 
 | 
            items.append((tag, self[tag])) 
 | 
        return items 
 | 
  
 | 
    def __len__(self): 
 | 
        return len(self.tagdata) + len(self.tags) 
 | 
  
 | 
    def __getitem__(self, tag): 
 | 
        try: 
 | 
            return self.tags[tag] 
 | 
        except KeyError: 
 | 
            type, data = self.tagdata[tag] # unpack on the fly 
 | 
            size, handler = self.load_dispatch[type] 
 | 
            self.tags[tag] = data = handler(self, data) 
 | 
            del self.tagdata[tag] 
 | 
            return data 
 | 
  
 | 
    def get(self, tag, default=None): 
 | 
        try: 
 | 
            return self[tag] 
 | 
        except KeyError: 
 | 
            return default 
 | 
  
 | 
    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 has_key(self, tag): 
 | 
        return self.tags.has_key(tag) or self.tagdata.has_key(tag) 
 | 
  
 | 
    def __setitem__(self, tag, value): 
 | 
        if type(value) is not type(()): 
 | 
            value = (value,) 
 | 
        self.tags[tag] = value 
 | 
  
 | 
    # load primitives 
 | 
  
 | 
    load_dispatch = {} 
 | 
  
 | 
    def load_byte(self, data): 
 | 
        l = [] 
 | 
        for i in range(len(data)): 
 | 
            l.append(ord(data[i])) 
 | 
        return tuple(l) 
 | 
    load_dispatch[1] = (1, load_byte) 
 | 
  
 | 
    def load_string(self, data): 
 | 
        if data[-1:] == '\0': 
 | 
            data = data[:-1] 
 | 
        return data 
 | 
    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: 
 | 
                import TiffTags 
 | 
                tagname = TiffTags.TAGS.get(tag, "unknown") 
 | 
                typname = TiffTags.TYPES.get(typ, "unknown") 
 | 
                print "tag: %s (%d)" % (tagname, tag), 
 | 
                print "- type: %s (%d)" % (typname, typ), 
 | 
  
 | 
            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: 
 | 
                raise IOError, "not enough data" 
 | 
  
 | 
            self.tagdata[tag] = typ, 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 = self.tags.items() 
 | 
        tags.sort() 
 | 
  
 | 
        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 self.tagtype.has_key(tag): 
 | 
                typ = self.tagtype[tag] 
 | 
  
 | 
            if typ == 1: 
 | 
                # byte data 
 | 
                data = value = string.join(map(chr, value), "") 
 | 
            elif typ == 7: 
 | 
                # untyped data 
 | 
                data = value = string.join(value, "") 
 | 
            elif type(value[0]) is type(""): 
 | 
                # string data 
 | 
                typ = 2 
 | 
                data = value = string.join(value, "\0") + "\0" 
 | 
            else: 
 | 
                # integer data 
 | 
                if tag == STRIPOFFSETS: 
 | 
                    stripoffsets = len(directory) 
 | 
                    typ = 4 # to avoid catch-22 
 | 
                elif tag in (X_RESOLUTION, Y_RESOLUTION): 
 | 
                    # identify rational data fields 
 | 
                    typ = 5 
 | 
                elif not typ: 
 | 
                    typ = 3 
 | 
                    for v in value: 
 | 
                        if v >= 65536: 
 | 
                            typ = 4 
 | 
                if typ == 3: 
 | 
                    data = string.join(map(o16, value), "") 
 | 
                else: 
 | 
                    data = string.join(map(o32, value), "") 
 | 
  
 | 
            if Image.DEBUG: 
 | 
                import TiffTags 
 | 
                tagname = TiffTags.TAGS.get(tag, "unknown") 
 | 
                typname = TiffTags.TYPES.get(typ, "unknown") 
 | 
                print "save: %s (%d)" % (tagname, tag), 
 | 
                print "- type: %s (%d)" % (typname, typ), 
 | 
                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, "")) 
 | 
            elif len(data) < 4: 
 | 
                append((tag, typ, len(value), data + (4-len(data))*"\0", "")) 
 | 
            else: 
 | 
                count = len(value) 
 | 
                if typ == 5: 
 | 
                    count = count / 2        # adjust for rational data field 
 | 
                append((tag, typ, count, o32(offset), data)) 
 | 
                offset = offset + len(data) 
 | 
                if offset & 1: 
 | 
                    offset = 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("\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("\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 
 | 
  
 | 
        # 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 = self.__frame + 1 
 | 
        self._setup() 
 | 
  
 | 
    def _tell(self): 
 | 
  
 | 
        return self.__frame 
 | 
  
 | 
    def _decoder(self, rawmode, layer): 
 | 
        "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 self.tag.has_key(JPEGTABLES): 
 | 
                # Hack to handle abbreviated JPEG headers 
 | 
                self.tile_prefix = self.tag[JPEGTABLES] 
 | 
        elif compression == "packbits": 
 | 
            args = rawmode 
 | 
        elif compression == "tiff_lzw": 
 | 
            args = rawmode 
 | 
            if self.tag.has_key(317): 
 | 
                # Section 14: Differencing Predictor 
 | 
                self.decoderconfig = (self.tag[PREDICTOR][0],) 
 | 
  
 | 
        if self.tag.has_key(ICCPROFILE): 
 | 
            self.info['icc_profile'] = self.tag[ICCPROFILE] 
 | 
  
 | 
        return args 
 | 
  
 | 
    def _setup(self): 
 | 
        "Setup this image object based on current tags" 
 | 
  
 | 
        if self.tag.has_key(0xBC01): 
 | 
            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 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 self.tag.has_key(STRIPOFFSETS): 
 | 
            # striped image 
 | 
            h = getscalar(ROWSPERSTRIP, ysize) 
 | 
            w = self.size[0] 
 | 
            a = None 
 | 
            for o in self.tag[STRIPOFFSETS]: 
 | 
                if not a: 
 | 
                    a = self._decoder(rawmode, l) 
 | 
                self.tile.append( 
 | 
                    (self._compression, 
 | 
                    (0, min(y, ysize), w, min(y+h, ysize)), 
 | 
                    o, a)) 
 | 
                y = y + h 
 | 
                if y >= self.size[1]: 
 | 
                    x = y = 0 
 | 
                    l = l + 1 
 | 
                    a = None 
 | 
        elif self.tag.has_key(TILEOFFSETS): 
 | 
            # 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 = 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 = map(lambda a: chr(a / 256), self.tag[COLORMAP]) 
 | 
            self.palette = ImagePalette.raw("RGB;L", string.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 type(value) in (type([]), type(())): 
 | 
        assert(len(value) % 2 == 0) 
 | 
        return value 
 | 
    if type(value) == type(1): 
 | 
        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) 
 | 
  
 | 
    # -- multi-page -- skip TIFF header on subsequent pages 
 | 
    if 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] 
 | 
  
 | 
    # 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): 
 | 
            if im.tag.tagdata.has_key(key): 
 | 
                ifd[key] = im.tag.tagdata.get(key) 
 | 
        # preserve some more tags from original TIFF image file 
 | 
        # -- 2008-06-06 Florian Hoech 
 | 
        ifd.tagtype = im.tag.tagtype 
 | 
        for key in (IPTC_NAA_CHUNK, PHOTOSHOP_CHUNK, XMP): 
 | 
            if im.tag.has_key(key): 
 | 
                ifd[key] = im.tag[key] 
 | 
        # preserve ICC profile (should also work when saving other formats 
 | 
        # which support profiles as TIFF) -- 2008-06-06 Florian Hoech 
 | 
        if im.info.has_key("icc_profile"): 
 | 
            ifd[ICCPROFILE] = im.info["icc_profile"] 
 | 
    if im.encoderinfo.has_key("description"): 
 | 
        ifd[IMAGEDESCRIPTION] = im.encoderinfo["description"] 
 | 
    if im.encoderinfo.has_key("resolution"): 
 | 
        ifd[X_RESOLUTION] = ifd[Y_RESOLUTION] \ 
 | 
                                = _cvt_res(im.encoderinfo["resolution"]) 
 | 
    if im.encoderinfo.has_key("x resolution"): 
 | 
        ifd[X_RESOLUTION] = _cvt_res(im.encoderinfo["x resolution"]) 
 | 
    if im.encoderinfo.has_key("y resolution"): 
 | 
        ifd[Y_RESOLUTION] = _cvt_res(im.encoderinfo["y resolution"]) 
 | 
    if im.encoderinfo.has_key("resolution unit"): 
 | 
        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 im.encoderinfo.has_key("software"): 
 | 
        ifd[SOFTWARE] = im.encoderinfo["software"] 
 | 
    if im.encoderinfo.has_key("date time"): 
 | 
        ifd[DATE_TIME] = im.encoderinfo["date time"] 
 | 
    if im.encoderinfo.has_key("artist"): 
 | 
        ifd[ARTIST] = im.encoderinfo["artist"] 
 | 
    if im.encoderinfo.has_key("copyright"): 
 | 
        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(map(lambda v: ord(v) * 256, 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] = 1 # no compression 
 | 
  
 | 
    offset = ifd.save(fp) 
 | 
  
 | 
    ImageFile._save(im, fp, [ 
 | 
        ("raw", (0,0)+im.size, offset, (rawmode, stride, 1)) 
 | 
        ]) 
 | 
  
 | 
  
 | 
    # -- helper for multi-page save -- 
 | 
    if im.encoderinfo.has_key("_debug_multipage"): 
 | 
        #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") 
 |