diff options
Diffstat (limited to 'PIL/GifImagePlugin.py')
-rw-r--r-- | PIL/GifImagePlugin.py | 405 |
1 files changed, 405 insertions, 0 deletions
diff --git a/PIL/GifImagePlugin.py b/PIL/GifImagePlugin.py new file mode 100644 index 0000000..cce0fe2 --- /dev/null +++ b/PIL/GifImagePlugin.py @@ -0,0 +1,405 @@ +# +# The Python Imaging Library. +# $Id: GifImagePlugin.py 2134 2004-10-06 08:55:20Z fredrik $ +# +# GIF file handling +# +# History: +# 1995-09-01 fl Created +# 1996-12-14 fl Added interlace support +# 1996-12-30 fl Added animation support +# 1997-01-05 fl Added write support, fixed local colour map bug +# 1997-02-23 fl Make sure to load raster data in getdata() +# 1997-07-05 fl Support external decoder (0.4) +# 1998-07-09 fl Handle all modes when saving (0.5) +# 1998-07-15 fl Renamed offset attribute to avoid name clash +# 2001-04-16 fl Added rewind support (seek to frame 0) (0.6) +# 2001-04-17 fl Added palette optimization (0.7) +# 2002-06-06 fl Added transparency support for save (0.8) +# 2004-02-24 fl Disable interlacing for small images +# +# 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. +# + + +__version__ = "0.9" + + +import Image, ImageFile, ImagePalette + + +# -------------------------------------------------------------------- +# Helpers + +def i16(c): + return ord(c[0]) + (ord(c[1])<<8) + +def o16(i): + return chr(i&255) + chr(i>>8&255) + + +# -------------------------------------------------------------------- +# Identify/read GIF files + +def _accept(prefix): + return prefix[:6] in ["GIF87a", "GIF89a"] + +## +# Image plugin for GIF images. This plugin supports both GIF87 and +# GIF89 images. + +class GifImageFile(ImageFile.ImageFile): + + format = "GIF" + format_description = "Compuserve GIF" + + global_palette = None + + def data(self): + s = self.fp.read(1) + if s and ord(s): + return self.fp.read(ord(s)) + return None + + def _open(self): + + # Screen + s = self.fp.read(13) + if s[:6] not in ["GIF87a", "GIF89a"]: + raise SyntaxError, "not a GIF file" + + self.info["version"] = s[:6] + + self.size = i16(s[6:]), i16(s[8:]) + + self.tile = [] + + flags = ord(s[10]) + + bits = (flags & 7) + 1 + + if flags & 128: + # get global palette + self.info["background"] = ord(s[11]) + # check if palette contains colour indices + p = self.fp.read(3<<bits) + for i in range(0, len(p), 3): + if not (chr(i/3) == p[i] == p[i+1] == p[i+2]): + p = ImagePalette.raw("RGB", p) + self.global_palette = self.palette = p + break + + self.__fp = self.fp # FIXME: hack + self.__rewind = self.fp.tell() + self.seek(0) # get ready to read first frame + + def seek(self, frame): + + if frame == 0: + # rewind + self.__offset = 0 + self.dispose = None + self.__frame = -1 + self.__fp.seek(self.__rewind) + + if frame != self.__frame + 1: + raise ValueError, "cannot seek to frame %d" % frame + self.__frame = frame + + self.tile = [] + + self.fp = self.__fp + if self.__offset: + # backup to last frame + self.fp.seek(self.__offset) + while self.data(): + pass + self.__offset = 0 + + if self.dispose: + self.im = self.dispose + self.dispose = None + + self.palette = self.global_palette + + while 1: + + s = self.fp.read(1) + if not s or s == ";": + break + + elif s == "!": + # + # extensions + # + s = self.fp.read(1) + block = self.data() + if ord(s) == 249: + # + # graphic control extension + # + flags = ord(block[0]) + if flags & 1: + self.info["transparency"] = ord(block[3]) + self.info["duration"] = i16(block[1:3]) * 10 + try: + # disposal methods + if flags & 8: + # replace with background colour + self.dispose = Image.core.fill("P", self.size, + self.info["background"]) + elif flags & 16: + # replace with previous contents + self.dispose = self.im.copy() + except (AttributeError, KeyError): + pass + elif ord(s) == 255: + # + # application extension + # + self.info["extension"] = block, self.fp.tell() + if block[:11] == "NETSCAPE2.0": + self.info["loop"] = 1 # FIXME + while self.data(): + pass + + elif s == ",": + # + # local image + # + s = self.fp.read(9) + + # extent + x0, y0 = i16(s[0:]), i16(s[2:]) + x1, y1 = x0 + i16(s[4:]), y0 + i16(s[6:]) + flags = ord(s[8]) + + interlace = (flags & 64) != 0 + + if flags & 128: + bits = (flags & 7) + 1 + self.palette =\ + ImagePalette.raw("RGB", self.fp.read(3<<bits)) + + # image data + bits = ord(self.fp.read(1)) + self.__offset = self.fp.tell() + self.tile = [("gif", + (x0, y0, x1, y1), + self.__offset, + (bits, interlace))] + break + + else: + pass + # raise IOError, "illegal GIF tag `%x`" % ord(s) + + if not self.tile: + # self.__fp = None + raise EOFError, "no more images in GIF file" + + self.mode = "L" + if self.palette: + self.mode = "P" + + def tell(self): + return self.__frame + + +# -------------------------------------------------------------------- +# Write GIF files + +try: + import _imaging_gif +except ImportError: + _imaging_gif = None + +RAWMODE = { + "1": "L", + "L": "L", + "P": "P", +} + +def _save(im, fp, filename): + + if _imaging_gif: + # call external driver + try: + _imaging_gif.save(im, fp, filename) + return + except IOError: + pass # write uncompressed file + + try: + rawmode = RAWMODE[im.mode] + imOut = im + except KeyError: + # convert on the fly (EXPERIMENTAL -- I'm not sure PIL + # should automatically convert images on save...) + if Image.getmodebase(im.mode) == "RGB": + imOut = im.convert("P") + rawmode = "P" + else: + imOut = im.convert("L") + rawmode = "L" + + # header + for s in getheader(imOut, im.encoderinfo): + fp.write(s) + + flags = 0 + + try: + interlace = im.encoderinfo["interlace"] + except KeyError: + interlace = 1 + + # workaround for @PIL153 + if min(im.size) < 16: + interlace = 0 + + if interlace: + flags = flags | 64 + + try: + transparency = im.encoderinfo["transparency"] + except KeyError: + pass + else: + # transparency extension block + fp.write("!" + + chr(249) + # extension intro + chr(4) + # length + chr(1) + # transparency info present + o16(0) + # duration + chr(int(transparency)) # transparency index + + chr(0)) + + # local image header + fp.write("," + + o16(0) + o16(0) + # bounding box + o16(im.size[0]) + # size + o16(im.size[1]) + + chr(flags) + # flags + chr(8)) # bits + + imOut.encoderconfig = (8, interlace) + + ImageFile._save(imOut, fp, [("gif", (0,0)+im.size, 0, rawmode)]) + + fp.write("\0") # end of image data + + fp.write(";") # end of file + + try: + fp.flush() + except: pass + +def _save_netpbm(im, fp, filename): + + # + # If you need real GIF compression and/or RGB quantization, you + # can use the external NETPBM/PBMPLUS utilities. See comments + # below for information on how to enable this. + + import os + file = im._dump() + if im.mode != "RGB": + os.system("ppmtogif %s >%s" % (file, filename)) + else: + os.system("ppmquant 256 %s | ppmtogif >%s" % (file, filename)) + try: os.unlink(file) + except: pass + + +# -------------------------------------------------------------------- +# GIF utilities + +def getheader(im, info=None): + """Return a list of strings representing a GIF header""" + + optimize = info and info.get("optimize", 0) + + s = [ + "GIF87a" + # magic + o16(im.size[0]) + # size + o16(im.size[1]) + + chr(7 + 128) + # flags: bits + palette + chr(0) + # background + chr(0) # reserved/aspect + ] + + if optimize: + # minimize color palette + i = 0 + maxcolor = 0 + for count in im.histogram(): + if count: + maxcolor = i + i = i + 1 + else: + maxcolor = 256 + + # global palette + if im.mode == "P": + # colour palette + s.append(im.im.getpalette("RGB")[:maxcolor*3]) + else: + # greyscale + for i in range(maxcolor): + s.append(chr(i) * 3) + + return s + +def getdata(im, offset = (0, 0), **params): + """Return a list of strings representing this image. + The first string is a local image header, the rest contains + encoded image data.""" + + class collector: + data = [] + def write(self, data): + self.data.append(data) + + im.load() # make sure raster data is available + + fp = collector() + + try: + im.encoderinfo = params + + # local image header + fp.write("," + + o16(offset[0]) + # offset + o16(offset[1]) + + o16(im.size[0]) + # size + o16(im.size[1]) + + chr(0) + # flags + chr(8)) # bits + + ImageFile._save(im, fp, [("gif", (0,0)+im.size, 0, RAWMODE[im.mode])]) + + fp.write("\0") # end of image data + + finally: + del im.encoderinfo + + return fp.data + + +# -------------------------------------------------------------------- +# Registry + +Image.register_open(GifImageFile.format, GifImageFile, _accept) +Image.register_save(GifImageFile.format, _save) +Image.register_extension(GifImageFile.format, ".gif") +Image.register_mime(GifImageFile.format, "image/gif") + +# +# Uncomment the following line if you wish to use NETPBM/PBMPLUS +# instead of the built-in "uncompressed" GIF encoder + +# Image.register_save(GifImageFile.format, _save_netpbm) |