Web   ·   Wiki   ·   Activities   ·   Blog   ·   Lists   ·   Chat   ·   Meeting   ·   Bugs   ·   Git   ·   Translate   ·   Archive   ·   People   ·   Donate
summaryrefslogtreecommitdiffstats
path: root/PIL/GifImagePlugin.py
diff options
context:
space:
mode:
Diffstat (limited to 'PIL/GifImagePlugin.py')
-rw-r--r--PIL/GifImagePlugin.py405
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)