text: """ MODULE images2gif
  
  Provides a function (writeGif) to write animated gif from a series
  of PIL images or numpy arrays.
  
  This code is provided as is, and is free to use for all.
  
  Almar Klein (June 2009)
  
  - based on gifmaker (in the scripts folder of the source distribution of PIL)
  - based on gif file structure as provided by wikipedia
  
  """
  
  try:
      import PIL
      from PIL import Image, ImageChops
      from PIL.GifImagePlugin import getheader, getdata
  except ImportError:
      PIL = None
  
  try:
      import numpy as np
  except ImportError:
      np = None
  
  
  # getheader gives a 87a header and a color palette (two elements in a list).
  # getdata()[0] gives the Image Descriptor up to (including) "LZW min code size".
  # getdatas()[1:] is the image data itself in chuncks of 256 bytes (well
  # technically the first byte says how many bytes follow, after which that
  # amount (max 255) follows).
  
  
  def intToBin(i):
      """ Integer to two bytes """
      # devide in two parts (bytes)
      i1 = i % 256
      i2 = int(i / 256)
      # make string (little endian)
      return chr(i1) + chr(i2)
  
  
  def getheaderAnim(im):
      """ Animation header. To replace the getheader()[0] """
      bb = "GIF89a"
      bb += intToBin(im.size[0])
      bb += intToBin(im.size[1])
      bb += "\x87\x00\x00"
      return bb
  
  
  def getAppExt(loops=0):
      """ Application extention. Part that secifies amount of loops.
      if loops is 0, if goes on infinitely.
      """
      bb = "\x21\xFF\x0B"  # application extension
      bb += "NETSCAPE2.0"
      bb += "\x03\x01"
      if loops == 0:
          loops = 2 ** 16 - 1
      bb += intToBin(loops)
      bb += '\x00'  # end
      return bb
  
  
  def getGraphicsControlExt(duration=0.1):
      """ Graphics Control Extension. A sort of header at the start of
      each image. Specifies transparancy and duration. """
      bb = '\x21\xF9\x04'
      bb += '\x08'  # no transparancy
      bb += intToBin(int(duration * 100))  # in 100th of seconds
      bb += '\x00'  # no transparant color
      bb += '\x00'  # end
      return bb
  
  
  def _writeGifToFile(fp, images, durations, loops):
      """ Given a set of images writes the bytes to the specified stream.
      """
  
      # init
      frames = 0
      previous = None
  
      for im in images:
  
          if not previous:
              # first image
  
              # gather data
              palette = getheader(im)[1]
              data = getdata(im)
              imdes, data = data[0], data[1:]
              header = getheaderAnim(im)
              appext = getAppExt(loops)
              graphext = getGraphicsControlExt(durations[0])
  
              # write global header
              fp.write(header)
              fp.write(palette)
              fp.write(appext)
  
              # write image
              fp.write(graphext)
              fp.write(imdes)
              for d in data:
                  fp.write(d)
  
          else:
              # gather info (compress difference)
              data = getdata(im)
              imdes, data = data[0], data[1:]
              graphext = getGraphicsControlExt(durations[frames])
  
              # write image
              fp.write(graphext)
              fp.write(imdes)
              for d in data:
                  fp.write(d)
  
          #             # delta frame - does not seem to work
          #             delta = ImageChops.subtract_modulo(im, previous)
          #             bbox = delta.getbbox()
          #
          #             if bbox:
          #
          #                 # gather info (compress difference)
          #                 data = getdata(im.crop(bbox), offset = bbox[:2])
          #                 imdes, data = data[0], data[1:]
          #                 graphext = getGraphicsControlExt(durations[frames])
          #
          #                 # write image
          #                 fp.write(graphext)
          #                 fp.write(imdes)
          #                 for d in data:
          #                     fp.write(d)
          #
          #             else:
          #                 # FIXME: what should we do in this case?
          #                 pass
  
          # prepare for next round
          previous = im.copy()
          frames = frames + 1
  
      fp.write(";")  # end gif
      return frames
  
  
  def writeGif(filename, images, duration=0.1, loops=0, dither=1):
      """ writeGif(filename, images, duration=0.1, loops=0, dither=1)
      Write an animated gif from the specified images.
      images should be a list of numpy arrays of PIL images.
      Numpy images of type float should have pixels between 0 and 1.
      Numpy images of other types are expected to have values between 0 and 255.
      """
  
      if PIL is None:
          raise RuntimeError("Need PIL to write animated gif files.")
  
      images2 = []
  
      # convert to PIL
      for im in images:
  
          if isinstance(im, Image.Image):
              images2.append(im.convert('P', dither=dither))
  
          elif np and isinstance(im, np.ndarray):
              if im.dtype == np.uint8:
                  pass
              elif im.dtype in [np.float32, np.float64]:
                  im = (im * 255).astype(np.uint8)
              else:
                  im = im.astype(np.uint8)
              # convert
              if len(im.shape) == 3 and im.shape[2] == 3:
                  im = Image.fromarray(im, 'RGB').convert('P', dither=dither)
              elif len(im.shape) == 2:
                  im = Image.fromarray(im, 'L').convert('P', dither=dither)
              else:
                  raise ValueError("Array has invalid shape to be an image.")
              images2.append(im)
  
          else:
              raise ValueError("Unknown image type.")
  
      # check duration
      if hasattr(duration, '__len__'):
          if len(duration) == len(images2):
              durations = [d for d in duration]
          else:
              raise ValueError("len(duration) doesn't match amount of images.")
      else:
          durations = [duration for im in images2]
  
      # open file
      fp = open(filename, 'wb')
  
      # write
      try:
          n = _writeGifToFile(fp, images2, durations, loops)
          print(n, 'frames written')
      finally:
          fp.close()
  
  
  if __name__ == '__main__':
      im = np.zeros((200, 200), dtype=np.uint8)
      im[10:30, :] = 100
      im[:, 80:120] = 255
      im[-50:-40, :] = 50
  
      images = [im * 1.0, im * 0.8, im * 0.6, im * 0.4, im * 0]
      writeGif('lala3.gif', images, duration=0.5, dither=0)
  