diff options
Diffstat (limited to 'PIL/ImageFile.py')
-rw-r--r-- | PIL/ImageFile.py | 520 |
1 files changed, 520 insertions, 0 deletions
diff --git a/PIL/ImageFile.py b/PIL/ImageFile.py new file mode 100644 index 0000000..d51aff3 --- /dev/null +++ b/PIL/ImageFile.py @@ -0,0 +1,520 @@ +# +# The Python Imaging Library. +# $Id: ImageFile.py 2930 2006-12-02 13:50:40Z fredrik $ +# +# base class for image file handlers +# +# history: +# 1995-09-09 fl Created +# 1996-03-11 fl Fixed load mechanism. +# 1996-04-15 fl Added pcx/xbm decoders. +# 1996-04-30 fl Added encoders. +# 1996-12-14 fl Added load helpers +# 1997-01-11 fl Use encode_to_file where possible +# 1997-08-27 fl Flush output in _save +# 1998-03-05 fl Use memory mapping for some modes +# 1999-02-04 fl Use memory mapping also for "I;16" and "I;16B" +# 1999-05-31 fl Added image parser +# 2000-10-12 fl Set readonly flag on memory-mapped images +# 2002-03-20 fl Use better messages for common decoder errors +# 2003-04-21 fl Fall back on mmap/map_buffer if map is not available +# 2003-10-30 fl Added StubImageFile class +# 2004-02-25 fl Made incremental parser more robust +# +# Copyright (c) 1997-2004 by Secret Labs AB +# Copyright (c) 1995-2004 by Fredrik Lundh +# +# See the README file for information on usage and redistribution. +# + +import Image +import traceback, sys, string, os + +MAXBLOCK = 65536 + +SAFEBLOCK = 1024*1024 + +ERRORS = { + -1: "image buffer overrun error", + -2: "decoding error", + -3: "unknown error", + -8: "bad configuration", + -9: "out of memory error" +} + +# +# -------------------------------------------------------------------- +# Helpers + +def _tilesort(t1, t2): + # sort on offset + return cmp(t1[2], t2[2]) + +# +# -------------------------------------------------------------------- +# ImageFile base class + +## +# Base class for image file handlers. + +class ImageFile(Image.Image): + "Base class for image file format handlers." + + def __init__(self, fp=None, filename=None): + Image.Image.__init__(self) + + self.tile = None + self.readonly = 1 # until we know better + + self.decoderconfig = () + self.decodermaxblock = MAXBLOCK + + if Image.isStringType(fp): + # filename + self.fp = open(fp, "rb") + self.filename = fp + else: + # stream + self.fp = fp + self.filename = filename + + try: + self._open() + except IndexError, v: # end of data + if Image.DEBUG > 1: + traceback.print_exc() + raise SyntaxError, v + except TypeError, v: # end of data (ord) + if Image.DEBUG > 1: + traceback.print_exc() + raise SyntaxError, v + except KeyError, v: # unsupported mode + if Image.DEBUG > 1: + traceback.print_exc() + raise SyntaxError, v + except EOFError, v: # got header but not the first frame + if Image.DEBUG > 1: + traceback.print_exc() + raise SyntaxError, v + + if not self.mode or self.size[0] <= 0: + raise SyntaxError, "not identified by this driver" + + def draft(self, mode, size): + "Set draft mode" + + pass + + def verify(self): + "Check file integrity" + + # raise exception if something's wrong. must be called + # directly after open, and closes file when finished. + self.fp = None + + def load(self): + "Load image data based on tile list" + + pixel = Image.Image.load(self) + + if self.tile is None: + raise IOError("cannot load this image") + if not self.tile: + return pixel + + self.map = None + + readonly = 0 + + if self.filename and len(self.tile) == 1: + # try memory mapping + d, e, o, a = self.tile[0] + if d == "raw" and a[0] == self.mode and a[0] in Image._MAPMODES: + try: + if hasattr(Image.core, "map"): + # use built-in mapper + self.map = Image.core.map(self.filename) + self.map.seek(o) + self.im = self.map.readimage( + self.mode, self.size, a[1], a[2] + ) + else: + # use mmap, if possible + import mmap + file = open(self.filename, "r+") + size = os.path.getsize(self.filename) + # FIXME: on Unix, use PROT_READ etc + self.map = mmap.mmap(file.fileno(), size) + self.im = Image.core.map_buffer( + self.map, self.size, d, e, o, a + ) + readonly = 1 + except (AttributeError, EnvironmentError, ImportError): + self.map = None + + self.load_prepare() + + # look for read/seek overrides + try: + read = self.load_read + except AttributeError: + read = self.fp.read + + try: + seek = self.load_seek + except AttributeError: + seek = self.fp.seek + + if not self.map: + + # sort tiles in file order + self.tile.sort(_tilesort) + + try: + # FIXME: This is a hack to handle TIFF's JpegTables tag. + prefix = self.tile_prefix + except AttributeError: + prefix = "" + + for d, e, o, a in self.tile: + d = Image._getdecoder(self.mode, d, a, self.decoderconfig) + seek(o) + try: + d.setimage(self.im, e) + except ValueError: + continue + b = prefix + t = len(b) + while 1: + s = read(self.decodermaxblock) + if not s: + self.tile = [] + raise IOError("image file is truncated (%d bytes not processed)" % len(b)) + b = b + s + n, e = d.decode(b) + if n < 0: + break + b = b[n:] + t = t + n + + self.tile = [] + self.readonly = readonly + + self.fp = None # might be shared + + if not self.map and e < 0: + error = ERRORS.get(e, "decoder error %d" % e) + raise IOError(error + " when reading image file") + + # post processing + if hasattr(self, "tile_post_rotate"): + # FIXME: This is a hack to handle rotated PCD's + self.im = self.im.rotate(self.tile_post_rotate) + self.size = self.im.size + + self.load_end() + + return Image.Image.load(self) + + def load_prepare(self): + # create image memory if necessary + if not self.im or\ + self.im.mode != self.mode or self.im.size != self.size: + self.im = Image.core.new(self.mode, self.size) + # create palette (optional) + if self.mode == "P": + Image.Image.load(self) + + def load_end(self): + # may be overridden + pass + + # may be defined for contained formats + # def load_seek(self, pos): + # pass + + # may be defined for blocked formats (e.g. PNG) + # def load_read(self, bytes): + # pass + +## +# Base class for stub image loaders. +# <p> +# A stub loader is an image loader that can identify files of a +# certain format, but relies on external code to load the file. + +class StubImageFile(ImageFile): + "Base class for stub image loaders." + + def _open(self): + raise NotImplementedError( + "StubImageFile subclass must implement _open" + ) + + def load(self): + loader = self._load() + if loader is None: + raise IOError("cannot find loader for this %s file" % self.format) + image = loader.load(self) + assert image is not None + # become the other object (!) + self.__class__ = image.__class__ + self.__dict__ = image.__dict__ + + ## + # (Hook) Find actual image loader. + + def _load(self): + raise NotImplementedError( + "StubImageFile subclass must implement _load" + ) + +## +# (Internal) Support class for the <b>Parser</b> file. + +class _ParserFile: + # parser support class. + + def __init__(self, data): + self.data = data + self.offset = 0 + + def close(self): + self.data = self.offset = None + + def tell(self): + return self.offset + + def seek(self, offset, whence=0): + if whence == 0: + self.offset = offset + elif whence == 1: + self.offset = self.offset + offset + else: + # force error in Image.open + raise IOError("illegal argument to seek") + + def read(self, bytes=0): + pos = self.offset + if bytes: + data = self.data[pos:pos+bytes] + else: + data = self.data[pos:] + self.offset = pos + len(data) + return data + + def readline(self): + # FIXME: this is slow! + s = "" + while 1: + c = self.read(1) + if not c: + break + s = s + c + if c == "\n": + break + return s + +## +# Incremental image parser. This class implements the standard +# feed/close consumer interface. + +class Parser: + + incremental = None + image = None + data = None + decoder = None + finished = 0 + + ## + # (Consumer) Reset the parser. Note that you can only call this + # method immediately after you've created a parser; parser + # instances cannot be reused. + + def reset(self): + assert self.data is None, "cannot reuse parsers" + + ## + # (Consumer) Feed data to the parser. + # + # @param data A string buffer. + # @exception IOError If the parser failed to parse the image file. + + def feed(self, data): + # collect data + + if self.finished: + return + + if self.data is None: + self.data = data + else: + self.data = self.data + data + + # parse what we have + if self.decoder: + + if self.offset > 0: + # skip header + skip = min(len(self.data), self.offset) + self.data = self.data[skip:] + self.offset = self.offset - skip + if self.offset > 0 or not self.data: + return + + n, e = self.decoder.decode(self.data) + + if n < 0: + # end of stream + self.data = None + self.finished = 1 + if e < 0: + # decoding error + self.image = None + error = ERRORS.get(e, "decoder error %d" % e) + raise IOError(error + " when reading image file") + else: + # end of image + return + self.data = self.data[n:] + + elif self.image: + + # if we end up here with no decoder, this file cannot + # be incrementally parsed. wait until we've gotten all + # available data + pass + + else: + + # attempt to open this file + try: + try: + fp = _ParserFile(self.data) + im = Image.open(fp) + finally: + fp.close() # explicitly close the virtual file + except IOError: + pass # not enough data + else: + flag = hasattr(im, "load_seek") or hasattr(im, "load_read") + if flag or len(im.tile) != 1: + # custom load code, or multiple tiles + self.decode = None + else: + # initialize decoder + im.load_prepare() + d, e, o, a = im.tile[0] + im.tile = [] + self.decoder = Image._getdecoder( + im.mode, d, a, im.decoderconfig + ) + self.decoder.setimage(im.im, e) + + # calculate decoder offset + self.offset = o + if self.offset <= len(self.data): + self.data = self.data[self.offset:] + self.offset = 0 + + self.image = im + + ## + # (Consumer) Close the stream. + # + # @return An image object. + # @exception IOError If the parser failed to parse the image file. + + def close(self): + # finish decoding + if self.decoder: + # get rid of what's left in the buffers + self.feed("") + self.data = self.decoder = None + if not self.finished: + raise IOError("image was incomplete") + if not self.image: + raise IOError("cannot parse this image") + if self.data: + # incremental parsing not possible; reopen the file + # not that we have all data + try: + fp = _ParserFile(self.data) + self.image = Image.open(fp) + finally: + fp.close() # explicitly close the virtual file + return self.image + +# -------------------------------------------------------------------- + +## +# (Helper) Save image body to file. +# +# @param im Image object. +# @param fp File object. +# @param tile Tile list. + +def _save(im, fp, tile): + "Helper to save image based on tile list" + + im.load() + if not hasattr(im, "encoderconfig"): + im.encoderconfig = () + tile.sort(_tilesort) + # FIXME: make MAXBLOCK a configuration parameter + bufsize = max(MAXBLOCK, im.size[0] * 4) # see RawEncode.c + try: + fh = fp.fileno() + fp.flush() + except AttributeError: + # compress to Python file-compatible object + for e, b, o, a in tile: + e = Image._getencoder(im.mode, e, a, im.encoderconfig) + if o > 0: + fp.seek(o, 0) + e.setimage(im.im, b) + while 1: + l, s, d = e.encode(bufsize) + fp.write(d) + if s: + break + if s < 0: + raise IOError("encoder error %d when writing image file" % s) + else: + # slight speedup: compress to real file object + for e, b, o, a in tile: + e = Image._getencoder(im.mode, e, a, im.encoderconfig) + if o > 0: + fp.seek(o, 0) + e.setimage(im.im, b) + s = e.encode_to_file(fh, bufsize) + if s < 0: + raise IOError("encoder error %d when writing image file" % s) + try: + fp.flush() + except: pass + + +## +# Reads large blocks in a safe way. Unlike fp.read(n), this function +# doesn't trust the user. If the requested size is larger than +# SAFEBLOCK, the file is read block by block. +# +# @param fp File handle. Must implement a <b>read</b> method. +# @param size Number of bytes to read. +# @return A string containing up to <i>size</i> bytes of data. + +def _safe_read(fp, size): + if size <= 0: + return "" + if size <= SAFEBLOCK: + return fp.read(size) + data = [] + while size > 0: + block = fp.read(min(size, SAFEBLOCK)) + if not block: + break + data.append(block) + size = size - len(block) + return string.join(data, "") |