| # | 
| # The Python Imaging Library. | 
| # $Id$ | 
| # | 
| # 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, 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" | 
| } | 
|   | 
| def raise_ioerror(error): | 
|     try: | 
|         message = Image.core.getcodecstatus(error) | 
|     except AttributeError: | 
|         message = ERRORS.get(error) | 
|     if not message: | 
|         message = "decoder error %d" % error | 
|     raise IOError(message + " when reading image file") | 
|   | 
| # | 
| # -------------------------------------------------------------------- | 
| # 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: | 
|             raise_ioerror(e) | 
|   | 
|         # 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 | 
|                     raise_ioerror(e) | 
|                 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: | 
|                 self.image.load() | 
|                 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, "") |