diff options
Diffstat (limited to 'PIL/TiffImagePlugin.py')
-rw-r--r-- | PIL/TiffImagePlugin.py | 763 |
1 files changed, 0 insertions, 763 deletions
diff --git a/PIL/TiffImagePlugin.py b/PIL/TiffImagePlugin.py deleted file mode 100644 index 178066c..0000000 --- a/PIL/TiffImagePlugin.py +++ /dev/null @@ -1,763 +0,0 @@ -# -# The Python Imaging Library. -# $Id: TiffImagePlugin.py 2803 2006-07-31 19:18:57Z fredrik $ -# -# 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 - -try: - if sys.byteorder == "little": - byteorder = "II" - else: - byteorder = "MM" -except AttributeError: - if ord(array.array("i",[1]).tostring()[0]): - byteorder = "II" - else: - byteorder = "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) - -# 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 -EXTRASAMPLES = 338 -SAMPLEFORMAT = 339 -JPEGTABLES = 347 -COPYRIGHT = 33432 -IPTC_NAA_CHUNK = 33723 # newsphoto properties -PHOTOSHOP_CHUNK = 34377 # photoshop properties - -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 = { - # (PhotoInterpretation, SampleFormat, FillOrder, BitsPerSample, - # ExtraSamples) => mode, rawmode - (0, 1, 1, (1,), ()): ("1", "1;I"), - (0, 1, 2, (1,), ()): ("1", "1;IR"), - (0, 1, 1, (8,), ()): ("L", "L;I"), - (0, 1, 2, (8,), ()): ("L", "L;IR"), - (1, 1, 1, (1,), ()): ("1", "1"), - (1, 1, 2, (1,), ()): ("1", "1;R"), - (1, 1, 1, (8,), ()): ("L", "L"), - (1, 1, 1, (8,8), (2,)): ("LA", "LA"), - (1, 1, 2, (8,), ()): ("L", "L;R"), - (1, 1, 1, (16,), ()): ("I;16", "I;16"), - (1, 2, 1, (16,), ()): ("I;16S", "I;16S"), - (1, 2, 1, (32,), ()): ("I", "I;32S"), - (1, 3, 1, (32,), ()): ("F", "F;32F"), - (2, 1, 1, (8,8,8), ()): ("RGB", "RGB"), - (2, 1, 2, (8,8,8), ()): ("RGB", "RGB;R"), - (2, 1, 1, (8,8,8,8), (0,)): ("RGBX", "RGBX"), - (2, 1, 1, (8,8,8,8), (1,)): ("RGBA", "RGBa"), - (2, 1, 1, (8,8,8,8), (2,)): ("RGBA", "RGBA"), - (2, 1, 1, (8,8,8,8), (999,)): ("RGBA", "RGBA"), # corel draw 10 - (3, 1, 1, (1,), ()): ("P", "P;1"), - (3, 1, 2, (1,), ()): ("P", "P;1R"), - (3, 1, 1, (2,), ()): ("P", "P;2"), - (3, 1, 2, (2,), ()): ("P", "P;2R"), - (3, 1, 1, (4,), ()): ("P", "P;4"), - (3, 1, 2, (4,), ()): ("P", "P;4R"), - (3, 1, 1, (8,), ()): ("P", "P"), - (3, 1, 1, (8,8), (2,)): ("PA", "PA"), - (3, 1, 2, (8,), ()): ("P", "P;R"), - (5, 1, 1, (8,8,8,8), ()): ("CMYK", "CMYK"), - (6, 1, 1, (8,8,8), ()): ("YCbCr", "YCbCr"), - (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="II"): - self.prefix = prefix[:2] - if self.prefix == "MM": - self.i16, self.i32 = ib16, ib32 - # FIXME: save doesn't yet support big-endian mode... - 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.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 != byteorder: - a.byteswap() - return tuple(a) - load_dispatch[11] = (4, load_float) - - def load_double(self, data): - a = array.array("d", data) - if self.prefix != byteorder: - 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 - - if Image.DEBUG: - if tag in (COLORMAP, IPTC_NAA_CHUNK, PHOTOSHOP_CHUNK): - 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: - - if Image.DEBUG: - import TiffTags - tagname = TiffTags.TAGS.get(tag, "unknown") - print "save: %s (%d)" % (tagname, tag), - print "- value:", value - - if 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 - else: - 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), "") - - # 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) - 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],) - - 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 = ( - 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 - - xdpi = getscalar(X_RESOLUTION, (1, 1)) - ydpi = getscalar(Y_RESOLUTION, (1, 1)) - - if xdpi and ydpi and getscalar(RESOLUTION_UNIT, 1) == 1: - xdpi = xdpi[0] / (xdpi[1] or 1) - ydpi = ydpi[0] / (ydpi[1] or 1) - self.info["dpi"] = xdpi, ydpi - - # 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(324): - # tiled image - w = getscalar(322) - h = getscalar(323) - a = None - for o in self.tag[324]: - 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 - -SAVE_INFO = { - # mode => rawmode, photometrics, sampleformat, bitspersample, extra - "1": ("1", 1, 1, (1,), None), - "L": ("L", 1, 1, (8,), None), - "LA": ("LA", 1, 1, (8,8), 2), - "P": ("P", 3, 1, (8,), None), - "PA": ("PA", 3, 1, (8,8), 2), - "I": ("I;32S", 1, 2, (32,), None), - "I;16": ("I;16", 1, 1, (16,), None), - "I;16S": ("I;16S", 1, 2, (16,), None), - "F": ("F;32F", 1, 3, (32,), None), - "RGB": ("RGB", 2, 1, (8,8,8), None), - "RGBX": ("RGBX", 2, 1, (8,8,8,8), 0), - "RGBA": ("RGBA", 2, 1, (8,8,8,8), 2), - "CMYK": ("CMYK", 5, 1, (8,8,8,8), None), - "YCbCr": ("YCbCr", 6, 1, (8,8,8), None), - "LAB": ("LAB", 8, 1, (8,8,8), 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, photo, format, bits, extra = SAVE_INFO[im.mode] - except KeyError: - raise IOError, "cannot write mode %s as TIFF" % im.mode - - ifd = ImageFileDirectory() - - # tiff header (write via IFD to get everything right) - 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) - 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] = 1 - 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)) - ]) - -# -# -------------------------------------------------------------------- -# 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") |