# 
 | 
# The Python Imaging Library. 
 | 
# $Id$ 
 | 
# 
 | 
# 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": 
 | 
                        block = self.data() 
 | 
                        if len(block) >= 3 and ord(block[0]) == 1: 
 | 
                            self.info["loop"] = i16(block[1:3]) 
 | 
                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) 
 |