[Image-SIG] Update GIF support, again.

Fred L. Drake Fred L. Drake, Jr." <fdrake@acm.org
Tue, 9 Jun 1998 09:15:23 -0400 (EDT)


--uOKuwXZqyz
Content-Type: text/plain; charset=us-ascii
Content-Transfer-Encoding: 7bit


  Here's a new version of the image/gif (document) support; it turns
out I was using an old version of the ImageTk.py file from PIL, and
relied on an attribute that's no longer there.  Please use this
version instead.
  This version still requires the pil_interface.py file from PIL's
Grail/ directory to be importable; I intend to remove this requirement 
in the future (before a new Grail is released, certainly!).  I don't
know when I'll get to support inline animations, but that would be
somewhat useful, I think.  But I won't add that until I also add the
"Stop animations..." and "Stop this animation" operations!  ;-)


  -Fred

--
Fred L. Drake, Jr.	     <fdrake@acm.org>
Corporation for National Research Initiatives
1895 Preston White Dr.	    Reston, VA  20191



--uOKuwXZqyz
Content-Type: text/x-python
Content-Description: image/gif document support for Grail
Content-Disposition: inline;
	filename="image_gif.py"
Content-Transfer-Encoding: 7bit

# Copyright (c) CNRI 1996-1998, licensed under terms and conditions of
# license agreement obtained from handle "hdl:cnri/19980302135001",
# URL "http://grail.cnri.reston.va.us/LICENSE-0.4/", or file "LICENSE".
  
"""image/gif document handling for Grail.

This supports both plain Tk support and PIL-enhanced support.  When PIL is
available and the line

	browser--enable-pil: 1

is located in the ~/.grail/grail-preferences file, PIL will be used and can
support animation of GIF89a files as well as single-frame display.  We still
need a way for the user to STOP the animation!

The files Grail/*.py from the PIL distribution should be installed in the
same directory as this file.
"""
import AsyncImage
import grailutil
import os
import string
import sys
import tempfile
import Tkinter

from formatter import AS_IS

try:
    from cStringIO import StringIO
except ImportError:
    from StringIO import StringIO


ERROR_FILE = os.path.join("icons", "sadsmiley.gif")


class pil_interface:
    """Dummy class to keep us from having to define PILGifParser within a
    try/except construct."""
    pass

try:
    import Image
    import ImageTk
    from pil_interface import pil_interface
except ImportError:
    _use_pil = 0
else:
    _use_pil = 1


class PILGifParser(pil_interface):
    im = None
    currentpos = 0
    duration = 0
    loop = 0

    def close(self):
	if self.buf:
            self.label.config(text="<decoding>")
            self.label.update_idletasks()
            data = string.joinfields(self.buf, "")
            self.buf = None             # free lots of memory!
	    try:
		self.im = im = Image.open(StringIO(data))
		im.load()
		self.tkim = tkim = ImageTk.PhotoImage(im.mode, im.size)
		tkim.paste(im)
	    except:
                # XXX What was I trying to catch here?
                # I think (EOFError, IOError).
		self.broken = 1
		stdout = sys.stdout
		try:
		    sys.stdout = sys.stderr
		    print "Error decoding image:"
		    print str(sys.exc_type) + ":", sys.exc_value
		finally:
		    sys.stdout = stdout
            else:
		self.label.config(image=tkim)
		if im.info.has_key("duration"):
		    self.duration = im.info["duration"]
		if im.info.has_key("loop"):
		    self.duration = self.duration or 100
		    self.loop = im.info["loop"]
                    self.data = data
		if self.duration or self.loop:
		    self.viewer.register_reset_interest(self.cancel_loop)
		    self.after_id = self.label.after(self.duration,
						     self.next_image)
	if self.broken:
	    self.label.image = Tkinter.PhotoImage(
		file=grailutil.which(ERROR_FILE))
	    self.label.config(image = self.label.image)
	    self.viewer.text.insert(Tkinter.END, '\nBroken Image!')

    def next_image(self):
	newpos = self.currentpos + 1
	try:
	    self.im.seek(newpos)
	except (ValueError, EOFError):
            # past end of animation
            if self.loop:
                self.reset_loop()
            else:
                # all done
                self.viewer.unregister_reset_interest(self.cancel_loop)
                return
        else:
            self.currentpos = newpos
            self.tkim.paste(self.im)
        self.after_id = self.label.after(self.duration, self.next_image)

    def reset_loop(self):
        im = Image.open(StringIO(self.data))
        im.load()
        self.tkim.paste(im)
        self.im = im
        self.currentpos = 0

    def cancel_loop(self, *args):
	self.viewer.unregister_reset_interest(self.cancel_loop)
	self.label.after_cancel(self.after_id)


class TkGifParser:
    """Parser for image/gif files.

    Collect all the data on a temp file and then create an in-line
    image from it.
    """

    def __init__(self, viewer, reload=0):
	self.tf = self.tfname = None
	self.viewer = viewer
	self.viewer.new_font((AS_IS, AS_IS, AS_IS, 1))
	self.tfname = tempfile.mktemp()
	self.tf = open(self.tfname, 'wb')
	self.label = Tkinter.Label(self.viewer.text, text=self.tfname,
                                   highlightthickness=0, borderwidth=0)
	self.viewer.add_subwindow(self.label)

    def feed(self, data):
	self.tf.write(data)

    def close(self):
	if self.tf:
	    self.tf.close()
	    self.tf = None
	    self.label.image = Tkinter.PhotoImage(file=self.tfname)
	    self.label.config(image=self.label.image)
	if self.tfname:
	    try:
		os.unlink(self.tfname)
	    except os.error:
		pass


def parse_image_gif(*args, **kw):
    """Create the appropriate image handler, and replace this function with
    the handler for future references (to skip the determination step)."""

    global parse_image_gif
    if _use_pil and AsyncImage.isPILAllowed():
        parse_image_gif = PILGifParser
    else:
        parse_image_gif = TkGifParser
    return apply(parse_image_gif, args, kw)

--uOKuwXZqyz--